diff --git a/AGENTS.md b/AGENTS.md index 35f897d..7d1774b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # AGENTS.md — Project state, review findings, open items, and working rules -**Last updated: 2026-09-01** (Audit ueber Panel, App und Backend: vier Befunde, zwei behoben, -`2026.9.1.17`, Abschnitt BH. Davor: Setup mit gruener Bestaetigung und Erwartungswert je +**Last updated: 2026-09-01** (Strecken gestaffelt: Meter, Nachkommastelle, ganze Kilometer, +`2026.9.1.18`, Abschnitt BI. Davor: Audit ueber Panel, App und Backend, Abschnitt BH. Davor: Setup mit gruener Bestaetigung und Erwartungswert je Listenposition, Abschnitt BG. Davor: Rueckblick ohne Nullfahrten, Streckenwahl gemeinsam in `verlauf.py`, `2026.9.1.14`, Abschnitt BF. Davor: Fahrtstrecke auf 100 m: CAN als Anker, GNSS als Nachkommastelle, `2026.9.1.10`, Abschnitt BE. Davor: Nachlauf-Abzug nur mit zugeordnetem `TRIP_SENSOR`, `2026.9.1.9`; @@ -6994,6 +6994,39 @@ nachträglich. Die Fahrt dann zu verwerfen hieße, einen bereits gespeicherten D zu löschen. Das ist eine Entscheidung über die Daten des Eigentümers, keine über den Code — dieselbe Linie wie in Abschnitt W. +## BI. Strecken gestaffelt anzeigen: Meter, Nachkommastelle, ganze Kilometer (2026.9.1.18) + +Beim Audit fiel auf, dass die gewonnene Genauigkeit gar nicht ankam: `de()` formatiert ohne +Nachkommastelle, aus `6,896 km` wurde in beiden Oberflächen schlicht **„7 km"**. Wir haben den +ganzen Tag auf zehn Meter genau gemessen und es im letzten Schritt weggerundet. + +| Strecke | Anzeige | +|---|---| +| unter 1 km | Meter, ganzzahlig — `400 m`, `999 m` | +| bis 99,9 km | eine Nachkommastelle — `1,0 km`, `6,9 km` | +| ab 100 km | ganze Kilometer — `104 km` | + +`streckeTeile()`/`streckeText()` in beiden Codebasen, wortgleich. Zwei Feinheiten: + +- **Gestaffelt wird nach dem GERUNDETEN Wert**, nicht nach dem rohen. 0,9996 km sind gerundet + 1000 m und gehören damit in die km-Stufe — sonst stünde dort „1.000 m". Dasselbe an der Grenze zu 100. +- **`streckeTeile()` gibt Wert und Einheit getrennt zurück**, weil die Detailansicht die große Zahl + und ihre Einheit in getrennten Elementen setzt. Ohne das stünde unter „400" weiterhin „km". + +Im Panel heißt die Funktion `streckeText`, nicht `strecke`: den Namen gibt es dort schon für die +Ortsangaben einer Fahrt („Ingolstadt → München"). `node --check` hat die Kollision gefunden. + +Angewandt auf Einzelfahrt (Liste, Detail) und die Jahres-/Monatssummen. **Nicht** auf +Kilometerstände, Serviceintervalle oder Reichweite — das sind Zählerstände und Prognosen, keine +gefahrenen Strecken. + +Verifiziert: 12 Fälle gegen die Staffelung samt beider Grenzen, 165/165 Tests grün nach Anpassung +einer Erwartung, `node --check` als Modul, `tsc --noEmit` und `vite build` sauber, im Browser +nachgesehen: die Fahrt vom 01.09. steht jetzt mit **6,9 km** statt „7 km". + +Die angepasste Erwartung ist selbst ein Beleg: der Test verlangte `„43 km"` für eine Summe von +**42,5 km** — die alte Anzeige rundete also schon dort, wo es etwas zu zeigen gab. + ## Working conventions (observed — keep them) - German is the project language: identifiers, comments, commits, UI texts. Exceptions: diff --git a/companion-app/src/format.ts b/companion-app/src/format.ts index 34de300..e079be2 100644 --- a/companion-app/src/format.ts +++ b/companion-app/src/format.ts @@ -20,6 +20,36 @@ export function eur(wert: number | null | undefined): string { liegen. */ /** Wie de(), aber „–" statt „0" wenn nichts bekannt ist. */ +/** Eine gefahrene Strecke in der Staffelung, die der Eigentümer am 01.09.2026 + * festgelegt hat: + * + * unter 1 km -> Meter, ganzzahlig ("400 m", "999 m") + * bis 99,9 km -> eine Nachkommastelle ("1,0 km", "6,9 km") + * ab 100 km -> ganze Kilometer ("104 km") + * + * Seit der GNSS-Verfeinerung messen wir auf etwa zehn Meter genau; ohne + * Nachkommastelle wurde davon nichts sichtbar (aus 6,896 km wurde "7 km"). + * Umgekehrt wäre eine Nachkommastelle bei dreistelligen Summen nur Unruhe. + * + * Gestaffelt wird nach dem GERUNDETEN Wert: 0,9996 km sind gerundet 1000 m + * und gehören damit schon in die km-Stufe, sonst stünde dort "1.000 m". + * + * Wortgleich mit streckeTeile()/streckeText() im Panel. */ +export function streckeTeile(km: number | null | undefined): { wert: string; einheit: string } | null { + const n = Number(km) + if (km === null || km === undefined || !Number.isFinite(n)) return null + const meter = Math.round(n * 1000) + if (Math.abs(meter) < 1000) return { wert: de(meter), einheit: "m" } + const fein = Math.round(n * 10) / 10 + if (Math.abs(fein) < 100) return { wert: de(fein, 1), einheit: "km" } + return { wert: de(Math.round(n)), einheit: "km" } +} + +export function streckeText(km: number | null | undefined, ersatz = "–"): string { + const t = streckeTeile(km) + return t ? `${t.wert} ${t.einheit}` : ersatz +} + export function deOderStrich(wert: number | null | undefined, nachkomma = 0): string { return wert === null || wert === undefined ? "–" : de(wert, nachkomma) } diff --git a/companion-app/src/screens/FahrtDetail.tsx b/companion-app/src/screens/FahrtDetail.tsx index 0971779..838d4d6 100644 --- a/companion-app/src/screens/FahrtDetail.tsx +++ b/companion-app/src/screens/FahrtDetail.tsx @@ -15,7 +15,7 @@ import { ActionButton, Feld, Fig, Pill, Tile } from "@audi-dash/ui" import type { Fahrt } from "../api" import { useDaten } from "../daten/DatenKontext" -import { datum, dauer, de, deOderStrich, uhrzeit } from "../format" +import { datum, dauer, de, streckeTeile, uhrzeit } from "../format" import type { SeitenName } from "../navigation" import { Leerzustand, Wertzeile, Werteliste, bestaetigen } from "./bausteine" import { FahrtFelderFormular, fahrtFelderAusFahrt, fahrtFelderAuswerten } from "./FahrtFelder" @@ -109,7 +109,11 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n Distanz
- +
{/* Knopf, nicht Anzeige: im Panel schaltet dieselbe Pille seit jeher diff --git a/companion-app/src/screens/Fahrten.tsx b/companion-app/src/screens/Fahrten.tsx index ebe6d02..06ebdb8 100644 --- a/companion-app/src/screens/Fahrten.tsx +++ b/companion-app/src/screens/Fahrten.tsx @@ -9,7 +9,7 @@ import { Accordion, ActionButton, Feld, Tile } from "@audi-dash/ui" import { useDaten } from "../daten/DatenKontext" import { nachJahrUndMonat } from "../daten/statistik" -import { datum, de, summe } from "../format" +import { datum, de, streckeText, summe } from "../format" import type { SeitenName } from "../navigation" import { Blattzeile, Leerzustand } from "./bausteine" import { FahrtFelderFormular, LEERE_FAHRT_FELDER, fahrtFelderAuswerten } from "./FahrtFelder" @@ -64,7 +64,7 @@ export function Fahrten({ geheZu }: { geheZu: (name: SeitenName, id?: string) => title={String(jahr.jahr)} summary={ <> - {de(summe(jahr.eintraege, (f) => f.distance_km ?? 0))} km + {streckeText(summe(jahr.eintraege, (f) => f.distance_km ?? 0))}
{jahr.eintraege.length} Fahrt{jahr.eintraege.length === 1 ? "" : "en"} @@ -76,7 +76,7 @@ export function Fahrten({ geheZu }: { geheZu: (name: SeitenName, id?: string) => key={monat.schluessel} level={2} title={monatName(monat.jahr, monat.monat)} - summary={`${de(summe(monat.eintraege, (f) => f.distance_km ?? 0))} km`} + summary={streckeText(summe(monat.eintraege, (f) => f.distance_km ?? 0))} defaultOpen={monat === jahr.monate[0]} > {monat.eintraege.map((fahrt, i) => ( @@ -89,7 +89,7 @@ export function Fahrten({ geheZu }: { geheZu: (name: SeitenName, id?: string) => label={strecke(fahrt)} labelZusatz={`${datum(fahrt.ts_start)} · ${artText(fahrt.art)}`} wert={ - fahrt.distance_km != null ? `${de(fahrt.distance_km)} km` : "offen" + streckeText(fahrt.distance_km, "offen") } wertZusatz={ fahrt.verbrauch_l_100km != null diff --git a/companion-app/src/screens/screens.test.tsx b/companion-app/src/screens/screens.test.tsx index da894f5..64b48a3 100644 --- a/companion-app/src/screens/screens.test.tsx +++ b/companion-app/src/screens/screens.test.tsx @@ -126,9 +126,10 @@ describe("Inhalte kommen wirklich aus den Daten", () => { it("gruppiert Fahrten nach Jahr", async () => { zeige("trips") await waitFor(() => expect(screen.getByText("2026")).toBeTruthy()) - // Ganze Kilometer in der Liste, wie vTrips() im Panel (`de(t.distance_km)` - // ohne Nachkommastelle) - die Nachkommastelle steht auf der Einzelfahrt. - expect(screen.getByText("43 km")).toBeTruthy() + // Gestaffelt nach streckeText(): unter 100 km mit einer Nachkommastelle. + // Die Regel gilt seit dem 01.09.2026 fuer Einzelfahrten UND Summen, damit + // die auf zehn Meter genaue GNSS-Messung ueberhaupt sichtbar wird. + expect(screen.getByText("42,5 km")).toBeTruthy() }) it("zeigt bei einer Fahrt ohne Position keine erfundene Route", async () => { diff --git a/custom_components/audi_dashboard/frontend/app/bundle.json b/custom_components/audi_dashboard/frontend/app/bundle.json index 626c31f..b5d8a87 100644 --- a/custom_components/audi_dashboard/frontend/app/bundle.json +++ b/custom_components/audi_dashboard/frontend/app/bundle.json @@ -1 +1 @@ -{"version":"2026.9.1.17","sha256":"96fa9737a475fe862c2dd309490f687a3e3e4088a8f356acd3f5c718cc919306","bytes":265731,"gebaut":"2026-09-01T17:08:45Z"} \ No newline at end of file +{"version":"2026.9.1.18","sha256":"9dcad77b962e5ef55446d70031249261f850df3e12077bce31656bdf247dd6fe","bytes":265893,"gebaut":"2026-09-01T17:27:43Z"} \ 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 5a39165..733d79a 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 71b65d6..928fe3d 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js @@ -124,6 +124,38 @@ const $ = (s) => ROOT.querySelector(s); /* ---------------------------------------------------------------- Format */ const de = (n, d = 0) => Number(n ?? 0).toLocaleString("de-DE", { minimumFractionDigits: d, maximumFractionDigits: d }); +/* Eine gefahrene Strecke in der Staffelung, die der Eigentuemer am 01.09.2026 + festgelegt hat: + + unter 1 km -> Meter, ganzzahlig ("400 m", "999 m") + bis 99,9 km -> eine Nachkommastelle ("1,0 km", "6,9 km") + ab 100 km -> ganze Kilometer ("104 km") + + Der Grund fuer die Staffelung: seit der GNSS-Verfeinerung messen wir auf + etwa zehn Meter genau (Fahrt vom 01.09.: 5,015 km GNSS gegen 5,000 km am + Tacho, 0,3 %). Ohne Nachkommastelle wurde davon nichts sichtbar - aus + 6,896 km wurde in der Anzeige schlicht "7 km". Umgekehrt waere eine + Nachkommastelle bei dreistelligen Summen nur Unruhe. + + Gestaffelt wird nach dem GERUNDETEN Wert, nicht nach dem rohen: 0,9996 km + sind gerundet 1000 m und gehoeren damit schon in die km-Stufe, sonst stuende + dort "1.000 m". Dasselbe an der Grenze zu 100. */ +function streckeTeile(km) { + const n = Number(km); + if (km == null || !Number.isFinite(n)) return null; + const meter = Math.round(n * 1000); + if (Math.abs(meter) < 1000) return { wert: de(meter), einheit: "m" }; + const fein = Math.round(n * 10) / 10; + if (Math.abs(fein) < 100) return { wert: de(fein, 1), einheit: "km" }; + return { wert: de(Math.round(n)), einheit: "km" }; +} +// Der Name streckeText, nicht strecke: strecke(t) gibt es bereits fuer die +// Ortsangaben einer Fahrt ("Ingolstadt -> Muenchen"). +const streckeText = (km, ersatz = "–") => { + const t = streckeTeile(km); + return t ? `${t.wert} ${t.einheit}` : ersatz; +}; + /* Preis je Liter, oder null. Die Kosten muessen ausdruecklich geprueft werden, nicht nur die Liter: bei @@ -1430,7 +1462,7 @@ function teaser() { return `
Zuletzt ${t ? `` : ""} ${f ? ` + ${j}${streckeText(sum(tj, (t) => t.distance_km || 0))}
${tj.length} Fahrten
`; const monate = [...new Set(tj.map((t) => new Date(t.ts_start).toLocaleDateString("de-DE", { month: "long" })))]; monate.forEach((m, mi) => { const tm = tj.filter((t) => new Date(t.ts_start).toLocaleDateString("de-DE", { month: "long" }) === m); h += `
+ ${streckeText(sum(tm, (t) => t.distance_km || 0))}
`; tm.forEach((t) => { h += swipeZeile(`trip:${t.trip_id}`, ``); }); h += `
`; }); @@ -3588,7 +3620,7 @@ function vTrip(id) { ${mapBox()}
Distanz -
${t.distance_km != null ? de(t.distance_km) : "–"}km
+
${(streckeTeile(t.distance_km) || { wert: "–", einheit: "km" }).wert}${(streckeTeile(t.distance_km) || { einheit: "km" }).einheit}
diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index 49386c4..095cb4f 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.9.1.17", + "version": "2026.9.1.18", "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"],