use serde::Deserialize;
use std::collections::BTreeMap;
use std::sync::OnceLock;
pub const DATA_JSON: &str = include_str!("../data/scheduled_changes.json");
#[derive(Debug, Clone, Deserialize)]
pub struct Dataset {
pub copied_from: String,
pub snapshot_date: String,
pub source_commit: String,
pub scheduled: BTreeMap<String, CountrySchedule>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CountrySchedule {
pub researched_on: String,
pub coverage: String,
pub pending: Vec<Pending>,
pub scheduled_changes: Vec<Change>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Pending {
pub status: String,
pub description: String,
pub contribution_id: Option<String>,
pub source: PendingSource,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PendingSource {
pub url: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Change {
#[serde(skip_deserializing, default)]
pub country: String,
pub contribution_id: Option<String>,
pub effective_from: String,
pub date_basis: String,
pub change: Delta,
pub status: String,
pub note: String,
pub source: Source,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Delta {
pub rate: Option<f64>,
pub cap_annual_local: Option<i64>,
pub threshold_weekly_local: Option<i64>,
pub floor_annual_local: Option<i64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Source {
pub url: String,
pub quote: String,
pub read_on: String,
pub kind: String,
}
pub fn dataset() -> &'static Dataset {
static DATA: OnceLock<Dataset> = OnceLock::new();
DATA.get_or_init(|| {
let mut d: Dataset =
serde_json::from_str(DATA_JSON).expect("embedded dataset is valid JSON");
for (iso, s) in d.scheduled.iter_mut() {
for c in s.scheduled_changes.iter_mut() {
c.country = iso.clone();
}
}
d
})
}
pub fn changes() -> Vec<&'static Change> {
dataset()
.scheduled
.values()
.flat_map(|s| s.scheduled_changes.iter())
.collect()
}
pub fn changes_for(iso: &str) -> Vec<&'static Change> {
changes()
.into_iter()
.filter(|c| c.country.eq_ignore_ascii_case(iso))
.collect()
}
pub fn changes_after(date: &str) -> Vec<&'static Change> {
changes()
.into_iter()
.filter(|c| c.effective_from.as_str() > date)
.collect()
}
pub fn changes_for_after(iso: &str, date: &str) -> Vec<&'static Change> {
changes_for(iso)
.into_iter()
.filter(|c| c.effective_from.as_str() > date)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use sha2::{Digest, Sha256};
const DATA_SHA256: &str = "0b3443dbc3b00e1884a124ce503745c7b392fe02e0f662563c04ea3565e75576";
#[test]
fn embedded_copy_is_byte_identical() {
let hex: String = Sha256::digest(DATA_JSON.as_bytes())
.iter()
.map(|b| format!("{b:02x}"))
.collect();
assert_eq!(hex, DATA_SHA256);
}
#[test]
fn counts() {
assert_eq!(changes().len(), 66);
let mut iso: Vec<_> = changes().iter().map(|c| c.country.as_str()).collect();
iso.dedup();
assert_eq!(iso.len(), 28);
assert_eq!(dataset().scheduled.len(), 76);
}
#[test]
fn us_social_security_2027_cap() {
let c = changes_for("us")
.into_iter()
.find(|c| c.contribution_id.as_deref() == Some("social_security"))
.unwrap();
assert_eq!(c.effective_from, "2027-01-01");
assert_eq!(c.change.cap_annual_local, Some(192_300));
assert_eq!(c.status, "enacted");
}
#[test]
fn date_filters() {
assert!(changes_after("9999-12-31").is_empty());
assert_eq!(changes_after("0000-01-01").len(), changes().len());
assert!(changes_for_after("US", "2027-01-01")
.iter()
.all(|c| c.effective_from.as_str() > "2027-01-01"));
assert!(changes_for("XX").is_empty());
}
}