use crate::foundation::Unit;
use std::collections::HashMap;
pub fn canonical_mnemonic(raw: &str) -> String {
let key = raw.trim().to_ascii_uppercase();
let canonical = match key.as_str() {
"PHIE" | "PHI" | "PHI_E" | "EFFPHI" | "PHIEF" => "PHIE",
"PHIT" | "PHI_T" | "TOTPHI" => "PHIT",
"NPHI" | "TNPH" | "NEU" | "CNL" => "NPHI",
"SW" | "SWE" | "SUWI" | "SW_E" | "SWT" => "SW",
"GR" | "GRC" | "SGR" | "CGR" | "GAMMA" => "GR",
"VSH" | "VCL" | "VSHALE" | "VCLAY" | "VSHGR" => "VSH",
"PERM" | "K" | "KLOGH" | "KH" | "PERMH" => "PERM",
"RHOB" | "DEN" | "DENS" | "ZDEN" => "RHOB",
"DT" | "AC" | "SONIC" | "DTCO" => "DT",
"RT" | "RES" | "ILD" | "LLD" | "RDEP" => "RT",
"NTG" | "NET_GROSS" | "N_G" => "NTG",
other => return other.to_string(),
};
canonical.to_string()
}
pub fn parse_length_unit(s: &str) -> Option<Unit> {
match s.trim().to_ascii_lowercase().as_str() {
"m" | "metre" | "metres" | "meter" | "meters" => Some(Unit::Metres),
"ft" | "f" | "feet" | "foot" => Some(Unit::Feet),
_ => None,
}
}
pub fn is_percent_unit(s: &str) -> bool {
matches!(
s.trim().to_ascii_lowercase().as_str(),
"%" | "percent" | "pct" | "p.u." | "pu"
)
}
pub fn harmonise_fraction(value: f64, unit: &str) -> f64 {
if is_percent_unit(unit) {
value / 100.0
} else {
value
}
}
pub fn harmonise_length(value: f64, from: Unit, to: Unit) -> f64 {
from.convert(value, to)
}
#[derive(Debug, Clone, Default)]
pub struct NameMap {
map: HashMap<String, String>,
}
impl NameMap {
pub fn new() -> Self {
Self::default()
}
pub fn from_pairs(pairs: impl IntoIterator<Item = (String, String)>) -> Self {
let mut m = Self::new();
for (alias, canonical) in pairs {
m.insert(alias, canonical);
}
m
}
pub fn insert(&mut self, alias: impl Into<String>, canonical: impl Into<String>) {
self.map
.insert(alias.into().trim().to_ascii_lowercase(), canonical.into());
}
pub fn canonical(&self, name: &str) -> String {
self.map
.get(name.trim().to_ascii_lowercase().as_str())
.cloned()
.unwrap_or_else(|| name.trim().to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn mnemonic_aliases_resolve_case_insensitively() {
assert_eq!(canonical_mnemonic("phi"), "PHIE");
assert_eq!(canonical_mnemonic(" Suwi "), "SW");
assert_eq!(canonical_mnemonic("VCL"), "VSH");
assert_eq!(canonical_mnemonic("NPHI"), "NPHI");
assert_eq!(canonical_mnemonic("PHIT"), "PHIT");
assert_eq!(canonical_mnemonic(" custom "), "CUSTOM");
}
#[test]
fn length_units_parse() {
assert_eq!(parse_length_unit("M"), Some(Unit::Metres));
assert_eq!(parse_length_unit("feet"), Some(Unit::Feet));
assert_eq!(parse_length_unit("furlong"), None);
}
#[test]
fn percent_harmonises_to_fraction() {
assert!(is_percent_unit("%"));
assert!(!is_percent_unit("v/v"));
assert_relative_eq!(harmonise_fraction(25.0, "%"), 0.25);
assert_relative_eq!(harmonise_fraction(0.25, "v/v"), 0.25);
assert!(harmonise_fraction(f64::NAN, "%").is_nan());
}
#[test]
fn length_harmonises_via_unit() {
assert_relative_eq!(harmonise_length(100.0, Unit::Feet, Unit::Metres), 30.48);
}
#[test]
fn name_map_is_identity_for_unknowns() {
let m = NameMap::from_pairs([
("Brent Gp".to_string(), "Brent".to_string()),
("DUNLIN GROUP".to_string(), "Dunlin".to_string()),
]);
assert_eq!(m.canonical("brent gp"), "Brent"); assert_eq!(m.canonical("Dunlin Group"), "Dunlin");
assert_eq!(m.canonical(" Statfjord "), "Statfjord"); }
}