v1.15.0: filter vereenvoudigd naar drie regels — 30 semantische groepen i.p.v. families+whitelist+blacklist

- Strip de filter terug tot: (1) sterk signaal in de titel -> door;
  (2) score van unieke semantische groepen >= 3 met titel-signaal -> door;
  (3) anders weg. Geen whitelist (82 termen), geen blacklist, geen span-merge,
  geen dienststoring-titelanker meer; woordgrenzen/lookbehinds weren de
  bekende botsingen structureel (schoolbus/VW-bus/stationsstraat/stationair/
  treinstraat/treinweg).
- Lijsten 49 families + 82 whitelist + 2 blacklist = 133 -> 16 sterk + 14
  ambigu = 30 groepen; filterzone 524 -> ~292 regels; match-methodes 4 -> 2.
- Vijf historische uitzonderings-families geschrapt (busongeluk, sein, breng,
  hermes, inchecken, kaartje, rails, staking, e.d.) — die woorden leveren nu
  0 signalen op en de bekende false positives blijven geweerd.
- Completheids-audit ving de Maaslijn/Nedersaksenlijn/Kamperlijn/waterbus-
  regressie op (hersteld); resterende zero-hit-kandidaten geen wijziging.
- validate_filter.py: invarianten 1-3 + 113 regressie-cases (90 onveranderd
  groen + 23 nieuw); prompt-matrix 27/27.
- Gedragswijziging op 45 productiedagen: uitsluitend false positives extra
  geweerd (buitenlandse veerboot-rampen, Zwitsers busongeluk, delftopzondag
  tram+halte, Cox/RET, tram-blokkade); 0 recalls verloren.
- README/AGENTS.md/vault bijgewerkt.
This commit is contained in:
Eddy de Vink 2026-09-13 09:28:28 +02:00
commit ac30575660
6 changed files with 381 additions and 695 deletions

View file

