use std::collections::{HashMap, HashSet};
pub const EPISTEMIC_TYPES: &[&str] = &["FactualClaim", "Opinion", "Speculation", "Uncertainty"];
pub const CONTENT_TYPES: &[&str] = &["Chunk", "Document", "EntityMap", "Summary", "Translation"];
pub const ANALYSIS_TYPES: &[&str] = &[
"ConfidenceScore",
"Contradiction",
"ReasoningChain",
"RiskScore",
"SentimentScore",
];
pub const PRIMITIVE_TYPES: &[&str] = &[
"Boolean",
"Duration",
"Float",
"Integer",
"List",
"String",
"StructuredReport",
];
pub fn builtin_types() -> HashSet<&'static str> {
let mut s = HashSet::new();
for t in EPISTEMIC_TYPES {
s.insert(*t);
}
for t in CONTENT_TYPES {
s.insert(*t);
}
for t in ANALYSIS_TYPES {
s.insert(*t);
}
for t in PRIMITIVE_TYPES {
s.insert(*t);
}
s.insert("Any");
s.insert("Never");
s.insert("HighConfidenceFact");
s.insert("CitedFact");
s
}
pub fn ranged_types() -> HashMap<&'static str, (f64, f64)> {
let mut m = HashMap::new();
m.insert("RiskScore", (0.0, 1.0));
m.insert("ConfidenceScore", (0.0, 1.0));
m.insert("SentimentScore", (-1.0, 1.0));
m
}
fn parents() -> HashMap<&'static str, Option<&'static str>> {
let mut m = HashMap::new();
m.insert("HighConfidenceFact", Some("CitedFact"));
m.insert("CitedFact", Some("FactualClaim"));
m.insert("FactualClaim", Some("Any"));
m.insert("Opinion", Some("Any"));
m.insert("Speculation", Some("Any"));
m.insert("Uncertainty", Some("Any"));
m.insert("Any", None);
m.insert("Never", None);
m
}
fn ancestors(ty: &str) -> Vec<String> {
let map = parents();
let mut chain = vec![ty.to_string()];
let mut current = ty;
loop {
match map.get(current) {
Some(Some(parent)) => {
chain.push(parent.to_string());
current = parent;
}
_ => break,
}
}
chain
}
pub fn is_subtype(t1: &str, t2: &str) -> bool {
if t1 == t2 {
return true;
}
if t1 == "Never" {
return true;
}
if t2 == "Any" {
return true;
}
let t1_base = t1.split('<').next().unwrap_or(t1);
let t2_base = t2.split('<').next().unwrap_or(t2);
if t1_base == t2_base {
return true;
}
if t1_base == "Uncertainty" {
return true;
}
if is_nominal_subtype(t1_base, t2_base) {
return true;
}
false
}
fn is_nominal_subtype(t1: &str, t2: &str) -> bool {
let anc = ancestors(t1);
if anc.iter().any(|a| a == t2) {
return true;
}
if t2 == "String" && (t1 == "FactualClaim" || t1 == "CitedFact") {
return true;
}
if t2 == "Float" && (t1 == "RiskScore" || t1 == "ConfidenceScore" || t1 == "SentimentScore") {
return true;
}
if t1 == "StructuredReport" {
return true;
}
false
}
pub fn join(t1: &str, t2: &str) -> String {
if t1 == t2 {
return t1.to_string();
}
if t1 == "Never" {
return t2.to_string();
}
if t2 == "Never" {
return t1.to_string();
}
if t1 == "Any" || t2 == "Any" {
return "Any".to_string();
}
if t1 == "Uncertainty" || t2 == "Uncertainty" {
return "Uncertainty".to_string();
}
let anc1 = ancestors(t1);
let anc2: HashSet<String> = ancestors(t2).into_iter().collect();
for a in &anc1 {
if anc2.contains(a) {
return a.clone();
}
}
"Any".to_string()
}
pub fn meet(t1: &str, t2: &str) -> String {
if t1 == t2 {
return t1.to_string();
}
if is_subtype(t1, t2) {
return t1.to_string();
}
if is_subtype(t2, t1) {
return t2.to_string();
}
"Never".to_string()
}
pub fn propagate_uncertainty(types: &[&str]) -> String {
if types.is_empty() {
return "Any".to_string();
}
let mut result = types[0].to_string();
for &t in &types[1..] {
result = join(&result, t);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn subtype_reflexive() {
assert!(is_subtype("FactualClaim", "FactualClaim"));
assert!(is_subtype("String", "String"));
}
#[test]
fn subtype_lattice_chain() {
assert!(is_subtype("HighConfidenceFact", "CitedFact"));
assert!(is_subtype("HighConfidenceFact", "FactualClaim"));
assert!(is_subtype("HighConfidenceFact", "Any"));
assert!(is_subtype("CitedFact", "FactualClaim"));
assert!(is_subtype("FactualClaim", "Any"));
}
#[test]
fn subtype_not_reverse() {
assert!(!is_subtype("Any", "FactualClaim"));
assert!(!is_subtype("FactualClaim", "CitedFact"));
}
#[test]
fn subtype_never_is_bottom() {
assert!(is_subtype("Never", "Any"));
assert!(is_subtype("Never", "FactualClaim"));
assert!(is_subtype("Never", "String"));
}
#[test]
fn subtype_any_is_top() {
assert!(is_subtype("Opinion", "Any"));
assert!(is_subtype("Speculation", "Any"));
}
#[test]
fn subtype_uncertainty_taints() {
assert!(is_subtype("Uncertainty", "FactualClaim"));
assert!(is_subtype("Uncertainty", "String"));
}
#[test]
fn subtype_coercions() {
assert!(is_subtype("FactualClaim", "String"));
assert!(is_subtype("CitedFact", "String"));
assert!(is_subtype("RiskScore", "Float"));
assert!(is_subtype("ConfidenceScore", "Float"));
assert!(is_subtype("SentimentScore", "Float"));
assert!(is_subtype("StructuredReport", "ContractData"));
}
#[test]
fn join_same() {
assert_eq!(join("FactualClaim", "FactualClaim"), "FactualClaim");
}
#[test]
fn join_lattice() {
assert_eq!(join("HighConfidenceFact", "CitedFact"), "CitedFact");
assert_eq!(join("FactualClaim", "Opinion"), "Any");
assert_eq!(join("CitedFact", "HighConfidenceFact"), "CitedFact");
}
#[test]
fn join_uncertainty_taints() {
assert_eq!(join("FactualClaim", "Uncertainty"), "Uncertainty");
assert_eq!(join("Uncertainty", "String"), "Uncertainty");
}
#[test]
fn join_never_neutral() {
assert_eq!(join("Never", "FactualClaim"), "FactualClaim");
assert_eq!(join("String", "Never"), "String");
}
#[test]
fn meet_same() {
assert_eq!(meet("FactualClaim", "FactualClaim"), "FactualClaim");
}
#[test]
fn meet_subtype() {
assert_eq!(
meet("HighConfidenceFact", "FactualClaim"),
"HighConfidenceFact"
);
assert_eq!(
meet("FactualClaim", "HighConfidenceFact"),
"HighConfidenceFact"
);
}
#[test]
fn meet_incompatible() {
assert_eq!(meet("Opinion", "Speculation"), "Never");
}
#[test]
fn propagation_single() {
assert_eq!(propagate_uncertainty(&["FactualClaim"]), "FactualClaim");
}
#[test]
fn propagation_degrades() {
assert_eq!(propagate_uncertainty(&["CitedFact", "Opinion"]), "Any");
assert_eq!(
propagate_uncertainty(&["HighConfidenceFact", "CitedFact"]),
"CitedFact"
);
}
#[test]
fn propagation_uncertainty_taints_all() {
assert_eq!(
propagate_uncertainty(&["FactualClaim", "CitedFact", "Uncertainty"]),
"Uncertainty"
);
}
#[test]
fn builtin_types_contains_all() {
let bt = builtin_types();
assert!(bt.contains("String"));
assert!(bt.contains("FactualClaim"));
assert!(bt.contains("RiskScore"));
assert!(bt.contains("Document"));
assert!(bt.contains("Any"));
}
}