pub mod error;
pub mod fred;
pub mod parser;
pub mod ts;
pub mod ui;
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::{ file, fmt, fs, line };
use std::path::Path;
use std::str::FromStr;
use serde::Serialize;
use walkdir::WalkDir;
use countries::Country;
use fred_api::{ Fred };
use keytree::{ KeyTree, KeyTreeRef };
use keytree::serialize::{ IntoKeyTree, KeyTreeString };
use time_series::{ RegularTimeSeries, TimeSeries };
use crate::error::*;
use crate::parser::*;
use crate::ts::{ GraphicCategory, Transform };
#[derive(Clone, Debug, Serialize)]
pub struct MonthlyDate(time_series::MonthlyDate);
impl FromStr for MonthlyDate {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let year = s[..4].parse().map_err(|_| {
err(file!(), line!(), &format!("Parse datapoint {} failed", s))
})?;
let month = s[5..7].parse().map_err(|_| {
err(file!(), line!(), &format!("Parse datapoint {} failed", s))
})?;
Ok(MonthlyDate(time_series::MonthlyDate::ym(year, month)))
}
}
impl fmt::Display for MonthlyDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}-{}-01", self.0.year(), self.0.month())
}
}
#[derive(Clone, Copy, Debug, Serialize)]
pub struct DateRange(time_series::DateRange);
impl DateRange {
pub fn new(
first_date: &Option<MonthlyDate>,
last_date: &Option<MonthlyDate>) -> DateRange
{
DateRange(
time_series::DateRange::new(
&first_date.clone().map(|d| d.0),
&last_date.clone().map(|d| d.0),
)
)
}
pub fn first_date(&self) -> Option<MonthlyDate> {
self.0.first_date().map(|date| MonthlyDate(date))
}
pub fn last_date(&self) -> Option<MonthlyDate> {
self.0.last_date().map(|date| MonthlyDate(date))
}
}
impl fmt::Display for DateRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match (self.0.first_date(), self.0.last_date()) {
(None, None) => {
format!(
"open"
)
},
(Some(d1), None) => {
format!(
"from {}-{}-01",
d1.year(),
d1.month(),
)
},
(None, Some(d2)) => {
format!(
"to {}-{}-01",
d2.year(),
d2.month()
)
},
(Some(d1), Some(d2)) => {
format!(
"between {}-{}-01 and {}-{}-01",
d1.year(),
d1.month(),
d2.year(),
d2.month(),
)
},
};
write!(f, "{}", s)
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SeriesId(String);
impl SeriesId {
pub fn new(s: &str) -> Self {
SeriesId(s.to_string())
}
pub fn stem(&self) -> Self {
let inner = self.0.split('_').next().unwrap().clone();
SeriesId(String::from(inner))
}
}
impl FromStr for SeriesId {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(SeriesId(String::from(s)))
}
}
impl fmt::Display for SeriesId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub enum DataType {
U,
Cpi,
Inf,
Int,
}
impl FromStr for DataType {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"u" => Ok(DataType::U),
"cpi" => Ok(DataType::Cpi),
"inf" => Ok(DataType::Inf),
"int" => Ok(DataType::Int),
_ => Err(err(file!(), line!(), &format!("Failed to parse datatype {}.", s))),
}
}
}
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
DataType::U => "u",
DataType::Cpi => "cpi",
DataType::Inf => "inf",
DataType::Int => "int",
};
write!(f, "{}", s)
}
}
pub fn title(series_id: &str) -> Result<String, Error> {
let sid = SeriesId::from_str(series_id).unwrap();
let mut s = String::new();
let seriess = match Fred::series(&sid.to_string()) {
Ok(series) => series.seriess,
Err(e) => { return Err(err(file!(), line!(), &e.to_string())) },
};
for series in seriess.iter() {
s.push_str(&series.title)
}
Ok(s)
}
pub fn interest_rate_series(tags: &str) {
let tags_series = Fred::tags_series(tags).unwrap();
let series_items = tags_series.seriess;
let iter = series_items.iter();
for item in iter {
println!();
println!("{}", item.title);
println!("{}", item.id);
let tags = Fred::series_tags(&item.id).unwrap();
println!("{}", tags.one_line());
}
}
#[derive(Debug)]
pub struct DataSpec {
map: BTreeMap<(DataType, Country), Vec<SeriesSpec>>,
reverse: BTreeMap<SeriesId, (DataType, Country)>,
}
impl DataSpec {
pub fn empty() -> Self {
DataSpec {
map: BTreeMap::new(),
reverse: BTreeMap::new(),
}
}
pub fn get_series_spec(&self, series_id: &SeriesId) -> Option<SeriesSpec> {
let key = match self.reverse.get(&series_id) {
Some(key) => key,
None => { return None },
};
let seriess = self.map.get(&key).unwrap();
match seriess.iter().find(|&series| {
&series.series_id == series_id
}) {
Some(series_spec) => Some(series_spec.clone()),
None => None,
}
}
pub (crate) fn new() -> Self {
DataSpec {
map: BTreeMap::new(),
reverse: BTreeMap::new(),
}
}
pub (crate) fn from_vec(v: Vec<SeriesSpec>) -> Self {
let mut data_spec = DataSpec::new();
for series_spec in v {
data_spec.insert(&series_spec);
}
data_spec
}
pub fn insert(&mut self, series_spec: &SeriesSpec) {
match self.map.get_mut(&(series_spec.data_type, series_spec.country.unwrap())) {
None => {
self.map.insert(
(series_spec.data_type, series_spec.country.unwrap()),
vec!(series_spec.clone())
);
self.reverse.insert(
series_spec.series_id.clone(),
(series_spec.data_type, series_spec.country.unwrap()),
);
},
Some(value) => {
value.push(series_spec.clone());
self.reverse.insert(
series_spec.series_id.clone(),
(series_spec.data_type, series_spec.country.unwrap()),
);
},
}
}
pub fn generic_ts_spec(&self) -> ts::Spec {
let mut spec = ts::Spec::empty();
for ((data_type, country), series_specs) in self.map.iter() {
let mut page_spec = ts::PageSpec::new(
*country, *data_type, 0, None, );
let mut collated_graphic = ts::GraphicSpec {
category_opt: Some(GraphicCategory::Collation),
series_ids: Vec::new(),
graphic_range: None,
note: None,
};
let mut single_graphics = Vec::new();
for series_spec in series_specs {
let ts_series_spec = SeriesSpec {
country: None,
data_type: series_spec.data_type,
series_id: series_spec.series_id.clone(),
transforms: Vec::new(),
parser: ParserSpec::FredSpec(FredSpec::empty()),
};
page_spec.seriess.insert(series_spec.series_id.clone(), ts_series_spec);
let single_series_graphic = ts::GraphicSpec {
category_opt: Some(GraphicCategory::Source),
series_ids: vec!(series_spec.series_id.clone()),
graphic_range: None,
note: None,
};
single_graphics.push(single_series_graphic);
collated_graphic.series_ids.push(series_spec.series_id.clone());
};
let mut graphics = vec!(collated_graphic);
graphics.append(&mut single_graphics);
page_spec.graphics = graphics;
spec.0.push(page_spec);
}
spec
}
pub fn from_file(path: &str) -> Result<Self, Error> {
dbg!(path);
let source_spec = match fs::read_to_string(path) {
Ok(ss) => ss,
Err(e) => { return Err(
err(file!(), line!(), &format!("Failed to read file {}.", e))
)},
};
let kt = KeyTree::parse(&source_spec).unwrap();
kt.to_ref().try_into().map_err(|e: keytree::error::Error| {
err(file!(), line!(), &e.to_string())
})
}
pub fn write(&self, root_path: &str) -> Result<(), Error> {
for (_, series_specs) in &self.map {
for series_spec in series_specs {
series_spec.write_data_to_file(root_path)?;
series_spec.write_meta_to_file(root_path)?;
}
}
Ok(())
}
pub fn update_write(&self, root_path: &str) -> Result<(), Error> {
for (_, series_specs) in &self.map {
for series_spec in series_specs {
if !series_spec.exists(root_path)? {
dbg!(&series_spec);
series_spec.write_data_to_file(root_path)?;
series_spec.write_meta_to_file(root_path)?;
}
}
}
self.remove_old(root_path)?;
Ok(())
}
pub fn remove_old(&self, root_path: &str) -> Result<(), Error> {
for entry in WalkDir::new(root_path) {
let entry = entry.unwrap();
if !entry.file_type().is_dir() {
let pathbuf = entry.path();
let mut path_iter = pathbuf
.iter()
.rev()
.map(|os_str| os_str.to_str()
.unwrap());
let mut file_parts = path_iter
.next()
.unwrap()
.split('.');
let file_stem = file_parts.next().unwrap();
let file_ext = file_parts.next().unwrap();
if file_ext == "csv" || file_ext == "meta" {
let series_id = SeriesId::new(file_stem);
match self.get_series_spec(&series_id) {
Some(_) => {},
None => {
println!("remove file: {}", entry.path().display());
},
}
}
};
}
Ok(())
}
pub fn resume_write(&self, series_id: &str, root_path: &str) -> Result<(), Error> {
let sid = SeriesId::new(series_id);
for (_, series_specs) in self
.map
.iter()
.skip_while(|_| {
match self.get_series_spec(&sid) {
Some(series_spec) => sid != series_spec.series_id,
None => true,
}
})
{
for series_spec in series_specs {
series_spec.write_data_to_file(root_path)?;
series_spec.write_meta_to_file(root_path)?;
}
}
Ok(())
}
}
impl<'a> TryInto<DataSpec> for KeyTreeRef<'a> {
type Error = keytree::Error;
fn try_into(self) -> Result<DataSpec, Self::Error> {
let v: Vec<SeriesSpec> = self.vec_at("seriess::series")?;
Ok(DataSpec::from_vec(v))
}
}
impl IntoKeyTree for DataSpec {
fn keytree(&self) -> KeyTreeString {
let mut kt = KeyTreeString::new();
kt.push_key(0, "seriess");
for (_, series_specs) in &self.map {
for series_spec in series_specs {
kt.push_keytree(1, series_spec.keytree());
}
}
kt
}
}
#[derive(Clone, Debug)]
pub struct SeriesSpec {
country: Option<Country>,
pub data_type: DataType,
pub series_id: SeriesId,
pub transforms: Vec<Transform>,
pub parser: ParserSpec,
}
impl SeriesSpec {
pub fn country(&self) -> Country {
self.country.unwrap()
}
pub fn data_path(&self, root_path: &str, extension: &str) -> String {
format!(
"{}/{}/{}/{}.{}",
root_path,
self.data_type,
self.country().as_path(),
self.series_id.stem(),
extension,
)
}
pub fn dir_path(&self, root_path: &str) -> String {
format!(
"{}/{}/{}",
root_path,
self.data_type,
self.country().as_path(),
)
}
pub fn read_meta_from_file(&self, root_path: &str) -> SeriesMetaData
{
let meta_str = fs::read_to_string(
&self.data_path(root_path, "meta")
).unwrap();
let kt = KeyTree::parse(&meta_str).unwrap();
kt.to_ref().try_into().unwrap()
}
pub fn exists(&self, root_path: &str) -> Result<bool, Error> {
Ok(
Path::new(&self.data_path(root_path, "csv")).exists() &&
Path::new(&self.data_path(root_path, "meta")).exists()
)
}
pub fn read_data_with_transforms(
&self,
root_path: &str) -> Result<RegularTimeSeries<1>, Error>
{
let mut rts = self.read_data_without_transform(root_path)?;
for transform in &self.transforms {
rts = match transform {
Transform::ToMonthly => rts.to_monthly(0),
Transform::ToQuarterly => rts.to_quarterly(0),
Transform::YearOnYear => {
match rts.to_year_on_year(0) {
Ok(rts) => rts,
Err(e) => { return Err(err(file!(), line!(), &e.to_string())) },
}
},
Transform::DateRange(range) => rts.range(&range.0),
};
}
Ok(rts)
}
pub fn read_data_without_transform(
&self,
root_path: &str) -> Result<RegularTimeSeries<1>, Error>
{
let ts = TimeSeries::<1>::from_csv(&self.data_path(root_path, "csv"))
.map_err(|e| {
err(file!(), line!(), &e.to_string())
})?;
let rts = ts.try_into()
.map_err(|e: time_series::error::Error| {
err(file!(), line!(), &e.to_string())
})?;
Ok(rts)
}
pub fn to_data(
&self,
parser: ParserSpec) -> Result<RegularTimeSeries<1>, Error>
{
match self.parser {
ParserSpec::FredSpec(fred_spec) => FredParser(fred_spec).to_data(self),
ParserSpec::FredDailySpec(fred_daily_spec) => FredDailyParser(fred_daily_spec).to_data(self),
}
}
pub fn write_data_to_file(&self, root_path: &str) -> Result<(), Error> {
let rts = self.to_data(self.parser)?;
let mut s = String::new();
for dp in rts.iter(time_series::DateRange::new(&None, &None)) {
s.push_str(&format!(
"{}-{}-01, {}",
dp.date().year(),
dp.date().month(),
dp.value(0).to_string()
))
}
fs::create_dir_all(&self.dir_path(root_path))
.map_err(|e| err(file!(), line!(), &format!("Failed to create dir {}.", e)))?;
fs::write(self.data_path(root_path, "csv"), s)
.map_err(|e| err(file!(), line!(), &e.to_string()))?;
Ok(())
}
pub fn write_meta_to_file(&self, root_path: &str) -> Result<(), Error> {
let series = Fred::series(&self.series_id.to_string())
.map_err(|e| err(file!(), line!(), &e.to_string()))?;
let series_item = series.seriess.iter().next().ok_or_else(|| {
err(file!(), line!(), &format!("Expected series data for {}", self.series_id))
})?;
let meta = SeriesMetaData {
realtime: series.realtime_start.clone(),
series_id: self.series_id.clone(),
title: series_item.title.clone(),
observation_start: series_item.observation_start.clone(),
observation_end: series_item.observation_end.clone(),
frequency: series_item.frequency.clone(),
seasonal_adjustment: series_item.seasonal_adjustment.clone(),
};
let json = serde_json::to_string(&meta)
.map_err(|e| err(file!(), line!(), &e.to_string()))?;
fs::create_dir_all(self.dir_path(root_path))
.map_err(|e| err(file!(), line!(), &format!("Failed to create dir {}", e)))?;
let path = self.data_path(root_path, "meta");
println!("Writing {}", path);
fs::write(path, json)
.map_err(|e| err(file!(), line!(), &e.to_string()))?;
Ok(())
}
}
impl IntoKeyTree for SeriesSpec {
fn keytree(&self) -> KeyTreeString {
let mut kt = KeyTreeString::new();
kt.push_key(0, "series");
if let Some(country) = self.country {
kt.push_value(1, "country", country);
};
kt.push_value(1, "data_type", &self.data_type);
kt.push_value(1, "series_id", &self.series_id);
for f in &self.transforms {
kt.push_value(1, "f", f);
}
if let ParserSpec::FredDailySpec(_) = self.parser {
kt.push_value(1, "parse_cat", "fred_daily")
};
if let ParserSpec::FredSpec(parser_spec) = self.parser {
if parser_spec.drop_first != 0 {
kt.push_key(1, "parser");
kt.push_value(2, "drop_first", parser_spec.drop_first.to_string());
}
};
kt
}
}
impl<'a> TryInto<SeriesSpec> for KeyTreeRef<'a> {
type Error = keytree::Error;
fn try_into(self) -> Result<SeriesSpec, keytree::Error> {
let first_date = self.opt_value("series::first_date")?;
let last_date = self.opt_value("series::last_date")?;
let date_range = DateRange::new(&first_date, &last_date);
let mut transforms: Vec<Transform> = self.opt_vec_value("series::transform")?;
if first_date.is_some() || last_date.is_some() {
transforms.push(Transform::DateRange(date_range));
}
let a_parser: Option<String> = self.opt_value("series::parse_cat::fred_daily")?;
let b_parser: Option<String> = self.opt_value("series::parse_cat::fred")?;
let parser = match (a_parser.is_some(), b_parser.is_some()) {
(false, false) => ParserSpec::FredDailySpec(FredDailySpec::empty()),
(true, false) => ParserSpec::FredDailySpec(FredDailySpec::empty()),
(false, true) => ParserSpec::FredSpec(self.at("series::parser")?),
(true, true) => Err(keytree::error::err(file!(), line!(), "Only one parser allowed."))?,
};
Ok(
SeriesSpec{
country: self.opt_value("series::country")?,
data_type: self.value("series::data_type")?,
series_id: self.value("series::series_id")?,
transforms,
parser,
}
)
}
}
#[derive(Debug, Serialize)]
pub struct SeriesMetaData {
realtime: String,
series_id: SeriesId,
title: String,
observation_start: String,
observation_end: String,
frequency: String,
seasonal_adjustment: String,
}
impl SeriesMetaData {
pub fn from_file(
data_type: DataType,
country: Country,
series_id: SeriesId,
root_path: &str) -> Result<Self, Error>
{
let path = &format!(
"{}/{}/{}/{}.meta",
root_path,
data_type,
country,
series_id,
);
let meta_str = fs::read_to_string(&path).unwrap();
let kt = KeyTree::parse(&meta_str).unwrap();
Ok(kt.to_ref().try_into().unwrap())
}
}
impl<'a> TryInto<SeriesMetaData> for KeyTreeRef<'a> {
type Error = keytree::Error;
fn try_into(self) -> Result<SeriesMetaData, Self::Error> {
Ok(
SeriesMetaData {
realtime: self.value("series_meta::realtime")?,
series_id: self.value("series_meta::series_id")?,
title: self.value("series_meta::title")?,
observation_start: self.value("series_meta::observation_start")?,
observation_end: self.value("series_meta::observation_end")?,
frequency: self.value("series_meta::frequency")?,
seasonal_adjustment: self.value("series_meta::seasonal_adjustment")?,
}
)
}
}
impl IntoKeyTree for SeriesMetaData {
fn keytree(&self) -> KeyTreeString {
let mut kt = KeyTreeString::new();
kt.push_key(0, "series_meta");
kt.push_value(1, "realtime", &self.realtime);
kt.push_value(1, "series_id", &self.series_id.to_string());
kt.push_value(1, "title", &self.title);
kt.push_value(1, "observation_start", &self.observation_start);
kt.push_value(1, "observation_end", &self.observation_end);
kt.push_value(1, "frequency", &self.frequency);
kt.push_value(1, "seasonal_adjustment", &self.seasonal_adjustment);
kt
}
}
#[derive(Debug, Serialize)]
pub struct SeriesJson {
series_id: SeriesId,
rts: RegularTimeSeries<1>,
meta: Option<SeriesMetaData>,
transforms: Vec<Transform>,
}
#[derive(Clone, Copy, Debug, Serialize)]
pub struct GraphicRange {
min: f32,
max: f32,
}
impl FromStr for GraphicRange {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let segment: Vec<&str> = s.split(" to ").collect();
if segment[0].is_empty() || segment[1].is_empty() {
return Err(
err(file!(), line!(), &format!("Parse graphic range {} failed", s)))
};
let min = segment[0].parse()
.map_err(|_| err(file!(), line!(), &format!("Parse graphic range {} failed", s)))?;
let max = segment[1].parse()
.map_err(|_| err(file!(), line!(), &format!("Parse graphic range {} failed", s)))?;
Ok(GraphicRange { min, max })
}
}
impl fmt::Display for GraphicRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} to {}", self.min, self.max)
}
}