use crate::engine::SymbolicAnswer;
use crate::event_log::EventLog;
use crate::seed::{lexicon, Lexicon, Meaning, ROLE_MEASUREMENT_UNIT, ROLE_PHYSICAL_DIMENSION};
use crate::solver_handlers::finalize_simple;
pub fn try_incompatible_units(
prompt: &str,
normalized: &str,
log: &mut EventLog,
) -> Option<SymbolicAnswer> {
let (unit_a, dim_a, unit_b, dim_b) = detect_incompatible_unit_pair(normalized)?;
log.append(
"unit_incompatibility",
format!("{unit_a}:{dim_a} vs {unit_b}:{dim_b}"),
);
let body = format!(
"{unit_a} measures {dim_a}; {unit_b} measures {dim_b}. \
These are different physical dimensions and cannot be converted into each other. \
The incompatibility is recorded as a `unit_incompatibility` link in the network."
);
Some(finalize_simple(
prompt,
log,
"unit_incompatibility",
"response:unit_incompatibility",
&body,
1.0,
))
}
fn dimension_label<'a>(lex: &'a Lexicon, unit: &'a Meaning) -> Option<&'a str> {
unit.defined_by
.iter()
.filter_map(|slug| lex.meaning(slug))
.find(|m| m.has_role(ROLE_PHYSICAL_DIMENSION))
.and_then(|dim| dim.word_in("en"))
}
fn contains_unit_word(normalized: &str, unit: &str) -> bool {
if !unit.is_ascii() && !crate::coding::contains_cjk(unit) {
return normalized.contains(unit);
}
let boundary_ok = |ch: Option<char>| ch.map_or(true, |c| !c.is_alphabetic());
let mut search_from = 0;
while let Some(offset) = normalized[search_from..].find(unit) {
let start = search_from + offset;
let end = start + unit.len();
let before = normalized[..start].chars().next_back();
let after = normalized[end..].chars().next();
if boundary_ok(before) && boundary_ok(after) {
return true;
}
search_from = end;
}
false
}
#[allow(clippy::type_complexity)]
fn detect_incompatible_unit_pair(
normalized: &str,
) -> Option<(&'static str, &'static str, &'static str, &'static str)> {
let lex = lexicon();
let mut found: Vec<(&'static str, &'static str)> = Vec::new();
for unit in lex.meanings_with_role(ROLE_MEASUREMENT_UNIT) {
let Some(dim) = dimension_label(lex, unit) else {
continue;
};
if found.iter().any(|(_, seen)| *seen == dim) {
continue; }
let mut matched: Option<&'static str> = None;
for word in unit.words() {
if contains_unit_word(normalized, word) {
matched = Some(word);
break;
}
}
if let Some(word) = matched {
found.push((word, dim));
}
}
if found.len() < 2 {
return None;
}
let (unit_a, dim_a) = found[0];
let (unit_b, dim_b) = found[1];
Some((unit_a, dim_a, unit_b, dim_b))
}