v1.9.6: pendelbus/shuttlebus naar zwak + zwak-alleen moet titel-anker hebben

Aanleiding: Omroep Brabant-golfbericht (Solheim Cup Cromvoirt, 11-09-2026) kwam door
het filter. 'pendelbus' stond op de whitelist (evenementpendel vanaf station Den Bosch),
en 'station'+'bus'+'pendelbus' in de samenvatting was samen al drempel 3.

- pendelbus/shuttlebus: whitelist + medium -> zwak (1), één familie (synoniemen tellen
  niet als twee onafhankelijke signalen).
- _filter_passes: bij uitsluitend zwakke hits moet minstens één zwak woord in de TITEL
  staan; zwakke woorden alleen in de samenvatting zijn geen bewijs.
- validate_filter.py: invariant 1 uitgebreid + 3 regressiecases (golfbericht, zwak-alleen
  samenvatting, positieve controle met titel-anker).
- _filter_summary()/docstrings gelijkgetrokken met de werkelijke gewichten.
This commit is contained in:
Eddy de Vink 2026-09-11 07:49:05 +02:00
commit 1f168b2f54
3 changed files with 59 additions and 27 deletions

View file

@ -56,9 +56,12 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
# Gewicht 2 (medium): één woord in de titel volstaat; in de samenvatting moet er
# een tweede OV-woord bijkomen.
# Gewicht 1 (zwak/dubbelzinnig: los "bus", "breng", "overweg", "overwegen", "sein",
# "kaartje", "opwaarderen", "rails"): moet met een ander OV-woord combineren (een
# tweede, andere familie — dezelfde zwakke familie in titel én samenvatting telt
# niet als combinatie). Weert zo "VW-bus"/"schoolbus", "weer op de rails", het
# "kaartje", "opwaarderen", "rails", "pendelbus"): moet met een ander OV-woord
# combineren (een tweede, andere familie — dezelfde zwakke familie in titel én
# samenvatting telt niet als combinatie) EN minstens één zwak woord moet in de
# TITEL staan: een combinatie van alleen zwakke woorden in de samenvatting is
# geen bewijs (het golfbericht met "station" + "pendelbus" + "bus" in de
# samenvatting is geen OV-nieuws). Weert zo "VW-bus"/"schoolbus", "weer op de rails", het
# werkwoord "brengen" ("hoe breng je ... in beeld"), het idioom "goed met elkaar
# overweg kunnen", het werkwoord "overwegen" ("gratieverlening overwegen"), het
# werkwoord "opwaarderen" ("een wijk opwaarderen") en brandweerjargon
@ -101,7 +104,15 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
# woord nooit de doorslag kan geven. Bekende dubbelzinnige:
# trein, bus, station(s), sprinter, reiziger, vervoerder,
# inchecken/uitchecken, sein, overweg, breng, kaartje,
# opwaarderen, rails, dienstregeling, chipkaart, ontsporen.
# opwaarderen, rails, dienstregeling, chipkaart, ontsporen,
# pendelbus/shuttlebus (evenement-/hotel-/parkeerpendel;
# synoniemen -> bewust één familie, anders tellen twee
# synoniemen als twee onafhankelijke signalen).
# EXTRA REGEL voor zwak-alleen: staat er uitsluitend zwak
# bewijs in de SAMENVATTING (geen enkele zwakke hit in de
# titel), dan niet doorlaten. Twee/meer zwakke woorden in
# de samenvatting zijn anders samen al 3 punten
# ("station" + "pendelbus" + "bus" in een golfverslag).
# 3. BLACKLIST -> gematchte woorden zonder OV-waarde (stationsstraat,
# stationair, stationcar, ...); blokkeren de familie niet.
#
@ -128,13 +139,14 @@ MATCH_FILTER_FAMILIES: list[tuple[str, int]] = [
# medium (2)
(r"\btrams?\b", 2),
(r"\bmetro(?:'?s)?\b", 2),
(r"\b(?:openbaar vervoer|lightrail|pendelbus|shuttlebus|halte|bushokje|busbaan)\w*\b", 2),
(r"\b(?:openbaar vervoer|lightrail|halte|bushokje|busbaan)\w*\b", 2),
(r"\b(?:ov-chipkaart|ovpay|ov-pay|studentenreisproduct)\w*\b", 2),
(r"\b(?:ov-staking|conducteursstaking)\w*\b", 2),
(r"\bconduct(?:eur|eurs)\b", 2), # trein-/buspersoneel: onmiskenbaar OV-jargon
(r"\b(?:pont|ponten)\b", 2), # waterbus/veerdienst
# zwak (1) — dubbelzinnig, moet combineren
(r"(?<!-)\bbus(?:sen)?\b", 1), # ná een koppelteken geen OV: 'VW-bus', 'school-bus'
(r"\b(?:pendelbus|shuttlebus)\w*\b", 1), # evenement-/hotel-/parkeerpendel ('pendelbus naar de golfbaan') is geen OV; synoniemen bewust in één familie
(r"\b(?:inchecken|uitchecken)\w*\b", 1), # ook op luchthavens/hotels — geen OV-jargon, moet combineren
(r"\bsprinters?\b", 1), # ook atleet ('sprinter verpulvert record') — aparte familie, moet combineren
(r"\bvervoerders?\b", 1), # ook logistiek ('vrachtvervoerder') — aparte familie, moet combineren
@ -201,7 +213,7 @@ MATCH_FILTER_WHITELIST: frozenset[str] = frozenset({
"busstaking", "tramhalte", "tramlijn", "tramnet", "tramverkeer",
"metrostation", "metrolijn", "metroverkeer",
# Tarief/vervoerder-jargon
"ov-chipkaart", "ov-staking", "lightrail", "pendelbus", "shuttlebus",
"ov-chipkaart", "ov-staking", "lightrail",
"conducteur", "conducteurs",
"openbaar vervoer",
})
@ -378,16 +390,19 @@ class RSSBot(Plugin):
"""Gewogen familiescore: doorlaten bij 2×(titelscore) + 1×(samenvattingscore) >= drempel.
Sterke families (gewicht 3) halen de drempel al met één woord in titel óf
samenvatting. Medium (2) in de titel ook; in de samenvatting moet een tweede
OV-woord bijkomen. Zwakke woorden (losse 'bus', 'breng', 'overweg', 'overwegen',
'sein', 'opwaarderen', 'kaartje', 'rails') moeten combineren met een ander
OV-woord; dezelfde zwakke familie in titel én samenvatting is géén combinatie.
Weert oa 'VW-bus' (bus matcht niet ná een koppelteken), 'schoolbus', 'bussen
rijden om' in een woningbrand-lead, het werkwoord 'brengen', het idioom 'goed
met elkaar overweg kunnen', 'een wijk opwaarderen', brandweerjargon
'sein brandmeester' en figuratief 'weer op de rails'.
Bewust doorgelaten: krant 'De Metro'/café 'De Tram' (medium), film-'treinramp'
en atleet-'sprinter' (sterk)."""
samenvatting. Medium (2) in de titel ook; in de samenvatting moet er een
tweede OV-woord bijkomen.
Zwakke woorden (losse 'bus', 'breng', 'overweg', 'overwegen', 'sein',
'opwaarderen', 'kaartje', 'rails', 'pendelbus') moeten combineren met een
ander OV-woord én minstens één zwak woord moet in de titel staan; dezelfde
zwakke familie in titel én samenvatting is géén combinatie. Weert oa
'VW-bus' (bus matcht niet ná een koppelteken), 'schoolbus', 'bussen rijden
om' in een woningbrand-lead, het werkwoord 'brengen', het idioom 'goed met
elkaar overweg kunnen', 'een wijk opwaarderen', brandweerjargon
'sein brandmeester', figuratief 'weer op de rails' en een golfverslag
waarin de samenvatting 'station' + 'pendelbus' + 'bus' noemt.
Bewust doorgelaten: krant 'De Metro'/café 'De Tram' (medium) en atleet-
'sprinter' in combinatie met ander OV-woord."""
# Whitelist: één onmiskenbare OV-term (titel óf samenvatting) laat altijd door.
if RSSBot._whitelist_hit(title) or RSSBot._whitelist_hit(summary):
return True
@ -396,8 +411,12 @@ class RSSBot(Plugin):
score = 2 * sum(w for _, w in t_hits) + sum(w for _, w in s_hits)
if score < MATCH_FILTER_THRESHOLD:
return False
# Alleen zwakke families? Dan moet een tweede (verschillende) familie meetellen.
if max(w for _, w in t_hits + s_hits) == 1 and len({i for i, _ in t_hits + s_hits}) < 2:
if max(w for _, w in t_hits + s_hits) == 1:
# Alleen zwakke families: dan moeten ≥2 verschillende families meetellen
# én minstens één zwak woord moet in de TITEL staan. Zwakke woorden in
# de samenvatting alleen zijn geen bewijs ('station' + 'pendelbus' +
# 'bus' in een golfverslag telt anders op tot de drempel).
if not t_hits or len({i for i, _ in t_hits + s_hits}) < 2:
return False
return True
@ -406,17 +425,20 @@ class RSSBot(Plugin):
"""Mensvriendelijke weergave van het vaste gewogen match-filter."""
return (
"**OV-matchfilter (vast in code, gewogen families):**\n"
"- Sterk (3): vervoerders (NS, Arriva, ProRail, Qbuzz, ...), spoorinfra (spoorlijn, spoorwegovergang, seinstoring, perron), "
"station(s), bushalte/buslijn/bushokje/busstation, intercity/sprinter/streekvervoer/veerdienst/buurtbus, 'lijn 12'\n"
"- Medium (2): tram, metro, openbaar vervoer/dienstregeling/inchecken, halte, bushokje, busbaan, vervoerder, reiziger, "
"ov-chipkaart/ovpay/reisproduct, ov-staking, pont\n"
"- Zwak (1): losse 'bus' (niet ná een koppelteken), 'trein', 'breng', 'overweg', 'overwegen', 'sein', 'kaartje', 'opwaarderen', 'rails' — moet combineren met een ander OV-woord\n\n"
"- Sterk (3): vervoerders (NS, Arriva, ProRail, Qbuzz, ...), spoorinfra (spoorlijn, spoorwegovergang, perron, spoorstoring), "
"trein-/metro-/busstation, samenstellingen als bushalte/buslijn/bushokje/busbaan/busverkeer, treinstoring/treinverkeer/treinstaking, "
"intercity/streekvervoer/veerdienst/buurtbus/belbus, 'lijn 12'\n"
"- Medium (2): tram, metro, openbaar vervoer, lightrail, halte, pont, ov-chipkaart/ovpay, ov-staking, conducteur\n"
"- Zwak (1): losse 'bus' (niet ná een koppelteken), 'pendelbus'/'shuttlebus', 'trein', 'station(s)', 'breng', 'overweg', 'overwegen', "
"'sein', 'kaartje', 'opwaarderen', 'rails', 'inchecken'/'uitchecken', 'dienstregeling', 'chipkaart', 'reisproduct', 'vervoersbewijs', "
"'ontsporen', 'reiziger', 'vervoerder', 'sprinter' — moet combineren met een ander OV-woord, met minstens één zwak woord in de titel\n\n"
"**Whitelist (altijd door):** één onmiskenbare OV-term in titel óf samenvatting is genoeg "
"(NS, ProRail, Nederlandse Spoorwegen, spoorstoring, perron, bushalte, metrostation, conducteur, "
"ov-chipkaart, ovpay, openbaar vervoer, dienstregeling, ...)\n\n"
"ov-chipkaart, ovpay, openbaar vervoer, ...)\n\n"
"Uitgesloten (blacklist): woorden die wél matchen maar geen OV-waarde hebben, "
"o.a. straatnamen rond stations ('stationsstraat', 'stationsweg', ...) en niet-OV-vormen als 'stationcar' en 'stationair'\n\n"
"Doorlaten: `2×(titel-score) + 1×(samenvatting-score) ≥ 3`"
"Doorlaten: `2×(titel-score) + 1×(samenvatting-score) ≥ 3`; bij uitsluitend zwakke woorden tellen alleen "
"≥2 verschillende families én een zwak woord in de titel"
)
async def _send(

View file

@ -1,5 +1,5 @@
id: nl.eddydevink.rss
version: 1.9.5
version: 1.9.6
modules:
- buzzrss
main_class: RSSBot

View file

@ -77,6 +77,7 @@ AMBIGUOUS = [
"inchecken", "uitchecken", "sein", "overweg", "overwegen", "breng",
"kaartje", "opwaarderen", "rails", "dienstregeling", "chipkaart",
"reisproduct", "vervoersbewijs", "vervoerbewijs",
"pendelbus", "shuttlebus",
"ontspor", "ontspoor", "ontspoord", "ontspoorde", "ontsporing",
]
print(f"== invariant 1: {len(AMBIGUOUS)} dubbelzinnige woorden -> alleen zwak (1), nooit whitelist ==")
@ -123,7 +124,16 @@ GOLDEN = [
("Fietser gewond op de Stationsstraat", "Het slachtoffer is naar het ziekenhuis gebracht.", False),
("Kunstenaar maakt kaartje voor het museum", "", False),
("Benzineprijs stijgt opnieuw", "De pomp wordt duurder door de olieprijs.", False),
# geweerd — zwakke woorden alléén in de samenvatting (geen titel-anker).
# Echt geval: Omroep Brabant, Solheim Cup-golfverslag (11-09-2026) — 'pendelbus'
# stond op de whitelist en 'station'+'pendelbus'+'bus' in de samenvatting was
# samen score 3.
("Golf-fans uit de hele wereld naar Cromvoirt: 'Beter dan sterrenrestaurant'",
"Op het station in Den Bosch stapte de oud-prof in de pendelbus naar Bernardus Golf. "
"In de bus bleek ze zeker niet de enige Amerikaanse.", False),
("Toernooi trekt duizenden bezoekers", "Vanaf het station rijden pendelbussen en extra bussen.", False),
# door — echte OV-berichten
("Trein vertraagd, reizigers pakken de bus", "De storing duurde een uur.", True),
("Station Amersfoort krijgt nieuwe perrons", "De NS laat de perrons vernieuwen.", True),
("Trein ontspoorde bij Weesp", "De spoorlijn was uren dicht.", True),
("Bushalte aan de Markt wordt verplaatst", "De halte gaat 200 meter opschuiven.", True),