use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Observation {
pub sequence_id: String,
pub step: u32,
pub state: String,
pub action: String,
pub outcome: Outcome,
pub score: Option<f64>,
pub weight: f64,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Outcome {
Success,
Failure,
Draw,
#[default]
Unknown,
}
impl Outcome {
pub fn parse(s: &str) -> Option<Self> {
match s {
"success" => Some(Outcome::Success),
"failure" => Some(Outcome::Failure),
"draw" => Some(Outcome::Draw),
"unknown" => Some(Outcome::Unknown),
_ => None,
}
}
}
pub const DEFAULT_CONFIDENCE_K: f64 = 20.0;
pub const DEFAULT_DRAW_VALUE: f64 = 0.5;
#[derive(Debug, Clone)]
pub struct BuildConfig {
pub min_count: u64,
pub min_weighted_count: f64,
pub min_confidence: f64,
pub max_step: Option<u32>,
pub smoothing_alpha: f64,
pub score_weight: f64,
pub success_weight: f64,
pub count_weight: f64,
pub max_actions_per_state: Option<usize>,
pub confidence_k: f64,
pub draw_value: f64,
pub tag_filter: Option<Vec<String>>,
}
impl Default for BuildConfig {
fn default() -> Self {
Self {
min_count: 1,
min_weighted_count: 0.0,
min_confidence: 0.0,
max_step: None,
smoothing_alpha: 5.0,
score_weight: 1.0,
success_weight: 1.0,
count_weight: 1.0,
max_actions_per_state: None,
confidence_k: DEFAULT_CONFIDENCE_K,
draw_value: DEFAULT_DRAW_VALUE,
tag_filter: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PriorAction {
pub action: String,
pub count: u64,
pub weighted_count: f64,
pub success_rate: Option<f64>,
pub mean_score: Option<f64>,
pub prior: f64,
pub confidence: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PriorEntry {
pub state: String,
pub actions: Vec<PriorAction>,
}
#[derive(Debug, Clone, Default)]
pub struct PriorBook {
pub entries: HashMap<String, Vec<PriorAction>>,
}
fn sort_actions(actions: &mut [PriorAction]) {
actions.sort_by(|a, b| {
b.prior
.partial_cmp(&a.prior)
.unwrap_or(Ordering::Equal)
.then_with(|| a.action.cmp(&b.action))
});
}
impl PriorBook {
pub fn entries_sorted(&self) -> Vec<PriorEntry> {
let mut states: Vec<&String> = self.entries.keys().collect();
states.sort();
states
.into_iter()
.map(|state| {
let mut actions = self.entries[state].clone();
sort_actions(&mut actions);
PriorEntry {
state: state.clone(),
actions,
}
})
.collect()
}
pub fn query(&self, state: &str, top_k: Option<usize>) -> Vec<PriorAction> {
let Some(actions) = self.entries.get(state) else {
return Vec::new();
};
let mut actions = actions.clone();
sort_actions(&mut actions);
if let Some(k) = top_k {
actions.truncate(k);
}
actions
}
}