Skip to main content

anno_eval/eval/
demographic_bias.rs

1//! Demographic bias evaluation for Named Entity Recognition.
2//!
3//! Measures systematic disparities in NER performance across demographic groups,
4//! including ethnicity, region, name script, and intersectional categories.
5//!
6//! # Research Background
7//!
8//! - Mishra et al. (2020): "Assessing Demographic Bias in NER" - NER models perform
9//!   better on names from specific demographic groups
10//! - Mansfield et al. (2022): "Behind the Mask" - PII masking systems miss
11//!   non-Western names more often
12//! - Loessberg-Zahl (2024): "Multicultural Name Recognition" - NER struggles with
13//!   names not seen in training data
14//! - Li et al. (2022): "HERB" - Regional bias in language models
15//!
16//! # Critical Finding: Character-Based Models Are Less Biased
17//!
18//! Mishra et al. (2020) found that:
19//! - **Debiased word embeddings do NOT help** resolve NER demographic bias
20//! - **Character-based models (ELMo-style) show the least bias** across demographics
21//! - This suggests subword/character representations better generalize to unseen names
22//!
23//! Implications for model selection:
24//! - Prefer character-level or subword models over word-level models for fair NER
25//! - ELMo, BERT (with WordPiece), and similar subword models are better choices
26//! - Pure word2vec or GloVe-based models will exhibit more demographic bias
27//!
28//! # Key Metrics
29//!
30//! - **Recognition Rate**: % of names correctly identified as PERSON
31//! - **Demographic Parity**: Max gap in recognition rates across groups
32//! - **Script Bias**: Performance difference for non-Latin scripts
33//!
34//! # Example
35//!
36//! ```rust
37//! use anno_eval::eval::demographic_bias::{DemographicBiasEvaluator, create_diverse_name_dataset};
38//! use anno::RegexNER;
39//!
40//! let names = create_diverse_name_dataset();
41//! let evaluator = DemographicBiasEvaluator::default();
42//! // let results = evaluator.evaluate_ner(&RegexNER::new(), &names);
43//! ```
44
45use crate::{EntityType, Model};
46use serde::{Deserialize, Serialize};
47use std::collections::HashMap;
48
49// =============================================================================
50// Demographic Categories
51// =============================================================================
52
53/// Ethnicity/origin category for name classification.
54///
55/// Based on US Census categories and extended for global coverage.
56/// These are used for MEASUREMENT only - to detect bias, not to make assumptions.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub enum Ethnicity {
59    /// European/Caucasian names (Smith, Johnson, Mueller)
60    European,
61    /// African-American names (DeShawn, Latoya, Jamal)
62    AfricanAmerican,
63    /// Hispanic/Latino names (García, Rodriguez, Martinez)
64    Hispanic,
65    /// East Asian names (Wang, Kim, Tanaka)
66    EastAsian,
67    /// South Asian names (Patel, Singh, Kumar)
68    SouthAsian,
69    /// Middle Eastern/North African names (Ahmed, Fatima, Hassan)
70    MiddleEastern,
71    /// African names (Okonkwo, Adebayo, Mensah)
72    African,
73    /// Indigenous/Native names (various origins)
74    Indigenous,
75}
76
77/// Geographic region for location/organization bias testing.
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub enum Region {
80    /// North America (US, Canada, Mexico)
81    NorthAmerica,
82    /// Western Europe (UK, France, Germany, etc.)
83    WesternEurope,
84    /// Eastern Europe (Russia, Poland, Ukraine, etc.)
85    EasternEurope,
86    /// East Asia (China, Japan, Korea)
87    EastAsia,
88    /// South Asia (India, Pakistan, Bangladesh)
89    SouthAsia,
90    /// Southeast Asia (Vietnam, Thailand, Indonesia)
91    SoutheastAsia,
92    /// Middle East (Saudi Arabia, Iran, UAE)
93    MiddleEast,
94    /// Africa (Nigeria, Kenya, South Africa)
95    Africa,
96    /// Latin America (Brazil, Argentina, Colombia)
97    LatinAmerica,
98    /// Oceania (Australia, New Zealand)
99    Oceania,
100}
101
102/// Script type for text encoding bias.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
104pub enum Script {
105    /// Latin alphabet (English, Spanish, French)
106    Latin,
107    /// Cyrillic (Russian, Ukrainian, Serbian)
108    Cyrillic,
109    /// Arabic script
110    Arabic,
111    /// Chinese characters (Hanzi/Kanji)
112    Chinese,
113    /// Japanese (Hiragana, Katakana, Kanji mix)
114    Japanese,
115    /// Korean (Hangul)
116    Korean,
117    /// Devanagari (Hindi, Sanskrit)
118    Devanagari,
119    /// Thai script
120    Thai,
121    /// Greek alphabet
122    Greek,
123    /// Hebrew script
124    Hebrew,
125}
126
127// =============================================================================
128// Name Example
129// =============================================================================
130
131/// A name example with demographic metadata for bias testing.
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct NameExample {
134    /// The full name text
135    pub name: String,
136    /// First name only (for partial matching tests)
137    pub first_name: String,
138    /// Last name only
139    pub last_name: String,
140    /// Ethnicity/origin category
141    pub ethnicity: Ethnicity,
142    /// Primary script used
143    pub script: Script,
144    /// Gender if known (for intersectional analysis)
145    pub gender: Option<Gender>,
146    /// Whether this is a common or rare name
147    pub frequency: NameFrequency,
148}
149
150// Re-export the canonical Gender from `anno::core`.
151// This unifies the type system across the anno ecosystem.
152pub use anno::Gender;
153
154/// Name frequency category.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
156pub enum NameFrequency {
157    /// Very common (top 100 in origin country)
158    Common,
159    /// Moderately common
160    Moderate,
161    /// Rare or unusual
162    Rare,
163}
164
165impl NameExample {
166    /// Create a new name example.
167    pub fn new(
168        first_name: &str,
169        last_name: &str,
170        ethnicity: Ethnicity,
171        script: Script,
172        gender: Option<Gender>,
173        frequency: NameFrequency,
174    ) -> Self {
175        Self {
176            name: format!("{} {}", first_name, last_name),
177            first_name: first_name.to_string(),
178            last_name: last_name.to_string(),
179            ethnicity,
180            script,
181            gender,
182            frequency,
183        }
184    }
185}
186
187// =============================================================================
188// Location Example
189// =============================================================================
190
191/// A location example with regional metadata.
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct LocationExample {
194    /// Location name
195    pub name: String,
196    /// Geographic region
197    pub region: Region,
198    /// Primary script used
199    pub script: Script,
200    /// Location type (city, country, landmark)
201    pub location_type: LocationType,
202}
203
204/// Type of location.
205#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
206pub enum LocationType {
207    /// Major city
208    City,
209    /// Country name
210    Country,
211    /// State/Province/Region
212    SubnationalRegion,
213    /// Landmark or geographic feature
214    Landmark,
215}
216
217impl LocationExample {
218    /// Create a new location example.
219    pub fn new(name: &str, region: Region, script: Script, location_type: LocationType) -> Self {
220        Self {
221            name: name.to_string(),
222            region,
223            script,
224            location_type,
225        }
226    }
227}
228
229// =============================================================================
230// Evaluation Results
231// =============================================================================
232
233/// Results of demographic bias evaluation.
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct DemographicBiasResults {
236    /// Overall recognition rate across all names
237    pub overall_recognition_rate: f64,
238    /// Recognition rate by ethnicity
239    pub by_ethnicity: HashMap<String, f64>,
240    /// Recognition rate by script
241    pub by_script: HashMap<String, f64>,
242    /// Recognition rate by gender (intersectional)
243    pub by_gender: HashMap<String, f64>,
244    /// Recognition rate by frequency
245    pub by_frequency: HashMap<String, f64>,
246    /// Maximum gap between any two ethnicity groups
247    pub ethnicity_parity_gap: f64,
248    /// Maximum gap between Latin and non-Latin scripts
249    pub script_bias_gap: f64,
250    /// Intersectional analysis: ethnicity × gender
251    pub intersectional: HashMap<String, f64>,
252    /// Extended intersectional analysis: ethnicity × gender × frequency
253    pub extended_intersectional: HashMap<String, f64>,
254    /// Number of names tested
255    pub total_tested: usize,
256    /// Detailed per-name results
257    pub detailed: Vec<NameResult>,
258    /// Statistical results (if multiple seeds used)
259    pub statistical: Option<crate::eval::bias_config::StatisticalBiasResults>,
260    /// Frequency-weighted results (if enabled)
261    pub frequency_weighted: Option<crate::eval::bias_config::FrequencyWeightedResults>,
262    /// Distribution validation (if enabled)
263    pub distribution_validation: Option<crate::eval::bias_config::DistributionValidation>,
264}
265
266/// Result for a single name.
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct NameResult {
269    /// The name tested
270    pub name: String,
271    /// Whether it was recognized as PERSON
272    pub recognized: bool,
273    /// Confidence if recognized
274    pub confidence: Option<f64>,
275    /// Ethnicity category
276    pub ethnicity: String,
277    /// Script used
278    pub script: String,
279}
280
281/// Results of regional bias evaluation.
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub struct RegionalBiasResults {
284    /// Overall recognition rate
285    pub overall_recognition_rate: f64,
286    /// Recognition rate by region
287    pub by_region: HashMap<String, f64>,
288    /// Recognition rate by script
289    pub by_script: HashMap<String, f64>,
290    /// Maximum gap between regions
291    pub regional_parity_gap: f64,
292    /// Number of locations tested
293    pub total_tested: usize,
294}
295
296// =============================================================================
297// Evaluator
298// =============================================================================
299
300/// Evaluator for demographic bias in NER systems.
301#[derive(Debug, Clone, Default)]
302pub struct DemographicBiasEvaluator {
303    /// Include detailed per-name results
304    pub detailed: bool,
305    /// Configuration for bias evaluation
306    pub config: crate::eval::bias_config::BiasDatasetConfig,
307}
308
309impl DemographicBiasEvaluator {
310    /// Create a new evaluator.
311    pub fn new(detailed: bool) -> Self {
312        Self {
313            detailed,
314            config: crate::eval::bias_config::BiasDatasetConfig::default(),
315        }
316    }
317
318    /// Create evaluator with configuration.
319    pub fn with_config(
320        detailed: bool,
321        config: crate::eval::bias_config::BiasDatasetConfig,
322    ) -> Self {
323        Self { detailed, config }
324    }
325
326    /// Evaluate NER model for demographic bias on names.
327    pub fn evaluate_ner(&self, model: &dyn Model, names: &[NameExample]) -> DemographicBiasResults {
328        let mut by_ethnicity: HashMap<String, (usize, usize)> = HashMap::new();
329        let mut by_script: HashMap<String, (usize, usize)> = HashMap::new();
330        let mut by_gender: HashMap<String, (usize, usize)> = HashMap::new();
331        let mut by_frequency: HashMap<String, (usize, usize)> = HashMap::new();
332        let mut intersectional: HashMap<String, (usize, usize)> = HashMap::new();
333        let mut extended_intersectional: HashMap<String, (usize, usize)> = HashMap::new();
334        let mut detailed_results = Vec::new();
335        let mut total_recognized = 0;
336        let mut recognized_flags = Vec::new();
337        let mut name_strings = Vec::new();
338
339        for name_example in names {
340            // Create test sentence with realistic context
341            let text = create_realistic_sentence(&name_example.name);
342
343            // Extract entities
344            let entities = model.extract_entities(&text, None).unwrap_or_default();
345
346            // Check if name was recognized as PERSON
347            let recognized = entities.iter().any(|e| {
348                e.entity_type == EntityType::Person
349                    && e.extract_text(&text).contains(&name_example.first_name)
350            });
351
352            let confidence = if recognized {
353                entities
354                    .iter()
355                    .find(|e| e.entity_type == EntityType::Person)
356                    .map(|e| e.confidence)
357            } else {
358                None
359            };
360
361            if recognized {
362                total_recognized += 1;
363            }
364            recognized_flags.push(recognized);
365            name_strings.push(name_example.name.clone());
366
367            // Update ethnicity stats
368            let eth_key = format!("{:?}", name_example.ethnicity);
369            let eth_entry = by_ethnicity.entry(eth_key.clone()).or_insert((0, 0));
370            eth_entry.1 += 1;
371            if recognized {
372                eth_entry.0 += 1;
373            }
374
375            // Update script stats
376            let script_key = format!("{:?}", name_example.script);
377            let script_entry = by_script.entry(script_key.clone()).or_insert((0, 0));
378            script_entry.1 += 1;
379            if recognized {
380                script_entry.0 += 1;
381            }
382
383            // Update gender stats
384            if let Some(gender) = name_example.gender {
385                let gender_key = format!("{:?}", gender);
386                let gender_entry = by_gender.entry(gender_key).or_insert((0, 0));
387                gender_entry.1 += 1;
388                if recognized {
389                    gender_entry.0 += 1;
390                }
391            }
392
393            // Update frequency stats
394            let freq_key = format!("{:?}", name_example.frequency);
395            let freq_entry = by_frequency.entry(freq_key).or_insert((0, 0));
396            freq_entry.1 += 1;
397            if recognized {
398                freq_entry.0 += 1;
399            }
400
401            // Update intersectional stats (ethnicity × gender)
402            if let Some(gender) = name_example.gender {
403                let inter_key = format!("{:?}_{:?}", name_example.ethnicity, gender);
404                let inter_entry = intersectional.entry(inter_key).or_insert((0, 0));
405                inter_entry.1 += 1;
406                if recognized {
407                    inter_entry.0 += 1;
408                }
409
410                // Extended intersectional: ethnicity × gender × frequency
411                let ext_inter_key = format!(
412                    "{:?}_{:?}_{:?}",
413                    name_example.ethnicity, gender, name_example.frequency
414                );
415                let ext_inter_entry = extended_intersectional
416                    .entry(ext_inter_key)
417                    .or_insert((0, 0));
418                ext_inter_entry.1 += 1;
419                if recognized {
420                    ext_inter_entry.0 += 1;
421                }
422            }
423
424            if self.detailed {
425                detailed_results.push(NameResult {
426                    name: name_example.name.clone(),
427                    recognized,
428                    confidence: confidence.map(|c| c.value()),
429                    ethnicity: eth_key,
430                    script: script_key,
431                });
432            }
433        }
434
435        // Convert counts to rates
436        let to_rate = |counts: &HashMap<String, (usize, usize)>| -> HashMap<String, f64> {
437            counts
438                .iter()
439                .map(|(k, (correct, total))| {
440                    let rate = if *total > 0 {
441                        *correct as f64 / *total as f64
442                    } else {
443                        0.0
444                    };
445                    (k.clone(), rate)
446                })
447                .collect()
448        };
449
450        let ethnicity_rates = to_rate(&by_ethnicity);
451        let script_rates = to_rate(&by_script);
452        let gender_rates = to_rate(&by_gender);
453        let frequency_rates = to_rate(&by_frequency);
454        let intersectional_rates = to_rate(&intersectional);
455        let extended_intersectional_rates = to_rate(&extended_intersectional);
456
457        // Compute parity gaps
458        let ethnicity_parity_gap = compute_max_gap(&ethnicity_rates);
459
460        // Script bias: compare Latin to non-Latin
461        let latin_rate = script_rates.get("Latin").copied().unwrap_or(0.0);
462        let non_latin_rates: Vec<f64> = script_rates
463            .iter()
464            .filter(|(k, _)| k.as_str() != "Latin")
465            .map(|(_, v)| *v)
466            .collect();
467        let avg_non_latin = if non_latin_rates.is_empty() {
468            latin_rate
469        } else {
470            non_latin_rates.iter().sum::<f64>() / non_latin_rates.len() as f64
471        };
472        let script_bias_gap = (latin_rate - avg_non_latin).abs();
473
474        // Compute frequency-weighted results if enabled
475        let frequency_weighted = if self.config.frequency_weighted {
476            // Create frequency map (simplified - in real implementation, load from data)
477            let mut frequencies = HashMap::new();
478            for name_example in names {
479                let freq = match name_example.frequency {
480                    NameFrequency::Common => 0.5,
481                    NameFrequency::Moderate => 0.3,
482                    NameFrequency::Rare => 0.2,
483                };
484                frequencies.insert(name_example.name.clone(), freq);
485            }
486            Some(crate::eval::bias_config::FrequencyWeightedResults::new(
487                &recognized_flags,
488                &frequencies,
489                &name_strings,
490            ))
491        } else {
492            None
493        };
494
495        // Compute statistical results if multiple seeds
496        let statistical = if self.config.evaluation_seeds.len() > 1 {
497            // For now, compute from single run - in full implementation, run multiple times
498            let values = vec![total_recognized as f64 / names.len().max(1) as f64];
499            Some(
500                crate::eval::bias_config::StatisticalBiasResults::from_values(
501                    &values,
502                    self.config.confidence_level,
503                ),
504            )
505        } else {
506            None
507        };
508
509        // Validate against reference distributions if enabled
510        let distribution_validation = if self.config.validate_distributions {
511            Some(validate_demographic_distribution(&ethnicity_rates))
512        } else {
513            None
514        };
515
516        DemographicBiasResults {
517            overall_recognition_rate: if names.is_empty() {
518                0.0
519            } else {
520                total_recognized as f64 / names.len() as f64
521            },
522            by_ethnicity: ethnicity_rates,
523            by_script: script_rates,
524            by_gender: gender_rates,
525            by_frequency: frequency_rates,
526            ethnicity_parity_gap,
527            script_bias_gap,
528            intersectional: intersectional_rates,
529            extended_intersectional: extended_intersectional_rates,
530            total_tested: names.len(),
531            detailed: detailed_results,
532            statistical,
533            frequency_weighted,
534            distribution_validation,
535        }
536    }
537
538    /// Evaluate NER model for regional bias on locations.
539    pub fn evaluate_locations(
540        &self,
541        model: &dyn Model,
542        locations: &[LocationExample],
543    ) -> RegionalBiasResults {
544        let mut by_region: HashMap<String, (usize, usize)> = HashMap::new();
545        let mut by_script: HashMap<String, (usize, usize)> = HashMap::new();
546        let mut total_recognized = 0;
547
548        for loc in locations {
549            let text = create_realistic_location_sentence(&loc.name);
550            let entities = model.extract_entities(&text, None).unwrap_or_default();
551
552            let recognized = entities.iter().any(|e| {
553                e.entity_type == EntityType::Location && e.extract_text(&text).contains(&loc.name)
554            });
555
556            if recognized {
557                total_recognized += 1;
558            }
559
560            // Update region stats
561            let region_key = format!("{:?}", loc.region);
562            let region_entry = by_region.entry(region_key).or_insert((0, 0));
563            region_entry.1 += 1;
564            if recognized {
565                region_entry.0 += 1;
566            }
567
568            // Update script stats
569            let script_key = format!("{:?}", loc.script);
570            let script_entry = by_script.entry(script_key).or_insert((0, 0));
571            script_entry.1 += 1;
572            if recognized {
573                script_entry.0 += 1;
574            }
575        }
576
577        let to_rate = |counts: &HashMap<String, (usize, usize)>| -> HashMap<String, f64> {
578            counts
579                .iter()
580                .map(|(k, (correct, total))| {
581                    let rate = if *total > 0 {
582                        *correct as f64 / *total as f64
583                    } else {
584                        0.0
585                    };
586                    (k.clone(), rate)
587                })
588                .collect()
589        };
590
591        let region_rates = to_rate(&by_region);
592        let script_rates = to_rate(&by_script);
593        let regional_parity_gap = compute_max_gap(&region_rates);
594
595        RegionalBiasResults {
596            overall_recognition_rate: if locations.is_empty() {
597                0.0
598            } else {
599                total_recognized as f64 / locations.len() as f64
600            },
601            by_region: region_rates,
602            by_script: script_rates,
603            regional_parity_gap,
604            total_tested: locations.len(),
605        }
606    }
607}
608
609/// Compute maximum gap between any two rates.
610fn compute_max_gap(rates: &HashMap<String, f64>) -> f64 {
611    if rates.len() < 2 {
612        return 0.0;
613    }
614
615    let values: Vec<f64> = rates.values().copied().collect();
616    let min = values.iter().copied().fold(f64::INFINITY, f64::min);
617    let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
618
619    max - min
620}
621
622// =============================================================================
623// Realistic Sentence Contexts
624// =============================================================================
625
626/// Create a realistic sentence context for a name.
627///
628/// Uses diverse sentence structures that better reflect real-world usage
629/// patterns than simple templates.
630fn create_realistic_sentence(name: &str) -> String {
631    // Use hash of name to deterministically select sentence template
632    use std::collections::hash_map::DefaultHasher;
633    use std::hash::{Hash, Hasher};
634    let mut hasher = DefaultHasher::new();
635    name.hash(&mut hasher);
636    let hash = hasher.finish();
637
638    let templates = [
639        format!("{} was interviewed by the news team.", name),
640        format!("The award was presented to {} at the ceremony.", name),
641        format!("{} published a groundbreaking research paper.", name),
642        format!("According to {}, the project will launch next month.", name),
643        format!("{} joined the company as a senior executive.", name),
644        format!("The conference featured a keynote speech by {}.", name),
645        format!(
646            "{} received recognition for outstanding contributions.",
647            name
648        ),
649        format!(
650            "In a statement, {} expressed support for the initiative.",
651            name
652        ),
653        format!("{} was elected to the board of directors.", name),
654        format!(
655            "The research team, led by {}, made significant discoveries.",
656            name
657        ),
658        format!("{} announced plans to expand operations globally.", name),
659        format!("During the meeting, {} proposed a new strategy.", name),
660        format!("{} has been appointed as the new department head.", name),
661        format!("The organization honored {} for years of service.", name),
662        format!("{} spoke at the international summit in Geneva.", name),
663        format!("After careful consideration, {} decided to proceed.", name),
664        format!(
665            "{} collaborated with international partners on the project.",
666            name
667        ),
668        format!(
669            "The committee selected {} as the recipient of the award.",
670            name
671        ),
672        format!("{} provided expert testimony during the hearing.", name),
673        format!(
674            "In an exclusive interview, {} discussed future plans.",
675            name
676        ),
677    ];
678
679    templates[hash as usize % templates.len()].clone()
680}
681
682// =============================================================================
683// Diverse Name Dataset
684// =============================================================================
685
686/// Create a diverse name dataset for bias testing.
687///
688/// Includes names from multiple ethnicities, scripts, genders, and frequencies.
689/// Based on census data and common names from various countries.
690pub fn create_diverse_name_dataset() -> Vec<NameExample> {
691    let mut names = Vec::new();
692    names.extend(european_names());
693    names.extend(african_american_names());
694    names.extend(hispanic_names());
695    names.extend(east_asian_names());
696    names.extend(south_asian_names());
697    names.extend(middle_eastern_names());
698    names.extend(african_names());
699    names
700}
701
702// =============================================================================
703// Helper functions for each ethnicity group
704// =============================================================================
705
706/// European names (English, German, French, Italian, Nordic, etc.)
707fn european_names() -> Vec<NameExample> {
708    let mut names = Vec::new();
709    // === European Names ===
710    names.extend(vec![
711        NameExample::new(
712            "James",
713            "Smith",
714            Ethnicity::European,
715            Script::Latin,
716            Some(Gender::Masculine),
717            NameFrequency::Common,
718        ),
719        NameExample::new(
720            "Mary",
721            "Johnson",
722            Ethnicity::European,
723            Script::Latin,
724            Some(Gender::Feminine),
725            NameFrequency::Common,
726        ),
727        NameExample::new(
728            "William",
729            "Williams",
730            Ethnicity::European,
731            Script::Latin,
732            Some(Gender::Masculine),
733            NameFrequency::Common,
734        ),
735        NameExample::new(
736            "Emma",
737            "Brown",
738            Ethnicity::European,
739            Script::Latin,
740            Some(Gender::Feminine),
741            NameFrequency::Common,
742        ),
743        NameExample::new(
744            "Heinrich",
745            "Mueller",
746            Ethnicity::European,
747            Script::Latin,
748            Some(Gender::Masculine),
749            NameFrequency::Moderate,
750        ),
751        NameExample::new(
752            "François",
753            "Dubois",
754            Ethnicity::European,
755            Script::Latin,
756            Some(Gender::Masculine),
757            NameFrequency::Moderate,
758        ),
759        NameExample::new(
760            "Giulia",
761            "Rossi",
762            Ethnicity::European,
763            Script::Latin,
764            Some(Gender::Feminine),
765            NameFrequency::Moderate,
766        ),
767        NameExample::new(
768            "Björk",
769            "Guðmundsdóttir",
770            Ethnicity::European,
771            Script::Latin,
772            Some(Gender::Feminine),
773            NameFrequency::Rare,
774        ),
775    ]);
776    names
777}
778
779/// African-American names
780fn african_american_names() -> Vec<NameExample> {
781    let mut names = Vec::new();
782    // === African-American Names ===
783    names.extend(vec![
784        NameExample::new(
785            "DeShawn",
786            "Jackson",
787            Ethnicity::AfricanAmerican,
788            Script::Latin,
789            Some(Gender::Masculine),
790            NameFrequency::Common,
791        ),
792        NameExample::new(
793            "Latoya",
794            "Williams",
795            Ethnicity::AfricanAmerican,
796            Script::Latin,
797            Some(Gender::Feminine),
798            NameFrequency::Common,
799        ),
800        NameExample::new(
801            "Jamal",
802            "Robinson",
803            Ethnicity::AfricanAmerican,
804            Script::Latin,
805            Some(Gender::Masculine),
806            NameFrequency::Common,
807        ),
808        NameExample::new(
809            "Aaliyah",
810            "Washington",
811            Ethnicity::AfricanAmerican,
812            Script::Latin,
813            Some(Gender::Feminine),
814            NameFrequency::Common,
815        ),
816        NameExample::new(
817            "Tyrone",
818            "Davis",
819            Ethnicity::AfricanAmerican,
820            Script::Latin,
821            Some(Gender::Masculine),
822            NameFrequency::Common,
823        ),
824        NameExample::new(
825            "Imani",
826            "Johnson",
827            Ethnicity::AfricanAmerican,
828            Script::Latin,
829            Some(Gender::Feminine),
830            NameFrequency::Moderate,
831        ),
832        NameExample::new(
833            "Darnell",
834            "Thompson",
835            Ethnicity::AfricanAmerican,
836            Script::Latin,
837            Some(Gender::Masculine),
838            NameFrequency::Moderate,
839        ),
840        NameExample::new(
841            "Shaniqua",
842            "Brown",
843            Ethnicity::AfricanAmerican,
844            Script::Latin,
845            Some(Gender::Feminine),
846            NameFrequency::Rare,
847        ),
848    ]);
849    names
850}
851
852/// Hispanic/Latino names
853fn hispanic_names() -> Vec<NameExample> {
854    vec![
855        // === Hispanic Names ===
856        NameExample::new(
857            "José",
858            "García",
859            Ethnicity::Hispanic,
860            Script::Latin,
861            Some(Gender::Masculine),
862            NameFrequency::Common,
863        ),
864        NameExample::new(
865            "María",
866            "Rodriguez",
867            Ethnicity::Hispanic,
868            Script::Latin,
869            Some(Gender::Feminine),
870            NameFrequency::Common,
871        ),
872        NameExample::new(
873            "Carlos",
874            "Martinez",
875            Ethnicity::Hispanic,
876            Script::Latin,
877            Some(Gender::Masculine),
878            NameFrequency::Common,
879        ),
880        NameExample::new(
881            "Isabella",
882            "Lopez",
883            Ethnicity::Hispanic,
884            Script::Latin,
885            Some(Gender::Feminine),
886            NameFrequency::Common,
887        ),
888        NameExample::new(
889            "Diego",
890            "Hernandez",
891            Ethnicity::Hispanic,
892            Script::Latin,
893            Some(Gender::Masculine),
894            NameFrequency::Common,
895        ),
896        NameExample::new(
897            "Sofía",
898            "González",
899            Ethnicity::Hispanic,
900            Script::Latin,
901            Some(Gender::Feminine),
902            NameFrequency::Common,
903        ),
904        NameExample::new(
905            "Javier",
906            "Pérez",
907            Ethnicity::Hispanic,
908            Script::Latin,
909            Some(Gender::Masculine),
910            NameFrequency::Moderate,
911        ),
912        NameExample::new(
913            "Guadalupe",
914            "Sánchez",
915            Ethnicity::Hispanic,
916            Script::Latin,
917            Some(Gender::Neutral),
918            NameFrequency::Moderate,
919        ),
920    ]
921}
922
923/// East Asian names (Chinese, Japanese, Korean)
924fn east_asian_names() -> Vec<NameExample> {
925    vec![
926        // === East Asian Names ===
927        // Chinese (Latin transliteration)
928        NameExample::new(
929            "Wei",
930            "Wang",
931            Ethnicity::EastAsian,
932            Script::Latin,
933            Some(Gender::Masculine),
934            NameFrequency::Common,
935        ),
936        NameExample::new(
937            "Li",
938            "Zhang",
939            Ethnicity::EastAsian,
940            Script::Latin,
941            Some(Gender::Feminine),
942            NameFrequency::Common,
943        ),
944        NameExample::new(
945            "Ming",
946            "Chen",
947            Ethnicity::EastAsian,
948            Script::Latin,
949            Some(Gender::Neutral),
950            NameFrequency::Common,
951        ),
952        // Chinese (characters)
953        NameExample::new(
954            "伟",
955            "王",
956            Ethnicity::EastAsian,
957            Script::Chinese,
958            Some(Gender::Masculine),
959            NameFrequency::Common,
960        ),
961        NameExample::new(
962            "丽",
963            "张",
964            Ethnicity::EastAsian,
965            Script::Chinese,
966            Some(Gender::Feminine),
967            NameFrequency::Common,
968        ),
969        // Japanese
970        NameExample::new(
971            "Takeshi",
972            "Tanaka",
973            Ethnicity::EastAsian,
974            Script::Latin,
975            Some(Gender::Masculine),
976            NameFrequency::Common,
977        ),
978        NameExample::new(
979            "Yuki",
980            "Yamamoto",
981            Ethnicity::EastAsian,
982            Script::Latin,
983            Some(Gender::Neutral),
984            NameFrequency::Common,
985        ),
986        NameExample::new(
987            "太郎",
988            "田中",
989            Ethnicity::EastAsian,
990            Script::Japanese,
991            Some(Gender::Masculine),
992            NameFrequency::Common,
993        ),
994        NameExample::new(
995            "花子",
996            "山本",
997            Ethnicity::EastAsian,
998            Script::Japanese,
999            Some(Gender::Feminine),
1000            NameFrequency::Common,
1001        ),
1002        // Korean
1003        NameExample::new(
1004            "Min-jun",
1005            "Kim",
1006            Ethnicity::EastAsian,
1007            Script::Latin,
1008            Some(Gender::Masculine),
1009            NameFrequency::Common,
1010        ),
1011        NameExample::new(
1012            "Seo-yeon",
1013            "Park",
1014            Ethnicity::EastAsian,
1015            Script::Latin,
1016            Some(Gender::Feminine),
1017            NameFrequency::Common,
1018        ),
1019        NameExample::new(
1020            "민준",
1021            "김",
1022            Ethnicity::EastAsian,
1023            Script::Korean,
1024            Some(Gender::Masculine),
1025            NameFrequency::Common,
1026        ),
1027    ]
1028}
1029
1030/// South Asian names (Indian, Pakistani, Bangladeshi, etc.)
1031fn south_asian_names() -> Vec<NameExample> {
1032    vec![
1033        // === South Asian Names ===
1034        NameExample::new(
1035            "Raj",
1036            "Patel",
1037            Ethnicity::SouthAsian,
1038            Script::Latin,
1039            Some(Gender::Masculine),
1040            NameFrequency::Common,
1041        ),
1042        NameExample::new(
1043            "Priya",
1044            "Sharma",
1045            Ethnicity::SouthAsian,
1046            Script::Latin,
1047            Some(Gender::Feminine),
1048            NameFrequency::Common,
1049        ),
1050        NameExample::new(
1051            "Arjun",
1052            "Singh",
1053            Ethnicity::SouthAsian,
1054            Script::Latin,
1055            Some(Gender::Masculine),
1056            NameFrequency::Common,
1057        ),
1058        NameExample::new(
1059            "Aisha",
1060            "Khan",
1061            Ethnicity::SouthAsian,
1062            Script::Latin,
1063            Some(Gender::Feminine),
1064            NameFrequency::Common,
1065        ),
1066        NameExample::new(
1067            "Vikram",
1068            "Kumar",
1069            Ethnicity::SouthAsian,
1070            Script::Latin,
1071            Some(Gender::Masculine),
1072            NameFrequency::Common,
1073        ),
1074        NameExample::new(
1075            "Sunita",
1076            "Gupta",
1077            Ethnicity::SouthAsian,
1078            Script::Latin,
1079            Some(Gender::Feminine),
1080            NameFrequency::Common,
1081        ),
1082        // Devanagari script
1083        NameExample::new(
1084            "राज",
1085            "पटेल",
1086            Ethnicity::SouthAsian,
1087            Script::Devanagari,
1088            Some(Gender::Masculine),
1089            NameFrequency::Common,
1090        ),
1091        NameExample::new(
1092            "प्रिया",
1093            "शर्मा",
1094            Ethnicity::SouthAsian,
1095            Script::Devanagari,
1096            Some(Gender::Feminine),
1097            NameFrequency::Common,
1098        ),
1099    ]
1100}
1101
1102/// Middle Eastern names (Arabic, Persian, Turkish, etc.)
1103fn middle_eastern_names() -> Vec<NameExample> {
1104    vec![
1105        // === Middle Eastern Names ===
1106        NameExample::new(
1107            "Ahmed",
1108            "Hassan",
1109            Ethnicity::MiddleEastern,
1110            Script::Latin,
1111            Some(Gender::Masculine),
1112            NameFrequency::Common,
1113        ),
1114        NameExample::new(
1115            "Fatima",
1116            "Ali",
1117            Ethnicity::MiddleEastern,
1118            Script::Latin,
1119            Some(Gender::Feminine),
1120            NameFrequency::Common,
1121        ),
1122        NameExample::new(
1123            "Mohammed",
1124            "Ibrahim",
1125            Ethnicity::MiddleEastern,
1126            Script::Latin,
1127            Some(Gender::Masculine),
1128            NameFrequency::Common,
1129        ),
1130        NameExample::new(
1131            "Layla",
1132            "Omar",
1133            Ethnicity::MiddleEastern,
1134            Script::Latin,
1135            Some(Gender::Feminine),
1136            NameFrequency::Common,
1137        ),
1138        NameExample::new(
1139            "Yusuf",
1140            "Mustafa",
1141            Ethnicity::MiddleEastern,
1142            Script::Latin,
1143            Some(Gender::Masculine),
1144            NameFrequency::Common,
1145        ),
1146        NameExample::new(
1147            "Mariam",
1148            "Khalil",
1149            Ethnicity::MiddleEastern,
1150            Script::Latin,
1151            Some(Gender::Feminine),
1152            NameFrequency::Common,
1153        ),
1154        // Arabic script
1155        NameExample::new(
1156            "أحمد",
1157            "حسن",
1158            Ethnicity::MiddleEastern,
1159            Script::Arabic,
1160            Some(Gender::Masculine),
1161            NameFrequency::Common,
1162        ),
1163        NameExample::new(
1164            "فاطمة",
1165            "علي",
1166            Ethnicity::MiddleEastern,
1167            Script::Arabic,
1168            Some(Gender::Feminine),
1169            NameFrequency::Common,
1170        ),
1171    ]
1172}
1173
1174/// African names (from various African countries)
1175fn african_names() -> Vec<NameExample> {
1176    let mut names = Vec::new();
1177    // === African Names ===
1178    names.extend(vec![
1179        NameExample::new(
1180            "Chidi",
1181            "Okonkwo",
1182            Ethnicity::African,
1183            Script::Latin,
1184            Some(Gender::Masculine),
1185            NameFrequency::Common,
1186        ),
1187        NameExample::new(
1188            "Amara",
1189            "Adebayo",
1190            Ethnicity::African,
1191            Script::Latin,
1192            Some(Gender::Feminine),
1193            NameFrequency::Common,
1194        ),
1195        NameExample::new(
1196            "Kwame",
1197            "Mensah",
1198            Ethnicity::African,
1199            Script::Latin,
1200            Some(Gender::Masculine),
1201            NameFrequency::Common,
1202        ),
1203        NameExample::new(
1204            "Nneka",
1205            "Nwosu",
1206            Ethnicity::African,
1207            Script::Latin,
1208            Some(Gender::Feminine),
1209            NameFrequency::Common,
1210        ),
1211        NameExample::new(
1212            "Oluwaseun",
1213            "Afolabi",
1214            Ethnicity::African,
1215            Script::Latin,
1216            Some(Gender::Masculine),
1217            NameFrequency::Moderate,
1218        ),
1219        NameExample::new(
1220            "Chidinma",
1221            "Eze",
1222            Ethnicity::African,
1223            Script::Latin,
1224            Some(Gender::Feminine),
1225            NameFrequency::Moderate,
1226        ),
1227        NameExample::new(
1228            "Tendai",
1229            "Moyo",
1230            Ethnicity::African,
1231            Script::Latin,
1232            Some(Gender::Neutral),
1233            NameFrequency::Moderate,
1234        ),
1235        NameExample::new(
1236            "Zainab",
1237            "Diallo",
1238            Ethnicity::African,
1239            Script::Latin,
1240            Some(Gender::Feminine),
1241            NameFrequency::Moderate,
1242        ),
1243    ]);
1244
1245    // === Cyrillic Names (Russian/Eastern European) ===
1246    names.extend(vec![
1247        NameExample::new(
1248            "Ivan",
1249            "Petrov",
1250            Ethnicity::European,
1251            Script::Latin,
1252            Some(Gender::Masculine),
1253            NameFrequency::Common,
1254        ),
1255        NameExample::new(
1256            "Olga",
1257            "Ivanova",
1258            Ethnicity::European,
1259            Script::Latin,
1260            Some(Gender::Feminine),
1261            NameFrequency::Common,
1262        ),
1263        NameExample::new(
1264            "Иван",
1265            "Петров",
1266            Ethnicity::European,
1267            Script::Cyrillic,
1268            Some(Gender::Masculine),
1269            NameFrequency::Common,
1270        ),
1271        NameExample::new(
1272            "Ольга",
1273            "Иванова",
1274            Ethnicity::European,
1275            Script::Cyrillic,
1276            Some(Gender::Feminine),
1277            NameFrequency::Common,
1278        ),
1279        NameExample::new(
1280            "Dmytro",
1281            "Shevchenko",
1282            Ethnicity::European,
1283            Script::Latin,
1284            Some(Gender::Masculine),
1285            NameFrequency::Moderate,
1286        ),
1287        NameExample::new(
1288            "Katarzyna",
1289            "Kowalski",
1290            Ethnicity::European,
1291            Script::Latin,
1292            Some(Gender::Feminine),
1293            NameFrequency::Moderate,
1294        ),
1295        NameExample::new(
1296            "Alexander",
1297            "Volkov",
1298            Ethnicity::European,
1299            Script::Latin,
1300            Some(Gender::Masculine),
1301            NameFrequency::Common,
1302        ),
1303        NameExample::new(
1304            "Sofia",
1305            "Kozlova",
1306            Ethnicity::European,
1307            Script::Latin,
1308            Some(Gender::Feminine),
1309            NameFrequency::Common,
1310        ),
1311        NameExample::new(
1312            "Dmitri",
1313            "Sokolov",
1314            Ethnicity::European,
1315            Script::Latin,
1316            Some(Gender::Masculine),
1317            NameFrequency::Common,
1318        ),
1319        NameExample::new(
1320            "Anastasia",
1321            "Popova",
1322            Ethnicity::European,
1323            Script::Latin,
1324            Some(Gender::Feminine),
1325            NameFrequency::Common,
1326        ),
1327    ]);
1328
1329    // === Additional European Names (Expanded) ===
1330    names.extend(vec![
1331        NameExample::new(
1332            "Robert",
1333            "Jones",
1334            Ethnicity::European,
1335            Script::Latin,
1336            Some(Gender::Masculine),
1337            NameFrequency::Common,
1338        ),
1339        NameExample::new(
1340            "Patricia",
1341            "Garcia",
1342            Ethnicity::European,
1343            Script::Latin,
1344            Some(Gender::Feminine),
1345            NameFrequency::Common,
1346        ),
1347        NameExample::new(
1348            "Michael",
1349            "Miller",
1350            Ethnicity::European,
1351            Script::Latin,
1352            Some(Gender::Masculine),
1353            NameFrequency::Common,
1354        ),
1355        NameExample::new(
1356            "Jennifer",
1357            "Davis",
1358            Ethnicity::European,
1359            Script::Latin,
1360            Some(Gender::Feminine),
1361            NameFrequency::Common,
1362        ),
1363        NameExample::new(
1364            "David",
1365            "Rodriguez",
1366            Ethnicity::European,
1367            Script::Latin,
1368            Some(Gender::Masculine),
1369            NameFrequency::Common,
1370        ),
1371        NameExample::new(
1372            "Linda",
1373            "Martinez",
1374            Ethnicity::European,
1375            Script::Latin,
1376            Some(Gender::Feminine),
1377            NameFrequency::Common,
1378        ),
1379        NameExample::new(
1380            "Richard",
1381            "Hernandez",
1382            Ethnicity::European,
1383            Script::Latin,
1384            Some(Gender::Masculine),
1385            NameFrequency::Common,
1386        ),
1387        NameExample::new(
1388            "Barbara",
1389            "Lopez",
1390            Ethnicity::European,
1391            Script::Latin,
1392            Some(Gender::Feminine),
1393            NameFrequency::Common,
1394        ),
1395        NameExample::new(
1396            "Joseph",
1397            "Wilson",
1398            Ethnicity::European,
1399            Script::Latin,
1400            Some(Gender::Masculine),
1401            NameFrequency::Common,
1402        ),
1403        NameExample::new(
1404            "Elizabeth",
1405            "Anderson",
1406            Ethnicity::European,
1407            Script::Latin,
1408            Some(Gender::Feminine),
1409            NameFrequency::Common,
1410        ),
1411        NameExample::new(
1412            "Thomas",
1413            "Thomas",
1414            Ethnicity::European,
1415            Script::Latin,
1416            Some(Gender::Masculine),
1417            NameFrequency::Common,
1418        ),
1419        NameExample::new(
1420            "Jessica",
1421            "Taylor",
1422            Ethnicity::European,
1423            Script::Latin,
1424            Some(Gender::Feminine),
1425            NameFrequency::Common,
1426        ),
1427        NameExample::new(
1428            "Charles",
1429            "Moore",
1430            Ethnicity::European,
1431            Script::Latin,
1432            Some(Gender::Masculine),
1433            NameFrequency::Common,
1434        ),
1435        NameExample::new(
1436            "Sarah",
1437            "Jackson",
1438            Ethnicity::European,
1439            Script::Latin,
1440            Some(Gender::Feminine),
1441            NameFrequency::Common,
1442        ),
1443        NameExample::new(
1444            "Christopher",
1445            "Martin",
1446            Ethnicity::European,
1447            Script::Latin,
1448            Some(Gender::Masculine),
1449            NameFrequency::Common,
1450        ),
1451        NameExample::new(
1452            "Karen",
1453            "Lee",
1454            Ethnicity::European,
1455            Script::Latin,
1456            Some(Gender::Feminine),
1457            NameFrequency::Common,
1458        ),
1459        NameExample::new(
1460            "Daniel",
1461            "Thompson",
1462            Ethnicity::European,
1463            Script::Latin,
1464            Some(Gender::Masculine),
1465            NameFrequency::Common,
1466        ),
1467        NameExample::new(
1468            "Nancy",
1469            "White",
1470            Ethnicity::European,
1471            Script::Latin,
1472            Some(Gender::Feminine),
1473            NameFrequency::Common,
1474        ),
1475        NameExample::new(
1476            "Matthew",
1477            "Harris",
1478            Ethnicity::European,
1479            Script::Latin,
1480            Some(Gender::Masculine),
1481            NameFrequency::Common,
1482        ),
1483        NameExample::new(
1484            "Betty",
1485            "Sanchez",
1486            Ethnicity::European,
1487            Script::Latin,
1488            Some(Gender::Feminine),
1489            NameFrequency::Common,
1490        ),
1491    ]);
1492
1493    // === Additional African-American Names (Expanded) ===
1494    names.extend(vec![
1495        NameExample::new(
1496            "Malik",
1497            "Anderson",
1498            Ethnicity::AfricanAmerican,
1499            Script::Latin,
1500            Some(Gender::Masculine),
1501            NameFrequency::Common,
1502        ),
1503        NameExample::new(
1504            "Keisha",
1505            "Thomas",
1506            Ethnicity::AfricanAmerican,
1507            Script::Latin,
1508            Some(Gender::Feminine),
1509            NameFrequency::Common,
1510        ),
1511        NameExample::new(
1512            "Andre",
1513            "Harris",
1514            Ethnicity::AfricanAmerican,
1515            Script::Latin,
1516            Some(Gender::Masculine),
1517            NameFrequency::Common,
1518        ),
1519        NameExample::new(
1520            "Tiffany",
1521            "Clark",
1522            Ethnicity::AfricanAmerican,
1523            Script::Latin,
1524            Some(Gender::Feminine),
1525            NameFrequency::Common,
1526        ),
1527        NameExample::new(
1528            "Marcus",
1529            "Lewis",
1530            Ethnicity::AfricanAmerican,
1531            Script::Latin,
1532            Some(Gender::Masculine),
1533            NameFrequency::Common,
1534        ),
1535        NameExample::new(
1536            "Nicole",
1537            "Walker",
1538            Ethnicity::AfricanAmerican,
1539            Script::Latin,
1540            Some(Gender::Feminine),
1541            NameFrequency::Common,
1542        ),
1543        NameExample::new(
1544            "Darius",
1545            "Hall",
1546            Ethnicity::AfricanAmerican,
1547            Script::Latin,
1548            Some(Gender::Masculine),
1549            NameFrequency::Common,
1550        ),
1551        NameExample::new(
1552            "Monique",
1553            "Allen",
1554            Ethnicity::AfricanAmerican,
1555            Script::Latin,
1556            Some(Gender::Feminine),
1557            NameFrequency::Common,
1558        ),
1559        NameExample::new(
1560            "Terrell",
1561            "Young",
1562            Ethnicity::AfricanAmerican,
1563            Script::Latin,
1564            Some(Gender::Masculine),
1565            NameFrequency::Common,
1566        ),
1567        NameExample::new(
1568            "Danielle",
1569            "King",
1570            Ethnicity::AfricanAmerican,
1571            Script::Latin,
1572            Some(Gender::Feminine),
1573            NameFrequency::Common,
1574        ),
1575        NameExample::new(
1576            "Kendrick",
1577            "Wright",
1578            Ethnicity::AfricanAmerican,
1579            Script::Latin,
1580            Some(Gender::Masculine),
1581            NameFrequency::Common,
1582        ),
1583        NameExample::new(
1584            "Brittany",
1585            "Lopez",
1586            Ethnicity::AfricanAmerican,
1587            Script::Latin,
1588            Some(Gender::Feminine),
1589            NameFrequency::Common,
1590        ),
1591        NameExample::new(
1592            "Jermaine",
1593            "Hill",
1594            Ethnicity::AfricanAmerican,
1595            Script::Latin,
1596            Some(Gender::Masculine),
1597            NameFrequency::Common,
1598        ),
1599        NameExample::new(
1600            "Crystal",
1601            "Scott",
1602            Ethnicity::AfricanAmerican,
1603            Script::Latin,
1604            Some(Gender::Feminine),
1605            NameFrequency::Common,
1606        ),
1607        NameExample::new(
1608            "Antoine",
1609            "Green",
1610            Ethnicity::AfricanAmerican,
1611            Script::Latin,
1612            Some(Gender::Masculine),
1613            NameFrequency::Common,
1614        ),
1615        NameExample::new(
1616            "Ebony",
1617            "Adams",
1618            Ethnicity::AfricanAmerican,
1619            Script::Latin,
1620            Some(Gender::Feminine),
1621            NameFrequency::Common,
1622        ),
1623        NameExample::new(
1624            "Reginald",
1625            "Baker",
1626            Ethnicity::AfricanAmerican,
1627            Script::Latin,
1628            Some(Gender::Masculine),
1629            NameFrequency::Common,
1630        ),
1631        NameExample::new(
1632            "Jasmine",
1633            "Nelson",
1634            Ethnicity::AfricanAmerican,
1635            Script::Latin,
1636            Some(Gender::Feminine),
1637            NameFrequency::Common,
1638        ),
1639        NameExample::new(
1640            "Darnell",
1641            "Carter",
1642            Ethnicity::AfricanAmerican,
1643            Script::Latin,
1644            Some(Gender::Masculine),
1645            NameFrequency::Common,
1646        ),
1647        NameExample::new(
1648            "LaTasha",
1649            "Mitchell",
1650            Ethnicity::AfricanAmerican,
1651            Script::Latin,
1652            Some(Gender::Feminine),
1653            NameFrequency::Common,
1654        ),
1655    ]);
1656
1657    // === Additional Hispanic Names (Expanded) ===
1658    names.extend(vec![
1659        NameExample::new(
1660            "Alejandro",
1661            "Fernandez",
1662            Ethnicity::Hispanic,
1663            Script::Latin,
1664            Some(Gender::Masculine),
1665            NameFrequency::Common,
1666        ),
1667        NameExample::new(
1668            "Valentina",
1669            "Ramirez",
1670            Ethnicity::Hispanic,
1671            Script::Latin,
1672            Some(Gender::Feminine),
1673            NameFrequency::Common,
1674        ),
1675        NameExample::new(
1676            "Sebastian",
1677            "Torres",
1678            Ethnicity::Hispanic,
1679            Script::Latin,
1680            Some(Gender::Masculine),
1681            NameFrequency::Common,
1682        ),
1683        NameExample::new(
1684            "Camila",
1685            "Flores",
1686            Ethnicity::Hispanic,
1687            Script::Latin,
1688            Some(Gender::Feminine),
1689            NameFrequency::Common,
1690        ),
1691        NameExample::new(
1692            "Mateo",
1693            "Rivera",
1694            Ethnicity::Hispanic,
1695            Script::Latin,
1696            Some(Gender::Masculine),
1697            NameFrequency::Common,
1698        ),
1699        NameExample::new(
1700            "Lucia",
1701            "Gomez",
1702            Ethnicity::Hispanic,
1703            Script::Latin,
1704            Some(Gender::Feminine),
1705            NameFrequency::Common,
1706        ),
1707        NameExample::new(
1708            "Nicolas",
1709            "Diaz",
1710            Ethnicity::Hispanic,
1711            Script::Latin,
1712            Some(Gender::Masculine),
1713            NameFrequency::Common,
1714        ),
1715        NameExample::new(
1716            "Elena",
1717            "Reyes",
1718            Ethnicity::Hispanic,
1719            Script::Latin,
1720            Some(Gender::Feminine),
1721            NameFrequency::Common,
1722        ),
1723        NameExample::new(
1724            "Gabriel",
1725            "Morales",
1726            Ethnicity::Hispanic,
1727            Script::Latin,
1728            Some(Gender::Masculine),
1729            NameFrequency::Common,
1730        ),
1731        NameExample::new(
1732            "Sofia",
1733            "Ortiz",
1734            Ethnicity::Hispanic,
1735            Script::Latin,
1736            Some(Gender::Feminine),
1737            NameFrequency::Common,
1738        ),
1739        NameExample::new(
1740            "Adrian",
1741            "Gutierrez",
1742            Ethnicity::Hispanic,
1743            Script::Latin,
1744            Some(Gender::Masculine),
1745            NameFrequency::Common,
1746        ),
1747        NameExample::new(
1748            "Isabella",
1749            "Chavez",
1750            Ethnicity::Hispanic,
1751            Script::Latin,
1752            Some(Gender::Feminine),
1753            NameFrequency::Common,
1754        ),
1755        NameExample::new(
1756            "Luis",
1757            "Jimenez",
1758            Ethnicity::Hispanic,
1759            Script::Latin,
1760            Some(Gender::Masculine),
1761            NameFrequency::Common,
1762        ),
1763        NameExample::new(
1764            "Gabriela",
1765            "Moreno",
1766            Ethnicity::Hispanic,
1767            Script::Latin,
1768            Some(Gender::Feminine),
1769            NameFrequency::Common,
1770        ),
1771        NameExample::new(
1772            "Fernando",
1773            "Alvarez",
1774            Ethnicity::Hispanic,
1775            Script::Latin,
1776            Some(Gender::Masculine),
1777            NameFrequency::Common,
1778        ),
1779        NameExample::new(
1780            "Valeria",
1781            "Ruiz",
1782            Ethnicity::Hispanic,
1783            Script::Latin,
1784            Some(Gender::Feminine),
1785            NameFrequency::Common,
1786        ),
1787        NameExample::new(
1788            "Ricardo",
1789            "Vargas",
1790            Ethnicity::Hispanic,
1791            Script::Latin,
1792            Some(Gender::Masculine),
1793            NameFrequency::Common,
1794        ),
1795        NameExample::new(
1796            "Andrea",
1797            "Mendoza",
1798            Ethnicity::Hispanic,
1799            Script::Latin,
1800            Some(Gender::Feminine),
1801            NameFrequency::Common,
1802        ),
1803        NameExample::new(
1804            "Eduardo",
1805            "Castillo",
1806            Ethnicity::Hispanic,
1807            Script::Latin,
1808            Some(Gender::Masculine),
1809            NameFrequency::Common,
1810        ),
1811        NameExample::new(
1812            "Natalia",
1813            "Ramos",
1814            Ethnicity::Hispanic,
1815            Script::Latin,
1816            Some(Gender::Feminine),
1817            NameFrequency::Common,
1818        ),
1819    ]);
1820
1821    // === Additional East Asian Names (Expanded) ===
1822    names.extend(vec![
1823        NameExample::new(
1824            "Hiroshi",
1825            "Suzuki",
1826            Ethnicity::EastAsian,
1827            Script::Latin,
1828            Some(Gender::Masculine),
1829            NameFrequency::Common,
1830        ),
1831        NameExample::new(
1832            "Yuki",
1833            "Takahashi",
1834            Ethnicity::EastAsian,
1835            Script::Latin,
1836            Some(Gender::Neutral),
1837            NameFrequency::Common,
1838        ),
1839        NameExample::new(
1840            "Kenji",
1841            "Tanaka",
1842            Ethnicity::EastAsian,
1843            Script::Latin,
1844            Some(Gender::Masculine),
1845            NameFrequency::Common,
1846        ),
1847        NameExample::new(
1848            "Sakura",
1849            "Watanabe",
1850            Ethnicity::EastAsian,
1851            Script::Latin,
1852            Some(Gender::Feminine),
1853            NameFrequency::Common,
1854        ),
1855        NameExample::new(
1856            "Jun",
1857            "Ito",
1858            Ethnicity::EastAsian,
1859            Script::Latin,
1860            Some(Gender::Neutral),
1861            NameFrequency::Common,
1862        ),
1863        NameExample::new(
1864            "Mei",
1865            "Nakamura",
1866            Ethnicity::EastAsian,
1867            Script::Latin,
1868            Some(Gender::Feminine),
1869            NameFrequency::Common,
1870        ),
1871        NameExample::new(
1872            "Xiaoming",
1873            "Li",
1874            Ethnicity::EastAsian,
1875            Script::Latin,
1876            Some(Gender::Masculine),
1877            NameFrequency::Common,
1878        ),
1879        NameExample::new(
1880            "Xiaoli",
1881            "Wang",
1882            Ethnicity::EastAsian,
1883            Script::Latin,
1884            Some(Gender::Feminine),
1885            NameFrequency::Common,
1886        ),
1887        NameExample::new(
1888            "Jian",
1889            "Liu",
1890            Ethnicity::EastAsian,
1891            Script::Latin,
1892            Some(Gender::Masculine),
1893            NameFrequency::Common,
1894        ),
1895        NameExample::new(
1896            "Yan",
1897            "Zhang",
1898            Ethnicity::EastAsian,
1899            Script::Latin,
1900            Some(Gender::Feminine),
1901            NameFrequency::Common,
1902        ),
1903        NameExample::new(
1904            "Hye-jin",
1905            "Park",
1906            Ethnicity::EastAsian,
1907            Script::Latin,
1908            Some(Gender::Feminine),
1909            NameFrequency::Common,
1910        ),
1911        NameExample::new(
1912            "Seung-ho",
1913            "Kim",
1914            Ethnicity::EastAsian,
1915            Script::Latin,
1916            Some(Gender::Masculine),
1917            NameFrequency::Common,
1918        ),
1919        NameExample::new(
1920            "Ji-woo",
1921            "Lee",
1922            Ethnicity::EastAsian,
1923            Script::Latin,
1924            Some(Gender::Neutral),
1925            NameFrequency::Common,
1926        ),
1927        NameExample::new(
1928            "Soo-jin",
1929            "Choi",
1930            Ethnicity::EastAsian,
1931            Script::Latin,
1932            Some(Gender::Feminine),
1933            NameFrequency::Common,
1934        ),
1935        NameExample::new(
1936            "Min-ho",
1937            "Jung",
1938            Ethnicity::EastAsian,
1939            Script::Latin,
1940            Some(Gender::Masculine),
1941            NameFrequency::Common,
1942        ),
1943        NameExample::new(
1944            "明",
1945            "王",
1946            Ethnicity::EastAsian,
1947            Script::Chinese,
1948            Some(Gender::Masculine),
1949            NameFrequency::Common,
1950        ),
1951        NameExample::new(
1952            "美",
1953            "李",
1954            Ethnicity::EastAsian,
1955            Script::Chinese,
1956            Some(Gender::Feminine),
1957            NameFrequency::Common,
1958        ),
1959        NameExample::new(
1960            "健",
1961            "张",
1962            Ethnicity::EastAsian,
1963            Script::Chinese,
1964            Some(Gender::Masculine),
1965            NameFrequency::Common,
1966        ),
1967        NameExample::new(
1968            "花子",
1969            "佐藤",
1970            Ethnicity::EastAsian,
1971            Script::Japanese,
1972            Some(Gender::Feminine),
1973            NameFrequency::Common,
1974        ),
1975        NameExample::new(
1976            "太郎",
1977            "鈴木",
1978            Ethnicity::EastAsian,
1979            Script::Japanese,
1980            Some(Gender::Masculine),
1981            NameFrequency::Common,
1982        ),
1983    ]);
1984
1985    // === Additional South Asian Names (Expanded) ===
1986    names.extend(vec![
1987        NameExample::new(
1988            "Amit",
1989            "Patel",
1990            Ethnicity::SouthAsian,
1991            Script::Latin,
1992            Some(Gender::Masculine),
1993            NameFrequency::Common,
1994        ),
1995        NameExample::new(
1996            "Kavita",
1997            "Sharma",
1998            Ethnicity::SouthAsian,
1999            Script::Latin,
2000            Some(Gender::Feminine),
2001            NameFrequency::Common,
2002        ),
2003        NameExample::new(
2004            "Rahul",
2005            "Singh",
2006            Ethnicity::SouthAsian,
2007            Script::Latin,
2008            Some(Gender::Masculine),
2009            NameFrequency::Common,
2010        ),
2011        NameExample::new(
2012            "Deepika",
2013            "Kumar",
2014            Ethnicity::SouthAsian,
2015            Script::Latin,
2016            Some(Gender::Feminine),
2017            NameFrequency::Common,
2018        ),
2019        NameExample::new(
2020            "Vikram",
2021            "Gupta",
2022            Ethnicity::SouthAsian,
2023            Script::Latin,
2024            Some(Gender::Masculine),
2025            NameFrequency::Common,
2026        ),
2027        NameExample::new(
2028            "Anjali",
2029            "Mehta",
2030            Ethnicity::SouthAsian,
2031            Script::Latin,
2032            Some(Gender::Feminine),
2033            NameFrequency::Common,
2034        ),
2035        NameExample::new(
2036            "Rohan",
2037            "Desai",
2038            Ethnicity::SouthAsian,
2039            Script::Latin,
2040            Some(Gender::Masculine),
2041            NameFrequency::Common,
2042        ),
2043        NameExample::new(
2044            "Meera",
2045            "Joshi",
2046            Ethnicity::SouthAsian,
2047            Script::Latin,
2048            Some(Gender::Feminine),
2049            NameFrequency::Common,
2050        ),
2051        NameExample::new(
2052            "Siddharth",
2053            "Reddy",
2054            Ethnicity::SouthAsian,
2055            Script::Latin,
2056            Some(Gender::Masculine),
2057            NameFrequency::Common,
2058        ),
2059        NameExample::new(
2060            "Kiran",
2061            "Nair",
2062            Ethnicity::SouthAsian,
2063            Script::Latin,
2064            Some(Gender::Neutral),
2065            NameFrequency::Common,
2066        ),
2067        NameExample::new(
2068            "Arjun",
2069            "Iyer",
2070            Ethnicity::SouthAsian,
2071            Script::Latin,
2072            Some(Gender::Masculine),
2073            NameFrequency::Common,
2074        ),
2075        NameExample::new(
2076            "Divya",
2077            "Menon",
2078            Ethnicity::SouthAsian,
2079            Script::Latin,
2080            Some(Gender::Feminine),
2081            NameFrequency::Common,
2082        ),
2083        NameExample::new(
2084            "Nikhil",
2085            "Rao",
2086            Ethnicity::SouthAsian,
2087            Script::Latin,
2088            Some(Gender::Masculine),
2089            NameFrequency::Common,
2090        ),
2091        NameExample::new(
2092            "Shreya",
2093            "Malhotra",
2094            Ethnicity::SouthAsian,
2095            Script::Latin,
2096            Some(Gender::Feminine),
2097            NameFrequency::Common,
2098        ),
2099        NameExample::new(
2100            "Aditya",
2101            "Kapoor",
2102            Ethnicity::SouthAsian,
2103            Script::Latin,
2104            Some(Gender::Masculine),
2105            NameFrequency::Common,
2106        ),
2107        NameExample::new(
2108            "Pooja",
2109            "Agarwal",
2110            Ethnicity::SouthAsian,
2111            Script::Latin,
2112            Some(Gender::Feminine),
2113            NameFrequency::Common,
2114        ),
2115        NameExample::new(
2116            "Ravi",
2117            "Bhatt",
2118            Ethnicity::SouthAsian,
2119            Script::Latin,
2120            Some(Gender::Masculine),
2121            NameFrequency::Common,
2122        ),
2123        NameExample::new(
2124            "Neha",
2125            "Chopra",
2126            Ethnicity::SouthAsian,
2127            Script::Latin,
2128            Some(Gender::Feminine),
2129            NameFrequency::Common,
2130        ),
2131        NameExample::new(
2132            "Karan",
2133            "Verma",
2134            Ethnicity::SouthAsian,
2135            Script::Latin,
2136            Some(Gender::Masculine),
2137            NameFrequency::Common,
2138        ),
2139        NameExample::new(
2140            "Sanjana",
2141            "Saxena",
2142            Ethnicity::SouthAsian,
2143            Script::Latin,
2144            Some(Gender::Feminine),
2145            NameFrequency::Common,
2146        ),
2147    ]);
2148
2149    // === Additional Middle Eastern Names (Expanded) ===
2150    names.extend(vec![
2151        NameExample::new(
2152            "Omar",
2153            "Hassan",
2154            Ethnicity::MiddleEastern,
2155            Script::Latin,
2156            Some(Gender::Masculine),
2157            NameFrequency::Common,
2158        ),
2159        NameExample::new(
2160            "Zara",
2161            "Ali",
2162            Ethnicity::MiddleEastern,
2163            Script::Latin,
2164            Some(Gender::Feminine),
2165            NameFrequency::Common,
2166        ),
2167        NameExample::new(
2168            "Tariq",
2169            "Ibrahim",
2170            Ethnicity::MiddleEastern,
2171            Script::Latin,
2172            Some(Gender::Masculine),
2173            NameFrequency::Common,
2174        ),
2175        NameExample::new(
2176            "Amina",
2177            "Omar",
2178            Ethnicity::MiddleEastern,
2179            Script::Latin,
2180            Some(Gender::Feminine),
2181            NameFrequency::Common,
2182        ),
2183        NameExample::new(
2184            "Khalil",
2185            "Mustafa",
2186            Ethnicity::MiddleEastern,
2187            Script::Latin,
2188            Some(Gender::Masculine),
2189            NameFrequency::Common,
2190        ),
2191        NameExample::new(
2192            "Noor",
2193            "Khalil",
2194            Ethnicity::MiddleEastern,
2195            Script::Latin,
2196            Some(Gender::Feminine),
2197            NameFrequency::Common,
2198        ),
2199        NameExample::new(
2200            "Rashid",
2201            "Mahmoud",
2202            Ethnicity::MiddleEastern,
2203            Script::Latin,
2204            Some(Gender::Masculine),
2205            NameFrequency::Common,
2206        ),
2207        NameExample::new(
2208            "Samira",
2209            "Haddad",
2210            Ethnicity::MiddleEastern,
2211            Script::Latin,
2212            Some(Gender::Feminine),
2213            NameFrequency::Common,
2214        ),
2215        NameExample::new(
2216            "Bashir",
2217            "Nasser",
2218            Ethnicity::MiddleEastern,
2219            Script::Latin,
2220            Some(Gender::Masculine),
2221            NameFrequency::Common,
2222        ),
2223        NameExample::new(
2224            "Leila",
2225            "Fadel",
2226            Ethnicity::MiddleEastern,
2227            Script::Latin,
2228            Some(Gender::Feminine),
2229            NameFrequency::Common,
2230        ),
2231        NameExample::new(
2232            "Karim",
2233            "Said",
2234            Ethnicity::MiddleEastern,
2235            Script::Latin,
2236            Some(Gender::Masculine),
2237            NameFrequency::Common,
2238        ),
2239        NameExample::new(
2240            "Yasmin",
2241            "Malik",
2242            Ethnicity::MiddleEastern,
2243            Script::Latin,
2244            Some(Gender::Feminine),
2245            NameFrequency::Common,
2246        ),
2247        NameExample::new(
2248            "Jamal",
2249            "Rahman",
2250            Ethnicity::MiddleEastern,
2251            Script::Latin,
2252            Some(Gender::Masculine),
2253            NameFrequency::Common,
2254        ),
2255        NameExample::new(
2256            "Soraya",
2257            "Abbas",
2258            Ethnicity::MiddleEastern,
2259            Script::Latin,
2260            Some(Gender::Feminine),
2261            NameFrequency::Common,
2262        ),
2263        NameExample::new(
2264            "Nabil",
2265            "Hakim",
2266            Ethnicity::MiddleEastern,
2267            Script::Latin,
2268            Some(Gender::Masculine),
2269            NameFrequency::Common,
2270        ),
2271        NameExample::new(
2272            "Rania",
2273            "Farid",
2274            Ethnicity::MiddleEastern,
2275            Script::Latin,
2276            Some(Gender::Feminine),
2277            NameFrequency::Common,
2278        ),
2279        NameExample::new(
2280            "Tariq",
2281            "Zaki",
2282            Ethnicity::MiddleEastern,
2283            Script::Latin,
2284            Some(Gender::Masculine),
2285            NameFrequency::Common,
2286        ),
2287        NameExample::new(
2288            "Dina",
2289            "Salem",
2290            Ethnicity::MiddleEastern,
2291            Script::Latin,
2292            Some(Gender::Feminine),
2293            NameFrequency::Common,
2294        ),
2295        NameExample::new(
2296            "Malik",
2297            "Nasir",
2298            Ethnicity::MiddleEastern,
2299            Script::Latin,
2300            Some(Gender::Masculine),
2301            NameFrequency::Common,
2302        ),
2303        NameExample::new(
2304            "Hala",
2305            "Qureshi",
2306            Ethnicity::MiddleEastern,
2307            Script::Latin,
2308            Some(Gender::Feminine),
2309            NameFrequency::Common,
2310        ),
2311    ]);
2312
2313    // === Additional African Names (Expanded) ===
2314    names.extend(vec![
2315        NameExample::new(
2316            "Kofi",
2317            "Mensah",
2318            Ethnicity::African,
2319            Script::Latin,
2320            Some(Gender::Masculine),
2321            NameFrequency::Common,
2322        ),
2323        NameExample::new(
2324            "Amina",
2325            "Diallo",
2326            Ethnicity::African,
2327            Script::Latin,
2328            Some(Gender::Feminine),
2329            NameFrequency::Common,
2330        ),
2331        NameExample::new(
2332            "Kwame",
2333            "Asante",
2334            Ethnicity::African,
2335            Script::Latin,
2336            Some(Gender::Masculine),
2337            NameFrequency::Common,
2338        ),
2339        NameExample::new(
2340            "Fatou",
2341            "Ndiaye",
2342            Ethnicity::African,
2343            Script::Latin,
2344            Some(Gender::Feminine),
2345            NameFrequency::Common,
2346        ),
2347        NameExample::new(
2348            "Bakary",
2349            "Traore",
2350            Ethnicity::African,
2351            Script::Latin,
2352            Some(Gender::Masculine),
2353            NameFrequency::Common,
2354        ),
2355        NameExample::new(
2356            "Aissatou",
2357            "Ba",
2358            Ethnicity::African,
2359            Script::Latin,
2360            Some(Gender::Feminine),
2361            NameFrequency::Common,
2362        ),
2363        NameExample::new(
2364            "Ibrahim",
2365            "Sow",
2366            Ethnicity::African,
2367            Script::Latin,
2368            Some(Gender::Masculine),
2369            NameFrequency::Common,
2370        ),
2371        NameExample::new(
2372            "Mariama",
2373            "Diallo",
2374            Ethnicity::African,
2375            Script::Latin,
2376            Some(Gender::Feminine),
2377            NameFrequency::Common,
2378        ),
2379        NameExample::new(
2380            "Sekou",
2381            "Keita",
2382            Ethnicity::African,
2383            Script::Latin,
2384            Some(Gender::Masculine),
2385            NameFrequency::Common,
2386        ),
2387        NameExample::new(
2388            "Awa",
2389            "Cisse",
2390            Ethnicity::African,
2391            Script::Latin,
2392            Some(Gender::Feminine),
2393            NameFrequency::Common,
2394        ),
2395        NameExample::new(
2396            "Moussa",
2397            "Toure",
2398            Ethnicity::African,
2399            Script::Latin,
2400            Some(Gender::Masculine),
2401            NameFrequency::Common,
2402        ),
2403        NameExample::new(
2404            "Kadiatou",
2405            "Sangare",
2406            Ethnicity::African,
2407            Script::Latin,
2408            Some(Gender::Feminine),
2409            NameFrequency::Common,
2410        ),
2411        NameExample::new(
2412            "Youssouf",
2413            "Kone",
2414            Ethnicity::African,
2415            Script::Latin,
2416            Some(Gender::Masculine),
2417            NameFrequency::Common,
2418        ),
2419        NameExample::new(
2420            "Aminata",
2421            "Diop",
2422            Ethnicity::African,
2423            Script::Latin,
2424            Some(Gender::Feminine),
2425            NameFrequency::Common,
2426        ),
2427        NameExample::new(
2428            "Boubacar",
2429            "Sall",
2430            Ethnicity::African,
2431            Script::Latin,
2432            Some(Gender::Masculine),
2433            NameFrequency::Common,
2434        ),
2435        NameExample::new(
2436            "Hawa",
2437            "Ba",
2438            Ethnicity::African,
2439            Script::Latin,
2440            Some(Gender::Feminine),
2441            NameFrequency::Common,
2442        ),
2443        NameExample::new(
2444            "Mamadou",
2445            "Diallo",
2446            Ethnicity::African,
2447            Script::Latin,
2448            Some(Gender::Masculine),
2449            NameFrequency::Common,
2450        ),
2451        NameExample::new(
2452            "Ramatoulaye",
2453            "Ndiaye",
2454            Ethnicity::African,
2455            Script::Latin,
2456            Some(Gender::Feminine),
2457            NameFrequency::Common,
2458        ),
2459        NameExample::new(
2460            "Amadou",
2461            "Sow",
2462            Ethnicity::African,
2463            Script::Latin,
2464            Some(Gender::Masculine),
2465            NameFrequency::Common,
2466        ),
2467        NameExample::new(
2468            "Aissata",
2469            "Traore",
2470            Ethnicity::African,
2471            Script::Latin,
2472            Some(Gender::Feminine),
2473            NameFrequency::Common,
2474        ),
2475    ]);
2476    names
2477}
2478
2479/// Validate demographic distribution against a reference distribution.
2480///
2481/// The reference distribution here is a coarse, hard-coded default used for
2482/// sanity checks in tests; do not treat it as authoritative demographic data.
2483fn validate_demographic_distribution(
2484    observed: &HashMap<String, f64>,
2485) -> crate::eval::bias_config::DistributionValidation {
2486    // Coarse default reference distribution for name-recognition sanity checks.
2487    // Replace with a cited, task-appropriate reference distribution for any real analysis.
2488    let mut reference = HashMap::new();
2489    reference.insert("European".to_string(), 0.60);
2490    reference.insert("Hispanic".to_string(), 0.19);
2491    reference.insert("AfricanAmerican".to_string(), 0.13);
2492    reference.insert("EastAsian".to_string(), 0.06);
2493    reference.insert("SouthAsian".to_string(), 0.02);
2494    reference.insert("MiddleEastern".to_string(), 0.01);
2495    reference.insert("African".to_string(), 0.01);
2496    reference.insert("Indigenous".to_string(), 0.01);
2497
2498    // Normalize observed to proportions (if they're counts, convert to rates)
2499    let total: f64 = observed.values().sum();
2500    let normalized_observed: HashMap<String, f64> = if total > 0.0 {
2501        observed
2502            .iter()
2503            .map(|(k, v)| (k.clone(), v / total))
2504            .collect()
2505    } else {
2506        observed.clone()
2507    };
2508
2509    crate::eval::bias_config::DistributionValidation::validate(
2510        &normalized_observed,
2511        &reference,
2512        0.10, // 10% tolerance
2513    )
2514}
2515
2516/// Create a realistic sentence context for a location.
2517fn create_realistic_location_sentence(location: &str) -> String {
2518    use std::collections::hash_map::DefaultHasher;
2519    use std::hash::{Hash, Hasher};
2520    let mut hasher = DefaultHasher::new();
2521    location.hash(&mut hasher);
2522    let hash = hasher.finish();
2523
2524    let templates = [
2525        format!("The summit was held in {} last month.", location),
2526        format!("{} has become a major tech hub in recent years.", location),
2527        format!("Tourists flock to {} during the summer months.", location),
2528        format!(
2529            "The conference in {} attracted thousands of attendees.",
2530            location
2531        ),
2532        format!("{} is known for its vibrant cultural scene.", location),
2533        format!(
2534            "Business leaders met in {} to discuss trade policies.",
2535            location
2536        ),
2537        format!(
2538            "{} hosted the international competition this year.",
2539            location
2540        ),
2541        format!("The economic growth in {} has been remarkable.", location),
2542        format!(
2543            "{} is home to several world-renowned universities.",
2544            location
2545        ),
2546        format!(
2547            "The climate summit in {} addressed global challenges.",
2548            location
2549        ),
2550    ];
2551
2552    templates[hash as usize % templates.len()].clone()
2553}
2554
2555/// Create a diverse location dataset for regional bias testing.
2556pub fn create_diverse_location_dataset() -> Vec<LocationExample> {
2557    vec![
2558        // North America
2559        LocationExample::new(
2560            "New York",
2561            Region::NorthAmerica,
2562            Script::Latin,
2563            LocationType::City,
2564        ),
2565        LocationExample::new(
2566            "Los Angeles",
2567            Region::NorthAmerica,
2568            Script::Latin,
2569            LocationType::City,
2570        ),
2571        LocationExample::new(
2572            "Toronto",
2573            Region::NorthAmerica,
2574            Script::Latin,
2575            LocationType::City,
2576        ),
2577        LocationExample::new(
2578            "Mexico City",
2579            Region::NorthAmerica,
2580            Script::Latin,
2581            LocationType::City,
2582        ),
2583        // Western Europe
2584        LocationExample::new(
2585            "London",
2586            Region::WesternEurope,
2587            Script::Latin,
2588            LocationType::City,
2589        ),
2590        LocationExample::new(
2591            "Paris",
2592            Region::WesternEurope,
2593            Script::Latin,
2594            LocationType::City,
2595        ),
2596        LocationExample::new(
2597            "Berlin",
2598            Region::WesternEurope,
2599            Script::Latin,
2600            LocationType::City,
2601        ),
2602        LocationExample::new(
2603            "Amsterdam",
2604            Region::WesternEurope,
2605            Script::Latin,
2606            LocationType::City,
2607        ),
2608        // Eastern Europe
2609        LocationExample::new(
2610            "Moscow",
2611            Region::EasternEurope,
2612            Script::Latin,
2613            LocationType::City,
2614        ),
2615        LocationExample::new(
2616            "Москва",
2617            Region::EasternEurope,
2618            Script::Cyrillic,
2619            LocationType::City,
2620        ),
2621        LocationExample::new(
2622            "Warsaw",
2623            Region::EasternEurope,
2624            Script::Latin,
2625            LocationType::City,
2626        ),
2627        LocationExample::new(
2628            "Kyiv",
2629            Region::EasternEurope,
2630            Script::Latin,
2631            LocationType::City,
2632        ),
2633        // East Asia
2634        LocationExample::new("Tokyo", Region::EastAsia, Script::Latin, LocationType::City),
2635        LocationExample::new(
2636            "東京",
2637            Region::EastAsia,
2638            Script::Japanese,
2639            LocationType::City,
2640        ),
2641        LocationExample::new(
2642            "Beijing",
2643            Region::EastAsia,
2644            Script::Latin,
2645            LocationType::City,
2646        ),
2647        LocationExample::new(
2648            "北京",
2649            Region::EastAsia,
2650            Script::Chinese,
2651            LocationType::City,
2652        ),
2653        LocationExample::new("Seoul", Region::EastAsia, Script::Latin, LocationType::City),
2654        LocationExample::new("서울", Region::EastAsia, Script::Korean, LocationType::City),
2655        // South Asia
2656        LocationExample::new(
2657            "Mumbai",
2658            Region::SouthAsia,
2659            Script::Latin,
2660            LocationType::City,
2661        ),
2662        LocationExample::new(
2663            "Delhi",
2664            Region::SouthAsia,
2665            Script::Latin,
2666            LocationType::City,
2667        ),
2668        LocationExample::new(
2669            "Dhaka",
2670            Region::SouthAsia,
2671            Script::Latin,
2672            LocationType::City,
2673        ),
2674        LocationExample::new(
2675            "Karachi",
2676            Region::SouthAsia,
2677            Script::Latin,
2678            LocationType::City,
2679        ),
2680        // Southeast Asia
2681        LocationExample::new(
2682            "Bangkok",
2683            Region::SoutheastAsia,
2684            Script::Latin,
2685            LocationType::City,
2686        ),
2687        LocationExample::new(
2688            "Singapore",
2689            Region::SoutheastAsia,
2690            Script::Latin,
2691            LocationType::City,
2692        ),
2693        LocationExample::new(
2694            "Jakarta",
2695            Region::SoutheastAsia,
2696            Script::Latin,
2697            LocationType::City,
2698        ),
2699        LocationExample::new(
2700            "Ho Chi Minh City",
2701            Region::SoutheastAsia,
2702            Script::Latin,
2703            LocationType::City,
2704        ),
2705        // Middle East
2706        LocationExample::new(
2707            "Dubai",
2708            Region::MiddleEast,
2709            Script::Latin,
2710            LocationType::City,
2711        ),
2712        LocationExample::new(
2713            "دبي",
2714            Region::MiddleEast,
2715            Script::Arabic,
2716            LocationType::City,
2717        ),
2718        LocationExample::new(
2719            "Tehran",
2720            Region::MiddleEast,
2721            Script::Latin,
2722            LocationType::City,
2723        ),
2724        LocationExample::new(
2725            "Riyadh",
2726            Region::MiddleEast,
2727            Script::Latin,
2728            LocationType::City,
2729        ),
2730        // Africa
2731        LocationExample::new("Lagos", Region::Africa, Script::Latin, LocationType::City),
2732        LocationExample::new("Nairobi", Region::Africa, Script::Latin, LocationType::City),
2733        LocationExample::new("Cairo", Region::Africa, Script::Latin, LocationType::City),
2734        LocationExample::new(
2735            "Johannesburg",
2736            Region::Africa,
2737            Script::Latin,
2738            LocationType::City,
2739        ),
2740        LocationExample::new(
2741            "Addis Ababa",
2742            Region::Africa,
2743            Script::Latin,
2744            LocationType::City,
2745        ),
2746        // Latin America
2747        LocationExample::new(
2748            "São Paulo",
2749            Region::LatinAmerica,
2750            Script::Latin,
2751            LocationType::City,
2752        ),
2753        LocationExample::new(
2754            "Buenos Aires",
2755            Region::LatinAmerica,
2756            Script::Latin,
2757            LocationType::City,
2758        ),
2759        LocationExample::new(
2760            "Bogotá",
2761            Region::LatinAmerica,
2762            Script::Latin,
2763            LocationType::City,
2764        ),
2765        LocationExample::new(
2766            "Lima",
2767            Region::LatinAmerica,
2768            Script::Latin,
2769            LocationType::City,
2770        ),
2771        // Oceania
2772        LocationExample::new("Sydney", Region::Oceania, Script::Latin, LocationType::City),
2773        LocationExample::new(
2774            "Melbourne",
2775            Region::Oceania,
2776            Script::Latin,
2777            LocationType::City,
2778        ),
2779        LocationExample::new(
2780            "Auckland",
2781            Region::Oceania,
2782            Script::Latin,
2783            LocationType::City,
2784        ),
2785    ]
2786}
2787
2788// =============================================================================
2789// Tests
2790// =============================================================================
2791
2792#[cfg(test)]
2793mod tests {
2794    use super::*;
2795
2796    #[test]
2797    fn test_create_diverse_names() {
2798        let names = create_diverse_name_dataset();
2799
2800        // Should have names from all ethnicities
2801        let ethnicities: std::collections::HashSet<_> =
2802            names.iter().map(|n| format!("{:?}", n.ethnicity)).collect();
2803
2804        assert!(
2805            ethnicities.contains("European"),
2806            "Should have European names"
2807        );
2808        assert!(
2809            ethnicities.contains("AfricanAmerican"),
2810            "Should have African-American names"
2811        );
2812        assert!(
2813            ethnicities.contains("Hispanic"),
2814            "Should have Hispanic names"
2815        );
2816        assert!(
2817            ethnicities.contains("EastAsian"),
2818            "Should have East Asian names"
2819        );
2820        assert!(
2821            ethnicities.contains("SouthAsian"),
2822            "Should have South Asian names"
2823        );
2824        assert!(
2825            ethnicities.contains("MiddleEastern"),
2826            "Should have Middle Eastern names"
2827        );
2828        assert!(ethnicities.contains("African"), "Should have African names");
2829    }
2830
2831    #[test]
2832    fn test_multiple_scripts() {
2833        let names = create_diverse_name_dataset();
2834
2835        let scripts: std::collections::HashSet<_> =
2836            names.iter().map(|n| format!("{:?}", n.script)).collect();
2837
2838        assert!(scripts.contains("Latin"), "Should have Latin script");
2839        assert!(scripts.contains("Chinese"), "Should have Chinese script");
2840        assert!(scripts.contains("Japanese"), "Should have Japanese script");
2841        assert!(scripts.contains("Arabic"), "Should have Arabic script");
2842        assert!(scripts.contains("Cyrillic"), "Should have Cyrillic script");
2843    }
2844
2845    #[test]
2846    fn test_gender_balance() {
2847        let names = create_diverse_name_dataset();
2848
2849        let masculine = names
2850            .iter()
2851            .filter(|n| n.gender == Some(Gender::Masculine))
2852            .count();
2853        let feminine = names
2854            .iter()
2855            .filter(|n| n.gender == Some(Gender::Feminine))
2856            .count();
2857
2858        // Should have roughly balanced genders
2859        let ratio = masculine as f64 / feminine.max(1) as f64;
2860        assert!(
2861            (0.7..=1.3).contains(&ratio),
2862            "Gender ratio should be roughly balanced, got {:.2}",
2863            ratio
2864        );
2865    }
2866
2867    #[test]
2868    fn test_diverse_locations() {
2869        let locations = create_diverse_location_dataset();
2870
2871        let regions: std::collections::HashSet<_> = locations
2872            .iter()
2873            .map(|l| format!("{:?}", l.region))
2874            .collect();
2875
2876        assert!(regions.len() >= 8, "Should cover at least 8 regions");
2877        assert!(regions.contains("Africa"), "Should have African locations");
2878        assert!(
2879            regions.contains("LatinAmerica"),
2880            "Should have Latin American locations"
2881        );
2882        assert!(
2883            regions.contains("MiddleEast"),
2884            "Should have Middle Eastern locations"
2885        );
2886    }
2887
2888    #[test]
2889    fn test_parity_gap_computation() {
2890        let mut rates = HashMap::new();
2891        rates.insert("A".to_string(), 0.9);
2892        rates.insert("B".to_string(), 0.7);
2893        rates.insert("C".to_string(), 0.8);
2894
2895        let gap = compute_max_gap(&rates);
2896        assert!((gap - 0.2).abs() < 0.001, "Gap should be 0.2, got {}", gap);
2897    }
2898}