gdelt 0.1.0

CLI for GDELT Project - optimized for agentic usage with local data caching
//! CAMEO code definitions and lookup tables.
//!
//! CAMEO (Conflict and Mediation Event Observations) is the coding
//! system used by GDELT to categorize events.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use once_cell::sync::Lazy;

/// CAMEO event code
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameoCode {
    pub code: String,
    pub description: String,
    pub quad_class: Option<u8>,
    pub goldstein_scale: Option<f64>,
}

/// Country/region code
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CountryCode {
    pub code: String,
    pub name: String,
    pub fips: Option<String>,
    pub iso: Option<String>,
}

/// Quad class descriptions
pub static QUAD_CLASSES: Lazy<HashMap<u8, &'static str>> = Lazy::new(|| {
    let mut m = HashMap::new();
    m.insert(1, "Verbal Cooperation");
    m.insert(2, "Material Cooperation");
    m.insert(3, "Verbal Conflict");
    m.insert(4, "Material Conflict");
    m
});

/// Root CAMEO event codes with descriptions and Goldstein scales
pub static ROOT_CODES: Lazy<Vec<CameoCode>> = Lazy::new(|| {
    vec![
        CameoCode { code: "01".to_string(), description: "Make public statement".to_string(), quad_class: Some(1), goldstein_scale: Some(0.0) },
        CameoCode { code: "02".to_string(), description: "Appeal".to_string(), quad_class: Some(1), goldstein_scale: Some(3.0) },
        CameoCode { code: "03".to_string(), description: "Express intent to cooperate".to_string(), quad_class: Some(1), goldstein_scale: Some(4.0) },
        CameoCode { code: "04".to_string(), description: "Consult".to_string(), quad_class: Some(1), goldstein_scale: Some(1.0) },
        CameoCode { code: "05".to_string(), description: "Engage in diplomatic cooperation".to_string(), quad_class: Some(2), goldstein_scale: Some(3.5) },
        CameoCode { code: "06".to_string(), description: "Engage in material cooperation".to_string(), quad_class: Some(2), goldstein_scale: Some(6.0) },
        CameoCode { code: "07".to_string(), description: "Provide aid".to_string(), quad_class: Some(2), goldstein_scale: Some(7.0) },
        CameoCode { code: "08".to_string(), description: "Yield".to_string(), quad_class: Some(2), goldstein_scale: Some(5.0) },
        CameoCode { code: "09".to_string(), description: "Investigate".to_string(), quad_class: Some(1), goldstein_scale: Some(-0.5) },
        CameoCode { code: "10".to_string(), description: "Demand".to_string(), quad_class: Some(3), goldstein_scale: Some(-5.0) },
        CameoCode { code: "11".to_string(), description: "Disapprove".to_string(), quad_class: Some(3), goldstein_scale: Some(-2.0) },
        CameoCode { code: "12".to_string(), description: "Reject".to_string(), quad_class: Some(3), goldstein_scale: Some(-4.0) },
        CameoCode { code: "13".to_string(), description: "Threaten".to_string(), quad_class: Some(3), goldstein_scale: Some(-6.0) },
        CameoCode { code: "14".to_string(), description: "Protest".to_string(), quad_class: Some(3), goldstein_scale: Some(-6.5) },
        CameoCode { code: "15".to_string(), description: "Exhibit force posture".to_string(), quad_class: Some(4), goldstein_scale: Some(-7.0) },
        CameoCode { code: "16".to_string(), description: "Reduce relations".to_string(), quad_class: Some(4), goldstein_scale: Some(-4.0) },
        CameoCode { code: "17".to_string(), description: "Coerce".to_string(), quad_class: Some(4), goldstein_scale: Some(-7.0) },
        CameoCode { code: "18".to_string(), description: "Assault".to_string(), quad_class: Some(4), goldstein_scale: Some(-9.0) },
        CameoCode { code: "19".to_string(), description: "Fight".to_string(), quad_class: Some(4), goldstein_scale: Some(-10.0) },
        CameoCode { code: "20".to_string(), description: "Use unconventional mass violence".to_string(), quad_class: Some(4), goldstein_scale: Some(-10.0) },
    ]
});

/// Actor type codes
pub static ACTOR_TYPE_CODES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
    let mut m = HashMap::new();
    m.insert("GOV", "Government");
    m.insert("MIL", "Military");
    m.insert("REB", "Rebels");
    m.insert("OPP", "Opposition");
    m.insert("PTY", "Political Party");
    m.insert("AGR", "Agriculture");
    m.insert("BUS", "Business");
    m.insert("CRM", "Criminal");
    m.insert("CVL", "Civilian");
    m.insert("DEV", "Development");
    m.insert("EDU", "Education");
    m.insert("ELI", "Elite");
    m.insert("ENV", "Environment");
    m.insert("HLH", "Health");
    m.insert("HRI", "Human Rights");
    m.insert("IGO", "Intergovernmental Organization");
    m.insert("JUD", "Judiciary");
    m.insert("LAB", "Labor");
    m.insert("LEG", "Legislature");
    m.insert("MED", "Media");
    m.insert("MNC", "Multinational Corporation");
    m.insert("NGO", "Non-Governmental Organization");
    m.insert("REF", "Refugees");
    m.insert("REL", "Religion");
    m.insert("SPY", "Intelligence");
    m.insert("UAF", "Unaligned Armed Forces");
    m
});

/// Major country codes (FIPS)
pub static MAJOR_COUNTRIES: Lazy<Vec<CountryCode>> = Lazy::new(|| {
    vec![
        CountryCode { code: "US".to_string(), name: "United States".to_string(), fips: Some("US".to_string()), iso: Some("USA".to_string()) },
        CountryCode { code: "CH".to_string(), name: "China".to_string(), fips: Some("CH".to_string()), iso: Some("CHN".to_string()) },
        CountryCode { code: "RS".to_string(), name: "Russia".to_string(), fips: Some("RS".to_string()), iso: Some("RUS".to_string()) },
        CountryCode { code: "UK".to_string(), name: "United Kingdom".to_string(), fips: Some("UK".to_string()), iso: Some("GBR".to_string()) },
        CountryCode { code: "FR".to_string(), name: "France".to_string(), fips: Some("FR".to_string()), iso: Some("FRA".to_string()) },
        CountryCode { code: "GM".to_string(), name: "Germany".to_string(), fips: Some("GM".to_string()), iso: Some("DEU".to_string()) },
        CountryCode { code: "JA".to_string(), name: "Japan".to_string(), fips: Some("JA".to_string()), iso: Some("JPN".to_string()) },
        CountryCode { code: "IN".to_string(), name: "India".to_string(), fips: Some("IN".to_string()), iso: Some("IND".to_string()) },
        CountryCode { code: "BR".to_string(), name: "Brazil".to_string(), fips: Some("BR".to_string()), iso: Some("BRA".to_string()) },
        CountryCode { code: "CA".to_string(), name: "Canada".to_string(), fips: Some("CA".to_string()), iso: Some("CAN".to_string()) },
        CountryCode { code: "AU".to_string(), name: "Australia".to_string(), fips: Some("AS".to_string()), iso: Some("AUS".to_string()) },
        CountryCode { code: "IT".to_string(), name: "Italy".to_string(), fips: Some("IT".to_string()), iso: Some("ITA".to_string()) },
        CountryCode { code: "SP".to_string(), name: "Spain".to_string(), fips: Some("SP".to_string()), iso: Some("ESP".to_string()) },
        CountryCode { code: "MX".to_string(), name: "Mexico".to_string(), fips: Some("MX".to_string()), iso: Some("MEX".to_string()) },
        CountryCode { code: "KS".to_string(), name: "South Korea".to_string(), fips: Some("KS".to_string()), iso: Some("KOR".to_string()) },
        CountryCode { code: "SA".to_string(), name: "Saudi Arabia".to_string(), fips: Some("SA".to_string()), iso: Some("SAU".to_string()) },
        CountryCode { code: "TU".to_string(), name: "Turkey".to_string(), fips: Some("TU".to_string()), iso: Some("TUR".to_string()) },
        CountryCode { code: "IR".to_string(), name: "Iran".to_string(), fips: Some("IR".to_string()), iso: Some("IRN".to_string()) },
        CountryCode { code: "IS".to_string(), name: "Israel".to_string(), fips: Some("IS".to_string()), iso: Some("ISR".to_string()) },
        CountryCode { code: "EG".to_string(), name: "Egypt".to_string(), fips: Some("EG".to_string()), iso: Some("EGY".to_string()) },
    ]
});

/// Lookup a CAMEO code by its prefix
pub fn lookup_code(code: &str) -> Option<&'static CameoCode> {
    let root = if code.len() >= 2 { &code[0..2] } else { code };
    ROOT_CODES.iter().find(|c| c.code == root)
}

/// Get the quad class description
pub fn quad_class_description(class: u8) -> Option<&'static str> {
    QUAD_CLASSES.get(&class).copied()
}

/// Get actor type description
pub fn actor_type_description(code: &str) -> Option<&'static str> {
    ACTOR_TYPE_CODES.get(code).copied()
}

/// Lookup country by code
pub fn lookup_country(code: &str) -> Option<&'static CountryCode> {
    let code_upper = code.to_uppercase();
    MAJOR_COUNTRIES.iter().find(|c| c.code == code_upper || c.fips.as_deref() == Some(&code_upper) || c.iso.as_deref() == Some(&code_upper))
}

/// Search codes by description pattern
pub fn search_codes(pattern: &str) -> Vec<&'static CameoCode> {
    let pattern_lower = pattern.to_lowercase();
    ROOT_CODES
        .iter()
        .filter(|c| c.description.to_lowercase().contains(&pattern_lower))
        .collect()
}