diff --git a/AGENTS.md b/AGENTS.md index 76b72d4..9fa874f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,8 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-08-31** (Neustart-Festhänger der App, OTA-Prüfsumme, Statistik-Kopfzahl, +**Last updated: 2026-08-31** (Audit über Panel und iOS-App: sieben Befunde, sechs behoben, +`ZUENDUNG_SENSOR` auf das Trip-Signal des FMM003 umgelegt; Manifest `2026.8.31.5`, siehe +Abschnitt AV. Davor am selben Tag: Neustart-Festhänger der App, OTA-Prüfsumme, Statistik-Kopfzahl, Setup-Filter und die gemessene Wahrheit über die Dongle-Konfiguration; Manifest `2026.8.31.4`, siehe Abschnitte AT und AU. Davor: 2026-08-30 (owner findings batch - vehicle pin, tile chevrons, select arrows, SmartDeal dialog, address cache and pull-to-refresh; manifest `2026.8.30.19`, see sections AO, AP and AQ. @@ -6189,6 +6191,109 @@ mitten in der Fahrt eine Lücke von **2739 s** ohne einen einzigen Satz. Dagegen Einstellung am Gerät; das ist der in Abschnitt P/W beschriebene Fall, den die Plausibilitätsgrenze (`UNPLAUSIBLE_KMH`) abfängt. +## AV. Audit Panel/iOS und die sieben Befunde daraus (2026.8.31.5) + +Bericht mit allen Messwerten: +https://claude.ai/code/artifact/59b55b39-0d49-483d-a3b7-c29ca59af7bf + +Der Eigentümer hat „alles" angewiesen, danach ausdrücklich auch die bis dahin +vertagte Sensor-Umstellung. Alle sieben Befunde sind erledigt bis auf Befund 07, der +eine Angabe des Eigentümers braucht. + +### Die Zündung liegt jetzt auf dem Trip-Signal des Geräts + +`ZUENDUNG_SENSOR` zeigt in `audi_ha_test` nicht mehr auf +`engine_ignition_or_acc_status` (den rohen, prellenden Eingang), sondern auf +`binary_sensor.…_trip_status_true_if_trip_started_false_if_stopped`. Begründung +vollständig in Abschnitt AU. Kurz: das Trip-Signal beginnt erst, wenn Zündung **und** +Bewegung **und** `Start Speed` zusammenkommen, und endet erst 900 s nach Zündung aus — +die Pausentoleranz kommt damit vom Gerät, ohne dass wir einen zweiten Zeitgeber bauen. + +Geschrieben über den echten Dienst (`entitaeten_schreiben`), nicht durch Hantieren an +der Datei: nur diese eine Rolle hat sich geändert, alle 23 Einträge stehen noch. +**Merkposten Werkzeug:** `hass.callService(...)` aus dem Browser-Werkzeug heraus +scheiterte reproduzierbar mit der nichtssagenden Meldung `3`, `hass.callApi("POST", +"services/…")` mit demselben Rumpf ging durch. Bei so einer Meldung also den REST-Weg +nehmen, statt den Dienst zu verdächtigen. + +**Der Preis, bewusst in Kauf genommen:** das Fahrtende liegt 15 Minuten hinter dem +tatsächlichen — genau wie mit der früheren eigenen Pausentoleranz, also kein Rückschritt. +Wer es genauer will, müsste das Ende nachträglich auf den letzten Punkt mit +Geschwindigkeit > 0 zurückrechnen; eigenes Arbeitspaket, hier nicht angefangen. + +### Live-Erkennung und Import bewerten dasselbe Fenster wieder gleich + +`MINDESTDAUER_S = 60` lebt jetzt in `verlauf.py` neben `UNPLAUSIBLE_KMH` und aus +demselben Grund. `fahrterkennung.fahrt_beenden()` prüft sie, bevor eine Fahrt entsteht; +der laufende Start wird trotzdem zurückgesetzt, sonst hinge er für immer. + +Vorher stand die Zahl allein in `historienimport.py` — dessen eigener Kommentar den +Widerspruch sogar benannte („die Live-Erkennung legt sie zwar an"). Am 31.08. sind daraus +an einem Tag **elf Fahrten mit 0 km** entstanden, zwei davon mit null Sekunden Dauer, alle +als „vollständig" im Bestand. Die Umstellung oben nimmt die Ursache, diese Grenze fängt +den Rest ab; beides zusammen, nicht eines statt des anderen. + +**Nicht angefasst: die elf bereits gespeicherten Fahrten.** Das ist die echte Historie des +Eigentümers, und deren Bereinigung ist seine Entscheidung — dieselbe Linie wie in +Abschnitt W. + +### Das Panel erfand einen Literpreis von 0,00 € + +`f.liters ? f.fuel_total_eur / f.liters : null` — bei einem automatisch erkannten +Tankvorgang stehen die Kosten noch nicht fest, und `null / 11.5` ist in JavaScript `0`. +Liste, Einzelbeleg und CSV zeigten daraufhin einen Preis, den niemand bezahlt hat. Neue +Hilfsfunktion `literpreis(f)` prüft beides, wie die App an derselben Stelle seit jeher; +vier Aufrufstellen umgestellt. Live bestätigt: die drei Zeilen vom 27./29.08. zeigen jetzt +nur noch die Liter. + +### „Ø NaN €/l" und „Ø ∞ €/l" waren erreichbar + +`schnittpreis()` teilte ohne Nennerprüfung: ein Zeitraum ohne Liter ergibt `0/0`, einer +mit Kosten aber ohne Liter eine Division durch null, und `de()` reicht beides durch. +Einzelne solche Datensätze gibt es real; es braucht nur einen Monat, der ausschließlich +daraus besteht. Jetzt `if (liter <= 0) return 0`, wie `format.ts` es seit jeher tut. + +### Der Setup-Dialog ist wirklich modal + +Die Tab-Leiste hat einen eigenen, früher registrierten Zuhörer und umging damit die Sperre +`if (setupOffen) return` des großen click-Handlers — obwohl der Dialog +`aria-modal="true"` trägt. Er schwebte anschließend über einer anderen Seite, mitsamt der +nicht gespeicherten Zuordnung. Dieselbe Sperre (plus `sheet`) steht jetzt auch dort. +Live geprüft: Tabwechsel bei offenem Dialog bleibt wirkungslos, nach „Abbrechen" gehen +alle fünf Tabs und eine Detailseite wieder. + +### Verwaister Code entfernt + +Panel: `bildSrc()`, `ring()`, `zustandsDaten()` (ohne Aufrufstelle) sowie +`.bildgrid`, `.platzhalter-datei` und die beiden `.row-fussnote`-Regeln — Reste dreier +abgelöster Entwürfe. App: 20 `.dm-*`-Regeln, darunter `dm-detail__zahl`, seit der +Umstellung auf `Fig` ohne Markup. + +**Wie geprüft wurde, damit die Liste stimmt:** ein Skript liest jede Regel und sucht ihre +Verwender im Quelltext beider Codebasen. Zwei Fallen dabei, beide real aufgetreten: Namen +aus deutschen Kommentaren sehen wie Klassen aus, und `ads-dot--${status}` wird zur +Laufzeit zusammengesetzt und taucht in keiner Suche auf — sechs Design-System-Klassen +standen deshalb zunächst falsch auf der Liste und wurden vor dem Melden aussortiert. + +### Offen: Befund 07, Erstzulassung + +`fahrzeug.erstzulassung` ist leer, deshalb steht bei der Hauptuntersuchung in beiden +Oberflächen „kein Eintrag im Wartungsplan" — ohne dieses Datum gibt es keinen Anker, +weder aus dem Wartungsplan noch aus einer Fahrzeugmeldung. Kein Codefehler; einzutragen +unter Einstellungen → Fahrzeug einrichten. Bewusst nicht erfunden. + +### Was am Panel und an der App in Ordnung war + +Vier von fünf Tabs textgleich (die fünfte Abweichung war Befund 02 selbst), Service und +Fahrzeugstatus ebenfalls, 153 `esc()`-Aufrufe ohne Lücke bei Freitext, beide +Frontend-Dateien als **Modul** geparst (siehe Abschnitt AQ), 165/165 Tests grün, in der +Konsole beider Oberflächen nur das bekannte ServiceWorker-Rauschen. + +Verifiziert: `py_compile` auf den drei Backend-Dateien, `node --check` gegen die +Modul-Grammatik, `tsc --noEmit` sauber, 165/165, `vite build` sauber, +`audi_ha_test` auf `2026.8.31.5` sauber gestartet, und jede der fünf Codeänderungen +einzeln live nachgewiesen. + ## Working conventions (observed — keep them) - German is the project language: identifiers, comments, commits, UI texts. Exceptions: diff --git a/companion-app/src/stile/screens.css b/companion-app/src/stile/screens.css index b15de44..b4d75ab 100644 --- a/companion-app/src/stile/screens.css +++ b/companion-app/src/stile/screens.css @@ -533,72 +533,7 @@ button.dm-serviceblock:active { border: 0; background: none; cursor: pointer; -} - -.dm-bildmenu-fang { - position: fixed; - inset: 0; - z-index: 1000; -} - -.dm-bildmenu { - position: absolute; - z-index: 1001; - bottom: var(--sp-5); - left: 50%; - transform: translateX(-50%); - min-width: 160px; - display: flex; - flex-direction: column; - border: 0; - border-radius: 14px; - padding: var(--sp-2); - background: rgba(40, 44, 52, .92); - -webkit-backdrop-filter: saturate(180%) blur(22px); - backdrop-filter: saturate(180%) blur(22px); - box-shadow: 0 12px 40px rgba(0, 0, 0, .5); -} - -[data-theme="tag"] .dm-bildmenu { - background: rgba(250, 250, 252, .92); -} - -.dm-bildmenu--rad { - bottom: auto; - top: 90px; - left: 22px; - transform: none; -} - -.dm-bildmenu button { - display: block; - width: 100%; - text-align: left; - background: none; - border: 0; - border-bottom: 1px solid var(--line); - color: var(--fg); - font-family: inherit; - font-size: 16px; - padding: 13px 16px; - min-height: 46px; - border-radius: 10px; - cursor: pointer; -} - -.dm-bildmenu button:last-child { - border-bottom: 0; -} - -.dm-bildmenu button:active { - background: var(--shade); -} - -.dm-bildmenu__loeschen { - color: var(--bad); -} - -/* Name des Autohauses: 15px in --fg, wie im Panel. */ +}/* Name des Autohauses: 15px in --fg, wie im Panel. */ .dm-autohaus__name { font-size: 15px; margin-top: var(--sp-3); @@ -802,39 +737,13 @@ button.dm-serviceblock:active { font-size: 15px; font-weight: 300; color: var(--fg); -} - -/* "Letzte Fahrt"/"Letzte Tankung" auf der Übersicht - deckungsgleich mit - .leaf .v/.leaf .v small im Panel (15px/400/var(--fg2), Zusatzzeile - 12.5px/var(--fg3), KEINE Wide-Schrift/Hero-Zahl). War hier 20px/300 ohne - Farbangabe - eine erfundene "große Zahl"-Behandlung, die dieser Kachel im - Panel nie zustand (Parität-Audit 2026-08-30). Nur hier verwendet (die - anderen NaviKachel-Aufrufe in MeinAudi.tsx/Service.tsx/Versicherung.tsx - nutzen .dm-navikachel__wert direkt, unangetastet). */ -.dm-navikachel__zahl { - font-size: 15px; - font-weight: 400; - color: var(--fg2); -} -.dm-navikachel__wert .dm-navikachel__zahl + small { - font-size: 12.5px; -} - -.dm-navikachel__wert small, +}.dm-navikachel__wert small, .dm-wertzeile small { display: block; margin-top: 2px; font-size: 12.5px; color: var(--fg3); -} - -.dm-aktionen { - display: flex; - justify-content: center; - padding: var(--sp-2) 0 var(--sp-4); -} - -.dm-textknopf { +}.dm-textknopf { border: 0; background: transparent; color: var(--fg2); @@ -1207,37 +1116,7 @@ button.dm-listenzeile { gap: var(--sp-3); margin: var(--sp-2) 0 var(--sp-3); flex-wrap: wrap; -} - -.dm-detail__zahl { - font-size: 40px; - font-weight: 300; - line-height: 1; - letter-spacing: -0.02em; -} - -/* Einzelfahrt/Einzelbeleg: das Panel zeichnet die Distanz- bzw. Liter-Zahl in - vTrip()/dem Tankvorgang-Detail eigens bei 52px (style="font-size:52px"), - größer als die übrigen Detailseiten mit ihrer gemeinsamen 40px-.fig - kein - Versehen, sondern die Hauptzahl der jeweiligen Seite. */ -.dm-detail__zahl--gross { - font-size: 52px; -} - -.dm-detail__einheit { - font-size: 15px; - color: var(--fg2); - margin-left: 6px; -} - -.dm-detail__marken { - display: flex; - flex-wrap: wrap; - gap: 6px; - align-items: center; -} - -/* Deckend wie .mapbox im Panel (audi-dashboard-ios.css). --tile-2 ist im +}/* Deckend wie .mapbox im Panel (audi-dashboard-ios.css). --tile-2 ist im Nachtmodus ein zweiter Schleier auf der ohnehin durchscheinenden Kachel - die Flaeche hinter den Kartenkacheln wurde dadurch heller als vorgesehen. */ .dm-karte { @@ -1336,13 +1215,7 @@ button.dm-listenzeile { height: 4px; border-radius: 50%; background: var(--fg3); -} - -.dm-liste__serie { - color: var(--fg2); -} - -/* 170px hoch wie BV_H im Panel — 120px ließen die Kurve sichtbar flacher +}/* 170px hoch wie BV_H im Panel — 120px ließen die Kurve sichtbar flacher wirken als dort. */ .dm-diagramm { width: 100%; @@ -1458,32 +1331,7 @@ button.dm-listenzeile { .dm-archivbtn:hover, .dm-archivbtn:active { color: var(--fg); -} - -.dm-waehlen { - display: flex; - flex-wrap: wrap; - gap: var(--sp-2); - margin-top: var(--sp-3); -} - -.dm-waehlen__knopf { - display: inline-flex; - align-items: center; - padding: 10px 16px; - border: 1px solid var(--line-strong); - border-radius: var(--r-pill); - font-size: 13px; - font-weight: 300; - color: var(--fg); - text-decoration: none; -} - -.dm-waehlen__knopf:hover { - background: var(--shade); -} - -/* Bleibt beim Scrollen unten stehen, damit ungespeicherte Aenderungen nicht +}/* Bleibt beim Scrollen unten stehen, damit ungespeicherte Aenderungen nicht aus dem Blick geraten. */ .dm-speicherleiste { position: sticky; @@ -1911,15 +1759,7 @@ button.dm-listenzeile { border-color: var(--fg); border-style: solid; background: var(--shade); -} - -.dm-beleg-knoepfe { - display: flex; - flex-direction: column; - gap: var(--sp-2); - margin-top: var(--sp-3); -} -/* Solldruck: vier Zeichen ("2,40"), nicht die volle Feldbreite. +}/* Solldruck: vier Zeichen ("2,40"), nicht die volle Feldbreite. Deckungsgleich mit .druckfeld in audi-dashboard.css (58px). Die Klasse stand hier schon im Markup, hatte aber keine Regel - die Felder nahmen deshalb die ganze Zeile ein. */ diff --git a/custom_components/audi_dashboard/fahrterkennung.py b/custom_components/audi_dashboard/fahrterkennung.py index 187eb9b..85015f8 100644 --- a/custom_components/audi_dashboard/fahrterkennung.py +++ b/custom_components/audi_dashboard/fahrterkennung.py @@ -26,7 +26,7 @@ from typing import TYPE_CHECKING from . import screening from .ablage import neue_id from .veroeffentlichung import zustand_oder_none -from .verlauf import verlauf_lesen, zeit_normalisiert +from .verlauf import MINDESTDAUER_S, verlauf_lesen, zeit_normalisiert if TYPE_CHECKING: from .koordinator import Koordinator @@ -170,6 +170,20 @@ async def _letztes_lebenszeichen( async def fahrt_beenden( k: Koordinator, start_ts: datetime.datetime, ende_ts: datetime.datetime ) -> None: + dauer_s = int((ende_ts - start_ts).total_seconds()) + if dauer_s < MINDESTDAUER_S: + # Zündung an, aber nicht gefahren - oder ein prellender + # Zündungseingang. Der Import verwirft solche Fenster seit jeher; + # hier taten wir es nicht, und der Bestand füllte sich mit + # Nulleinträgen (siehe MINDESTDAUER_S in verlauf.py). Der laufende + # Start wird trotzdem zurückgesetzt, sonst hinge er für immer. + await k.fahrt_start_setzen(None) + _LOGGER.info( + "Zündung war nur %s s an - keine Fahrt angelegt (Grenze %s s)", + dauer_s, + MINDESTDAUER_S, + ) + return fahrt = leere_fahrt(start_ts, ende_ts, "ha") await k.ablage.fahrt_anhaengen(fahrt) await k.fahrt_start_setzen(None) diff --git a/custom_components/audi_dashboard/frontend/app/bundle.json b/custom_components/audi_dashboard/frontend/app/bundle.json index 0815347..608f94b 100644 --- a/custom_components/audi_dashboard/frontend/app/bundle.json +++ b/custom_components/audi_dashboard/frontend/app/bundle.json @@ -1 +1 @@ -{"version":"2026.8.31.4","sha256":"48d4965f198c06dbcad882c76eca656de6cc8c18a17c7ff24a39c5c9a1e8c5a9","bytes":266032,"gebaut":"2026-08-31T13:30:52Z"} \ No newline at end of file +{"version":"2026.8.31.5","sha256":"3f297e72a5b215e54d1eb17d134d6d66ef65d1d29a5793220ff2456b6c6cc5c8","bytes":265697,"gebaut":"2026-08-31T18:03:21Z"} \ No newline at end of file diff --git a/custom_components/audi_dashboard/frontend/app/bundle.zip b/custom_components/audi_dashboard/frontend/app/bundle.zip index 09e0cb1..b3e1659 100644 Binary files a/custom_components/audi_dashboard/frontend/app/bundle.zip and b/custom_components/audi_dashboard/frontend/app/bundle.zip differ diff --git a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js index c239f96..6ef6f9f 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js @@ -124,11 +124,29 @@ const $ = (s) => ROOT.querySelector(s); /* ---------------------------------------------------------------- Format */ const de = (n, d = 0) => Number(n ?? 0).toLocaleString("de-DE", { minimumFractionDigits: d, maximumFractionDigits: d }); +/* Preis je Liter, oder null. + + Die Kosten muessen ausdruecklich geprueft werden, nicht nur die Liter: bei + einem automatisch erkannten Tankvorgang steht der Preis noch nicht fest, und + `null / 11.5` ist in JavaScript `0`. Die Liste, der Einzelbeleg und die + CSV-Ausgabe zeigten daraufhin "0,00 €/l" - einen Preis, den niemand bezahlt + hat. Die App prueft an derselben Stelle seit jeher auf beides. */ +const literpreis = (f) => (f && f.liters && f.fuel_total_eur != null ? f.fuel_total_eur / f.liters : null); const eur = (n) => de(n, 2); const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); const sum = (a, f) => a.reduce((s, x) => s + f(x), 0); /* Durchschnittspreis IMMER mengengewichtet: Summe Kosten / Summe Liter */ -const schnittpreis = (list) => sum(list, (f) => f.fuel_total_eur ?? f.summe ?? 0) / sum(list, (f) => f.liters ?? f.liter ?? 0); +/* Ohne die Nennerpruefung liefert ein Zeitraum, in dem kein Tankvorgang Liter + traegt, 0/0 = NaN - und einer mit Kosten aber ohne Liter eine Division durch + null. de() reicht beides ungefiltert durch, die Kopfzeile las dann + "Ø NaN €/l" bzw. "Ø ∞ €/l". Einzelne solche Datensaetze gibt es real; es + braucht nur einen Monat, der ausschliesslich daraus besteht. Die App hat + diesen Schutz samt Test seit jeher (format.ts). */ +const schnittpreis = (list) => { + const liter = sum(list, (f) => f.liters ?? f.liter ?? 0); + if (liter <= 0) return 0; + return sum(list, (f) => f.fuel_total_eur ?? f.summe ?? 0) / liter; +}; /* -------------------------------------------------------- Adapter-Schicht Übersetzt die Fahrzeugprofil-Struktur aus dem Backend (fahrzeug/ @@ -333,7 +351,6 @@ function bildInfo(i) { } return { label: b.label, datei: b.datei, src: `/local/bilder/${b.datei}?v=${bildVersion}` }; } -function bildSrc(i) { return bildInfo(i).src; } function carImg(modus) { if (modus === "fest") { const info = bildInfo(startIndex()); @@ -460,19 +477,6 @@ function ics(titel, datum, ort, notiz) { a.download = titel.replace(/[^\wäöüÄÖÜß ]/g, "").replace(/\s+/g, "-") + ".ics"; document.body.appendChild(a); a.click(); a.remove(); } -function ring(pct) { - const r = 64, c = 2 * Math.PI * r, on = (c * pct) / 100; - return `
- - - - -
-
${pct}%
- ${de(CONFIG.tankvolumen * pct / 100, 1)} Liter -
`; -} /* ------------------------------------------------------------------ Karte Leaflet weiterhin per CDN geladen (siehe Kopfkommentar). */ @@ -3925,7 +3929,7 @@ function tankFelder(praefix, f) { const kraftstoff = q && q.fuel_type ? q.fuel_type : KRAFTSTOFFSORTEN[0]; const station = q && q.station_name ? q.station_name : ""; const preisText = q && q.liters && q.fuel_total_eur != null && q.liters > 0 - ? `Preis je Liter: ${de(q.fuel_total_eur / q.liters, 2)} €` : "Preis je Liter: wird berechnet"; + ? `Preis je Liter: ${de(literpreis(q), 2)} €` : "Preis je Liter: wird berechnet"; return `
@@ -4006,7 +4010,7 @@ function vFuel() { ${eur(sum(fm, (f) => f.fuel_total_eur || 0))} € Ø ${de(schnittpreis(fm), 2)} €/l
`; - fm.forEach((f) => { const preis = f.liters ? f.fuel_total_eur / f.liters : null; h += swipeZeile(`fill:${f.tank_id}`, ``); }); @@ -4021,7 +4025,7 @@ function vFuel() { function vFill(id) { const f = FILLS.find((x) => x.tank_id === id); if (!f) return `
Tankvorgang nicht gefunden
`; - const berechneterPreis = f.liters ? f.fuel_total_eur / f.liters : null; + const berechneterPreis = literpreis(f); if (tankBearbeitenOffen) { return ` @@ -4323,7 +4327,7 @@ function datensatzExportieren(art) { TRIPS.map((t) => [t.trip_id, t.ts_start, t.ts_end, t.distance_km != null ? de(t.distance_km) : "", t.art, t.status])); } else if (art === "tanken") { csvDatei("tankvorgaenge.csv", ["ID", "Zeitpunkt", "Station", "Liter", "€/l", "Kosten €"], - FILLS.map((f) => [f.tank_id, f.ts, f.station_name || "", f.liters != null ? de(f.liters, 2) : "", f.liters ? de(f.fuel_total_eur / f.liters, 2) : "", f.fuel_total_eur != null ? eur(f.fuel_total_eur) : ""])); + FILLS.map((f) => [f.tank_id, f.ts, f.station_name || "", f.liters != null ? de(f.liters, 2) : "", literpreis(f) != null ? de(literpreis(f), 2) : "", f.fuel_total_eur != null ? eur(f.fuel_total_eur) : ""])); } else if (art === "service") { csvDatei("wartungsplan.csv", ["Datum", "km", "Art", "Werkstatt", "Kosten €"], CAR.service.buch.map((e2) => [e2.datum, de(e2.km), e2.art, e2.werkstatt, e2.kosten != null ? eur(e2.kosten) : ""])); @@ -4940,7 +4944,17 @@ function ereignisseVerdrahten() { .replace(/audiRingsClip/g, "audiRingsClipNav"); tabbar.prepend(marke); } - ROOT.getElementById("tabbar").addEventListener("click", (e) => { const b = e.target.closest(".tab"); if (b) go(b.dataset.tab); }); + /* Der eigene Zuhoerer der Tab-Leiste haengt vor der Sperre des grossen + click-Handlers (`if (setupOffen) return`), umging sie deshalb und liess + einen Tabwechsel zu, waehrend das Setup-Fenster offen stand - obwohl es + aria-modal="true" traegt. Das Fenster schwebte danach ueber einer anderen + Seite, mitsamt der nicht gespeicherten Zuordnung. Dieselbe Sperre also + auch hier. */ + ROOT.getElementById("tabbar").addEventListener("click", (e) => { + if (setupOffen || sheet) return; + const b = e.target.closest(".tab"); + if (b) go(b.dataset.tab); + }); ROOT.getElementById("back").addEventListener("click", () => zurueck()); ROOT.getElementById("ringsbtn").addEventListener("click", () => go("einst")); /* In Telefonbreite sind die Ringe oben rechts der Zugang zu den @@ -5807,10 +5821,6 @@ function ereignisseVerdrahten() { } /* ------------------------------------------------------------ Datenladen */ -function zustandsDaten(entityId) { - const st = HASS && HASS.states[entityId]; - return st && st.attributes ? st.attributes.daten : undefined; -} let letzterProfilState = null, letzterFahrtenState = null, letzterTankState = null, letzterStatusState = null, letzterBelegState = null, letzterBattState = null, letzterVersionState = null; diff --git a/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css b/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css index 4a413b4..8bf09c4 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-ios.css @@ -142,7 +142,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo .row dt{font-size:16px;color:var(--fg)} .row dd{font-size:16px;color:var(--fg2)} .row dd small{font-size:12.5px;margin-top:2px} -.row-fussnote{font-size:12.5px} .rows-fest dt{flex:0 0 150px} .chev{stroke:var(--ios-grau);stroke-width:1.6} @@ -352,7 +351,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo /* Feste 220px hier wuerden das Foto wieder beschneiden - die Hoehe bestimmt seit dem 2026-08-31 das Bild selbst (siehe audi-dashboard.css). */ .bildbox.carfix.klein{height:auto} - .bildgrid{grid-template-columns:repeat(4,1fr)} .quad{gap:14px} /* Design-Audit 2026-08-13: .setup-popup/.sdpopup/.sheet/.standortmenu sind @@ -392,7 +390,6 @@ main#view{--seitenrand:16px;padding:0 var(--seitenrand) 30px;scroll-behavior:smo deutlich sichtbares transluzentes Grau, dieselbe Konvention wie .feld select/.aktion ("das hier ist eine Flaeche"). */ .bildbox.bild-fehlt .platzhalter{border:0;background:var(--ios-fill);border-radius:var(--r-tile)} -.platzhalter-datei{display:none} /* Zeilen mit Wert plus Statuspunkt rechts (Sicherheitscheck) ruhiger setzen */ .row dd .dot{width:8px;height:8px} diff --git a/custom_components/audi_dashboard/frontend/audi-dashboard.css b/custom_components/audi_dashboard/frontend/audi-dashboard.css index 9721e83..1693ac8 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard.css +++ b/custom_components/audi_dashboard/frontend/audi-dashboard.css @@ -418,12 +418,10 @@ button.tile, .tilebtn { transition: background .15s, transform .1s; } .row dd { margin: 0; font-size: 14px; color: var(--fg); text-align: right; } .row dd small { display: block; font-size: 12.5px; color: var(--fg3); margin-top: 3px; } .rows-fest dd { min-width: 120px; } -/* Fussnote unter einer Zeile, ueber die volle Kachelbreite statt in der - rechten Spalte: dort blieben nur ~217px, in die eine Prognose wie - "9.029 km, voraussichtlich am 12.04.2027" zwangslaeufig umbrach. - :has() grenzt das Umbrechen auf genau die Zeilen ein, die eine Fussnote - fuehren - alle anderen .row bleiben unveraendert einzeilig. */ -.row:has(.row-fussnote) { flex-wrap: wrap; } +/* .row-fussnote ist mit dem Umbau der Service-Seite auf .termingruppe + entfallen (2026-08-18) - dort gibt es keine dt/dd-Spalten mehr, die um + Platz konkurrieren. Die beiden Regeln dafuer standen seither ohne + passendes Markup da. */ /* Sammelzeilen der Sicherheit-Ansicht (vSicherheit()): Kreis-Häkchen + Text + optionaler Chevron statt dt/dd. Erbt Abstand/Trennlinie von .row, ersetzt aber dessen space-between-Layout und setzt die Button-Vorgaben zurück, wenn @@ -449,13 +447,6 @@ button.sicherheitszeile:active { background: var(--tile-2); } und Farbe wie jede andere Nebenzeile. */ .row dd.row-hinweis { font-size: 12.5px; color: var(--fg3); } -.row-fussnote { - flex: 1 0 100%; - text-align: left; - font-size: 12.5px; - color: var(--fg3); - margin-top: 4px; -} .leaf { display: flex; align-items: center; justify-content: space-between; diff --git a/custom_components/audi_dashboard/historienimport.py b/custom_components/audi_dashboard/historienimport.py index b4e55aa..6f49106 100644 --- a/custom_components/audi_dashboard/historienimport.py +++ b/custom_components/audi_dashboard/historienimport.py @@ -45,6 +45,7 @@ from .batterie import SPANNUNG_MIN_V from .fahrterkennung import leere_fahrt from .tankerkennung import LITER_SCHWELLE, leerer_tankvorgang, schwelle_prozent from .verlauf import ( + MINDESTDAUER_S, durchschnitt_kmh, hoechstwert_im_fenster, route_aus_verlauf, @@ -68,10 +69,8 @@ _LOGGER = logging.getLogger(__name__) # Import und Live-Erkennung am Rand überschneiden. TANK_DUBLETTE_MIN = 90 -# Fahrten, die kürzer sind, sind Zündung-an-ohne-Fahrt (Radio, Tür öffnen mit -# Zündung, Diagnose) - die Live-Erkennung legt sie zwar an, im Rückblick -# fluten sie den Bestand aber mit Nulleinträgen. Bewusst konservativ. -MINDESTDAUER_S = 60 +# MINDESTDAUER_S liegt seit dem 31.08.2026 in verlauf.py - die Live-Erkennung +# prüft seitdem dieselbe Grenze, siehe die Begründung dort. # UNPLAUSIBLE_KMH selbst lebt in verlauf.py - gemeinsam mit screening.py, das # dieselbe Prüfung seit 2026.8.27 auch für die Live-Vervollständigung nutzt diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index 1d90a11..58166f2 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -1,7 +1,7 @@ { "domain": "audi_dashboard", "name": "Audi Dashboard", - "version": "2026.8.31.4", + "version": "2026.8.31.5", "documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md", "issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues", "codeowners": ["@paul"], diff --git a/custom_components/audi_dashboard/verlauf.py b/custom_components/audi_dashboard/verlauf.py index b9843ab..af008d8 100644 --- a/custom_components/audi_dashboard/verlauf.py +++ b/custom_components/audi_dashboard/verlauf.py @@ -118,6 +118,18 @@ def zahl(wert: object) -> float | None: # bewerten. UNPLAUSIBLE_KMH = 300 +# Kuerzer als das ist Zuendung ohne Fahrt: Radio, Tuer oeffnen, Diagnose - oder, +# seit die Pausentoleranz entfallen ist, ein prellender Zuendungseingang. Am +# 31.08.2026 sind daraus an einem einzigen Tag elf Fahrten mit 0 km entstanden, +# zwei davon mit null Sekunden Dauer. +# +# Die Zahl stand vorher nur in historienimport.py, dessen eigener Kommentar den +# Widerspruch sogar benannte ("die Live-Erkennung legt sie zwar an"): derselbe +# Vorgang wurde beim Import verworfen und live gespeichert. Sie lebt deshalb +# jetzt hier, gemeinsam mit UNPLAUSIBLE_KMH und aus demselben Grund - beide +# Wege duerfen denselben Vorgang nie unterschiedlich bewerten. +MINDESTDAUER_S = 60 + def durchschnitt_kmh(distanz_km: float | None, dauer_s: float | None) -> float | None: """km/h aus Strecke und Dauer, oder None wenn eine der beiden fehlt oder