fix: settle silent-only cases to Empty oneliner state
Introduce `TranscriptState { Pending, Silent, Content }` in
doctate-common as the canonical three-way state of a recording's
`.transcript.txt` sidecar. Previously each call-site projected the
raw `Option<String>` / `.exists()` onto its own 2-state view and the
projections disagreed: the UI treated a 0-byte silent transcript like
`Content` while the oneliner worker treated it like `Pending`,
leaving silent-only cases stuck on "generiere Titel …" forever with
no persisted oneliner.json.
`update_oneliner` now settles a silent-only case to
`OnelinerState::Empty` when no `Content` transcript exists and no
recording is still `Pending`, so the UI resolves to "unbenannt" and
recovery treats it as terminal.
Single reader: `paths::read_transcript_state`. All call-sites
(scan_recordings, compute_oneliner_display, has_pending_recordings,
all_transcripts_joined, read_recordings, enqueue_pending_for_user,
scan_m4as, cases_needing_oneliner_in, case_recordings.html) go
through the same typed abstraction and must handle `Silent` via
exhaustive match.
Regression tests:
- silent_case_empty_test: heal path settles silent-only case to
Empty without calling Ollama
- case_page_silent_only_shows_empty_not_generating: UI renders
"unbenannt", not "generiere Titel …"
This commit is contained in:
@@ -338,6 +338,45 @@ async fn case_page_uses_oneliner_as_h1_when_present() {
|
||||
);
|
||||
}
|
||||
|
||||
/// A case with only silent recordings (empty 0-byte transcript) and a
|
||||
/// persisted `OnelinerState::Empty` must render the "unbenannt" variant
|
||||
/// of the oneliner, never "generiere Titel …". Regression guard for
|
||||
/// the bug where the UI's `compute_oneliner_display` collapsed
|
||||
/// `TranscriptState::Silent` onto "transcribed" and then chose
|
||||
/// `Generating` because no state was persisted — the combination left
|
||||
/// cases stuck on "generiere Titel …" forever.
|
||||
#[tokio::test]
|
||||
async fn case_page_silent_only_shows_empty_not_generating() {
|
||||
let config = config_with_llm(unique_tmp("cp-silent"));
|
||||
let case_id = "33333333-3333-3333-3333-333333333333";
|
||||
let case_dir = seed_case(&config.data_path, "dr_a", case_id);
|
||||
// `Some("")` produces a 0-byte transcript sidecar — the whisper-as-
|
||||
// silence output. Combined with `OnelinerState::Empty`, this is the
|
||||
// terminal shape of a silent-only case after the fix.
|
||||
seed_recording(&case_dir, "10-00-00", Some(""));
|
||||
seed_oneliner_state(
|
||||
&case_dir,
|
||||
&OnelinerState::Empty {
|
||||
generated_at: "2026-04-21T11:00:00Z".into(),
|
||||
},
|
||||
);
|
||||
|
||||
let app = doctate_server::create_router(config);
|
||||
let cookie = login(app.clone(), "dr_a").await;
|
||||
|
||||
let resp = app.oneshot(get_page(case_id, &cookie, "")).await.unwrap();
|
||||
let body = body_string(resp).await;
|
||||
|
||||
assert!(
|
||||
!body.contains("generiere Titel"),
|
||||
"silent-only case must not be stuck on 'generiere Titel …'"
|
||||
);
|
||||
assert!(
|
||||
body.contains("unbenannt"),
|
||||
"expected oneliner to render as 'unbenannt' for Empty state"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn case_page_falls_back_to_generic_title_for_empty_state() {
|
||||
// An `Empty` state (LLM deliberately returned no medical content) is
|
||||
|
||||
Reference in New Issue
Block a user