Systems and subsystems
The routing log and explanation JSON
For every file the runner writes one JSON record: every detector's score, status, sentence and features, the fusion inputs and arithmetic, the routing log, and the version of everything that produced it. It is the explainability deliverable and the frontend's data.
In plain words
The brief asks for explainable results: which techniques said what, and why the file got its number. The record answers that in plain sentences a person can check, one per detector, plus a routing log that says what each rule decided. The eight worked examples on How it decides are these records, printed.
What goes in and comes out
In: the outputs of analyze_clip for one file. Out: results/<basename>.json, and the same object as the API's response.
Where it lives
analyze_clip, detector_entry, fusion_block, routing_log and fusion_line in pipeline.py, lines 537–750.
The record's shape
- Top level:
filename,duration_s,probability_synthetic,verdict,is_speech,default_answer_applied,fusion,detectors,routing_log,flag(empty ordecode_error),seconds,version(pipeline.py, lines 737–750). - Each detector entry:
name,role(fused, evidence, routing, gate),status,score,evidence,features,seconds,error. - The fusion block:
rule,inputs,weights,terms,fused,p_fused,imputed, adetailwith the rule's name, the base rank and whether the Spectra step fired, and the block bounds (pipeline.py, lines 671–674). - The routing log is a list of lines in a fixed order: decode, container, compression, enf, splice, speaker_drift, speech_gate, fusion. The fusion line prints the weighted terms and the Spectra outcome, for example "M3 margin +9.50source: docs/reports/2026-09-26_worked-examples.md, section 4-hgt1046947wav-the-deep-detectors-disagree-drift-flagged-p--0003-z-mean-0686, no suppression (M3 never promotes)" (pipeline.py, lines 574–596).
- The version block names the git commit, every model directory with the M5 checkpoint's hash prefixes, the constants file, the rule, the policy and the polarity, so a record can be traced to the exact build that made it (pipeline.py, lines 730–736).
- The verdict is "undetermined" if the file did not decode or has no speech, else "synthetic" when the pre-map fused probability is at least 0.5source: src/hearsay/pipeline.py line 652 (GitHub; assumes main is public), else "real" (pipeline.py, lines 650–653).
Status
Built; contract version v0. The frontend reads a static dump of these records for the test set under the shipped rule, and the live API returns the same object.
Numbers
None of its own; the record carries the detectors' numbers.
Known limits
The verdict label ignores --flip and uses the pre-map probability; the ranking the metric scores is probability_synthetic. The frontend contract's line reference into the gate module is stale.
Tests that pin it
tests/test_pipeline.py: the response shape and required version keys, the routing-log order and wording, the fusion line lists every weighted term, the verdict rule.
Sources
The frontend contract; the worked examples; the runner report.