feat(storage): persist requested language at meeting creation (T8.7, M4.2)

This commit is contained in:
iamdoubz
2026-07-07 13:02:14 -05:00
parent 1fb1da0946
commit 66d9eff2f9
+66 -2
View File
@@ -42,6 +42,12 @@ pub struct NewMeeting {
/// re-rendering notes.md on reprocess/resume reapplies the same
/// section structure instead of losing it.
pub template_id: Option<String>,
/// Transcription language requested at recording start (T8.7, FR-TRX-4):
/// `None` means auto-detect. Recorded immediately (not just at
/// `finalize_meeting`) so a crash-recovered `recovering` meeting still
/// knows what was asked for; `finalize_meeting`'s `language` overwrites
/// this with whatever whisper.cpp actually resolved/detected.
pub language: Option<String>,
}
/// `list_meetings` filters (Phase 8, FR-SEARCH-2). All fields are ANDed
@@ -703,8 +709,8 @@ impl Store for SqliteStore {
let audio_path = folder.join("audio.wav");
let now = now_unix();
sqlx::query(
"INSERT INTO meetings (id, title, started_at, folder_path, audio_path, status, calendar_event_id, template_id, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'recording', ?, ?, ?, ?)",
"INSERT INTO meetings (id, title, started_at, folder_path, audio_path, status, calendar_event_id, template_id, language, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'recording', ?, ?, ?, ?, ?)",
)
.bind(&id)
.bind(&m.title)
@@ -713,6 +719,7 @@ impl Store for SqliteStore {
.bind(audio_path.display().to_string())
.bind(&m.calendar_event_id)
.bind(&m.template_id)
.bind(&m.language)
.bind(now)
.bind(now)
.execute(&self.pool)
@@ -1785,6 +1792,60 @@ mod tests {
}
}
/// T8.7/FR-TRX-4: the language requested at `create_meeting` is visible
/// immediately (not just after `finalize_meeting`) — a crash-recovered
/// `recovering` meeting still knows what was asked for.
#[tokio::test]
async fn create_meeting_persists_the_requested_language_immediately() {
let store = SqliteStore::connect_in_memory().await.unwrap();
let id = store
.create_meeting(NewMeeting {
title: "Reunión semanal".to_string(),
calendar_event_id: None,
template_id: None,
language: Some("es".to_string()),
})
.await
.unwrap();
let meeting = store.get_meeting(&id).await.unwrap();
assert_eq!(meeting.language.as_deref(), Some("es"));
}
/// `finalize_meeting` overwrites whatever `create_meeting` stored with
/// the language whisper.cpp actually resolved/detected (T8.7, FR-TRX-4)
/// — e.g. "auto" mode's detected result, or an English-only model's
/// forced "en".
#[tokio::test]
async fn finalize_meeting_overwrites_the_requested_language_with_the_resolved_one() {
let store = SqliteStore::connect_in_memory().await.unwrap();
let id = store
.create_meeting(NewMeeting {
title: "Auto-detect meeting".to_string(),
calendar_event_id: None,
template_id: None,
language: None, // requested "auto"
})
.await
.unwrap();
store
.finalize_meeting(
&id,
FinalizeMeeting {
segments: Vec::new(),
speakers: Vec::new(),
duration_secs: 42,
recorded: false,
language: Some("fr".to_string()), // what auto-detect resolved to
backend_used: Some("cpu".to_string()),
model_used: Some("small-q5_1".to_string()),
},
)
.await
.unwrap();
let meeting = store.get_meeting(&id).await.unwrap();
assert_eq!(meeting.language.as_deref(), Some("fr"));
}
#[tokio::test]
async fn rename_meeting_updates_the_title() {
let store = SqliteStore::connect_in_memory().await.unwrap();
@@ -1793,6 +1854,7 @@ mod tests {
title: "Untitled meeting".to_string(),
calendar_event_id: None,
template_id: None,
language: None,
})
.await
.unwrap();
@@ -1818,6 +1880,7 @@ mod tests {
title: "Untitled meeting".to_string(),
calendar_event_id: None,
template_id: None,
language: None,
})
.await
.unwrap();
@@ -1862,6 +1925,7 @@ mod tests {
title: "Untitled meeting".to_string(),
calendar_event_id: None,
template_id: None,
language: None,
})
.await
.unwrap();