Widget with today's and tomorrow's prices

This commit is contained in:
Adam Štrauch 2024-09-25 23:43:28 +02:00
parent 9dc3378337
commit aeb17d6969
Signed by: cx
GPG Key ID: 7262DAFE292BCE20
3 changed files with 181 additions and 3 deletions

153
calculator/index.html Normal file
View File

@ -0,0 +1,153 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<script>
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
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++) {
if(data.cheapest_hours.hours.includes(i)) {
document.getElementById(prefix + i).className = "px-1 bg-green-500";
} else if (data.most_expensive_hours.hours.includes(i)) {
document.getElementById(prefix + i).className = "px-1 bg-rose-400";
} else {
document.getElementById(prefix + i).className = "px-1 bg-amber-100";
}
document.getElementById(prefix + i).innerText = Math.round(data.total.hours[i]*100)/100;
}
})
}
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);
};
</script>
</head>
<body class="bg-neutral-800">
<table class="table border-collapse border border-slate-500 text-center w-full">
<tr class="bg-neutral-400">
<th class="px-1"></th>
<th class="px-1">00</th>
<th class="px-1">01</th>
<th class="px-1">02</th>
<th class="px-1">03</th>
<th class="px-1">04</th>
<th class="px-1">05</th>
<th class="px-1">06</th>
<th class="px-1">07</th>
<th class="px-1">08</th>
<th class="px-1">09</th>
<th class="px-1">10</th>
<th class="px-1">11</th>
<th class="px-1">12</th>
<th class="px-1">13</th>
<th class="px-1">14</th>
<th class="px-1">15</th>
<th class="px-1">16</th>
<th class="px-1">17</th>
<th class="px-1">18</th>
<th class="px-1">19</th>
<th class="px-1">20</th>
<th class="px-1">21</th>
<th class="px-1">22</th>
<th class="px-1">23</th>
</tr>
<tr>
<td id="today" class="px-1 bg-neutral-200 w-16">Dnes</td>
<td id="today0" class="px-1">-</td>
<td id="today1" class="px-1">-</td>
<td id="today2" class="px-1">-</td>
<td id="today3" class="px-1">-</td>
<td id="today4" class="px-1">-</td>
<td id="today5" class="px-1">-</td>
<td id="today6" class="px-1">-</td>
<td id="today7" class="px-1">-</td>
<td id="today8" class="px-1">-</td>
<td id="today9" class="px-1">-</td>
<td id="today10" class="px-1">-</td>
<td id="today11" class="px-1">-</td>
<td id="today12" class="px-1">-</td>
<td id="today13" class="px-1">-</td>
<td id="today14" class="px-1">-</td>
<td id="today15" class="px-1">-</td>
<td id="today16" class="px-1">-</td>
<td id="today17" class="px-1">-</td>
<td id="today18" class="px-1">-</td>
<td id="today19" class="px-1">-</td>
<td id="today20" class="px-1">-</td>
<td id="today21" class="px-1">-</td>
<td id="today22" class="px-1">-</td>
<td id="today23" class="px-1">-</td>
</tr>
<tr>
<td id="tomorrow" class="px-1 bg-neutral-200 w-16">Zítra</td>
<td id="tomorrow0" class="px-1">-</td>
<td id="tomorrow1" class="px-1">-</td>
<td id="tomorrow2" class="px-1">-</td>
<td id="tomorrow3" class="px-1">-</td>
<td id="tomorrow4" class="px-1">-</td>
<td id="tomorrow5" class="px-1">-</td>
<td id="tomorrow6" class="px-1">-</td>
<td id="tomorrow7" class="px-1">-</td>
<td id="tomorrow8" class="px-1">-</td>
<td id="tomorrow9" class="px-1">-</td>
<td id="tomorrow10" class="px-1">-</td>
<td id="tomorrow11" class="px-1">-</td>
<td id="tomorrow12" class="px-1">-</td>
<td id="tomorrow13" class="px-1">-</td>
<td id="tomorrow14" class="px-1">-</td>
<td id="tomorrow15" class="px-1">-</td>
<td id="tomorrow16" class="px-1">-</td>
<td id="tomorrow17" class="px-1">-</td>
<td id="tomorrow18" class="px-1">-</td>
<td id="tomorrow19" class="px-1">-</td>
<td id="tomorrow20" class="px-1">-</td>
<td id="tomorrow21" class="px-1">-</td>
<td id="tomorrow22" class="px-1">-</td>
<td id="tomorrow23" class="px-1">-</td>
</tr>
</table>
</body>
</html>

View File

