use crate::engine::SymbolicAnswer;
use crate::event_log::EventLog;
use crate::solver_handlers::finalize_simple;
pub fn try_incompatible_units(
prompt: &str,
normalized: &str,
log: &mut EventLog,
) -> Option<SymbolicAnswer> {
let (unit_a, unit_b) = detect_incompatible_unit_pair(normalized)?;
let (dim_a, dim_b) = (dimension_of(unit_a), dimension_of(unit_b));
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,
))
}
const LENGTH_UNITS: &[&str] = &[
"meter",
"metre",
"meters",
"metres",
"km",
"kilometer",
"kilometre",
"cm",
"centimeter",
"centimetre",
"mm",
"millimeter",
"millimetre",
"метр",
"метра",
"метров",
"километр",
"сантиметр",
"миллиметр",
];
const DATA_UNITS: &[&str] = &[
"byte",
"bytes",
"kilobyte",
"kilobytes",
"kb",
"megabyte",
"megabytes",
"mb",
"gigabyte",
"gigabytes",
"gb",
"terabyte",
"terabytes",
"tb",
"bit",
"bits",
"байт",
"байта",
"байтов",
"килобайт",
"мегабайт",
"гигабайт",
"терабайт",
];
const MASS_UNITS: &[&str] = &[
"kilogram",
"kilograms",
"kg",
"gram",
"grams",
"pound",
"pounds",
"килограмм",
"грамм",
];
const TIME_UNITS: &[&str] = &[
"second",
"seconds",
"minute",
"minutes",
"hour",
"hours",
"секунда",
"секунды",
"секунд",
"минута",
"минуты",
"минут",
"час",
"часа",
"часов",
];
const TEMPERATURE_UNITS: &[&str] = &[
"celsius",
"fahrenheit",
"kelvin",
"цельсий",
"фаренгейт",
"кельвин",
];
fn dimension_of(unit: &str) -> &'static str {
if LENGTH_UNITS.contains(&unit) {
return "length";
}
if DATA_UNITS.contains(&unit) {
return "data storage";
}
if MASS_UNITS.contains(&unit) {
return "mass";
}
if TIME_UNITS.contains(&unit) {
return "time";
}
if TEMPERATURE_UNITS.contains(&unit) {
return "temperature";
}
"unknown"
}
fn contains_unit_word(normalized: &str, unit: &str) -> bool {
if !unit.is_ascii() {
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
}
fn detect_incompatible_unit_pair(normalized: &str) -> Option<(&'static str, &'static str)> {
let all_units: &[(&[&str], &'static str)] = &[
(LENGTH_UNITS, "length"),
(DATA_UNITS, "data storage"),
(MASS_UNITS, "mass"),
(TIME_UNITS, "time"),
(TEMPERATURE_UNITS, "temperature"),
];
let mut found: Vec<(&'static str, &'static str)> = Vec::new();
for (units, dim) in all_units {
for unit in *units {
if contains_unit_word(normalized, unit) && !found.iter().any(|(_, d)| d == dim) {
found.push((unit, dim));
}
}
}
if found.len() < 2 {
return None;
}
let (unit_a, dim_a) = found[0];
let (unit_b, dim_b) = found[1];
if dim_a == dim_b {
return None;
}
Some((unit_a, unit_b))
}