v1.12.0: max-2-stelsel — enkele familie-hit kan nooit solo doorlaten

- Gewicht-3-tier vervallen: alle families wegen max 2; drempel blijft 3,
  dus elke echte OV-bericht combineert met een tweede signaal (besluit
  2026-09-12, gevalideerd op 205 productie-artikelen).
- 'hermes' uit vervoerders + whitelist -> eigen zwakke familie (achternaam
  Ton Hermes, Margraten false positive 2026-09-12).
- Whitelist geen auto-pass meer: titel-hit = +2 titel-anker (onmiskenbare
  termen behouden hun recall; samenvatting blijft inert).
- Completheids-audit (2026-09-12): +20 termen/frasen —
  sterk: spoorsector, spoorbeheerder, spoornet, spoorvervoerder,
  treinvertraging/hinder/uitval/verstoring, ertms, teso, rangeer/rangeren,
  ice-<stad>/ice-trein, maaslijn/nedersaksenlijn/kamperlijn, ns international,
  deutsche bahn, european sleeper, italo, nightjet;
  zwak: busongeluk, busvervoer, spoor, staking, passagier(+reizigers),
  vervoer, vervoersbedrijf, vervoerregio, ritten, bovenleiding, abonnement.
- validate_filter.py: invariant gewicht in {1,2}, 46 dubbelzinnige + 39
  onmiskenbare termen, 52 regressie-cases groen.
This commit is contained in:
Eddy de Vink 2026-09-12 19:53:41 +02:00
commit 7997d7572a

View file

