v1.13.0: één term = één signaal — overlappende familie-treffers wegen 1×
'busbaan' stond in de bus-samenstelling-familie (fam 4) én de halte/busbaan-familie (fam 13): één woord scoorde 2+2=4 en liet zo een niet-OV-bericht door (Omroep Brabant politie-achtervolging 'via de busbaan', 13-09-2026). Latente dubbeltellingen: 'ov-chipkaart' bevat 'chipkaart', 'openbaar vervoer' bevat 'vervoer'. _fam_hits voegt nu overlappende span van verschillende families samen met het hoogste gewicht (retour blijft (familie-index, gewicht), dus de familie-één-signaal-regel over titel+samenvatting is intact). Beleidscommentaar + regressie (56 cases) bijgewerkt; 45-dagen-replay (90 geposte entries): precies 1 gedragsverschil — het fatbikers- bericht wordt nu geweerd, delftopzondag (tram+halte, twee termen) blijft door (besluit 2026-09-13).
This commit is contained in:
parent
e1b30fc2ee
commit
1346cbb549
3 changed files with 53 additions and 11 deletions
|
|
@ -55,8 +55,11 @@ 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).
|
||||
# Per familie wordt 1× geteld per ARTIKEL (titel én samenvatting samen; herhaling
|
||||
# of het woord in beide velden = één signaal — 'busstation' 60× weegt 1×, besluit
|
||||
# 2026-09-11). Drempel (3): doorlaten als de som van de unieke familie-gewichten
|
||||
# >= MATCH_FILTER_THRESHOLD (titel weegt niet extra).
|
||||
# 2026-09-11). Eén term die meerdere families matcht telt óók maar 1× (hoogste
|
||||
# gewicht wint): 'busbaan' zit in de bus-samenstelling- én de halte/busbaan-familie,
|
||||
# 'ov-chipkaart' bevat 'chipkaart', 'openbaar vervoer' bevat 'vervoer' — geen
|
||||
# dubbeltelling (besluit 2026-09-13). Drempel (3): doorlaten als de som van de
|
||||
# unieke familie-gewichten >= MATCH_FILTER_THRESHOLD (titel weegt niet extra).
|
||||
# MAX-GEWICHT 2 — besluit 2026-09-12: een enkele FAMILIE-hit kan nooit solo
|
||||
# doorlaten (2 < 3); écht OV-nieuws combineert in de praktijk altijd met een
|
||||
# tweede signaal (gevalideerd op 205 productie-artikelen; zie beleidsblok).
|
||||
|
|
@ -118,6 +121,10 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
|
|||
# 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).
|
||||
# Overlappende treffers van verschillende families (één term op één plek)
|
||||
# tellen samen als 1× met het hoogste gewicht — een term mag nooit dubbel
|
||||
# wegen ('busbaan' in de bus-samenstelling- én de halte/busbaan-familie,
|
||||
# besluit 2026-09-13).
|
||||
# - sterk (2): onmiskenbaar OV-jargon/merken/samenvoegingen (spoorlijn,
|
||||
# bushalte, busverkeer, intercity, veerboot, ertms,
|
||||
# rangeer..., ice-frasen, ...). Eén hit (titel óf samenvatting)
|
||||
|
|
@ -175,7 +182,7 @@ MATCH_FILTER_FAMILIES: list[tuple[str, int]] = [
|
|||
(r"\b(?:spoorweg|spoorba(?:an|nen)|spoorlijn|spoorverkeer|spoorwegovergang|spoorstoring|perron|seinstoring|wisselstoring|spoorstaking|spoorsector|spoorbeheerder|spoornet|spoorvervoerder|ertms)\w*\b", 2), # spoorinfra; let op de klinkerwisseling: 'ontsporing' (open lettergreep, één o) vs 'ontspoord'/'ontspoorde' (dicht, dubbel o) — vandaar beide stammen. Géén 'spoorbrug': die is 2 maar vaak louter locatie (besluit 2026-09-12). Uitbreiding 2026-09-12 (completheids-audit): spoorsector, spoorbeheerder, spoornet, spoorvervoerder, ertms
|
||||
(r"\b(?:treinstation|treinstations|metrostation|metrostations|busstation|busstations)\b", 2), # station-samenstellingen: onmiskenbaar OV, maar één station-woord náást een ander onderwerp ('pand naast busstation', besluit 2026-09-11) is geen OV-bericht; in de samenvatting moet een tweede OV-woord bijkomen
|
||||
(r"(?<![\w-])\bstations?\b", 1), # los 'station(s)' is ZWAK: ook tv-/radio-/regionale omroepstations ('de lokale stations'), moet combineren met een andere OV-familie
|
||||
(r"\b(?:bus|tram|metro)-?(?:halte|haltes|lijn|lijnen|verkeer|net|staking|ba(?:an|nen)|hok(?:je|jes)|chauffeur)\w*\b", 2), # voertuig-samenstelling (bushalte/buslijn/bushokje/busbanen/buschauffeur/…); géén 'station': *station-samenstellingen zitten in de station-familie, anders telde 'busstation' dubbel mee — fix 2026-09-11. Géén 'ongeluk'/'vervoer': die zijn bewust zwak (busongeluk = ook touringcar, busvervoer = ook bedrijfsvervoer, audit 2026-09-12)
|
||||
(r"\b(?:bus|tram|metro)-?(?:halte|haltes|lijn|lijnen|verkeer|net|staking|ba(?:an|nen)|hok(?:je|jes)|chauffeur)\w*\b", 2), # voertuig-samenstelling (bushalte/buslijn/bushokje/busbanen/buschauffeur/…); géén 'station': *station-samenstellingen zitten in de station-familie, anders telde 'busstation' dubbel mee — fix 2026-09-11. Géén 'ongeluk'/'vervoer': die zijn bewust zwak (busongeluk = ook touringcar, busvervoer = ook bedrijfsvervoer, audit 2026-09-12). 'busbaan' staat óók in fam 13: overlappende treffers tellen 1× met het hoogste gewicht (besluit 2026-09-13)
|
||||
(r"\b(?:treinstoring|treinverkeer|treinstaking|treinongeval|treinbotsing|treinongeluk|treinvertraging|treinhinder|treinuitval|treinverstoring)\w*\b", 2), # trein-jargon-samenstellingen: onmiskenbaar OV; uitgebreid met vertraging/hinder/uitval/verstoring (audit 2026-09-12)
|
||||
(r"\b\w*trein\w*\b", 1), # losse 'trein' en algemene samenstellingen (stoptrein, goederentrein, treinverkeer al in bovenstaande familie) — ZWAK: 'de trein naar mijn nieuwe huis' is geen OV-jargon, moet combineren met een andere familie
|
||||
(r"\b(?:intercity|streekvervoer|stadsvervoer|veerdienst|veerboot|veerboten|veerpont|voetveer|veerverbinding|buurtbus|belbus|maaslijn|nedersaksenlijn|kamperlijn)\w*\b", 2), # treintypes, vervoerssectoren, veren en lijnnamen ('sprinter' staat in de zwakke groep: atleet-botsing); maaslijn/nedersaksenlijn/kamperlijn via audit 2026-09-12
|
||||
|
|
@ -185,7 +192,7 @@ MATCH_FILTER_FAMILIES: list[tuple[str, int]] = [
|
|||
# onmiskenbaar OV maar iets algemener (2, moet ook combineren)
|
||||
(r"\btrams?\b", 2),
|
||||
(r"\bmetro(?:'?s)?\b", 2),
|
||||
(r"\b(?:openbaar vervoer|lightrail|halte|bushokje|busbaan)\w*\b", 2),
|
||||
(r"\b(?:openbaar vervoer|lightrail|halte|bushokje|busbaan)\w*\b", 2), # 'busbaan' ook in fam 4, 'openbaar vervoer' bevat 'vervoer' (fam 41): één term = één signaal (besluit 2026-09-13)
|
||||
(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
|
||||
|
|
@ -442,25 +449,47 @@ class RSSBot(Plugin):
|
|||
|
||||
@staticmethod
|
||||
def _fam_hits(text: str) -> list[tuple[int, int]]:
|
||||
"""(familie-index, gewicht) voor elke OV-familie die in text voorkomt (1× per familie).
|
||||
"""(familie-index, gewicht) per OV-signaal in text (1× per familie).
|
||||
|
||||
Gematchte woorden uit MATCH_FILTER_BLACKLIST (o.a. 'stationsstraat')
|
||||
tellen niet mee en blokkeren de familie niet: een écht OV-woord in
|
||||
dezelfde tekst scoort gewoon verder."""
|
||||
hits: list[tuple[int, int]] = []
|
||||
dezelfde tekst scoort gewoon verder.
|
||||
|
||||
Eén term telt maximaal één signaal (besluit 2026-09-13): overlappende
|
||||
treffers van verschillende families op dezelfde plek worden samengevoegd
|
||||
met het hoogste gewicht. Voorheen telde één woord dubbel als het in
|
||||
meerdere families stond — 'busbaan' zit in de bus-samenstelling-familie
|
||||
én in de halte/busbaan-familie (2+2=4), 'ov-chipkaart' bevat 'chipkaart'
|
||||
(2+1=3), 'openbaar vervoer' bevat 'vervoer' (2+1=3). Max-2-stelsel:
|
||||
één term kan nooit solo doorlaten."""
|
||||
spans: list[tuple[int, int, int, int]] = [] # (start, end, familie-idx, gewicht)
|
||||
for idx, (family, weight) in enumerate(_compiled_match_families):
|
||||
for m in family.finditer(text):
|
||||
if m.group(0).lower() in MATCH_FILTER_BLACKLIST:
|
||||
continue
|
||||
hits.append((idx, weight))
|
||||
spans.append((m.start(), m.end(), idx, weight))
|
||||
break
|
||||
return hits
|
||||
spans.sort()
|
||||
merged: list[tuple[int, int, int, int]] = []
|
||||
for start, end, idx, weight in spans:
|
||||
if merged and start < merged[-1][1]:
|
||||
s0, e0, i0, w0 = merged[-1]
|
||||
if weight > w0:
|
||||
merged[-1] = (s0, max(e0, end), idx, weight)
|
||||
else:
|
||||
merged[-1] = (s0, max(e0, end), i0, w0)
|
||||
else:
|
||||
merged.append((start, end, idx, weight))
|
||||
return [(idx, w) for _, _, idx, w in merged]
|
||||
|
||||
@staticmethod
|
||||
def _filter_passes(title: str, summary: str) -> bool:
|
||||
"""Gewogen familiescore (max-2-stelsel, besluit 2026-09-12): elke OV-familie
|
||||
telt 1× per artikel (titel + samenvatting samen; herhaling of beide velden =
|
||||
één signaal). Doorlaten bij som >= drempel (3). Geen enkele familie weegt
|
||||
één signaal). Overlappende treffers van verschillende families tellen samen
|
||||
als 1× met het hoogste gewicht — 'busbaan' (bus-samenstelling- én
|
||||
halte/busbaan-familie) levert één signaal van 2, geen 4 (besluit 2026-09-13).
|
||||
Doorlaten bij som >= drempel (3). Geen enkele familie weegt
|
||||
meer dan 2, dus een enkele familie-hit kan nooit solo doorlaten — 'hermes'
|
||||
als achternaam passeerde vroeger op één vervoerder-hit van 3 (Margraten,
|
||||
2026-09-12); écht OV-nieuws combineert in de praktijk altijd met een tweede
|
||||
|
|
@ -494,6 +523,7 @@ class RSSBot(Plugin):
|
|||
s_hits = RSSBot._fam_hits(summary)
|
||||
# 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).
|
||||
# _fam_hits levert per term al het hoogste gewicht bij overlappende families.
|
||||
combined = dict(t_hits + s_hits)
|
||||
score = sum(combined.values())
|
||||
whitelist_title = RSSBot._whitelist_hit(title)
|
||||
|
|
|
|||
Loading…
Reference in a new issue