Setup-Menü: Sensorkennung per Mehrheitsentscheid statt nächstem Nachbarn; Ellipsis entfernt; Filter/Tanken-Fixes

- entitaetIdKurz(): Präfix per Stimmenmehrheit über alle zugeordneten Sensoren statt
  des einzelnen nächsten Nachbarn - verhindert Über-Kürzung durch zufällig geteilte
  Namensteile zwischen verwandten Feldern (can_fuel_volume->fuel_volume,
  oil_change_due->due waren falsch).
- .setup-feld-hinweis: Ellipsis-Kürzung entfernt, voller Sensorname sichtbar.
- entitaetKandidaten(): "Nur passende Sensoren anzeigen" filtert jetzt auch nach
  device_class für Datum/Timestamp-Felder, nicht nur nach Domain/Einheit.
- tankFelder(): Kilometerstand/Getankte Liter in .mitEinheit gewrappt (wie alle
  anderen km/l-Felder im Rest der App).
- distanzVorschlag(): negative Vorschläge (fehlerhafte km-Basis eines vorherigen
  Tankvorgangs) werden nicht mehr angezeigt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 11:15:12 +02:00
parent 4f85a179cf
commit d1cc742a59
4 changed files with 124 additions and 29 deletions
+67
View File
@@ -4272,6 +4272,49 @@ and their `_STRECKE_SENSOR` counterparts (`oil_change_distance`/`inspection_dist
other specifically and reduce to `"due"`/`"distance"` - each field lands next to its true sibling, not just
"some" shared prefix.
**Follow-up (`.9`), two more owner-reported items on the same session:**
1. **"Nur passende Sensoren anzeigen" only hard-filtered by domain/unit, not by the "Datum" output shown
next to a field's headline** (`feldEinheitAnzeige()`). Fields with `device_classes: ["date",
"timestamp"]` and no `units` (the two Ölwechsel/Inspektion date roles) fell through both the domain
check (only restricts to `sensor` domain, which is nearly every entity) and the unit check (empty
`units` array skips it entirely) - so with the filter ON, every `sensor.*` entity in the whole instance
was offered, not just date/timestamp ones. `entitaetKandidaten()` now mirrors `feldEinheitAnzeige()`'s
own decision exactly: when a field has no `units` but its `device_classes` includes `date`/`timestamp`,
a candidate must itself have `device_class` `date` or `timestamp` to pass the hard filter. Verified live
by reading the actual rendered candidate list for "Nächster Ölwechsel (Datum)" via the shadow DOM and
cross-checking each of the 14 results' real `device_class` against `hass.states` - all 14 were genuine
`date`/`timestamp` sensors, zero false positives.
2. **`Neuer Tankvorgang` (`tankFelder()`): "Kilometerstand" and "Getankte Liter" were the only two fields
in the form without the `.mitEinheit` wrapper** every other km/€/l field in the app already uses (see
e.g. the Wartungsplan "Kilometerstand"/"Kosten" fields, `vSbuch()`) - so they rendered at the browser's
default, much wider intrinsic input size instead of the app's usual fixed compact width, and without a
unit suffix. Wrapped both in `.mitEinheit` (`km`/`l`), matching the existing convention instead of
introducing new CSS.
3. **Real bug: "Gefahrene Distanz" showed `-98` when opening a new Tankvorgang.** `distanzVorschlag()`
subtracts the last recorded fill's `odometer_km` from the live odometer with no floor - live-inspected
the actual data in `audi_ha_test` and found the most recent stored fill (`f-c7b0e818451e`, `source:
"auto"`, 2026-08-27T17:37) carries `odometer_km: 21400`, while the vehicle's current live odometer
(`sensor.audi_dashboard_fahrzeugstatus`'s `km`) reads `21302` - lower than that fill's own recorded
basis, so the subtraction went negative (`21302 - 21400 = -98`, exactly the reported number). Left the
underlying record itself untouched - unclear whether it's a genuine real-vehicle detection or leftover
test-data noise, and either way it's the owner's own trip/tank history, not something to delete
unilaterally. Fixed the display symptom instead: `distanzVorschlag()` now returns `""` (no suggestion)
rather than a negative number - a negative "distance driven" is never meaningful regardless of why the
underlying data disagrees. **Worth the owner's own look**: that stray fill's `odometer_km` (21400)
exceeding the current live reading (21302) suggests either a bad detection or a since-corrected odometer
somewhere in this test container's history - if it recurs on the real instance, worth checking there too.
4. Also handled the owner's report "there is still the Text with the round collums in the Sensor Page"
(referring to the `.7`/`.8` ellipsis fix) - re-verified directly against the live served CSS and by
scanning every text node and placeholder in the rendered Setup popup's DOM: the ellipsis truncation is
genuinely gone server-side, nothing found. Most likely a stale cached browser tab on the owner's side
(this project has hit that exact class of issue repeatedly - see section N) rather than a remaining code
defect; flagged to the owner rather than guessed around further.
Verified: `node --check` clean, manifest bumped to `2026.8.28.9`, `audi_ha_test` restarted and confirmed
clean via log; all three fixes re-verified live in the browser (Tanken form screenshot showing `0` instead
of `-98` and the shrunk `Kilometerstand`/`Getankte Liter` fields; Setup candidate-list device_class check
above).
Verified live in `audi_ha_test` at every step (`.6` then `.7`), each time reading the actual rendered Setup
popup fresh after a real restart (not a stale tab - confirmed via `performance.getEntriesByType('resource')`
showing the new `?v=` served with a nonzero transfer size each time). Final state (`.7`), all 20 single-value
@@ -4281,6 +4324,30 @@ fields checked in one pass: every FMM003 role shows its bare signal name
from either source. `node --check` clean at each step. Manifest `2026.8.28.5``.6``.7`, `audi_ha_test`
restarted and confirmed clean via log each time.
**`.7`'s "due"/"distance" reduction was itself wrong - owner caught it (`.8`): "Tankvolumen only shows
'fuel_volume' but the Sensor is 'can_fuel_volume'... nächster Ölwechsel is 'due' but shall be
'oil_change_due'."** `.7`'s per-entity nearest-single-relative algorithm still over-stripped: two fields from
the SAME sensor source can themselves share a namepart that has nothing to do with the installation prefix
(`oil_change_due`/`oil_change_distance` share `"oil_change_"`; `can_fuel_volume` shares `"can_"` with another
CAN-sourced sensor) - the nearest relative for a given entity is exactly the one most likely to share extra,
misleading structure with it, so picking the single nearest relative's prefix was the wrong metric all along,
not just an edge case. Replaced with a majority vote: every OTHER mapped entity casts one "vote" for whatever
prefix it shares with the entity being displayed (via the existing `praefixPaar()`), and the prefix with the
most votes wins (ties favor the shorter/safer one). The true installation prefix wins because an entity
shares it with nearly every other sensor from the same source (many votes); an incidental shared namepart
like `"oil_change_"` is shared with only the one sibling field (one vote) - so the majority is structurally
biased toward the correct, install-level prefix regardless of how many simultaneous sensor sources exist.
Also removed the CSS ellipsis truncation on the sensor-identity hint (`.setup-feld-hinweis` in
`audi-dashboard.css` had `white-space:nowrap;overflow:hidden;text-overflow:ellipsis`) per the owner's
explicit "remove the round collum (...)" - full identifiers like `can_fuel_volume` now show in full
(wrapping within the existing `max-width:55%` instead of truncating). Verified live in `audi_ha_test` at
`.8`: every field re-checked against the real Setup data - `can_fuel_volume`, `oil_change_due`/
`oil_change_distance`, `inspection_due`/`inspection_distance` all now show their full, correct suffix; the
FMM003 cluster (`external_power_voltage`, `can_ambient_air_temperature`, ...) and the `audi_rs_4_avant_`
cluster (`total_vehicle_mileage_read_from_can`, `front_left_door`, ...) both still strip correctly - the
majority-vote fix didn't regress the two-simultaneous-sources case `.7` was built to solve. `node --check`
clean, manifest bumped to `2026.8.28.8`, `audi_ha_test` restarted and confirmed clean via log.
---
## Working conventions (observed — keep them)
@@ -2759,7 +2759,15 @@ function entitaetKandidaten(feldKey, suchtext, aktuellerWert) {
if (e.id === aktuellerWert) return true;
if (setupNurPassend) {
if (feld.domains && feld.domains.length && !feld.domains.includes(e.domain)) return false;
if (feld.units && feld.units.length && !feld.units.includes(e.unit)) return false;
if (feld.units && feld.units.length) {
if (!feld.units.includes(e.unit)) return false;
} else if (feld.device_classes && (feld.device_classes.includes("date") || feld.device_classes.includes("timestamp"))) {
// Kein Einheiten-Feld, aber "Datum" ist der angezeigte Output
// (feldEinheitAnzeige()) - dieselbe Bedingung hier gespiegelt, damit
// "Nur passende Sensoren anzeigen" wirklich nur Sensoren mit
// passendem Output zeigt, nicht jeden beliebigen sensor.*.
if (e.deviceClass !== "date" && e.deviceClass !== "timestamp") return false;
}
}
if (text && !(e.id.toLowerCase().includes(text) || e.name.toLowerCase().includes(text))) return false;
return true;
@@ -3517,7 +3525,13 @@ function distanzVorschlag(aktuellerKm) {
if (aktuellerKm === "" || aktuellerKm == null || !FILLS.length) return "";
const letzte = FILLS.reduce((a, b) => (new Date(b.ts) > new Date(a.ts) ? b : a));
if (letzte.odometer_km == null) return "";
return Math.round((aktuellerKm - letzte.odometer_km) * 10) / 10;
const distanz = Math.round((aktuellerKm - letzte.odometer_km) * 10) / 10;
// Ein negativer Wert ist nie eine gefahrene Distanz - kann bei einer
// fehlerhaft/verzögert erfassten Kilometerstand-Basis des letzten
// Tankvorgangs vorkommen (z. B. ein "auto" erkannter Vorgang, dessen
// km-Basis über einen späteren, korrigierten/niedrigeren Kilometerstand
// hinausragt). Lieber kein Vorschlag als ein irreführender negativer.
return distanz < 0 ? "" : distanz;
}
function tankFelder(praefix, f) {
@@ -3541,12 +3555,14 @@ function tankFelder(praefix, f) {
return `
<div class="feld"><label>Datum</label><input type="date" id="${praefix}Datum" value="${datum}"></div>
<div class="feld"><label>Uhrzeit</label><input type="time" id="${praefix}Zeit" value="${zeit}"></div>
<div class="feld"><label>Kilometerstand</label><input type="number" id="${praefix}Km" value="${km}"></div>
<div class="feld"><label>Kilometerstand</label>
<span class="mitEinheit"><input type="number" id="${praefix}Km" value="${km}"><span>km</span></span></div>
<div class="feld"><label>Gefahrene Distanz</label>
<span class="mitEinheit"><input type="number" step="1" id="${praefix}Distanz" value="${distanz}" placeholder="0"><span>km</span></span></div>
<div class="feld"><label>Kraftstoff</label>
<select id="${praefix}Kraftstoff">${KRAFTSTOFFSORTEN.map((k) => `<option ${k === kraftstoff ? "selected" : ""}>${k}</option>`).join("")}</select></div>
<div class="feld"><label>Getankte Liter</label><input type="number" step="0.01" id="${praefix}Liter" data-tankpreisfeld="${praefix}" value="${liter}" placeholder="0,00"></div>
<div class="feld"><label>Getankte Liter</label>
<span class="mitEinheit"><input type="number" step="0.01" id="${praefix}Liter" data-tankpreisfeld="${praefix}" value="${liter}" placeholder="0,00"><span>l</span></span></div>
<div class="feld"><label>Kosten (real, nach Rabatt)</label>
<span class="mitEinheit"><input type="number" step="0.01" id="${praefix}Kosten" data-tankpreisfeld="${praefix}" value="${kosten}" placeholder="0,00"><span></span></span></div>
${CAR.smartdeal.aktiv ? `<div class="feld"><label>SmartDeal-Ersparnis</label>
@@ -4082,35 +4098,47 @@ function setupAndereIdsOhneDomain(ausser) {
return ids.map((id) => id.includes(".") ? id.split(".").slice(1).join(".") : id);
}
/* Die Entity-ID ohne Domain und ohne das installationsspezifische Präfix,
das sie mit ihrem NÄCHSTEN Verwandten unter den übrigen zugeordneten
Sensoren teilt (dem, mit dem sie das längste gemeinsame Präfix hat) - die
tatsächliche, stabile Kennung des zugeordneten Sensors, unabhängig davon,
wie Geräte-Slug und Integrationsinstanz beim jeweiligen Nutzer heißen
(z. B. "testcar_b9_fmm003_testintegratoin_" oder "audi_rs_4_avant_").
/* Die Entity-ID ohne Domain und ohne das installationsspezifische Präfix -
die tatsächliche, stabile Kennung des zugeordneten Sensors, unabhängig
davon, wie Geräte-Slug und Integrationsinstanz beim jeweiligen Nutzer
heißen (z. B. "testcar_b9_fmm003_testintegratoin_" oder
"audi_rs_4_avant_").
Bewusst PRO ENTITÄT der beste Nachbar statt eines einzigen, für alle
Felder gemeinsamen Präfixes (frühere Fassung) - eine reale Installation
hat oft MEHRERE Sensorquellen gleichzeitig (hier: FMM003 für Fahrt-/
Tank-/Batteriewerte, eine zweite Integration für Türen/Fenster/
Service-Termine, per "Nur passende Sensoren anzeigen" beide gleichzeitig
vorgeschlagen) - keine ist objektiv "die Mehrheit". Ein einziges globales
Präfix (egal ob per fortlaufender Verengung oder per Mehrheitsentscheid)
kann immer nur EINE Quelle richtig kürzen und zeigt die Sensoren der
anderen ungekürzt - genau das hat der Nutzer live beobachtet
("audi_rs_4_avant_" gewann die Abstimmung, alle FMM003-Felder blieben
unangetastet). Mit dem nächsten Verwandten statt einem globalen Sieger
kürzt jede Rolle unabhängig von den anderen richtig, egal wie viele
verschiedene Quellen gleichzeitig im Spiel sind. Findet sich kein
Verwandter mit gemeinsamem Präfix, bleibt die volle domain-freie ID
stehen statt einer geratenen, möglicherweise falschen Kürzung. */
Das Präfix ist NICHT das mit dem einzelnen nächsten Verwandten geteilte
(frühere Fassung) - das kürzte in der Praxis zu viel: zwei Felder
derselben Sensorquelle können selbst wieder ein gemeinsames Namens-
Teilstück haben, das nichts mit der Installation zu tun hat (z. B. teilen
sich "oil_change_due" und "oil_change_distance" das Wortstück
"oil_change_", "can_fuel_volume" teilt sich "can_" mit einem anderen
CAN-Sensor) - genau das hat der Nutzer live beobachtet: "can_fuel_volume"
wurde zu bloß "fuel_volume", "oil_change_due" zu bloß "due". Stattdessen
zählt jedes Präfix, das eine Entität mit irgendeiner ANDEREN teilt, als
eine Stimme; das Präfix mit den MEISTEN Stimmen gewinnt. Das echte
Installationspräfix teilt sich die Entität mit fast allen anderen
Sensoren ihrer Quelle (viele Stimmen), ein zufällig geteiltes
Namens-Teilstück nur mit dem einen verwandten Feld (eine Stimme) - die
Mehrheit entscheidet also richtig, ohne einen einzelnen "nächsten"
Nachbarn zu bevorzugen. Bei Gleichstand gewinnt das kürzere Präfix
(vorsichtiger - weniger Kürzung im Zweifel). Mehrere gleichzeitige
Sensorquellen (z. B. FMM003 und eine zweite, "audi_rs_4_avant_"-
präfixte Integration) bleiben dabei weiterhin unabhängig voneinander
korrekt gekürzt, da die Stimmen pro Entität und nicht global gezählt
werden. Findet sich kein Verwandter mit gemeinsamem Präfix, bleibt die
volle domain-freie ID stehen statt einer geratenen, möglicherweise
falschen Kürzung. */
function entitaetIdKurz(entityId) {
if (!entityId) return null;
const ohneDomain = entityId.includes(".") ? entityId.split(".").slice(1).join(".") : entityId;
let bestesPraefix = null, besteLaenge = 0;
const stimmen = new Map();
for (const andere of setupAndereIdsOhneDomain(entityId)) {
const kandidat = praefixPaar(ohneDomain, andere);
if (kandidat && kandidat.length > besteLaenge) { bestesPraefix = kandidat; besteLaenge = kandidat.length; }
if (!kandidat) continue;
stimmen.set(kandidat, (stimmen.get(kandidat) || 0) + 1);
}
let bestesPraefix = null, besteStimmen = 0;
for (const [kandidat, anzahl] of stimmen) {
const besser = anzahl > besteStimmen || (anzahl === besteStimmen && bestesPraefix && kandidat.length < bestesPraefix.length);
if (besser) { bestesPraefix = kandidat; besteStimmen = anzahl; }
}
return bestesPraefix ? ohneDomain.slice(bestesPraefix.length) : ohneDomain;
}
@@ -1265,7 +1265,7 @@ button.leaf:active { background: var(--tile-2); }
.setup-feld-einheit { color: var(--fg3); font-size: 12.5px; }
.setup-feld-hinweis {
font-size: 12.5px; color: var(--fg2); line-height: 1.4; text-align: right;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 55%;
max-width: 55%;
}
.setup-unterfelder { display: flex; flex-direction: column; gap: 12px; }
.setup-unterfeld { display: flex; flex-direction: column; gap: 4px; }
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.8.28.7",
"version": "2026.8.28.9",
"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"],