@ -73,12 +73,16 @@ def check(cond: bool, msg: str):
# --- Invariant 1: dubbelzinnige woorden mogen nooit sterk/medium of whitelist zijn
AMBIGUOUS = [
"trein", "bus", "station", "stations", "sprinter", "sprinters",
"reiziger", "reizigers", "vervoerder", "vervoerders",
"reiziger", "reizigers", "passagier", "passagiers", "vervoerder", "vervoerders",
"inchecken", "uitchecken", "sein", "overweg", "overwegen", "breng",
"kaartje", "opwaarderen", "rails", "dienstregeling", "chipkaart",
"reisproduct", "vervoersbewijs", "vervoerbewijs",
"pendelbus", "shuttlebus",
"ontspor", "ontspoor", "ontspoord", "ontspoorde", "ontsporing",
# audit 2026-09-12 (max-2-stelsel + completheids-audit)
"hermes", "spoor", "staking", "stakingen", "vervoer", "vervoersbedrijf",
"vervoerregio", "ritten", "bovenleiding", "abonnement",
"busongeluk", "busongelukken", "busvervoer",
]
print(f"== invariant 1: {len(AMBIGUOUS)} dubbelzinnige woorden -> alleen zwak (1), nooit whitelist ==")
for w in AMBIGUOUS:
@ -93,6 +97,12 @@ UNAMBIGUOUS = [
"conducteur", "ovpay", "ns", "prorail",
"treinstoring", "halte", "openbaar vervoer", "intercity", "buurtbus",
"veerdienst", "lijn 12", "ov-chipkaart", "spoorbrug",
# audit 2026-09-12 (completheids-audit, max-2-stelsel)
"spoorsector", "spoorbeheerder", "spoornet", "spoorvervoerder",
"treinvertraging", "treinhinder", "treinuitval", "treinverstoring",
"ertms", "teso", "rangeerterrein", "rangeren", "ice-berlijn",
"maaslijn", "nedersaksenlijn", "kamperlijn",
"ns international", "deutsche bahn", "european sleeper", "italo", "nightjet",
]
print(f"== invariant 2: {len(UNAMBIGUOUS)} onmiskenbare OV-termen -> sterk/medium of whitelist ==")
for w in UNAMBIGUOUS:
@ -100,10 +110,10 @@ for w in UNAMBIGUOUS:
check(whitelisted(w) or any(g >= 2 for g in gew),
f"{w!r} heeft whitelist of gewicht >= 2 (gewichten {sorted(gew)})")
# --- Invariant 3: patronen compileren en gewichten geldig
print("== invariant 3: alle 24+ families compileren, gewicht in {1,2,3} ==")
# --- Invariant 3: patronen compileren en gewichten geldig (max-2, besluit 2026-09-12)
print("== invariant 3: alle families compileren, gewicht in {1,2} ==")
for p, w in FAMILIES:
check(w in (1, 2, 3), f"familie {p[:40]!r} heeft geldig gewicht {w}")
check(w in (1, 2), f"familie {p[:40]!r} heeft geldig gewicht {w} (max-2-stelsel)")
check(len(COMPILED) == len(FAMILIES), "alle families compileren")
# --- Regressie: gedrag van _filter_passes op bekende cases (precision-first)
@ -183,6 +193,26 @@ GOLDEN = [
("Lijn 12 rijdt vanaf maandag een andere route", "De route gaat via het centrum.", True),
("Werkzaamheden aan perron", "Het perron wordt volgende maand opgeknapt.", True),
("Reactie Nederlandse Spoorwegen", "De Nederlandse Spoorwegen zeggen meer in te zetten op punctualiteit.", True),
# max-2-stelsel (besluit 2026-09-12): geproduceerd op 205 productie-artikelen
# + LLM-audit. Geen enkele familie weegt meer dan 2 → één hit kan nooit solo
# doorlaten; whitelist-term in de titel telt +2 (titel-anker).
("Veel animo voor 'adopteren' graf op Amerikaanse begraafplaats Margraten",
"…'Het voelde echt alsof je in de rij stond voor festivaltickets'… 'Ik ben bang dat mensen met de slaapzak voor de laptop gaan liggen', zei Ton Hermes eerder.", False),
# ^ echte false positive 2026-09-12: 'hermes' (achternaam) scoorde vroeger 3 via de vervoerdersfamilie
("TESO onderzoekt komst derde veerboot tussen Texel en Den Oever",
"Veerdienst TESO onderzoekt of het haalbaar is om een derde veerboot in te zetten tussen Oudeschild en Den Oever.", True), # teso(2) + veerboot(2) = 4
("Vrouw buschauffeur Rob zwaar aangeslagen na dodelijk ongeval",
"Bij het dodelijke busongeluk in Zwitserland zat de politicus achter het stuur.", True), # buschauffeur(2) + busongeluk(1) = 3
("Vakbonden noemen stakingsdag in het OV 'een geslaagde dag'",
"Gisteren was de landelijke stakingsdag in het OV; medewerkers legden in de spoorsector 24 uur het werk neer.", True), # ov(2) + staking(1) + spoorsector(2)
("Dodental veerbootbrand Filipijnen loopt verder op", "Zeker 76 mensen kwamen om bij de brand op de veerboot.", False), # één veerboot-hit (2) = geen NL-OV
("ICE Berlijn voegt na Hamburg vanaf december ook stop in Bremen toe",
"De ICE Berlijn gaat vanaf december via Bremen en Hamburg Hauptbahnhof naar Berlijn rijden. Dat meldt NS International.", True), # ice-berlijn(2) + ns(2) = 4
("Laatste noot voor vernielde stationspiano Eindhoven",
"De piano in het station was jarenlang een begrip reizigers produceerden er filmpjes.", False), # cultuurstukje; 'station'/'reizigers' zwak-alleen zonder titel-anker
("Meerdere doden na busongeluk in Zwitserland", "Het busongeluk gebeurde naast de snelweg bij een afrit.", False), # busongeluk(1) alleen = zwak-alleen
("Antwerpen ontvangt eerste ICE", "De eerste ICE van Deutsche Bahn rijdt vanaf volgende maand naar Antwerpen.", False), # bewust geweerd: 'ice van' is NIET opgenomen (ijs-botsing); deutsche bahn(2) alleen < 3 — was vóór de wijziging óók al geweerd
("NS verlengt noodregeling tot volgend jaar", "De vervoerder kampt met personeelstekorten.", True), # ns(2) + vervoerder(1) = 3
]
print(f"== regressie: {len(GOLDEN)} cases (precision-first) ==")
for t, s, want in GOLDEN: