MV: Server-side Volltextsuche im ParLDokAdapter via facet_fulltext=0 #12

Closed
opened 2026-04-08 12:48:03 +02:00 by tobias · 1 comment
Owner

Sub-Issue von #11 — der MV-Teil zuerst, weil ParlDok 8.x (J3S GmbH) den Volltext-Pfad bereits im Frontend nutzt und das Tag-Format reverse-engineerbar ist.

Was zu tun ist

ParLDokAdapter in app/parlamente.py so erweitern, dass query als Server-side Volltext-Filter ans Backend geht statt client-seitig auf Titel + Urheber zu matchen.

Was bereits bekannt ist

Aus dem Reverse-Engineering von bundle.js (https://www.dokumentation.landtag-mv.de/parldok/js/bundle.js) für Issue #4:

pd = {
  facet_fulltext: 0,
  facet_author: 1,
  facet_fraction: 2,
  facet_kind: 7,
  facet_type: 8,
  facet_lp: 10,
  ...
}

facet_fulltext = 0 ist also der Tag-Typ für die freie Volltextsuche. Aktuell baut _build_search_body nur ein WP-Tag:

"tags": [{"type": self.FACET_LP, "id": self.wahlperiode}]

Erweitern auf:

tags = [{"type": self.FACET_LP, "id": self.wahlperiode}]
if query:
    tags.append({"type": 0, "t": query, ...})  # genaues Schema noch zu verifizieren

Offene Punkte beim Reverse Engineering

  • Welches Feld im Tag-Dict trägt den Suchbegriff? Mögliche Kandidaten: t, id, text, value. Im JS-Code wird das Tag in addSearchtag ggf. mit pd.currentFTSearchMode als mode-Feld kombiniert ("Alle", oder ein spezifisches Index-Suchfeld).
  • Wie verhält sich der Server bei Mehr-Wort-Queries? Vermutlich AND-Verknüpfung default; Phrase-Suche evtl. via Quotes.
  • Browser-DevTools-Trace einer echten Suche auf https://www.dokumentation.landtag-mv.de/parldok/ liefert das exakte Payload-Schema in 5 Minuten.

Akzeptanzkriterien

  • ADAPTERS["MV"].search("Schule", limit=20) liefert ≥10 Treffer (in der MV-DB gibt es mit Sicherheit dutzende Schul-Anträge in WP8)
  • Anzahl Server-Round-Trips bleibt bei ≤3 pro Suche (kein Brute-Force-Pagination mehr nötig)
  • Bestehende leere-Query-Suche (Browse) funktioniert weiterhin
  • Smoke-Test im prod via /api/search-landtag?q=Schule&bundesland=MV
Sub-Issue von #11 — der MV-Teil zuerst, weil ParlDok 8.x (J3S GmbH) den Volltext-Pfad bereits im Frontend nutzt und das Tag-Format reverse-engineerbar ist. ## Was zu tun ist `ParLDokAdapter` in `app/parlamente.py` so erweitern, dass `query` als **Server-side Volltext-Filter** ans Backend geht statt client-seitig auf Titel + Urheber zu matchen. ## Was bereits bekannt ist Aus dem Reverse-Engineering von `bundle.js` (https://www.dokumentation.landtag-mv.de/parldok/js/bundle.js) für Issue #4: ```js pd = { facet_fulltext: 0, facet_author: 1, facet_fraction: 2, facet_kind: 7, facet_type: 8, facet_lp: 10, ... } ``` `facet_fulltext = 0` ist also der Tag-Typ für die freie Volltextsuche. Aktuell baut `_build_search_body` nur ein WP-Tag: ```python "tags": [{"type": self.FACET_LP, "id": self.wahlperiode}] ``` Erweitern auf: ```python tags = [{"type": self.FACET_LP, "id": self.wahlperiode}] if query: tags.append({"type": 0, "t": query, ...}) # genaues Schema noch zu verifizieren ``` ## Offene Punkte beim Reverse Engineering - Welches Feld im Tag-Dict trägt den Suchbegriff? Mögliche Kandidaten: `t`, `id`, `text`, `value`. Im JS-Code wird das Tag in `addSearchtag` ggf. mit `pd.currentFTSearchMode` als `mode`-Feld kombiniert (`"Alle"`, oder ein spezifisches Index-Suchfeld). - Wie verhält sich der Server bei Mehr-Wort-Queries? Vermutlich AND-Verknüpfung default; Phrase-Suche evtl. via Quotes. - Browser-DevTools-Trace einer echten Suche auf https://www.dokumentation.landtag-mv.de/parldok/ liefert das exakte Payload-Schema in 5 Minuten. ## Akzeptanzkriterien - [ ] `ADAPTERS["MV"].search("Schule", limit=20)` liefert ≥10 Treffer (in der MV-DB gibt es mit Sicherheit dutzende Schul-Anträge in WP8) - [ ] Anzahl Server-Round-Trips bleibt bei ≤3 pro Suche (kein Brute-Force-Pagination mehr nötig) - [ ] Bestehende leere-Query-Suche (Browse) funktioniert weiterhin - [ ] Smoke-Test im prod via `/api/search-landtag?q=Schule&bundesland=MV`
Author
Owner

Erledigt in 6184bf8.

Tag-Schema komplett aus bundle.js reverse-engineered (pd.addInput Zeile 72247):

{"type": 0,                       # facet_fulltext
 "id": getFulltextId(query),      # non-alphanum → "-"
 "fulltext": query,
 "label": query,
 "field": "Alle"}                 # pd.currentFTSearchMode default

getFulltextId = re.sub(r"[^a-zA-Z0-9]", "-", term) — server-side dedup-Schlüssel für identische Such-Facetten.

Pagination funktioniert direkt: die queryid aus dem ersten Fulltext/Search-Response trägt den Volltext-Filter automatisch durch alle Fulltext/Resultpage-Folgecalls — kein erneutes Tag-Senden nötig.

Verifikation gegen Akzeptanzkriterien:

Kriterium Vorher Jetzt
search("Schule", limit=20) ≥ 10 Treffer 3 20
Round-Trips ≤ 3 pro Suche bis zu 10 typisch 1–2 ✓ (server narrowed result set)
Browse-Modus (leere query) funktioniert ✓ (unchanged code path)
Live-Smoke-Test /api/search-landtag?q=Schule&bundesland=MV [] (0) 20 ✓

Lokale Cross-Checks für andere Begriffe: Klima → 10, Wohnen → 10, jeweils mit echten Volltext-Hits über mehrere Parteien und Zeiträume.

#13 (BE/LSA via PortalaAdapter eUI) ist davon unabhängig, weil Portala/eUI ein anderes Backend-Schema nutzt — der sf-Index-Name muss erst per DevTools-Trace ermittelt werden. Wird separat angegangen.

Erledigt in 6184bf8. **Tag-Schema** komplett aus `bundle.js` reverse-engineered (`pd.addInput` Zeile 72247): ```python {"type": 0, # facet_fulltext "id": getFulltextId(query), # non-alphanum → "-" "fulltext": query, "label": query, "field": "Alle"} # pd.currentFTSearchMode default ``` `getFulltextId` = `re.sub(r"[^a-zA-Z0-9]", "-", term)` — server-side dedup-Schlüssel für identische Such-Facetten. **Pagination** funktioniert direkt: die `queryid` aus dem ersten `Fulltext/Search`-Response trägt den Volltext-Filter automatisch durch alle `Fulltext/Resultpage`-Folgecalls — kein erneutes Tag-Senden nötig. **Verifikation gegen Akzeptanzkriterien:** | Kriterium | Vorher | Jetzt | |---|---|---| | `search("Schule", limit=20)` ≥ 10 Treffer | 3 | **20** ✓ | | Round-Trips ≤ 3 pro Suche | bis zu 10 | typisch 1–2 ✓ (server narrowed result set) | | Browse-Modus (leere query) funktioniert | ✓ | ✓ (unchanged code path) | | Live-Smoke-Test `/api/search-landtag?q=Schule&bundesland=MV` | `[]` (0) | 20 ✓ | Lokale Cross-Checks für andere Begriffe: `Klima` → 10, `Wohnen` → 10, jeweils mit echten Volltext-Hits über mehrere Parteien und Zeiträume. **#13 (BE/LSA via PortalaAdapter eUI)** ist davon unabhängig, weil Portala/eUI ein anderes Backend-Schema nutzt — der `sf`-Index-Name muss erst per DevTools-Trace ermittelt werden. Wird separat angegangen.
Sign in to join this conversation.
No description provided.