use super::super::*;
use super::{geo_vote_classifier, make_result};
#[test]
fn test_coordinates_longitude_detected() {
let values: Vec<String> = vec!["-74.0060", "151.2093", "-0.1278", "139.6917", "2.3522"]
.into_iter()
.map(String::from)
.collect();
let result = disambiguate_coordinates(&values);
assert_eq!(result, Some("geography.coordinate.longitude".to_string()));
}
#[test]
fn test_coordinates_latitude_detected() {
let values: Vec<String> = vec!["40.7128", "-33.8688", "51.5074", "35.6762", "-22.9068"]
.into_iter()
.map(String::from)
.collect();
let result = disambiguate_coordinates(&values);
assert_eq!(result, Some("geography.coordinate.latitude".to_string()));
}
#[test]
fn test_numeric_postal_code_detection() {
let values: Vec<String> = vec![
"10001", "90210", "30301", "60601", "02101", "75001", "33101", "94102", "20001", "98101",
]
.into_iter()
.map(String::from)
.collect();
let results: Vec<ClassificationResult> = values
.iter()
.map(|_| ClassificationResult {
label: "geography.address.postal_code".to_string(),
confidence: 0.6,
all_scores: vec![],
})
.collect();
let votes = [
("geography.address.postal_code".to_string(), 6),
("representation.numeric.integer_number".to_string(), 4),
];
let top_labels: Vec<&str> = votes.iter().map(|(l, _)| l.as_str()).collect();
let result = disambiguate_numeric(&values, &results, &top_labels);
assert!(result.is_some());
let (label, _rule) = result.unwrap();
assert_eq!(label, "geography.address.postal_code");
}
#[test]
fn test_strip_locale_suffix_4level_country() {
let (base, locale) = strip_locale_suffix("geography.address.postal_code.EN_US");
assert_eq!(base, "geography.address.postal_code");
assert_eq!(locale, Some("EN_US"));
}
#[test]
fn test_header_hint_postal() {
assert_eq!(header_hint("zip"), Some("geography.address.postal_code"));
assert_eq!(
header_hint("zip_code"),
Some("geography.address.postal_code")
);
assert_eq!(
header_hint("Postal Code"),
Some("geography.address.postal_code")
);
assert_eq!(
header_hint("postcode"),
Some("geography.address.postal_code")
);
}
#[test]
fn coord_veto_corroborates_real_coordinate_headers() {
for h in [
"lat",
"latitude",
"lon",
"lng",
"long",
"longitude",
"lat_dd",
"y_lat",
"decimalLatitude",
"gps_lat",
"coord_x",
"wgs84_lat",
"LATITUDE",
"Longitude",
] {
assert!(header_corroborates_coordinate(h), "should corroborate: {h}");
}
}
#[test]
fn coord_veto_does_not_corroborate_false_friends_or_quantities() {
for h in [
"mag",
"mean",
"std",
"rms",
"error",
"depth",
"score",
"rate",
"probability",
"latency",
"translate",
"correlation",
"plate",
"population",
"magnitude",
"temperature",
"elevation_error",
] {
assert!(
!header_corroborates_coordinate(h),
"should NOT corroborate: {h}"
);
}
}
#[test]
fn coord_veto_value_gate_requires_numeric() {
assert!(values_look_like_generic_decimals(&[
"0.12".into(),
"-3.4".into(),
"51.5".into(),
"2.0".into(),
"9.9".into()
]));
assert!(!values_look_like_generic_decimals(&[
"alpha".into(),
"beta".into(),
"gamma".into()
]));
assert!(!values_look_like_generic_decimals(&[]));
}
#[test]
fn coord_veto_is_a_default_on_header_hint() {
assert!(!rhh::is_disabled("header_hint_coord_veto"));
}
#[test]
fn state_code_header_corroboration() {
for h in [
"State",
"Provider State",
"DL State",
"ADDRESS STATE",
"province",
] {
assert!(header_corroborates_state(h), "should corroborate: {h}");
}
for h in [
"statement",
"status",
"real estate",
"estate",
"country",
"city",
] {
assert!(!header_corroborates_state(h), "should NOT corroborate: {h}");
}
}
#[test]
fn region_header_corroboration() {
for h in [
"Region",
"County",
"district",
"borough",
"work_location_borough",
"province",
] {
assert!(header_corroborates_region(h), "should corroborate: {h}");
}
for h in ["city", "town", "name", "regional_office", "districting"] {
assert!(
!header_corroborates_region(h),
"should NOT corroborate: {h}"
);
}
}
#[test]
fn state_code_value_vocab_gate() {
assert!(values_look_like_state_codes(&[
"GA".into(),
"FL".into(),
"NM".into(),
"TX".into(),
]));
assert!(values_look_like_state_codes(&[
"ca".into(),
"ny".into(),
"wa".into(),
]));
assert!(values_look_like_state_codes(&[
"NSW".into(),
"VIC".into(),
"QLD".into(),
]));
assert!(!values_look_like_state_codes(&[
"California".into(),
"Florida".into(),
"Texas".into(),
]));
assert!(!values_look_like_state_codes(&[
"GB".into(),
"FR".into(),
"DE".into(),
"JP".into(),
"CN".into(),
]));
assert!(!values_look_like_state_codes(&["TX".into(), "FL".into()]));
}
#[test]
fn state_code_promote_is_a_default_on_header_hint() {
assert!(!rhh::is_disabled("header_hint_state_code_promote"));
}
#[test]
fn country_code_corroboration_is_default_on() {
assert!(!rhh::is_disabled("country_code_corroboration"));
}
#[test]
fn geo_code_membership_vote_is_default_on() {
assert!(!rhh::is_disabled("geo_code_membership_vote"));
}
#[test]
fn geo_vote_flips_country_dominant_state_code_to_country() {
let cc = geo_vote_classifier();
let vals: Vec<String> = [
"IT", "GB", "NL", "ES", "FR", "CN", "SE", "DE", "IN", "US-DE",
]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("jurisdiction", &vals, "geography.location.state_code", 0.99)
.unwrap();
assert_eq!(r.label, "geography.location.country_code");
assert_eq!(
r.disambiguation_rule.as_deref(),
Some("geo_code_membership_vote:geography.location.state_code")
);
}
#[test]
fn geo_vote_keeps_genuine_us_states() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["CA", "TX", "NY", "FL", "DE", "IN"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("state", &vals, "geography.location.state_code", 0.90)
.unwrap();
assert_ne!(r.label, "geography.location.country_code");
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_code_membership_vote:geography.location.state_code")
);
}
#[test]
fn geo_vote_leaves_ambiguous_columns_alone() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["DE", "IN", "CA", "DE", "IN", "CA"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("code", &vals, "geography.location.country_code", 0.80)
.unwrap();
assert_eq!(r.label, "geography.location.country_code");
assert!(
!r.disambiguation_applied
|| r.disambiguation_rule.as_deref()
!= Some("geo_code_membership_vote:geography.location.country_code")
);
}
#[test]
fn geo_vote_keeps_canadian_provinces_as_state_not_country() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["NL", "NL", "NL", "SK", "YT", "QC", "ON"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("state", &vals, "geography.location.state_code", 0.90)
.unwrap();
assert_ne!(r.label, "geography.location.country_code");
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_code_membership_vote:geography.location.state_code")
);
}
#[test]
fn geo_nonmembership_demotion_is_default_on() {
assert!(!rhh::is_disabled("geo_code_nonmembership_demotion"));
}
#[test]
fn geo_nonmembership_demotes_nonlocation_short_codes_to_word() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["OT", "EQ", "MH", "SP", "PL", "OT", "EQ", "OT"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("work_type", &vals, "geography.location.state_code", 0.90)
.unwrap();
assert_eq!(r.label, "representation.text.word");
assert_eq!(
r.disambiguation_rule.as_deref(),
Some("geo_code_nonmembership_demotion:geography.location.state_code")
);
}
#[test]
fn geo_nonmembership_keeps_hyphenated_iso3166_2_subdivisions() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["CA-ON", "CA-BC", "CA-QC", "GB-ENG", "DE-BW", "FR-IDF"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("region", &vals, "geography.location.state_code", 0.90)
.unwrap();
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_code_nonmembership_demotion:geography.location.state_code")
);
assert_eq!(r.label, "geography.location.region");
assert_eq!(
r.disambiguation_rule.as_deref(),
Some("geo_subdivision_membership_promote:geography.location.state_code")
);
}
#[test]
fn geo_subdivision_promote_is_default_on() {
assert!(!rhh::is_disabled("geo_subdivision_membership_promote"));
}
#[test]
fn geo_subdivision_promote_recovers_iso3166_2_to_region() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["US-PA", "US-KS", "US-AK", "GB-ENG", "CA-ON", "US-CA"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense(
"iso_region",
&vals,
"representation.identifier.alphanumeric_id",
0.60,
)
.unwrap();
assert_eq!(r.label, "geography.location.region");
assert_eq!(
r.disambiguation_rule.as_deref(),
Some("geo_subdivision_membership_promote:representation.identifier.alphanumeric_id")
);
}
#[test]
fn geo_subdivision_promote_keeps_nonmember_hyphen_codes() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["OS-10", "AB-99", "XX-01", "ZZ-ZZ", "QW-42", "OS-11"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense(
"code",
&vals,
"representation.identifier.alphanumeric_id",
0.80,
)
.unwrap();
assert_ne!(r.label, "geography.location.region");
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_subdivision_membership_promote:representation.identifier.alphanumeric_id")
);
}
#[test]
fn geo_nonmembership_keeps_constant_country_column() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["GB", "GB", "GB", "GB", "GB", "GB", "GB"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("country", &vals, "geography.location.country_code", 0.95)
.unwrap();
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_code_nonmembership_demotion:geography.location.country_code")
);
assert_eq!(r.label, "geography.location.country_code");
}
#[test]
fn geo_nonmembership_keeps_alpha3_country_column() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["FRA", "GBR", "USA", "DEU", "ITA", "ESP", "CHN", "IND"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("country", &vals, "geography.location.country_code", 0.95)
.unwrap();
assert!(
r.label.starts_with("geography."),
"alpha-3 country column must stay a geography type, got {}",
r.label
);
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_code_nonmembership_demotion:geography.location.country_code")
);
}
#[test]
fn geo_nonmembership_keeps_genuine_country_column() {
let cc = geo_vote_classifier();
let vals: Vec<String> = ["GB", "FR", "DE", "IT", "CN", "ES", "NL", "SE"]
.into_iter()
.map(String::from)
.collect();
let r = cc
.compose_from_sense("country", &vals, "geography.location.country_code", 0.95)
.unwrap();
assert_eq!(r.label, "geography.location.country_code");
assert_ne!(
r.disambiguation_rule.as_deref(),
Some("geo_code_nonmembership_demotion:geography.location.country_code")
);
}
#[test]
fn postal_veto_corroborates_real_postal_headers() {
for h in [
"zip",
"ZIP",
"zip_code",
"zipcode",
"business_zip",
"billing zip",
"postal_code",
"PostalCode",
"postcode",
"post_code",
"PLZ",
"cep",
"pincode",
"pin_code_area_pincode",
] {
assert!(header_corroborates_postal(h), "should corroborate: {h}");
}
}
#[test]
fn postal_veto_does_not_corroborate_false_friends_or_quantities() {
for h in [
"zipper",
"unzip",
"gzip_size",
"averageDailyVolume10Day",
"fullTimeEmployees",
"totalCurrentLiabilities",
"conversation_no",
"voteId",
"d_week_seq",
"destination_x",
"sId",
"1001",
"code",
"postage_paid",
] {
assert!(
!header_corroborates_postal(h),
"should NOT corroborate: {h}"
);
}
}
#[test]
fn postal_veto_value_gate_requires_bare_integers_without_leading_zeros() {
assert!(values_look_like_generic_integers(&[
"1191".into(),
"1497".into(),
"2200".into(),
"5322".into()
]));
assert!(!values_look_like_generic_integers(&[
"01219".into(),
"1191".into(),
"1497".into()
]));
assert!(!values_look_like_generic_integers(&[
"12.5".into(),
"9.1".into(),
"3.3".into()
]));
assert!(!values_look_like_generic_integers(&[
"12345-6789".into(),
"98765-4321".into()
]));
assert!(!values_look_like_generic_integers(&[]));
}
#[test]
fn postal_veto_is_a_default_on_header_hint() {
assert!(!rhh::is_disabled("header_hint_postal_veto"));
}
#[test]
fn ac01_r25_http_status_gate_preserves_real_postal_codes() {
let values: Vec<String> = vec![
"10001", "90210", "33139", "60601", "02134", "94102", "30308",
]
.into_iter()
.map(String::from)
.collect();
let result = value_sharpen(&values, "representation.numeric.integer_number", 0.8, None);
assert!(result.is_some(), "R12 should fire on 5-digit postal codes");
let (label, _) = result.unwrap();
assert_eq!(label, "geography.address.postal_code");
}
#[test]
fn ac01_r25_http_status_gate_preserves_3digit_postal() {
let values: Vec<String> = vec!["601", "900", "750", "602", "850", "700", "801"]
.into_iter()
.map(String::from)
.collect();
let result = value_sharpen(&values, "representation.numeric.integer_number", 0.8, None);
if let Some((label, _)) = &result {
assert_eq!(
label, "geography.address.postal_code",
"3-digit values outside 100-599 should still be postal_code"
);
}
}
#[test]
fn state_hint_uses_live_region_leaf() {
assert_ne!(header_hint("state"), Some("geography.location.state"));
assert_ne!(header_hint("province"), Some("geography.location.state"));
}
#[test]
fn v15_subcountry_not_mapped_to_state() {
assert_eq!(header_hint("subcountry"), None);
}
#[test]
fn test_detect_locale_calling_code_uk() {
let yaml = r#"
geography.contact.calling_code:
title: International Calling Code
designation: locale_specific
tier: [VARCHAR, contact]
release_priority: 3
samples: ["+1"]
validation:
type: string
pattern: "^\\+?[0-9]{1,4}$"
validation_by_locale:
EN_US:
type: string
pattern: "^\\+?1$"
EN_GB:
type: string
pattern: "^\\+?44$"
DE:
type: string
pattern: "^\\+?49$"
FR:
type: string
pattern: "^\\+?33$"
"#;
let mut taxonomy = Taxonomy::from_yaml(yaml).unwrap();
taxonomy.compile_locale_validators();
let values: Vec<String> = vec!["+44", "44", "+44", "+44", "44"]
.into_iter()
.map(String::from)
.collect();
let locale =
detect_locale_from_validation(&values, "geography.contact.calling_code", &taxonomy);
assert_eq!(
locale,
Some("EN_GB".to_string()),
"+44 calling codes should detect EN_GB"
);
}
#[test]
fn test_detect_locale_calling_code_de() {
let yaml = r#"
geography.contact.calling_code:
title: International Calling Code
designation: locale_specific
tier: [VARCHAR, contact]
release_priority: 3
samples: ["+1"]
validation:
type: string
pattern: "^\\+?[0-9]{1,4}$"
validation_by_locale:
EN_US:
type: string
pattern: "^\\+?1$"
EN_GB:
type: string
pattern: "^\\+?44$"
DE:
type: string
pattern: "^\\+?49$"
"#;
let mut taxonomy = Taxonomy::from_yaml(yaml).unwrap();
taxonomy.compile_locale_validators();
let values: Vec<String> = vec!["+49", "49", "+49"]
.into_iter()
.map(String::from)
.collect();
let locale =
detect_locale_from_validation(&values, "geography.contact.calling_code", &taxonomy);
assert_eq!(
locale,
Some("DE".to_string()),
"+49 calling codes should detect DE"
);
}
#[test]
fn test_geography_protection_person_name_hints() {
assert!(
PERSON_NAME_HINTS.contains(&"identity.person.full_name"),
"PERSON_NAME_HINTS should include full_name"
);
assert!(
PERSON_NAME_HINTS.contains(&"identity.person.last_name"),
"PERSON_NAME_HINTS should include last_name"
);
assert!(
PERSON_NAME_HINTS.contains(&"identity.person.first_name"),
"PERSON_NAME_HINTS should include first_name"
);
assert!(
!PERSON_NAME_HINTS.contains(&"identity.person.email"),
"email should NOT be in PERSON_NAME_HINTS"
);
assert!(
!PERSON_NAME_HINTS.contains(&"identity.person.phone_number"),
"phone_number should NOT be in PERSON_NAME_HINTS"
);
}
#[test]
fn test_location_types_module_level() {
assert!(
LOCATION_TYPES.contains(&"geography.location.city"),
"LOCATION_TYPES should include city"
);
assert!(
LOCATION_TYPES.contains(&"geography.location.country"),
"LOCATION_TYPES should include country"
);
assert!(
LOCATION_TYPES.contains(&"geography.location.region"),
"LOCATION_TYPES should include region"
);
assert!(
LOCATION_TYPES.contains(&"geography.location.state"),
"LOCATION_TYPES should include state"
);
assert!(
LOCATION_TYPES.contains(&"geography.location.continent"),
"LOCATION_TYPES should include continent"
);
}
#[test]
fn test_geo_override_same_domain() {
assert!(
LOCATION_TYPES.contains(&"geography.location.city"),
"city must be a location type"
);
assert!(
LOCATION_TYPES.contains(&"geography.location.country"),
"country must be a location type"
);
}
#[test]
fn ac01_bitcoin_address_not_captured_by_address_hint() {
assert_eq!(
header_hint("bitcoin_address"),
None,
"bitcoin_address must not match the address keyword"
);
assert_eq!(
header_hint("btc_address"),
None,
"btc_address must not match the address keyword"
);
assert_eq!(
header_hint("crypto_address"),
None,
"crypto_address must not match the address keyword"
);
assert_eq!(
header_hint("wallet_address"),
None,
"wallet_address must not match the address keyword"
);
assert_eq!(
header_hint("street_address"),
Some("geography.address.street_address"),
"street_address should still return street_address"
);
assert_eq!(
header_hint("full_address"),
Some("geography.address.full_address"),
"full_address should still return full_address"
);
assert_eq!(
header_hint("home_address"),
Some("geography.address.full_address"),
"home_address should still return full_address"
);
}
#[test]
fn ac04_icao_header_hint() {
assert_eq!(
header_hint("icao"),
Some("geography.transportation.icao_code"),
"icao should hint to icao_code"
);
assert_eq!(
header_hint("ICAO"),
Some("geography.transportation.icao_code"),
"ICAO (uppercase) should hint to icao_code"
);
assert_eq!(
header_hint("icao_code"),
Some("geography.transportation.icao_code"),
"icao_code should hint to icao_code"
);
}
#[test]
fn debug_icao_sharpen_override() {
let mock = crate::inference::MockClassifier::new("geography.transportation.unlocode");
let cc = ColumnClassifier::with_defaults(Box::new(mock));
let mut result = make_result("geography.transportation.unlocode", 0.919);
let sample: Vec<String> = vec!["EGLL".into(), "KJFK".into(), "LFPG".into()];
let hint = header_hint("icao");
assert_eq!(hint, Some("geography.transportation.icao_code"));
cc.apply_header_sharpen(&mut result, "icao", &sample);
eprintln!("result.label = {}", result.label);
eprintln!("result.rule = {:?}", result.disambiguation_rule);
assert_eq!(
result.label, "geography.transportation.icao_code",
"icao hint must override unlocode@0.919 via same-category path"
);
}
#[test]
fn test_r21_coordinate_gate_demotes_out_of_range() {
let values: Vec<String> = vec![
"127.013", "573.817", "201.998", "10.0", "224.419", "177.684", "97.335", "671.0", "450.2",
"300.1",
]
.into_iter()
.map(String::from)
.collect();
let result = value_sharpen(&values, "geography.coordinate.longitude", 0.94, None);
assert!(result.is_some(), "R21 should fire for out-of-range values");
let (label, rule) = result.unwrap();
assert_eq!(label, "representation.numeric.decimal_number");
assert!(rule.contains("coordinate_plausibility_gate"));
}
#[test]
fn test_r21_coordinate_gate_keeps_real_coordinates() {
let values: Vec<String> = vec![
"-122.4194",
"139.6917",
"2.3522",
"-73.9857",
"151.2093",
"-43.1729",
"28.9784",
"-3.7038",
"103.8198",
"-46.6333",
]
.into_iter()
.map(String::from)
.collect();
let result = value_sharpen(&values, "geography.coordinate.longitude", 0.95, None);
assert!(
result.is_none()
|| result
.as_ref()
.unwrap()
.1
.contains("coordinate_disambiguation"),
"R21 should not fire for valid coordinates"
);
}
#[test]
fn ac05_country_exact_match_still_hints_country() {
assert_eq!(header_hint("country"), Some("geography.location.country"));
}
#[test]
fn ac05_alpha2_regex_matches_country_codes() {
let codes = ["AU", "US", "GB", "DE", "FR", "JP", "CN", "BR"];
for code in &codes {
assert!(
code.len() == 2 && code.chars().all(|c| c.is_ascii_uppercase()),
"{} should match alpha-2 pattern",
code
);
}
}
#[test]
fn ac05_alpha2_regex_rejects_state_codes() {
let state_codes = ["NSW", "VIC", "QLD", "CA", "NY"];
for code in &state_codes {
let matches = code.len() == 2 && code.chars().all(|c| c.is_ascii_uppercase());
if code.len() == 3 {
assert!(
!matches,
"{} (3-char) should not match alpha-2 pattern",
code
);
}
}
}
#[test]
fn ac05_alpha2_regex_rejects_country_names() {
let names = ["Australia", "United States", "Germany", "France"];
for name in &names {
assert!(
!(name.len() == 2 && name.chars().all(|c| c.is_ascii_uppercase())),
"{} should not match alpha-2 pattern",
name
);
}
}