YazSes v0.3.0 — Evaluation Plan¶
1. Primary Metrics¶
One primary metric is defined per capability cluster (cap-001 through cap-005). Each metric has a formal definition, a measurement method, and a pass/fail threshold derived from the PRD NFRs.
cap-001 — SSH/Remote Voice Forwarding¶
Metric: End-to-end text injection latency over SSH tunnel.
Formal definition: T_inject = t_char_appears_remote − t_hotkey_release, measured in milliseconds. t_hotkey_release is the UNIX timestamp at which the hold-to-talk hotkey on_hold_end event fires on the local machine. t_char_appears_remote is the timestamp logged by the remote agent immediately after InjectorBackend.inject(text) returns on the remote machine.
Measurement method: Integration test. Local machine sends a known 10-character string through a containerised SSH tunnel to a remote agent mock. The mock logs timestamps. Median and 99th-percentile latencies are computed over 50 injections. Two network conditions are simulated: LAN (netem 1 ms RTT) and WAN-equivalent (netem 100 ms RTT).
Pass threshold: - Median latency ≤ 500 ms on LAN (NFR-001.1). - Median latency ≤ 800 ms on WAN (NFR-001.1). - 99th-percentile ≤ 1000 ms on WAN (graceful bound; no NFR defined).
cap-002 — Streaming Transcription with Real-Time Display and Correction¶
Metric A: Time-to-first-partial (TTFP) — the delay between speech onset and the first stable partial hypothesis appearing in the active window.
Formal definition: TTFP = t_first_inject − t_speech_onset, where t_speech_onset is the sample index at which RMS exceeds vad_threshold, converted to wall-clock time, and t_first_inject is the timestamp at which StreamingInjector.inject_partial() is first called with a non-empty string.
Measurement method: Unit test using pre-recorded audio fixtures (3 s clean speech, 16 kHz, float32). The StreamingEngine is run with a mock injector that records call timestamps. TTFP is computed across 20 fixture utterances.
Pass threshold: Median TTFP ≤ 600 ms (NFR-002.1). [EVIDENCE src-002, src-003]
Metric B: Correction operation latency — the time from commit(final_text) call to the last keypress event completing in the mock injector.
Formal definition: T_correction = t_last_keypress − t_commit_call, measured in milliseconds.
Measurement method: Unit test. StreamingInjector.commit() is called with _chars_injected = 50 (representative median). Mock injector records event timestamps. Test is run 100 times.
Pass threshold: Median T_correction ≤ 200 ms (NFR-002.2). [EVIDENCE src-004]
cap-003 — Code Command Grammar¶
Metric: Command recognition precision and false-positive rate on a labelled phrase set.
Formal definition (precision): P = TP / (TP + FP), where TP is the count of command phrases correctly classified to their intended intent, and FP is the count of dictation-only phrases incorrectly classified as a command intent.
Formal definition (false-positive rate): FPR = FP_dictation / N_dictation, where N_dictation = 500 words in dictation corpus segmented into utterance-length phrases.
Measurement method: Parametrised pytest fixture. Two test sets (see §3). grammar.classify() is called with each phrase. Results compared against ground-truth labels.
Pass threshold: - Precision ≥ 90% on the 50-command phrase set (NFR-003.1). - Zero false-positive commands on the 500-word dictation corpus (NFR-003.2).
cap-004 — Offline Disfluency Filter¶
Metric: Rule-path runtime per transcript and filter correctness.
Formal definition (runtime): Median wall-clock execution time of DisfluencyFilter.filter_transcript(text, config) across a 100-transcript corpus.
Formal definition (correctness): F_correct = (transcripts where output matches expected_clean) / 100. Expected clean output is determined by a human-authored reference for each transcript.
Measurement method: Pytest benchmark. Each of the 100 synthetic transcripts is passed through filter_transcript(). time.perf_counter() wraps each call. Correctness is checked against stored expected outputs.
Pass threshold: - Median runtime ≤ 10 ms per transcript (NFR-004.1). [EVIDENCE src-012] - Correctness ≥ 95 / 100 on the synthetic corpus (authorial threshold; NFR for correctness is implicit in the PRD's "must not alter proper nouns or code identifiers" constraint).
cap-005 — Accessibility Profile¶
Metric: Enrollment wizard completion time and VAD parameter accuracy.
Formal definition (wizard time): T_enroll = t_wizard_complete − t_wizard_start, in minutes of user-interactive time (excluding audio recording processing time).
Formal definition (VAD accuracy): After enrollment, the derived vad_threshold must correctly classify ≥ 95% of voiced frames as speech and ≥ 95% of silent frames as silence on the calibration utterances.
Measurement method: End-to-end test using synthetic audio fixtures for 20 calibration utterances. Enrollment wizard is run non-interactively using a fixture driver. T_enroll is measured excluding I/O prompts (interactive time proxy). VAD accuracy is computed on a held-out 30-utterance fixture set.
Pass threshold: - T_enroll ≤ 10 minutes of user time (NFR-005.1). - VAD frame classification accuracy ≥ 95% on held-out fixtures. - All accessibility settings accessible via CLI without GUI (NFR-005.2). [EVIDENCE src-008]
2. Baseline System¶
All v0.3.0 metrics are compared against YazSes v0.2.4 — the current production release.
| Baseline property | Value |
|---|---|
| Version | v0.2.4 |
| ASR backend | faster-whisper 1.2.1, CPU int8, tiny.en |
| Streaming | Disabled (not implemented) |
| SSH remote | Not supported |
| Command grammar | Not implemented |
| Disfluency filter | Not implemented |
| Accessibility options | Model selection only (no enrollment, no evdev_device, no silence/padding config) |
| Hotkey latency | Measured as t_inject − t_hotkey_release on local machine |
| WER on clean speech | Measured on LibriSpeech test-clean subset (50 utterances, tiny.en) |
The baseline WER and hotkey latency measurements are collected fresh from v0.2.4 before the v0.3.0 implementation begins, and stored in tests/fixtures/baseline_metrics.json. These values are used in §5 (Regression Tests).
3. Test Dataset Construction¶
cap-001: SSH Integration Test Corpus¶
Construction method: A Docker Compose environment (tests/integration/docker/ssh/) runs a minimal OpenSSH server (Alpine Linux image) in a container. The local test harness:
- Starts the container with
AllowTcpForwarding yesinsshd_config. - Generates a throwaway RSA keypair for the test session.
- Invokes
yazses remote --host test@localhost --port 2222via the test harness. - Injects 50 text strings of varying length (5, 10, 20, 50, 100 characters) into the tunnel.
- The remote agent mock inside the container logs receipt timestamps.
Network simulation: tc netem is used to apply controlled delay: LAN = 1 ms RTT, WAN = 100 ms RTT.
Dataset size: 50 injections × 2 network conditions = 100 latency data points.
Ground truth: Exact text received by remote agent mock must match text sent by local daemon. Any mismatch is a data-integrity failure (separate from latency).
cap-002: Streaming Latency Test Corpus¶
Construction method: 20 audio fixture files (WAV, 16 kHz, float32, 3–8 s duration) are selected from LibriSpeech test-clean (first 20 utterances of speaker 1089). These fixtures are committed to tests/fixtures/streaming/.
t_speech_onsetfor each fixture is annotated by a one-time script that applies the default RMS VAD to identify the first voiced frame. Annotations are stored intests/fixtures/streaming/onsets.json.- The
StreamingEngineruns against each fixture with a mock injector. Partial hypothesis timestamps and character counts are recorded. TTFPandT_correctionare computed from logged timestamps.
Coverage: Fixtures include fast speech (6+ words/s), slow speech (2 words/s), and utterances with initial pause (simulating delayed phonation onset). [EVIDENCE src-002]
cap-003: Command Recognition Test Corpus¶
Construction method: Two independent phrase sets, authored manually and stored in tests/fixtures/commands/:
Set A — command phrases (50 items): One phrase per unique action in FR-003.3's command table, plus variants covering ordinal words ("one"/"1", "two"/"2", "three"/"3") and optional tokens ("undo" / "undo that" / "undo 3 times"). Each phrase is labelled with its expected IntentType and action.
Set B — dictation corpus (500 words): A 500-word passage of natural English prose (first 500 words of a public-domain text), segmented into 25 utterance-length phrases of ~20 words each. No intentional command phrases. Expected classification for every phrase: IntentType.DICTATE.
Ground truth: Authored labels are considered ground truth. No ASR inference is involved; grammar.classify() receives text strings directly.
cap-004: Disfluency Filter Corpus¶
Construction method: 100 synthetic transcripts authored in tests/fixtures/disfluency/corpus.json. Each entry has three fields: input (raw transcript with known fillers/repetitions/triggers), expected (human-authored clean output), and rule_path (which of the three filter rules — filler removal, repetition dedup, self-correction — should fire).
Distribution across rule paths: - 40 transcripts: filler-word removal only (rules A). - 30 transcripts: repetition deduplication (rule B). - 20 transcripts: self-correction trigger (rule C). - 10 transcripts: combined (rules A+B or A+C).
Each transcript also includes a "no-change" control section: a proper noun, a code identifier (e.g., FasterWhisperEngine), and a quoted string. These must be preserved verbatim to validate NFR-004.2. [EVIDENCE src-012]
cap-005: Accommodation Setting Test Corpus¶
Construction method: Five test scenarios in tests/fixtures/accessibility/:
-
Footpedal evdev simulation: A mock evdev device node is injected into the Linux hotkey backend via dependency injection (replacing the real
evdev.InputDeviceconstructor with a mock that replays a hold-press event sequence). The test confirms the daemon entersRECORDINGstate on the mock hold event. -
Silence threshold test: Audio fixture with 600 ms of trailing silence. The default
min_silence_ms = 500must stop recording within 600±50 ms of the silence onset. An extendedmin_silence_ms = 1500must continue recording through the 600 ms silence. -
Pre-speech padding test: Audio fixture with 300 ms of silence followed by 2 s of speech. With
pre_speech_padding_ms = 400, the audio array passed to the STT engine must begin 400 ms before the VAD-detected speech onset. -
Model selection test: ASR engine initialisation is mocked. The test confirms that setting
stt.model = "base.en"in config causesWhisperModel("base.en", ...)to be called with the correct model identifier. -
Enrollment wizard fixture test: 20 synthetic calibration utterances (WAV files) with known RMS statistics. The enrollment wizard must produce a
vad_thresholdwithin ±15% of the analytically computed optimal threshold. [EVIDENCE src-008]
4. Pass/Fail Thresholds¶
| Metric ID | Capability | Metric | Threshold | NFR Source |
|---|---|---|---|---|
| M-001-LAN | cap-001 | SSH injection latency, LAN | ≤ 500 ms median | NFR-001.1 |
| M-001-WAN | cap-001 | SSH injection latency, WAN | ≤ 800 ms median | NFR-001.1 |
| M-001-INT | cap-001 | Data integrity through tunnel | 100% exact match | FR-001.3 |
| M-002-TTFP | cap-002 | Time-to-first-partial | ≤ 600 ms median | NFR-002.1 |
| M-002-COR | cap-002 | Correction operation latency | ≤ 200 ms median | NFR-002.2 |
| M-002-CANCEL | cap-002 | No partial text after cancel | 0 chars remaining | NFR-002.3 |
| M-003-PREC | cap-003 | Command recognition precision | ≥ 90% on 50-item set | NFR-003.1 |
| M-003-FPR | cap-003 | False-positive command rate | 0 on 500-word corpus | NFR-003.2 |
| M-003-RT | cap-003 | Grammar classifier runtime | ≤ 5 ms per classify() call | FR-003.2 |
| M-004-RT | cap-004 | Disfluency filter runtime | ≤ 10 ms per transcript | NFR-004.1 |
| M-004-CORR | cap-004 | Filter correctness | ≥ 95 / 100 transcripts | NFR-004.2 |
| M-004-PNID | cap-004 | Proper noun / identifier preservation | 100% unmodified | NFR-004.2 |
| M-005-ENR | cap-005 | Enrollment wizard time | ≤ 10 minutes user time | NFR-005.1 |
| M-005-VAD | cap-005 | VAD accuracy post-enrollment | ≥ 95% frame classification | derived from cap-005 |
| M-005-CLI | cap-005 | Accessibility settings via CLI | All settings reachable | NFR-005.2 |
5. Regression Tests¶
The following baseline measurements from v0.2.4 must not degrade in v0.3.0.
R-001: WER on Clean Speech¶
Measurement: Word Error Rate of FasterWhisperEngine on the first 50 utterances of LibriSpeech test-clean with tiny.en model, CPU int8.
Baseline value: Stored in tests/fixtures/baseline_metrics.json as wer_librispeech_test_clean_tiny_en.
Pass threshold: v0.3.0 WER ≤ baseline WER + 0.5 percentage points. A tolerance of 0.5 pp accounts for minor environment differences across test runs. [EVIDENCE src-001, src-009]
Rationale: cap-002 (streaming), cap-004 (disfluency filter), and cap-005 (VAD changes) all touch the audio-to-text pipeline. None should alter the core Whisper model output. If WER increases, it indicates an unintended modification to the STT path.
R-002: Hotkey Detection Latency¶
Measurement: Time from hardware key event to daemon._on_hold_start() being called, measured in the Linux evdev path using a synthetic evdev input fixture.
Baseline value: Stored in tests/fixtures/baseline_metrics.json as hotkey_latency_ms_p99.
Pass threshold: v0.3.0 99th-percentile hotkey latency ≤ baseline + 10 ms. The 10 ms allowance accounts for timer jitter in CI.
Rationale: cap-005 adds the evdev_device config path to the Linux hotkey backend. The default path must not be slower.
R-003: Config Load and Schema Validation¶
Measurement: Config.load() must succeed on a minimal v0.2.4-era config.toml (without any v0.3.0 keys) without raising an exception, and must produce safe defaults for all new v0.3.0 config fields.
Pass threshold: Zero exceptions; all new fields have expected default values.
Rationale: The v0.3.0 config schema adds 5 new dataclasses with default values. Existing user configs must remain valid (no breaking schema changes).
R-004: IPC Protocol Backward Compatibility¶
Measurement: The existing test_ipc_protocol.py suite (covering JSON-RPC 2.0 framing, status, shutdown, and inject methods) must pass without modification.
Pass threshold: All existing IPC tests pass. No new required fields in existing RPC method schemas.
6. Test Tooling¶
pytest Structure¶
tests/
__init__.py
# Existing tests (must continue passing — R-003, R-004)
test_auto_inject.py
test_cleaner.py
test_config.py
test_hold_detector.py
test_ipc_protocol.py
test_platform_factory.py
# New v0.3.0 unit tests
test_disfluency_filter.py # cap-004: 100-corpus parametrised test
test_grammar_classifier.py # cap-003: 50+500-phrase parametrised test
test_streaming_engine.py # cap-002: TTFP and correction latency
test_streaming_injector.py # cap-002: _chars_injected state machine
test_accessibility_enroll.py # cap-005: enrollment wizard with fixtures
test_vad_calibrated.py # cap-005: threshold derivation
test_audio_padding.py # cap-005: pre-speech ring buffer
test_remote_agent.py # cap-001: agent JSON-RPC mock
# Integration tests
integration/
test_ssh_tunnel.py # cap-001: Docker Compose SSH tunnel
Key Test Fixtures¶
tests/fixtures/
streaming/
*.wav # 20 LibriSpeech utterances
onsets.json # speech onset annotations
commands/
command_phrases.json # 50 labelled command phrases
dictation_corpus.txt # 500-word prose passage
disfluency/
corpus.json # 100 synthetic transcripts with expected output
accessibility/
calibration_utterances/ # 20 synthetic calibration WAV files
rms_stats.json # expected RMS statistics for fixture utterances
baseline_metrics.json # v0.2.4 baseline WER and latency values
Fixture Design Principles¶
- All audio fixtures are committed as WAV files (16 kHz, 16-bit, mono) to keep the test suite self-contained.
- No real microphone, no
sounddevicecalls in unit tests. TheAudioRecorderis replaced by a fixture-reading mock viapytest-mock. - No real SSH server in unit tests. The
RemoteForwarderandremote/agent.pyare tested with mock socket connections. - Integration tests (Docker SSH) are gated behind the
--integrationpytest marker and skipped in standard CI runs to avoid Docker-in-Docker complexity on Windows and macOS CI runners.
CI Configuration¶
The existing .github/workflows/test.yml runs uv run pytest tests/ -v on Python 3.11 and 3.12 across Linux, macOS, and Windows. For v0.3.0, the following changes are made:
- A
pytest.ini_options.markersentry is added forintegrationto suppress the SSH Docker test on non-Linux CI runners. - The Linux CI job gains a step to build the Docker SSH container before running integration tests:
docker compose -f tests/integration/docker/docker-compose.yml up -d. pytest-benchmarkis added to the dev dependency group for the runtime measurements in M-003-RT and M-004-RT.