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:
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regressionstest für shell_beleg_parser.py gegen zehn echte Shell-eReceipts.
|
||||
"""Regressionstest für shell_beleg_parser.py gegen elf echte Shell-eReceipts.
|
||||
|
||||
Die PDFs in belege/ sind reale, vom Nutzer bereitgestellte Tankbelege aus
|
||||
vier verschiedenen Stationen (Ingolstadt/Zrenner, Rain am Lech/Bauch,
|
||||
Ansbach/Sengül, Königsbronn/Mogler) und decken damit die tatsächlich
|
||||
fünf verschiedenen Stationen (Ingolstadt/Zrenner an zwei Adressen, Rain am
|
||||
Lech/Bauch, Ansbach/Sengül, Königsbronn/Mogler) und decken damit die tatsächlich
|
||||
beobachtete Formatvielfalt ab, nicht nur eine angenommene Struktur:
|
||||
- Markenzeile: "SHELL STATION" / "Shell Station" / "Shell-Station"
|
||||
- Rabattbezeichnung: "V-Power Smart Deal" / "ClubSmartRabatt"
|
||||
@@ -42,12 +42,13 @@ class AlleBelegeParsenOhneFehler(unittest.TestCase):
|
||||
cls.dateien = sorted(f for f in os.listdir(_BELEGE) if f.endswith(".pdf"))
|
||||
cls.ergebnisse = {f: _parsen_datei(f) for f in cls.dateien}
|
||||
|
||||
def test_zehn_belege_gefunden(self):
|
||||
self.assertEqual(len(self.dateien), 10, self.dateien)
|
||||
def test_elf_belege_gefunden(self):
|
||||
self.assertEqual(len(self.dateien), 11, self.dateien)
|
||||
|
||||
def test_pflichtfelder_gesetzt(self):
|
||||
pflichtfelder = [
|
||||
"receipt_key", "receipt_no", "ts", "station_id", "station_name",
|
||||
"receipt_key", "receipt_no", "ts", "station_id", "station_brand",
|
||||
"station_name",
|
||||
"station_address", "article_no", "product_name", "fuel_type",
|
||||
"liters", "list_price_per_l", "fuel_total_eur",
|
||||
"receipt_total_eur", "net_eur", "vat_eur",
|
||||
@@ -72,12 +73,35 @@ class AlleBelegeParsenOhneFehler(unittest.TestCase):
|
||||
# Rabatt darf den Bruttobetrag nicht übersteigen
|
||||
self.assertLess(d["discount"], d["fuel_total_eur"])
|
||||
|
||||
def test_marke_als_eigenes_feld(self):
|
||||
# Seit dem 04.09.2026 steht die Kopfmarke ZUSAETZLICH als eigenes Feld
|
||||
# da, nicht nur in station_name eingefaltet. Genau daran fehlte sie
|
||||
# jedem Beleg, den ein aelterer Parser eingelesen hatte: dort blieb der
|
||||
# Betreibername stehen, und nachtraeglich war die Marke nirgends mehr
|
||||
# zu holen (Befund des Eigentuemers, 04.09.2026).
|
||||
for datei, d in self.ergebnisse.items():
|
||||
with self.subTest(datei=datei):
|
||||
self.assertEqual(d["station_brand"], "Shell")
|
||||
|
||||
def test_marke_ohne_vollstaendiges_einlesen(self):
|
||||
# marke_aus_pdf() liest nur den Belegkopf. Das ist der Weg, ueber den
|
||||
# belege.marken_nachtragen() die Marke bei Altbelegen nachtraegt - er
|
||||
# darf nicht vom vollstaendigen Parsen abhaengen.
|
||||
for datei in self.dateien:
|
||||
with self.subTest(datei=datei):
|
||||
self.assertEqual(p.marke_aus_pdf(os.path.join(_BELEGE, datei)), "Shell")
|
||||
|
||||
def test_marke_aus_pdf_ohne_datei(self):
|
||||
# Eine fehlende Belegdatei ist kein Fehlerfall - marken_nachtragen()
|
||||
# laeuft ueber Vorgaenge, deren PDF laengst geloescht sein kann.
|
||||
self.assertIsNone(p.marke_aus_pdf(os.path.join(_BELEGE, "gibtesnicht.pdf")))
|
||||
|
||||
def test_receipt_key_eindeutig(self):
|
||||
schluessel = [d["receipt_key"] for d in self.ergebnisse.values()]
|
||||
self.assertEqual(len(schluessel), len(set(schluessel)), schluessel)
|
||||
|
||||
def test_bekannte_stationen(self):
|
||||
# Vier verschiedene Standorte in den zehn Belegen - stellt sicher, dass
|
||||
# Fünf verschiedene Standorte in den elf Belegen - stellt sicher, dass
|
||||
# die Stationserkennung nicht zufällig nur für einen Beleg passt.
|
||||
# Anzeigeformat seit 2026-08-16: "Marke, Straße, Ort" statt des reinen
|
||||
# Betreibernamens ("A. Zrenner GmbH" o. ä.) - der Betreiber sagt beim
|
||||
@@ -88,6 +112,7 @@ class AlleBelegeParsenOhneFehler(unittest.TestCase):
|
||||
"Shell, Münchener Str.15a, Ingolstadt",
|
||||
"Shell, Nürnberger Str.74, Ansbach",
|
||||
"Shell, Heidenheimerstr.6, Königsbronn",
|
||||
"Shell, Richard-Wagner-Str. 9, Ingolstadt",
|
||||
}
|
||||
self.assertEqual(namen, erwartet)
|
||||
|
||||
@@ -107,6 +132,11 @@ class AlleBelegeParsenOhneFehler(unittest.TestCase):
|
||||
"e-receipt3.pdf": dict(liters=21.73, fuel_total_eur=44.53, discount=6.08, station_id="0000000456"),
|
||||
"e-receipt5.pdf": dict(liters=47.76, fuel_total_eur=79.23, discount=12.90, station_id="0000002227"),
|
||||
"e-receipt9.pdf": dict(liters=51.59, fuel_total_eur=85.59, discount=13.41, station_id="0000002103"),
|
||||
# Der Beleg, der in der App nur mit Datum und Uhrzeit ankam
|
||||
# (Eigentuemer, 04.09.2026). Der Parser hat ihn immer vollstaendig
|
||||
# gelesen - der Fehler sass in Tanken.tsx, das andere Feldnamen
|
||||
# abfragte, als belege.py veroeffentlicht.
|
||||
"geteilt-2026-09-04.pdf": dict(liters=43.87, fuel_total_eur=95.60, discount=11.84, station_id="0000000459"),
|
||||
}
|
||||
for datei, erwartet in stichproben.items():
|
||||
d = self.ergebnisse[datei]
|
||||
@@ -120,7 +150,7 @@ class AlleBelegeParsenOhneFehler(unittest.TestCase):
|
||||
class BelegOhneRabatt(unittest.TestCase):
|
||||
"""SmartDeal/ClubSmart ist optional - nicht jeder Tankvorgang hat einen
|
||||
Rabatt. Simuliert durch Entfernen der Rabattzeilen aus einem echten
|
||||
Beleg, da keiner der zehn realen Belege rabattfrei war."""
|
||||
Beleg, da keiner der elf realen Belege rabattfrei war."""
|
||||
|
||||
def test_ohne_rabatt_bleibt_alles_andere_korrekt(self):
|
||||
text = p._text_aus_pdf(os.path.join(_BELEGE, "e-receipt.pdf"))
|
||||
|
||||
Reference in New Issue
Block a user