use shodh_memory::embeddings::ner::{NerConfig, NerEntityType, NeuralNer};
use std::path::PathBuf;
fn create_test_ner() -> NeuralNer {
let config = NerConfig {
model_path: PathBuf::from("nonexistent.onnx"),
tokenizer_path: PathBuf::from("nonexistent.json"),
max_length: 128,
confidence_threshold: 0.5,
};
NeuralNer::new_fallback(config)
}
mod indian_entities {
use super::*;
#[test]
fn test_indian_it_companies() {
let ner = create_test_ner();
let companies = vec![
"TCS", "Infosys", "Wipro", "HCL", "Flipkart", "Paytm", "Zomato", "Swiggy", "Ola",
];
for company in companies {
let text = format!("{} reported strong growth this quarter", company);
let entities = ner.extract(&text).unwrap();
let found = entities.iter().any(|e| {
e.text.to_lowercase() == company.to_lowercase()
&& e.entity_type == NerEntityType::Organization
});
assert!(
found
|| entities
.iter()
.any(|e| e.text.to_lowercase() == company.to_lowercase()),
"Should find Indian IT company: {}",
company
);
}
}
#[test]
fn test_indian_conglomerates() {
let ner = create_test_ner();
let conglomerates = vec!["Tata", "Reliance", "Adani", "Birla", "Mahindra", "Godrej"];
for company in conglomerates {
let text = format!("{} group announced expansion plans", company);
let entities = ner.extract(&text).unwrap();
assert!(
entities.iter().any(|e| e.text == company),
"Should find Indian conglomerate: {}",
company
);
}
}
#[test]
fn test_indian_metro_cities() {
let ner = create_test_ner();
let metros = vec![
"Mumbai",
"Delhi",
"Bangalore",
"Bengaluru",
"Hyderabad",
"Chennai",
"Kolkata",
"Pune",
];
for city in metros {
let text = format!("The office is located in {}", city);
let entities = ner.extract(&text).unwrap();
let found = entities
.iter()
.find(|e| e.text.to_lowercase() == city.to_lowercase());
assert!(found.is_some(), "Should find Indian metro: {}", city);
assert_eq!(
found.unwrap().entity_type,
NerEntityType::Location,
"{} should be Location",
city
);
}
}
#[test]
fn test_indian_tier2_cities() {
let ner = create_test_ner();
let cities = vec![
"Jaipur",
"Lucknow",
"Kochi",
"Coimbatore",
"Indore",
"Nagpur",
"Vadodara",
];
for city in cities {
let text = format!("New branch opening in {}", city);
let entities = ner.extract(&text).unwrap();
let found = entities
.iter()
.find(|e| e.text.to_lowercase() == city.to_lowercase());
assert!(found.is_some(), "Should find Indian tier-2 city: {}", city);
}
}
#[test]
fn test_indian_tech_hubs() {
let ner = create_test_ner();
let text = "The startup ecosystem in Bangalore and Hyderabad is thriving";
let entities = ner.extract(text).unwrap();
let bangalore = entities
.iter()
.any(|e| e.text == "Bangalore" && e.entity_type == NerEntityType::Location);
let hyderabad = entities
.iter()
.any(|e| e.text == "Hyderabad" && e.entity_type == NerEntityType::Location);
assert!(bangalore, "Should find Bangalore");
assert!(hyderabad, "Should find Hyderabad");
}
}
mod global_entities {
use super::*;
#[test]
fn test_global_tech_giants() {
let ner = create_test_ner();
let companies = vec![
"Microsoft",
"Google",
"Apple",
"Amazon",
"Meta",
"Netflix",
"Tesla",
];
for company in companies {
let text = format!("{} announced new products", company);
let entities = ner.extract(&text).unwrap();
let found = entities.iter().find(|e| e.text == company);
assert!(found.is_some(), "Should find global tech: {}", company);
assert_eq!(
found.unwrap().entity_type,
NerEntityType::Organization,
"{} should be Organization",
company
);
}
}
#[test]
fn test_global_cities() {
let ner = create_test_ner();
let cities = vec![
"London",
"Tokyo",
"Singapore",
"Dubai",
"Sydney",
"Berlin",
"Paris",
];
for city in cities {
let text = format!("Conference will be held in {}", city);
let entities = ner.extract(&text).unwrap();
let found = entities
.iter()
.find(|e| e.text.to_lowercase() == city.to_lowercase());
assert!(found.is_some(), "Should find global city: {}", city);
}
}
}
mod mixed_entities {
use super::*;
#[test]
fn test_org_and_location_same_sentence() {
let ner = create_test_ner();
let text = "Microsoft headquarters in Seattle employs thousands";
let entities = ner.extract(text).unwrap();
let has_microsoft = entities
.iter()
.any(|e| e.text == "Microsoft" && e.entity_type == NerEntityType::Organization);
let has_seattle = entities
.iter()
.any(|e| e.text == "Seattle" && e.entity_type == NerEntityType::Location);
assert!(has_microsoft, "Should find Microsoft as Organization");
assert!(has_seattle, "Should find Seattle as Location");
}
#[test]
fn test_indian_company_global_location() {
let ner = create_test_ner();
let text = "Infosys opened new office in London";
let entities = ner.extract(text).unwrap();
let has_infosys = entities
.iter()
.any(|e| e.text == "Infosys" && e.entity_type == NerEntityType::Organization);
let has_london = entities
.iter()
.any(|e| e.text == "London" && e.entity_type == NerEntityType::Location);
assert!(has_infosys, "Should find Infosys");
assert!(has_london, "Should find London");
}
#[test]
fn test_global_company_indian_location() {
let ner = create_test_ner();
let text = "Google expanding operations in Bangalore";
let entities = ner.extract(text).unwrap();
let has_google = entities
.iter()
.any(|e| e.text == "Google" && e.entity_type == NerEntityType::Organization);
let has_bangalore = entities
.iter()
.any(|e| e.text == "Bangalore" && e.entity_type == NerEntityType::Location);
assert!(has_google, "Should find Google");
assert!(has_bangalore, "Should find Bangalore");
}
#[test]
fn test_multiple_organizations() {
let ner = create_test_ner();
let text = "Microsoft and Google are competing with Apple";
let entities = ner.extract(text).unwrap();
let org_count = entities
.iter()
.filter(|e| e.entity_type == NerEntityType::Organization)
.count();
assert!(org_count >= 2, "Should find at least 2 organizations");
}
#[test]
fn test_multiple_locations() {
let ner = create_test_ner();
let text = "Traveling from Mumbai to Delhi via Bangalore";
let entities = ner.extract(text).unwrap();
let loc_count = entities
.iter()
.filter(|e| e.entity_type == NerEntityType::Location)
.count();
assert!(loc_count >= 2, "Should find at least 2 locations");
}
}
mod edge_cases {
use super::*;
#[test]
fn test_empty_input() {
let ner = create_test_ner();
let entities = ner.extract("").unwrap();
assert!(entities.is_empty());
}
#[test]
fn test_whitespace_only() {
let ner = create_test_ner();
let entities = ner.extract(" \t\n ").unwrap();
assert!(entities.is_empty());
}
#[test]
fn test_no_entities() {
let ner = create_test_ner();
let text = "it was cold and dark";
let entities = ner.extract(text).unwrap();
let named_entities: Vec<_> = entities
.iter()
.filter(|e| {
matches!(
e.entity_type,
NerEntityType::Person | NerEntityType::Organization | NerEntityType::Location
)
})
.collect();
assert!(
named_entities.is_empty(),
"expected no named entities from: {}",
text
);
}
#[test]
fn test_unicode_text() {
let ner = create_test_ner();
let text = "Microsoft ने नया प्रोडक्ट लॉन्च किया";
let entities = ner.extract(text).unwrap();
assert!(entities.iter().any(|e| e.text == "Microsoft"));
}
#[test]
fn test_very_long_text() {
let ner = create_test_ner();
let mut text = String::new();
for i in 0..100 {
if i % 10 == 0 {
text.push_str("Microsoft is great. ");
} else {
text.push_str("This is some filler text. ");
}
}
let entities = ner.extract(&text).unwrap();
let microsoft_count = entities.iter().filter(|e| e.text == "Microsoft").count();
assert_eq!(microsoft_count, 1, "Should deduplicate Microsoft mentions");
}
#[test]
fn test_special_characters() {
let ner = create_test_ner();
let text = "Microsoft™ vs Google® in Seattle!";
let entities = ner.extract(text).unwrap();
for entity in &entities {
assert!(!entity.text.contains('™'));
assert!(!entity.text.contains('®'));
}
}
#[test]
fn test_case_sensitivity() {
let ner = create_test_ner();
let entities_lower = ner.extract("microsoft google apple").unwrap();
let entities_proper = ner.extract("Microsoft Google Apple").unwrap();
assert!(
!entities_proper.is_empty(),
"Properly capitalized entities should be found"
);
}
#[test]
fn test_numbers_in_text() {
let ner = create_test_ner();
let text = "Microsoft earned $100 billion in 2023 in Seattle";
let entities = ner.extract(text).unwrap();
assert!(entities.iter().any(|e| e.text == "Microsoft"));
assert!(entities.iter().any(|e| e.text == "Seattle"));
}
#[test]
fn test_abbreviations() {
let ner = create_test_ner();
let text = "TCS and IBM are competing";
let entities = ner.extract(text).unwrap();
let has_tcs = entities
.iter()
.any(|e| e.text.to_lowercase() == "tcs" || e.text == "TCS");
assert!(has_tcs, "Should find TCS abbreviation");
}
}
mod performance {
use super::*;
use std::time::Instant;
#[test]
fn test_extraction_latency_short_text() {
let ner = create_test_ner();
let text = "Microsoft is headquartered in Seattle";
let start = Instant::now();
let iterations = 1000;
for _ in 0..iterations {
let _ = ner.extract(text).unwrap();
}
let elapsed = start.elapsed();
let avg_ms = elapsed.as_secs_f64() * 1000.0 / iterations as f64;
println!("Average extraction time (short): {:.3}ms", avg_ms);
assert!(
avg_ms < 1.0,
"Fallback extraction should be under 1ms, got {:.3}ms",
avg_ms
);
}
#[test]
fn test_extraction_latency_long_text() {
let ner = create_test_ner();
let text =
"Microsoft and Google are expanding their operations in Bangalore and Hyderabad. \
Amazon is also opening new offices in Mumbai. Meanwhile, Flipkart and Paytm \
continue to grow in the Indian market. The tech industry in India is booming \
with cities like Chennai, Pune, and Kolkata becoming new tech hubs.";
let start = Instant::now();
let iterations = 100;
for _ in 0..iterations {
let _ = ner.extract(text).unwrap();
}
let elapsed = start.elapsed();
let avg_ms = elapsed.as_secs_f64() * 1000.0 / iterations as f64;
println!("Average extraction time (long): {:.3}ms", avg_ms);
assert!(
avg_ms < 5.0,
"Fallback extraction should be under 5ms, got {:.3}ms",
avg_ms
);
}
#[test]
fn test_many_entities() {
let ner = create_test_ner();
let text = "Microsoft Google Apple Amazon Meta Netflix Tesla IBM Oracle SAP \
Infosys TCS Wipro Flipkart Paytm Zomato Swiggy Ola \
Mumbai Delhi Bangalore Hyderabad Chennai Kolkata Pune \
London Tokyo Singapore Dubai Sydney Berlin Paris";
let start = Instant::now();
let entities = ner.extract(text).unwrap();
let elapsed = start.elapsed();
println!(
"Extracted {} entities in {:.3}ms",
entities.len(),
elapsed.as_secs_f64() * 1000.0
);
assert!(entities.len() >= 10, "Should find many entities");
assert!(
elapsed.as_millis() < 50,
"Should complete in under 50ms even with many entities"
);
}
}
mod accuracy {
use super::*;
fn get_test_cases() -> Vec<(&'static str, Vec<(&'static str, NerEntityType)>)> {
vec![
(
"Microsoft CEO visited Seattle",
vec![
("Microsoft", NerEntityType::Organization),
("Seattle", NerEntityType::Location),
],
),
(
"Infosys opened office in Bangalore",
vec![
("Infosys", NerEntityType::Organization),
("Bangalore", NerEntityType::Location),
],
),
(
"Google and Amazon compete globally",
vec![
("Google", NerEntityType::Organization),
("Amazon", NerEntityType::Organization),
],
),
(
"Mumbai to Delhi flight",
vec![
("Mumbai", NerEntityType::Location),
("Delhi", NerEntityType::Location),
],
),
]
}
#[test]
fn test_accuracy_on_test_set() {
let ner = create_test_ner();
let test_cases = get_test_cases();
let mut total_expected = 0;
let mut total_found = 0;
for (text, expected) in test_cases {
let entities = ner.extract(text).unwrap();
total_expected += expected.len();
for (expected_text, expected_type) in expected {
let found = entities.iter().any(|e| {
e.text.to_lowercase() == expected_text.to_lowercase()
&& e.entity_type == expected_type
});
if found {
total_found += 1;
} else {
println!(
"Missing: {} ({:?}) in '{}'",
expected_text, expected_type, text
);
}
}
}
let recall = total_found as f64 / total_expected as f64;
println!(
"Recall: {:.1}% ({}/{})",
recall * 100.0,
total_found,
total_expected
);
assert!(
recall >= 0.7,
"Recall should be at least 70%, got {:.1}%",
recall * 100.0
);
}
}