@ -3,10 +3,11 @@ import calendar
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 from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
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 CheapestHours, DayPrice, Price from calculator.schema import CheapestHours, DayPrice, MostExpensiveHours, Price
from .consts import VAT from .consts import VAT
@ -14,6 +15,13 @@ def days_in_month(year: int, month: int) -> int:
return calendar.monthrange(year, month)[1] return calendar.monthrange(year, month)[1]
app = FastAPI(title="Spot market home calculator", version="0.1", description="Calculate your energy costs based on spot market prices") app = FastAPI(title="Spot market home calculator", version="0.1", description="Calculate your energy costs based on spot market prices")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/", description="Redirect to /docs") @app.get("/", description="Redirect to /docs")
def docs(): def docs():
@ -31,6 +39,7 @@ Return spot prices for the whole day with all fees included.<br>
**kwh_fees_high** - additional fees per kWh in high tariff, usually distribution + other fees, default is 1.83474 (D57d, BezDodavatele)<br> **kwh_fees_high** - additional fees per kWh in high tariff, usually distribution + other fees, default is 1.83474 (D57d, BezDodavatele)<br>
**sell_fees** - selling energy fees, default is 0.45 (BezDodavatele)<br> **sell_fees** - selling energy fees, default is 0.45 (BezDodavatele)<br>
**num_cheapest_hours** - number of cheapest hours to return, default is 8, use this to plan your consumption<br> **num_cheapest_hours** - number of cheapest hours to return, default is 8, use this to plan your consumption<br>
**num_most_expensive_hours** - number of the most expensive hours to return, default is 8, use this to plan your consumption<br>
**low_tariff_hours** - list of low tariff hours, default is 0,1,2,3,4,5,6,7,9,10,11,13,14,16,17,18,20,21,22,23 (D57d, ČEZ)<br> **low_tariff_hours** - list of low tariff hours, default is 0,1,2,3,4,5,6,7,9,10,11,13,14,16,17,18,20,21,22,23 (D57d, ČEZ)<br>
<br> <br>
Output:<br> Output:<br>
@ -80,6 +89,7 @@ def read_item(
low_tariff_hours:str="0,1,2,3,4,5,6,7,9,10,11,13,14,16,17,18,20,21,22,23", low_tariff_hours:str="0,1,2,3,4,5,6,7,9,10,11,13,14,16,17,18,20,21,22,23",
no_cache:bool = False, no_cache:bool = False,
num_cheapest_hours:int = 8, num_cheapest_hours:int = 8,
num_most_expensive_hours:int = 8,
) -> DayPrice: ) -> DayPrice:
if not date: if not date:
@ -115,6 +125,7 @@ def read_item(
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[str(hour)] if is_today else None)
cheapest_hours = [int(k) for k, v in list(spot_hours_total_sorted.items())[0:num_cheapest_hours]] cheapest_hours = [int(k) for k, v in list(spot_hours_total_sorted.items())[0:num_cheapest_hours]]
most_expensive_hours = [int(k) for k, v in list(reversed(spot_hours_total_sorted.items()))[0:num_most_expensive_hours]]
data = DayPrice( data = DayPrice(
monthly_fees=monthly_fees * VAT, monthly_fees=monthly_fees * VAT,
@ -128,6 +139,13 @@ def read_item(
spot=spot, spot=spot,
total=spot_total, total=spot_total,
sell=spot_for_sell, sell=spot_for_sell,
cheapest_hours=CheapestHours(hours=cheapest_hours, is_cheapest=hour in cheapest_hours if is_today else None) cheapest_hours=CheapestHours(hours=cheapest_hours, is_cheapest=hour in cheapest_hours if is_today else None),
most_expensive_hours=MostExpensiveHours(hours=most_expensive_hours, is_the_most_expensive=hour in most_expensive_hours if is_today else None),
) )
return data return data
@app.get("/widget", response_class=HTMLResponse)
def get_widget():
with open("calculator/index.html", "r") as file:
html_content = file.read()
return HTMLResponse(content=html_content)

View File

@ -14,6 +14,12 @@ class CheapestHours:
is_cheapest: Optional[bool] = None is_cheapest: Optional[bool] = None
@dataclass
class MostExpensiveHours:
hours: List[int]
is_the_most_expensive: Optional[bool] = None
@dataclass @dataclass
class DayPrice: class DayPrice:
monthly_fees: float monthly_fees: float
@ -28,6 +34,7 @@ class DayPrice:
total: Price total: Price
sell: Price sell: Price
cheapest_hours: CheapestHours cheapest_hours: CheapestHours
most_expensive_hours: MostExpensiveHours