use anyhow::{Result, ensure};
use sea_orm::DatabaseConnection;
use serde::{Deserialize, Serialize};
use crate::acp_trajectory::checkpoint::types::{
AssessmentRevision, AssessmentSource, Checkpoint, EffectiveAssessment,
};
use crate::acp_trajectory::{CanonicalStore, SessionIdentity};
use crate::evolution::evidence::EvidencePacket;
use crate::evolution::rubric::RubricEvaluation;
use crate::evolution::scoring::{self, RubricSubmission, ScoringReceipt};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "snake_case")]
pub enum CheckpointAction {
List,
Freeze { expected_watermark: i64 },
Review { checkpoint_id: String },
Submit { submission: Box<RubricSubmission> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewInput {
pub identity: SessionIdentity,
pub checkpoint: Checkpoint,
pub evidence: EvidencePacket,
pub expected_revision: Option<String>,
pub previous: Option<RubricEvaluation>,
#[serde(default)]
pub prefill_revision: Option<String>,
#[serde(default)]
pub history: Vec<AssessmentRevision>,
pub current_watermark: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", content = "data", rename_all = "snake_case")]
pub enum CheckpointReport {
List {
checkpoints: Vec<Checkpoint>,
effective: Box<EffectiveAssessment>,
},
Review(Box<ReviewInput>),
Receipt {
receipt: Box<ScoringReceipt>,
effective: Box<EffectiveAssessment>,
},
}
async fn review(
store: &CanonicalStore,
identity: &SessionIdentity,
checkpoint_id: &str,
) -> Result<CheckpointReport> {
let input = scoring::prepare(store, identity, checkpoint_id).await?;
let effective = store.effective_assessment(identity).await?;
ensure!(
effective.current_revision == input.expected_revision,
"The assessment changed while opening the review; reopen the checkpoint"
);
let history: Vec<_> = store
.assessment_history(identity)
.await?
.into_iter()
.filter(|revision| revision.input.checkpoint_id == checkpoint_id)
.collect();
let prior = history
.iter()
.find(|revision| Some(&revision.revision_id) == effective.current_revision.as_ref())
.or_else(|| {
history
.iter()
.rev()
.find(|revision| revision.input.source == AssessmentSource::Human)
})
.or_else(|| history.last());
let prefill_revision = prior.map(|revision| revision.revision_id.clone());
let previous = prior
.and_then(|revision| revision.input.assessment.as_ref())
.and_then(|content| serde_json::from_str(&content.explanation).ok());
let after = store.effective_assessment(identity).await?;
ensure!(
after.current_revision == input.expected_revision
&& after.current_watermark == effective.current_watermark
&& after.source_capture_states == effective.source_capture_states,
"The session or assessment changed while opening the review; reopen the checkpoint"
);
Ok(CheckpointReport::Review(Box::new(ReviewInput {
identity: identity.clone(),
checkpoint: store
.checkpoint_content(identity, checkpoint_id)
.await?
.checkpoint,
evidence: input.evidence,
expected_revision: input.expected_revision,
previous,
prefill_revision,
history,
current_watermark: effective.current_watermark,
})))
}
pub(super) async fn operate(
db: &DatabaseConnection,
owner: &str,
source: String,
session_id: String,
action: CheckpointAction,
) -> Result<CheckpointReport> {
let store = CanonicalStore::new(db.clone());
let identity = SessionIdentity {
owner: owner.to_owned(),
source,
native_session_id: session_id,
};
match action {
CheckpointAction::List => Ok(CheckpointReport::List {
checkpoints: store.checkpoints(&identity).await?,
effective: Box::new(store.effective_assessment(&identity).await?),
}),
CheckpointAction::Freeze { expected_watermark } => {
let checkpoint = store
.freeze_checkpoint(&identity, expected_watermark)
.await?;
review(&store, &identity, &checkpoint.checkpoint_id).await
}
CheckpointAction::Review { checkpoint_id } => {
review(&store, &identity, &checkpoint_id).await
}
CheckpointAction::Submit { submission } => {
let receipt = scoring::submit(&store, &identity, *submission).await?;
Ok(CheckpointReport::Receipt {
receipt: Box::new(receipt),
effective: Box::new(store.effective_assessment(&identity).await?),
})
}
}
}