Refactor recording metadata to JSON sidecar

Replaces the `.transcript.txt` sidecar with a structured `.json` file
for recording metadata. This change consolidates transcript text,
duration, and other potential metadata into a single, extensible JSON
object.

This also refactors the `TranscriptState` enum to better represent the
on-disk state (absence of file means pending) and the in-memory
representation. The `Transcript` enum now specifically models the
terminal outcomes of the transcriber (`Silent` or `Content`).

The commit includes updates to documentation, data structures, path
handling, and various tests to align with the new metadata format.
This commit is contained in:
2026-04-27 12:48:25 +02:00
parent a510c20e75
commit 66b3b7e4c8
20 changed files with 637 additions and 335 deletions
+22 -9
View File
@@ -236,7 +236,14 @@ async fn recovery_enqueues_only_pending_recordings() {
std::fs::create_dir_all(&case_a).unwrap();
std::fs::write(case_a.join("2026-04-10T10-00-00Z.m4a"), b"x").unwrap();
std::fs::write(case_a.join("2026-04-11T10-00-00Z.m4a"), b"x").unwrap();
std::fs::write(case_a.join("2026-04-11T10-00-00Z.transcript.txt"), b"done").unwrap();
common::seed_recording_meta(
&case_a,
"2026-04-11T10-00-00Z",
common::Transcript::Content {
text: "done".into(),
},
None,
);
// User 2, case with one pending .m4a.
let case_b = root.join("dr_b/bbbb");
@@ -380,14 +387,14 @@ async fn worker_leaves_m4a_intact_on_transient_whisper_error() {
"transient error must not mark {} as failed",
failed.display()
);
// And no transcript yet either (the 503 produced no text).
let transcript = case_dir.join("2026-04-13T10-31-00Z.transcript.txt");
assert!(!transcript.exists(), "no transcript expected on 503");
// And no metadata sidecar yet either (the 503 produced no text).
let meta = case_dir.join("2026-04-13T10-31-00Z.json");
assert!(!meta.exists(), "no recording meta expected on 503");
}
/// The worker must route the Whisper response through the Gazetteer
/// before persisting. Mock returns the drift form "Zerebrum"; the
/// persisted `.transcript.txt` must contain the canonical "Cerebrum".
/// persisted `<stem>.json` must carry the canonical "Cerebrum".
#[tokio::test]
async fn transcribe_worker_normalizes_whisper_output() {
let server = MockServer::start().await;
@@ -438,10 +445,16 @@ async fn transcribe_worker_normalizes_whisper_output() {
)
.await;
let transcript_path = case_dir.join("2026-04-13T10-30-00Z.transcript.txt");
assert!(transcript_path.exists(), "transcript missing");
let transcript = std::fs::read_to_string(&transcript_path).unwrap();
assert_eq!(transcript, "Patient mit Blutung im Cerebrum.");
let meta_path = case_dir.join("2026-04-13T10-30-00Z.json");
assert!(meta_path.exists(), "recording meta missing");
let bytes = std::fs::read(&meta_path).unwrap();
let meta: doctate_common::RecordingMeta = serde_json::from_slice(&bytes).unwrap();
match meta.transcript {
doctate_common::Transcript::Content { text } => {
assert_eq!(text, "Patient mit Blutung im Cerebrum.");
}
other => panic!("expected Transcript::Content, got {other:?}"),
}
}
// -------------------- Ollama client --------------------