use crate::adapters::common::numbers::parse_number;
use crate::adapters::common::periods;
use crate::error::Result;
use crate::models::economic::{EconomicSeries, MacroObservation};
use super::models::BlsSeries;
#[derive(Debug, PartialEq, Eq)]
pub(super) struct Period {
pub date: String,
pub frequency: &'static str,
}
pub(super) fn resolve_period(year: &str, period: &str) -> Option<Period> {
let (kind, n) = period.split_at_checked(1)?;
let n: u32 = n.parse().ok()?;
match kind {
"M" if (1..=12).contains(&n) => Some(Period {
date: periods::month_start(year, n),
frequency: "Monthly",
}),
"Q" if (1..=4).contains(&n) => Some(Period {
date: periods::quarter_start(year, n),
frequency: "Quarterly",
}),
"S" if (1..=2).contains(&n) => Some(Period {
date: periods::half_start(year, n),
frequency: "Semiannual",
}),
"A" => Some(Period {
date: periods::year_start(year),
frequency: "Annual",
}),
_ => None,
}
}
pub(super) fn parse_value(raw: &str) -> Option<f64> {
parse_number(raw, &["-"])
}
pub(super) fn to_canonical(series_id: &str, series: BlsSeries) -> EconomicSeries {
let title = series.catalog.and_then(|c| c.series_title);
let mut frequency: Option<&'static str> = None;
let mut observations: Vec<MacroObservation> = series
.data
.into_iter()
.filter_map(|point| {
let period = resolve_period(&point.year, &point.period)?;
frequency.get_or_insert(period.frequency);
Some(MacroObservation {
date: period.date,
value: parse_value(&point.value),
})
})
.collect();
observations.sort_by(|a, b| a.date.cmp(&b.date));
EconomicSeries {
series_id: series_id.to_string(),
title,
units: None,
frequency: frequency.map(str::to_string),
observations,
}
}
pub(crate) async fn fetch_economic_series_response(series_id: &str) -> Result<EconomicSeries> {
let series = super::client()?.series(series_id).await?;
Ok(to_canonical(series_id, series))
}