SPSFTMLRZE2R4EBAAQKWBUZDRYZ36S2VFTBIJRE6XS7JMKELV4AQC UKQAGL5F5LWZZR7RWFJI3H2MW6LXPRGPXAIGNBZI5IVJLWUFHLGAC GQVS55HIQLU7KPJNRMF57QUM4EATSWFQRCS7ZEJMJPUXFX2NHSYAC XI5ALEH6NPTQWB6O62QV62EP4H3K7WSNTHCOGT3LZIIU6I2YDGIQC TTR5IFSG25VNBQ2F2FNOLUMTEIHVBHFOEXYB2ZWEHWOURUV4GJMQC SAHJYVNBUBBIUBI4ZMAXK4QJFOT54M5UA3W2HQMTNDSP3GGCRX7QC 2SABVMY3A2RZDF3KJXZSMJ2UQ4Q5EW422G4DVBJRKK26S2ESGVQAC U4VCAFXQNTKC7KWWE3B2JJMZMFRGLBOHSZIOXCE6EEXW7WE2Q5NAC GUXZCEWWPBCHXO26JVWZ74CTSDFDDO775YR7FKY7UGVZCA7GCSYAC A6ZAYJNBYFXPUTCHTQDGAWUZIYOXNO3IJBN73ZX6PEJ3T4NMNOGQC 77SIQZ3EGGV6KSECMLPDKQFGEC7CCFAPWGER7ZARQ5STDKJNU6GQC UUD3CJZLSTAVLMIXIWE4CHN5HQSFM6K3DCPWECJMKGXDOROK4G4AC 4MG5JFXTKAE3SOVKGGNKEUTNCKOWEBHTGKVZHJWLWE3PTZTQKHPAC 5POF332LJEBGWEJUGI34P33Q4BQP7CAQNV5ODQT3PWG7FI7VNLOQC AT753JPOOWZCIYNKAG27LZSZZ72ZILWVENG42Y6U2S34JD3B6ZZQC CUADTSHQNPGMWHIJCWXKHNNIY4UJOYZ7XBZA5CJ2VVJJAMSNT4BAC JTX5OHWHO647X4XLQPUDLH34QG2VDP7N7X6OL7XEIZQYVWELV6CAC let series_spec = data_spec.get_series_spec(&self.series_id);series_spec.time_series_data(root_path)
let series_spec = match data_spec.get_series_spec(&self.series_id) {Some(series_spec) => series_spec,None => {return Err(series_id_not_in_dataspec(file!(),line!(),&self.series_id.to_string()))},};data_from_file(series_spec.country,series_spec.data_type,series_spec.series_id,root_path)
use crate::error::{Error,};
use crate::error::*;// --- Client-facing data-structures --------------------------------------------------------------/// `(DataType, Country)` key to lookup data or spec.#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]pub struct PageKey {///pub country: Country,///pub data_type: DataType,///pub index: usize,}impl PageKey {/// Return a new `PageKey`.pub fn new(country: Country, data_type: DataType, index: usize) -> Self {PageKey { country, data_type, index }}}impl<'a> TryInto<PageKey> for KeyTreeRef<'a> {type Error = keytree::Error;
// -- "ts_spec.keytree" ---------------------------------------------------------------------------
fn try_into(self) -> Result<PageKey, Self::Error> {Ok(PageKey {index: self.value("key::index")?,data_type: self.value("key::data_type")?,country: self.value("key::country")?,})}}
/// Top-level time-series specification data-structure.////// It can be serialized into something like,/// ```text/// ts_spec:/// page:/// data_type: u/// country: Australia/// graphic:/// series:/// id: AUSURANAA/// id: AUSURAQS/// id: AUSURHARMADSMEI/// id: AUSURHARMMDSMEI/// ```
/// Represents a series of graphics to be plotted on one page. A `PageKey` is a combination of/// `Country` and `DataType`. It is served to the client as one chunk of JSON.// GraphicJson. Json holds the JSON serialization as a String. PageJson is the largest component// that is serializable into JSON.
pub (crate) fn new() -> TSSpec {TSSpec(Vec::new())
/// Build time-series data from a time-series specification.pub fn from_spec(spec: &Spec, root_path: &str) -> Result<Self, Error> {spec.into_data(root_path)
/// Read from file.pub fn from_file(path: &str) -> Self {let ts_spec = fs::read_to_string(path).unwrap();let kt = KeyTree::parse(&ts_spec).unwrap();kt.to_ref().try_into().unwrap()
pub (crate) fn insert(&mut self, key: &PageKey, value: String) {match self.0.get_mut(key) {Some(_) => {println!("Tried to insert page_key: {:?} twice.", key);panic!();},None => {self.0.insert(*key, value);},}
/// ts_spec:/// page:/// data_type: u/// country: Australia/// graphic:/// series:/// id: AUSURANAA/// id: AUSURAQS/// id: AUSURHARMADSMEI/// id: AUSURHARMMDSMEIpub fn into_json(&self,data_spec: &DataSpec,root_path: &str) -> Result<TSJson, Error>{let mut ts_json = TSJson::new();
/// Converts specification data into `TSData`.pub struct Json(Vec<PageJson>);
for graphic in &page_spec.graphics {let json = graphic.into_json(data_spec, root_path)?;value.push(json);
let key = PageKey::new(page_json.country,page_json.data_type,page_json.index);let value = match serde_json::to_string(&page_json) {Ok(s) => s,Err(err) => {eprintln!("{}", err.to_string());panic!();},
impl IntoKeyTree for TSSpec {fn keytree(&self) -> KeyTreeString {let mut kt = KeyTreeString::new();kt.push_key(0, "ts_spec");for page in &self.0 {kt.push_keytree(1, page.keytree());
#[derive(Debug, Serialize)]/// Serializable into JSON data for a time-series HTML page.pub struct PageJson {country: Country,data_type: DataType,index: usize,graphics: Vec<GraphicJson>,}impl PageJson {pub (crate) fn new(country: Country, data_type: DataType, index: usize) -> Self {PageJson {country, data_type, index, graphics: Vec::new()
impl<'a> TryInto<TSSpec> for KeyTreeRef<'a> {type Error = keytree::Error;fn try_into(self) -> Result<TSSpec, Self::Error> {let page_vec: Vec<TSPageSpec> = self.vec_at("ts_spec::page")?;Ok(TSSpec(page_vec))
pub (crate) fn push(&mut self, graphic_json: GraphicJson) {self.graphics.push(graphic_json);
/// Component of `TSSpec`.////// Mirrored with a keytree specification which is the `country:` key in/// ```/// page:/// country: Australia/// data_type: u/// index: 0/// graphic:/// series:/// series_id: AUSURANAA/// data_type: u/// series:/// data_type: u/// series_id: AUSURAQS/// series:/// data_type: u/// series_id: AUSURHARMADSMEI/// series:/// data_type: u/// series_id: AUSURHARMMDSMEI/// ```#[derive(Debug)]pub struct TSPageSpec {
/// GraphicJson is the largest component that is serializable into JSON.#[derive(Debug, Serialize)]pub struct GraphicJson {
impl TSPageSpec {pub (crate) fn new(key: PageKey, graphics: Vec<TSGraphicSpec>) -> Self {TSPageSpec {country: key.country,data_type: key.data_type,index: key.index,graphics: graphics,
impl GraphicJson {pub (crate) fn new() -> Self {GraphicJson {height: None,series: Vec::new(),
pub (crate) fn key(&self) -> PageKey {PageKey {country: self.country,data_type: self.data_type,index: self.index,
// === Time-series Specification ==================================================================/// Time-series specification./// ```/// ts_spec:/// page:/// country: Australia/// data_type: u/// index: 0/// graphic:/// series:/// data_type: u/// series_id: AUSURAMS/// series:/// data_type: u/// series_id: AUSURANAA/// ```pub struct Spec(Vec<PageSpec>);impl Spec {// The Spec components do not map well to Json components.// 1. Json is not serializable - it is a HashMap which maps PageKeys to Strings to serve.// 2. PageJson does not exists. PageSpec.into_json creates a Vec<GraphicJson>.pub (crate) fn into_data(&self, root_path: &str) -> Result<TSData, Error> {let mut json = Json::new();for page_spec in &self.0 {let mut page_json = PageJson::new(page_spec.country,page_spec.data_type,page_spec.index,);for graphic_spec in &page_spec.graphics {let mut graphic_json = GraphicJson::new();for series_spec in &graphic_spec.seriess {let rts = data_from_file(page_spec.country,series_spec.data_type,series_spec.series_id.clone(),root_path,)?;let meta = meta_from_file(page_spec.country,series_spec.data_type,series_spec.series_id.clone(),root_path,);graphic_json.push(rts, meta);}page_json.push(graphic_json);}json.push(page_json);
impl IntoKeyTree for TSPageSpec {fn keytree(&self) -> KeyTreeString {
/// Read in ts specification from file./// ```/// let ts_spec = ts::Spec::from_file("ts_spec.keytree");/// ```pub fn from_file(path: &str) -> Result<Self, Error> {let source_spec = match fs::read_to_string(path) {Ok(ss) => ss,Err(err) => { return Err(failed_to_read_file(file!(),line!(),&err.to_string()))},};let kt = KeyTree::parse(&source_spec).unwrap();kt.to_ref().try_into().map_err(|err: keytree::error::Error| {keytree_error(file!(), line!(), &err.to_string())})}
kt.push_key(0, "page");kt.push_value(1, "country", &self.country.to_string());kt.push_value(1, "data_type", &self.data_type.to_string());kt.push_value(1, "index", &self.index.to_string());for graphic in &self.graphics {kt.push_keytree(1, graphic.keytree());}kt
pub (crate) fn push(&mut self, page_spec: PageSpec) {self.0.push(page_spec)
fn try_into(self) -> Result<TSPageSpec, Self::Error> {Ok(TSPageSpec {country: self.value("page::country")?,data_type: self.value("page::data_type")?,index: self.value("page::index")?,graphics: self.vec_at("page::graphic")?,})
fn try_into(self) -> Result<Spec, keytree::Error> {Ok(Spec(self.vec_at("ts_spec::page")?))
/// graphic:/// series:/// series_id: AUSURANAA/// data_type: u/// series:/// data_type: u/// series_id: AUSURAQS/// series:/// data_type: u/// series_id: AUSURHARMADSMEI/// series:/// data_type: u/// series_id: AUSURHARMMDSMEI
/// page:/// country: Australia/// data_type: u/// index: 0/// graphic:/// series:/// data_type: u/// series_id: AUSURAMS/// series:/// data_type: u/// series_id: AUSURANAA
#[derive(Debug)]pub struct TSGraphicSpec {/// Height in pixels of the graphic.pub height: Option<f32>,/// e.g. [ AUSURHARMADSMEI, ... ]pub series: Vec<TSSeriesSpec>,
pub struct PageSpec {country: Country,data_type: DataType,index: usize,graphics: Vec<GraphicSpec>,
let series_spec = data_spec.get_series_spec(series_id);let ts = series_spec.time_series_data(root_path)?;let meta = series_spec.meta(root_path);series_json.push(ts, meta);}
fn try_into(self) -> Result<PageSpec, keytree::Error> {
impl IntoKeyTree for TSGraphicSpec {fn keytree(&self) -> KeyTreeString {let mut kt = KeyTreeString::new();
/// Component of time-series specification./// ```/// graphic:/// series:/// data_type: u/// series_id: AUSURAMS/// series:/// data_type: u/// series_id: AUSURANAA/// ```pub struct GraphicSpec {height: Option<f32>,seriess: Vec<self::SeriesSpec>,}
kt.push_key(0, "graphic");if let Some(height) = self.height {kt.push_value(1, "height", &height.to_string());};for series in &self.series {kt.push_keytree(1, series.keytree());
impl GraphicSpec {pub (crate) fn new() -> Self {GraphicSpec {height: None,seriess: Vec::new(),
TSGraphicSpec {height: self.opt_value("graphic::height")?,series: self.vec_at("graphic::series")?,
GraphicSpec{height: self.opt_value("graphic::height")?,seriess: self.vec_at("graphic::series")?,
}}impl IntoKeyTree for TSSeriesSpec {fn keytree(&self) -> KeyTreeString {let mut kt = KeyTreeString::new();kt.push_key(0, "series");kt.push_value(1, "data_type", &self.data_type.to_string());kt.push_value(1, "series_id", &self.series_id.to_string());kt
/// Represents a series of graphics to be plotted on one page. A `PageKey` is a combination of/// `Country` and `DataType`. It is served to the client as one chunk of JSON.#[derive(Debug)]pub struct TSJson(pub HashMap<PageKey, String>);impl TSJson {pub (crate) fn new() -> TSJson {TSJson(HashMap::new())}pub (crate) fn insert(&mut self, key: &PageKey, value: Vec<GraphicJson>) {match self.0.get_mut(key) {Some(_) => {println!("Tried to insert page_key: {:?} twice.", key);panic!();},None => {self.0.insert(*key,serde_json::to_string(&value).unwrap(),);},}}}/// Respresents a graphic to be served to the client as JSON.#[derive(Debug, Serialize)]pub struct GraphicJson {///pub height: Option<f32>,///pub series: SeriesJson,}/// `SeriesJson` is a time-series to be served to the client as JSON.#[derive(Debug, Serialize)]pub struct SeriesJson(Vec<(RegularTimeSeries<1>, SeriesMetaData)>);impl SeriesJson {/// Create a new, empty `SeriesJson`.pub fn new() -> Self {SeriesJson(Vec::new())}/// Push time-series data and associated meta-data onto `SeriesJson`.pub fn push(&mut self, ts: RegularTimeSeries<1>, meta: SeriesMetaData) {self.0.push((ts, meta));}}/// `(DataType, Country)` key to lookup data or spec.#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]pub struct PageKey {///pub country: Country,///pub data_type: DataType,///pub index: usize,}impl PageKey {/// Return a new `PageKey`.pub fn new(country: Country, data_type: DataType, index: usize) -> Self {PageKey { country, data_type, index }}}impl<'a> TryInto<PageKey> for KeyTreeRef<'a> {type Error = keytree::Error;fn try_into(self) -> Result<PageKey, Self::Error> {Ok(PageKey {index: self.value("key::index")?,data_type: self.value("key::data_type")?,country: self.value("key::country")?,})}}
let data_spec = DataSpec::from_file("source_data.keytree").unwrap();let ts_spec = TSSpec::from_file("ts_spec.keytree");
// Step 1// println!("{}", DataSelector::from_file("series_selector.keytree")// .into_data_spec()// .unwrap()// .keytree());
let ts_json = ts_spec.into_json(&data_spec, &root_dir);}
// println!("{}", DataSelector::from_file("germany_selector.keytree")// .into_data_spec()// .unwrap()// .keytree());// println!("{}", DataSelector::from_file("austria_selector.keytree")// .into_data_spec()// .unwrap()// .keytree());// (paste this)// Step 2let data_spec = DataSpec::from_file("source_data.keytree").unwrap();// data_spec.update_write(&root_dir).unwrap();
// println!("{}", data_spec.generic_ts_spec().keytree());// (paste this)// Step 4let ts_spec = ts::Spec::from_file("ts_spec.keytree").unwrap();let ts_data = TSData::from_spec(&ts_spec, &root_dir).unwrap();dbg!(&ts_data);// Fred::series_observations("AUSURTOTMDSMEI").unwrap();// JSON does have "." as first value. What should we do? drop_until_parsable ? Or just define// dates?// We have to know where the parsing happens.// It happens in "TimeSeries::<1>::from_csv(&path)"}
//! to resume.
//! to resume. To update the data files due to a change of the DataSpec,//! ```//! let date = OffsetDateTime::now_utc() - Duration::days(3);//! data_spec//! .update_write(date.into(), &root_dir)//! .unwrap();//! ```
let year = s[..4].parse().map_err(|_| parse_date("failed"))?;let month = s[5..7].parse().map_err(|_| parse_date("failed"))?;
let year = s[..4].parse().map_err(|_| parse_date(file!(), line!(), "failed"))?;let month = s[5..7].parse().map_err(|_| parse_date(file!(), line!(), "failed"))?;
pub fn get_series_spec(&self, series_id: &SeriesId) -> SeriesSpec {let key = self.reverse.get(&series_id).unwrap();let seriess = self.map.get(&key).unwrap();(*seriess.iter().find(|series| &series.series_id == series_id).unwrap()).clone()}
pub fn get_series_spec(&self, series_id: &SeriesId) -> Option<SeriesSpec> {
/// Test if a key `(DataType, Country)` has a `SeriesId`.pub fn key_has_series_id(&self, key: (DataType, Country), series_id: &SeriesId) -> Result<bool, Error> {let series_specs = match self.map.get(&key) {Some(series_specs) => series_specs,None => { return Err(key_not_in_dataspec(&key.0.to_string(), &key.1.to_string())) },
let key = match self.reverse.get(&series_id) {Some(key) => key,None => { return None },
series_spec.write_data(root_path);series_spec.write_meta(root_path);
series_spec.write_data(root_path)?;series_spec.write_meta(root_path)?;}}Ok(())}// Need to keep a record of all files and remove files that shouldn't exist.//// i.e. its not sufficient to check if a file exists. We need to check if a file remains. So we// need to create a file list./// Only make requests and updata data to Fred for files that are in `DataSpec` but do not exist/// as data files.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)? {series_spec.write_data(root_path)?;series_spec.write_meta(root_path)?;}}}self.remove_old(root_path)?;Ok(())}/// Run through data files, query and remove any files that are not in directory.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() { continue };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") {println!("Unknown file [{}]", pathbuf.to_str().unwrap());panic!();
.skip_while(|((data_type, country), series_specs)| {match self.key_has_series_id((*data_type, *country), &sid) {Err(err) => {println!("{}", err);panic!();},Ok(is_true) => !is_true,
.skip_while(|_| {match self.get_series_spec(&sid) {Some(series_spec) => sid != series_spec.series_id,None => true,
/// Check if a file exists.pub fn exists(&self, root_path: &str) -> Result<bool, Error> {let csv_path = data_path(root_path,self.data_type,self.country,self.series_id.clone(),"csv",);let meta_path = data_path(root_path,self.data_type,self.country,self.series_id.clone(),"meta",);Ok(Path::new(&csv_path).exists() &&Path::new(&meta_path).exists())}
/// Return the meta data for a `SeriesSpec`.pub fn meta(&self, root_path: &str) -> SeriesMetaData {
/// Use `Self` to make a Fred request for series data, and return a `RegularTimeSeries`.pub fn into_time_series(&self) -> Result<RegularTimeSeries<1>, Error> {// Select the observation data from the FRED data.let observations = match Fred::series_observations(&self.series_id.to_string()) {Ok(series_obs) => series_obs.observations,Err(err) => { return Err(fred_error(file!(), line!(), &err.to_string())) },};
let path = &format!("{}/{}/{}/{}.meta",root_path,self.data_type,self.country.as_path(),self.series_id,);
// We need to build a RegularTimeSeries here, and then use RegularTimeSeries here// to build csv file.// Data is parsed into a RegularTimeSeries
let path = &format!("{}/{}/{}/{}.csv",root_path,self.data_type,self.country.as_path(),self.series_id,);
// Drop the first n itemslet skip = match self.drop_first {None => 0,Some(n) => n,};for (i, obs) in observations.iter().enumerate().skip(skip) {let date = MonthlyDate::from_str(&obs.date)?;let value: f32 = match obs.value.parse() {Ok(n) => n,Err(_) => {let err = parse_fred_value_failed(file!(),line!(),&self.data_type.to_string(),&self.country.to_string(),&self.series_id.to_string(),&format!("{}, {}",obs.date,obs.value,),i + 1,);self.soft_parse(observations, err);unreachable!();},};
match TimeSeries::<1>::from_csv(&path) {Ok(ts) => {match ts.try_into() {Ok(rts) => Ok(rts),Err(err) => Err(from_time_series(&err.to_string())),}
let date_point = DatePoint::<1>::new(date.0, [value]);v.push(date_point)}let ts = TimeSeries::new(v);let rts = match ts.try_into() {Ok(rts) => {rts},Err(_) => {let err = expected_regular_time_series(file!(),line!(),&self.data_type.to_string(),&self.country.to_string(),&self.series_id.to_string(),);self.soft_parse(observations, err);unreachable!();
Err(err) => Err(from_time_series(&err.to_string())),
};Ok(rts)}/// Parse observations into string for debugging and exit.pub fn soft_parse(&self, observations: fred_api::Observations, err: Error) {for (i, obs) in observations.iter().enumerate() {eprintln!("{} {}, {}",i + 1,obs.date,obs.value,)
/// Fetches data as specified in `checked_data.keytree` and saves to disk.pub fn write_data(&self, root_path: &str) {
let mut csv = String::new();for date_point in rts.iter(DateRange::new(None, None)) {let date = date_point.date();let date_str = format!("{}-{:02}-01",date.year(),date.month(),);
// Select the observation data from the FRED data.let observations = match Fred::series_observations(&self.series_id.to_string()) {Ok(series_obs) => series_obs.observations,Err(err) => {println!("{}", err);panic! ();},};let mut data = String::new();for obs in observations.iter() {data.push_str(&obs.to_string());data.push('\n');
let line = format!("{}, {}\n",date_str,date_point.value(0),);csv.push_str(&line);
/// Fetches data as specified in `checked_data.keytree` and saves meta_data to disk.pub fn write_meta(&self, root_path: &str) {
/// Fetches data as specified in `source_data.keytree` and saves meta_data to disk.pub fn write_meta(&self, root_path: &str) -> Result<(), Error> {
// self.time_stamp = Some(TimeStamp::now());
/// Read csv data from file and return a time-series.pub fn data_from_file(country: Country,data_type: DataType,series_id: SeriesId,root_path: &str) -> Result<RegularTimeSeries<1>, Error>{let path = data_path(root_path,data_type,country,series_id.clone(),"csv");match TimeSeries::<1>::from_csv(&path) {Ok(ts) => {match ts.try_into() {Ok(rts) => Ok(rts),Err(err) => {Err(time_series_from_csv_failed(file!(),line!(),&data_type.to_string(),&country.to_string(),&series_id.to_string(),&err.to_string(),))},}},Err(err) => Err(time_series_from_csv_failed(file!(),line!(),&data_type.to_string(),&country.to_string(),&series_id.to_string(),&err.to_string(),))}}/// Return the meta data for a `SeriesSpec`.pub fn meta_from_file(country: Country,data_type: DataType,series_id: SeriesId,root_path: &str) -> SeriesMetaData{let path = data_path(root_path,data_type,country,series_id.clone(),"meta");let meta_str = fs::read_to_string(path).unwrap();let kt = KeyTree::parse(&meta_str).unwrap();kt.to_ref().try_into().unwrap()}
walkdir = "2.3.2"