Sammelstand der offenen Arbeit im Dashboard. Einstellungen: eine Eingabemaske fuer die Energiepreise unter restricted/settings.php, dazu costs.php als Modell und ajax/settings.php als Endpunkt. Die Preise stehen jetzt in gridCosts, gasCosts und fuelCosts statt fest im Quelltext von getStats.php; die Jahresstatistik rechnet ueber LEAD() den jeweils gueltigen Zeitraum aus und schlaegt den anteiligen Grundpreis auf. Eingegeben wird in gewohnten Einheiten - l/100 km, kWh/100 km, Euro je Liter -, umgerechnet wird beim Speichern. solarLog_costs.sql beschreibt die Umstellung der Tabellen. Skoda: eigene Seite mit Live-Werten ueber MQTT und Historie ueber ajax/skoda.php, Kommandos ueber ajax/skodaCmd.php. Karte: Leaflet mit einem eigenen Kachel-Zwischenspeicher (ajax/tile.php), dessen Ablage unter tiles/ nicht ins Repository gehoert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
212 lines
9.9 KiB
PHP
212 lines
9.9 KiB
PHP
<?php
|
|
|
|
require_once("../helper.php");
|
|
require_once("../restricted/costs.php");
|
|
if(!isset($_GET["year"])){
|
|
$_GET["year"] = date("Y");
|
|
}else{
|
|
$_GET["year"] = intval($_GET["year"]);
|
|
}
|
|
if($_GET["year"] < 2018 || $_GET["year"] > date("Y")){
|
|
exit;
|
|
}
|
|
if(isset($_GET["type"])){
|
|
if(strtolower($_GET["type"]) =="lastyear"){
|
|
$_GET["year"] = date("Y")-1;
|
|
}elseif(strtolower($_GET["type"]) =="prelastyear"){
|
|
$_GET["year"] = date("Y")-2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Die Jahresstatistik als eine Abfrage - jetzt auf stats_daily.
|
|
*
|
|
* Gerechnet wird nicht mehr ueber die Rohdaten aus EnergyFlow, sondern ueber
|
|
* die Tageswerte. Zwei Gruende:
|
|
*
|
|
* 1. Die Rohdaten werden nach zwoelf Monaten ausgeduennt bzw. ins
|
|
* Stundenarchiv verschoben (solarlog-rollup.sh). Eine Kachel, die auf
|
|
* EnergyFlow rechnet, zeigt fuer alte Jahre danach zu wenig an.
|
|
* 2. Ein Jahr sind 365 Zeilen statt rund 105.000.
|
|
*
|
|
* Die Tarife bleiben unveraendert: je Zeile ein Stichtag, das Ende holt
|
|
* LEAD() beim naechsten. Weil ein Stichtag ein DATUM ist, kann ein Tag nie
|
|
* zwei Tarife haben - die Verdichtung auf Tage verliert hier also nichts.
|
|
*
|
|
* Was in stats_daily vorberechnet sein MUSS, weil es eine Bedingung je
|
|
* Messwert auswertet und sich aus Tagessummen nicht rekonstruieren laesst:
|
|
*
|
|
* eigenverbrauch_kwh pv - Einspeisung - Batterieladung
|
|
* - Heizstab + Batterieentladung. Dazu addiert
|
|
* wird pv_fehlbetrag_kwh: bei Ausfall einzelner
|
|
* Wechselrichter fehlt deren Beitrag in pv, alle
|
|
* Senken sind dagegen gemessen - die Differenz
|
|
* faellt sonst zu klein oder negativ aus.
|
|
* heizstab_solar_kwh Heizstab, anteilig nach autonomy
|
|
* auto_*_solar_kwh Wallbox, anteilig nach autonomy - dem vom
|
|
* Geraet in feiner Aufloesung gerechneten
|
|
* Nicht-Netz-Anteil der Zufuhr
|
|
*
|
|
* Einheiten sind in stats_daily bereits bereinigt: PL*_EV und PL*_EVog
|
|
* liefern Kilowatt, alle uebrigen Leistungsspalten Watt. In der alten
|
|
* Abfrage stand das als /12 gegen /12000 nebeneinander.
|
|
*
|
|
* Die Solaranteile teilen jetzt durch NULLIF(...): ohne Ladung im Jahr kam
|
|
* vorher 0/0 heraus.
|
|
*
|
|
* "Ersparnis Batterie" rechnet den Nutzen netto: was die entladene Energie
|
|
* aus dem Netz gekostet haette, abzueglich der Verguetung, die fuer den zum
|
|
* Laden verwendeten Strom entgangen ist. Brutto waere die Kachel zu
|
|
* freundlich - der Speicher ist nicht umsonst zu fuellen.
|
|
*
|
|
* Achtung beim Lesen: "Verbrauchsersparnis" ist der GESAMTE Eigenverbrauch
|
|
* zum Strompreis. "Ersparnis Batterie" und "Ersparnis Solarladung" sind
|
|
* Teilmengen davon, nach Herkunft bzw. Verwendung herausgegriffen. Sie
|
|
* duerfen nicht addiert werden.
|
|
*
|
|
* Der Grundpreis fehlt weiterhin bewusst - er haengt an den Tagen des
|
|
* Jahres, nicht an einem Messwert, und kommt aus grundpreisImJahr() dazu.
|
|
*/
|
|
function statistikAbfrage($jahr)
|
|
{
|
|
return "WITH
|
|
preis AS (SELECT active_date, cost, gain,
|
|
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM gridCosts),
|
|
gas AS (SELECT active_date, cost, kwhPerLitre,
|
|
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM gasCosts),
|
|
sprit AS (SELECT active_date, cost, lPer100km, kwhPer100km,
|
|
LEAD(active_date) OVER (ORDER BY active_date) AS folgt FROM fuelCosts)
|
|
SELECT
|
|
SUM(d.netz_bezug_kwh) AS 'Stromverbrauch',
|
|
SUM(d.netz_bezug_kwh * preis.cost) AS 'Stromkosten',
|
|
SUM((d.eigenverbrauch_kwh + IFNULL(d.pv_fehlbetrag_kwh,0)) * preis.cost) AS 'Verbrauchsersparnis',
|
|
SUM(d.netz_einsp_kwh * preis.gain) AS 'Einspeisevergütung',
|
|
SUM(d.heizstab_solar_kwh * gas.cost / NULLIF(gas.kwhPerLitre,0)) AS 'Ersparnis Heizung',
|
|
SUM(d.batt_entladen_kwh * preis.cost - d.batt_laden_kwh * preis.gain) AS 'Ersparnis Batterie',
|
|
SUM(d.autarkie_avg * d.stunden_erfasst) / NULLIF(SUM(d.stunden_erfasst),0) AS 'Ø Autarkie',
|
|
SUM(d.auto_eg_kwh) AS 'Autoladung ges. EG',
|
|
SUM(d.auto_eg_solar_kwh) / NULLIF(SUM(d.auto_eg_kwh),0) * 100 AS 'Autoladung Solar EG',
|
|
SUM(d.auto_eg_solar_kwh * preis.cost) AS 'Ersparnis Solarladung EG',
|
|
SUM(d.auto_eg_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS 'Benzin gespart EG',
|
|
SUM(d.auto_eg_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0) * sprit.cost) AS 'Ersparnis Benzin EG',
|
|
SUM(d.auto_og_kwh) AS 'Autoladung ges. OG',
|
|
SUM(d.auto_og_solar_kwh) / NULLIF(SUM(d.auto_og_kwh),0) * 100 AS 'Autoladung Solar OG',
|
|
SUM(d.auto_og_solar_kwh * preis.cost) AS 'Ersparnis Solarladung OG',
|
|
SUM(d.auto_og_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS 'Benzin gespart OG',
|
|
SUM(d.auto_og_kwh * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0) * sprit.cost) AS 'Ersparnis Benzin OG'
|
|
FROM stats_daily d
|
|
JOIN preis ON d.tag >= preis.active_date AND (preis.folgt IS NULL OR d.tag < preis.folgt)
|
|
LEFT JOIN gas ON d.tag >= gas.active_date AND (gas.folgt IS NULL OR d.tag < gas.folgt)
|
|
LEFT JOIN sprit ON d.tag >= sprit.active_date AND (sprit.folgt IS NULL OR d.tag < sprit.folgt)
|
|
WHERE YEAR(d.tag) = " . intval($jahr) . ";";
|
|
}
|
|
|
|
$Query = statistikAbfrage($_GET["year"]);
|
|
$PrevQuery = statistikAbfrage($_GET["year"] - 1);
|
|
|
|
// Reihenfolge wie in der Abfrage, mit "Verg.+Einsp. Strom" an Stelle 4.
|
|
$units = Array("kWh","€","€","€","€","€","€","%",
|
|
"kWh","%","€","L","€",
|
|
"kWh","%","€","L","€");
|
|
// Nur bei Verbrauch und Kosten ist weniger besser.
|
|
$LessIsBetter = array_merge([true, true], array_fill(0, count($units) - 2, false));
|
|
|
|
function array_insert($array,$values,$offset) {
|
|
return array_slice($array, 0, $offset, true) + $values + array_slice($array, $offset, NULL, true);
|
|
}
|
|
|
|
|
|
if (checkLogin()) {
|
|
|
|
$html = "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
|
|
$mysql = solarDb();
|
|
$Res = mysqli_query($mysql,$Query);
|
|
$ResPrev = mysqli_query($mysql,$PrevQuery);
|
|
if(!$Res || !$ResPrev){
|
|
echo "Error:<br>".mysqli_error($mysql)."<br />";
|
|
}
|
|
if ($Res->num_rows > 0) {
|
|
$i = 0;
|
|
$row = $Res->fetch_assoc();
|
|
$rowPrev = $ResPrev->fetch_assoc();
|
|
// Der Grundpreis faellt unabhaengig vom Verbrauch an und laesst sich
|
|
// deshalb nicht aus den Messwerten summieren.
|
|
$row["Stromkosten"] += grundpreisImJahr($_GET["year"]);
|
|
$rowPrev["Stromkosten"] += grundpreisImJahr($_GET["year"] - 1);
|
|
//$row["Vergütung+Einsparung Strom"] = $row["Einspeisevergütung"]+$row["Verbrauchsersparnis"];
|
|
$row = array_insert($row,["Verg.+Einsp. Strom" => $row["Einspeisevergütung"]+$row["Verbrauchsersparnis"]],4);
|
|
$rowPrev = array_insert($rowPrev,["Verg.+Einsp. Strom" => $rowPrev["Einspeisevergütung"]+$rowPrev["Verbrauchsersparnis"]],4);
|
|
$i = 0;
|
|
foreach ($row as $name => $value) {
|
|
if(str_starts_with($name,"Autoladung ges.")){
|
|
$html .= "</div><hr class='mt-3 mb-3 border-light' />";
|
|
$html .= "<div class='row row-cols-2 row-cols-md-4 g-2 row-info'>";
|
|
}
|
|
/*$html .= "<div class='col-12 col-sm-4 col-md-2 col-xl-1'>
|
|
<div class='info-box'>
|
|
<div class='info-box-content'>
|
|
<span class='info-box-text'>".$name."</span>
|
|
<span class='info-box-number'>".
|
|
number_format(floatval($value),2,",",".")
|
|
."<small>".$units[$i++]."</small>
|
|
</span>
|
|
</div>
|
|
<!-- /.info-box-content -->
|
|
</div>
|
|
</div>";*/
|
|
$html .= "<div class='col'>
|
|
<div class='card stats border-0'>
|
|
<div class='card-body bg-dark bg-gradient rounded-top pb-1 pt-1'>
|
|
".$name."
|
|
</div>
|
|
<div class='card-footer text-center'>";
|
|
if($_GET["year"] != date("Y")){
|
|
if($LessIsBetter[$i]){
|
|
if($value == 0)
|
|
$dev=100;
|
|
else
|
|
$dev = $rowPrev[$name]/$value;
|
|
//$first = round($rowPrev[$name]*10/$rowPrev[$name]); //allow for 10% deviation for displaying no tendency
|
|
//$second = round($value); //allow for 10% deviation for displaying no tendency
|
|
$arrowGood = "down";
|
|
$arrowBad = "up";
|
|
}else{
|
|
if($rowPrev[$name] == 0)
|
|
$dev=100;
|
|
else
|
|
$dev = $value/$rowPrev[$name];
|
|
//$first = round($value); //allow for 10% deviation for displaying no tendency
|
|
//$second = round($rowPrev[$name]); //allow for 10% deviation for displaying no tendency
|
|
$arrowGood = "up";
|
|
$arrowBad = "down";
|
|
}
|
|
if($dev > 1.05){
|
|
$html .= "<span class='float-right text-success-emphasis'>
|
|
<i class='bi bi-arrow-".$arrowGood."' style='font-size: 0.9em;'></i> ";
|
|
}elseif($dev < 0.95){
|
|
$html .= "<span class='float-right text-danger-emphasis'>
|
|
<i class='bi bi-arrow-".$arrowBad."' style='font-size: 0.9em;'></i> ";
|
|
}else{
|
|
$html .= "<span class='float-right'>
|
|
<i class='bi bi-arrow-right' style='font-size: 0.9em;'></i> ";
|
|
}
|
|
}
|
|
else{
|
|
$html .= "<span class='float-right'>";
|
|
}
|
|
$html .= number_format(floatval($value),2,",",".")
|
|
."<small> ".$units[$i]."</small></span></div>
|
|
<!-- /.info-box-content -->
|
|
</div>
|
|
</div>";
|
|
$i++;
|
|
}
|
|
}
|
|
$html .= "</div>";
|
|
}
|
|
|
|
|
|
//header('Content-Type: application/json');
|
|
echo $html;
|
|
//echo '{"labels":[1761322682000,1761322782000,1761322882000,1761322982000,1761323082000,1761323182000,1761323282000],"datasets":[{"stack": "Stack 0","cubicInterpolationMode":"monotone","fill":"origin","label":"Acquisitions by year","data":[10,20,50,20,10,5,70]},{"fill": "false","stack": "Stack 1","cubicInterpolationMode": "monotone","label": "Acquisitions by year","data": [10,20,50,20,10,5,70]}]}';
|