diff --git a/AGENTS.md b/AGENTS.md index 6d68f3b..d22c51d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,8 @@ # AGENTS.md — Project state, review findings, open items, and working rules **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 +`2026.9.1.23`, Abschnitt BJ - dazu Orte in der Fahrtenliste ohne Netzabruf. +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`; @@ -7070,6 +7071,32 @@ Verifiziert: Panel als Modul geparst, `tsc --noEmit` und `vite build` sauber, 16 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`). +### Nachtrag (2026.9.1.21–.23): die Liste zeigt Orte, ohne einen einzigen Abruf + +**Was schon im Speicher liegt, zeigt die Liste umsonst mit.** `ortAusCache()` liest nur den +Zwischenspeicher und fragt nie das Netz. Ein Abruf je Zeile wäre genau der Vorratsabruf, um dessen +Unterlassung Nominatim bittet — bei zweihundert Fahrten vierhundert Anfragen für einen Blick auf +die Liste. + +**In der Liste nur der Ort, nicht die Anschrift** (Wunsch des Eigentümers): eine Zeile trägt Datum, +Art, Strecke und Verbrauch; „Schottenau, 85072 Eichstätt → Am Anger 7a, 85111 Adelschlag" ist länger +als der ganze Rest. Auf der Einzelfahrt bleibt die vollständige Anschrift. + +Der Ortsname wird beim Auflösen **separat gemerkt** (`ort:`-Schlüssel), nicht nachträglich aus der +Anschrift geschnitten — fällt Nominatim auf `display_name` zurück, stünde hinter dem letzten Komma +das **Land**, und in der Liste stünde „Deutschland → Deutschland". + +**Der Rückfall war trotzdem nötig, und das Ausliefern hat es gezeigt:** die Liste stand weiter voller +Anschriften. Grund — der Ortsname entsteht nur bei einem *frischen* Abruf, und die Adressen lagen +längst im Speicher, also kehrte die Auflösung sofort zurück und schrieb nichts. `stadtAusAnschrift()` +schneidet den Ort deshalb aus einer bereits gespeicherten Anschrift, **aber nur wenn hinter dem +letzten Komma eine Postleitzahl steht**. Damit greift es bei unserem eigenen Format +`„, "` und niemals bei `display_name`. + +Geprüft: 7 Fälle gegen den Schnitt (mit/ohne Straße, benannter Platz, ohne PLZ, `display_name`-Form, +`null`). Im Browser nachgesehen: `„Eichstätt → Adelschlag"` in der Liste, volle Anschrift auf der +Einzelfahrt. + ## Working conventions (observed — keep them) - German is the project language: identifiers, comments, commits, UI texts. Exceptions: diff --git a/companion-app/src/daten/geokodierung.ts b/companion-app/src/daten/geokodierung.ts index 36f0c79..59b89ff 100644 --- a/companion-app/src/daten/geokodierung.ts +++ b/companion-app/src/daten/geokodierung.ts @@ -77,6 +77,67 @@ function cacheSchreiben(suche: string, treffer: Koordinate | null): void { * Koordinatenpaar. Auf drei Nachkommastellen gerundet (~110 m), damit ein * paar Meter Abweichung keinen neuen Abruf auslösen. */ +/** Ein bereits aufgeloester Ort aus dem Zwischenspeicher - OHNE Netzabruf. + * + * Die Einzelfahrt loest Koordinaten bei Bedarf auf und legt das Ergebnis im + * selben Fach ab wie die Standortansicht. Was einmal dort steht, kann die + * Liste umsonst mitbenutzen: kein zusaetzlicher Abruf, keine Wartezeit, kein + * Flackern beim Zeichnen. + * + * Bewusst NUR aus dem Speicher. Ein Abruf je Zeile waere genau der + * Vorratsabruf, um dessen Unterlassung Nominatim in seinen + * Nutzungsbedingungen bittet - bei zweihundert Fahrten waeren das + * vierhundert Anfragen fuer einen Blick auf die Liste. + * + * Schluessel wie bei der Aufloesung: drei Nachkommastellen (~110 m). */ +/** Der Ortsname allein, fuer die Fahrtenliste. + * + * In der Liste steht eine Fahrt in einer Zeile neben Datum, Art, Strecke und + * Verbrauch. Zwei volle Anschriften passen dort nicht - "Schottenau, 85072 + * Eichstaett -> Am Anger 7a, 85111 Adelschlag" ist laenger als der Rest der + * Zeile zusammen. Auf der Einzelfahrt bleibt die vollstaendige Anschrift. + * + * Der Ort wird beim Aufloesen SEPARAT gemerkt (Schluessel "ort:"), nicht + * nachtraeglich aus der Anschrift geschnitten. Das Zerlegen der eigenen + * Ausgabe waere fast immer richtig und in einem Fall falsch: faellt Nominatim + * auf display_name zurueck, steht hinter dem letzten Komma das LAND. Dann + * stuende in der Liste "Deutschland -> Deutschland". */ +/** Der Ortsname aus einer bereits gespeicherten Anschrift. + * + * Rueckfall fuer alles, was VOR dem Merken des Ortsnamens aufgeloest wurde: + * dort liegt nur die volle Anschrift, und ein neuer Netzabruf, nur um den Ort + * zu erfahren, waere Verschwendung. + * + * Geschnitten wird ausschliesslich, wenn hinter dem letzten Komma eine + * Postleitzahl steht - unser eigenes Format ist ", ". + * Faellt Nominatim auf display_name zurueck, fehlt die PLZ und hinter dem + * letzten Komma stuende das LAND; dann lieber die volle Anschrift zeigen als + * "Deutschland". */ +export function stadtAusAnschrift(anschrift: string | null | undefined): string | null { + if (!anschrift) return null + const teil = String(anschrift).split(", ").pop()?.trim() ?? "" + const treffer = teil.match(/^\d{4,5}\s+(.+)$/) + return treffer && treffer[1] ? treffer[1].trim() : null +} + +export function stadtAusCache( + lat: number | null | undefined, + lon: number | null | undefined, +): string | null { + if (lat === null || lat === undefined || lon === null || lon === undefined) return null + const wert = cacheLesen()[`ort:${lat.toFixed(3)},${lon.toFixed(3)}`] + return typeof wert === "string" && wert ? wert : null +} + +export function ortAusCache( + lat: number | null | undefined, + lon: number | null | undefined, +): string | null { + if (lat === null || lat === undefined || lon === null || lon === undefined) return null + const wert = cacheLesen()[`rev:${lat.toFixed(3)},${lon.toFixed(3)}`] + return typeof wert === "string" ? wert : null +} + export async function koordinatenAufloesen(lat: number, lon: number): Promise { const schluessel = `rev:${lat.toFixed(3)},${lon.toFixed(3)}` const cache = cacheLesen() @@ -96,7 +157,8 @@ export async function koordinatenAufloesen(lat: number, lon: number): Promise { + if (adresse) return adresse + const gemerkt = stadtAusCache(lat, lon) + if (gemerkt) return gemerkt + const voll = ortAusCache(lat, lon) + return stadtAusAnschrift(voll) ?? voll + } + const a = ortsname(fahrt.start_address, fahrt.start_lat, fahrt.start_lon) + const b = ortsname(fahrt.end_address, fahrt.end_lat, fahrt.end_lon) if (!a && !b) return "Fahrt ohne Ortsangabe" if (!b) return a ?? null if (!a) return b diff --git a/custom_components/audi_dashboard/frontend/app/bundle.json b/custom_components/audi_dashboard/frontend/app/bundle.json index 3484e6b..b25058a 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.20","sha256":"20cc80f881d429303da2558b3171aabe414851aacbe2fee9253f7026c376043b","bytes":266021,"gebaut":"2026-09-01T17:35:23Z"} \ No newline at end of file +{"version":"2026.9.1.23","sha256":"c3e1376f4e314b2994bc6341c423b92d251142e0abd2abe4752e1f5f1a578d13","bytes":266223,"gebaut":"2026-09-01T18:35: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 5dc9543..e48b24f 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 9308c75..76ac985 100644 --- a/custom_components/audi_dashboard/frontend/audi-dashboard-app.js +++ b/custom_components/audi_dashboard/frontend/audi-dashboard-app.js @@ -754,6 +754,7 @@ let USER_POS_FEHLER = null; let USER_WATCH = null; // watchPosition-Kennung, solange die Standortseite offen ist let STANDORT_ADRESSE = null; // zuletzt aufgelöste Adresse (Cache über Koordinaten-Rundung) let STANDORT_ADRESSE_KEY = null; +let STANDORT_STADT = ""; // reiner Ortsname zum selben Punkt let STANDORTMENU_OFFEN = false; let SMAP = null, SMAP_TILE = null, SMAP_STIL = null; // Vollbild-Karte (eigenständig von MAP/TMAP); SMAP_STIL: null/"strasse" oder "satellit" let TMAP = null, TMAP_TILE = null; // Vorschau-Karte der Übersicht-Kachel @@ -1016,7 +1017,8 @@ async function standortAdresseAufloesen(lat, lon) { const daten = await res.json(); const a = daten.address || {}; const strasse = [a.road, a.house_number].filter(Boolean).join(" "); - const ort = [a.postcode, a.city || a.town || a.village].filter(Boolean).join(" "); + STANDORT_STADT = (a.city || a.town || a.village || "").trim(); + const ort = [a.postcode, STANDORT_STADT].filter(Boolean).join(" "); /* Steht das Auto auf einem benannten Platz - Parkplatz, Parkhaus, Rastplatz -, sagt dessen Name mehr als die naechstgelegene Hausnummer. Nominatim liefert ihn im Feld name; die Pruefung auf die Art der @@ -1038,6 +1040,9 @@ async function standortAdresseAufloesen(lat, lon) { // Nur echte Treffer merken - eine gedrosselte Antwort ist kein "hier gibt es // keine Adresse" und soll beim naechsten Mal erneut versucht werden. if (STANDORT_ADRESSE) geoCacheSchreiben(cacheKey, STANDORT_ADRESSE); + // Der reine Ortsname zusaetzlich, fuer die Fahrtenliste - siehe + // stadtAusCache(). + if (STANDORT_STADT) geoCacheSchreiben("ort:" + key, STANDORT_STADT); return STANDORT_ADRESSE; } function standortAdresseLaden() { @@ -3720,8 +3725,73 @@ function istNachtZeit(ts) { } /* D3: Die Strecke führt die Zeile, das Datum steht darunter. Umbruch nur zwischen den Orten, nie am Pfeil. */ +/* Ein bereits aufgeloester Ort aus dem Zwischenspeicher - OHNE Netzabruf. + + Die Einzelfahrt loest Koordinaten bei Bedarf auf und legt das Ergebnis im + selben Fach ab wie die Standortansicht. Was einmal dort steht, kann die + Liste umsonst mitbenutzen: kein zusaetzlicher Abruf, keine Wartezeit, kein + Flackern beim Zeichnen. + + Bewusst NUR aus dem Speicher. Ein Abruf je Zeile waere genau der + Vorratsabruf, um dessen Unterlassung Nominatim in seinen + Nutzungsbedingungen bittet - bei zweihundert Fahrten waeren das + vierhundert Anfragen fuer einen Blick auf die Liste. + + Schluessel wie bei der Aufloesung: drei Nachkommastellen (~110 m). */ +function ortAusCache(lat, lon) { + if (lat == null || lon == null) return null; + const wert = geoCacheLesen()["rev:" + lat.toFixed(3) + "," + lon.toFixed(3)]; + return typeof wert === "string" ? wert : null; +} + +/* Der Ortsname allein, fuer die Fahrtenliste. + + In der Liste steht eine Fahrt in einer Zeile neben Datum, Art, Strecke und + Verbrauch. Zwei volle Anschriften passen dort nicht - "Schottenau, 85072 + Eichstaett -> Am Anger 7a, 85111 Adelschlag" ist laenger als der Rest der + Zeile zusammen. Auf der Einzelfahrt bleibt die vollstaendige Anschrift. + + Der Ort wird beim Aufloesen SEPARAT gemerkt (Schluessel "ort:"), nicht + nachtraeglich aus der Anschrift geschnitten. Das Zerlegen der eigenen + Ausgabe waere fast immer richtig und in einem Fall falsch: faellt Nominatim + auf display_name zurueck, steht hinter dem letzten Komma das LAND. Dann + stuende in der Liste "Deutschland -> Deutschland". */ +/* Der Ortsname aus einer bereits gespeicherten Anschrift. + + Rueckfall fuer alles, was VOR dem Merken des Ortsnamens aufgeloest wurde: + dort liegt nur die volle Anschrift, und ein neuer Netzabruf, nur um den Ort + zu erfahren, waere Verschwendung. + + Geschnitten wird ausschliesslich, wenn hinter dem letzten Komma eine + Postleitzahl steht - unser eigenes Format ist ", ". + Faellt Nominatim auf display_name zurueck, fehlt die PLZ und hinter dem + letzten Komma stuende das LAND; dann lieber die volle Anschrift zeigen als + "Deutschland". */ +function stadtAusAnschrift(anschrift) { + if (!anschrift) return null; + const teil = String(anschrift).split(", ").pop().trim(); + const treffer = teil.match(/^\d{4,5}\s+(.+)$/); + return treffer ? treffer[1].trim() : null; +} + +function stadtAusCache(lat, lon) { + if (lat == null || lon == null) return null; + const wert = geoCacheLesen()["ort:" + lat.toFixed(3) + "," + lon.toFixed(3)]; + return typeof wert === "string" && wert ? wert : null; +} + function strecke(t) { - const a = t.start_address, b = t.end_address; + // Ortsname bevorzugt; ohne ihn die volle Anschrift, damit eine vor dieser + // Aenderung aufgeloeste Fahrt nicht ploetzlich wieder ortlos dasteht. + const ortsname = (adresse, lat, lon) => { + if (adresse) return adresse; + const gemerkt = stadtAusCache(lat, lon); + if (gemerkt) return gemerkt; + const voll = ortAusCache(lat, lon); + return stadtAusAnschrift(voll) || voll; + }; + const a = ortsname(t.start_address, t.start_lat, t.start_lon); + const b = ortsname(t.end_address, t.end_lat, t.end_lon); if (!a && !b) return "Fahrt ohne Ortsangabe"; if (!b) return esc(a); if (!a) return esc(b); diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index 9e922f0..0bcc0e0 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.20", + "version": "2026.9.1.23", "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"],