use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::model::evidence::Source;
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(untagged)]
pub enum Value {
Num(f64),
Cat(String),
}
impl std::fmt::Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Num(x) => write!(f, "{x}"),
Value::Cat(s) => write!(f, "{s}"),
}
}
}
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum Region {
Intervals(Vec<(f64, f64)>),
Values(Vec<String>),
}
#[derive(Clone, Debug, Serialize)]
pub struct Bound {
pub entity: String,
pub slot: String,
pub region: Region,
pub entropy_bits: f64,
pub max_entropy_bits: f64,
pub map_estimate: Value,
#[serde(skip)]
pub posterior: Vec<f64>,
}
impl Bound {
pub fn knowledge_ratio(&self) -> f64 {
if self.max_entropy_bits == 0.0 {
1.0
} else {
1.0 - self.entropy_bits / self.max_entropy_bits
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Tri {
True,
Possible,
False,
}
impl std::fmt::Display for Tri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Tri::True => write!(f, "true"),
Tri::Possible => write!(f, "possible"),
Tri::False => write!(f, "false"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum Predicate {
Gt { value: f64 },
Lt { value: f64 },
Between { lo: f64, hi: f64 },
Is { value: String },
IsNot { value: String },
}
impl Predicate {
pub(crate) fn matches(&self, v: &Value) -> Result<bool> {
match (self, v) {
(Predicate::Gt { value }, Value::Num(x)) => Ok(x > value),
(Predicate::Lt { value }, Value::Num(x)) => Ok(x < value),
(Predicate::Between { lo, hi }, Value::Num(x)) => Ok(lo <= x && x <= hi),
(Predicate::Is { value }, Value::Cat(s)) => Ok(s == value),
(Predicate::IsNot { value }, Value::Cat(s)) => Ok(s != value),
_ => Err(Error::Invalid(
"predicate type does not match slot domain".into(),
)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FindMode {
Possible,
Certain,
}
impl std::str::FromStr for FindMode {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"possible" => Ok(FindMode::Possible),
"certain" => Ok(FindMode::Certain),
_ => Err(Error::Invalid(
"mode must be 'possible' or 'certain'".into(),
)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProcurementAction {
pub name: String,
pub slot: String,
pub cost: f64,
pub source: Source,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub answer_width: Option<f64>,
}
#[derive(Clone, Debug, Serialize)]
pub struct ResolveStep {
pub action: ProcurementAction,
pub expected_entropy_bits: f64,
pub expected_gain_bits: f64,
}
#[derive(Clone, Debug, Serialize)]
pub struct ResolvePlan {
pub steps: Vec<ResolveStep>,
pub start_entropy_bits: f64,
pub planned_entropy_bits: f64,
pub validated_entropy_bits: Option<f64>,
pub total_cost: f64,
}
#[derive(Clone, Debug, Serialize)]
pub struct DecisionStep {
pub action: ProcurementAction,
pub expected_risk: f64,
pub expected_gain: f64,
}
#[derive(Clone, Debug, Serialize)]
pub struct DecisionPlan {
pub objective: String,
pub units: String,
pub steps: Vec<DecisionStep>,
pub start_risk: f64,
pub planned_risk: f64,
pub validated_risk: Option<f64>,
pub total_cost: f64,
pub recommended_now: String,
pub recommended_after: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
pub enum JoinPredicate {
Gt { left: String, right: String },
Lt { left: String, right: String },
Approx {
left: String,
right: String,
tol: f64,
},
Same { left: String, right: String },
}
impl JoinPredicate {
pub fn slots(&self) -> (&str, &str) {
match self {
JoinPredicate::Gt { left, right }
| JoinPredicate::Lt { left, right }
| JoinPredicate::Approx { left, right, .. }
| JoinPredicate::Same { left, right } => (left, right),
}
}
pub(crate) fn is_numeric(&self) -> bool {
!matches!(self, JoinPredicate::Same { .. })
}
pub(crate) fn is_symmetric(&self) -> bool {
matches!(
self,
JoinPredicate::Approx { .. } | JoinPredicate::Same { .. }
)
}
}
fn default_true() -> bool {
true
}
fn default_join_limit() -> usize {
1000
}
#[derive(Clone, Debug, Deserialize)]
pub struct JoinOptions {
#[serde(default)]
pub left_prefix: Option<String>,
#[serde(default)]
pub right_prefix: Option<String>,
#[serde(default)]
pub min_probability: f64,
#[serde(default)]
pub certain_only: bool,
#[serde(default = "default_true")]
pub require_evidence: bool,
#[serde(default = "default_join_limit")]
pub limit: usize,
}
impl Default for JoinOptions {
fn default() -> Self {
JoinOptions {
left_prefix: None,
right_prefix: None,
min_probability: 0.0,
certain_only: false,
require_evidence: true,
limit: default_join_limit(),
}
}
}
#[derive(Clone, Debug, Serialize)]
pub struct JoinMatch {
pub left: String,
pub right: String,
pub probability: f64,
pub certainty: Tri,
}
#[derive(Clone, Debug, Serialize)]
pub struct JoinResult {
pub matches: Vec<JoinMatch>,
pub pairs_examined: usize,
pub truncated: bool,
}