Add months endpoint to electricity API

This commit is contained in:
2025-09-08 18:04:27 +02:00
parent 6f3e0629c5
commit 84231b5ef7
2 changed files with 240 additions and 1 deletions

View File

@@ -1,3 +1,5 @@
use shared_api_lib::year_month::YearMonth;
pub fn get_day_power_entities(date: &chrono::NaiveDate, database_path: &str) -> Vec<LogEntity> {
let mut items = Vec::new();
@@ -195,3 +197,135 @@ pub struct LogDateSummary {
pub total_power_return_night: f64,
pub gas_consumption_in_cubic_meters: f64,
}
pub fn get_month_power_summaries(
start: &YearMonth,
stop: &YearMonth,
database_path: &str,
) -> Vec<LogMonthSummary> {
let mut items = Vec::new();
let maybe_connection = rusqlite::Connection::open(database_path);
if maybe_connection.is_err() {
log::error!(
"Failed to open database connection to {} with error {}",
database_path,
maybe_connection.unwrap_err()
);
return items;
}
let connection = maybe_connection.unwrap();
let maybe_statement = connection.prepare(
"SELECT
STRFTIME('%Y-%m', \"Date\") AS YearMonth,
MAX(TotalPowerConsumptionDay),
MIN(TotalPowerConsumptionDay),
MAX(TotalPowerConsumptionNight),
MIN(TotalPowerConsumptionNight),
MAX(TotalPowerReturnDay),
MIN(TotalPowerReturnDay),
MAX(TotalPowerReturnNight),
MIN(TotalPowerReturnNight),
MAX(GasConsumptionInCubicMeters),
MIN(GasConsumptionInCubicMeters)
FROM \"ElectricityLog\"
WHERE \"Date\" >= :start_date AND \"Date\" <= :stop_date
GROUP BY YearMonth
ORDER BY YearMonth;",
);
if maybe_statement.is_err() {
log::error!(
"Failed to prepate database statement with error {}",
maybe_statement.unwrap_err()
);
return items;
}
let formatted_start = format!("{}-01", start);
let formatted_stop = format!("{}-31", stop);
log::info!(
"Fetching month power data for date range {} to {}",
formatted_start,
formatted_stop
);
let mut statement = maybe_statement.unwrap();
let maybe_row_iterator = statement.query_map(
&[
(":start_date", &formatted_start),
(":stop_date", &formatted_stop),
],
|row| {
Ok(LogMonthSummaryEntity {
year_month: row.get(0)?,
total_power_consumption_day: row.get::<usize, f64>(1)?
- row.get::<usize, f64>(2)?,
total_power_consumption_night: row.get::<usize, f64>(3)?
- row.get::<usize, f64>(4)?,
total_power_return_day: row.get::<usize, f64>(5)? - row.get::<usize, f64>(6)?,
total_power_return_night: row.get::<usize, f64>(7)? - row.get::<usize, f64>(8)?,
gas_consumption_in_cubic_meters: row.get::<usize, f64>(9)?
- row.get::<usize, f64>(10)?,
})
},
);
match maybe_row_iterator {
Ok(iterator) => {
for row in iterator {
match row {
Ok(entity) => {
if let Some(year_month) = YearMonth::try_parse(&entity.year_month) {
items.push(LogMonthSummary {
year: year_month.year,
month: year_month.month,
total_power_consumption_day: entity.total_power_consumption_day,
total_power_consumption_night: entity.total_power_consumption_night,
total_power_return_day: entity.total_power_return_day,
total_power_return_night: entity.total_power_return_night,
gas_consumption_in_cubic_meters: entity
.gas_consumption_in_cubic_meters,
});
} else {
log::error!(
"Failed to parse year month {} from SQL row",
entity.year_month
);
}
}
Err(error) => log::error!(
"Failed to interpret row from SQL query with error {}",
error
),
}
}
}
Err(error) => {
log::error!(
"Failed to execute month power data SQL query with error {}",
error
);
}
}
items
}
struct LogMonthSummaryEntity {
pub year_month: String,
pub total_power_consumption_day: f64,
pub total_power_consumption_night: f64,
pub total_power_return_day: f64,
pub total_power_return_night: f64,
pub gas_consumption_in_cubic_meters: f64,
}
pub struct LogMonthSummary {
pub year: i32,
pub month: u8,
pub total_power_consumption_day: f64,
pub total_power_consumption_night: f64,
pub total_power_return_day: f64,
pub total_power_return_night: f64,
pub gas_consumption_in_cubic_meters: f64,
}

View File

@@ -131,7 +131,61 @@ async fn serve(configuration: &std::sync::Arc<CommandLineArgs>) {
},
);
warp::serve(day.or(days))
let months = warp::get()
.and(warp::path("months"))
.and(warp::query::<HashMap<String, String>>())
.and(warp::header::headers_cloned())
.and(warp::any().map({
let configuration = configuration.clone();
move || configuration.clone()
}))
.map(
|query: HashMap<String, String>,
headers,
configuration: std::sync::Arc<CommandLineArgs>| {
if !has_required_header(
&headers,
&configuration.http_header_name_to_validate,
&configuration.http_header_value_to_validate,
) {
log::info!("Access requested to /months with invalid header value");
return Response::builder()
.status(403)
.body(String::from("Forbidden"));
}
let maybe_start =
shared_api_lib::query_helpers::try_parse_query_month_year(query.get("start"));
if maybe_start.is_none() {
return Response::builder()
.status(400)
.body(String::from("Unsupported \"start\" param in query."));
}
let maybe_stop =
shared_api_lib::query_helpers::try_parse_query_month_year(query.get("stop"));
if maybe_stop.is_none() {
return Response::builder()
.status(400)
.body(String::from("Unsupported \"stop\" param in query."));
}
let start = maybe_start.unwrap();
let stop = maybe_stop.unwrap();
if start > stop {
return Response::builder().status(400).body(String::from(
"Param \"start\" must be smaller than or equal to param \"stop\" in query.",
));
}
let json = get_months_power_json(&start, &stop, &configuration.database_path);
return Response::builder()
.header("Content-Type", "application/json")
.body(json);
},
);
warp::serve(day.or(days).or(months))
.run(([127, 0, 0, 1], configuration.listening_port))
.await;
}
@@ -301,3 +355,54 @@ struct DaysResponseItem {
total_power_return: f64,
total_gas_use: f64,
}
fn get_months_power_json(
start: &shared_api_lib::year_month::YearMonth,
stop: &shared_api_lib::year_month::YearMonth,
database_path: &str,
) -> String {
let summaries = database::get_month_power_summaries(&start, &stop, &database_path);
let mut response_model = MonthsResponse {
month_logs: Vec::new(),
};
for summary in summaries {
response_model.month_logs.push(MonthsResponseItem {
year: summary.year,
month: summary.month,
total_power_use: summary.total_power_consumption_day
+ summary.total_power_consumption_night,
total_power_return: summary.total_power_return_day + summary.total_power_return_night,
total_gas_use: summary.gas_consumption_in_cubic_meters,
});
}
match serde_json::to_string(&response_model) {
Ok(json) => json,
Err(error) => {
log::error!(
"Failed to format JSON data for date range {} to {} with error {}",
start,
stop,
error
);
return String::from("[]");
}
}
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct MonthsResponse {
month_logs: Vec<MonthsResponseItem>,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct MonthsResponseItem {
year: i32,
month: u8,
total_power_use: f64,
total_power_return: f64,
total_gas_use: f64,
}