Zeilenenden vereinheitlichen: LF ueberall ausser im Fremdcode

Bisher entschied jede Arbeitsstation fuer sich, welche Zeilenenden ein
Commit bekommt: Git fuer Windows setzt core.autocrlf=input in seiner
System-Konfiguration, die NAS setzt nichts. Weil der Bestand ausserdem in
sich gemischt war - 34 PHP-Dateien mit LF, 30 mit CRLF -, gab es gar kein
richtiges Zeilenende, an das ein Werkzeug sich haette halten koennen. Jede
Ergaenzung mit LF in einer CRLF-Datei ergab eine gemischte Datei; helper.php
und js/solar/homeMQTT.js waren bereits so entstanden.

.gitattributes macht die Zeilenenden zur Eigenschaft des Repositorys. LF,
weil dieses Verzeichnis der Web-Ordner der NAS ist und von Linux
ausgeliefert wird - und weil Git fuer Windows mit "input" ohnehin schon
dorthin zeigt.

restricted/WebAuthn bleibt ausgenommen und damit byteweise so, wie die
Bibliothek geliefert wurde; darunter liegen 292 Zertifikate.

Von den 47 geaenderten Dateien wurde nachgerechnet keine einzige im Inhalt
angefasst - der Vergleich HEAD-ohne-CR gegen Index ist ueberall gleich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 23:04:00 +02:00
co-authored by Claude Opus 5
parent abdf6880eb
commit 93fb054b66
48 changed files with 13209 additions and 13192 deletions
+211 -211
View File
@@ -1,211 +1,211 @@
<?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]}]}';
<?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]}]}';