decision_cockpit 0.1.0

Layer — product decision memory with MCP tools and an embedded review dashboard
Documentation
use sqlx::PgPool;
use uuid::Uuid;

use crate::domain::candidates::ExtractionCandidate;

pub async fn create(
    pool: &PgPool,
    document_id: Option<Uuid>,
    candidate_type: &str,
    payload: &serde_json::Value,
) -> Result<ExtractionCandidate, sqlx::Error> {
    let id = Uuid::new_v4();
    sqlx::query_as::<_, ExtractionCandidate>(
        r#"
        INSERT INTO extraction_candidates (id, document_id, candidate_type, payload, status)
        VALUES ($1, $2, $3, $4, 'pending')
        RETURNING id, document_id, candidate_type, payload, status, created_at, updated_at
        "#,
    )
    .bind(id)
    .bind(document_id)
    .bind(candidate_type)
    .bind(payload)
    .fetch_one(pool)
    .await
}

pub async fn get(
    pool: &PgPool,
    id: Uuid,
) -> Result<Option<ExtractionCandidate>, sqlx::Error> {
    sqlx::query_as::<_, ExtractionCandidate>(
        r#"
        SELECT id, document_id, candidate_type, payload, status, created_at, updated_at
        FROM extraction_candidates
        WHERE id = $1
        "#,
    )
    .bind(id)
    .fetch_optional(pool)
    .await
}

pub async fn list_by_document(
    pool: &PgPool,
    document_id: Uuid,
    status: Option<&str>,
) -> Result<Vec<ExtractionCandidate>, sqlx::Error> {
    sqlx::query_as::<_, ExtractionCandidate>(
        r#"
        SELECT id, document_id, candidate_type, payload, status, created_at, updated_at
        FROM extraction_candidates
        WHERE document_id = $1
          AND ($2::text IS NULL OR status = $2)
        ORDER BY created_at ASC
        "#,
    )
    .bind(document_id)
    .bind(status)
    .fetch_all(pool)
    .await
}

pub async fn list_all(
    pool: &PgPool,
    status: Option<&str>,
    limit: i64,
) -> Result<Vec<ExtractionCandidate>, sqlx::Error> {
    sqlx::query_as::<_, ExtractionCandidate>(
        r#"
        SELECT id, document_id, candidate_type, payload, status, created_at, updated_at
        FROM extraction_candidates
        WHERE ($1::text IS NULL OR status = $1)
        ORDER BY created_at DESC
        LIMIT $2
        "#,
    )
    .bind(status)
    .bind(limit)
    .fetch_all(pool)
    .await
}

pub async fn update_status(
    pool: &PgPool,
    id: Uuid,
    status: &str,
) -> Result<Option<ExtractionCandidate>, sqlx::Error> {
    sqlx::query_as::<_, ExtractionCandidate>(
        r#"
        UPDATE extraction_candidates
        SET status = $2, updated_at = now()
        WHERE id = $1
        RETURNING id, document_id, candidate_type, payload, status, created_at, updated_at
        "#,
    )
    .bind(id)
    .bind(status)
    .fetch_optional(pool)
    .await
}