use std::convert::TryInto;
use fred_api::Fred;
use keytree::{ KeyTreeRef };
use time_series::{ DatePoint, RegularTimeSeries, TimeSeries };
use crate::SeriesSpec;
use crate::error::*;
#[derive(Clone, Copy, Debug)]
pub enum ParserSpec {
FredSpec(FredSpec),
FredDailySpec(FredDailySpec),
}
#[derive(Clone, Copy, Debug)]
pub struct FredSpec {
pub drop_first: usize
}
impl FredSpec {
pub fn empty() -> Self {
FredSpec { drop_first: 0 }
}
}
#[derive(Clone, Copy, Debug)]
pub struct FredDailySpec;
impl FredDailySpec {
pub fn empty() -> Self {
FredDailySpec
}
}
impl<'a> TryInto<FredSpec> for KeyTreeRef<'a> {
type Error = keytree::Error;
fn try_into(self) -> Result<FredSpec, Self::Error> {
let drop_first: usize = self.opt_value("parser::drop_first")?
.unwrap_or(0);
Ok(FredSpec { drop_first })
}
}
pub struct FredLineParser;
impl FredLineParser {
pub fn default() -> Self {
FredLineParser
}
}
pub enum FredLine {
Ok(DatePoint::<1>),
Dot(time_series::MonthlyDate),
}
impl FredLine {
fn to_datepoint(&self) -> Option<DatePoint::<1>> {
match self {
FredLine::Ok(dp) => Some(*dp),
FredLine::Dot(_) => None,
}
}
fn new(obs: &fred_api::Observation, line: usize) -> Result<FredLine, Error> {
let year = obs.date[..4].parse()
.map_err(|_| {
err(
file!(), line!(),
&format!( "Failed to parse {}, {} at line {}.", obs.date, obs.value, &line),
)
})?;
let month = obs.date[5..7].parse()
.map_err(|_| {
err(file!(), line!(),
&format!("Failed to parse {}, {} at line {}.",
obs.date,
obs.value,
&line.to_string(),
),
)
})?;
let date = time_series::MonthlyDate::ym(year, month);
match obs.value.as_str() {
"." => Ok(FredLine::Dot(date)),
_ => {
let value = obs.value[12..].parse::<f32>()
.map_err(|_| {
err(
file!(), line!(),
&format!("Failed to parse {}, {} at line {}.",
obs.date,
obs.value,
&line,
),
)
})?;
Ok(FredLine::Ok(
DatePoint::new(date, [value])
))
},
}
}
}
pub struct FredParser(pub FredSpec);
impl FredParser {
pub (crate) fn drop_first(&self) -> usize {
self.0.drop_first
}
pub (crate) fn to_data(&self, spec: &SeriesSpec) -> Result<RegularTimeSeries::<1>, Error> {
let observations = match Fred::series_observations(&spec.series_id.to_string()) {
Ok(series_obs) => series_obs.observations,
Err(e) => {
return Err(err(file!(), line!(), &e.to_string()))
},
};
let mut ts = TimeSeries::<1>::new(Vec::new());
for (line_num, obs) in observations.iter()
.enumerate()
.skip(self.0.drop_first)
{
let dp = FredLine::new(obs, line_num + 1)?.to_datepoint().ok_or_else(|| {
err(file!(), line!(),
&format!("Expected datapoint found dot from obs: {}, {} at line {}.",
obs.date,
obs.value,
&(line_num + 1).to_string(),
),
)
})?;
ts.push(dp);
}
ts.try_into()
.map_err(|e: time_series::error::Error| err(file!(), line!(), &e.to_string()))
}
}
pub enum Parser {
FredParser,
FredDailyParser,
}
pub struct FredDailyParser(pub FredDailySpec);
impl FredDailyParser {
pub (crate) fn to_data(&self, series_spec: &SeriesSpec) -> Result<RegularTimeSeries::<1>, Error> {
let observations = match Fred::series_observations(&series_spec.series_id.to_string()) {
Ok(series_obs) => series_obs.observations,
Err(e) => { return Err(err(file!(), line!(), &e.to_string())) },
};
let mut ts = TimeSeries::<1>::new(Vec::new());
let mut month_data = Vec::new();
let mut current_date = time_series::MonthlyDate::ym(0,0);
for (i, obs) in observations.iter().enumerate() {
match FredLine::new(obs, i + 1)?.to_datepoint() {
Some(dp) => {
if dp.date() != current_date {
let value = month_data.iter().sum::<f32>() / month_data.len() as f32;
ts.push(DatePoint::<1>::new(current_date, [value]));
current_date = dp.date();
} else { month_data.push(dp.value(0)); }
},
None => {}, }
}
ts.try_into()
.map_err(|_| {
err(file!(), line!(), &format!("Expected regular time-series ({} {} {})",
&series_spec.country(),
&series_spec.data_type,
&series_spec.series_id,
))
})
}
}