ddcd9396b02de0187982fc98c63d262eb427eedb
13 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
863284e540 |
Hauptuntersuchung: Ableitung aus Erstzulassung und Wartungsplan, drittes Feld entfernt
Gemeldet: Wartungsplan leer, Erstzulassung 01.03.2025, erwartet 01.03.2028 -
gezeigt wurden drei verschiedene Antworten (Uebersicht 01.08.2026, Mein Audi
"August 2026", Service "kein Eintrag im Wartungsplan").
Ursache war das Profilfeld hauptuntersuchung_faellig ("08/2026"), das die
Ableitung ueberstimmte, in keiner Oberflaeche ein Eingabefeld hatte und im
Backend kein Schema. Drei Bildschirme verarbeiteten denselben Wert
verschieden. Das Feld ist samt Lese- und Schreibweg entfernt; es gibt jetzt
zwei Quellen: Wartungsplan + 24 Monate, sonst Erstzulassung + 36 Monate.
Dabei mitbehoben:
* Die Service-Kachel verschluckte abgeleitete Termine, sobald kein
Wartungsplan-Eintrag dahinterstand.
* monatePlus() in der App verlor einen Tag ueber die Zeitumstellung (drei von
fuenf gemessenen Faellen) - betraf Oelwechsel- und Inspektionsprognose
genauso. Das Panel rechnete dort seit jeher richtig.
* Die App verlangte fuer jeden Wartungsplan-Eintrag eine Kilometerangabe. Eine
Hauptuntersuchung ist rein datumsbasiert; ohne km waere der erste HU-Eintrag
ignoriert worden. Ausserdem nahm das Panel den ersten Treffer der Liste, die
App den juengsten - beide nehmen jetzt den juengsten.
* Monatsgenauigkeit ("03/2025") bleibt auf beiden Seiten erhalten.
Neu: paritaet_hu.test.ts vergleicht die App gegen die aus der Panel-Quelle
herausgeschnittenen Originalfunktionen, 15 Faelle. Gesamt 292 App-Tests, 73
Python-Tests mit 231 Untertests, 0 Tracebacks. Live an der Testinstanz auf
allen drei gemeldeten Bildschirmen geprueft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9ac69cdf44 |
Eine Neuschreibung ist kein Wechsel - Wartezeit startet nicht mehr neu
Home Assistant schreibt denselben Zustandswert auch neu, wenn sich nur ein Attribut geruehrt hat. Am 04.09.2026 gezaehlt: von 22 Zustandszeilen der Zuendung waren nur 6 echte Wechsel, die uebrigen 16 Neuschreibungen - und alle 6 Wechsel lagen ausserhalb der Fahrten. Das Zuendungssignal ist unterwegs stabil. Der Beobachter unterschied das nicht. Fiel eine Neuschreibung von "aus" in die laufende Wartezeit, brach Zweig 2 die wartende Bestaetigung ab und begann die 15 Minuten von vorn. Am 04.09. ist es nicht passiert - die Neuschreibungen lagen hinter dem Fenster -, aber das Loch war da. _signalwechsel() bekommt jetzt mit, ob es ein echter Wechsel war. Eine Neuschreibung, waehrend fuer diese Fahrt schon eine Bestaetigung wartet, laesst sie stehen. Bewusst NICHT als Filter ganz oben: _spaetes_aus() verlaesst sich darauf, ein Zuendungs-Aus auch als off -> off zu sehen - daran haengt das Nachtragen vorlaeufiger Enden. Die Pruefung sitzt nur in Zweig 2, und ein eigener Test belegt, dass der andere Weg weiter offen ist. 21 Tests (von 18), Gegenprobe mit ausgeschalteter Pruefung scheitert wie erwartet an einem. py_compile sauber, audi_ha_test auf 2026.9.4.25 ohne Traceback. AGENTS.md haelt ausserdem fest: die Beschreibung des Beenden-Knopfes war zu weit gefasst (die Korrektur zieht nur nach hinten, nie nach vorn), und der flespi-Cache laesst sich ueber DELETE auf die Einstellung zum Neulesen zwingen - mit dem Haken, dass dabei ausstehende Werte verloren gingen. |
||
|
|
242312441d |
Pausenregel rechnet in Geraetezeit - zwei Fahrten bleiben zwei
Am 04.09.2026 war der Dongle von 18:25 bis 23:36 ohne Verbindung. Als er sich meldete, kamen das Zuendungs-Aus der ersten Fahrt und das Ein der zweiten in DERSELBEN Sekunde bei Home Assistant an - ihre Geraetezeiten lagen 5 Stunden 12 auseinander (18:23:32 und 23:35:43). Die Pausenregel mass an der Ankunft: 0 Sekunden, also dieselbe Fahrt. Ergebnis war ein Eintrag von 18:10 bis 23:42 ueber 5 h 33 ohne Strecke, und die Fahrt um 23:30 gab es gar nicht. Zwei Stellen im Code trugen dazu bei: - zuendung_geaendert() rief bedingungslos warte_ende_ab_abbrechen(); das "an" loeschte damit das wartende Ende des "aus" derselben Sekunde. - Der Fall "an, waehrend eine Fahrt laeuft" fiel wortlos durch alle drei Zweige von _signalwechsel(). Dazu steht seit dem 02.09. ein Kommentar im Code - damals wurde eine Sperre eingebaut, die die Reihenfolge richtig stellt, aber nicht den Ausgang. Neu entscheidet _zuendung_kehrt_zurueck() in Geraetezeit. Der Koordinator fuehrt mit, welches Ende gerade seine Pausenzeit absitzt (ende_wartet). Liegt zwischen dem echten Ende und dem neuen "an" mehr als die Pausenzeit, wird die alte Fahrt auf ihr berechnetes Ende geschlossen und eine neue begonnen. Gemessen wird ab dem echten Ende, nicht ab dem Signal. Ohne wartendes Ende passiert nichts - ein Schnitt auf Verdacht waere schlimmer als eine zu lange Fahrt. Das behebt die Verspaetung nicht, es macht sie harmlos. Die Tests mussten zwei Dinge trennen, die vorher eine Zahl waren: PAUSE_S (900, das Fenster der Regel) und WARTEN (0,15 s, die Wanduhrzeit des Tests). 18 Tests in test_zuendungspause.py (von 14), py_compile sauber, audi_ha_test auf 2026.9.4.24 ohne Traceback. Gegenprobe am alten Stand: 2 der 4 neuen Tests scheitern dort, mit der Korrektur laufen alle 18. |
||
|
|
8557bfe230 |
Markenerkennung: Grenzen gemessen, Summenzeile im Kopf ist keine Marke
Auf die Frage, ob Belege einheitlich gestaltet sind: nein. Die Erkennung haengt an drei Annahmen - Marke in den ersten acht Zeilen, Marke in der festen Liste, Marke ueberhaupt vorhanden. Alle drei sind jetzt gemessen und in Tests festgehalten. Dabei ein echter Defekt gefunden: "Total" ist Kopfmarke UND Summenwort. Ein Beleg mit Betrag in den ersten acht Zeilen lieferte faelschlich die Marke "Total" (an einem echten Beleg reproduziert). _betragszeile() filtert Summenzeilen jetzt aus dem Kopf - verlangt werden beide Kennzeichen zugleich, Summenwort und Geldangabe, damit "SHELL TANKSTELLE, 6450 SOELDEN" nicht mitfaellt. Gegenprobe: ein echter "TOTAL TANKSTELLE"-Kopf ergibt weiter Total. Der Ausfall beschaedigt nichts: mit unbekannter Marke im Kopf bleiben Liter, Betrag, Rabatt und Anschrift unveraendert richtig, nur die Marke fehlt. Parser-Suite 17 Tests (von 11), alle zwoelf abgelegten Belege liefern weiter Shell, audi_ha_test auf 2026.9.4.22 ohne Traceback. |
||
|
|
bdbdb4a010 |
Tankstellenmarke als eigenes Feld, Belegfelder in der App richtig zugeordnet
Die Marke steht auf jedem Beleg, war aber nirgends gespeichert: der Parser erkennt sie seit dem 16.08.2026, faltete sie aber nur in station_name. Fuer jeden Beleg, den eine aeltere Fassung eingelesen hat, blieb dort der Betreibername stehen und die Marke war danach nicht mehr zu holen. - shell_beleg_parser: station_brand als eigenes Feld aus beiden Parserwegen, dazu marke_aus_text()/marke_aus_pdf() - sie lesen nur den Belegkopf und haengen nicht am vollstaendigen Einlesen. - belege.marken_nachtragen(): traegt die Marke einmal beim Start aus der abgelegten Belegdatei nach. Nur wo das Feld gar nicht vorkommt und die Datei liegt; ein einmal eingetragenes Feld wird nie ueberschrieben. - Panel und App: die gespeicherte Marke geht vor, geraten wird nur, wo keine da ist. Ein Teil, der fuer sich genommen eine Kopfmarke ist, taugt zudem nie als Ort. Dazu Punkt 5 der Meldung vom 04.09.2026: ein eingelesener Beleg fuellte nur Datum und Uhrzeit. Der Parser las ihn immer vollstaendig - die App fragte liter/kosten/station/ersparnis/kraftstoff ab, veroeffentlicht werden aber die Namen des Belegs (liters/fuel_total_eur/station_name/discount/fuel_type). Uebereingestimmt hat allein ts. Die Zuordnung steht jetzt als belegFormularwerte() in daten/belegentwurf.ts und wird gegen die echte Nutzlast dieses Belegs geprueft. tsc sauber, 258 Tests (die drei neuen Marken-Tests gegen den alten Stand als scheiternd nachgewiesen), vite build sauber, Panel als Modul geparst, Parser-Suite 11 Tests gegen elf echte Belege. Panel und App liefern fuer elf Faelle byteweise dasselbe. Live in audi_ha_test auf 2026.9.4.21 ohne Traceback: Uebersicht "Shell, Nuernberger Str., Ansbach", Einzelbeleg zweizeilig mit Marke, und der echte geteilte Beleg fuellt durchs Backend das ganze Formular. |
||
|
|
7f386d19ea |
Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt (2026.9.4.1)
Das Fahrtfenster stand seit dem 01.09. in der Gerätezeit, der Verlauf war nach Ankunftszeit sortiert. Zwei Uhren, und das Gerät puffert: was verspätet ankam, fiel aus einem Fenster heraus, in dem es der Sache nach lag. Am 03.09. hat das eine Fahrt gekostet - 26 von 48 GPS-Punkten und 3,0 statt 7 km. verlauf_lesen() stempelt jeden Punkt jetzt mit der Gerätezeit seines Datensatzes. Eine Stelle, an der alle Verbraucher vorbeikommen. Dazu zwei Riegel aus derselben Untersuchung: ein Rückschritt von 43 Metern ist keine Zählerrücksetzung mehr (aus 87 Metern wurden vorher 13,7 km), und das Fahrtende wird auf das letzte Lebenszeichen des Geräts geklemmt - der Nachlauf ist eine Rechnung, kein Messwert. Am echten Recorder nachgerechnet: Tacho 3,0 -> 7,0 km, GNSS 2,952 -> 6,877 km, Route 26 -> 47 Punkte, Ende 23:21:57 -> 23:17:48. Beim ersten Lauf haben zwei Fahrten eine Strecke bekommen, die vorher gar keine hatten. Nicht das Gerät war schuld: es hat lückenlos aufgezeichnet. Die zehn Minuten Funkstille waren eine offene, aber tote TCP-Sitzung (flespi-Log: 703 s, 16 Nachrichten) - kein Funkloch. Begründung und die Empfehlung fürs Gerät stehen in AGENTS.md, Abschnitt CB. Dazu die neun Paritätsbefunde, alle Richtung Panel gelöst - darunter ein unmaskierter Punkt in beiden Codebasen, der aus "vor 3 Tage" ein "vor 3 Tag" machte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6ec31b5275 |
Audit, zweite Runde: der Knopf schrieb ein erfundenes Ende (2026.9.3.14)
_letztes_lebenszeichen() fiel auf "jetzt" zurueck, sobald verlauf_lesen() eine leere Liste lieferte - und die bedeutet sowohl "nichts aufgezeichnet" als auch "nicht lesbar". Am 03.09. trat der zweite Fall ein: der Knopf traf einen Neustart, der recorder war noch nicht bereit, die Abfrage lief 2:47 und kam leer zurueck. Die Fahrt bekam 18:43 statt 17:59 als Ende, 74 Minuten statt 30. Drei Quellen in fester Reihenfolge statt einer: Aufzeichnung, dann hass.states (last_reported/last_updated - weiss dasselbe ohne Datenbank), dann "jetzt", und das nur noch mit Warnung. Vier Regressionstests, drei davon gegen den alten Stand nachweislich rot. Dazu aus der Bestandspruefung: odometer_km liegt in drei alten Tankvorgaengen als Zeichenkette vor, ablage.distanz_seit_letzter_tankung() rechnete damit float - str. Laeuft jetzt durch als_kilometerstand(). Der Eingang war schon zu; das ist Altbestand. Und acht Dienste hatten keinen Klarnamen in translations/de.json - jetzt 28 Dienste, 28 Klarnamen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4d4890034a |
Regler stellt den Schlaf-Timeout des Dongles (2026.9.3.10)
Der Regler "Fahrt beenden" ist jetzt eine Zahl fuer zwei Dinge: unsere eigene
Wartezeit bis zum Fahrtende und der Schlaf-Timeout des FMM003 (103). So lange
bleibt der Dongle wach und wartet darauf, dass es gleich weitergeht; danach
schlaeft er. Die 180 s Ignition OFF Delay laufen davor und bleiben unsichtbar -
sie werden nur von der Fahrtdauer abgezogen. Der Regler geht deshalb von 1 bis
60 in Einerschritten; das Geraet kennt kein Timeout 0.
flespi.py liest und schreibt die Geraete-Konfiguration. Drei Annahmen sind an
der echten API gefallen und stehen als Messung im Modulkopf: der Wert liegt in
current (nicht value), die Einstellung heisst sleep_mode mit dem Wert eine Ebene
tiefer unter mode, und ein PUT braucht {"properties": ..., "address": ...}.
Die eigene Auftrags-Warteschlange ist wieder entfallen. Sie war fuer "das Geraet
schlaeft" gebaut - das gilt aber nur fuer das Geraet, nicht fuer die
Schnittstelle: die vollstaendige Konfiguration kam herein, waehrend das Fahrzeug
seit dem Vortag stand. flespi puffert selbst (pending + address=connection), ein
zweites Auftragsbuch waere eine zweite Warteschlange fuer dieselbe Aufgabe.
Gegengelesen wird nach jedem Schreiben; als Erfolg zaehlt current ODER pending -
letzteres ist bei stehendem Fahrzeug der Normalfall.
Live gegen den echten Account belegt: Token sieht genau ein Geraet, 127
Einstellungen bei parkendem Fahrzeug gelesen, Regler 15 geschrieben und als
pending bestaetigt (Nachbarfelder unveraendert), zweiter Neustart ohne erneute
Anfrage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5ccbfec92d |
Fahrt beenden auf Knopfdruck, Schieberegler, Zuendungspause (2026.9.3.5-.7)
Vier Themen aus einer Sitzung. 1. Die Fahrt endet wieder bei uns statt im Dongle (Abschnitt BW). Der Ignition-OFF-Timeout des FMM003 (900 s) und sein Schlaf-Timeout (ebenfalls 900 s) starten beide beim Zuendungs-Aus und fallen in derselben Sekunde - am 02.09. zweimal beobachtet, einmal ging das Trip-Ende verloren, einmal kam es eine Sekunde vor dem Schlaf an. Ausloeser ist jetzt die Zuendung, die Wartezeit laeuft in Home Assistant. ZUENDUNG_NACHLAUF_S = 180 wird in beiden Wegen abgezogen (ueber das Trip-Signal 1080 s, weil dessen Ende selbst an der verzoegerten Zuendung haengt). 2. Schieberegler, neu in beiden Codebasen. Schrittweite 5 Minuten; der Daumen war im Tagmodus weiss auf weiss und traegt jetzt einen Ring aus --line-strong - ein Token, das genau dort sichtbar ist, wo es gebraucht wird. 3. Knopf "Fahrt beenden" in der Zuletzt-Kachel (Abschnitt BX). Schliesst auf das letzte Lebenszeichen, nicht auf "jetzt", und kennzeichnet das Ende als vorlaeufig. Ein spaet eintreffendes Zuendungs-Aus zieht es nach - innerhalb von sechs Stunden, nur nach vorn, und nur bei einer vorlaeufigen Fahrt. ts_end wandert bewusst NICHT in edited_fields, sonst blockierte der Schutz fuer Handeingaben genau diese Korrektur. 4. Das OTA-Buendel kann auf dem Mac gar nicht entstehen (Abschnitt BV): npm run ota ist eine Windows-PowerShell-Datei, ios-signieren.sh baut ein frisches dist/ und fasst das Buendel nie an. Ausgeliefert war deshalb eine Oberflaeche ohne den Versionshinweis unter richtiger Nummer. Verifiziert: Backend 27/27 (5 Signalwechsel, 14 Zuendungspause, 8 Fahrtende), companion-app tsc sauber und 179/179, Panel als Modul geparst, audi_ha_test auf 2026.9.3.7 sauber gestartet. Live am laufenden Panel und ohne Rueckstand belegt: Wartezeit samt Abbruch, die 180-s-Rechnung, der Regler im Tagmodus und der Knopf von der laufenden Fahrt bis zum verworfenen Kurzvorgang - Fahrten vorher 16, nachher 16. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6016053a92 |
Kalender ueber EventKit, Orte nach dem Import, Fotos verkleinert, Signalsperre (2026.9.3.1-.3)
- Kalendertermine gehen nativ ueber EventKit statt ueber das Teilen-Blatt:
Apples Kalender meldet sich beim System gar nicht als Teilen-Ziel an, ein
anderes Dateiformat haette also nie geholfen (Abschnitt BS).
- Der Rueckblick stoesst jetzt selbst das Screening an, wenn er Fahrten
angelegt hat - sonst blieb eine importierte Fahrt ohne Ort liegen, solange
das Fahrzeug steht (Abschnitt BT).
- Fahrzeugfotos werden vor dem Upload im Browser auf 2000 px/WebP gebracht.
Gemessen: 3.099.022 -> 325.994 Bytes. Damit ist die Nachrichtengrenze der
WebSocket-Verbindung kein Thema mehr ("connection lost" auf der realen
Instanz). Formate, die der Browser weder umwandeln noch anzeigen kann,
werden mit klarer Meldung abgelehnt statt roh gespeichert.
- Zwei Wechsel des Fahrtsignals in derselben Sekunde kosteten eine ganze
Fahrt: das "on" ueberholte den noch laufenden Beende-Vorgang und fiel durch
beide Zweige. Neue asyncio-Sperre plus Regressionstest, der ohne sie
nachweislich rot ist (Abschnitt BU).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5a048488ea |
Selbst-Update-Sicherung, Service-Prognose ohne Servicebuch und Sicherheit neu gestaltet
- aktualisierung.py: Backup-Ordner verliert sein manifest.json beim Sichern, sonst laedt HA ihn nach einem Neustart als zweite audi_dashboard-Integration (Ursache des raetselhaften "gleicher Pfad"-Umbenennungs-Fehlers). - service.ts/audi-dashboard-app.js: Uebersicht faellt jetzt wie Service schon auf die Fahrzeugmeldung zurueck, wenn kein Servicebucheintrag existiert; eigenes (kuerzeres) Oelwechsel-Intervall wird jetzt auch ohne Servicebuch- eintrag naeherungsweise aus der Herstellermeldung hochgerechnet, statt weiterhin unveraendert die Herstellervorgabe zu zeigen. - Sicherheit neu gestaltet: vier Sammelzeilen (Fahrzeug verriegelt / Tueren und Klappen geschlossen / Fenster und Dach geschlossen / Kein Licht) statt einer flachen Liste, mit neuen Tuerschloss-, Dach- und Standlicht-Sensor- rollen, Kreis-Haekchen-Symbolen und einer Detailseite je Tuer/Motorhaube/ Kofferraum in beiden Frontends. Details, Verifikation und Sensor-Rollen in AGENTS.md (Abschnitte J/M/N). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
fb251154c7 |
Ölwechsel/Inspektion-Anzeige repariert, Selbst-Update der Integration gebaut
Zwei getrennte Themen in einem Commit, beide in derselben Sitzung entstanden:
1. Ölwechsel/Inspektion wurden komplett ausgeblendet ("kein Eintrag im
Servicebuch"), sobald kein Servicebucheintrag vorlag - selbst wenn der
zugeordnete Sensor eine gültige Fälligkeit meldete. In beiden Frontends
prüfte die Anzeige nur den Servicebuch-Zweig, bevor sie die
Fahrzeugmeldung überhaupt las. Jetzt steht die Fahrzeugmeldung für sich;
fehlt zusätzlich ein Servicebucheintrag, übernimmt eine neue,
fahrtenlog-basierte Prognose (kmProTagAusFahrten()/meldungsPrognose())
die Hochrechnung statt der Servicebuch-Rate - deckelt auf die vom
Fahrzeug selbst gemeldete Zeitgrenze, falls zu wenig gefahren wird.
2. install.ps1 als Update-Weg wird von Windows Smart App Control blockiert,
ohne Umgehungsmöglichkeit. Die Integration lädt sich jetzt auf
Tastendruck selbst von Gitea (aktualisierung.py), verifiziert das
Manifest vor jedem Tausch und tauscht per os.rename mit automatischem
Rollback bei Fehlern - install.ps1 bleibt nur noch für die
Erstinstallation nötig. Zugangstoken über einen neuen OptionsFlow in
entry.options, nie in configuration.yaml.
Nebenbei: mehrere seit der HACS-Ausschluss-Entscheidung liegen gebliebene
falsche HACS-Referenzen in Code-Kommentaren und einem UI-Text korrigiert.
Details, Sicherheitsbegründung und Verifikationsstand in AGENTS.md,
Abschnitte I und J.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|
|
d8b12da36d |
pyscript-Backend zur echten HA-Integration umgebaut (HACS-fähig)
Das Backend liegt jetzt als custom_components/audi_dashboard/ vor - eine normale Home-Assistant-Integration mit Config-Flow, einer sensor-Plattform und 18 Diensten. Damit ist die App über HACS installierbar; bis das Repo auf GitHub gespiegelt ist (HACS spricht ausschließlich mit GitHub), installiert homeassistant/installationspaket/install.ps1 denselben Ordner ohne HACS. Fünf Installationsschritte entfallen ersatzlos: der pyscript:-Block, der panel_custom:-Block, das Kopieren der Oberfläche nach www/, das langlebige Zugriffstoken (der Verlauf wird direkt über die recorder-API gelesen) und "pip install pypdf" (steht in manifest.json). Das Fahrzeugprofil legt die Integration beim ersten Start aus ihrer Vorlage an. Drei alte Schwächen sind dabei mit erledigt: - Die Nutzlast landet nicht mehr in der Recorder-Datenbank (_unrecorded_attributes - das kann nur eine echte Entität). - Eine laufende Fahrt überlebt einen Neustart (Store statt Arbeitsspeicher); fiel sie während eines Ausfalls ins Ende, schließt nach_neustart_fortsetzen() sie beim letzten aufgezeichneten Zeitpunkt. - Sensor-Zuordnungen wirken sofort - die Zustandsbeobachter werden neu gebunden, der Neustart-Hinweis und der Neustart-Dienst sind weg. Namensvertrag geändert, beide Oberflächen mitgezogen: pyscript.audi_dashboard_x -> sensor.audi_dashboard_x, pyscript.audi_dashboard_y -> audi_dashboard.y. Eine Companion-App vom alten Stand findet nach dem Umstieg nichts mehr und muss neu gebaut werden; das Panel liegt in der Integration und kann nicht driften. Der selbstgebaute Updater entfällt - HACS ist die Update-Mechanik, die Home Assistant kennt. Die Versionierung schrumpft auf eine Quelle: manifest.json. Geprüft am laufenden Testcontainer (Container byteweise identisch mit dem Repo): alle 18 Dienste, Panel, Config-Entry neu laden, Historienimport, echter Shell-Beleg in-process, Neuinstallation im Wegwerf-Container blank mit automatisch nachinstalliertem pypdf. Companion-App: tsc sauber, 112/112 Tests, beide Rauchtests gegen das laufende Backend grün. Belegparser 8/8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |