← Back to devlog

Step 11: mixed-base / heterozygote calling

This step adds practical mixed-base calling so the viewer can surface likely heterozygous positions instead of only showing single-letter primary calls.

What changed

I added a pure calling engine in src/calling/mixedBase.ts. For each called base, it samples A/C/G/T amplitudes at the peak position, ranks primary vs secondary peaks, computes the secondary/primary ratio, and emits a two-base IUPAC code when the ratio meets the threshold. Calls with invalid peaks or degenerate signal are preserved as-is.

The TraceViewer now keeps a per-slot mixed-base threshold, recomputes calls on load/strand switch/slot restore/threshold changes, and uses the computed sequence everywhere the user sees or exports sequence text (panel + FASTA export).

UI and accessibility

Controls now include a dedicated mixed-base ratio slider with an explicit accessible label, numeric output, and a polite live region that reports the ambiguous-base count. Ambiguous positions are highlighted in both the sequence panel and chromatogram, and exposed through data attributes so E2E assertions can verify real behavior changes without pixel-only checks.

Verification

Vitest now includes exact mixed-base assertions on a synthetic planted-double-peak trace and exact IUPAC calls at known indices on the real 3100.ab1 fixture. Playwright adds threshold-driven E2E checks that confirm ambiguity counts/highlights change and FASTA output includes ambiguity characters at permissive settings.