use std::future::Future;
use std::pin::Pin;
use serde::{Deserialize, Serialize};
use crate::skills::task::SkillTask;
use crate::skills::transcript::Transcript;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraderOutcome {
pub id: String,
pub score: f64,
pub passed: bool,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub notes: String,
}
impl GraderOutcome {
pub fn pass(id: impl Into<String>) -> Self {
Self {
id: id.into(),
score: 1.0,
passed: true,
notes: String::new(),
}
}
pub fn fail(id: impl Into<String>, notes: impl Into<String>) -> Self {
Self {
id: id.into(),
score: 0.0,
passed: false,
notes: notes.into(),
}
}
pub fn partial(id: impl Into<String>, score: f64, notes: impl Into<String>) -> Self {
let score = score.clamp(0.0, 1.0);
Self {
id: id.into(),
score,
passed: score >= 1.0,
notes: notes.into(),
}
}
pub fn skipped(id: impl Into<String>, notes: impl Into<String>) -> Self {
Self {
id: id.into(),
score: 1.0,
passed: true,
notes: notes.into(),
}
}
}
pub trait Grader: Send + Sync {
fn id(&self) -> &str;
fn grade(&self, task: &SkillTask, transcript: &Transcript) -> GraderOutcome;
}
pub trait AsyncGrader: Send + Sync {
fn id(&self) -> &str;
fn grade<'a>(
&'a self,
task: &'a SkillTask,
transcript: &'a Transcript,
) -> Pin<Box<dyn Future<Output = GraderOutcome> + Send + 'a>>;
}
impl<G: Grader + ?Sized> AsyncGrader for G {
fn id(&self) -> &str {
Grader::id(self)
}
fn grade<'a>(
&'a self,
task: &'a SkillTask,
transcript: &'a Transcript,
) -> Pin<Box<dyn Future<Output = GraderOutcome> + Send + 'a>> {
let outcome = Grader::grade(self, task, transcript);
Box::pin(async move { outcome })
}
}