feat: Add reanalyze case functionality
Introduce a new endpoint for reanalyzing cases. This feature allows administrators to rebuild analysis input files and enqueue new analysis jobs. The system will then generate a new document version, which will become the displayed document.
This commit is contained in:
@@ -87,6 +87,72 @@ pub async fn handle_close_case(
|
||||
Ok(Redirect::to(&format!("/web/cases/{case_id}")))
|
||||
}
|
||||
|
||||
/// POST /web/cases/{case_id}/reanalyze
|
||||
///
|
||||
/// Admin-only. Build a fresh `analysis_input_v{N+1}.json` from the current
|
||||
/// transcripts and enqueue a new analyze job. `find_latest_document` picks the
|
||||
/// currently-highest N; the worker will write `document_v{N+1}.md`, which then
|
||||
/// wins the "highest version" race and becomes the displayed document.
|
||||
pub async fn handle_reanalyze_case(
|
||||
user: AuthenticatedWebUser,
|
||||
State(config): State<Arc<Config>>,
|
||||
State(analyze_tx): State<AnalyzeSender>,
|
||||
AxumPath(case_id): AxumPath<String>,
|
||||
) -> Result<Redirect, AppError> {
|
||||
uuid::Uuid::parse_str(&case_id)
|
||||
.map_err(|_| AppError::BadRequest("Invalid case_id".into()))?;
|
||||
|
||||
if !user.is_admin() {
|
||||
return Err(AppError::Forbidden("Admin erforderlich".into()));
|
||||
}
|
||||
|
||||
if !config.llm_configured() {
|
||||
return Err(AppError::ServiceUnavailable(
|
||||
"LLM-Analyse nicht konfiguriert".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let user_root = config.data_path.join(&user.slug);
|
||||
let case_dir = user_root.join("open").join(&case_id);
|
||||
if !tokio::fs::try_exists(&case_dir).await.unwrap_or(false) {
|
||||
warn!(slug = %user.slug, case_id = %case_id, "reanalyze: case not found in open/");
|
||||
return Err(AppError::NotFound("Case not found".into()));
|
||||
}
|
||||
|
||||
let current_version = find_latest_document(&case_dir)
|
||||
.await
|
||||
.map(|(v, _)| v)
|
||||
.ok_or_else(|| {
|
||||
AppError::BadRequest("Kein bestehendes Dokument — nutze 'Fall abschließen'".into())
|
||||
})?;
|
||||
let next_version = current_version + 1;
|
||||
|
||||
let input = build_analysis_input(&case_dir, next_version).await?;
|
||||
|
||||
let input_path = case_dir.join(format!("analysis_input_v{next_version}.json"));
|
||||
write_input_create_new(&input_path, &input).await?;
|
||||
|
||||
if analyze_tx
|
||||
.send(AnalyzeJob {
|
||||
case_dir: case_dir.clone(),
|
||||
version: next_version,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
warn!(case_id = %case_id, "analyze send failed (worker gone)");
|
||||
}
|
||||
|
||||
info!(
|
||||
slug = %user.slug,
|
||||
case_id = %case_id,
|
||||
version = next_version,
|
||||
recordings = input.recordings.len(),
|
||||
"reanalyze requested; analysis enqueued"
|
||||
);
|
||||
|
||||
Ok(Redirect::to(&format!("/web/cases/{case_id}")))
|
||||
}
|
||||
|
||||
/// GET /web/cases/{case_id}/document
|
||||
///
|
||||
/// Render the latest `document_v{N}.md` for the case. The handler scans the
|
||||
|
||||
@@ -24,6 +24,10 @@ pub fn api_router() -> Router<AppState> {
|
||||
"/web/cases/{case_id}/close",
|
||||
post(case_actions::handle_close_case),
|
||||
)
|
||||
.route(
|
||||
"/web/cases/{case_id}/reanalyze",
|
||||
post(case_actions::handle_reanalyze_case),
|
||||
)
|
||||
.route(
|
||||
"/web/cases/{case_id}/document",
|
||||
get(case_actions::handle_document_view),
|
||||
|
||||
@@ -43,6 +43,7 @@ struct CaseDetailTemplate {
|
||||
llm_missing: bool,
|
||||
analyzing: bool,
|
||||
has_document: bool,
|
||||
can_reanalyze: bool,
|
||||
}
|
||||
|
||||
/// Four mutually exclusive flags derived from filesystem state + config.
|
||||
@@ -54,6 +55,7 @@ struct CaseFlags {
|
||||
analyzing: bool,
|
||||
can_close: bool,
|
||||
llm_missing: bool,
|
||||
can_reanalyze: bool,
|
||||
}
|
||||
|
||||
async fn compute_flags(
|
||||
@@ -61,6 +63,7 @@ async fn compute_flags(
|
||||
status: &str,
|
||||
recordings: &[RecordingView],
|
||||
llm_configured: bool,
|
||||
is_admin: bool,
|
||||
) -> CaseFlags {
|
||||
let has_document = any_document_exists(case_dir).await;
|
||||
let analyzing = !has_document
|
||||
@@ -79,6 +82,7 @@ async fn compute_flags(
|
||||
analyzing,
|
||||
can_close: transcribed_stage && llm_configured,
|
||||
llm_missing: transcribed_stage && !llm_configured,
|
||||
can_reanalyze: has_document && is_admin && llm_configured,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +144,14 @@ pub async fn handle_case_detail(
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
let flags = compute_flags(&case_dir, &status, &recordings, config.llm_configured()).await;
|
||||
let flags = compute_flags(
|
||||
&case_dir,
|
||||
&status,
|
||||
&recordings,
|
||||
config.llm_configured(),
|
||||
user.is_admin(),
|
||||
)
|
||||
.await;
|
||||
let case_id_short = case_id.chars().take(8).collect();
|
||||
|
||||
CaseDetailTemplate {
|
||||
@@ -154,6 +165,7 @@ pub async fn handle_case_detail(
|
||||
llm_missing: flags.llm_missing,
|
||||
analyzing: flags.analyzing,
|
||||
has_document: flags.has_document,
|
||||
can_reanalyze: flags.can_reanalyze,
|
||||
}
|
||||
.render()
|
||||
.map(Html)
|
||||
|
||||
Reference in New Issue
Block a user