Add data comparison

This commit is contained in:
hodasemi 2023-09-20 12:19:41 +02:00
parent 71ea1cd984
commit 348bf19a3b
4 changed files with 305 additions and 1 deletions

View file

@ -13,3 +13,4 @@ serde = { version="1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
futures = "0.3.28" futures = "0.3.28"
tokio = { version="1.32.0", features=["macros", "rt-multi-thread"] } tokio = { version="1.32.0", features=["macros", "rt-multi-thread"] }
chrono = "0.4.31"

280
src/data.rs Normal file
View file

@ -0,0 +1,280 @@
#[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<f32>| unreachable!("create array from vec from an array"))
}
fn split_into_days(
input: Vec<(NaiveDateTime, f32)>,
) -> HashMap<NaiveDate, Vec<(NaiveTime, f32)>> {
let mut map: HashMap<NaiveDate, Vec<(NaiveTime, f32)>> = 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<u32, Vec<f32>> {
let mut map: HashMap<u32, Vec<f32>> = 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<Date, (f32, HashMap<&str, f32>)> {
let mut map: HashMap<Date, (f32, HashMap<&str, f32>)> = 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));
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<NaiveDate, HashMap<u32, Vec<f32>>> = 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<Day>)> = 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::<f32>()
/ consumptions.len() as f32
}
None => 0.0,
};
TimeFrame::new(
index as u8,
index as u8 + 1,
price,
consumption,
)
})
.collect::<Vec<TimeFrame>>()
.try_into()
.unwrap_or_else(|_: Vec<TimeFrame>| {
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(())
}
}
/*
*/

View file

@ -43,6 +43,28 @@ impl DataBase {
Ok(()) Ok(())
} }
pub fn read(&self, device: &str) -> Result<Vec<(u64, f32)>> {
self.sql
.prepare(&format!(
// "
// SELECT time, watts
// FROM data
// WHERE device={device}
// "
"
SELECT time, watts
FROM data
"
))?
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))?
.map(|row| {
let (time, watts) = row?;
Ok((time, watts))
})
.collect()
}
} }
#[cfg(test)] #[cfg(test)]

View file

@ -6,6 +6,7 @@ use std::{
use crate::db::DataBase; use crate::db::DataBase;
mod data;
mod db; mod db;
mod devices; mod devices;
mod tasmota; mod tasmota;