Setup-Menue: Sensorkennung per naechstem Verwandten statt globalem Praefix

Zwei weitere Korrekturrunden, beide vom Nutzer live erkannt:

- .6: paarweise Abstimmung statt fortlaufender Verengung - robuster gegen
  einen einzelnen Ausreisser, aber grundsaetzlich noch falsch.
- .7: die eigentliche Ursache war, dass diese Installation zwei etwa
  gleich grosse Sensorquellen gleichzeitig hat (9 FMM003-Rollen, 18 Rollen
  einer zweiten Integration mit "audi_rs_4_avant_"-Praefix) - ein
  EINZELNES globales "Sieger"-Praefix kann immer nur eine der beiden
  Quellen richtig kuerzen. entitaetIdKurz() sucht jetzt pro Feld den
  naechsten Verwandten unter allen zugeordneten Sensoren und kuerzt nur
  dessen gemeinsames Praefix - unabhaengig von Anzahl und Groesse der
  Quellen.

Diesmal vor dem Deploy anhand der ECHTEN sensor.audi_dashboard_entitaeten-
Zuordnung durchgerechnet statt mit erfundenen Test-IDs (das hatte den
.6-Fehler faelschlich als behoben erscheinen lassen). Version 2026.8.28.7,
live verifiziert: alle 20 Einzelwert-Felder zeigen die richtige, kurze
Kennung, keine installationsspezifischen Reste aus beiden Quellen mehr.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 10:54:16 +02:00
parent af64fc39e3
commit 4f85a179cf
3 changed files with 83 additions and 38 deletions
@@ -4061,53 +4061,58 @@ function sheetMarkup() {
/* ------------------------------------------------------------ Setup-Popup */
const LUPE_SVG = `<svg viewBox="0 0 20 20" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><circle cx="8.5" cy="8.5" r="6"/><path d="m17 17-4.3-4.3"/></svg>`;
/* Das gemeinsame Präfix aller aktuell zugeordneten Sensoren (domain-frei),
bis zum letzten gemeinsamen "_" - z. B. "testcar_b9_fmm003_testintegratoin_".
NICHT hart auf "fmm003_" verdrahtet: sowohl der Geräte-Slug
("testcar_b9") als auch der Instanzname der Integration
("testintegratoin") sind installationsspezifisch, vom Nutzer beim
Einrichten seines eigenen flespi-Kanals selbst vergeben - bei ihm heißt
das anders. Nur was WIRKLICH von allen zugeordneten Sensoren geteilt
wird, ist reines Rauschen; alles danach ist die eigentliche, stabile
Signalbezeichnung. Mit weniger als zwei zugeordneten Sensoren (nichts zum
Vergleichen) oder wenn Sensoren aus zwei erkennbar verschiedenen Quellen
kommen (kein gemeinsames Präfix), liefert das null - dann zeigt
entitaetIdKurz() lieber die volle, ungekürzte ID als eine geratene,
möglicherweise falsche Kürzung. */
function setupGemeinsamesPraefix() {
/* Gemeinsames Präfix zweier domain-freier IDs, bis zum letzten gemeinsamen
"_" - oder null, wenn sie nicht einmal ein volles Segment teilen. */
function praefixPaar(a, b) {
let i = 0;
while (i < a.length && i < b.length && a[i].toLowerCase() === b[i].toLowerCase()) i++;
const idxUnter = a.slice(0, i).lastIndexOf("_");
return idxUnter > 0 ? a.slice(0, idxUnter + 1) : null;
}
/* Alle aktuell im Setup zugeordneten Entity-IDs (domain-frei), außer der
übergebenen selbst - Vergleichsbasis für entitaetIdKurz() unten. */
function setupAndereIdsOhneDomain(ausser) {
const ids = [];
for (const feld of setupKatalog()) {
const wert = setupZuordnung[feld.key];
if (feld.liste) { for (const w of (wert || [])) if (w) ids.push(w); }
else if (wert) ids.push(wert);
if (feld.liste) { for (const w of (wert || [])) if (w && w !== ausser) ids.push(w); }
else if (wert && wert !== ausser) ids.push(wert);
}
const ohneDomain = ids.map((id) => id.includes(".") ? id.split(".").slice(1).join(".") : id);
if (ohneDomain.length < 2) return null;
let praefix = ohneDomain[0];
for (const s of ohneDomain.slice(1)) {
let i = 0;
while (i < praefix.length && i < s.length && praefix[i].toLowerCase() === s[i].toLowerCase()) i++;
praefix = praefix.slice(0, i);
if (!praefix) return null;
}
const idxUnter = praefix.lastIndexOf("_");
return idxUnter > 0 ? praefix.slice(0, idxUnter + 1) : null;
return ids.map((id) => id.includes(".") ? id.split(".").slice(1).join(".") : id);
}
/* Die Entity-ID ohne Domain und ohne das gemeinsame, installationsspezifische
Präfix (siehe setupGemeinsamesPraefix()) - die tatsächliche, stabile
Kennung des zugeordneten Sensors, unabhängig davon, wie Geräte-Slug und
Integrationsinstanz beim jeweiligen Nutzer heißen. Passt das gemeinsame
Präfix nicht auf DIESE Entität (z. B. eine einzelne Entität aus einer
anderen Quelle als der Rest), bleibt die volle domain-freie ID stehen. */
/* 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_").
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. */
function entitaetIdKurz(entityId) {
if (!entityId) return null;
const ohneDomain = entityId.includes(".") ? entityId.split(".").slice(1).join(".") : entityId;
const praefix = setupGemeinsamesPraefix();
if (praefix && ohneDomain.toLowerCase().startsWith(praefix.toLowerCase())) {
return ohneDomain.slice(praefix.length);
let bestesPraefix = null, besteLaenge = 0;
for (const andere of setupAndereIdsOhneDomain(entityId)) {
const kandidat = praefixPaar(ohneDomain, andere);
if (kandidat && kandidat.length > besteLaenge) { bestesPraefix = kandidat; besteLaenge = kandidat.length; }
}
return ohneDomain;
return bestesPraefix ? ohneDomain.slice(bestesPraefix.length) : ohneDomain;
}
/* Die Art des erwarteten Werts, als kurzes Klammer-Etikett direkt neben der
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.8.28.5",
"version": "2026.8.28.7",
"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"],