Switch to 15 mins intervals
This commit is contained in:
parent
96e4d2f1df
commit
6071a1ccc2
5 changed files with 265 additions and 131 deletions
|
@ -8,7 +8,7 @@ from calculator.miner import PriceNotFound, get_energy_prices, get_eur_czk_ratio
|
|||
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
|
||||
|
||||
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)
|
||||
|
||||
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_total[key] = (value * currency_ratio / 1000 + kwh_fees) * VAT
|
||||
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_total = Price(hours=spot_hours_total_sorted, now=spot_hours_total[str(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_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[hour] if is_today else None)
|
||||
|
||||
return SpotPrices(
|
||||
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_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
|
||||
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(
|
||||
[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
|
||||
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]
|
||||
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)]
|
||||
charging_hours = [k for k, v in spot_prices_today.spot_hours_total_sorted.hours.items()][0:4]
|
||||
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
|
||||
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,
|
||||
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"
|
||||
|
|
|
@ -7,31 +7,31 @@
|
|||
|
||||
<script>
|
||||
const priceColorMap = {
|
||||
0: "bg-lime-200",
|
||||
1: "bg-lime-300",
|
||||
2: "bg-lime-400",
|
||||
3: "bg-lime-500",
|
||||
4: "bg-lime-600",
|
||||
5: "bg-lime-700",
|
||||
6: "bg-amber-200",
|
||||
7: "bg-amber-300",
|
||||
8: "bg-amber-400",
|
||||
9: "bg-amber-500",
|
||||
10: "bg-amber-600",
|
||||
11: "bg-amber-700",
|
||||
12: "bg-orange-300",
|
||||
13: "bg-orange-400",
|
||||
14: "bg-orange-500",
|
||||
15: "bg-orange-600",
|
||||
16: "bg-rose-400",
|
||||
17: "bg-rose-500",
|
||||
18: "bg-rose-600",
|
||||
19: "bg-rose-700",
|
||||
20: "bg-rose-800",
|
||||
21: "bg-fuchsia-500",
|
||||
22: "bg-fuchsia-700",
|
||||
23: "bg-fuchsia-800",
|
||||
24: "bg-fuchsia-950",
|
||||
0: "bg-lime-100",
|
||||
1: "bg-lime-200",
|
||||
2: "bg-lime-300",
|
||||
3: "bg-lime-400",
|
||||
4: "bg-lime-500",
|
||||
5: "bg-lime-600",
|
||||
6: "bg-lime-700",
|
||||
7: "bg-amber-100",
|
||||
8: "bg-amber-200",
|
||||
9: "bg-amber-300",
|
||||
10: "bg-amber-400",
|
||||
11: "bg-amber-500",
|
||||
12: "bg-amber-600",
|
||||
13: "bg-amber-700",
|
||||
14: "bg-orange-200",
|
||||
15: "bg-orange-300",
|
||||
16: "bg-orange-400",
|
||||
17: "bg-orange-500",
|
||||
18: "bg-orange-600",
|
||||
19: "bg-orange-700",
|
||||
20: "bg-rose-300",
|
||||
21: "bg-rose-400",
|
||||
22: "bg-rose-500",
|
||||
23: "bg-rose-600",
|
||||
24: "bg-rose-700",
|
||||
}
|
||||
|
||||
function getBgColor(value) {
|
||||
|
@ -47,67 +47,26 @@
|
|||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
function loadData(date, today) {
|
||||
let prefix = today ? 'today' : 'tomorrow';
|
||||
|
||||
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')
|
||||
.then(response => response.json())
|
||||
.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++) {
|
||||
let extra_class = "";
|
||||
let border = "border-dotted"
|
||||
if(today && i == new Date().getHours()) {
|
||||
extra_class = " font-bold text-xl";
|
||||
border = "border-solid"
|
||||
}
|
||||
|
||||
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 getHourFromTimeString(timeStr) {
|
||||
return parseInt(timeStr.split(':')[0]);
|
||||
}
|
||||
|
||||
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);
|
||||
function getMinuteFromTimeString(timeStr) {
|
||||
return parseInt(timeStr.split(':')[1]);
|
||||
}
|
||||
|
||||
function isCurrentQuarter(hour, minute, isToday) {
|
||||
if (!isToday) return false;
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
const currentMinute = now.getMinutes();
|
||||
|
||||
if (hour !== currentHour) return false;
|
||||
|
||||
const currentQuarter = Math.floor(currentMinute / 15) * 15;
|
||||
return minute === currentQuarter;
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
loadAllData();
|
||||
// Reload data every hour (3600000 milliseconds)
|
||||
setInterval(loadAllData, 3600000);
|
||||
};
|
||||
async function createTableRows() {
|
||||
const tbody = document.getElementById('prices-table-body');
|
||||
tbody.innerHTML = '';
|
||||
|
@ -115,6 +74,7 @@
|
|||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
const tomorrowString = formatDate(tomorrow);
|
||||
|
||||
// Check if tomorrow's data is available
|
||||
let tomorrowAvailable = false;
|
||||
try {
|
||||
|
@ -138,10 +98,12 @@
|
|||
const dateString = formatDate(date);
|
||||
label = offset === (tomorrowAvailable ? 1 : 0) ? `Dnes (${dateString})` : dateString;
|
||||
}
|
||||
|
||||
const dateString = formatDate(date);
|
||||
const rowId = `row-${dateString}`;
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = rowId;
|
||||
|
||||
// Date cell
|
||||
const dateCell = document.createElement('td');
|
||||
let dateCellClass = 'px-1 w-16 ';
|
||||
|
@ -155,14 +117,16 @@
|
|||
dateCell.id = `date-${dateString}`;
|
||||
dateCell.innerText = label;
|
||||
tr.appendChild(dateCell);
|
||||
// Hour cells
|
||||
|
||||
// Hour cells (24 hours)
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const td = document.createElement('td');
|
||||
td.className = 'px-1';
|
||||
td.className = 'px-1 text-xs leading-tight';
|
||||
td.id = `${dateString}-${i}`;
|
||||
td.innerText = '-';
|
||||
td.innerHTML = '<div>-</div><div>-</div><div>-</div><div>-</div>';
|
||||
tr.appendChild(td);
|
||||
}
|
||||
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
@ -173,30 +137,135 @@
|
|||
.then(data => {
|
||||
if (data.detail == "prices not found") {
|
||||
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;
|
||||
}
|
||||
|
||||
// Group prices by hour
|
||||
const hourlyData = {};
|
||||
for (let i = 0; i < 24; i++) {
|
||||
let extra_class = "";
|
||||
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}`);
|
||||
if (data.cheapest_hours_by_average.hours.includes(i)) {
|
||||
td.className = `px-1 ${border} border-4 border-lime-400 ${getBgColor(value)}${extra_class}`;
|
||||
} else if (data.most_expensive_hours_by_average.hours.includes(i)) {
|
||||
td.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";
|
||||
hourlyData[i] = [];
|
||||
}
|
||||
|
||||
// Process all time entries and group by hour
|
||||
Object.keys(data.total.hours).forEach(timeStr => {
|
||||
const hour = getHourFromTimeString(timeStr);
|
||||
const minute = getMinuteFromTimeString(timeStr);
|
||||
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 {
|
||||
// Legacy hourly format - add all quarters for this hour
|
||||
for (let min = 0; min < 60; min += 15) {
|
||||
cheapestTimes.add(`${timeStr}:${min.toString().padStart(2, '0')}`);
|
||||
}
|
||||
}
|
||||
td.className = `px-1 ${getBgColor(value)}${extra_class}`;
|
||||
});
|
||||
}
|
||||
|
||||
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.innerText = value;
|
||||
|
||||
td.className = cellClasses;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -207,6 +276,7 @@
|
|||
const tomorrow = new Date(today);
|
||||
tomorrow.setDate(today.getDate() + 1);
|
||||
const tomorrowString = formatDate(tomorrow);
|
||||
|
||||
// Check if tomorrow's data is available
|
||||
let tomorrowAvailable = false;
|
||||
try {
|
||||
|
@ -216,6 +286,7 @@
|
|||
tomorrowAvailable = true;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
let rowCount = tomorrowAvailable ? 15 : 14;
|
||||
for (let offset = 0; offset < rowCount; offset++) {
|
||||
let date;
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import datetime
|
||||
import calendar
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
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.schema import BatteryChargingInfo, CheapestHours, DayPrice, MostExpensiveHours, Price
|
||||
|
||||
|
@ -33,7 +34,7 @@ Return spot prices for the whole day with all fees included.<br>
|
|||
<br>
|
||||
**Options:**<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>
|
||||
**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>
|
||||
|
@ -137,7 +138,7 @@ rest:
|
|||
@app.get("/price/day/{date}", description=docs)
|
||||
def read_item(
|
||||
date: Optional[datetime.date]=None,
|
||||
hour: Optional[int]=None,
|
||||
hour: Optional[str]=None,
|
||||
monthly_fees: float=610.84,
|
||||
daily_fees: float=4.18,
|
||||
kwh_fees_low: float=1.35022,
|
||||
|
@ -154,11 +155,24 @@ def read_item(
|
|||
if not date:
|
||||
date = datetime.date.today()
|
||||
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
|
||||
|
||||
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_hour = monthly_fees / days_in_month(date.year, date.month) / 24
|
||||
|
||||
|
@ -167,14 +181,14 @@ def read_item(
|
|||
except PriceNotFound:
|
||||
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]]
|
||||
most_expensive_hours = [int(k) for k, v in list(reversed(spot_prices.spot_hours_total_sorted.hours.items()))[0:num_most_expensive_hours]]
|
||||
cheapest_hours = [k for k, v in list(spot_prices.spot_hours_total_sorted.hours.items())[0:num_cheapest_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 %
|
||||
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
|
||||
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]
|
||||
most_expensive_hours_by_average = list(set(range(24)) - set(cheapest_hours_by_average))
|
||||
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([k for k,v in spot_prices.spot_hours_total_sorted.hours.items()]) - set(cheapest_hours_by_average))
|
||||
|
||||
data = DayPrice(
|
||||
monthly_fees=monthly_fees * VAT,
|
||||
|
|
|
@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|||
import datetime
|
||||
import json
|
||||
import os
|
||||
from typing import Dict
|
||||
from typing import Dict, List
|
||||
|
||||
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:
|
||||
r = requests.get(url_energy.format(date_str))
|
||||
try:
|
||||
for raw in r.json()["data"]["dataLine"][1]["point"]:
|
||||
hours[str(int(raw["x"])-1)] = raw["y"]
|
||||
except IndexError:
|
||||
raise PriceNotFound()
|
||||
data = r.json()["data"]["dataLine"][1]["point"]
|
||||
|
||||
if len(data) == 24:
|
||||
try:
|
||||
for raw in data:
|
||||
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:
|
||||
raise PriceNotFound()
|
||||
|
||||
# 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:
|
||||
f.write(json.dumps(hours))
|
||||
# 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):
|
||||
# r = requests.get(url_currency)
|
||||
|
|
|
@ -5,7 +5,7 @@ from typing import Dict, List, Optional
|
|||
|
||||
@dataclass
|
||||
class Price:
|
||||
hours: Dict[str, float]
|
||||
hours: Dict[str, List[float]]
|
||||
now: Optional[float] = None # Price in current hour
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue