v1.9.9: één OV-familie telt 1x per artikel — herhaling en beide velden wegen niet dubbel

Gebruikersbesluit 2026-09-11: 'busstation' 60x in een artikel is één signaal, niet
60x de score. _filter_passes combineert titel- en samenvattingshits nu per familie
(dict over beide teksten). Gevolg (regressie-pinned): 'Pand naast busstation'
(titel+samenvatting) en 'Busstation wordt verbouwd' (zonder andere OV-term) zijn
nu geweerd; combinaties over 2 families (metro+halte, metrostation+metro) blijven
door.
This commit is contained in:
Eddy de Vink 2026-09-11 09:28:09 +02:00
commit ff7e7ac81c
4 changed files with 23 additions and 16 deletions

View file

@ -28,7 +28,7 @@ exclude_paywalled: true
# 0 = uit. # 0 = uit.
title_dedup_days: 30 title_dedup_days: 30
# OV-matchfilter: VAST in code (gewogen families + blacklist), niet via config te wijzigen. # OV-matchfilter: VAST in code (gewogen families + blacklist), niet via config te wijzigen.
# Doorlaten als titelscore + samenvattingscore >= 3 (titel weegt niet extra). Zie de families en de # Doorlaten als som van unieke OV-families >= 3; één familie telt 1× per artikel (titel + samenvatting; herhaling of beide velden = 1 signaal). Zie de families en de
# blacklist in buzzrss/bot.py (MATCH_FILTER_FAMILIES / MATCH_FILTER_BLACKLIST). # blacklist in buzzrss/bot.py (MATCH_FILTER_FAMILIES / MATCH_FILTER_BLACKLIST).
# `!rss set-filter` en `!rss filter` tonen alleen de huidige (vaste) filter. # `!rss set-filter` en `!rss filter` tonen alleen de huidige (vaste) filter.
# Users who can bypass room permission checks # Users who can bypass room permission checks

View file

@ -50,10 +50,10 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
# Gewogen OV-trefwoord-families voor het match-filter. Elke tuple = (regex, gewicht). # Gewogen OV-trefwoord-families voor het match-filter. Elke tuple = (regex, gewicht).
# Per familie wordt 1× geteld per tekst (niet per cluster/herhaling). Drempel (3): # Per familie wordt 1× geteld per ARTIKEL (titel én samenvatting samen; herhaling
# doorlaten als titelscore + samenvattingscore >= MATCH_FILTER_THRESHOLD # of het woord in beide velden = één signaal — 'busstation' 60× weegt 1×, besluit
# (titel weegt NIET extra — besluit 2026-09-11: 'restaurant naast busstation' # 2026-09-11). Drempel (3): doorlaten als de som van de unieke familie-gewichten
# als enige medium-hit in de titel is geen OV-bericht). # >= MATCH_FILTER_THRESHOLD (titel weegt niet extra).
# Gewicht 3 (sterk, onmiskenbaar OV): één woord in titel óf samenvatting volstaat. # Gewicht 3 (sterk, onmiskenbaar OV): één woord in titel óf samenvatting volstaat.
# Gewicht 2 (medium): moet combineren met een andere OV-familie; één medium-hit, # Gewicht 2 (medium): moet combineren met een andere OV-familie; één medium-hit,
# in titel óf samenvatting, haalt de drempel niet. # in titel óf samenvatting, haalt de drempel niet.
@ -96,7 +96,9 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
# NIET: station-samenstellingen (busstation/metrostation) — een bericht over # NIET: station-samenstellingen (busstation/metrostation) — een bericht over
# een pand 'naast het busstation' is geen OV-nieuws (besluit 2026-09-11); # een pand 'naast het busstation' is geen OV-nieuws (besluit 2026-09-11);
# die wegen mee via de gewogen families (medium). # die wegen mee via de gewogen families (medium).
# 2. GEWOGEN FAMILIES -> score = titel + samenvatting >= drempel (3). # 2. GEWOGEN FAMILIES -> score = som van unieke OV-families over het hele artikel
# (titel + samenvatting; per familie 1× per artikel — herhaling of beide velden
# tellen niet dubbel, besluit 2026-09-11) >= drempel (3).
# - sterk (3): onmiskenbaar OV-jargon/merken/samenvoegingen (spoorlijn, # - sterk (3): onmiskenbaar OV-jargon/merken/samenvoegingen (spoorlijn,
# bushalte, busverkeer, intercity, lijn 12, ...). # bushalte, busverkeer, intercity, lijn 12, ...).
# - medium (2): onmiskenbaar OV maar iets algemener (tram, metro, halte, # - medium (2): onmiskenbaar OV maar iets algemener (tram, metro, halte,
@ -395,9 +397,10 @@ class RSSBot(Plugin):
@staticmethod @staticmethod
def _filter_passes(title: str, summary: str) -> bool: def _filter_passes(title: str, summary: str) -> bool:
"""Gewogen familiescore: doorlaten bij titelscore + samenvattingscore >= drempel """Gewogen familiescore: elke OV-familie telt 1× per artikel (titel + samenvatting
(titel weegt NIET extra — één medium-hit in de titel, 'restaurant naast samen; herhaling of beide velden = één signaal). Doorlaten bij som >= drempel.
busstation', is geen OV-bewijs; besluit 2026-09-11). (titel weegt NIET extra — één medium-hit, 'restaurant naast busstation',
is geen OV-bewijs; besluit 2026-09-11).
Sterke families (gewicht 3) halen de drempel al met één woord in titel óf Sterke families (gewicht 3) halen de drempel al met één woord in titel óf
samenvatting. Medium (2) moet combineren met een andere OV-familie. samenvatting. Medium (2) moet combineren met een andere OV-familie.
@ -417,15 +420,18 @@ class RSSBot(Plugin):
return True return True
t_hits = RSSBot._fam_hits(title) t_hits = RSSBot._fam_hits(title)
s_hits = RSSBot._fam_hits(summary) s_hits = RSSBot._fam_hits(summary)
score = sum(w for _, w in t_hits) + sum(w for _, w in s_hits) # Eén familie telt 1× per ARTIKEL (titel én samenvatting samen): 'busstation'
# in beide velden, of 60× in de tekst, blijft één signaal (besluit 2026-09-11).
combined = dict(t_hits + s_hits)
score = sum(combined.values())
if score < MATCH_FILTER_THRESHOLD: if score < MATCH_FILTER_THRESHOLD:
return False return False
if max(w for _, w in t_hits + s_hits) == 1: if max(combined.values()) == 1:
# Alleen zwakke families: dan moeten ≥2 verschillende families meetellen # Alleen zwakke families: dan moeten ≥2 verschillende families meetellen
# én minstens één zwak woord moet in de TITEL staan. Zwakke woorden in # én minstens één zwak woord moet in de TITEL staan. Zwakke woorden in
# de samenvatting alleen zijn geen bewijs ('station' + 'pendelbus' + # de samenvatting alleen zijn geen bewijs ('station' + 'pendelbus' +
# 'bus' in een golfverslag telt anders op tot de drempel). # '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: if not t_hits or len(combined) < 2:
return False return False
return True return True
@ -447,7 +453,8 @@ class RSSBot(Plugin):
"ov-chipkaart, ovpay, openbaar vervoer, ...)\n\n" "ov-chipkaart, ovpay, openbaar vervoer, ...)\n\n"
"Uitgesloten (blacklist): woorden die wél matchen maar geen OV-waarde hebben, " "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" "o.a. straatnamen rond stations ('stationsstraat', 'stationsweg', ...) en niet-OV-vormen als 'stationcar' en 'stationair'\n\n"
"Doorlaten: `titel-score + samenvatting-score ≥ 3` (titel weegt niet extra); " "Doorlaten: som van unieke OV-families ≥ 3 — één familie telt 1× per artikel "
"(titel + samenvatting samen; herhaling of beide velden = één signaal); "
"bij uitsluitend zwakke woorden tellen alleen " "bij uitsluitend zwakke woorden tellen alleen "
"≥2 verschillende families én een zwak woord in de titel" "≥2 verschillende families én een zwak woord in de titel"
) )

