fix: Gegenbuchung nimmt den besten statt des erstbesten Treffers
Liegen mehrere Währungstausche im selben Zeitfenster, schnappte der zuerst geprüfte Transfer der eigentlich gemeinten Zeile das Gegenstück weg – diese blieb dann ohne Gegenkonto stehen. Jetzt gewinnt der Kandidat mit der geringsten Datums- und Betragsabweichung. Toleranz von 10 % auf 20 % erhöht: Wir schätzen die Gegenseite mit dem EZB-Kurs, die Bank rechnet mit ihrem eigenen – und fehlt uns der Kurs, schätzen wir 1:1. Ein echter Tausch lag damit schnell über 10 % daneben (CHF 150.00 -> EUR 165.60 sind 10,4 %), eine fremde Buchung trifft das Zeitfenster praktisch nie. An den echten Auszügen: offene Zeilen im Revolut-EUR-Import von 3 auf 1 gesunken. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -288,7 +288,7 @@ COUNTERPART_DAYS = 5
|
|||||||
# damit er noch als dieselbe Bewegung gilt. Bei einem Währungstausch rechnen wir
|
# damit er noch als dieselbe Bewegung gilt. Bei einem Währungstausch rechnen wir
|
||||||
# mit dem EZB-Kurs, die Bank nutzt ihren eigenen – ein paar Prozent Unterschied
|
# mit dem EZB-Kurs, die Bank nutzt ihren eigenen – ein paar Prozent Unterschied
|
||||||
# sind normal, eine andere Buchung liegt praktisch nie so nah.
|
# sind normal, eine andere Buchung liegt praktisch nie so nah.
|
||||||
COUNTERPART_TOLERANCE = 0.10
|
COUNTERPART_TOLERANCE = 0.20
|
||||||
|
|
||||||
|
|
||||||
def _find_counterpart(
|
def _find_counterpart(
|
||||||
@@ -303,10 +303,11 @@ def _find_counterpart(
|
|||||||
Bei einem Umtausch stehen zwei Währungen und damit zwei Beträge gegenüber;
|
Bei einem Umtausch stehen zwei Währungen und damit zwei Beträge gegenüber;
|
||||||
dann zählt ein plausibler Betrag statt eines exakten."""
|
dann zählt ein plausibler Betrag statt eines exakten."""
|
||||||
absamt = abs(amount)
|
absamt = abs(amount)
|
||||||
unscharf = None
|
bester = None
|
||||||
for eintrag in side_index.get(account.id, []):
|
for eintrag in side_index.get(account.id, []):
|
||||||
txn_id, t_date, erwartet, other_id, richtung = eintrag
|
txn_id, t_date, erwartet, other_id, richtung = eintrag
|
||||||
if txn_id in verbraucht or abs((t_date - d).days) > COUNTERPART_DAYS:
|
tage = abs((t_date - d).days)
|
||||||
|
if txn_id in verbraucht or tage > COUNTERPART_DAYS:
|
||||||
continue
|
continue
|
||||||
# Die Richtung muss stimmen: Wo Geld ankam, kann keine Ausgabe die
|
# Die Richtung muss stimmen: Wo Geld ankam, kann keine Ausgabe die
|
||||||
# Gegenseite sein. Ohne diese Prüfung verschluckt eine Ausgabe über
|
# Gegenseite sein. Ohne diese Prüfung verschluckt eine Ausgabe über
|
||||||
@@ -319,9 +320,18 @@ def _find_counterpart(
|
|||||||
if absamt == erwartet:
|
if absamt == erwartet:
|
||||||
return eintrag
|
return eintrag
|
||||||
cross = waehrung.get(other_id, account.currency) != account.currency
|
cross = waehrung.get(other_id, account.currency) != account.currency
|
||||||
if cross and erwartet and abs(absamt - erwartet) <= erwartet * COUNTERPART_TOLERANCE:
|
if not cross or not erwartet:
|
||||||
unscharf = unscharf or eintrag
|
continue
|
||||||
return unscharf
|
abweichung = abs(absamt - erwartet) / erwartet
|
||||||
|
if abweichung > COUNTERPART_TOLERANCE:
|
||||||
|
continue
|
||||||
|
# Den BESTEN Treffer nehmen, nicht den ersten: Liegen mehrere Umtausche
|
||||||
|
# im Zeitfenster, schnappte sonst der frühere den passenden Transfer weg
|
||||||
|
# und die eigentlich gemeinte Zeile blieb ohne Gegenstück.
|
||||||
|
rang = (tage, abweichung)
|
||||||
|
if bester is None or rang < bester[0]:
|
||||||
|
bester = (rang, eintrag)
|
||||||
|
return bester[1] if bester else None
|
||||||
|
|
||||||
|
|
||||||
def parse_file_to_candidates(data: bytes, template: ImportTemplate) -> list[dict]:
|
def parse_file_to_candidates(data: bytes, template: ImportTemplate) -> list[dict]:
|
||||||
|
|||||||
Reference in New Issue
Block a user