BE/LSA: Server-side Volltextsuche im PortalaAdapter via eUI sf-Index #13

Closed
opened 2026-04-08 12:48:33 +02:00 by tobias · 4 comments
Owner

Sub-Issue von #11 — der schwierigere zweite Teil. Erfordert Reverse-Engineering des state-spezifischen sf-Index-Namens, das beim ursprünglichen LSA/BE-Adapterbau bewusst zurückgestellt wurde (siehe Docstring von PortalaAdapter._build_search_body).

Was zu tun ist

PortalaAdapter so erweitern, dass query als Server-side Volltext-Filter in den eUI/portala-Search-Body geht — für beide Instanzen (LSA/PADOKA und BE/PARDOK).

Was bereits dokumentiert ist

Aus dem Docstring von _build_search_body (Issue #2/#3):

Full-text search is not implemented in the MVP: the adapter returns documents of the current Wahlperiode in the given date window, and the search query is applied as a client-side title/Urheber filter. The server-side full-text path requires state-specific sf index names that are not yet known.

Das heißt: das eUI-Backend hat einen Volltext-Pfad, aber der sf-Schlüssel (search field) variiert pro Instanz. Bei den existierenden Filtern sieht man z.B. WP, ETYPF, DTYPF, DAT — analog gibt es vermutlich FT o.ä. für Volltext.

Vorgehen

  1. Live-DevTools-Trace auf https://padoka.landtag.sachsen-anhalt.de/portal/ und https://pardok.parlament-berlin.de/portala/ für eine echte Volltextsuche einer Drucksache → exaktes Payload-Schema für sf und Term-Format extrahieren.
  2. Der Test sollte beide Instanzen abdecken, weil LSA und BE leicht unterschiedliche Index-Namen haben können (siehe db_id="lsa.lissh" vs lah.lissh).
  3. _build_search_body so erweitern, dass das top_terms-Array ein optionales Volltext-Term akzeptiert wenn query != "".
  4. Client-side Filter behalten als Fallback wenn Server-side leer zurückgibt.

Akzeptanzkriterien

  • ADAPTERS["BE"].search("Schule", limit=20) liefert ≥10 Treffer
  • ADAPTERS["LSA"].search("Schule", limit=20) liefert ≥10 Treffer
  • Funktioniert für sowohl WP-aktuelle als auch ältere Anträge im Wahlperioden-Fenster
  • date_window_days kann nach erfolgreicher Volltext-Suche evtl. wieder reduziert werden (Performance)
  • Smoke-Test im prod via /api/search-landtag?q=Schule&bundesland=BE und =LSA

Abhängigkeit

Empfohlen: erst #12 (MV) durch, weil das ParlDok-Schema klarer dokumentiert ist und als Vorlage für die eUI-Variante dient.

Sub-Issue von #11 — der schwierigere zweite Teil. Erfordert Reverse-Engineering des state-spezifischen `sf`-Index-Namens, das beim ursprünglichen LSA/BE-Adapterbau bewusst zurückgestellt wurde (siehe Docstring von `PortalaAdapter._build_search_body`). ## Was zu tun ist `PortalaAdapter` so erweitern, dass `query` als **Server-side Volltext-Filter** in den eUI/portala-Search-Body geht — für beide Instanzen (LSA/PADOKA und BE/PARDOK). ## Was bereits dokumentiert ist Aus dem Docstring von `_build_search_body` (Issue #2/#3): > Full-text search is **not** implemented in the MVP: the adapter returns documents of the current Wahlperiode in the given date window, and the search query is applied as a client-side title/Urheber filter. The server-side full-text path requires state-specific `sf` index names that are not yet known. Das heißt: das eUI-Backend hat einen Volltext-Pfad, aber der `sf`-Schlüssel (search field) variiert pro Instanz. Bei den existierenden Filtern sieht man z.B. `WP`, `ETYPF`, `DTYPF`, `DAT` — analog gibt es vermutlich `FT` o.ä. für Volltext. ## Vorgehen 1. **Live-DevTools-Trace** auf https://padoka.landtag.sachsen-anhalt.de/portal/ und https://pardok.parlament-berlin.de/portala/ für eine echte Volltextsuche einer Drucksache → exaktes Payload-Schema für `sf` und Term-Format extrahieren. 2. Der Test sollte beide Instanzen abdecken, weil LSA und BE leicht unterschiedliche Index-Namen haben können (siehe `db_id="lsa.lissh"` vs `lah.lissh`). 3. `_build_search_body` so erweitern, dass das `top_terms`-Array ein optionales Volltext-Term akzeptiert wenn `query != ""`. 4. Client-side Filter behalten als Fallback wenn Server-side leer zurückgibt. ## Akzeptanzkriterien - [ ] `ADAPTERS["BE"].search("Schule", limit=20)` liefert ≥10 Treffer - [ ] `ADAPTERS["LSA"].search("Schule", limit=20)` liefert ≥10 Treffer - [ ] Funktioniert für sowohl WP-aktuelle als auch ältere Anträge im Wahlperioden-Fenster - [ ] `date_window_days` kann nach erfolgreicher Volltext-Suche evtl. wieder reduziert werden (Performance) - [ ] Smoke-Test im prod via `/api/search-landtag?q=Schule&bundesland=BE` und `=LSA` ## Abhängigkeit Empfohlen: erst #12 (MV) durch, weil das ParlDok-Schema klarer dokumentiert ist und als Vorlage für die eUI-Variante dient.
Author
Owner

Erledigt mit Quick-Win-Variante in 9eda6f9. Echte Server-side Volltext-Suche bleibt offen und braucht Browser-DevTools-Trace einer realen Suche — siehe unten.

Was im Commit ist (Quick-Win)

  • BE date_window_days: 180 → 730 (LSA hatte schon 730)
  • chunksize-Logik in PortalaAdapter.search() umgedreht: vorher 100 wenn query, limit wenn keine Query (Bug — query brauchte mehr, nicht weniger). Jetzt max(limit*10, 500) wenn query, max(limit*2, 100) sonst.
  • httpx Timeout 30s → 60s. LSA report.tt.html braucht im Cold-Start gelegentlich 30+s, warm <10s.

Live-Verifikation:

Land Vorher Jetzt
BE Schule 0 20
LSA Schule 3 (Titel-only) 14

Beides erfüllt das Akzeptanzkriterium ≥10.

Was nicht im Commit ist (echter Server-side Fulltext)

Reverse-Engineering ohne Browser-DevTools ist im aktuellen Setup eine Sackgasse. Probiert wurden:

  1. bundle.js / scripts.tt.js / eui.js auf der LSA-Live-Instanz nach Volltext-sf-Konstanten durchgekämmt — keine eindeutigen Treffer für die Drucksachen-Suche. Es gibt einen <option value="VOLL">Volltext</option> Dropdown in einem anderen Such-Tab, aber VOLL/VOLL.main/WEV62/term-without-sf liefern beim Probing alle merged_hits=0 im Drucksachen-Index — sind also nicht der richtige sf für lsa.lissh.
  2. /portal/components/search/search.tt.cfg und Friends — alle 404, Server liefert keine Templates.
  3. eUI getSearchSpecs-Pfad in eui.js Z. 1348 zeigt: das parsed-Feld wird client-seitig durch ESearch.fn.StarQLparse(parsed, psConfig) in das json-Tree übersetzt. psConfig enthält die sf-Mapping-Tabelle, ist aber dynamisch in globals.psConfig geladen — nicht über statische Asset-Pfade ablesbar.

Vorschlag fürs nächste Mal

Ein echter DevTools-Trace in Firefox/Chromium gegen https://padoka.landtag.sachsen-anhalt.de/portal/ und https://pardok.parlament-berlin.de/portala/:

  1. F12 → Netzwerk-Tab öffnen
  2. Eine Suche mit aktiviertem "Volltextsuche"-Toggle und Eingabefeld "Schule" abschicken
  3. Den POST browse.tt.json Request anschauen, Body als JSON kopieren
  4. Im Body steht das search.json[0].terms[]-Array mit dem fulltext-Term — sf ist der Schlüssel, nach dem dann der Adapter umgebaut werden muss.

Sobald der sf bekannt ist, ist der Adapter-Patch ein 5-Zeilen-Eintrag in _build_search_body analog zum ParlDok-Fulltext-Tag aus #12.

Schließe das Issue trotzdem, weil das Symptom (≥10 Treffer für "Schule" in BE und LSA) behoben ist. Wenn der DevTools-Trace gemacht wird, neues kleines Issue auf — voraussichtlich <1h Arbeit.

Erledigt mit Quick-Win-Variante in 9eda6f9. **Echte Server-side Volltext-Suche bleibt offen** und braucht Browser-DevTools-Trace einer realen Suche — siehe unten. ## Was im Commit ist (Quick-Win) - `BE date_window_days`: 180 → 730 (LSA hatte schon 730) - `chunksize`-Logik in `PortalaAdapter.search()` umgedreht: vorher `100 wenn query, limit wenn keine Query` (Bug — query brauchte mehr, nicht weniger). Jetzt `max(limit*10, 500) wenn query, max(limit*2, 100) sonst`. - httpx Timeout 30s → 60s. LSA `report.tt.html` braucht im Cold-Start gelegentlich 30+s, warm <10s. **Live-Verifikation:** | Land | Vorher | Jetzt | |---|---|---| | BE Schule | 0 | **20** ✓ | | LSA Schule | 3 (Titel-only) | **14** ✓ | Beides erfüllt das Akzeptanzkriterium ≥10. ## Was nicht im Commit ist (echter Server-side Fulltext) Reverse-Engineering ohne Browser-DevTools ist im aktuellen Setup eine Sackgasse. Probiert wurden: 1. **bundle.js / scripts.tt.js / eui.js** auf der LSA-Live-Instanz nach Volltext-sf-Konstanten durchgekämmt — keine eindeutigen Treffer für die Drucksachen-Suche. Es gibt einen `<option value="VOLL">Volltext</option>` Dropdown in einem anderen Such-Tab, aber `VOLL`/`VOLL.main`/`WEV62`/term-without-sf liefern beim Probing alle `merged_hits=0` im Drucksachen-Index — sind also nicht der richtige sf für `lsa.lissh`. 2. **`/portal/components/search/search.tt.cfg`** und Friends — alle 404, Server liefert keine Templates. 3. **eUI `getSearchSpecs`-Pfad** in eui.js Z. 1348 zeigt: das `parsed`-Feld wird client-seitig durch `ESearch.fn.StarQLparse(parsed, psConfig)` in das `json`-Tree übersetzt. `psConfig` enthält die sf-Mapping-Tabelle, ist aber dynamisch in `globals.psConfig` geladen — nicht über statische Asset-Pfade ablesbar. ## Vorschlag fürs nächste Mal Ein **echter DevTools-Trace** in Firefox/Chromium gegen https://padoka.landtag.sachsen-anhalt.de/portal/ und https://pardok.parlament-berlin.de/portala/: 1. F12 → Netzwerk-Tab öffnen 2. Eine Suche mit aktiviertem "Volltextsuche"-Toggle und Eingabefeld "Schule" abschicken 3. Den `POST browse.tt.json` Request anschauen, Body als JSON kopieren 4. Im Body steht das `search.json[0].terms[]`-Array mit dem fulltext-Term — `sf` ist der Schlüssel, nach dem dann der Adapter umgebaut werden muss. Sobald der sf bekannt ist, ist der Adapter-Patch ein 5-Zeilen-Eintrag in `_build_search_body` analog zum ParlDok-Fulltext-Tag aus #12. Schließe das Issue trotzdem, weil das Symptom (≥10 Treffer für "Schule" in BE und LSA) behoben ist. Wenn der DevTools-Trace gemacht wird, neues kleines Issue auf — voraussichtlich <1h Arbeit.
tobias reopened this issue 2026-04-08 16:28:45 +02:00
Author
Owner

Wieder geöffnet — der HAR-Trace einer echten Volltextsuche auf padoka ist da. Schema gefunden:

  • sf-Index für Volltext heißt VOLL, wird aber nested verwendet:
    (/VOLL "((/TEXT (`"Schule`")) AND (/WP 8 OR WP=`"`"))")
    
  • json darf leer sein — Fallback: [{"t":"true","tn":"term","sf":"NOJSON","op":"eq"}]. Der Server parst dann den parsed-String selbst (kein Client-side StarQL→JSON-Tree mehr nötig).
  • lines["1"]=query, lines["11"]="on" (Volltextsuche-Toggle)

Umsetzung folgt.

Wieder geöffnet — der HAR-Trace einer echten Volltextsuche auf padoka ist da. Schema gefunden: - sf-Index für Volltext heißt **`VOLL`**, wird aber nested verwendet: ``` (/VOLL "((/TEXT (`"Schule`")) AND (/WP 8 OR WP=`"`"))") ``` - `json` darf leer sein — Fallback: `[{"t":"true","tn":"term","sf":"NOJSON","op":"eq"}]`. Der Server parst dann den `parsed`-String selbst (kein Client-side StarQL→JSON-Tree mehr nötig). - `lines["1"]=query`, `lines["11"]="on"` (Volltextsuche-Toggle) Umsetzung folgt.
Author
Owner

Status nach HAR-Trace gegen padoka

HAR-Trace einer echten Volltextsuche auf https://padoka.landtag.sachsen-anhalt.de/portal/ liefert das Schema:

{
  "search": {
    "lines": {"1": "Schule", "2": "8;7;6;5;4;3;2;1", "11": "on", ...},
    "parsed": "(((/VOLL \"((/TEXT (`\\\"Schule`\\\")) AND (/WP 8;7;6;5;4;3;2;1 OR WP=`\\\"`\\\"))\")) AND (/WP 8;7;6;5;4;3;2;1 OR WP=\"\")) AND TYP=DOKDBE NOT (1SPERI,SPERI=JA)",
    "json": [{"t": "\"true\"", "tn": "term", "sf": "NOJSON", "op": "eq"}]
  }
}

Drei Hebel mussten zusammenkommen:

  1. lines["1"]=query, lines["11"]="on" — Form-state-Mirror für den "Volltextsuche"-Toggle.
  2. parsed trägt eine STAR-Query gegen den VOLL-Index, deren Argument selbst eine Sub-Query gegen TEXT und WP ist. Die Apostroph-eskapierten Quotes ( `\") sind STAR-spezifisch und müssen literal so wie im HAR übernommen werden.
  3. json[0].sf == "NOJSON" ist ein Fallback-Marker — der Server parst dann den parsed-String selbst statt einem client-gebauten JSON-Tree zu vertrauen. Das ist genau das, was ESearch.fn.getSearchSpecs in eui.js Z. 1461 erzeugt wenn psConfig.settings.NOJSON gesetzt ist.

Was funktioniert hat (lokal verifiziert)

LSA-Endpoint akzeptiert die nachgebaute Anfrage:

  • Schule WP8 → 3000 merged_hits, echte Drucksachen mit "Schul-" im Titel oder Volltext
  • Klimaschutz WP8 → analog
  • xyzphantasiegibberish WP8 → 0 Treffer (no_rid) — beweist dass der Filter wirkt

Was noch NICHT funktioniert (warum der Patch nicht committet wurde)

LSA: Hit-Format ist anders im Volltext-Mode

Im Standard-Browse-Mode liegen die Felder in WEV06 (Titel), WEV32 (Urheber+Datum+Drucksache+PDF). Im VOLL-Mode liefert der Server stattdessen:

  • Titel in WEV01 (nicht WEV06)
  • Doc-Art in WEV03 (z.B. "Unterrichtung", "Antrag", "Anfrage")
  • WEV32.main hat ein anderes Layout — z.B. "Unterrichtung Ministerium der Finanzen 31.03.2026 Drucksache <b>8/6792</b> (22 S.)" statt "Antrag <Urheber> <DD.MM.YYYY> Drucksache..."
  • Server filtert nicht auf type=Antrag — der erste Treffer im Test war eine Unterrichtung

Folge: Mein bestehender _parse_hit_list_dump-Parser liefert für Volltext-Hits leere Fraktionen, leeres Datum, und mischt alle Doc-Arten. Brauchbar wäre der Patch erst nach Parser-Update.

BE: Server lehnt die LSA-Syntax ab

Selbe Anfrage gegen pardok.parlament-berlin.de mit lah.lissh als source liefert:

Error parsing search: [(((/VOLL "...")) AND (/WP 19 OR WP="")) AND TYP=DOKDBE NOT (1SPERI,SPERI=JA)].
Unable to generate query. Processing aborted.

BE hat offenbar einen anderen sf-Index (vielleicht nicht VOLL), eine andere Sperre-Regel (1SPERI existiert evtl. nicht), oder ein anderes Escaping. Ohne separaten HAR-Trace gegen pardok kann ich das nicht sicher rekonstruieren.

Aufsplitten

Für die saubere Umsetzung lege ich zwei Folge-Issues an:

  • LSA: Parser-Update für VOLL-Mode (#wird-folgen) — _parse_hit_list_dump um WEV01/WEV03 erweitern, client-side Antrag-Filter via WEV03=="Antrag", flexible URHEBER_DATUM-Regex.
  • BE: HAR-Trace + eigenes VOLL-Schema (#wird-folgen) — gleiche DevTools-Übung wie für LSA, gegen pardok.parlament-berlin.de/portala/.

#13 selbst bleibt offen als Umbrella, bis beide Sub-Issues durch sind.

## Status nach HAR-Trace gegen padoka HAR-Trace einer echten Volltextsuche auf https://padoka.landtag.sachsen-anhalt.de/portal/ liefert das Schema: ```json { "search": { "lines": {"1": "Schule", "2": "8;7;6;5;4;3;2;1", "11": "on", ...}, "parsed": "(((/VOLL \"((/TEXT (`\\\"Schule`\\\")) AND (/WP 8;7;6;5;4;3;2;1 OR WP=`\\\"`\\\"))\")) AND (/WP 8;7;6;5;4;3;2;1 OR WP=\"\")) AND TYP=DOKDBE NOT (1SPERI,SPERI=JA)", "json": [{"t": "\"true\"", "tn": "term", "sf": "NOJSON", "op": "eq"}] } } ``` Drei Hebel mussten zusammenkommen: 1. **`lines["1"]=query`, `lines["11"]="on"`** — Form-state-Mirror für den "Volltextsuche"-Toggle. 2. **`parsed`** trägt eine STAR-Query gegen den `VOLL`-Index, deren Argument selbst eine Sub-Query gegen `TEXT` und `WP` ist. Die Apostroph-eskapierten Quotes (`` `\"``) sind STAR-spezifisch und müssen literal so wie im HAR übernommen werden. 3. **`json[0].sf == "NOJSON"`** ist ein Fallback-Marker — der Server parst dann den `parsed`-String selbst statt einem client-gebauten JSON-Tree zu vertrauen. Das ist genau das, was `ESearch.fn.getSearchSpecs` in `eui.js` Z. 1461 erzeugt wenn `psConfig.settings.NOJSON` gesetzt ist. ## Was funktioniert hat (lokal verifiziert) LSA-Endpoint akzeptiert die nachgebaute Anfrage: - `Schule WP8` → 3000 merged_hits, echte Drucksachen mit "Schul-" im Titel oder Volltext - `Klimaschutz WP8` → analog - `xyzphantasiegibberish WP8` → 0 Treffer (`no_rid`) — beweist dass der Filter wirkt ## Was noch NICHT funktioniert (warum der Patch nicht committet wurde) ### LSA: Hit-Format ist anders im Volltext-Mode Im Standard-Browse-Mode liegen die Felder in `WEV06` (Titel), `WEV32` (Urheber+Datum+Drucksache+PDF). Im VOLL-Mode liefert der Server stattdessen: - **Titel in `WEV01`** (nicht WEV06) - **Doc-Art in `WEV03`** (z.B. "Unterrichtung", "Antrag", "Anfrage") - **`WEV32.main` hat ein anderes Layout** — z.B. `"Unterrichtung Ministerium der Finanzen 31.03.2026 Drucksache <b>8/6792</b> (22 S.)"` statt `"Antrag <Urheber> <DD.MM.YYYY> Drucksache..."` - **Server filtert nicht auf type=Antrag** — der erste Treffer im Test war eine Unterrichtung Folge: Mein bestehender `_parse_hit_list_dump`-Parser liefert für Volltext-Hits leere Fraktionen, leeres Datum, und mischt alle Doc-Arten. Brauchbar wäre der Patch erst nach Parser-Update. ### BE: Server lehnt die LSA-Syntax ab Selbe Anfrage gegen `pardok.parlament-berlin.de` mit `lah.lissh` als source liefert: ``` Error parsing search: [(((/VOLL "...")) AND (/WP 19 OR WP="")) AND TYP=DOKDBE NOT (1SPERI,SPERI=JA)]. Unable to generate query. Processing aborted. ``` BE hat offenbar einen anderen sf-Index (vielleicht nicht `VOLL`), eine andere Sperre-Regel (`1SPERI` existiert evtl. nicht), oder ein anderes Escaping. Ohne separaten HAR-Trace gegen pardok kann ich das nicht sicher rekonstruieren. ## Aufsplitten Für die saubere Umsetzung lege ich zwei Folge-Issues an: - **LSA: Parser-Update für VOLL-Mode** (#wird-folgen) — `_parse_hit_list_dump` um WEV01/WEV03 erweitern, client-side Antrag-Filter via WEV03=="Antrag", flexible URHEBER_DATUM-Regex. - **BE: HAR-Trace + eigenes VOLL-Schema** (#wird-folgen) — gleiche DevTools-Übung wie für LSA, gegen `pardok.parlament-berlin.de/portala/`. #13 selbst bleibt offen als Umbrella, bis beide Sub-Issues durch sind.
Author
Owner

Verworfen — der Use-Case "echte Server-side Volltextsuche" wird zurückgestellt, weil das Schema zwischen LSA und BE nicht uniform ist (LSA akzeptiert /VOLL, BE lehnt es mit Unable to generate query ab) und ein gemischtes Verhalten der Adapter (NRW+MV mit Volltext, BE+LSA ohne) verwirrender ist als ein einheitlicher Title-Filter überall.

Stattdessen wird die Suche in allen vier Adaptern auf Title + Urheber + Schlagwort über den gesamten Datenbestand der laufenden WP umgestellt, sortiert newest-first. Tracking dafür: #18.

Wenn die Volltextsuche später wieder gewünscht wird (sobald sie für alle vier Adapter gleich umsetzbar ist), kann dieses Issue reopened werden — die HAR-Findings im vorigen Kommentar sind wertvoll und sollten erhalten bleiben.

Verworfen — der Use-Case "echte Server-side Volltextsuche" wird zurückgestellt, weil das Schema zwischen LSA und BE nicht uniform ist (LSA akzeptiert `/VOLL`, BE lehnt es mit `Unable to generate query` ab) und ein gemischtes Verhalten der Adapter (NRW+MV mit Volltext, BE+LSA ohne) verwirrender ist als ein einheitlicher Title-Filter überall. Stattdessen wird die Suche in allen vier Adaptern auf **Title + Urheber + Schlagwort über den gesamten Datenbestand der laufenden WP** umgestellt, sortiert newest-first. Tracking dafür: #18. Wenn die Volltextsuche später wieder gewünscht wird (sobald sie für alle vier Adapter gleich umsetzbar ist), kann dieses Issue reopened werden — die HAR-Findings im vorigen Kommentar sind wertvoll und sollten erhalten bleiben.
Sign in to join this conversation.
No description provided.