Drei Teile, die zusammengehoeren: die Fahrzeugkachel zeigt den Ladestand jetzt dort, wo er im Auto auch sitzt, die Jahresstatistik wird zur Tabelle, und beide holen ihr Aussehen aus einer gemeinsamen Farbwelt. Fahrzeugkachel. Die Kachel "Batterie und Ladung" ist aufgeloest; ihr Inhalt steckt in der Draufsicht. Die Akkuplatte im Fahrzeugboden fuellt sich von hinten nach vorn, traegt die Zielmarke und nennt Prozent und Reichweite in ihrer Mitte - auf dem Goldenen Schnitt der Plattenhoehe, damit sie nicht am Klimaknopf klebt. Beim Laden wandern Streifen im gefuellten Teil nach vorn, und vom Anschluss fliesst eine Linie in die Platte. Was sich nicht zeichnen laesst - Ladeleistung, Restzeit, Kilometerstand, Klima, Herkunft des Ladestroms - steht als Kennzahl daneben, dazu die gefahrenen Kilometer aus Woche, Monat und Jahr (?was=strecke, gerechnet aus dem Zaehlerstand statt aus erkannten Fahrten - faellt eine Zeile aus, fehlt die Fahrt in der Liste, im Zaehlerstand aber nicht). Am Fahrzeug selbst: kein Panoramadach, das hat unseres nicht. Reling und Spoiler neu gezeichnet, weil sie mit der Dachplatte bisher ein geschlossenes Rechteck bildeten und das wie ein Fensterrahmen aussah. Die Blende am Bug ist schmaler und heller - von oben sieht man von einer senkrechten Flaeche nur einen Streifen, und fast schwarz las sie sich wie ein Loch. Die Ladeklappe ist gebaut wie der Klimaknopf; den Ladezustand traegt jetzt die Farbe statt einer zweiten Form. Jahresvergleich. getStats.php liefert kein fertiges HTML mehr, sondern JSON, das sich selbst beschreibt: Einheit, Nachkommastellen, Richtung und Zugehoerigkeit stehen als Metadaten je Kennzahl, nicht mehr als zwei Arrays, die ueber den Spaltenindex mit der Abfrage synchron gehalten werden mussten. Zwei Abfragen holen alles - Monatswerte, daraus die ganzen Jahre und die Summe seit Aufzeichnungsbeginn, dazu der gleiche Zeitraum jedes Jahres bis zum heutigen Kalendertag. Verhaeltniszahlen sind auf jeder Ebene neu aus den Summen gerechnet; ein Jahresmittel ist nicht das Mittel der Monatsmittel. Gezeichnet wird das von einem Baustein, den sich Historien- und Solarseite teilen: dort drei Jahresspalten mit verschiebbarem Fenster bis zurueck zum ersten Jahr, hier eine Spalte ohne Werkzeuge. Zeilen, die sich aufteilen, stehen als "davon" darunter; was bereits in einer anderen Zeile steckt, als "darin" samt Namen der Zeile - der Beitrag der Batterie und die Solarladung des Autos duerfen nicht zum Ertrag addiert werden. Zusammengefasste Zeilen sind zunaechst zu, damit die Tabelle mit zehn Zeilen aufmacht statt mit siebzehn. Farbwelt. Statt Regel fuer Regel zu ueberschreiben, ist die Palette ausgetauscht: ein paar Variablen am Wurzelelement, an die Bootstrap und AdminLTE ohnehin gehen. Anthrazit als Grund, eine Spur hellere Flaeche fuer Karten, Haarlinien statt Rahmen und Schatten, Bernstein als einzige Akzentfarbe - auf dieser Seite die Farbe der eigenen Anlage. Gruen, Rot und Gelb bleiben den Zustaenden vorbehalten. Erklaerungen, die bisher nur im title standen, sind antippbar: am Finger gibt es kein Ueberfahren, und ein title erscheint dort nie. Die beiden neuen Abfragen sind gegen solarLog gelaufen, die Monatswerte decken sich mit einer unabhaengigen Zerlegung. grundpreisImJahr() ruft jetzt grundpreisImZeitraum() auf - dieselbe Rechnung, nur auch fuer einen Teil des Jahres. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
370 lines
18 KiB
PHP
370 lines
18 KiB
PHP
<?php
|
|
/*
|
|
* Jahresstatistik der Anlage - alle Jahre, alle Zeitraeume, als JSON.
|
|
*
|
|
* Frueher lieferte diese Datei fertiges HTML: je Aufruf ein Jahr, 18 Kacheln,
|
|
* Einheit und Richtung in zwei Arrays, die ueber den Spaltenindex mit der
|
|
* Abfrage synchron gehalten werden mussten. Wer eine Kennzahl einfuegte,
|
|
* musste an drei Stellen zaehlen.
|
|
*
|
|
* Jetzt kommt alles auf einmal und beschreibt sich selbst:
|
|
*
|
|
* kennzahlen Metadaten je Kennzahl - Name, Einheit, Nachkommastellen,
|
|
* Richtung, Gruppe, Einrueckung. Wer eine hinzufuegt, schreibt
|
|
* sie EINMAL in KENNZAHLEN und liefert ihren Wert in
|
|
* kennzahlenAus(); Anzeige und Reihenfolge ergeben sich daraus.
|
|
* werte drei Saetze: ganze Jahre, gleicher Zeitraum (1. Januar bis
|
|
* zum heutigen Stichtag) und die Summe ueber alle Jahre.
|
|
* monate Monatswerte je Jahr fuer die Verlaufsbalken.
|
|
*
|
|
* Warum drei Saetze und nicht einer: ein laufendes Jahr gegen ein volles zu
|
|
* stellen vergleicht Aepfel mit Birnen. Der Satz "gleich" schneidet jedes Jahr
|
|
* am selben Kalendertag ab und macht das laufende Jahr damit beurteilbar.
|
|
*
|
|
* Gerechnet wird auf stats_daily, den Tageswerten:
|
|
*
|
|
* 1. Die Rohdaten werden nach zwoelf Monaten ausgeduennt bzw. ins
|
|
* Stundenarchiv verschoben (solarlog-rollup.sh). Wer auf EnergyFlow
|
|
* rechnet, zeigt fuer alte Jahre 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
|
|
*
|
|
* Die Abfragen liefern nur SUMMEN. Jede Verhaeltniszahl - Autarkie, Quoten,
|
|
* effektiver Preis - rechnet kennzahlenAus() daraus, und zwar auf jeder Ebene
|
|
* neu. Das ist keine Formsache: Durchschnitte darf man nicht addieren, ein
|
|
* Jahresmittel ist nicht das Mittel der Monatsmittel, und die Autarkie ueber
|
|
* acht Jahre schon gar nicht die Summe von acht Prozentzahlen.
|
|
*
|
|
* Der Grundpreis faellt unabhaengig vom Verbrauch an und steckt deshalb in
|
|
* keiner Summe, sondern kommt aus grundpreisImZeitraum() dazu.
|
|
*
|
|
* Achtung beim Lesen der Ertragsgruppe: Eigenverbrauch, Einspeisung und
|
|
* Heizung TEILEN den Ertrag auf, sie ergeben ihn zusammen. Der Beitrag der
|
|
* Batterie und die Solarladung des Autos stecken dagegen IM EIGENVERBRAUCH -
|
|
* der Strom, der aus der Batterie kommt oder ins Auto geht, ist ja Strom, der
|
|
* im Haus geblieben ist. Sie sind nach Herkunft bzw. Verwendung
|
|
* herausgegriffen und duerfen nicht dazuaddiert werden. Die Heizung ist davon
|
|
* unberuehrt: heizstab_solar_kwh ist aus eigenverbrauch_kwh herausgerechnet
|
|
* und gegen den Gaspreis bewertet, nicht gegen den Strompreis.
|
|
*
|
|
* Beim Beitrag der Batterie ist "darin" der Groesse nach zu lesen: gerechnet
|
|
* ist er netto, also abzueglich der Verguetung, die fuer den zum Laden
|
|
* verwendeten Strom entgangen ist. Brutto waere die Zeile zu freundlich - der
|
|
* Speicher ist nicht umsonst zu fuellen.
|
|
*/
|
|
|
|
require_once("../helper.php");
|
|
require_once("../restricted/costs.php");
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
if (!checkLogin()) {
|
|
http_response_code(403);
|
|
echo json_encode(["error" => "Nicht angemeldet."]);
|
|
exit;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- Kennzahlen --
|
|
*
|
|
* Die Anzeige-Beschreibung. Reihenfolge ist Anzeigereihenfolge.
|
|
*
|
|
* einheit wie sie an der Zeile steht
|
|
* dez Nachkommastellen. Jahressummen brauchen keine Cent.
|
|
* invers weniger ist besser (Verbrauch, Kosten)
|
|
* punkte Prozentwert; Veraenderungen sind Prozentpunkte, nicht Prozent
|
|
* summe Zeile, unter der sich Teilbetraege aufreihen
|
|
* stufe/teil eingerueckt: "davon" teilt die Summe darueber auf
|
|
* teilmenge "darin": steckt bereits in der genannten Zeile und darf nicht
|
|
* dazuaddiert werden. Der Wert ist deren id, damit die Anzeige
|
|
* sagen kann, WORIN es steckt - die blosse Reihenfolge liesse
|
|
* offen, ob die vorangehende oder die uebergeordnete Zeile
|
|
* gemeint ist.
|
|
* klapp Zeile mit Teilzeilen, zunaechst zusammengeklappt
|
|
* unter gehoert in die Klappgruppe dieser Zeile. Das ist etwas
|
|
* anderes als teilmenge: unter sagt, WOMIT eine Zeile
|
|
* auf- und zugeht, teilmenge, WORIN ihr Wert steckt.
|
|
* braucht Spalte in stats_daily, ohne die es die Kennzahl nicht gibt
|
|
*/
|
|
const KENNZAHLEN = [
|
|
["gruppe" => "Strom vom Netz"],
|
|
["id" => "bezug", "name" => "Netzbezug", "einheit" => "kWh", "dez" => 0, "invers" => true],
|
|
["id" => "kosten", "name" => "Netzkosten (mit Grundpreis)", "einheit" => "€", "dez" => 0, "invers" => true],
|
|
["id" => "preis", "name" => "Effektiver Strompreis", "einheit" => "ct/kWh", "dez" => 1, "invers" => true],
|
|
|
|
["gruppe" => "Was die Anlage einbringt"],
|
|
["id" => "ertrag", "name" => "Ertrag der Anlage", "einheit" => "€", "dez" => 0, "summe" => true, "klapp" => true],
|
|
["id" => "eigen", "name" => "Eigenverbrauch", "einheit" => "€", "dez" => 0, "teil" => true, "unter" => "ertrag"],
|
|
["id" => "batterie", "name" => "Beitrag der Batterie", "einheit" => "€", "dez" => 0, "teilmenge" => "eigen", "unter" => "ertrag"],
|
|
["id" => "solarauto", "name" => "Solarladung des Autos", "einheit" => "€", "dez" => 0, "teilmenge" => "eigen", "unter" => "ertrag"],
|
|
["id" => "einsp", "name" => "Einspeisung", "einheit" => "€", "dez" => 0, "teil" => true, "unter" => "ertrag"],
|
|
["id" => "heizung", "name" => "Heizung über den Heizstab", "einheit" => "€", "dez" => 0, "teil" => true, "unter" => "ertrag"],
|
|
["id" => "autarkie", "name" => "Autarkie", "einheit" => "%", "dez" => 1, "punkte" => true],
|
|
["id" => "quote", "name" => "Eigenverbrauchsquote", "einheit" => "%", "dez" => 1, "punkte" => true,
|
|
"braucht" => "pv_kwh"],
|
|
|
|
["gruppe" => "Auto"],
|
|
["id" => "geladen", "name" => "Geladen", "einheit" => "kWh", "dez" => 0, "summe" => true, "klapp" => true],
|
|
["id" => "ladenEG", "name" => "Wallbox EG", "einheit" => "kWh", "dez" => 0, "teil" => true, "unter" => "geladen"],
|
|
["id" => "ladenOG", "name" => "Wallbox OG", "einheit" => "kWh", "dez" => 0, "teil" => true, "unter" => "geladen"],
|
|
["id" => "solaranteil","name" => "Solaranteil der Ladung", "einheit" => "%", "dez" => 1, "punkte" => true],
|
|
["id" => "benzin", "name" => "Benzin gespart", "einheit" => "L", "dez" => 0],
|
|
["id" => "benzingeld", "name" => "Ersparnis gegenüber Benzin", "einheit" => "€", "dez" => 0],
|
|
];
|
|
|
|
/* Die drei Kennzahlen, die oben gross stehen. */
|
|
const LEITZAHLEN = ["ertrag", "kosten", "autarkie"];
|
|
|
|
/* ------------------------------------------------------------------ Summen --
|
|
*
|
|
* Alle Grundgroessen in einem Ausdruck. Was hier steht, ist addierbar - genau
|
|
* das ist die Bedingung dafuer, dass Monat, Jahr, Zeitraum und Gesamtsumme
|
|
* aus derselben Abfrage entstehen koennen.
|
|
*/
|
|
function summenSQL($hatPv)
|
|
{
|
|
return "
|
|
SUM(d.netz_bezug_kwh) AS bezug_kwh,
|
|
SUM(d.netz_bezug_kwh * preis.cost) AS bezug_eur,
|
|
SUM(d.netz_einsp_kwh) AS einsp_kwh,
|
|
SUM(d.netz_einsp_kwh * preis.gain) AS einsp_eur,
|
|
SUM((d.eigenverbrauch_kwh + IFNULL(d.pv_fehlbetrag_kwh,0)) * preis.cost) AS eigen_eur,
|
|
" . ($hatPv ? "SUM(d.pv_kwh + IFNULL(d.pv_fehlbetrag_kwh,0))" : "NULL") . " AS pv_kwh,
|
|
SUM(d.heizstab_solar_kwh * gas.cost / NULLIF(gas.kwhPerLitre,0)) AS heiz_eur,
|
|
SUM(d.batt_entladen_kwh * preis.cost - d.batt_laden_kwh * preis.gain) AS batt_eur,
|
|
SUM(d.autarkie_avg * d.stunden_erfasst) AS autarkie_gew,
|
|
SUM(d.stunden_erfasst) AS stunden,
|
|
SUM(d.auto_eg_kwh) AS eg_kwh,
|
|
SUM(d.auto_og_kwh) AS og_kwh,
|
|
SUM(d.auto_eg_solar_kwh) AS eg_solar_kwh,
|
|
SUM(d.auto_og_solar_kwh) AS og_solar_kwh,
|
|
SUM(d.auto_eg_solar_kwh * preis.cost) AS eg_solar_eur,
|
|
SUM(d.auto_og_solar_kwh * preis.cost) AS og_solar_eur,
|
|
SUM((d.auto_eg_kwh + d.auto_og_kwh) * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)) AS benzin_l,
|
|
SUM((d.auto_eg_kwh + d.auto_og_kwh) * sprit.lPer100km / NULLIF(sprit.kwhPer100km,0)
|
|
* sprit.cost) AS benzin_eur";
|
|
}
|
|
|
|
/** Der gemeinsame Rumpf: Tageswerte, an jedem Tag der damals gueltige Tarif. */
|
|
function vonTarifen()
|
|
{
|
|
return "
|
|
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)";
|
|
}
|
|
|
|
function mitTarifen()
|
|
{
|
|
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 ";
|
|
}
|
|
|
|
/* --------------------------------------------------------------- Rechnung --
|
|
*
|
|
* Aus Summen werden Kennzahlen. Diese Funktion ist die einzige Stelle, an der
|
|
* eine Kennzahl definiert ist - sie laeuft fuer jeden Monat, jedes Jahr, jeden
|
|
* Zeitraum und die Gesamtsumme mit denselben Regeln.
|
|
*
|
|
* null heisst "nicht bestimmbar" und nicht "null": ohne Bezug gibt es keinen
|
|
* effektiven Preis, ohne Ladung keinen Solaranteil. Die Anzeige zeigt darauf
|
|
* einen Strich statt einer 0.
|
|
*/
|
|
function kennzahlenAus($s, $grundpreis)
|
|
{
|
|
$teile = function ($zaehler, $nenner, $faktor = 1) {
|
|
return ($nenner === null || $nenner <= 0 || $zaehler === null)
|
|
? null : $zaehler / $nenner * $faktor;
|
|
};
|
|
|
|
$bezug = floatval($s["bezug_kwh"]);
|
|
$kosten = floatval($s["bezug_eur"]) + $grundpreis;
|
|
$geladen = floatval($s["eg_kwh"]) + floatval($s["og_kwh"]);
|
|
$solar = floatval($s["eg_solar_kwh"]) + floatval($s["og_solar_kwh"]);
|
|
|
|
return [
|
|
"bezug" => $bezug,
|
|
"kosten" => $kosten,
|
|
"preis" => $teile($kosten, $bezug, 100),
|
|
"ertrag" => floatval($s["eigen_eur"]) + floatval($s["einsp_eur"]) + floatval($s["heiz_eur"]),
|
|
"eigen" => floatval($s["eigen_eur"]),
|
|
"einsp" => floatval($s["einsp_eur"]),
|
|
"heizung" => floatval($s["heiz_eur"]),
|
|
"batterie" => floatval($s["batt_eur"]),
|
|
"solarauto" => floatval($s["eg_solar_eur"]) + floatval($s["og_solar_eur"]),
|
|
"autarkie" => $teile($s["autarkie_gew"], $s["stunden"]),
|
|
// Eigenverbrauchsquote: der Anteil der Erzeugung, der im Haus blieb.
|
|
"quote" => $s["pv_kwh"] === null ? null
|
|
: $teile(floatval($s["pv_kwh"]) - floatval($s["einsp_kwh"]),
|
|
floatval($s["pv_kwh"]), 100),
|
|
"geladen" => $geladen,
|
|
"ladenEG" => floatval($s["eg_kwh"]),
|
|
"ladenOG" => floatval($s["og_kwh"]),
|
|
"solaranteil" => $teile($solar, $geladen, 100),
|
|
"benzin" => floatval($s["benzin_l"]),
|
|
"benzingeld" => floatval($s["benzin_eur"]),
|
|
];
|
|
}
|
|
|
|
/** Zwei Summensaetze addieren - fuer Jahre aus Monaten und die Gesamtsumme. */
|
|
function summenPlus($a, $b)
|
|
{
|
|
if ($a === null) return $b;
|
|
foreach ($b as $k => $v) {
|
|
if ($v === null) continue;
|
|
$a[$k] = ($a[$k] === null ? 0 : $a[$k]) + $v;
|
|
}
|
|
return $a;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ Abruf --*/
|
|
|
|
$db = solarDb();
|
|
|
|
// Gibt es die Erzeugung als Tageswert? Ohne sie entfaellt die
|
|
// Eigenverbrauchsquote - lieber eine Kennzahl weniger als eine Abfrage, die
|
|
// an einer fehlenden Spalte scheitert.
|
|
$hatPv = false;
|
|
if ($res = $db->query("SHOW COLUMNS FROM stats_daily LIKE 'pv_kwh'")) {
|
|
$hatPv = $res->num_rows > 0;
|
|
}
|
|
|
|
$heute = new DateTimeImmutable("today");
|
|
|
|
/*
|
|
* Monatswerte. Daraus entstehen die Verlaufsbalken, durch Addition die ganzen
|
|
* Jahre und durch nochmalige Addition die Gesamtsumme - eine Abfrage fuer
|
|
* drei Ebenen.
|
|
*/
|
|
$monate = [];
|
|
$sql = mitTarifen() . "YEAR(d.tag) AS jahr, MONTH(d.tag) AS monat, " . summenSQL($hatPv)
|
|
. vonTarifen() . " GROUP BY 1, 2 ORDER BY 1, 2";
|
|
$res = $db->query($sql);
|
|
if (!$res) {
|
|
http_response_code(500);
|
|
echo json_encode(["error" => "Statistik nicht abrufbar: " . $db->error]);
|
|
exit;
|
|
}
|
|
while ($z = $res->fetch_assoc()) {
|
|
$jahr = intval($z["jahr"]);
|
|
$monat = intval($z["monat"]);
|
|
unset($z["jahr"], $z["monat"]); // ab hier sind nur noch Summen drin
|
|
$monate[$jahr][$monat] = $z;
|
|
}
|
|
|
|
/*
|
|
* Gleicher Zeitraum: jedes Jahr vom 1. Januar bis zum heutigen Kalendertag.
|
|
* Verglichen wird nach Monat und Tag statt nach DAYOFYEAR - der 7. September
|
|
* ist in einem Schaltjahr der 251., sonst der 250. Tag, und ein Vergleich
|
|
* ueber die Tagesnummer waere in Schaltjahren um einen Tag verschoben.
|
|
*/
|
|
$gleichSummen = [];
|
|
$sql = mitTarifen() . "YEAR(d.tag) AS jahr, " . summenSQL($hatPv) . vonTarifen()
|
|
. " WHERE (MONTH(d.tag) < MONTH(CURDATE())
|
|
OR (MONTH(d.tag) = MONTH(CURDATE()) AND DAY(d.tag) <= DAY(CURDATE())))
|
|
GROUP BY 1 ORDER BY 1";
|
|
$res = $db->query($sql);
|
|
while ($res && $z = $res->fetch_assoc()) {
|
|
$jahr = intval($z["jahr"]);
|
|
unset($z["jahr"]);
|
|
$gleichSummen[$jahr] = $z;
|
|
}
|
|
|
|
$jahre = array_keys($monate);
|
|
sort($jahre);
|
|
|
|
/* ------------------------------------------------------- Antwort aufbauen --*/
|
|
|
|
$antwort = [
|
|
"stichtag" => $heute->format("Y-m-d"),
|
|
"jahre" => array_values($jahre),
|
|
"kennzahlen" => array_values(array_filter(KENNZAHLEN, function ($k) use ($hatPv) {
|
|
return !isset($k["braucht"]) || ($k["braucht"] === "pv_kwh" && $hatPv);
|
|
})),
|
|
"leitzahlen" => LEITZAHLEN,
|
|
"voll" => [],
|
|
"gleich" => [],
|
|
"monate" => [],
|
|
];
|
|
|
|
$gesamtSummen = null;
|
|
$gesamtGrund = 0.0;
|
|
|
|
foreach ($jahre as $jahr) {
|
|
$jahresSummen = null;
|
|
$grundJahr = grundpreisImJahr($jahr);
|
|
|
|
// Monatswerte. Der Grundpreis des Jahres verteilt sich nach Tagen auf die
|
|
// Monate - fuer den Verlauf genau genug, und eine Abfrage je Monat waere
|
|
// hundert Abfragen fuer eine Balkenhoehe.
|
|
$tageImJahr = intval((new DateTimeImmutable($jahr . "-12-31"))->format("z")) + 1;
|
|
for ($m = 1; $m <= 12; $m++) {
|
|
if (!isset($monate[$jahr][$m])) continue;
|
|
$tageImMonat = intval((new DateTimeImmutable(sprintf("%d-%02d-01", $jahr, $m)))->format("t"));
|
|
$werte = kennzahlenAus($monate[$jahr][$m], $grundJahr * $tageImMonat / $tageImJahr);
|
|
foreach ($werte as $id => $wert) {
|
|
$antwort["monate"][$jahr][$id][$m - 1] = $wert;
|
|
}
|
|
$jahresSummen = summenPlus($jahresSummen, $monate[$jahr][$m]);
|
|
}
|
|
// Monate ohne Daten bleiben als Luecke stehen, damit der Verlauf zwoelf
|
|
// Plaetze hat und der Januar links liegt.
|
|
foreach (array_keys($antwort["monate"][$jahr] ?? []) as $id) {
|
|
for ($m = 0; $m < 12; $m++) {
|
|
if (!array_key_exists($m, $antwort["monate"][$jahr][$id])) {
|
|
$antwort["monate"][$jahr][$id][$m] = null;
|
|
}
|
|
}
|
|
ksort($antwort["monate"][$jahr][$id]);
|
|
$antwort["monate"][$jahr][$id] = array_values($antwort["monate"][$jahr][$id]);
|
|
}
|
|
|
|
if ($jahresSummen !== null) {
|
|
$antwort["voll"][$jahr] = kennzahlenAus($jahresSummen, $grundJahr);
|
|
$gesamtSummen = summenPlus($gesamtSummen, $jahresSummen);
|
|
$gesamtGrund += $grundJahr;
|
|
}
|
|
|
|
if (isset($gleichSummen[$jahr])) {
|
|
/*
|
|
* Der Stichtag im jeweiligen Jahr. Der Tag wird auf die Laenge des
|
|
* Monats begrenzt: am 29. Februar gibt es in den anderen Jahren
|
|
* keinen 29. Februar, und ein ungueltiges Datum rutschte sonst
|
|
* stillschweigend in den Maerz.
|
|
*/
|
|
$tag = min(intval($heute->format("d")),
|
|
intval((new DateTimeImmutable($jahr . "-" . $heute->format("m") . "-01"))->format("t")));
|
|
$bis = sprintf("%d-%s-%02d", $jahr, $heute->format("m"), $tag);
|
|
$antwort["gleich"][$jahr] = kennzahlenAus(
|
|
$gleichSummen[$jahr], grundpreisImZeitraum($jahr . "-01-01", $bis));
|
|
}
|
|
}
|
|
|
|
// Die Summe ueber alles - dieselbe Rechnung, nur ueber alle Jahre.
|
|
$antwort["gesamt"] = $gesamtSummen === null ? null : kennzahlenAus($gesamtSummen, $gesamtGrund);
|
|
|
|
echo json_encode($antwort, JSON_UNESCAPED_UNICODE);
|