Defer oneliner regeneration until batch end
Avoid redundant LLM calls by waiting until all recordings in a batch have been processed before regenerating the oneliner. This change introduces a helper function `has_pending_recordings` to check for any `.m4a` files without a corresponding transcript, and the `update_oneliner` logic now only proceeds if no pending recordings are found.
This commit is contained in:
@@ -78,11 +78,16 @@ pub async fn run(
|
|||||||
"Transcript written"
|
"Transcript written"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Regenerate the oneliner from all transcripts of the case. Later
|
// Regenerate the oneliner, but only after the *last* pending
|
||||||
// recordings override earlier ones — symmetric to the analyze-LLM
|
// recording in the batch — otherwise we'd burn LLM calls on
|
||||||
// rule "Spätere Aufnahmen haben Vorrang". Silent / empty cases are
|
// incomplete inputs that the next job would redo. Later
|
||||||
// no-ops; LLM errors leave any existing oneliner untouched.
|
// recordings still override earlier ones (symmetric to the
|
||||||
if let Some(case_dir) = audio_path.parent() {
|
// analyze-LLM "Spätere Aufnahmen haben Vorrang" rule). Silent /
|
||||||
|
// empty cases are no-ops; LLM errors leave any existing
|
||||||
|
// oneliner untouched.
|
||||||
|
if let Some(case_dir) = audio_path.parent()
|
||||||
|
&& !has_pending_recordings(case_dir).await
|
||||||
|
{
|
||||||
update_oneliner(case_dir, &client, &config, &vocab).await;
|
update_oneliner(case_dir, &client, &config, &vocab).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,6 +162,29 @@ pub(crate) async fn update_oneliner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true if `case_dir` contains at least one `.m4a` recording
|
||||||
|
/// that has no corresponding `.transcript.txt`. Used to gate the
|
||||||
|
/// oneliner-regenerate call: we only run it after the *last* pending
|
||||||
|
/// transcription in a batch, not after every single one. `.m4a.failed`
|
||||||
|
/// entries are skipped — they don't block the oneliner. Fail-open on
|
||||||
|
/// read errors: if we can't scan, return false so the oneliner runs
|
||||||
|
/// anyway (one redundant call beats never).
|
||||||
|
pub(crate) async fn has_pending_recordings(case_dir: &Path) -> bool {
|
||||||
|
let Ok(mut entries) = tokio::fs::read_dir(case_dir).await else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
while let Ok(Some(entry)) = entries.next_entry().await {
|
||||||
|
let path = entry.path();
|
||||||
|
if path.extension().and_then(|s| s.to_str()) != Some("m4a") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !path.with_extension("transcript.txt").exists() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Read every non-empty `*.transcript.txt` in `case_dir`, ordered
|
/// Read every non-empty `*.transcript.txt` in `case_dir`, ordered
|
||||||
/// chronologically (ISO-8601 filename sort equals chronological order), and
|
/// chronologically (ISO-8601 filename sort equals chronological order), and
|
||||||
/// join them with `\n\n---\n\n`. Empty / whitespace-only transcripts (silent
|
/// join them with `\n\n---\n\n`. Empty / whitespace-only transcripts (silent
|
||||||
@@ -261,4 +289,61 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(all_transcripts_joined(dir.path()).await.is_none());
|
assert!(all_transcripts_joined(dir.path()).await.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn has_pending_recordings_true_when_m4a_without_transcript() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
tokio::fs::write(dir.path().join("2026-04-16T10-00-00Z.m4a"), b"a")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio::fs::write(
|
||||||
|
dir.path().join("2026-04-16T10-00-00Z.transcript.txt"),
|
||||||
|
"text",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio::fs::write(dir.path().join("2026-04-16T10-05-00Z.m4a"), b"b")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(has_pending_recordings(dir.path()).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn has_pending_recordings_false_when_all_transcribed() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
tokio::fs::write(dir.path().join("2026-04-16T10-00-00Z.m4a"), b"a")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio::fs::write(
|
||||||
|
dir.path().join("2026-04-16T10-00-00Z.transcript.txt"),
|
||||||
|
"text1",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio::fs::write(dir.path().join("2026-04-16T10-05-00Z.m4a"), b"b")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
tokio::fs::write(
|
||||||
|
dir.path().join("2026-04-16T10-05-00Z.transcript.txt"),
|
||||||
|
"text2",
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(!has_pending_recordings(dir.path()).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn has_pending_recordings_ignores_failed() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
tokio::fs::write(dir.path().join("2026-04-16T10-00-00Z.m4a.failed"), b"x")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(!has_pending_recordings(dir.path()).await);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn has_pending_recordings_false_for_empty_dir() {
|
||||||
|
let dir = tempdir().unwrap();
|
||||||
|
assert!(!has_pending_recordings(dir.path()).await);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user