refactor(server): split build_analysis_input into pure + I/O parts
Replace the imperative loop that mixed mtime-tracking, transcript reading, and validation with three clearly-scoped functions: - compute_last_mtime: pure reduction over all m4as, returns max mtime. - read_recordings: I/O loop that reads each transcript sidecar, aborts on missing transcript (legacy "Nicht alle Aufnahmen sind transkribiert" 400 body), silently skips blank ones. - build_analysis_input: orchestrator that composes the above with RFC3339 formatting. Error bodies are preserved byte-for-byte. Replaces a one-pass loop with two passes; n <= ~10 in practice - negligible.
This commit is contained in:
@@ -124,13 +124,44 @@ pub(crate) async fn build_analysis_input(case_dir: &Path) -> Result<AnalysisInpu
|
||||
return Err(AppError::BadRequest("Keine Aufnahmen im Fall".into()));
|
||||
}
|
||||
|
||||
let mut recordings: Vec<RecordingInput> = Vec::new();
|
||||
let mut last_mtime: SystemTime = SystemTime::UNIX_EPOCH;
|
||||
for (m4a_path, mtime) in &m4as {
|
||||
if *mtime > last_mtime {
|
||||
last_mtime = *mtime;
|
||||
}
|
||||
// last_mtime is derived from ALL m4as (including ones that turn out
|
||||
// to have blank transcripts) — a late blank addendum still counts as
|
||||
// case activity. Computed up-front so `read_recordings` can focus on
|
||||
// I/O + validation.
|
||||
let last_mtime = compute_last_mtime(&m4as);
|
||||
let recordings = read_recordings(&m4as).await?;
|
||||
|
||||
// Silent-only case: `recordings` may be empty here. The analyze worker
|
||||
// handles that by writing a stub document instead of calling the LLM.
|
||||
let last_recording_mtime = OffsetDateTime::from(last_mtime)
|
||||
.format(&Rfc3339)
|
||||
.map_err(|e| AppError::Internal(format!("format mtime: {e}")))?;
|
||||
|
||||
Ok(AnalysisInput {
|
||||
last_recording_mtime,
|
||||
recordings,
|
||||
})
|
||||
}
|
||||
|
||||
/// Max modification time across all m4as. Falls back to UNIX_EPOCH for
|
||||
/// an empty slice — callers should have already rejected that case.
|
||||
fn compute_last_mtime(m4as: &[(PathBuf, SystemTime)]) -> SystemTime {
|
||||
m4as.iter()
|
||||
.map(|(_, mtime)| *mtime)
|
||||
.max()
|
||||
.unwrap_or(SystemTime::UNIX_EPOCH)
|
||||
}
|
||||
|
||||
/// Read the `.transcript.txt` sidecar for every m4a. Any missing
|
||||
/// transcript aborts with the legacy 400 "Nicht alle Aufnahmen sind
|
||||
/// transkribiert" body. Blank transcripts are silently skipped — they
|
||||
/// represent recordings the transcriber classified as silence and must
|
||||
/// not be pushed to the LLM.
|
||||
async fn read_recordings(
|
||||
m4as: &[(PathBuf, SystemTime)],
|
||||
) -> Result<Vec<RecordingInput>, AppError> {
|
||||
let mut recordings: Vec<RecordingInput> = Vec::new();
|
||||
for (m4a_path, _) in m4as {
|
||||
let transcript_path = m4a_path.with_extension("transcript.txt");
|
||||
let text = tokio::fs::read_to_string(&transcript_path)
|
||||
.await
|
||||
@@ -148,17 +179,7 @@ pub(crate) async fn build_analysis_input(case_dir: &Path) -> Result<AnalysisInpu
|
||||
text,
|
||||
});
|
||||
}
|
||||
|
||||
// Silent-only case: `recordings` may be empty here. The analyze worker
|
||||
// handles that by writing a stub document instead of calling the LLM.
|
||||
let last_recording_mtime = OffsetDateTime::from(last_mtime)
|
||||
.format(&Rfc3339)
|
||||
.map_err(|e| AppError::Internal(format!("format mtime: {e}")))?;
|
||||
|
||||
Ok(AnalysisInput {
|
||||
last_recording_mtime,
|
||||
recordings,
|
||||
})
|
||||
Ok(recordings)
|
||||
}
|
||||
|
||||
/// Return `(path, mtime)` for every `.m4a` file directly under `case_dir`,
|
||||
|
||||
Reference in New Issue
Block a user