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.
This commit is contained in:
2026-09-04 17:00:22 +02:00
parent a99ae432fc
commit bdbdb4a010
17 changed files with 572 additions and 60 deletions
@@ -123,6 +123,25 @@ def _marke(zeilen):
return None
def marke_aus_text(text):
"""Die Kopfmarke eines Belegtextes - der oeffentliche Weg zu _marke().
Getrennt vom vollstaendigen Einlesen, weil die Marke nur die ersten Zeilen
braucht: sie laesst sich auch aus einem Beleg holen, an dem beide Parser
scheitern wuerden. Genau das braucht das Nachtragen in belege.py fuer
Belege, die vor dem 16.08.2026 eingelesen wurden."""
return _marke((text or "").splitlines())
def marke_aus_pdf(pdf_pfad):
"""Wie marke_aus_text(), aber liest das PDF selbst. None, wenn die Datei
sich nicht lesen laesst - eine fehlende Marke ist kein Fehlerfall."""
try:
return marke_aus_text(_text_aus_pdf(pdf_pfad))
except Exception:
return None
def _ist_strasse(zeile):
"""Straßenzeilen tragen eine Hausnummer oder ein Straßenwort - der
Betreibername ("AUTO B. FRISCHMANN GMBH") tut beides nicht."""
@@ -234,6 +253,12 @@ def _parsen(text):
# Anzeigename "Marke, Straße, Ort" (siehe _tankstelle). Der Ort steht
# auf dem Bon als "85057 Ingolstadt" - die PLZ gehört nur in die
# ausführliche station_address darunter, nicht in den Anzeigenamen.
# Die Marke steht seit dem 04.09.2026 ZUSAETZLICH als eigenes Feld da.
# Sie nur in den Anzeigenamen zu falten hat sie fuer jeden Beleg
# verloren, den ein aelterer Parser eingelesen hat - dort blieb der
# Betreibername stehen ("TC Sengül GmbH"), und keine Oberflaeche
# konnte die Marke nachtraeglich noch finden.
"station_brand": _marke(text.splitlines()),
"station_name": _tankstelle(
_marke(text.splitlines()),
station.group("strasse").strip(),
@@ -325,7 +350,8 @@ def _parsen_generisch(text):
break
if station_address and strasse and strasse not in station_address:
station_address = f"{strasse}, {station_address}"
station_name = _tankstelle(_marke(zeilen), strasse, ort, ersatzname=betreiber)
marke = _marke(zeilen)
station_name = _tankstelle(marke, strasse, ort, ersatzname=betreiber)
# ---- Gesamtbetrag ------------------------------------------------------
# Zeilenweise statt über den ganzen Text, aus zwei Gründen, die beide an
@@ -408,6 +434,7 @@ def _parsen_generisch(text):
"ts_payment": None,
"ts_tse": None,
"station_id": None,
"station_brand": marke,
"station_name": station_name,
"station_address": station_address,
"article_no": None,