Fahrten bearbeitbar, Inspektionsprognose, Bootstrap-Takt reisst nicht mehr ab
Sechs gemeldete Punkte plus ein Fund beim Nachlesen des Ladevorgangs: - HECKKLAPPENSCHLOSS_SENSOR ueberall entfernt (einstellungen.py, entitaeten.py-Katalog, _sicherheitscheck()). HECKKLAPPE_SENSOR bleibt. - Bild-Platzhalter in den Einstellungen war halb abgeschnitten: die bare .carfix-Regel der Desktop-Container-Query traf auch den Platzhalter-Div, dessen inset:0 von der festen Hoehe uebersteuert wurde. Auf .bildbox eingegrenzt. - Inspektion bekommt eine eigene "voraussichtlich am ..."-Zeile (nur Datum). oelwechselPrognose() dafuer auf den gemeinsamen Kern servicePrognose() zurueckgefuehrt. - "Neue Fahrt" bietet jetzt alle Felder, die die Einzelfahrt anzeigt, und die Einzelfahrt laesst sich bearbeiten - gemeinsames fahrtFelder()/ fahrtFormularWerte() nach dem Muster von tankFelder(). Backend: Service audi_dashboard_fahrt_aktualisieren, profil.fahrt_bearbeiten() (edited_fields schuetzt gegen die Automatik, nicht gegen den Nutzer). Die Art-Pille speichert jetzt ebenfalls statt nur lokal umzuschalten. - Titelzeile steht auf dem Handy buendig zu den Kacheln: .back:not(.on) gibt seine Breite und den Gap auf allen Breiten frei, .topbar links 16px. - Ladebildschirm: nachladeAnstossen() und datenLaden() stiegen bei fehlendem HASS aus, ohne eine naechste Runde zu planen - genau die Sackgasse, aus der bisher nur ein Menueklick half. Beide planen jetzt weiter, der Timer wird beim Abhaengen gestoppt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,13 +122,43 @@ def _fahrt_beenden(start_ts, ende_ts):
|
||||
fahrtabschluss_logik.screening_durchfuehren(einstellungen.KM_SENSOR)
|
||||
|
||||
|
||||
def _handfelder(start_ort, ziel_ort, odo_start, odo_end, distanz):
|
||||
"""Die von Hand befüllbaren Felder einer Fahrt auf ihre Datensatz-Namen
|
||||
abgebildet, ohne die leer gelassenen. Der Aufrufer schreibt die Schlüssel
|
||||
zusätzlich nach edited_fields - das schützt sie vor dem automatischen
|
||||
Kilometerstand-Screening (§7.2), das sonst später darüberschreiben würde."""
|
||||
werte = {
|
||||
"start_address": start_ort or None,
|
||||
"end_address": ziel_ort or None,
|
||||
"odo_start": odo_start,
|
||||
"odo_end": odo_end,
|
||||
"distance_km": distanz,
|
||||
}
|
||||
return {k: v for k, v in werte.items() if v is not None}
|
||||
|
||||
|
||||
def _strecke_ergaenzen(felder):
|
||||
"""Fehlt die Distanz, ergibt sie sich aus den beiden Kilometerständen -
|
||||
dieselbe Rechnung wie im Screening (fahrtabschluss_logik)."""
|
||||
if "distance_km" not in felder and "odo_start" in felder and "odo_end" in felder:
|
||||
felder["distance_km"] = round(felder["odo_end"] - felder["odo_start"], 1)
|
||||
return felder
|
||||
|
||||
|
||||
@service
|
||||
def audi_dashboard_fahrt_manuell_anlegen(ts_start=None, ts_end=None, art="privat"):
|
||||
def audi_dashboard_fahrt_manuell_anlegen(ts_start=None, ts_end=None, art="privat",
|
||||
start_ort=None, ziel_ort=None,
|
||||
odo_start=None, odo_end=None, distanz=None):
|
||||
"""Manuelle Fahrterfassung aus der Oberfläche (§5.3), unabhängig von der
|
||||
automatischen Zündungs-Erkennung. Aufruf als pyscript.audi_dashboard_fahrt_manuell_anlegen."""
|
||||
automatischen Zündungs-Erkennung. Dieselben Felder wie beim Bearbeiten
|
||||
einer bestehenden Fahrt (siehe audi_dashboard_fahrt_aktualisieren); alles
|
||||
außer Start- und Endzeitpunkt ist optional und bleibt leer, wenn es das
|
||||
Screening später selbst ergänzen soll. Aufruf als
|
||||
pyscript.audi_dashboard_fahrt_manuell_anlegen."""
|
||||
start_ts = datetime.datetime.fromisoformat(ts_start)
|
||||
ende_ts = datetime.datetime.fromisoformat(ts_end)
|
||||
dauer_s = int((ende_ts - start_ts).total_seconds())
|
||||
hand = _strecke_ergaenzen(_handfelder(start_ort, ziel_ort, odo_start, odo_end, distanz))
|
||||
fahrt = {
|
||||
"trip_id": profil.neue_id("t"),
|
||||
"ts_start": start_ts.isoformat(),
|
||||
@@ -152,11 +182,54 @@ def audi_dashboard_fahrt_manuell_anlegen(ts_start=None, ts_end=None, art="privat
|
||||
"status": "offen",
|
||||
"edited_fields": [],
|
||||
}
|
||||
fahrt.update(hand)
|
||||
fahrt["edited_fields"] = sorted(hand)
|
||||
if fahrt["distance_km"] is not None:
|
||||
fahrt["km_quelle"] = "manuell"
|
||||
fahrt["status"] = "vollständig"
|
||||
profil.fahrt_anhaengen(fahrt)
|
||||
frontend_veroeffentlichung.fahrten_veroeffentlichen()
|
||||
fahrtabschluss_logik.screening_durchfuehren(einstellungen.KM_SENSOR)
|
||||
|
||||
|
||||
@service
|
||||
def audi_dashboard_fahrt_aktualisieren(trip_id=None, ts_start=None, ts_end=None, art=None,
|
||||
start_ort=None, ziel_ort=None,
|
||||
odo_start=None, odo_end=None, distanz=None):
|
||||
"""Bearbeitet eine bestehende Fahrt (Einzelfahrt-Seite), egal ob sie
|
||||
automatisch erkannt oder von Hand angelegt wurde. Aufruf als
|
||||
pyscript.audi_dashboard_fahrt_aktualisieren."""
|
||||
if not trip_id:
|
||||
log.warning("audi_dashboard: fahrt_aktualisieren ohne trip_id aufgerufen")
|
||||
return
|
||||
aenderungen = _strecke_ergaenzen(_handfelder(start_ort, ziel_ort, odo_start, odo_end, distanz))
|
||||
if art:
|
||||
aenderungen["art"] = art
|
||||
if ts_start and ts_end:
|
||||
start_ts = datetime.datetime.fromisoformat(ts_start)
|
||||
ende_ts = datetime.datetime.fromisoformat(ts_end)
|
||||
aenderungen["ts_start"] = start_ts.isoformat()
|
||||
aenderungen["ts_end"] = ende_ts.isoformat()
|
||||
aenderungen["duration_s"] = int((ende_ts - start_ts).total_seconds())
|
||||
if aenderungen.get("distance_km") is not None:
|
||||
aenderungen["km_quelle"] = "manuell"
|
||||
aenderungen["status"] = "vollständig"
|
||||
# Von Hand gesetzte Felder bleiben von Hand gesetzt: die Liste wächst mit,
|
||||
# damit ein späteres Screening sie nicht wieder überschreibt.
|
||||
# Bewusst eine Listen-Abstraktion statt next(<Generator>): pyscript kann
|
||||
# keine Generator-Ausdrücke auswerten ("not implemented ast
|
||||
# ast_generatorexp"), Listen-Abstraktionen dagegen schon.
|
||||
passend = [f for f in profil.fahrten_lesen() if f.get("trip_id") == trip_id]
|
||||
handfelder = {"start_address", "end_address", "odo_start", "odo_end", "distance_km"}
|
||||
bisher = set(passend[0].get("edited_fields", [])) if passend else set()
|
||||
aenderungen["edited_fields"] = sorted(bisher | (handfelder & set(aenderungen)))
|
||||
if profil.fahrt_bearbeiten(trip_id, aenderungen):
|
||||
frontend_veroeffentlichung.fahrten_veroeffentlichen()
|
||||
log.info(f"audi_dashboard: Fahrt {trip_id} aktualisiert")
|
||||
else:
|
||||
log.warning(f"audi_dashboard: Fahrt {trip_id} nicht gefunden")
|
||||
|
||||
|
||||
@service
|
||||
def audi_dashboard_fahrt_loeschen(trip_id=None):
|
||||
"""Löscht eine Fahrt unwiderruflich (Löschfunktion in der Oberfläche,
|
||||
|
||||
@@ -67,7 +67,6 @@ FENSTER_SENSOREN = []
|
||||
|
||||
# Heckklappe und Motorhaube - keine Quelle mehr vorhanden.
|
||||
HECKKLAPPE_SENSOR = ""
|
||||
HECKKLAPPENSCHLOSS_SENSOR = ""
|
||||
HAUBE_SENSOR = ""
|
||||
|
||||
# Vom Fahrzeug selbst gemeldete Service-Fälligkeit (ergänzt die App-eigene,
|
||||
|
||||
@@ -85,10 +85,6 @@ FELDER = [
|
||||
"hinweis": "\"aus\"/off = zu.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["heckklappe", "tailgate", "kofferraum", "trunk"]},
|
||||
{"key": "HECKKLAPPENSCHLOSS_SENSOR", "label": "Heckklappenschloss", "gruppe": "sicherheit",
|
||||
"hinweis": "\"on\" = entriegelt, \"aus\"/off = verriegelt.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["lock"], "units": [], "liste": False, "pflicht": False,
|
||||
"stichworte": ["heckklappe", "tailgate", "schloss", "lock"]},
|
||||
{"key": "HAUBE_SENSOR", "label": "Motorhaube", "gruppe": "sicherheit",
|
||||
"hinweis": "\"aus\"/off = zu.",
|
||||
"domains": ["binary_sensor"], "device_classes": ["door", "opening"], "units": [], "liste": False, "pflicht": False,
|
||||
|
||||
@@ -138,7 +138,6 @@ def _sicherheitscheck():
|
||||
eintraege.append({"label": f"Fenster {pos}", "ok": None if w is None else w == "off"})
|
||||
for label, sensor in [
|
||||
("Heckklappe", einstellungen.HECKKLAPPE_SENSOR),
|
||||
("Heckklappenschloss", einstellungen.HECKKLAPPENSCHLOSS_SENSOR),
|
||||
("Motorhaube", einstellungen.HAUBE_SENSOR),
|
||||
]:
|
||||
w = zustand_oder_none(sensor)
|
||||
|
||||
@@ -138,16 +138,20 @@ def offene_fahrten():
|
||||
return sorted(offen, key=lambda f: f.get("ts_start", ""))
|
||||
|
||||
|
||||
def _datensatz_aktualisieren(zeilen, id_feld, id_wert, aenderungen, schreiben):
|
||||
def _datensatz_aktualisieren(zeilen, id_feld, id_wert, aenderungen, schreiben, schutz=True):
|
||||
"""Ersetzt ausgewählte Felder eines Datensatzes anhand seiner ID und
|
||||
schreibt den gesamten Bestand neu. Manuell geänderte Felder
|
||||
(edited_fields, analog zu §7.7 Regel 3) werden dabei nie überschrieben.
|
||||
Gemeinsame Grundlage für Fahrten und Tankvorgänge - beide Archive
|
||||
funktionieren nach demselben Muster."""
|
||||
funktionieren nach demselben Muster.
|
||||
|
||||
schutz=False hebt genau diese Sperre auf - nötig für Eingaben aus der
|
||||
Oberfläche: edited_fields schützt gegen die automatische Ergänzung, nicht
|
||||
gegen den Menschen, der das Feld gerade selbst korrigiert."""
|
||||
geaendert = False
|
||||
for d in zeilen:
|
||||
if d.get(id_feld) == id_wert:
|
||||
geschuetzt = set(d.get("edited_fields", []))
|
||||
geschuetzt = set(d.get("edited_fields", [])) if schutz else set()
|
||||
for feld, wert in aenderungen.items():
|
||||
if feld not in geschuetzt:
|
||||
d[feld] = wert
|
||||
@@ -162,6 +166,13 @@ def fahrt_aktualisieren(trip_id, aenderungen):
|
||||
return _datensatz_aktualisieren(fahrten_lesen(), "trip_id", trip_id, aenderungen, fahrten_schreiben)
|
||||
|
||||
|
||||
def fahrt_bearbeiten(trip_id, aenderungen):
|
||||
"""Wie fahrt_aktualisieren(), aber für Eingaben aus der Oberfläche: eine
|
||||
von Hand gesetzte Angabe sticht auch dann, wenn dasselbe Feld schon einmal
|
||||
von Hand gesetzt wurde (siehe schutz-Parameter oben)."""
|
||||
return _datensatz_aktualisieren(fahrten_lesen(), "trip_id", trip_id, aenderungen, fahrten_schreiben, schutz=False)
|
||||
|
||||
|
||||
def fahrt_loeschen(trip_id):
|
||||
fahrten = fahrten_lesen()
|
||||
uebrig = [f for f in fahrten if f.get("trip_id") != trip_id]
|
||||
|
||||
Reference in New Issue
Block a user