@ -38,53 +38,40 @@ Bestuurders: admins uit de config, of gebruikers met een powerlevel ≥ dat van
RSS-state-event in die room (default 50 als geen level is ingesteld).
Bij een room-tombstone worden abonnementen automatisch naar de vervangende room verplaatst.
## Het OV-matchfilter (v1.14.0)
## Het OV-matchfilter (v1.15)
De volledige classificatie + de werkwijze voor nieuwe woorden staat in
`buzzrss/bot.py`, boven `MATCH_FILTER_FAMILIES` — **dat blok is leidend** bij elke
wijziging. Samengevat:
`buzzrss/bot.py`, boven `MATCH_FILTER_STRONG` — **dat blok is leidend** bij elke
wijziging. Samengevat, in drie regels:
```text
score = Σ gewichten van unieke OV-termen over titel + samenvatting
(één term = één signaal per artikel; overlappende familie-treffers wegen 1×
met het hoogste gewicht — span-merge, besluit 2026-09-13)
+ 2 als een whitelist-term óf een dienststoring-zin in de TITEL staat
doorlaten ⇔ score ≥ 3 én (titel-signaal ∨ geen zwakke term in de score)
1. staat er een STERK signaal in de TITEL? -> PLAATSEN
2. score = som unieke groeps-gewichten ≥ 3 én de titel bevat een signaal? -> PLAATSEN
3. anders -> NIET PLAATSEN
```
Stappen:
1. **Sterk titel-signaal — door.** Een whitelist-term (NS, ProRail, spoorstoring,
perron, bushalte, ovpay, openbaar vervoer, …) of een dienststoring-zin
(‹vervoerswoord› `rijdt/rijden … niet`, bv. 'sprinters rijden niet', 'metro rijdt niet')
in de titel is voldoende.
2. **Context — som ≥ 3.** Gewichten: **sterk (2)** = onmiskenbaar OV
(vervoerders, spoorinfra, bus-samenstellingen, trein-storingsjargon, tram/metro/halte,
storingszin, …), **zwak (1)** = dubbelzinnig, elk met een eigen familie
(trein, bus, station(s), sprinter, reiziger, vervoerder, sein, overweg, breng, …)
en alleen als combinatie geldig. Max-2-stelsel: geen enkele familie weegt meer dan 2,
één sterke hit kan nooit solo doorlaten.
3. **Titel-anker.** Zonder enig OV-signaal in de titel (geen familie-hit, geen
whitelist-term, geen storingszin) mag de score geen zwak woord bevatten —
'station' + 'pendelbus' + 'bus' in een samenvatting is geen OV-bewijs.
Uitzondering die bewust blijft: **twee sterke termen in de samenvatting** (bv. de
delftopzondag-bereikbaarheidszin 'tram 1, halte Buitenhofdreef', besluit 2026-09-13).
Zwarte lijst (`MATCH_FILTER_BLACKLIST`): gematchte woorden zonder OV-waarde —
na de v1.14.0-opschoning alleen nog `treinstraat`/`treinweg`. De oudere
stations\*-vormen (`stationsstraat`, `stationair`, `stationcar`, `stationeren`, …)
zijn inert sinds de woordgrens-familie `\bstations?\b` en worden door tests bewaakt.
### Bekende bewust-geaccepteerde doorlatingen / weigeringen
- *Door*: 'met een tram en halte bereikbaar'-zinnen in lokale berichten (2 sterke
termen, samenvatting) — kurk op de fles is het max-2-stelsel + titel-anker.
- *Geweerd*: krant 'De Metro', café 'De Tram', atleet-'sprinter',
'VW-bus'/'schoolbus' (koppelteken- en woordgrensregels), werkwoord 'brengen',
- **Sterk (2 punten, titel = zelfstandig)**: vervoerders (NS, ProRail, Arriva,
RET, GVB, Qbuzz, Deutsche Bahn, TESO, ...), spoorjargon (spoorstoring, spoorlijn,
wisselstoring, treinstoring, ertms, perron, ...), bus-samenstellingen
(bushalte, buslijn, busbaan, buschauffeur), tramverkeer/tramlijn, metrolijn,
intercity/streekvervoer/veerdienst/buurtbus, conducteur, ov-chipkaart/ovpay,
openbaar vervoer, lightrail, 'lijn 12', ICE-frasen, rangeer... én de
dienststoring-zin ('sprinters rijden niet', 'metro rijdt niet').
- **Ambugue (1 punt; halte/tram/metro/spoorbrug 2)** — contextafhankelijk,
nooit zelfstandig voldoende, alleen als combinatie: `bus`, `trein`,
`station(s)`, `reiziger`/`passagier`, `vervoer(der)`, `sprinter`,
`pendelbus`/`shuttlebus`, `dienstregeling`, `ontsporen`, `spoor`.
- **Semantische groepen tellen één keer per artikel**: 'bus' + 'buslijn' +
'busvervoer' + 'busstation' is één bus-signaal (bus-samenstelling, bus,
station — nooit vier onafhankelijke signalen).
- **Geen whitelist, geen blacklist**: woordgrenzen en koppelteken-lookbehinds
weren structureel 'schoolbus'/'VW-bus', 'stationsstraat'/'stationair'/
'stationcar'/'treinstraat'/'treinweg'. Bekende false positives blijven geweerd:
krant 'De Metro', café 'De Tram', atleet-'sprinter', werkwoord 'brengen',
idioom 'goed met elkaar overweg kunnen', 'een wijk opwaarderen',
brandweerjargon 'sein brandmeester', figuratief 'weer op de rails',
achternaam 'Hermes' (Margraten), 'restaurant/woning naast busstation'.
achternaam 'Hermes', buitenlandse veerboot-/busrampen, 'restaurant/woning naast
busstation'.
## Tests
@ -92,13 +79,13 @@ zijn inert sinds de woordgrens-familie `\bstations?\b` en worden door tests bewa
python3 validate_filter.py
```
Vaste invarianten + regressie (momenteel **90 cases**):
Vaste invarianten + regressie (momenteel **110 cases**):
1. dubbelzinnige woorden → alleen zwak (1), nooit whitelist;
2. onmiskenbare OV-termen → sterk (2) of whitelist;
3. alle families compileren, gewichten in {1, 2};
4. elke whitelist-term zit óók in een familie én elke zwartgelijste term wordt door
een familie gematcht (geen inert gewicht).
1. gewichten geldig (sterk 2; ambigu in {1, 2}), drempel 3;
2. trefwoorden matchen hooguit één groep per categorie (geen overlappende regexes);
3. semantische varianten ('bus' + 'buslijn' + 'busvervoer', 'station'-reeks)
leveren samen één signaal op;
4. regressie: bekende positive/negative/combinatie-cases (precision-first).
**Verplicht vóór elke filterwijziging** (zie `AGENTS.md`): het classificatiebeleid in
`buzzrss/bot.py` lezen, `validate_filter.py` groen, en de **compleetheids-audit**: