diff --git a/AGENTS.md b/AGENTS.md index 7d1774b..6d68f3b 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** (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 +**Last updated: 2026-09-01** (Einzelfahrt: Orte aus den Koordinaten, Kleingedrucktes weg, „Min.", +`2026.9.1.20`, Abschnitt BJ. Davor: Strecken gestaffelt, 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`; @@ -7027,6 +7027,49 @@ 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. +## BJ. Einzelfahrt: Orte statt „unbekannt", Kleingedrucktes weg, „Min." (2026.9.1.19/.20) + +### Start und Ziel standen auf „unbekannt", obwohl die Position bekannt war + +`start_address`/`end_address` stehen in `HANDFELDER` — das Backend füllt sie **nie** von +selbst, sie kommen nur aus einer von Hand angelegten oder bearbeiteten Fahrt. Die **Koordinaten** +dagegen trägt das Screening ein: 12 von 14 Fahrten im Testbestand hatten Start- und Zielposition, +aber genau eine hatte eine Adresse. + +Beide Oberflächen lösen die Koordinaten jetzt beim Öffnen der Einzelfahrt auf — über **denselben +Cache**, den die Standortansicht benutzt (`rev:`-Schlüssel, drei Nachkommastellen ≈ 110 m). Ein +Abruf je Ort, danach nie wieder. + +**Bewusst nicht in der Liste.** Ein Vorratsabruf für alle Fahrten wäre genau das, worum Nominatim +in seinen Nutzungsbedingungen bittet, es nicht zu tun. Dort steht deshalb weiterhin „Fahrt ohne +Ortsangabe", bis jemand die Fahrt einmal geöffnet hat. + +Beide Fassungen zeigen bis zur Antwort „wird ermittelt …" und fallen auf „unbekannt" zurück, wenn +es keine Koordinaten gibt oder Nominatim nichts findet. Das Panel prüft vor dem Eintragen, ob noch +dieselbe Fahrt offen ist — wer während des Abrufs weiterblättert, soll nicht die Adresse der +vorigen Fahrt in der neuen sehen. + +Live nachgewiesen an der Fahrt vom 01.09.: `Start · Schottenau, 85072 Eichstätt` — +`Ziel · Am Anger 7a, 85111 Adelschlag`. + +### Drei Erläuterungen entfernt + +Auf Wunsch des Eigentümers entfallen die Kleingedruckten unter Durchschnitts- und +Höchstgeschwindigkeit sowie Verbrauch. Die Herleitungen stehen weiterhin im Quelltext +(`durchschnitt_kmh()`, `hoechstwert_im_fenster()`, `verbrauch_aus_literstaenden()`) — es +verschwindet die Anzeige, nicht das Wissen. Die Ortsangaben unter Start und Ziel bleiben: das sind +Daten, keine Erläuterung. + +### „min" → „Min." + +Deutsche Abkürzung, wie sie die Oberfläche an anderer Stelle längst verwendet +(`„Geparkt seit 2 Tg. 16 Std. 12 Min."`). In `dauerText()` bzw. `dauer()`, also überall wo eine +Dauer erscheint. Zwei Testerwartungen mitgezogen. + +Verifiziert: Panel als Modul geparst, `tsc --noEmit` und `vite build` sauber, 165/165 Tests grün, +im Browser nachgesehen — Adressen, `Dauer 13 Min.`, und die Liste zeigt die Staffelung aus +Abschnitt BI (`6,9 km`, `21,0 km`, `104 km`, `478 km`). + ## Working conventions (observed — keep them) - German is the project language: identifiers, comments, commits, UI texts. Exceptions: diff --git a/companion-app/src/format.test.ts b/companion-app/src/format.test.ts index 44a2ffc..271f14a 100644 --- a/companion-app/src/format.test.ts +++ b/companion-app/src/format.test.ts @@ -24,11 +24,11 @@ describe("Zahlen im deutschen Format", () => { describe("dauer", () => { it("zeigt unter einer Stunde nur Minuten", () => { - expect(dauer(2700)).toBe("45 min") + expect(dauer(2700)).toBe("45 Min.") }) it("zeigt darüber Stunden und Minuten zweistellig", () => { - expect(dauer(8040)).toBe("2 h 14 min") + expect(dauer(8040)).toBe("2 h 14 Min.") }) it("zeigt einen Strich statt „0 min", () => { diff --git a/companion-app/src/format.ts b/companion-app/src/format.ts index e079be2..465b1a8 100644 --- a/companion-app/src/format.ts +++ b/companion-app/src/format.ts @@ -125,8 +125,10 @@ export function dauer(sekunden: number | null | undefined): string { const minutenGesamt = Math.round(sekunden / 60) const stunden = Math.floor(minutenGesamt / 60) const minuten = minutenGesamt % 60 - if (stunden === 0) return `${minuten} min` - return `${stunden} h ${String(minuten).padStart(2, "0")} min` + // "Min." statt "min": deutsche Abkuerzung, wie sie die Oberflaeche sonst + // auch verwendet ("Geparkt seit 2 Tg. 16 Std. 12 Min."). + if (stunden === 0) return `${minuten} Min.` + return `${stunden} h ${String(minuten).padStart(2, "0")} Min.` } /** diff --git a/companion-app/src/screens/FahrtDetail.tsx b/companion-app/src/screens/FahrtDetail.tsx index 32f8649..610cb25 100644 --- a/companion-app/src/screens/FahrtDetail.tsx +++ b/companion-app/src/screens/FahrtDetail.tsx @@ -15,6 +15,7 @@ import { ActionButton, Feld, Fig, Pill, Tile } from "@audi-dash/ui" import type { Fahrt } from "../api" import { useDaten } from "../daten/DatenKontext" +import { koordinatenAufloesen } from "../daten/geokodierung" import { datum, dauer, de, streckeTeile, uhrzeit } from "../format" import type { SeitenName } from "../navigation" import { Leerzustand, Wertzeile, Werteliste, bestaetigen } from "./bausteine" @@ -41,6 +42,51 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n // render()). const [artVorgriff, setzeArtVorgriff] = useState<"privat" | "arbeitsweg" | null>(null) + /* Ortsangaben zu Start und Ziel. + + Das Backend fuellt start_address/end_address NIE von selbst - sie stehen in + HANDFELDER und kommen nur aus einer von Hand angelegten oder bearbeiteten + Fahrt. Die Koordinaten dagegen traegt das Screening ein. Deshalb stand hier + bisher "unbekannt", obwohl die Position bekannt war (vom Eigentuemer + gemeldet, 01.09.2026). + + Aufgeloest wird erst beim Oeffnen der Fahrt und ueber denselben Cache wie + die Standortansicht - ein Abruf je Ort, danach nie wieder. Ein Vorratsabruf + fuer alle Fahrten der Liste waere genau das, worum Nominatim in seinen + Nutzungsbedingungen bittet, es nicht zu tun. */ + const [orte, setzeOrte] = useState<{ start: string | null; ziel: string | null }>({ + start: null, + ziel: null, + }) + const startLat = fahrt?.start_lat + const startLon = fahrt?.start_lon + const endLat = fahrt?.end_lat + const endLon = fahrt?.end_lon + useEffect(() => { + let abgemeldet = false + setzeOrte({ start: null, ziel: null }) + const holen = async (lat?: number | null, lon?: number | null) => + lat == null || lon == null ? null : await koordinatenAufloesen(lat, lon) + void (async () => { + const start = await holen(startLat, startLon) + if (!abgemeldet) setzeOrte((o) => ({ ...o, start })) + const ziel = await holen(endLat, endLon) + if (!abgemeldet) setzeOrte((o) => ({ ...o, ziel })) + })() + return () => { + abgemeldet = true + } + }, [startLat, startLon, endLat, endLon]) + + const ortText = ( + adresse: string | null | undefined, + aufgeloest: string | null, + lat?: number | null, + lon?: number | null, + ) => adresse ?? aufgeloest ?? (lat == null || lon == null ? "unbekannt" : "wird ermittelt …") + const startOrt = ortText(fahrt?.start_address, orte.start, startLat, startLon) + const zielOrt = ortText(fahrt?.end_address, orte.ziel, endLat, endLon) + // Faellt der Vorgriff mit dem echten Wert zusammen, wird er ueberfluessig. // Bleibt er stehen, wuerde eine spaetere Aenderung von aussen (Panel, // zweites Geraet) von einem laengst erledigten Tipp ueberdeckt. @@ -132,12 +178,12 @@ export function FahrtDetail({ id, geheZu }: { id: string | undefined; geheZu: (n ${artText(t.art)}
-
Start
${dezeitpunkt(new Date(t.ts_start))}${esc(t.start_address || "unbekannt")}
-
Ziel
${dezeitpunkt(new Date(t.ts_end))}${esc(t.end_address || "unbekannt")}
+
Start
${dezeitpunkt(new Date(t.ts_start))}${fahrtOrtText(t.start_address, t.start_lat, t.start_lon)}
+
Ziel
${dezeitpunkt(new Date(t.ts_end))}${fahrtOrtText(t.end_address, t.end_lat, t.end_lon)}
Startkilometer
${t.odo_start != null ? de(t.odo_start) + " km" : "noch offen"}
Endkilometer
${t.odo_end != null ? de(t.odo_end) + " km" : "noch offen"}
Dauer
${dauerText(t.duration_s)}
@@ -4717,7 +4755,11 @@ function render() { const gleicheAnsicht = letzteAnsicht === route.name + "|" + (route.id || ""); let head; if (route.name === "home") { head = ["", "Übersicht"]; v.innerHTML = vHome(); } - else if (route.name === "trip") { head = ["Fahrt", "Einzelfahrt"]; v.innerHTML = vTrip(route.id); } + else if (route.name === "trip") { + head = ["Fahrt", "Einzelfahrt"]; v.innerHTML = vTrip(route.id); + const t = TRIPS.find((x) => x.trip_id === route.id); + if (t) fahrtOrteNachtragen(t); + } else if (route.name === "fill") { head = ["Tankvorgang", "Einzelbeleg"]; v.innerHTML = vFill(route.id); } else if (route.name === "service") { head = ["Wartung", "Service"]; v.innerHTML = vService(); } else if (route.name === "reifen") { head = ["Fahrzeug", "Reifen"]; v.innerHTML = vReifen(); } diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index def43cf..9e922f0 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.19", + "version": "2026.9.1.20", "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"],