Switch to 15 mins intervals

This commit is contained in:
Adam Štrauch 2025-10-02 17:48:31 +02:00
parent 96e4d2f1df
commit 6071a1ccc2
Signed by: cx
GPG key ID: 7262DAFE292BCE20
5 changed files with 265 additions and 131 deletions

View file

@ -8,7 +8,7 @@ from calculator.miner import PriceNotFound, get_energy_prices, get_eur_czk_ratio
from calculator.schema import BatteryChargingInfo, Price, SpotPrices from calculator.schema import BatteryChargingInfo, Price, SpotPrices
def get_spot_prices(date: datetime.date, hour:int, kwh_fees_low:float, kwh_fees_high:float, sell_fees:float, VAT:float, low_tariff_hours:List[int], no_cache: bool = False) -> SpotPrices: def get_spot_prices(date: datetime.date, hour:str, kwh_fees_low:float, kwh_fees_high:float, sell_fees:float, VAT:float, low_tariff_hours:List[int], no_cache: bool = False) -> SpotPrices:
is_today = datetime.date.today() == date is_today = datetime.date.today() == date
spot_hours = {} spot_hours = {}
@ -19,21 +19,21 @@ def get_spot_prices(date: datetime.date, hour:int, kwh_fees_low:float, kwh_fees_
currency_ratio = get_eur_czk_ratio(date, no_cache=no_cache) currency_ratio = get_eur_czk_ratio(date, no_cache=no_cache)
for key, value in spot_data.items(): for key, value in spot_data.items():
kwh_fees = kwh_fees_low if int(key) in low_tariff_hours else kwh_fees_high kwh_fees = kwh_fees_low if key in low_tariff_hours else kwh_fees_high
spot_hours[key] = value * currency_ratio / 1000 spot_hours[key] = value * currency_ratio / 1000
spot_hours_total[key] = (value * currency_ratio / 1000 + kwh_fees) * VAT spot_hours_total[key] = (value * currency_ratio / 1000 + kwh_fees) * VAT
spot_hours_for_sell[key] = value * currency_ratio / 1000 - sell_fees spot_hours_for_sell[key] = value * currency_ratio / 1000 - sell_fees
spot = Price(hours=spot_hours, now=spot_hours[str(hour)] if is_today else None) spot = Price(hours=spot_hours, now=spot_hours[hour] if is_today else None)
spot_hours_total_sorted = {k: v for k, v in sorted(spot_hours_total.items(), key=lambda item: item[1])} spot_hours_total_sorted = {k: v for k, v in sorted(spot_hours_total.items(), key=lambda item: item[1])}
spot_total = Price(hours=spot_hours_total_sorted, now=spot_hours_total[str(hour)] if is_today else None) spot_total = Price(hours=spot_hours_total_sorted, now=spot_hours_total[hour] if is_today else None)
spot_for_sell = Price(hours=spot_hours_for_sell, now=spot_hours_for_sell[str(hour)] if is_today else None) spot_for_sell = Price(hours=spot_hours_for_sell, now=spot_hours_for_sell[hour] if is_today else None)
return SpotPrices( return SpotPrices(
spot=spot, spot=spot,
spot_hours_total_sorted=Price(hours=spot_hours_total_sorted, now=spot_hours_total[str(hour)] if is_today else None), spot_hours_total_sorted=Price(hours=spot_hours_total_sorted, now=spot_hours_total[hour] if is_today else None),
spot_total=spot_total, spot_total=spot_total,
spot_for_sell=spot_for_sell, spot_for_sell=spot_for_sell,
) )
@ -48,14 +48,13 @@ def battery_charging_info(kwh_fees_low:float, kwh_fees_high:float, sell_fees:flo
# average4hours = sum(list(spot_prices_today.spot_hours_total_sorted.hours.values())[0:4]) / 4 # average4hours = sum(list(spot_prices_today.spot_hours_total_sorted.hours.values())[0:4]) / 4
max_cheapest_hour = max(list(spot_prices_today.spot_hours_total_sorted.hours.values())[0:4]) max_cheapest_hour = max(list(spot_prices_today.spot_hours_total_sorted.hours.values())[0:4])
average4expensive_hours = sum(list(spot_prices_today.spot_hours_total_sorted.hours.values())[20:24]) / 4
max_most_expensive_hour = max( max_most_expensive_hour = max(
[x[1] for x in list(spot_prices_today.spot_hours_total_sorted.hours.items())[0:20]] [x[1] for x in list(spot_prices_today.spot_hours_total_sorted.hours.items())[0:20]]
) if spot_prices_today.spot_hours_total_sorted.hours else 0 ) if spot_prices_today.spot_hours_total_sorted.hours else 0
diff = max_most_expensive_hour - max_cheapest_hour diff = max_most_expensive_hour - max_cheapest_hour
charging_hours = [int(k) for k, v in spot_prices_today.spot_hours_total_sorted.hours.items()][0:4] charging_hours = [k for k, v in spot_prices_today.spot_hours_total_sorted.hours.items()][0:4]
discharging_hours = [int(k) for k, v in spot_prices_today.spot_hours_total_sorted.hours.items() if v > (max_cheapest_hour + battery_kwh_price)] discharging_hours = [k for k, v in spot_prices_today.spot_hours_total_sorted.hours.items() if v > (max_cheapest_hour + battery_kwh_price)]
# Add charging hours if the price is just 10% above the most expensive charging hour # Add charging hours if the price is just 10% above the most expensive charging hour
if charging_hours: if charging_hours:
@ -87,3 +86,16 @@ def battery_charging_info(kwh_fees_low:float, kwh_fees_high:float, sell_fees:flo
is_discharging_hour=hour in discharging_hours if len(discharging_hours) > 0 and is_viable else False, is_discharging_hour=hour in discharging_hours if len(discharging_hours) > 0 and is_viable else False,
total_price=spot_prices_today.spot_hours_total_sorted total_price=spot_prices_today.spot_hours_total_sorted
) )
def minutes_to_15mins(mins:int|str) -> str:
mins = int(mins)
if mins < 15:
return "00"
elif mins < 30:
return "15"
elif mins < 45:
return "30"
else:
return "45"

View file

@ -7,31 +7,31 @@
<script> <script>
const priceColorMap = { const priceColorMap = {
0: "bg-lime-200", 0: "bg-lime-100",
1: "bg-lime-300", 1: "bg-lime-200",
2: "bg-lime-400", 2: "bg-lime-300",
3: "bg-lime-500", 3: "bg-lime-400",
4: "bg-lime-600", 4: "bg-lime-500",
5: "bg-lime-700", 5: "bg-lime-600",
6: "bg-amber-200", 6: "bg-lime-700",
7: "bg-amber-300", 7: "bg-amber-100",
8: "bg-amber-400", 8: "bg-amber-200",
9: "bg-amber-500", 9: "bg-amber-300",
10: "bg-amber-600", 10: "bg-amber-400",
11: "bg-amber-700", 11: "bg-amber-500",
12: "bg-orange-300", 12: "bg-amber-600",
13: "bg-orange-400", 13: "bg-amber-700",
14: "bg-orange-500", 14: "bg-orange-200",
15: "bg-orange-600", 15: "bg-orange-300",
16: "bg-rose-400", 16: "bg-orange-400",
17: "bg-rose-500", 17: "bg-orange-500",
18: "bg-rose-600", 18: "bg-orange-600",
19: "bg-rose-700", 19: "bg-orange-700",
20: "bg-rose-800", 20: "bg-rose-300",
21: "bg-fuchsia-500", 21: "bg-rose-400",
22: "bg-fuchsia-700", 22: "bg-rose-500",
23: "bg-fuchsia-800", 23: "bg-rose-600",
24: "bg-fuchsia-950", 24: "bg-rose-700",
} }
function getBgColor(value) { function getBgColor(value) {
@ -47,67 +47,26 @@
return `${year}-${month}-${day}`; return `${year}-${month}-${day}`;
} }
function loadData(date, today) { function getHourFromTimeString(timeStr) {
let prefix = today ? 'today' : 'tomorrow'; return parseInt(timeStr.split(':')[0]);
if(prefix == 'today') {
document.getElementById(prefix).innerText = "Dnes ("+date+")";
} else {
document.getElementById(prefix).innerText = "Zítra ("+date+")";
} }
fetch('/price/day/'+date+'?num_cheapest_hours=8') function getMinuteFromTimeString(timeStr) {
.then(response => response.json()) return parseInt(timeStr.split(':')[1]);
.then(data => {
if(data.detail == "prices not found") {
for (let i = 0; i < 24; i++) {
document.getElementById(prefix + i).innerText = "-";
}
return;
} }
for (let i = 0; i < 24; i++) { function isCurrentQuarter(hour, minute, isToday) {
let extra_class = ""; if (!isToday) return false;
let border = "border-dotted" const now = new Date();
if(today && i == new Date().getHours()) { const currentHour = now.getHours();
extra_class = " font-bold text-xl"; const currentMinute = now.getMinutes();
border = "border-solid"
if (hour !== currentHour) return false;
const currentQuarter = Math.floor(currentMinute / 15) * 15;
return minute === currentQuarter;
} }
let value = Math.round(data.total.hours[i]*100)/100;
if(data.cheapest_hours_by_average.hours.includes(i)) {
document.getElementById(prefix + i).className = "px-1 "+border+" border-4 border-lime-400 "+ getBgColor(value) + extra_class;
} else if (data.most_expensive_hours_by_average.hours.includes(i)) {
document.getElementById(prefix + i).className = "px-1 "+border+" border-4 border-rose-400 "+ getBgColor(value) + extra_class;
} else {
if(extra_class != "") {
extra_class = " border-4 border-solid border-gray-500";
}
document.getElementById(prefix + i).className = "px-1 "+ getBgColor(value) + extra_class;
}
document.getElementById(prefix + i).innerText = value;
}
})
}
function loadAllData() {
const currentDate = new Date();
const tomorrowDate = new Date(currentDate);
tomorrowDate.setDate(currentDate.getDate() + 1);
const currentDateString = formatDate(currentDate);
const tomorrowDateString = formatDate(tomorrowDate);
loadData(currentDateString, true);
loadData(tomorrowDateString, false);
}
window.onload = function() {
loadAllData();
// Reload data every hour (3600000 milliseconds)
setInterval(loadAllData, 3600000);
};
async function createTableRows() { async function createTableRows() {
const tbody = document.getElementById('prices-table-body'); const tbody = document.getElementById('prices-table-body');
tbody.innerHTML = ''; tbody.innerHTML = '';
@ -115,6 +74,7 @@
const tomorrow = new Date(today); const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1); tomorrow.setDate(today.getDate() + 1);
const tomorrowString = formatDate(tomorrow); const tomorrowString = formatDate(tomorrow);
// Check if tomorrow's data is available // Check if tomorrow's data is available
let tomorrowAvailable = false; let tomorrowAvailable = false;
try { try {
@ -138,10 +98,12 @@
const dateString = formatDate(date); const dateString = formatDate(date);
label = offset === (tomorrowAvailable ? 1 : 0) ? `Dnes (${dateString})` : dateString; label = offset === (tomorrowAvailable ? 1 : 0) ? `Dnes (${dateString})` : dateString;
} }
const dateString = formatDate(date); const dateString = formatDate(date);
const rowId = `row-${dateString}`; const rowId = `row-${dateString}`;
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.id = rowId; tr.id = rowId;
// Date cell // Date cell
const dateCell = document.createElement('td'); const dateCell = document.createElement('td');
let dateCellClass = 'px-1 w-16 '; let dateCellClass = 'px-1 w-16 ';
@ -155,14 +117,16 @@
dateCell.id = `date-${dateString}`; dateCell.id = `date-${dateString}`;
dateCell.innerText = label; dateCell.innerText = label;
tr.appendChild(dateCell); tr.appendChild(dateCell);
// Hour cells
// Hour cells (24 hours)
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
const td = document.createElement('td'); const td = document.createElement('td');
td.className = 'px-1'; td.className = 'px-1 text-xs leading-tight';
td.id = `${dateString}-${i}`; td.id = `${dateString}-${i}`;
td.innerText = '-'; td.innerHTML = '<div>-</div><div>-</div><div>-</div><div>-</div>';
tr.appendChild(td); tr.appendChild(td);
} }
tbody.appendChild(tr); tbody.appendChild(tr);
} }
} }
@ -173,30 +137,135 @@
.then(data => { .then(data => {
if (data.detail == "prices not found") { if (data.detail == "prices not found") {
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
document.getElementById(`${date}-${i}`).innerText = "-"; document.getElementById(`${date}-${i}`).innerHTML = '<div>-</div><div>-</div><div>-</div><div>-</div>';
} }
return; return;
} }
// Group prices by hour
const hourlyData = {};
for (let i = 0; i < 24; i++) { for (let i = 0; i < 24; i++) {
let extra_class = ""; hourlyData[i] = [];
let border = "border-dotted";
if (isToday && i == new Date().getHours()) {
extra_class = " font-bold text-xl";
border = "border-solid";
} }
let value = Math.round(data.total.hours[i] * 100) / 100;
let td = document.getElementById(`${date}-${i}`); // Process all time entries and group by hour
if (data.cheapest_hours_by_average.hours.includes(i)) { Object.keys(data.total.hours).forEach(timeStr => {
td.className = `px-1 ${border} border-4 border-lime-400 ${getBgColor(value)}${extra_class}`; const hour = getHourFromTimeString(timeStr);
} else if (data.most_expensive_hours_by_average.hours.includes(i)) { const minute = getMinuteFromTimeString(timeStr);
td.className = `px-1 ${border} border-4 border-rose-400 ${getBgColor(value)}${extra_class}`; const value = data.total.hours[timeStr];
hourlyData[hour].push({
minute: minute,
value: value,
timeStr: timeStr
});
});
// Sort each hour's data by minute
for (let hour = 0; hour < 24; hour++) {
hourlyData[hour].sort((a, b) => a.minute - b.minute);
}
// Get cheapest and most expensive hours for 15-min intervals
const cheapestTimes = new Set();
const expensiveTimes = new Set();
if (data.cheapest_hours_by_average && data.cheapest_hours_by_average.hours) {
data.cheapest_hours_by_average.hours.forEach(timeStr => {
if (typeof timeStr === 'string' && timeStr.includes(':')) {
cheapestTimes.add(timeStr);
} else { } else {
if (extra_class != "") { // Legacy hourly format - add all quarters for this hour
extra_class = " border-4 border-solid border-gray-500"; for (let min = 0; min < 60; min += 15) {
cheapestTimes.add(`${timeStr}:${min.toString().padStart(2, '0')}`);
} }
td.className = `px-1 ${getBgColor(value)}${extra_class}`;
} }
td.innerText = value; });
}
if (data.most_expensive_hours_by_average && data.most_expensive_hours_by_average.hours) {
data.most_expensive_hours_by_average.hours.forEach(timeStr => {
if (typeof timeStr === 'string' && timeStr.includes(':')) {
expensiveTimes.add(timeStr);
} else {
// Legacy hourly format - add all quarters for this hour
for (let min = 0; min < 60; min += 15) {
expensiveTimes.add(`${timeStr}:${min.toString().padStart(2, '0')}`);
}
}
});
}
// Update each hour cell
for (let hour = 0; hour < 24; hour++) {
const td = document.getElementById(`${date}-${hour}`);
const quarters = hourlyData[hour];
let cellClasses = 'px-1 text-xs leading-tight';
// Check if we have only one value for this hour
if (quarters.length === 1) {
// Display single value across entire cell
const quarter = quarters[0];
const value = Math.round(quarter.value * 100) / 100;
const bgColor = getBgColor(value);
let extraClass = '';
let borderClass = '';
// Check if this is the current time quarter
if (isCurrentQuarter(hour, quarter.minute, isToday)) {
extraClass = 'font-bold';
borderClass = 'border-2 border-solid border-gray-800';
}
// Check if this quarter is in cheapest or most expensive
if (cheapestTimes.has(quarter.timeStr)) {
borderClass = 'border-2 border-dotted border-lime-400';
} else if (expensiveTimes.has(quarter.timeStr)) {
borderClass = 'border-2 border-dotted border-rose-400';
}
td.innerHTML = `<div class="${bgColor} ${extraClass} ${borderClass} h-full flex items-center justify-center">${value}</div>`;
} else {
// Fill missing quarters with empty divs for 4-quarter display
while (quarters.length < 4) {
quarters.push({ minute: quarters.length * 15, value: null, timeStr: null });
}
let cellHtml = '';
quarters.forEach((quarter, index) => {
if (quarter.value !== null) {
const value = Math.round(quarter.value * 100) / 100;
const bgColor = getBgColor(value);
let extraClass = '';
let borderClass = '';
// Check if this is the current time quarter
if (isCurrentQuarter(hour, quarter.minute, isToday)) {
extraClass = 'font-bold';
borderClass = 'border-2 border-solid border-gray-800';
}
// Check if this quarter is in cheapest or most expensive
if (cheapestTimes.has(quarter.timeStr)) {
borderClass = 'border-2 border-dotted border-lime-400';
} else if (expensiveTimes.has(quarter.timeStr)) {
borderClass = 'border-2 border-dotted border-rose-400';
}
cellHtml += `<div class="${bgColor} ${extraClass} ${borderClass}">${value}</div>`;
} else {
cellHtml += '<div>-</div>';
}
});
td.innerHTML = cellHtml;
}
td.className = cellClasses;
} }
}); });
} }
@ -207,6 +276,7 @@
const tomorrow = new Date(today); const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1); tomorrow.setDate(today.getDate() + 1);
const tomorrowString = formatDate(tomorrow); const tomorrowString = formatDate(tomorrow);
// Check if tomorrow's data is available // Check if tomorrow's data is available
let tomorrowAvailable = false; let tomorrowAvailable = false;
try { try {
@ -216,6 +286,7 @@
tomorrowAvailable = true; tomorrowAvailable = true;
} }
} catch (e) {} } catch (e) {}
let rowCount = tomorrowAvailable ? 15 : 14; let rowCount = tomorrowAvailable ? 15 : 14;
for (let offset = 0; offset < rowCount; offset++) { for (let offset = 0; offset < rowCount; offset++) {
let date; let date;

View file

@ -1,12 +1,13 @@
import datetime import datetime
import calendar import calendar
import re
from typing import List, Optional from typing import List, Optional
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse, HTMLResponse from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from calculator.calc import battery_charging_info, get_spot_prices from calculator.calc import battery_charging_info, get_spot_prices, minutes_to_15mins
from calculator.miner import PriceNotFound, get_energy_prices, get_eur_czk_ratio from calculator.miner import PriceNotFound, get_energy_prices, get_eur_czk_ratio
from calculator.schema import BatteryChargingInfo, CheapestHours, DayPrice, MostExpensiveHours, Price from calculator.schema import BatteryChargingInfo, CheapestHours, DayPrice, MostExpensiveHours, Price
@ -33,7 +34,7 @@ Return spot prices for the whole day with all fees included.<br>
<br> <br>
**Options:**<br> **Options:**<br>
**date** - date in format YYYY-MM-DD, default is today<br> **date** - date in format YYYY-MM-DD, default is today<br>
**hour** - hour of the day, default is current hour, works only when date is today<br> **hour** - hour of the day, default is current hour, works only when date is today<br>, in format HH:MM where MM can be 00, 15, 30 or 45 if the data is in 15-min intervals, or 00 if the data is in hourly intervals
**monthly_fees** - monthly fees, default is 509.24 (D57d, BezDodavatele)<br> **monthly_fees** - monthly fees, default is 509.24 (D57d, BezDodavatele)<br>
**daily_fees** - daily fees, default is 4.18 (BezDodavatele)<br> **daily_fees** - daily fees, default is 4.18 (BezDodavatele)<br>
**kwh_fees_low** - additional fees per kWh in low tariff, usually distribution + other fees, default is 1.62421 (D57d, BezDodavatele)<br> **kwh_fees_low** - additional fees per kWh in low tariff, usually distribution + other fees, default is 1.62421 (D57d, BezDodavatele)<br>
@ -137,7 +138,7 @@ rest:
@app.get("/price/day/{date}", description=docs) @app.get("/price/day/{date}", description=docs)
def read_item( def read_item(
date: Optional[datetime.date]=None, date: Optional[datetime.date]=None,
hour: Optional[int]=None, hour: Optional[str]=None,
monthly_fees: float=610.84, monthly_fees: float=610.84,
daily_fees: float=4.18, daily_fees: float=4.18,
kwh_fees_low: float=1.35022, kwh_fees_low: float=1.35022,
@ -154,11 +155,24 @@ def read_item(
if not date: if not date:
date = datetime.date.today() date = datetime.date.today()
if not hour: if not hour:
hour = datetime.datetime.now().hour now = datetime.datetime.now()
hour = f"{now.hour}:{minutes_to_15mins(now.minute)}"
if re.match(r"^\d{1,2}$", hour):
hour = f"{hour}:00"
hour_parts = hour.split(":")
hour = f"{hour_parts[0]}:{minutes_to_15mins(hour_parts[1])}"
is_today = datetime.date.today() == date is_today = datetime.date.today() == date
low_tariff_hours_parsed = [int(x.strip()) for x in low_tariff_hours.split(",")] low_tariff_hours_parsed = []
for low_hour in [x.strip() for x in low_tariff_hours.split(",")]:
low_tariff_hours_parsed.append(f"{low_hour}:00")
low_tariff_hours_parsed.append(f"{low_hour}:15")
low_tariff_hours_parsed.append(f"{low_hour}:30")
low_tariff_hours_parsed.append(f"{low_hour}:45")
monthly_fees = (monthly_fees + daily_fees * days_in_month(date.year, date.month)) monthly_fees = (monthly_fees + daily_fees * days_in_month(date.year, date.month))
monthly_fees_hour = monthly_fees / days_in_month(date.year, date.month) / 24 monthly_fees_hour = monthly_fees / days_in_month(date.year, date.month) / 24
@ -167,14 +181,14 @@ def read_item(
except PriceNotFound: except PriceNotFound:
raise HTTPException(status_code=404, detail="prices not found") raise HTTPException(status_code=404, detail="prices not found")
cheapest_hours = [int(k) for k, v in list(spot_prices.spot_hours_total_sorted.hours.items())[0:num_cheapest_hours]] cheapest_hours = [k for k, v in list(spot_prices.spot_hours_total_sorted.hours.items())[0:num_cheapest_hours]]
most_expensive_hours = [int(k) for k, v in list(reversed(spot_prices.spot_hours_total_sorted.hours.items()))[0:num_most_expensive_hours]] most_expensive_hours = [k for k, v in list(reversed(spot_prices.spot_hours_total_sorted.hours.items()))[0:num_most_expensive_hours]]
# Average over four cheapest hours and calculation of all hours that are in this average +20 % # Average over four cheapest hours and calculation of all hours that are in this average +20 %
four_cheapest_hours = [v for k, v in list(spot_prices.spot_hours_total_sorted.hours.items())[0:average_hours]] four_cheapest_hours = [v for k, v in list(spot_prices.spot_hours_total_sorted.hours.items())[0:average_hours]]
four_cheapest_hours_average = sum(four_cheapest_hours) / average_hours four_cheapest_hours_average = sum(four_cheapest_hours) / average_hours
cheapest_hours_by_average = [int(k) for k, v in list(spot_prices.spot_hours_total_sorted.hours.items()) if v < four_cheapest_hours_average * average_hours_threshold] cheapest_hours_by_average = [k for k, v in list(spot_prices.spot_hours_total_sorted.hours.items()) if v < four_cheapest_hours_average * average_hours_threshold]
most_expensive_hours_by_average = list(set(range(24)) - set(cheapest_hours_by_average)) most_expensive_hours_by_average = list(set([k for k,v in spot_prices.spot_hours_total_sorted.hours.items()]) - set(cheapest_hours_by_average))
data = DayPrice( data = DayPrice(
monthly_fees=monthly_fees * VAT, monthly_fees=monthly_fees * VAT,

View file

@ -2,7 +2,7 @@ from dataclasses import dataclass
import datetime import datetime
import json import json
import os import os
from typing import Dict from typing import Dict, List
import requests import requests
@ -33,19 +33,56 @@ def get_energy_prices(d: datetime.date=datetime.date.today(), no_cache:bool=Fals
if not hours or no_cache: if not hours or no_cache:
r = requests.get(url_energy.format(date_str)) r = requests.get(url_energy.format(date_str))
data = r.json()["data"]["dataLine"][1]["point"]
if len(data) == 24:
try: try:
for raw in r.json()["data"]["dataLine"][1]["point"]: for raw in data:
hours[str(int(raw["x"])-1)] = raw["y"] hour = str(int(raw["x"])-1)
hours[hour] = raw["y"]
except IndexError:
raise PriceNotFound()
else:
try:
mins_index = 0
hour = 0
for raw in data:
hour_str = f"{hour}:00"
if mins_index == 1:
hour_str = f"{hour}:15"
elif mins_index == 2:
hour_str = f"{hour}:30"
elif mins_index == 3:
hour_str = f"{hour}:45"
hours[hour_str] = raw["y"]
mins_index += 1
if mins_index >= 4:
mins_index = 0
hour += 1
except IndexError: except IndexError:
raise PriceNotFound() raise PriceNotFound()
# Only cache if all 24 hours are present # Only cache if all 24 hours are present
if len(hours) == 24: if len(hours) in (24, 96): # 96 for 15-min intervals
with open(cache_file, "w") as f: with open(cache_file, "w") as f:
f.write(json.dumps(hours)) f.write(json.dumps(hours))
# If incomplete, do not cache, but return what is available # If incomplete, do not cache, but return what is available
return hours # Ensure all hours are in the right format of HH:MM
correct_format_hours = {}
if len(hours) == 24:
for k, v in hours.items():
if ":" in k:
correct_format_hours[k] = v
else:
hour_int = int(k)
correct_format_hours[f"{hour_int}:00"] = v
else:
correct_format_hours = hours
return correct_format_hours
#def get_currency_ratio(currency): #def get_currency_ratio(currency):
# r = requests.get(url_currency) # r = requests.get(url_currency)

View file

@ -5,7 +5,7 @@ from typing import Dict, List, Optional
@dataclass @dataclass
class Price: class Price:
hours: Dict[str, float] hours: Dict[str, List[float]]
now: Optional[float] = None # Price in current hour now: Optional[float] = None # Price in current hour