mostlyright.finance.transcripts.stt
faster-whisper STT transcriber + alias-aware mention counter.
Two parts:
SttTranscriber
: Wraps faster-whisper’s WhisperModel (CTranslate2). large-v3 is the
default and the model the hosted path runs; small is the on-device
earnings.live floor, which counts jargon correctly on clean prepared
remarks at roughly 13% WER with no torch dependency. transcribe threads a
per-call initial_prompt through to the model
(WhisperModel.transcribe(..., initial_prompt=...)) for vocabulary biasing.
Every segment record carries the engine’s decode statistics (avg_logprob,
no_speech_prob, compression_ratio, temperature) and the result
carries language_probability; the decode computes all of them anyway.
Per-word {word, start, end, probability} records are opt-in via
transcribe(..., word_timestamps=True), because alignment is a second pass
and slows the decode. faster_whisper is lazy-imported inside
transcribe and guarded by the [earnings] extra, so importing this
module and running the unit tests, which mock WhisperModel, needs no heavy
dependency.
seed_initial_prompt / count_mentions
: Term seeding plus the alias-aware mention 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: it
explodes slash-synonyms and English plural/possessive forms — not verb tense,
per the Kalshi contract terms — and matches the acronym plural and possessive
(APIs, API's). Spelled-out separator matching (O-C-I / O.C.I /
O C I) is not performed: it false-positived on short acronyms and longer
spelled sequences (A I inside A I R), so acronym emission relies on
the per-call initial_prompt biasing instead. Known ASR mis-renders (OCI
rendered as OCR or OCIP) are opt-in via asr_misrenders=True, off by
default so OCR, a legitimate standalone term, does not over-count. It
returns (mention_count, [matched_surface_form, ...]): the integer is the
primary tally, from which the boolean is derived as count >= 1, and the
surface forms are the auditable spoken strings that feed
schema.finance.fact.v1.
This module does not settle a fact. Role anchoring and the fail-closed Kalshi filter are the role parser’s job.
Functions
Section titled “Functions”| Function | Description |
|---|---|
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”| Class | Description |
|---|---|
SttTranscriber([model_size, device, …]) | faster-whisper (CTranslate2) transcriber. |
TranscriptResult(text[, segments, language, …]) | The joined transcript text + the per-segment records + model info. |
class mostlyright.finance.transcripts.stt.SttTranscriber(model_size=‘large-v3’, , device=‘cpu’, compute_type=‘int8’)
Section titled “class mostlyright.finance.transcripts.stt.SttTranscriber(model_size=‘large-v3’, , device=‘cpu’, compute_type=‘int8’)”Bases: object
faster-whisper (CTranslate2) transcriber.
model_size selects the tier: large-v3 default (the model the hosted
path runs); 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=(), word_timestamps=False)
Section titled “transcribe(audio_path, , initial_prompt=None, target_terms=(), word_timestamps=False)”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.
Each segment record carries text / start / end, the decode
statistics the engine computes anyway (_SEGMENT_STAT_FIELDS —
free confidence signal, always present, None when the engine does not
report one), and words.
word_timestamps is opt-in (default off) because word-level alignment
runs a second pass over each segment and measurably slows the decode. Off
(the default), the flag is not passed to the engine at all and every
segment’s words is None (“never
asked”). On, each segment’s words is a list of
{word, start, end, probability} records ([] when the engine
aligned no words for that segment).
- Return type:
TranscriptResult - Parameters:
class mostlyright.finance.transcripts.stt.TranscriptResult(text, segments=, language=None, duration=None, language_probability=None)
Section titled “class mostlyright.finance.transcripts.stt.TranscriptResult(text, segments=, language=None, duration=None, language_probability=None)”Bases: object
The joined transcript text + the per-segment records + model info.
language_probability is the engine’s confidence in the detected
language (None when the engine does not report one). Fields are
append-only — a consumer constructing this positionally keeps working.
- Parameters:
duration: float | None
Section titled “duration: float | None”language: str | None
Section titled “language: str | None”language_probability: float | None
Section titled “language_probability: float | None”segments: list[dict[str, Any]]
Section titled “segments: list[dict[str, Any]]”mostlyright.finance.transcripts.stt.classify_mentions(transcript, term, , match_rule=‘plural_possessive_ok_no_tense’, asr_misrenders=False)
Section titled “mostlyright.finance.transcripts.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(): returns one record
per occurrence, each {"surface", "start", "compound_type"} where
compound_type is one of standalone / open / hyphenated /
closed / affix_derivation (the 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; pre-tariff is a
real occurrence, tagged hyphenated.
A second, 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 — 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
dependency). The heuristic is conservative: an ambiguous case becomes
a closed candidate for 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() (shared
_validated_forms_for_term() path): silently returning [] would
settle “not mentioned” on a config bug.
mostlyright.finance.transcripts.stt.count_mentions(transcript, term, , match_rule=‘plural_possessive_ok_no_tense’, asr_misrenders=False)
Section titled “mostlyright.finance.transcripts.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 at least
once”) is derived by the caller as count >= 1. matched_surface_forms
are the actually spoken strings, verbatim and in order, so each occurrence is
auditable against each venue’s stricter or looser rule; they feed
schema.finance.fact.v1.matched_surface_form.
Matching is never bare exact string equality: slash-synonyms, English plural
and possessive forms (under plural_possessive_ok_no_tense —
company/companies/company's, data center/data centers),
and acronym plurals and possessives (APIs, API's) all count.
Hyphenated compounds count for the bare term on both venues (pre-tariff,
tariff-based, non-fat, and pro-Palestine count for tariff,
fat, and Palestine; the Kalshi and Polymarket PDFs are quoted verbatim
in _form_to_pattern()). Verb-tense inflections (tariffed) do not
count. 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, because they
false-positive on short acronyms and longer spelled sequences; acronym
emission relies on initial_prompt biasing instead.
asr_misrenders, off by default, opts in to the known STT mis-render
aliases (OCR and 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 fails loud rather than silently falling through to exact
semantics and changing the settlement tally, and so does a term that would
count 0 for every transcript and settle a market “not mentioned” on a config
bug.
Known limitation, inherent to matching on spelling: a noun whose regular
plural is spelled identically to its present-tense verb (cost ->
costs, guide -> guides, result -> results) counts a
verb-only usage (“it costs a lot”) as a plural-noun mention. The no_tense
rule excludes +ed and +ing verb inflections, but a plural-noun and
present-tense homograph is indistinguishable without part-of-speech tagging.
The plural surface is accepted because the noun plural is the far more common,
settlement-relevant usage in prepared remarks.
mostlyright.finance.transcripts.stt.seed_initial_prompt(custom_strike_terms)
Section titled “mostlyright.finance.transcripts.stt.seed_initial_prompt(custom_strike_terms)”Build a per-call faster-whisper initial_prompt from target terms.
Vocabulary biasing: seeding the live market’s custom_strike words into the
prompt makes the model far likelier to emit the exact jargon token, which
mitigates the single-utterance rare-acronym miss. Slash-synonyms are exploded
so each alternative surface form is a distinct seeded token. Returns ""
for an empty term list.