#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Date { pub day: u8, pub month: u8, pub year: u32, } impl Date { pub fn new(day: u8, month: u8, year: u32) -> Self { Self { day, month, year } } } #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct Day { pub date: Date, pub hours: [TimeFrame; 24], } impl Day { pub fn new(date: Date, hours: [TimeFrame; 24]) -> Self { Self { date, hours } } pub fn cost(&self) -> f32 { let mut sum = 0.0; for hour in self.hours.iter() { sum += hour.cost(); } sum } pub fn consumption(&self) -> f32 { let mut sum = 0.0; for hour in self.hours.iter() { sum += hour.consumed; } sum / 1000.0 } } #[derive(Debug, Clone, PartialEq, PartialOrd)] pub struct TimeFrame { pub start: u8, pub end: u8, // const in euro per kWh pub cost: f32, // average Wh in the time frame pub consumed: f32, } impl TimeFrame { pub fn new(start: u8, end: u8, cost: f32, consumed: f32) -> Self { Self { start, end, cost, consumed, } } pub fn cost(&self) -> f32 { self.consumed / 1000.0 * self.cost } } #[cfg(test)] mod test { use std::collections::HashMap; use super::*; use crate::db::DataBase; use crate::devices::Devices; use anyhow::Result; use chrono::prelude::NaiveDateTime; use chrono::{Datelike, NaiveDate, NaiveTime, Timelike}; fn generate_price_list(low: f32, high: f32) -> [f32; 24] { [ vec![low; 6], vec![high; 4], vec![low; 4], vec![high; 6], vec![low; 4], ] .concat() .try_into() .unwrap_or_else(|_: Vec| unreachable!("create array from vec from an array")) } fn split_into_days( input: Vec<(NaiveDateTime, f32)>, ) -> HashMap> { let mut map: HashMap> = HashMap::new(); for (datetime, watts) in input { let date = datetime.date(); let tuple = (datetime.time(), watts); match map.get_mut(&date) { Some(list) => list.push(tuple), None => { map.insert(date, vec![tuple]); } } } map } fn split_into_hours(input: Vec<(NaiveTime, f32)>) -> HashMap> { let mut map: HashMap> = HashMap::new(); for (time, watts) in input { match map.get_mut(&time.hour()) { Some(list) => list.push(watts), None => { map.insert(time.hour(), vec![watts]); } } } map } fn create_cost_diff_overview( input: Vec<(&str, Vec<(Date, f32, f32)>)>, ) -> HashMap)> { let mut map: HashMap)> = HashMap::new(); for (provider, data) in input { for (date, cost, consumption) in data { match map.get_mut(&date) { Some((cons, provider_data)) => { assert_eq!(consumption, *cons); match provider_data.get(provider) { Some(_e) => panic!("double entry!?"), None => { provider_data.insert(provider, cost); } } } None => { map.insert(date, (consumption, HashMap::from([(provider, cost)]))); } } } } map } #[tokio::test] async fn example() -> Result<()> { let mut price_list = Vec::new(); // Drewag preise let drewag_price = 0.366; let drewag_prices = generate_price_list(drewag_price, drewag_price); price_list.push(("drewag", drewag_prices)); // tibber monthly let tibber_average_price = 0.25; let tibber_average_prices = generate_price_list(tibber_average_price, tibber_average_price); price_list.push(("tibber monthly", tibber_average_prices)); // tibber hourly prices let price_low = 0.19; let price_high = 0.366; let tibber_hourly_prices = generate_price_list(price_low, price_high); price_list.push(("tibber hourly", tibber_hourly_prices)); // tibber optimal prices let tibber_hourly_prices_optimal = generate_price_list(price_low, price_low); price_list.push(("tibber hourly (optimal)", tibber_hourly_prices_optimal)); let db = DataBase::new("data.db").await?; let devices = Devices::read("devices.conf").await?; for plug in devices.plugs { println!("===== data for plug {plug} ====="); let days: HashMap>> = split_into_days( db.read(&plug)? .into_iter() .map(|(time, watts)| { ( NaiveDateTime::from_timestamp_opt(time as i64 / 1000, 0).unwrap(), watts, ) }) .collect(), ) .into_iter() .map(|(date, entries)| (date, split_into_hours(entries))) .collect(); let data: Vec<(&str, Vec)> = price_list .iter() .map(|(provider, prices)| { ( *provider, days.iter() .map(|(date, entries)| { Day::new( Date::new( date.day() as u8, date.month() as u8, date.year() as u32, ), prices .into_iter() .enumerate() .map(|(index, &price)| { let consumption = match entries.get(&(index as u32)) { Some(consumptions) => { consumptions.iter().sum::() / consumptions.len() as f32 } None => 0.0, }; TimeFrame::new( index as u8, index as u8 + 1, price, consumption, ) }) .collect::>() .try_into() .unwrap_or_else(|_: Vec| { unreachable!("create array from vec from an array") }), ) }) .collect(), ) }) .collect(); let costs: Vec<(&str, Vec<(Date, f32, f32)>)> = data .iter() .map(|(provider, days)| { ( *provider, days.iter() .map(|day| (day.date.clone(), day.cost(), day.consumption())) .collect(), ) }) .collect(); let cost_diff = create_cost_diff_overview(costs); println!("{cost_diff:#?}"); println!(); } Ok(()) } } /* */