use crate::adapters::common::periods;
use crate::error::Result;
use crate::models::economic::{EconomicSeries, MacroObservation};
use super::models::WorldBankObservation;
pub(crate) const DEFAULT_COUNTRY: &str = "WLD";
pub(crate) fn split_series_id(series_id: &str) -> (String, String) {
match series_id.split_once('/') {
Some((country, indicator)) if !country.is_empty() && !indicator.is_empty() => {
(country.trim().to_uppercase(), indicator.trim().to_string())
}
_ => (DEFAULT_COUNTRY.to_string(), series_id.trim().to_string()),
}
}
pub(crate) fn normalize_date(period: &str) -> String {
let period = period.trim();
if let Some((year, quarter)) = period.split_once('Q')
&& let Ok(q) = quarter.parse::<u32>()
&& (1..=4).contains(&q)
{
return periods::quarter_start(year, q);
}
if let Some((year, month)) = period.split_once('M')
&& let Ok(m) = month.parse::<u32>()
&& (1..=12).contains(&m)
{
return periods::month_start(year, m);
}
if period.len() == 4 && period.chars().all(|c| c.is_ascii_digit()) {
return periods::year_start(period);
}
period.to_string()
}
pub(crate) fn infer_frequency(period: Option<&str>) -> Option<String> {
let first = period?;
if first.contains('Q') {
Some("Quarterly".to_string())
} else if first.contains('M') {
Some("Monthly".to_string())
} else if first.len() == 4 {
Some("Annual".to_string())
} else {
None
}
}
pub(crate) fn to_canonical(series_id: &str, raw: Vec<WorldBankObservation>) -> EconomicSeries {
let frequency = infer_frequency(raw.first().map(|o| o.date.as_str()));
let title = raw
.first()
.and_then(|o| o.indicator.as_ref())
.and_then(|i| i.value.clone());
let country = raw
.first()
.and_then(|o| o.country.as_ref())
.and_then(|c| c.value.clone());
let units = raw
.iter()
.find_map(|o| o.unit.as_ref().filter(|u| !u.trim().is_empty()).cloned());
let mut observations: Vec<MacroObservation> = raw
.into_iter()
.map(|o| MacroObservation {
date: normalize_date(&o.date),
value: o.value,
})
.collect();
observations.reverse();
EconomicSeries {
series_id: series_id.to_string(),
title: match (title, country) {
(Some(t), Some(c)) => Some(format!("{t} — {c}")),
(Some(t), None) => Some(t),
(None, c) => c,
},
units,
frequency,
observations,
}
}
pub(crate) async fn fetch_economic_series_response(series_id: &str) -> Result<EconomicSeries> {
let (country, indicator) = split_series_id(series_id);
let raw = super::client()?.indicator(&country, &indicator).await?;
Ok(to_canonical(series_id, raw))
}