decision_cockpit 0.1.0

Layer — product decision memory with MCP tools and an embedded review dashboard
Documentation
use axum::extract::{Path, Query, State};
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use uuid::Uuid;

use crate::domain::candidates::ExtractionCandidate;
use crate::domain::CandidateStatus;
use crate::error::{AppError, AppResult};
use crate::services::candidates as svc;
use crate::state::AppState;

#[derive(Debug, Deserialize)]
pub struct ListQuery {
    pub status: Option<String>,
    pub limit: Option<i64>,
}

pub async fn list(
    State(state): State<AppState>,
    Query(q): Query<ListQuery>,
) -> AppResult<Json<Vec<ExtractionCandidate>>> {
    let status = parse_status(q.status.as_deref())?;
    let candidates = svc::list_candidates(&state.pool, status, q.limit).await?;
    Ok(Json(candidates))
}

pub async fn list_for_document(
    State(state): State<AppState>,
    Path(document_id): Path<Uuid>,
    Query(q): Query<ListQuery>,
) -> AppResult<Json<Vec<ExtractionCandidate>>> {
    let status = parse_status(q.status.as_deref())?;
    let candidates =
        svc::list_candidates_for_document(&state.pool, document_id, status).await?;
    Ok(Json(candidates))
}

pub async fn accept(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> AppResult<Json<Value>> {
    let outcome = svc::accept_candidate(&state.pool, id).await?;
    Ok(Json(serde_json::to_value(outcome)?))
}

pub async fn reject(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> AppResult<Json<Value>> {
    let candidate = svc::reject_candidate(&state.pool, id).await?;
    Ok(Json(json!({
        "candidate_id": candidate.id,
        "status": candidate.status,
    })))
}

fn parse_status(raw: Option<&str>) -> AppResult<Option<CandidateStatus>> {
    match raw {
        None => Ok(None),
        Some(s) => serde_json::from_value::<CandidateStatus>(Value::String(s.to_string()))
            .map(Some)
            .map_err(|_| AppError::Validation(format!("invalid candidate status `{s}`"))),
    }
}