Files
doctate/server/src/error.rs
T
Brummel 70425939b2 feat: Add auto-trigger for LLM analysis
Introduces a new module `auto_trigger` responsible for opportunistically
initiating LLM analysis jobs. This mechanism scans case directories for
new or updated recordings and transcripts, automatically creating and
queuing analysis jobs when appropriate.

Key components:
- `evaluate_case`: Pure function to determine if a case is ready for
  analysis based on recording status, transcriptions, document
  modification times, and existing job/failure markers.
- `try_enqueue`: Orchestrates the analysis job creation and queuing
  process if `evaluate_case` returns `AutoDecision::Enqueue`.
- `try_enqueue_all_for_user`: Iterates through all user cases to trigger
  `try_enqueue` for eligible ones.
- `write_failure_marker`/`remove_failure_marker`: Handles persistent
  recording of analysis failures and their cleanup.

This feature aims to reduce manual intervention by automatically
analyzing new or changed case data.
2026-04-20 09:18:30 +02:00

54 lines
1.9 KiB
Rust

use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use serde_json::json;
#[derive(Debug)]
pub enum AppError {
Unauthorized,
BadRequest(String),
NotFound(String),
/// 403 — authenticated but lacks permission (e.g. non-admin calling an
/// admin-only endpoint).
Forbidden(String),
/// 409 — request is valid but conflicts with current state (e.g. an
/// analysis is already running for this case).
Conflict(String),
/// 503 — the feature is unavailable right now (e.g. no LLM configured).
ServiceUnavailable(String),
Internal(String),
/// 302 redirect. Used by web auth to send unauthenticated users to login.
Redirect(String),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
if let Self::Redirect(url) = &self {
return Response::builder()
.status(StatusCode::FOUND)
.header(axum::http::header::LOCATION, url)
.body(axum::body::Body::empty())
.unwrap();
}
let (status, message) = match &self {
Self::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_owned()),
Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
Self::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
Self::Forbidden(msg) => (StatusCode::FORBIDDEN, msg.clone()),
Self::Conflict(msg) => (StatusCode::CONFLICT, msg.clone()),
Self::ServiceUnavailable(msg) => (StatusCode::SERVICE_UNAVAILABLE, msg.clone()),
Self::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg.clone()),
Self::Redirect(_) => unreachable!(),
};
let body = axum::Json(json!({ "error": message }));
(status, body).into_response()
}
}
impl From<std::io::Error> for AppError {
fn from(err: std::io::Error) -> Self {
Self::Internal(err.to_string())
}
}