mostlyright.weather.earnings.stt
mostlyright.weather.earnings.stt
Section titled “mostlyright.weather.earnings.stt”faster-whisper STT transcriber + alias-aware mention counter (Phase 27, 27-03).
Two surfaces (D-27.5):
SttTranscriber
: Wraps faster-whisper’s WhisperModel (CTranslate2). large-v3 is the
default (the hosted / our-infra source-of-truth model); small is the
on-device earnings.live floor (perfect jargon counts on clean prepared
remarks at ~13% WER, no torch dep — RESEARCH-MARKETS §3.1). transcribe
threads a per-call initial_prompt through to the model
(WhisperModel.transcribe(..., initial_prompt=...)) for vocabulary biasing.
faster_whisper is LAZY-imported inside transcribe (guarded by the
[earnings] extra) so importing this module + the unit tests (which mock
WhisperModel) needs no heavy dep.
seed_initial_prompt / count_mentions
: The term-seeding + alias/phonetic-aware fuzzy counter. seed_initial_prompt
builds the per-call initial_prompt from the live market’s
custom_strike target terms (exploding slash-synonyms). count_mentions
counts a term’s mentions in a transcript, NEVER by exact string equality
(RESEARCH-MARKETS §3.3): it explodes slash-synonyms + English plural/possessive
forms (NOT verb-tense — the Kalshi rule, §2.1(3)) and matches the acronym
plural/possessive (APIs/API's). Spelled-out separator matching
(O-C-I / O.C.I / O C I) was DROPPED as unsafe — it false-positived
on short acronyms and longer spelled sequences (A I inside A I R);
acronym emission relies on the per-call initial_prompt biasing instead.
Known ASR mis-renders (OCI ↔ OCR/OCIP) are
OPT-IN via asr_misrenders=True — OFF by default so OCR (a legitimate
standalone term) is not a false-positive over-count. It returns
(mention_count, [matched_surface_form, ...]) — the integer is the primary
tally (boolean is derived as count >= 1); the surface forms are the
auditable spoken strings that feed schema.earnings_fact.v1.
This module does NOT settle a fact — role-anchoring + the fail-closed Kalshi filter is the 27-04 role parser’s job.
Functions
Section titled “Functions”classify_mentions(transcript, term, *[, …]) | Classify EACH occurrence of term in transcript by compound type. |
|---|---|
count_mentions(transcript, term, *[, …]) | Count term’s mentions in transcript (alias/phonetic-aware). |
seed_initial_prompt(custom_strike_terms) | Build a per-call faster-whisper initial_prompt from target terms. |
Classes
Section titled “Classes”SttTranscriber([model_size, device, …]) | faster-whisper (CTranslate2) transcriber (D-27.5). |
|---|---|
TranscriptResult(text[, segments, language, …]) | The joined transcript text + the per-segment records + model info. |
class mostlyright.weather.earnings.stt.SttTranscriber(model_size=‘large-v3’, , device=‘cpu’, compute_type=‘int8’)
Section titled “class mostlyright.weather.earnings.stt.SttTranscriber(model_size=‘large-v3’, , device=‘cpu’, compute_type=‘int8’)”Bases: object
faster-whisper (CTranslate2) transcriber (D-27.5).
model_size selects the tier: large-v3 default (hosted / our-infra
source-of-truth); small for the on-device earnings.live floor.
device / compute_type mirror faster-whisper’s constructor (CPU int8
is the keyless/offline default). The WhisperModel is constructed lazily
on first transcribe so importing this class needs no [earnings] extra.
transcribe(audio_path, , initial_prompt=None, target_terms=())
Section titled “transcribe(audio_path, , initial_prompt=None, target_terms=())”Transcribe audio_path via faster-whisper.
Threads initial_prompt straight through to
WhisperModel.transcribe(..., initial_prompt=...) for per-call
vocabulary biasing. If initial_prompt is None but target_terms
are given, the prompt is seeded from them via seed_initial_prompt().
Returns a TranscriptResult carrying the joined text (feeds
count_mentions()) plus the per-segment records.
- Return type:
TranscriptResult - Parameters:
class mostlyright.weather.earnings.stt.TranscriptResult(text, segments=, language=None, duration=None)
Section titled “class mostlyright.weather.earnings.stt.TranscriptResult(text, segments=, language=None, duration=None)”Bases: object
The joined transcript text + the per-segment records + model info.
- Parameters:
duration : float | None
Section titled “duration : float | None”language : str | None
Section titled “language : str | None”segments : list[dict[str, Any]]
Section titled “segments : list[dict[str, Any]]”mostlyright.weather.earnings.stt.classify_mentions(transcript, term, , match_rule=‘plural_possessive_ok_no_tense’, asr_misrenders=False)
Section titled “mostlyright.weather.earnings.stt.classify_mentions(transcript, term, , match_rule=‘plural_possessive_ok_no_tense’, asr_misrenders=False)”Classify EACH occurrence of term in transcript by compound type.
A SIBLING of count_mentions() (which is unchanged): returns one record
per occurrence, each {"surface", "start", "compound_type"} where
compound_type is one of standalone / open / hyphenated /
closed / affix_derivation (D-30 §2.1(3) cross-venue compound
divergence). It reuses the SAME form expansion + apostrophe + plural/
possessive machinery as count_mentions() (_match_forms_for_term +
_form_to_pattern) — never bare exact equality — so a possessive
(tariff's) is a standalone occurrence, not a miss.
The word-boundary pass finds standalone / open / hyphenated
occurrences exactly as count_mentions counts them (post-D-30 the hyphen
guard is gone, so pre-tariff is a real occurrence tagged hyphenated).
An ADDITIONAL substring pass finds closed candidates — a surface form
FUSED inside a longer word component where the term stays a distinct part
(wildfire, killjoy, and the wildfire component of
wildfire-related — R3-F1: hyphenated tokens are split and each component
scanned) — and separates true CLOSED compounds from affix_derivation
roots (joyful, incl. inside hyphenated tokens: joyful-sounding) via
a curated stdlib suffix heuristic (_closed_or_affix(); no dictionary
dep — D-30 decision 4). The heuristic is CONSERVATIVE: an ambiguous case is
a closed candidate that surfaces to a human reviewer, NEVER a silent
drop.
Overlap handling matches count_mentions: longest-first, and a span is
classified once (the word-boundary occurrences win; a closed substring pass
skips any span already covered so pre-tariff is not double-counted as both
hyphenated and closed).
Raises ValueError for an unrecognized match_rule or a degenerate
term — IDENTICALLY to count_mentions() (review F2, shared
_validated_forms_for_term() path): silently returning [] would
settle “not mentioned” on a config bug.
mostlyright.weather.earnings.stt.count_mentions(transcript, term, , match_rule=‘plural_possessive_ok_no_tense’, asr_misrenders=False)
Section titled “mostlyright.weather.earnings.stt.count_mentions(transcript, term, , match_rule=‘plural_possessive_ok_no_tense’, asr_misrenders=False)”Count term’s mentions in transcript (alias/phonetic-aware).
Returns (mention_count, matched_surface_forms). The integer is the
PRIMARY tally that earnings-mention markets settle on; the boolean (“said
= 1”) is DERIVED by the caller as
count >= 1.matched_surface_formsare the actually-spoken strings (verbatim, in order) so each occurrence is auditable against each venue’s stricter/looser rule (feedsschema.earnings_fact.v1.matched_surface_form).
Matching is NEVER bare exact string equality (RESEARCH-MARKETS §3.3): slash-
synonyms, English plural/possessive forms (under
plural_possessive_ok_no_tense — company/companies/company's,
data center/data centers), and acronym plural/possessives
(APIs/API's) all count. HYPHENATED compounds count for the bare term on
BOTH venues (pre-tariff / tariff-based / non-fat / pro-Palestine
count for tariff / fat / Palestine — D-30; verbatim Kalshi +
Polymarket PDFs cited in _form_to_pattern()). Verb-tense inflections
(tariffed) do NOT. A CLOSED (unhyphenated) compound (wildfire for
fire) does NOT count via count_mentions — the closed-compound
cross-venue divergence is tagged per-occurrence by classify_mentions().
Spelled-out separator forms (O-C-I / O.C.I / O C I) are NOT matched
— they were dropped as unsafe (false-positives on short acronyms / longer
spelled sequences); acronym emission relies on initial_prompt biasing.
asr_misrenders (default OFF) opts in to the known STT mis-render aliases
(OCR/OCIP for OCI). Leave it OFF unless the caller KNOWS the
engine mis-rendered the target acronym — OCR is a legitimate standalone
term, so aliasing it unconditionally over-counts.
Raises ValueError for an unrecognized match_rule (only
plural_possessive_ok_no_tense and exact are accepted), an empty
term, a term that expands to NO surface forms (a bare synonym
separator " / "), or a term whose every surface form is word-character-
free (a lone punctuation char "/" / "." / "-" / "&", which
survives as its own form but can never match a word-boundary-anchored pattern).
A typo’d rule must fail loud, NOT silently fall through to exact semantics
and quietly change the settlement tally; a term that counts 0 for EVERY
transcript (a market would settle “not mentioned” on a config bug) must fail
loud too.
KNOWN LIMITATION (inherent surface ambiguity, not a bug): a noun whose regular
plural is spelled identically to its present-tense verb (cost -> costs,
guide -> guides, result -> results) will count a VERB-only
usage ("it costs a lot") as a plural-noun mention. The no_tense rule
excludes +ed / +ing verb inflections, but a plural-noun / present-tense
homograph is indistinguishable without part-of-speech tagging (out of scope);
the plural surface is accepted because the NOUN plural is the far more common,
settlement-relevant usage in prepared remarks.
mostlyright.weather.earnings.stt.seed_initial_prompt(custom_strike_terms)
Section titled “mostlyright.weather.earnings.stt.seed_initial_prompt(custom_strike_terms)”Build a per-call faster-whisper initial_prompt from target terms.
Vocabulary biasing (RESEARCH-MARKETS §2.2): seeding the live market’s
custom_strike words into the prompt makes the model far likelier to emit
the exact jargon token (mitigates the single-utterance rare-acronym miss
class). Slash-synonyms are exploded so each alternative surface form is a
distinct seeded token. Returns "" for an empty term list.