fix: Bank-Suche verlor die besten Treffer beim Kuerzen
Der Fehler, den der Nutzer gefunden hat: Die Suche nach "n" lieferte "genau 200" Treffer und darunter nie N26. Ursache war die Reihenfolge – alphabetisch sortiert und ERST DANN bei 200 gekuerzt. "N26" liegt hinter "Aachener Bank", fiel also hinter zweihundert Banken heraus, die irgendwo ein n im Namen tragen. Die Bank war die ganze Zeit da. Jetzt wird zuerst bewertet (genauer Name > Namensanfang > Wortanfang > Teiltreffer > nur BIC/Gruppe) und erst danach gekuerzt. Ausserdem wird die ECHTE Trefferzahl gemeldet statt der gekuerzten – "genau 200" sah aus wie ein Suchergebnis, war aber die Obergrenze. Ein Regressionstest stellt den Fall nach: 300 Banken mit "n" im Namen, alphabetisch vor N26. Ohne den Fix faellt er um. Nebenbei geprueft und ausgeschlossen: /aspsps blaettert nicht, die Liste ist also vollstaendig; Sandbox und alphabetische Kuerzung waren es auch nicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,26 @@ def _client(db: Session, user: User) -> BankClient:
|
||||
MAX_TREFFER = 200
|
||||
|
||||
|
||||
def _rang(name: str, begriff: str, in_name: bool) -> int:
|
||||
"""Wie gut passt ein Name zum Suchbegriff? Kleiner ist besser.
|
||||
|
||||
Ohne Rangfolge entscheidet das Alphabet, welche Treffer die Obergrenze
|
||||
überleben – und dann fällt „N26" bei der Suche nach „n" hinter 200 Banken
|
||||
heraus, deren Name irgendwo ein n enthält. Genau dieser Fehler hat hier
|
||||
einen Abend gekostet.
|
||||
"""
|
||||
klein = name.lower()
|
||||
if klein == begriff:
|
||||
return 0
|
||||
if klein.startswith(begriff):
|
||||
return 1
|
||||
if any(wort.startswith(begriff) for wort in klein.replace("-", " ").split()):
|
||||
return 2
|
||||
if begriff in klein:
|
||||
return 3
|
||||
return 4 if not in_name else 3
|
||||
|
||||
|
||||
def banken(db: Session, user: User, land: str | None = None,
|
||||
suche: str | None = None) -> dict:
|
||||
"""Banken suchen – in einem Land oder über alle hinweg.
|
||||
@@ -75,43 +95,42 @@ def banken(db: Session, user: User, land: str | None = None,
|
||||
"""
|
||||
roh = _client(db, user).banken(land)
|
||||
begriff = (suche or "").strip().lower()
|
||||
treffer = []
|
||||
|
||||
passend = []
|
||||
for b in roh:
|
||||
name = b.get("name")
|
||||
if not name:
|
||||
continue
|
||||
# Auch im BIC und im Gruppennamen suchen: Banken heissen in der Liste
|
||||
name = str(name)
|
||||
# Auch BIC und Gruppenname durchsuchen: Banken heissen in der Liste
|
||||
# nicht immer so wie im Alltag.
|
||||
heu = " ".join(str(x) for x in (name, b.get("bic"), b.get("group")) if x).lower()
|
||||
if begriff and begriff not in heu:
|
||||
neben = " ".join(str(x) for x in (b.get("bic"), b.get("group")) if x).lower()
|
||||
if begriff and begriff not in name.lower() and begriff not in neben:
|
||||
continue
|
||||
treffer.append({"name": name, "country": b.get("country"),
|
||||
"logo": b.get("logo"), "psu_types": b.get("psu_types", []),
|
||||
"beta": bool(b.get("beta"))})
|
||||
treffer.sort(key=lambda b: (b["name"].lower(), b.get("country") or ""))
|
||||
# „gesamt" und eine Kostprobe mitliefern: Sonst sieht „keine Treffer"
|
||||
# gleich aus, egal ob die Liste leer kam, der Suchbegriff nicht passte –
|
||||
# oder die Anwendung als Sandbox angelegt wurde und lauter Test-Institute
|
||||
# liefert. An der Kostprobe sieht man Letzteres sofort.
|
||||
# Beispiele ueber den ganzen Bereich streuen, nicht nur den Anfang zeigen.
|
||||
# Endet die Liste alphabetisch bei „B", ist sie abgeschnitten – und man
|
||||
# sucht sonst ewig nach einer Bank, die schlicht nicht mitgeliefert wurde.
|
||||
passend.append({
|
||||
"name": name, "country": b.get("country"), "logo": b.get("logo"),
|
||||
"psu_types": b.get("psu_types", []), "beta": bool(b.get("beta")),
|
||||
"_rang": _rang(name, begriff, True) if begriff else 3,
|
||||
})
|
||||
|
||||
# Erst nach Güte, dann alphabetisch – und ERST DANN kürzen.
|
||||
passend.sort(key=lambda b: (b["_rang"], b["name"].lower(), b.get("country") or ""))
|
||||
gezeigt = [{k: v for k, v in b.items() if k != "_rang"} for b in passend[:MAX_TREFFER]]
|
||||
|
||||
namen = sorted({str(b.get("name")) for b in roh if b.get("name")})
|
||||
beispiele = (namen[:3] + namen[len(namen) // 2: len(namen) // 2 + 2] + namen[-3:]
|
||||
if len(namen) > 8 else namen)
|
||||
return {
|
||||
"gesamt": len(roh),
|
||||
"treffer": treffer[:MAX_TREFFER],
|
||||
"gekuerzt": len(treffer) > MAX_TREFFER,
|
||||
# Die ECHTE Trefferzahl, nicht die gekürzte. Sonst steht dort immer
|
||||
# „200" und man haelt die Obergrenze für ein Suchergebnis.
|
||||
"treffer_gesamt": len(passend),
|
||||
"treffer": gezeigt,
|
||||
"gekuerzt": len(passend) > MAX_TREFFER,
|
||||
"beispiele": beispiele,
|
||||
"von": namen[0] if namen else None,
|
||||
"bis": namen[-1] if namen else None,
|
||||
# Fängt überhaupt eine Bank mit diesen Buchstaben an? Wenn nicht, fehlt
|
||||
# der Teil des Alphabets, und der Suchbegriff ist unschuldig.
|
||||
"anfangsbuchstaben": sorted({n[:1].upper() for n in namen}),
|
||||
# Alles mit demselben Anfangsbuchstaben wie der Suchbegriff. Damit
|
||||
# sieht man, unter welchem Namen die gesuchte Bank dort wirklich steht
|
||||
# – Raten über Namensvarianten hat lange genug gedauert.
|
||||
"gleicher_buchstabe": (
|
||||
[n for n in namen if begriff and n[:1].lower() == begriff[:1]][:40]
|
||||
if begriff else []
|
||||
|
||||
Reference in New Issue
Block a user