Vollaudit: sechs echte Fehler in Backend, Panel und companion-app behoben

- companion-app Batterie.tsx: Generatorspannung zeigte sich faelschlich als
  Ruhespannung/verzerrte den SOH-Trend, Panel-Filter (AGM_RUHE_MAX_V) fehlte
- Manuell angelegte Fahrten/Tankvorgaenge trugen naive Zeitstempel und wurden
  von der Import-Dublettenpruefung stillschweigend uebersprungen; neue
  gemeinsame zeit_normalisiert() in verlauf.py schliesst die Luecke
- Batteriespannungsverlauf fehlte im Backup (sicherung.py)
- Tankvorgangs-Import verlor stillschweigend aeltere Tankvorgaenge vor Beginn
  der Litersensor-Historie; laeuft jetzt zweigleisig (Prozent + Liter)
- Panel-Statistikseite behauptete faelschlich, der Verbrauch je Fahrt komme
  vom Fahrzeug (OBD) - ist eine Naeherung aus dem Tankfuellstand
- companion-app Statistik.tsx: Arbeitsweg-Segment war noch rot statt neutral

Version 2026.8.27.19, OTA-Buendel neu gebaut, im Testcontainer verifiziert.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-28 00:05:58 +02:00
co-authored by Claude Sonnet 5
parent 8ba3519090
commit 90fff7b305
12 changed files with 223 additions and 69 deletions
+87
View File
@@ -3770,6 +3770,93 @@ verified, deployed fixes can vanish in one click.** (`git push` needed one retry
already-documented Gitea Credential Manager flakiness - consistent with every other push this project has
made.)
## Y. Full cross-codebase bug audit: six real defects found and fixed (2026.8.27.19)
Owner asked for a general audit of the app, not tied to a specific report. Ran three parallel reviews
(backend Python, panel JS, companion-app) rather than one pass, each grounded in reading the actual current
code rather than trusting this file's own history. Six real, concrete defects survived; three items the
reviews raised as *possible* concerns were checked and confirmed correct as-is (documented below so they
aren't re-flagged). No crash-class bugs (undefined-reference errors, the historical `bvRuhePunkte` class of
bug) were found anywhere - confirmed via ESLint `no-undef` over the whole panel file plus manual review.
**1. (High) companion-app's `Batterie.tsx` showed generator voltage as "Ruhespannung" and skewed the SOH
trend.** The panel's `bvPunkte` (the single, already-filtered `<= AGM_RUHE_MAX_V` array used for the
headline, trend, and diagram alike - see section X's `.16`/`.17` history) was never mirrored here:
companion-app's `messwerte` had no such filter, so the newest day's minimum reading - even a generator
reading above 13.0 V - could show as the "Ruhespannung" headline, and the trend average wasn't excluding
those days either. Fixed with the identical one-line filter the panel already applies:
`verlauf.filter((t) => t.min != null && t.min <= AGM_RUHE_MAX_V)` in `Batterie.tsx` - this one array already
drives the headline, the trend, and the diagram, so a single filter fixes all three. `BatterieListe.tsx`
(the measurement list) is correctly left unfiltered, exactly like the panel's `vBatterieliste()` - it tags
generator readings with a pill instead of hiding them, which is a different, already-correct concept.
**2. (High) Manually-entered trips/refuels could be silently duplicated by a later import.**
`historienimport.py`'s own docstring promises every import-created record is checked for overlap against
*all* existing ones, "egal ob live erkannt, von Hand angelegt oder aus einem früheren Import" - but the
manual-entry paths (`fahrterkennung.manuell_anlegen()`/`aktualisieren()`, `belege.tankvorgang_manuell()`/
`tankvorgang_aktualisieren()`) stored whatever timestamp the frontend sent verbatim via a raw
`datetime.fromisoformat()`, while `historienimport.als_zeit()` normalizes the same kind of input (naive =
local time, per its own comment) to a timezone-aware UTC datetime. `_ueberschneidet()`'s overlap check and
the tank-vorgänge dedup window both explicitly skipped any record with a naive timestamp - so every
manually-created trip or refuel was invisible to a later import's dedup, and could be re-created as a
duplicate. Fixed by moving the normalization into a shared function, `zeit_normalisiert()` in `verlauf.py`
(a leaf module with no local imports, chosen specifically to avoid a circular import between
`fahrterkennung.py`/`belege.py` and `historienimport.py`) - `historienimport.als_zeit` is now an alias for
it. Every write path (`manuell_anlegen`, `aktualisieren`, `tankvorgang_manuell`, `tankvorgang_aktualisieren`)
now normalizes through it before storing. `_ueberschneidet()`/`_tankvorgaenge_importieren()`'s
`bekannte_zeiten` collection were also changed to run existing stored timestamps through the same function
instead of skipping naive ones - this also correctly protects any already-stored legacy record with a naive
timestamp from before this fix, not just new writes.
**3. (Medium) Battery voltage history had no backup coverage.** `sicherung.py`'s `DATEIEN` tuple (used by
"Backup jetzt erstellen") listed `fahrzeugprofil.json`/`fahrten.jsonl`/`tankvorgaenge.jsonl`/
`entitaeten.json` but not `batteriespannung.jsonl` - even though `batterie.py`'s own docstring frames it as
a deliberately multi-year dataset for detecting battery aging. Added it to `DATEIEN`; `_kopieren()` already
skips any file that doesn't exist, so this is zero-risk for an install with no battery history yet.
**4. (Low/Medium) Tank-vorgang import was all-or-nothing per sensor once `TANK_LITER_SENSOR` was mapped.**
`_tankvorgaenge_importieren()` used to read *only* the liter sensor's history once one was mapped, silently
losing any refuel from before the liter sensor's own recorder history started (e.g. it was mapped
mid-project, per section Q) even though the percent sensor's own history covered that period. Rewrote the
function to run both passes when a liter sensor is mapped: the percent-based pass now covers the period
strictly *before* the liter history's first point, and the liter-based pass (more accurate, since it's
already in liters) covers everything from there on - both share the same `bekannte_zeiten`/`neue` state so
the same physical refill can't be created twice if the two histories overlap. Unmapped-liter-sensor
behavior (pure percent-based import) is unchanged.
**5. (Moderate) Panel's Statistik page mislabeled a tank-based estimate as vehicle-reported data.** The
"Vom Fahrzeug gemeldet, je Zeitraum" tile and its caption claimed `t.verbrauch_l_100km` (per-trip fuel
consumption) came from the vehicle's own OBD reporting - it doesn't; section V documented that this field
is entirely a backend-computed approximation from `TANK_LITER_SENSOR` liter deltas, and the trip-detail
page already correctly labels it "Näherung aus Tankfüllstand". Only the Statistik tile and a stale code
comment (`fahrtVerbrauch`'s own header) still claimed OBD provenance. Fixed both to say what the value
actually is - a text-only change, the underlying computation was already correct.
**6. (Low) companion-app's "Art der Fahrten" chart still colored the Arbeitsweg segment red.** The panel
fixed this exact chart on 2026-08-13 (section C, the iOS-overlay red-semantics-inversion finding: red is
reserved for destructive actions, this segment is a neutral data category) but companion-app's
`Statistik.tsx` never received the port - its Arbeitsweg segment stayed `var(--red)` while its sibling
Privat segment already used the neutral `var(--fg3)`. Changed to `var(--fg)`, matching the panel's own
Arbeitsweg color exactly (not `--fg3`, which the panel reserves for the Privat segment).
**Checked and confirmed NOT a bug** (raised by one of the three reviews, verified against current code):
`AGM_RUHE_MAX_V`, `NEBENWERT_MAX_ABSTAND_S=300`, `_letzte_gueltige_spannung`, the `ablage.py` temperature
backfill branch, `historienimport.py`'s `aussentemp` fetch, and `batterie.py`'s freshness check - all
already correct per sections T/W/X's history; the route/back-navigation tables (`ZURUECK` vs. the panel's
dispatcher) - a full cross-check found every route has exactly one matching entry both ways; and the panel's
`ereignisseVerdrahten()` listener wiring - guarded by the existing idempotency lock, no double-registration
risk.
Verified: all five touched backend files (`verlauf.py`, `historienimport.py`, `fahrterkennung.py`,
`belege.py`, `sicherung.py`) `py_compile` clean in `audi_ha_test`; panel `node --check` clean; companion-app
`tsc --noEmit` clean and full suite green at 146/146. Manifest bumped to `2026.8.27.19` (panel text
changed), `npm run ota` rerun (companion-app source changed, `Batterie.tsx`/`Statistik.tsx`), `audi_ha_test`
restarted and confirmed clean via log (`Audi Dashboard 2026.8.27.19 eingerichtet`, no traceback; the only
`ReferenceError: bvRuhePunkte` lines in the post-restart log predate the setup-complete line and come from a
stale browser tab's cached pre-restart script, not a new regression). **Not yet committed/pushed** - per
this file's own binding lesson from earlier the same day (section W), that should happen before any
self-update/install run is suggested or allowed; ask the owner before running one.
---
## Working conventions (observed — keep them)