use serde::{Deserialize, Serialize};
use crate::types::{IntendedUse, OrganicInorganic, PhysicalForm, SubstanceIdentifier};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ClassificationState {
pub identifier: SubstanceIdentifier,
pub is_mixture: Option<bool>,
pub component_count: Option<usize>,
pub components: Vec<PartialComponent>,
pub current_component_index: usize,
pub physical_form: Option<PhysicalForm>,
pub purity_pct: Option<f64>,
pub organic_inorganic: Option<OrganicInorganic>,
pub chapter_hint: Option<String>,
pub intended_use: Option<IntendedUse>,
pub detected_functional_groups: Vec<String>,
pub is_complete: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct PartialComponent {
pub identifier: SubstanceIdentifier,
pub weight_fraction_pct: Option<f64>,
pub is_solvent: bool,
}
impl ClassificationState {
pub fn has_identifier(&self) -> bool {
!self.identifier.is_empty()
}
pub fn confidence_estimate(&self) -> f32 {
let mut score: f32 = 0.0;
if self.identifier.cas.is_some() {
score += 0.40; } else if self.identifier.smiles.is_some() {
score += 0.30;
} else if self.identifier.iupac_name.is_some()
|| self.identifier.inchi.is_some()
|| self.identifier.inchi_key.is_some()
{
score += 0.25;
}
if self.physical_form.is_some() {
score += 0.15;
}
if self.organic_inorganic.is_some() {
score += 0.15;
}
if self.chapter_hint.is_some() {
score += 0.15;
}
if matches!(
self.intended_use,
Some(IntendedUse::Pharmaceutical) | Some(IntendedUse::Agricultural)
) {
score += 0.10;
}
if self.is_mixture == Some(true) {
let expected = self.component_count.unwrap_or(0);
let filled = self.components.iter().filter(|c| !c.identifier.is_empty()).count();
if expected > 0 && filled >= expected {
score += 0.05;
}
}
score.min(1.0)
}
pub fn all_components_filled(&self) -> bool {
if self.is_mixture != Some(true) {
return true;
}
let expected = self.component_count.unwrap_or(0);
if expected == 0 {
return false;
}
self.components.len() >= expected
&& self.components.iter().all(|c| !c.identifier.is_empty())
}
pub fn current_component_has_identifier(&self) -> bool {
self.components
.get(self.current_component_index)
.map(|c| !c.identifier.is_empty())
.unwrap_or(false)
}
pub fn current_component_has_fraction(&self) -> bool {
self.components
.get(self.current_component_index)
.map(|c| c.weight_fraction_pct.is_some())
.unwrap_or(false)
}
}