use crate::smiles::detector::FunctionalGroup;
use crate::types::OrganicInorganic;
#[derive(Debug, Clone)]
pub struct HeadingHint {
pub chapter: u8,
pub heading: Option<u16>,
pub rationale: &'static str,
pub confidence: f32,
}
static PRIORITY_MAP: &[(FunctionalGroup, u8, u16, &str, f32)] = &[
(
FunctionalGroup::Anhydride,
29, 2915,
"Acid anhydride → HS 29.15–29.17 (acyclic/aromatic acid anhydrides); \
use 29.17 for aromatic anhydrides",
0.65,
),
(
FunctionalGroup::Isocyanate,
29, 2929,
"Isocyanate / carbodiimide → HS 29.29",
0.70,
),
(
FunctionalGroup::Epoxide,
29, 2910,
"Epoxide → HS 29.10",
0.70,
),
(
FunctionalGroup::SulphonicAcid,
29, 2904,
"Organo-sulphonic acid → HS 29.04 (sulphonated derivatives)",
0.68,
),
(
FunctionalGroup::Nitrile,
29, 2926,
"Nitrile → HS 29.26",
0.70,
),
(
FunctionalGroup::Phosphate,
29, 2920,
"Organophosphate / phosphonate ester → HS 29.20",
0.62,
),
(
FunctionalGroup::Amide,
29, 2924,
"Amide → HS 29.24 (amide-function compounds)",
0.67,
),
(
FunctionalGroup::CarboxylicAcid,
29, 2915,
"Carboxylic acid → HS 29.15 (acyclic), 29.16 (cyclic), 29.17 (aromatic), \
or 29.18 (other with additional functions); heading depends on chain length / ring",
0.60,
),
(
FunctionalGroup::Ester,
29, 2915,
"Ester → HS 29.15–29.17 (depends on parent acid type and chain length)",
0.55,
),
(
FunctionalGroup::Aldehyde,
29, 2912,
"Aldehyde → HS 29.12",
0.67,
),
(
FunctionalGroup::Ketone,
29, 2914,
"Ketone / quinone → HS 29.14",
0.67,
),
(
FunctionalGroup::Phenol,
29, 2907,
"Phenol → HS 29.07",
0.67,
),
(
FunctionalGroup::Alcohol,
29, 2905,
"Alcohol → HS 29.05 (acyclic) or 29.06 (cyclic); \
polyols may fall under 29.05 subheading",
0.60,
),
(
FunctionalGroup::Thiol,
29, 2930,
"Thiol (mercaptan) → HS 29.30 (organo-sulphur compounds)",
0.65,
),
(
FunctionalGroup::Sulphide,
29, 2930,
"Thioether / sulphide → HS 29.30 (organo-sulphur compounds)",
0.65,
),
(
FunctionalGroup::Amine,
29, 2921,
"Amine → HS 29.21",
0.63,
),
(
FunctionalGroup::Nitro,
29, 2904,
"Nitro / nitroso compound → HS 29.04",
0.60,
),
(
FunctionalGroup::Ether,
29, 2909,
"Ether → HS 29.09",
0.63,
),
(
FunctionalGroup::Halide,
29, 2903,
"Organohalide → HS 29.03",
0.65,
),
(
FunctionalGroup::AromaticRing,
29, 0, "Aromatic compound → Chapter 29; heading depends on substituents",
0.40,
),
];
pub fn map_to_heading(
groups: &[FunctionalGroup],
organic_class: &OrganicInorganic,
) -> HeadingHint {
if matches!(organic_class, OrganicInorganic::Inorganic) {
return HeadingHint {
chapter: 28,
heading: None,
rationale: "Inorganic compound → Chapter 28; \
heading depends on element / salt type",
confidence: 0.55,
};
}
if matches!(organic_class, OrganicInorganic::Organometallic) {
return HeadingHint {
chapter: 29,
heading: Some(2931),
rationale: "Organometallic compound → HS 29.31",
confidence: 0.62,
};
}
for &(group, chapter, heading_code, rationale, confidence) in PRIORITY_MAP {
if groups.contains(&group) {
let heading = if heading_code == 0 { None } else { Some(heading_code) };
return HeadingHint { chapter, heading, rationale, confidence };
}
}
HeadingHint {
chapter: 29,
heading: None,
rationale: "Organic compound with no detected functional groups → \
Chapter 29 (unsubstituted hydrocarbon) or Chapter 38",
confidence: 0.35,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn hint(groups: &[FunctionalGroup]) -> HeadingHint {
map_to_heading(groups, &OrganicInorganic::Organic)
}
#[test]
fn inorganic_gives_ch28() {
let h = map_to_heading(&[], &OrganicInorganic::Inorganic);
assert_eq!(h.chapter, 28);
assert!(h.heading.is_none());
}
#[test]
fn organometallic_gives_2931() {
let h = map_to_heading(&[], &OrganicInorganic::Organometallic);
assert_eq!(h.heading, Some(2931));
}
#[test]
fn anhydride_wins_over_acid() {
let h = hint(&[FunctionalGroup::Anhydride, FunctionalGroup::CarboxylicAcid]);
assert_eq!(h.heading, Some(2915));
assert!(h.rationale.to_lowercase().contains("anhydride"));
}
#[test]
fn aldehyde_maps_to_2912() {
let h = hint(&[FunctionalGroup::Aldehyde]);
assert_eq!(h.heading, Some(2912));
}
#[test]
fn ketone_maps_to_2914() {
let h = hint(&[FunctionalGroup::Ketone]);
assert_eq!(h.heading, Some(2914));
}
#[test]
fn alcohol_maps_to_2905() {
let h = hint(&[FunctionalGroup::Alcohol]);
assert_eq!(h.heading, Some(2905));
}
#[test]
fn nitrile_maps_to_2926() {
let h = hint(&[FunctionalGroup::Nitrile]);
assert_eq!(h.heading, Some(2926));
}
#[test]
fn amine_maps_to_2921() {
let h = hint(&[FunctionalGroup::Amine]);
assert_eq!(h.heading, Some(2921));
}
#[test]
fn halide_maps_to_2903() {
let h = hint(&[FunctionalGroup::Halide]);
assert_eq!(h.heading, Some(2903));
}
#[test]
fn no_groups_gives_low_confidence() {
let h = hint(&[]);
assert!(h.confidence < 0.50);
}
#[test]
fn isocyanate_maps_to_2929() {
let h = hint(&[FunctionalGroup::Isocyanate]);
assert_eq!(h.heading, Some(2929));
}
#[test]
fn epoxide_maps_to_2910() {
let h = hint(&[FunctionalGroup::Epoxide]);
assert_eq!(h.heading, Some(2910));
}
}