use crate::composition::Composition;
use crate::process::{Atmosphere, DurationRange, RouteFamily, TemperatureRange};
use std::collections::BTreeSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StepGroupKey {
pub heating_operation_count: usize,
pub operation_index: usize,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SourcedValue<T> {
pub value: T,
pub doi: String,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CrossDoiFieldStatus<T> {
Agreement { value: T, source_dois: Vec<String> },
Conflict { values: Vec<SourcedValue<T>> },
InsufficientIndependentSources,
Unresolved,
SegmentationAmbiguous,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StepGroupAssessment {
pub key: StepGroupKey,
pub source_dois: Vec<String>,
pub temperature: CrossDoiFieldStatus<TemperatureRange>,
pub duration: CrossDoiFieldStatus<DurationRange>,
pub atmosphere: CrossDoiFieldStatus<Atmosphere>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RouteObservationAssessment {
pub target: Composition,
pub precursors: BTreeSet<Composition>,
pub route_family: RouteFamily,
pub has_multiple_operation_shapes: bool,
pub observed_operation_counts: Vec<usize>,
pub step_groups: Vec<StepGroupAssessment>,
}
impl RouteObservationAssessment {
pub fn independent_doi_count(&self) -> usize {
self.step_groups
.iter()
.flat_map(|g| &g.source_dois)
.collect::<BTreeSet<_>>()
.len()
}
pub fn has_any_conflict(&self) -> bool {
self.step_groups.iter().any(|g| {
matches!(g.temperature, CrossDoiFieldStatus::Conflict { .. })
|| matches!(g.duration, CrossDoiFieldStatus::Conflict { .. })
|| matches!(g.atmosphere, CrossDoiFieldStatus::Conflict { .. })
})
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LiteratureRouteEvidence {
pub assessment: RouteObservationAssessment,
pub limitations: Vec<String>,
}
pub fn literature_evidence_limitations(assessment: &RouteObservationAssessment) -> Vec<String> {
let mut limitations = vec![
"population-level base rates only -- Phase 20D's manual audit (58 DOIs) found this \
corpus cannot certify any individual observation as correct; this is reference-only \
evidence, never auto-applied to process conditions or score"
.to_string(),
"identity-audit status is unknown for the vast majority of DOIs in this corpus -- only \
58 of 6,370+ DOIs were manually verified against original papers, and 3 confirmed \
identity-level errors were found among them"
.to_string(),
"atmosphere agreement/conflict, when present, excludes free-text values -- most raw \
atmosphere data in this corpus is unstructured and never contributes to a verdict"
.to_string(),
];
if assessment.has_multiple_operation_shapes {
limitations.push(
"independent literature reports for this exact route describe different numbers \
of heating steps -- any single operation shape's agreement/conflict reflects only \
one of several reported process structures for this route, not a single settled \
procedure"
.to_string(),
);
}
if assessment.has_any_conflict() {
limitations.push(
"at least one field shows independent DOIs reporting different values at the same \
route and operation position -- disclosed as a real disagreement, never resolved \
or averaged"
.to_string(),
);
}
limitations
}