View file

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

View file

@ -145,10 +145,10 @@ GOLDEN = [
("Restaurant weg, pand staat te verloederen: 'Doorn in het oog'", ("Restaurant weg, pand staat te verloederen: 'Doorn in het oog'",
"De gemeente Midden-Delfland wil het leegstaande horecapand naast busstation Maasland " "De gemeente Midden-Delfland wil het leegstaande horecapand naast busstation Maasland "
"Viaduct kopen. Het gebouw staat al jaren leeg en maakt een vervallen indruk.", False), "Viaduct kopen. Het gebouw staat al jaren leeg en maakt een vervallen indruk.", False),
("Pand naast busstation wordt gesloopt", "Het leegstaande pand naast het busstation gaat plat.", True), # busstation in titel ÉN samenvatting: 2+2=4 — twee velden tellen mee ("Pand naast busstation wordt gesloopt", "Het leegstaande pand naast het busstation gaat plat.", False), # één familie in titel ÉN samenvatting = één signaal: 2 < 3 (besluit 2026-09-11)
("Nieuwe fietsenstalling bij busstation", "", False), # besluit 2026-09-11: één medium-hit in de titel is onvoldoende (titel weegt 1×, was 2×) ("Nieuwe fietsenstalling bij busstation", "", False), # besluit 2026-09-11: één medium-hit in de titel is onvoldoende (titel weegt 1×, was 2×)
("Restaurant naast busstation wil uitbreiden", "De eigenaar wil uitbreiden naar het naastgelegen pand.", False), # zelfde regel, letterlijk gebruikersvoorbeeld ("Restaurant naast busstation wil uitbreiden", "De eigenaar wil uitbreiden naar het naastgelegen pand.", False), # zelfde regel, letterlijk gebruikersvoorbeeld
("Busstation wordt verbouwd", "Het busstation krijgt andere perrons.", True), # busstation in titel én samenvatting: 4 ("Busstation wordt verbouwd", "De verbouwing duurt tot het voorjaar.", False), # herhaling/één woord = 1 signaal: 2 < 3 ('busstation' 60× weegt ook 1×)
("Metro rijdt niet", "De halte is tijdelijk gesloten.", True), # medium-combinatie over twee families: metro(2)+halte(2)=4 ("Metro rijdt niet", "De halte is tijdelijk gesloten.", True), # medium-combinatie over twee families: metro(2)+halte(2)=4
# busstation mag niet dubbel meetellen via 'bus' én 'station' (was 2×3=6→nu 1×2) # busstation mag niet dubbel meetellen via 'bus' én 'station' (was 2×3=6→nu 1×2)
("Metrostation Centraal tijdelijk gesloten", "De metro's rijden niet.", True), ("Metrostation Centraal tijdelijk gesloten", "De metro's rijden niet.", True),