Compare commits
6 changed files with 191 additions and 550 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,5 +1,2 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
# Taakbestanden die geen onderdeel zijn van de repo
|
|
||||||
Prompt voor agent*.md
|
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,10 @@ Maubot-plugin `nl.eddydevink.rss` (RSS → Matrix + WhatsApp), met een OV-nieuws
|
||||||
|
|
||||||
## Verplicht vóór elke filterwijziging
|
## Verplicht vóór elke filterwijziging
|
||||||
1. Lees het **volledige classificatiebeleid** in `buzzrss/bot.py` — het blok BOVEN `MATCH_FILTER_FAMILIES`
|
1. Lees het **volledige classificatiebeleid** in `buzzrss/bot.py` — het blok BOVEN `MATCH_FILTER_FAMILIES`
|
||||||
(max-2-stelsel: whitelist + gewichten 2/1, precision-first, en de werkwijze voor nieuwe woorden).
|
(driedeling whitelist / sterk / medium / zwak, precision-first, en de werkwijze voor nieuwe woorden).
|
||||||
2. Draai **`python3 buzzrss/validate_filter.py`** — de filter-invarianten en regressie-cases MOETEN groen
|
2. Draai **`python3 buzzrss/validate_filter.py`** — de filter-invarianten en regressie-cases MOETEN groen
|
||||||
zijn voordat filtercode gecommit/gedeployed wordt.
|
zijn voordat filtercode gecommit/gedeployed wordt.
|
||||||
3. **Compleetheids-audit (verplicht, besluit 2026-09-12):** check bij elke filterwijziging óók of de
|
3. Precision-first (gebruikersbesluit 2026-09-10): **liever een OV-bericht te veel gefilterd dan een
|
||||||
woordenlijsten compleet zijn — draai de OV-gap-scan over de afgewezen artikelen van de
|
|
||||||
OV-specialist-feeds (SpoorPro.nl / Treinreiziger.nl / Mobiliteit, query op `entry` in schema `mbp_rss`),
|
|
||||||
klasseer nieuwe kandidaten volgens het beleid (minimaal één extra LLM; enige stem TWIJFEL/NIET-OV => zwak)
|
|
||||||
en vul de families desgevraagd aan. Exacte procedure = stap (e) in het beleidsblok in `buzzrss/bot.py`.
|
|
||||||
4. Precision-first (gebruikersbesluit 2026-09-10): **liever een OV-bericht te veel gefilterd dan een
|
|
||||||
niet-OV-bericht te veel doorgelaten.** Bij twijfel over een woord: zwak maken (eigen familie), nooit
|
niet-OV-bericht te veel doorgelaten.** Bij twijfel over een woord: zwak maken (eigen familie), nooit
|
||||||
sterk/whitelist.
|
sterk/whitelist.
|
||||||
|
|
||||||
|
|
|
||||||
157
README.md
157
README.md
|
|
@ -1,157 +0,0 @@
|
||||||
# buzz-rss — RSS/Atom → Matrix, gefilterd op OV-relevantie
|
|
||||||
|
|
||||||
Maubot-plugin `nl.eddydevink.rss` (fork van [`xyz.maubot.rss`](https://github.com/maubot/rss))
|
|
||||||
die nieuwsfeeds pollt en **alleen openbaar-vervoer-gerelateerde berichten** doorplaatst
|
|
||||||
naar Matrix-rooms (optioneel ook alleen de link naar een WhatsApp-groep).
|
|
||||||
|
|
||||||
De filterlogica is **bewust vast in code** (niet via config) en volgt één businessregel:
|
|
||||||
|
|
||||||
> **Precision boven recall** — liever een relevant OV-artikel missen dan één
|
|
||||||
> niet-OV-artikel ten onrechte doorlaten. (Gebruikersbesluit 2026-09-10)
|
|
||||||
|
|
||||||
## Functionaliteit
|
|
||||||
|
|
||||||
- Abonneren op feeds per room, met per-feed meldingssjabloon en `m.notice`-optie.
|
|
||||||
- **Conditional fetch** (`ETag`/`Last-Modified`) en backoff bij faalende feeds.
|
|
||||||
- **HTML-scrape-modus**: bronnen zonder RSS/Atom/JSON worden als pagina gepolled via
|
|
||||||
per-feed `feed.scrape_config` (zie `buzzrss/html_scrape.py`; eerste productiefeed: CNV).
|
|
||||||
- **Paywall-uitsluiting** (`exclude_paywalled`) en **titel-dedup over feeds**
|
|
||||||
(`title_dedup_days`: zelfde titel van een andere site van dezelfde uitgever = niet dubbel posten).
|
|
||||||
- Per-run logfile met aantallen (`run_log_file`).
|
|
||||||
- **WhatsApp-forward**: alleen `entry.link` wordt gepost (vooruit-links, zonder titel),
|
|
||||||
met dedup-check tegen de doelgroep. Uit zolang `whatsapp_api`/`whatsapp_chat_id` leeg zijn.
|
|
||||||
|
|
||||||
## Commando's
|
|
||||||
|
|
||||||
Prefix uit config (`command_prefix`, default `rss`), eventueel zonder `!`:
|
|
||||||
|
|
||||||
| Commando | Aliassen | Werking |
|
|
||||||
|---|---|---|
|
|
||||||
| `!rss subscribe <url>` | `s`, `sub` | Deze room op een feed abonneren (nieuwe feed wordt direct geïndexeerd) |
|
|
||||||
| `!rss unsubscribe <feed-id>` | `u`, `unsub` | Abonnement van deze room opzeggen |
|
|
||||||
| `!rss subscriptions` | `ls`, `list`, `subs` | Abonnementen in deze room + huidige filter |
|
|
||||||
| `!rss template <feed-id> [sjabloon]` | `t`, `tpl` | Meldingssjabloon bekijken/wijzigen (variabelen: `$title`, `$link`, `$summary`, `$feed_title`, …) |
|
|
||||||
| `!rss notice <feed-id> <true\|false>` | `n` | Als `m.notice` versturen i.p.v. `m.text` |
|
|
||||||
| `!rss filter` | `f`, `set-filter`, `show-filter`, `get-filter` | Toont het vaste OV-matchfilter |
|
|
||||||
|
|
||||||
Bestuurders: admins uit de config, of gebruikers met een powerlevel ≥ dat van het
|
|
||||||
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)
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```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)
|
|
||||||
```
|
|
||||||
|
|
||||||
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',
|
|
||||||
idioom 'goed met elkaar overweg kunnen', 'een wijk opwaarderen',
|
|
||||||
brandweerjargon 'sein brandmeester', figuratief 'weer op de rails',
|
|
||||||
achternaam 'Hermes' (Margraten), 'restaurant/woning naast busstation'.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 validate_filter.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Vaste invarianten + regressie (momenteel **90 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).
|
|
||||||
|
|
||||||
**Verplicht vóór elke filterwijziging** (zie `AGENTS.md`): het classificatiebeleid in
|
|
||||||
`buzzrss/bot.py` lezen, `validate_filter.py` groen, en de **compleetheids-audit**:
|
|
||||||
OV-gap-scan over de afgewezen artikelen van de specialist-feeds (SpoorPro,
|
|
||||||
Treinreiziger, Mobiliteit; query op `entry` in PostgreSQL-schema `mbp_rss`),
|
|
||||||
nieuwe woordkandidaten klasseer je volgens het beleid (minimaal één extra
|
|
||||||
taalmodel; enige stem 'TWIJFEL'/'NIET-OV' ⇒ zwak).
|
|
||||||
|
|
||||||
## Configuratie
|
|
||||||
|
|
||||||
Zie `base-config.yaml` (wordt in het gebouwde `.mbp` meegeleverd):
|
|
||||||
|
|
||||||
| Sleutel | Default | Werking |
|
|
||||||
|---|---|---|
|
|
||||||
| `update_interval` | 60 | Poll-interval in minuten |
|
|
||||||
| `max_backoff` | 7200 | Maximale backoff (minuten) bij faalende feeds |
|
|
||||||
| `poll_parallelism_limit` | 10 | Maximaal aantal feeds tegelijk pollend |
|
|
||||||
| `spam_sleep` | 2 | Pauze tussen berichten bij broadcast (‑1 = parallel) |
|
|
||||||
| `command_prefix` | `rss` | Commando-prefix |
|
|
||||||
| `notification_template` | `New post in $feed_title: [$title]($link)` | Standaard meldingssjabloon |
|
|
||||||
| `run_log_file` | `/data/buzzrss-runs.log` | Per-run samenvattingsregel (leeg = uit) |
|
|
||||||
| `whatsapp_api` / `whatsapp_session` / `whatsapp_chat_id` | – | WhatsApp-forward (leeg adapter = uit) |
|
|
||||||
| `exclude_paywalled` | true | Betaalmuur-artikelen overslaan |
|
|
||||||
| `title_dedup_days` | 30 | Cross-feed titel-dedup-venster (0 = uit) |
|
|
||||||
| `admins` | – | Gebruikers die room-permissies mogen omzeilen |
|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
|
|
||||||
Productie draait het **geïnstalleerde `.mbp`** (maubot), niet deze werkdirectory —
|
|
||||||
een commit is pas actief na build + upload + restart. Exacte recepten staan in de
|
|
||||||
maubot-beheerskill (`skill://maubot`) en de Obsidian-vault (Matrix Synapse en MAS.md).
|
|
||||||
|
|
||||||
In het kort:
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. mbc build uit de VOLLEDIGE bron (buzzrss/ + maubot.yaml + base-config.yaml —
|
|
||||||
zonder base-config.yaml faalt de instance met BaseMissingError)
|
|
||||||
2. POST /_matrix/maubot/v1/plugins/upload?allow_override=true (admin-login)
|
|
||||||
3. docker restart matrix-bot-maubot
|
|
||||||
```
|
|
||||||
|
|
||||||
**NOOIT maubot `reload` gebruiken** — die laat zombie-commandhandlers achter
|
|
||||||
(één commando → N antwoorden). Alleen een proces-herstart wist de handlers.
|
|
||||||
|
|
||||||
## Architectuur in het kort
|
|
||||||
|
|
||||||
- Poll-loop → per feed conditional-fetch of HTML-scrape → nieuwe entries
|
|
||||||
(`feed_id` + `id` PK-dedup) → cross-feed titel-dedup → `_filter_passes` →
|
|
||||||
broadcast naar alle subscriptions + optioneel WhatsApp (`_send_whatsapp`).
|
|
||||||
- Persistente tabel `whatsapp_posted` voor WhatsApp-dedup.
|
|
||||||
- DB: PostgreSQL-schema `mbp_rss` (`feed`, `entry`, `subscription`,
|
|
||||||
`whatsapp_posted`); migraties via maubot's `UpgradeTable` (`buzzrss/migrations.py`).
|
|
||||||
|
|
||||||
## Licentie
|
|
||||||
|
|
||||||
AGPL-3.0-or-later (upstream: maubot/rss, © Tulir Asokan).
|
|
||||||
465
buzzrss/bot.py
465
buzzrss/bot.py
|
|
@ -55,28 +55,23 @@ 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 ARTIKEL (titel én samenvatting samen; herhaling
|
# 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
|
# of het woord in beide velden = één signaal — 'busstation' 60× weegt 1×, besluit
|
||||||
# 2026-09-11). Eén term die meerdere families matcht telt óók maar 1× (hoogste
|
# 2026-09-11). Drempel (3): doorlaten als de som van de unieke familie-gewichten
|
||||||
# gewicht wint): 'ov-chipkaart' bevat 'chipkaart', 'openbaar vervoer' bevat
|
# >= MATCH_FILTER_THRESHOLD (titel weegt niet extra).
|
||||||
# 'vervoer', 'treinstoring' matcht de zwakke \w*trein\w*-familie mee — geen
|
# Gewicht 3 (sterk, onmiskenbaar OV): één woord in titel óf samenvatting volstaat.
|
||||||
# dubbeltelling (besluit 2026-09-13; 'busbaan' stond in twee families en is
|
# Gewicht 2 (medium): moet combineren met een andere OV-familie; één medium-hit,
|
||||||
# uit de halte-familie verwijderd, refactor 2026-09-13). Drempel (3): doorlaten
|
# in titel óf samenvatting, haalt de drempel niet.
|
||||||
# als de som van de unieke familie-gewichten >= MATCH_FILTER_THRESHOLD
|
# Gewicht 1 (zwak/dubbelzinnig: los "bus", "breng", "overweg", "overwegen", "sein",
|
||||||
# (titel weegt niet extra).
|
# "kaartje", "opwaarderen", "rails", "pendelbus"): moet met een ander OV-woord
|
||||||
# MAX-GEWICHT 2 — besluit 2026-09-12: een enkele FAMILIE-hit kan nooit solo
|
# combineren (een tweede, andere familie — dezelfde zwakke familie in titel én
|
||||||
# doorlaten (2 < 3); écht OV-nieuws combineert in de praktijk altijd met een
|
# samenvatting telt niet als combinatie) EN minstens één zwak woord moet in de
|
||||||
# tweede signaal (gevalideerd op 205 productie-artikelen; zie beleidsblok).
|
# TITEL staan: een combinatie van alleen zwakke woorden in de samenvatting is
|
||||||
# Gewicht 2 (onmiskenbaar OV: merken, sectorjargon, samenstellingen als bushalte/
|
# geen bewijs (het golfbericht met "station" + "pendelbus" + "bus" in de
|
||||||
# spoorlijn/veerboot — de vroegere 'sterk(3)' én 'medium(2)' zijn hierin
|
# samenvatting is geen OV-nieuws). Weert zo "VW-bus"/"schoolbus", "weer op de rails", het
|
||||||
# opgegaan, want beide moeten nu combineren).
|
# werkwoord "brengen" ("hoe breng je ... in beeld"), het idioom "goed met elkaar
|
||||||
# Gewicht 1 (zwak/dubbelzinnig): moet met een andere OV-familie combineren
|
# overweg kunnen", het werkwoord "overwegen" ("gratieverlening overwegen"), het
|
||||||
# (een tweede, andere familie — dezelfde zwakke familie in titel én samenvatting
|
# werkwoord "opwaarderen" ("een wijk opwaarderen") en brandweerjargon
|
||||||
# telt niet als combinatie) EN minstens één zwak woord moet in de TITEL staan:
|
# ("sein brandmeester"), zonder eenduidige bushalte/overweg/NS/trein-berichten
|
||||||
# een combinatie van alleen zwakke woorden in de samenvatting is geen bewijs
|
# te raken.
|
||||||
# (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", het idioom "goed met elkaar overweg kunnen", het werkwoord
|
|
||||||
# "overwegen", het werkwoord "opwaarderen" en brandweerjargon ("sein brandmeester"),
|
|
||||||
# zonder eenduidige bushalte/overweg/NS/trein-berichten te raken.
|
|
||||||
# Inflectie: families matchen met \w* aan het eind, zodat meervoud en vervoeging
|
# Inflectie: families matchen met \w* aan het eind, zodat meervoud en vervoeging
|
||||||
# meegenomen worden ("spoorlijnen", "seinstoringen", "ontspoorde", "chipkaarten").
|
# meegenomen worden ("spoorlijnen", "seinstoringen", "ontspoorde", "chipkaarten").
|
||||||
# Een koppelteken vóór een match is géén OV-signaal: "VW-bus" en "radio-station"
|
# Een koppelteken vóór een match is géén OV-signaal: "VW-bus" en "radio-station"
|
||||||
|
|
@ -85,148 +80,105 @@ rss_change_level = EventType.find("xyz.maubot.rss", t_class=EventType.Class.STAT
|
||||||
# straatnamen rond stations — "de Stationsstraat afsluiten voor een festival"
|
# straatnamen rond stations — "de Stationsstraat afsluiten voor een festival"
|
||||||
# is geen OV-nieuws). Zwartgelijste woorden leveren geen score op, maar
|
# is geen OV-nieuws). Zwartgelijste woorden leveren geen score op, maar
|
||||||
# blokkeren de familie niet: een écht "station"-woord ernaast scoort gewoon.
|
# blokkeren de familie niet: een écht "station"-woord ernaast scoort gewoon.
|
||||||
# Bewust geaccepteerde botsingen: krant "De Metro"/café "De Tram" (gewicht 2,
|
# Bewust geaccepteerde botsingen: krant "De Metro"/café "De Tram" (medium).
|
||||||
# moet combineren). 'spoorbrug' weegt 2: een brug is vaak louter locatie
|
# 'spoorbrug' is MEDIUM (niet sterk/whitelist): een brug is vaak louter locatie
|
||||||
# ('bij de spoorbrug tussen Liempde en Boxtel', Stuifmail 2026-09-12) — écht
|
# ('bij de spoorbrug tussen Liempde en Boxtel', Stuifmail 2026-09-12) — één
|
||||||
# spoorbrug-nieuws combineert met trein/spoor/verkeer (besluit 2026-09-12).
|
# spoorbrug-hit in de samenvatting is geen OV-nieuws; écht spoorbrug-nieuws
|
||||||
# Naam-botsingen die bewust zijn afgezwakt (2026-09-12): 'hermes' (vervoerder
|
# combineert met trein/spoor/verkeer en haalt de drempel alsnog (besluit 2026-09-12).
|
||||||
# Hermes vs. achternaam Ton Hermes — Margraten-false positive), 'breng'
|
# Losse algemene woorden zijn zwak (moeten combineren met een andere familie):
|
||||||
# (vervoerder vs. werkwoord 'brengen'), 'spoor' (figuurlijk 'op het spoor van').
|
# "trein", "sprinter" (atleet), "vervoerder" (logistiek), "reiziger",
|
||||||
# Losse algemene woorden zijn zwak (moeten combineren): "trein", "sprinter"
|
# "inchecken"/"uitchecken" (Schiphol/hotel), "station(s)" (omroepen), "bus",
|
||||||
# (atleet), "vervoerder" (logistiek), "reiziger"/"passagier" (vliegen),
|
# "sein" (brandweer), "overweg", "breng", "kaartje", "opwaarderen", "rails".
|
||||||
# "inchecken"/"uitchecken" (Schiphol/hotel), "station(s)" (omroepen), "bus",
|
|
||||||
# "sein" (brandweer), "overweg", "breng", "kaartje", "opwaarderen", "rails",
|
|
||||||
# "staking" (alle sectoren), "vervoer" (vracht/auto), "abonnement", "ritten".
|
|
||||||
# Multi-woord-frasen en merknamen (ice-berlijn, deutsche bahn, european sleeper,
|
|
||||||
# ns international, teso, italo, nightjet, maaslijn) zijn onmiskenbaar OV en
|
|
||||||
# wegen 2; een los 'ice' is NIET toevoegbaar (case-insensitive botsing met 'ijs',
|
|
||||||
# besluit 2026-09-12).
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# FILTERCLASSIFICATIE-BELEID (bij elke aanpassing aan het filter volgen)
|
# FILTERCLASSIFICATIE-BELEID (bij elke aanpassing aan het filter volgen)
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# Doel: precisie boven recall. Liever een OV-bericht te veel gefilterd dan
|
# Doel: precisie boven recall. Liever een OV-bericht te veel gefilterd dan
|
||||||
# een niet-OV-bericht te veel doorgelaten (gebruikersbesluit, 2026-09-10).
|
# een niet-OV-bericht te veel doorgelaten (gebruikersbesluit, 2026-09-10).
|
||||||
#
|
#
|
||||||
# Beslissing, in drie stappen (refactor 2026-09-13; logica equivalent met de
|
# Drie lagen, elk met een eigen regel:
|
||||||
# voorheen gelaagde takken — MAX-2-stelsel, besluit 2026-09-12):
|
# 1. WHITELIST -> ALTIJD door BIJ EEN HIT IN DE TITEL. Alleen termen die
|
||||||
# 1. STERK SIGNAAL (whitelist-term óf dienststoring-zin in de TITEL) -> DOORLATEN.
|
# in élke context OV betekenen (merken: NS/ProRail/Arriva; onmiskenbaar jargon:
|
||||||
# Whitelist = termen die in élke context OV betekenen (merken:
|
# spoorstoring, perron, bushalte, conducteur, ovpay; multi-woord termen).
|
||||||
# NS/ProRail/Arriva; onmiskenbaar jargon: spoorstoring, perron, bushalte,
|
# REGEL: twijfel je of de term ook buiten het OV kan voorkomen -> NIET hier.
|
||||||
# conducteur, ovpay; multi-woord-frasen: nederlandse spoorwegen, openbaar
|
# NIET: station-samenstellingen (busstation/metrostation) — een bericht over
|
||||||
# vervoer, ice-berlijn ...). Een whitelist-hit in de titel telt +2,
|
# een pand 'naast het busstation' is geen OV-nieuws (besluit 2026-09-11);
|
||||||
# bovenop het familie-gewicht van dezelfde term (elke whitelist-term zit
|
# die wegen mee via de gewogen families (medium).
|
||||||
# óók in een familie) — samen altijd >= drempel, dus eenduidige OV-titels
|
# Een whitelist-term ALLEEN in de SAMENVATTING laat NIET onvoorwaardelijk
|
||||||
# ('Reactie Nederlandse Spoorwegen', 'Werkzaamheden aan perron') passeren
|
# door: een bijzin als 'de zakelijke bezoeker is ook nodig voor het openbaar
|
||||||
# alleen. REGEL: twijfel je of de term ook buiten het OV kan voorkomen ->
|
# vervoer' in een economiebericht is geen OV-nieuws (besluit 2026-09-12).
|
||||||
# NIET hier, maar zwak (eigen familie). Een whitelist-term ALLEEN in de
|
# Zo'n hit scoort via de gewogen families (dezelfde termen staan daar ook in).
|
||||||
# SAMENVATTING blijft inert: een bijzin als 'de zakelijke bezoeker is ook
|
# 2. GEWOGEN FAMILIES -> score = som van unieke OV-families over het hele artikel
|
||||||
# nodig voor het openbaar vervoer' in een economiebericht is geen
|
# (titel + samenvatting; per familie 1× per artikel — herhaling of beide velden
|
||||||
# OV-nieuws (besluit 2026-09-12); zo'n hit scoort alleen via de familie.
|
# tellen niet dubbel, besluit 2026-09-11) >= drempel (3).
|
||||||
# 2. CONTEXT: som van unieke OV-signalen over titel + samenvatting
|
# - sterk (3): onmiskenbaar OV-jargon/merken/samenvoegingen (spoorlijn,
|
||||||
# >= drempel (3). Per familie 1× per artikel (herhaling of beide velden
|
# bushalte, busverkeer, intercity, lijn 12, ...).
|
||||||
# tellen niet dubbel, besluit 2026-09-11); overlappende familie-treffers
|
# - medium (2): onmiskenbaar OV maar iets algemener (tram, metro, halte,
|
||||||
# van dezelfde term wegen 1× met het hoogste gewicht (span-merge in
|
# pont, ovpay, openbaar vervoer, trein-/bus-/metrostation).
|
||||||
# _fam_hits, besluit 2026-09-13 — 'ov-chipkaart' bevat 'chipkaart' maar
|
# Moet COMBINEREN met een andere OV-familie: één medium-hit,
|
||||||
# weegt als één term 2, geen 3).
|
# in titel óf samenvatting, is onvoldoende — de titel weegt
|
||||||
# - sterk (2): onmiskenbaar OV-jargon/merken/samenvoegingen (spoorlijn,
|
# niet extra ('restaurant naast busstation' in de titel is
|
||||||
# bushalte, busverkeer, intercity, veerboot, ertms,
|
# geen OV-bericht, besluit 2026-09-11).
|
||||||
# rangeer..., ice-frasen, ...) én het dienststoring-
|
# EXTRA (besluit 2026-09-12): een medium + zwak-combinatie
|
||||||
# contextpatroon 'vervoerswoord + rijdt/rijden (niet) +
|
# UITSLUITEND in de samenvatting (geen enkele hit in de
|
||||||
# niet' ('sprinters rijden niet', 'metro rijdt niet').
|
# titel) is geen OV-bewijs — 'openbaar vervoer' + 'zakelijke
|
||||||
# Eén hit (titel óf samenvatting) is onvoldoende: moet
|
# reizigers' in een economiebericht telt dan toch op tot de
|
||||||
# combineren (2 + 1 = 3). De vroegere 'één sterk woord
|
# drempel. Sterke (3) families blijven in de samenvatting
|
||||||
# volstaat'-regel is vervallen — 'hermes' als achternaam
|
# gewoon tellen.
|
||||||
# passeerde zo op één vervoerder-hit (Margraten,
|
|
||||||
# 2026-09-12).
|
|
||||||
# - zwak (1): DUBBELZINNIGHEID. Woorden die (ook) buiten het OV
|
# - zwak (1): DUBBELZINNIGHEID. Woorden die (ook) buiten het OV
|
||||||
# voorkomen. REGEL: elk dubbelzinnig woord krijgt een EIGEN
|
# voorkomen. REGEL: elk dubbelzinnig woord krijgt een EIGEN
|
||||||
# familie (niet samengevoegd), zodat twee verschillende
|
# familie (niet samengevoegd), zodat twee verschillende
|
||||||
# zwakke woorden wél als combinatie tellen, maar één enkel
|
# zwakke woorden wél als combinatie tellen, maar één enkel
|
||||||
# woord nooit de doorslag kan geven. Bekende dubbelzinnige:
|
# woord nooit de doorslag kan geven. Bekende dubbelzinnige:
|
||||||
# trein, bus, station(s), sprinter, reiziger/passagier,
|
# trein, bus, station(s), sprinter, reiziger, vervoerder,
|
||||||
# vervoerder, inchecken/uitchecken, sein, overweg, breng,
|
# inchecken/uitchecken, sein, overweg, breng, kaartje,
|
||||||
# kaartje, opwaarderen, rails, dienstregeling, chipkaart,
|
# opwaarderen, rails, dienstregeling, chipkaart, ontsporen,
|
||||||
# reisproduct, vervoer(s)bewijs, ontsporen,
|
|
||||||
# pendelbus/shuttlebus (evenement-/hotel-/parkeerpendel;
|
# pendelbus/shuttlebus (evenement-/hotel-/parkeerpendel;
|
||||||
# synoniemen -> bewust één familie), busongeluk/busvervoer
|
# synoniemen -> bewust één familie, anders tellen twee
|
||||||
# (touringcar/schoolbus), staking, vervoer, vervoersbedrijf,
|
# synoniemen als twee onafhankelijke signalen).
|
||||||
# vervoerregio, ritten, bovenleiding (industrie),
|
# EXTRA REGEL voor zwak-alleen: staat er uitsluitend zwak
|
||||||
# abonnement, hermes, spoor.
|
# bewijs in de SAMENVATTING (geen enkele zwakke hit in de
|
||||||
# 3. TITEL-ANKER: staat er geen enkel OV-signaal in de titel (geen
|
# titel), dan niet doorlaten. Twee/meer zwakke woorden in
|
||||||
# familie-hit, geen whitelist-term), dan is de score uitsluitend
|
# de samenvatting zijn anders samen al 3 punten
|
||||||
# samenvatting-bewijs en mag die geen zwak woord bevatten. Twee/meer
|
# ("station" + "pendelbus" + "bus" in een golfverslag).
|
||||||
# zwakke woorden in de samenvatting ('station' + 'pendelbus' + 'bus' in
|
# 3. BLACKLIST -> gematchte woorden zonder OV-waarde (stationsstraat,
|
||||||
# een golfverslag) en sterk+zwak uitsluitend in de samenvatting
|
# stationair, stationcar, ...); blokkeren de familie niet.
|
||||||
# ('openbaar vervoer' + 'zakelijke reizigers' in een economiebericht,
|
|
||||||
# beide besluiten 2026-09-12) zijn geen OV-bewijs. Alleen twee sterke
|
|
||||||
# termen in de samenvatting blijven bewust door (delftopzondag
|
|
||||||
# 'tram 1, halte Buitenhofdreef', besluit 2026-09-13).
|
|
||||||
# BLACKLIST -> gematchte woorden zonder OV-waarde, restant van
|
|
||||||
# straatnamen rond spoorlijnen die de zwakke \b\w*trein\w*\b-familie zou
|
|
||||||
# matchen ('treinstraat'/'treinweg'); blokkeren de familie niet. De
|
|
||||||
# oudere stations*-varianten zijn sinds de woordgrens-familie
|
|
||||||
# \bstations?\b inert en verwijderd (2026-09-13) — gedrag vastgelegd in
|
|
||||||
# validate_filter.py.
|
|
||||||
#
|
#
|
||||||
# BEOORDELEN VAN EEN NIEUW WOORD (incl. COMPLETEHEIDS-AUDIT, 2026-09-12):
|
# BEOORDELEN VAN EEN NIEUW WOORD:
|
||||||
# a) Kan het ook buiten het OV voorkomen? ja -> zwak (1), eigen familie.
|
# a) Kan het ook buiten het OV voorkomen? ja -> zwak (1), eigen familie.
|
||||||
# b) Is het een samenvoeging/merk die alleen OV kan betekenen? -> sterk (2)
|
# b) Is het een samenvoeging/merk die alleen OV kan betekenen? -> sterk (3)
|
||||||
# of whitelist (frasen die in élke context OV betekenen).
|
# of whitelist.
|
||||||
# c) Bij twijfel: zwak (1). Conservatief = precision-first.
|
# c) Bij twijfel: zwak (1). Conservatief = precision-first.
|
||||||
# d) Ter controle: leg nieuwe woordkandidaten voor aan minimaal één extra
|
# d) Ter controle: leg nieuwe woordkandidaten voor aan minimaal één extra
|
||||||
# taalmodel (de audit op 2026-09-10 gebruikte 3 modellen; enige stem
|
# taalmodel (de audit op 2026-09-10 gebruikte 3 modellen; enige stem
|
||||||
# 'TWIJFEL'/'NIET-OV' => behandelen als dubbelzinnig).
|
# 'TWIJFEL'/'NIET-OV' => behandelen als dubbelzinnig).
|
||||||
# e) COMPLETEHEID (besluit 2026-09-12): check bij elke filterwijziging óók of
|
|
||||||
# de families compleet zijn — draai de OV-gap-scan over de afgewezen
|
|
||||||
# artikelen van OV-specialist-feeds (SpoorPro/Treinreiziger/Mobiliteit),
|
|
||||||
# classificeer nieuwe kandidaten via stap a-d en vul aan. De audit van
|
|
||||||
# 2026-09-12 leverde o.a. spoorsector, spoorbeheerder, spoornet,
|
|
||||||
# spoorvervoerder, treinvertraging/uitval, ertms, teso, rangeer, ice-frasen,
|
|
||||||
# maaslijn, ns international, deutsche bahn, european sleeper, italo,
|
|
||||||
# nightjet (sterk) en 11 zwakke woorden op.
|
|
||||||
# Dienststoring-contextpatroon: vervoerswoord + 'rijdt/rijden (niet)' + 'niet'
|
|
||||||
# ('sprinters rijden niet', 'metro rijdt niet tussen Rotterdam en Schiedam').
|
|
||||||
# Gedeeld door de familie én het titel-anker (_disruption_hit): in de TITEL is de
|
|
||||||
# storingszin een sterk OV-signaal (net als een whitelist-term), in de samenvatting
|
|
||||||
# weegt hij als familie (2). 'rijden' an sich is géén OV-signaal. 'schoolbus'
|
|
||||||
# matcht niet ('bus' niet aan het woordbegin), refactor 2026-09-13.
|
|
||||||
_DISRUPTION_RE_SRC = (
|
|
||||||
r"\b(?:sprinters?|stoptrein(?:en)?|intercity'?s?|trein(?:en)?|trams?|bussen|bus|"
|
|
||||||
r"metro'?s?|veerdienst(?:en)?)\s+(?:rijdt|rijden|reed|reden)\s+niet\b"
|
|
||||||
)
|
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
MATCH_FILTER_FAMILIES: list[tuple[str, int]] = [
|
MATCH_FILTER_FAMILIES: list[tuple[str, int]] = [
|
||||||
# sterk (2) — onmiskenbaar OV, maar één hit is onvoldoende (max-2-stelsel,
|
# sterk (3)
|
||||||
# besluit 2026-09-12): elke echte OV-bericht combineert met een tweede signaal.
|
(r"\b(?:ns|arriva|qbuzz|connexxion|cxx|transdev|keolis|ret|gvb|ebs|htm|syntus|u-ov|allgo|flixbus|blauwnet|r-net|valleilijn|waterbus|eurostar|thalys|randstadrail|gvu|prorail|hermes|rrreis|qliner|ov)\b", 3), # vervoerders ('Breng' staat in de zwakke groep: botst met het werkwoord "brengen")
|
||||||
(r"\b(?:ns|arriva|qbuzz|connexxion|cxx|transdev|keolis|ret|gvb|ebs|htm|syntus|u-ov|allgo|flixbus|blauwnet|r-net|valleilijn|waterbus|eurostar|thalys|randstadrail|gvu|prorail|rrreis|qliner|ov|teso|deutsche bahn|european sleeper|ns international|italo|nightjet)\b", 2), # vervoerders; 'hermes' is zwak (achternaam-botsing, Margraten 2026-09-12), 'Breng' ook (werkwoord "brengen"); internationale vervoerders: teso (veer), deutsche bahn/european sleeper/italo/nightjet/ns international (audit 2026-09-12)
|
(r"\b(?:spoorweg|spoorba(?:an|nen)|spoorlijn|spoorverkeer|spoorwegovergang|spoorstoring|perron|seinstoring|wisselstoring|spoorstaking)\w*\b", 3), # spoorinfra; let op de klinkerwisseling: 'ontsporing' (open lettergreep, één o) vs 'ontspoord'/'ontspoorde' (dicht, dubbel o) — vandaar beide stammen. Géén 'spoorbrug': die is medium (vaak locatie-referentie, besluit 2026-09-12)
|
||||||
(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 MEDIUM — éé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"\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"(?<![\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). 'busbaan'/'bushokje' stonden óók in de halte-familie — verwijderd bij de refactor 2026-09-13 (geen dubbelwoorden in twee families)
|
(r"\b(?:bus|tram|metro)-?(?:halte|haltes|lijn|lijnen|verkeer|net|staking|ba(?:an|nen)|hok(?:je|jes)|chauffeur)\w*\b", 3), # voertuig-samenstelling (bushalte/buslijn/bushokje/busbanen/…); géén 'station': *station-samenstellingen zitten in de station-familie, anders telde 'busstation' dubbel mee (via 'bus' én 'station') — fix 2026-09-11
|
||||||
(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(?:treinstoring|treinverkeer|treinstaking|treinongeval|treinbotsing|treinongeluk)\w*\b", 3), # trein-jargon-samenstellingen: onmiskenbaar OV
|
||||||
(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\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
|
(r"\b(?:intercity|streekvervoer|stadsvervoer|veerdienst|veerboot|veerboten|veerpont|voetveer|veerverbinding|buurtbus|belbus)\w*\b", 3), # treintypes, vervoerssectoren en veren ('sprinter' staat in de zwakke groep: atleet-botsing)
|
||||||
(r"\blijn\s?\d{1,3}\b", 2), # 'lijn 12' — vervoerslijn met nummer
|
(r"\blijn\s?\d{1,3}\b", 3), # 'lijn 12' — vervoerslijn met nummer
|
||||||
(r"(?:\brangeer\w*|\brangeren\w*)\b", 2), # rangeren/rangerend/rangeerterrein/rangeerbedrijf: onmiskenbaar spoorjargon (audit 2026-09-12; stam 'rangeren' = ranger+en, 'rangeerterrein' = rangeer+...)
|
# medium (2)
|
||||||
(r"\bice[\s\-–—]*(?:berlijn|hamburg|brussel|bremen|parijs|frankfurt|keulen|amsterdam|bazel|milaan|kopenhagen|zurich|zürich|duitsland|international)\b|\bice-trein\w*\b", 2), # ICE-frasen: onmiskenbaar trein; los 'ice' is NIET toevoegbaar (case-insensitive botsing met 'ijs', besluit 2026-09-12)
|
|
||||||
# onmiskenbaar OV maar iets algemener (2, moet ook combineren)
|
|
||||||
(r"\btrams?\b", 2),
|
(r"\btrams?\b", 2),
|
||||||
(r"\bmetro(?:'?s)?\b", 2),
|
(r"\bmetro(?:'?s)?\b", 2),
|
||||||
(r"\b(?:openbaar vervoer|lightrail|halte)\w*\b", 2), # frasen en halte-hits; 'bushokje'/'busbaan' matchen al de bus-samenstelling-familie — bewust niet dubbel in twee families (refactor 2026-09-13)
|
(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-chipkaart|ovpay|ov-pay|studentenreisproduct)\w*\b", 2),
|
||||||
(r"\b(?:ov-staking|conducteursstaking)\w*\b", 2),
|
(r"\b(?:ov-staking|conducteursstaking)\w*\b", 2),
|
||||||
(_DISRUPTION_RE_SRC, 2), # dienststoring-context ('sprinters rijden niet', 'metro rijdt niet...'); zie _DISRUPTION_RE_SRC
|
|
||||||
(r"\bconduct(?:eur|eurs)\b", 2), # trein-/buspersoneel: onmiskenbaar OV-jargon
|
(r"\bconduct(?:eur|eurs)\b", 2), # trein-/buspersoneel: onmiskenbaar OV-jargon
|
||||||
(r"\b(?:pont|ponten)\b", 2), # waterbus/veerdienst
|
(r"\b(?:pont|ponten)\b", 2), # waterbus/veerdienst
|
||||||
(r"\bspoorbrug(?:gen)?\b", 2), # spoorinfra, maar vaak locatie-referentie ('bij de spoorbrug', Stuifmail 2026-09-12); moet combineren — zelfde regel als station-samenstellingen
|
(r"\bspoorbrug(?:gen)?\b", 2), # spoorinfra, maar MEDIUM: vaak locatie-referentie, geen OV-nieuws ('bij de spoorbrug', Stuifmail 2026-09-12); moet combineren — zelfde regel als station-samenstellingen (besluit 2026-09-11)
|
||||||
# zwak (1) — dubbelzinnig, moet combineren
|
# zwak (1) — dubbelzinnig, moet combineren
|
||||||
(r"(?<!-)\bbus(?:sen)?\b", 1), # ná een koppelteken geen OV: 'VW-bus', 'school-bus'
|
(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(?: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"\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"\bsprinters?\b", 1), # ook atleet ('sprinter verpulvert record') — aparte familie, moet combineren
|
||||||
(r"\bvervoerders?\b", 1), # ook logistiek ('vrachtvervoerder') — aparte familie, moet combineren
|
(r"\bvervoerders?\b", 1), # ook logistiek ('vrachtvervoerder') — aparte familie, moet combineren
|
||||||
(r"\b(?:reizigers?|passagiers?)\b", 1), # ook 'zakenreizigers vliegen' / vliegtuigpassagiers — synoniemen bewust één familie (audit 2026-09-12)
|
(r"\breizigers?\b", 1), # ook algemeen ('zakenreizigers vliegen') — aparte familie, moet combineren
|
||||||
(r"\bdienstregeling\w*\b", 1), # dubbelzinnig (modelaudit) — meestal OV, maar moet combineren
|
(r"\bdienstregeling\w*\b", 1), # dubbelzinnig (modelaudit) — meestal OV, maar moet combineren
|
||||||
(r"\bchipkaart(?:en)?\b", 1), # dubbelzinnig (bankpas/algemeen) — aparte familie
|
(r"\bchipkaart(?:en)?\b", 1), # dubbelzinnig (bankpas/algemeen) — aparte familie
|
||||||
(r"\breisproduct(?:en)?\b", 1), # dubbelzinnig — aparte familie
|
(r"\breisproduct(?:en)?\b", 1), # dubbelzinnig — aparte familie
|
||||||
|
|
@ -239,73 +191,59 @@ MATCH_FILTER_FAMILIES: list[tuple[str, int]] = [
|
||||||
(r"\bkaart(?:je|jes)\b", 1),
|
(r"\bkaart(?:je|jes)\b", 1),
|
||||||
(r"\bopwaarderen\b", 1), # ov-chipkaart opladen vs werkwoord "een wijk/park opwaarderen"
|
(r"\bopwaarderen\b", 1), # ov-chipkaart opladen vs werkwoord "een wijk/park opwaarderen"
|
||||||
(r"\brails\b", 1),
|
(r"\brails\b", 1),
|
||||||
(r"\bbusongeluk(?:ken)?\b", 1), # audit 2026-09-12: ook touringcar/schoolbus; combineert met buschauffeur/bus → door (busongeluk Zwitserland)
|
|
||||||
(r"\bbusvervoer\b", 1), # audit 2026-09-12: ook touringcar-/bedrijfsvervoer
|
|
||||||
(r"\bspoor\b", 1), # figuurlijk 'op het spoor van de daders', 'het spoor bijster'; écht spoornieuws combineert met spoorlijn/trein/perron
|
|
||||||
(r"\bstaking(?:en)?\b", 1), # arbeidsstaking in alle sectoren; 'spoorstaking'/'busstaking'/'ov-staking' zitten in de sterke families (besluit 2026-09-12)
|
|
||||||
(r"\bvervoer\b", 1), # ook vracht-/autovervoer; 'openbaar vervoer' zit in de sterke familie; samenstellingen (streekvervoer e.d.) matchen hier niet (geen \b)
|
|
||||||
(r"\bvervoersbedrijf(?:ven)?\b", 1), # ook goederen-/taxibedrijf (audit 2026-09-12)
|
|
||||||
(r"\bvervoerregio'?s?\b", 1), # OV-autoriteit ('Vervoerregio Amsterdam'), maar audit: ook algemener vervoersgebied
|
|
||||||
(r"\britten\b", 1), # ook fiets-/taxiritten (audit 2026-09-12)
|
|
||||||
(r"\bbovenleiding(?:en)?\b", 1), # audit 2026-09-12: ook water/gas/elektra-bovenleiding; spoor-combinatie (spoorlijn/ertms) haalt de drempel
|
|
||||||
(r"\babonnement(?:en)?\b", 1), # gym/museum/telefoon (audit 2026-09-12)
|
|
||||||
(r"\bhermes\b", 1), # vervoerder Hermes vs. achternaam (Ton Hermes, stichting Adoptie Graven Margraten, 2026-09-12)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Drempel voor doorlaten: som van unieke familie-gewichten >= 3. Max-2-stelsel
|
# Drempel voor doorlaten (score = 2×titel + 1×samenvatting).
|
||||||
# (besluit 2026-09-12): geen enkele familie weegt meer dan 2, dus een enkele hit
|
|
||||||
# kan nooit solo doorlaten; een whitelist-term in de titel telt +2 (zie
|
|
||||||
# _filter_passes). De oude '2×titel + 1×samenvatting'-rekensom is vervallen
|
|
||||||
# (2026-09-11: de titel weegt niet extra).
|
|
||||||
MATCH_FILTER_THRESHOLD = 3
|
MATCH_FILTER_THRESHOLD = 3
|
||||||
|
|
||||||
# Woorden die een familie wél matcht maar die geen OV-waarde hebben. Een hit dat
|
# Woorden die een familie wél matcht maar die geen OV-waarde hebben. Een hit dat
|
||||||
# hierin staat telt niet mee voor de score (en blokkeert de familie niet). Vorm:
|
# hierin staat telt niet mee voor de score (en blokkeert de familie niet). Vorm:
|
||||||
# exact woord (lowercase), zoals door de familie-regex wordt gematcht.
|
# exact woord (lowercase), zoals door de familie-regex wordt gematcht.
|
||||||
# Refactor 2026-09-13: alleen woorden die een HUIDIGE familie nog matchen blijven
|
|
||||||
# staan ('treinstraat'/'treinweg' via de zwakke \b\w*trein\w*\b-familie). De
|
|
||||||
# stations*-varianten (stationsstraat, stationair, stationeren, stationcar,
|
|
||||||
# stationwagen, stationsplein, ...) waren inert sinds de woordgrens-familie
|
|
||||||
# \bstations?\b en zijn verwijderd — dat gedrag is in validate_filter.py
|
|
||||||
# vastgelegd.
|
|
||||||
MATCH_FILTER_BLACKLIST: frozenset[str] = frozenset({
|
MATCH_FILTER_BLACKLIST: frozenset[str] = frozenset({
|
||||||
# Straatnamen rond spoorlijnen die de \b\w*trein\w*\b-familie zou matchen:
|
# Straatnamen rond stations (genitief-s-vorm van "\bstation\w*\b"): een straat
|
||||||
# een straat die toevallig 'Trein' heet maakt een bericht geen OV-bericht.
|
# die toevallig bij een station ligt maakt een bericht nog geen OV-bericht.
|
||||||
|
"stationsstraat", "stationsweg", "stationslaan", "stationsplein",
|
||||||
|
"stationssingel", "stationskade", "stationsdreef", "stationshof",
|
||||||
|
"stationspad", "stationsboulevard", "stationsgracht", "stationsdijk",
|
||||||
|
"stationssteeg", "stationswal", "stationsbuurt", "stationswijk", "stationspark",
|
||||||
|
# Idem voor "\btrein\w*\b".
|
||||||
"treinstraat", "treinweg",
|
"treinstraat", "treinweg",
|
||||||
|
# Idem voor "\bstation\w*\b": werkwoords-/bijvoeglijke vormen zonder OV-waarde
|
||||||
|
# ("motor stationair laten draaien", "auto's hier stationeren") en
|
||||||
|
# samenstellingen met een station in de naam ("stationcar", "stationwagen").
|
||||||
|
"stationair", "stationeel", "stationeren", "stationeert", "stationeerde",
|
||||||
|
"stationeerden", "stationerend", "stationcar", "stationwagen",
|
||||||
|
"stationnement", "stationering",
|
||||||
})
|
})
|
||||||
|
|
||||||
# MATCH_FILTER_WHITELIST: termen die een bericht per definitie OV maken. Sinds
|
# MATCH_FILTER_WHITELIST: termen die een bericht per definitie OV maken. Eén enkele
|
||||||
# besluit 2026-09-12 is dit geen auto-pass meer: een whitelist-hit in de TITEL
|
# hit in de TITEL laat ALTIJD door (geen familiescore nodig) — dit is het expliciete
|
||||||
# telt +2 (één onmiskenbare term 2, nog een signaal nodig voor de drempel 3), en
|
# "onmiskenbaar OV"-signaal, zodat we voor deze termen niet op de gewogen
|
||||||
# in de SAMENVATTING helemaal niet (die scoort via de overeenkomstige familie —
|
# heuristiek hoeven te leunen. Vergelijking is case-insensitive, dus ook 'nederlandse
|
||||||
# een whitelist-term zit altijd óók in een familie). Zie _filter_passes.
|
# spoorwegen', 'prorail' en 'NS' in all-caps titels tellen mee.
|
||||||
# "Onmiskenbaar" = in élke context OV: merken (NS/ProRail/Arriva), onmiskenbaar
|
# Een whitelist-hit ALLEEN in de samenvatting is géén onvoorwaardelijk signaal
|
||||||
# jargon (spoorstoring, perron, bushalte, conducteur, ovpay), multi-woord-frasen
|
# (besluit 2026-09-12: 'openbaar vervoer' als bijzin in een economiebericht is geen
|
||||||
# (nederlandse spoorwegen, openbaar vervoer, ice-berlijn ...).
|
# OV-nieuws); die term scoort dan via de gewogen families — voor whitelist-termen
|
||||||
# Regel: twijfel je of de term ook buiten het OV kan voorkomen -> NIET hier, maar
|
# die geen eigen familie hebben (bv. 'treinstel': alleen zwak via \w*trein\w*) is
|
||||||
# zwak (eigen familie). Daarom 'hermes' hieruit (achternaam, besluit 2026-09-12).
|
# een samenvatting-alleen hit dus zwak.
|
||||||
# 'treinstel'/'veerdienst'/'buurtbus'/'lijn 12' staan hier alleen als TITEL-anker
|
# Termen MOETEN in elke context OV betekenen. Niet hierop (algemeen/dubbelzinnig):
|
||||||
# (= het oude effect van één onmiskenbare term in de titel, nu via +2) — in de
|
# los 'trein', 'bus', 'metro', 'tram', 'station', 'lijn', 'ov', 'reiziger',
|
||||||
# samenvatting wegen ze als familie (2/1). Vergelijking is case-insensitive.
|
# 'vervoerder', 'sprinter',
|
||||||
|
# 'inchecken' (Schiphol/hotel), 'sein' (brandweerjargon), 'overweg', 'treinramp'
|
||||||
|
# (films) — die blijven in de gewogen families.
|
||||||
MATCH_FILTER_WHITELIST: frozenset[str] = frozenset({
|
MATCH_FILTER_WHITELIST: frozenset[str] = frozenset({
|
||||||
# Vervoerders en OV-merken (incl. multi-woord termen)
|
# Vervoerders en OV-merken (incl. multi-woord termen)
|
||||||
"nederlandse spoorwegen", "ns", "prorail", "randstadrail", "arriva", "qbuzz",
|
"nederlandse spoorwegen", "ns", "prorail", "randstadrail", "arriva", "qbuzz",
|
||||||
"connexxion", "transdev", "keolis", "ret", "gvb", "htm", "ebs", "syntus",
|
"connexxion", "transdev", "keolis", "ret", "gvb", "htm", "ebs", "syntus",
|
||||||
"u-ov", "allgo", "blauwnet", "r-net", "valleilijn", "rrreis",
|
"u-ov", "allgo", "blauwnet", "r-net", "valleilijn", "hermes", "rrreis",
|
||||||
"qliner", "flixbus", "eurostar", "thalys", "gvu", "ovpay",
|
"qliner", "flixbus", "eurostar", "thalys", "gvu", "ovpay",
|
||||||
"deutsche bahn", "european sleeper", "ns international", "italo", "nightjet",
|
|
||||||
"teso", "ice-berlijn",
|
|
||||||
# Spoorinfra-jargon (samenvoegingen, onmiskenbaar OV)
|
# Spoorinfra-jargon (samenvoegingen, onmiskenbaar OV)
|
||||||
"spoorstoring", "spoorverkeer", "spoorwegovergang", "spoorlijn", "spoorbaan",
|
"spoorstoring", "spoorverkeer", "spoorwegovergang", "spoorlijn", "spoorbaan",
|
||||||
"spoorstaking", "spoorsector", "spoorbeheerder", "spoornet", "spoorvervoerder",
|
"spoorstaking", "perron", "wisselstoring", "seinstoring", "treinstoring", "treinverkeer",
|
||||||
"ertms", "perron", "wisselstoring", "seinstoring", "treinstoring", "treinverkeer",
|
"treinstaking", "treinongeval", "treinbotsing", "treinstel",
|
||||||
"treinstaking", "treinongeval", "treinbotsing", "treinstel", "treinvertraging",
|
|
||||||
"treinhinder", "treinuitval", "treinverstoring", "maaslijn", "nedersaksenlijn",
|
|
||||||
"kamperlijn",
|
|
||||||
# Bus/tram/metro-jargon
|
# Bus/tram/metro-jargon
|
||||||
"bushalte", "bushaltes", "busbaan", "buslijn", "busnet",
|
"bushalte", "bushaltes", "busbaan", "buslijn", "busnet",
|
||||||
"busstaking", "buurtbus", "veerdienst", "lijn 12",
|
"busstaking", "tramhalte", "tramlijn", "tramnet", "tramverkeer",
|
||||||
"tramhalte", "tramlijn", "tramnet", "tramverkeer",
|
|
||||||
"metrolijn", "metroverkeer",
|
"metrolijn", "metroverkeer",
|
||||||
# Tarief/vervoerder-jargon
|
# Tarief/vervoerder-jargon
|
||||||
"ov-chipkaart", "ov-staking", "lightrail",
|
"ov-chipkaart", "ov-staking", "lightrail",
|
||||||
|
|
@ -325,9 +263,6 @@ _WHITELIST_RE = re.compile(
|
||||||
)
|
)
|
||||||
_WHITELIST_PHRASES: tuple[str, ...] = tuple(w for w in MATCH_FILTER_WHITELIST if " " in w)
|
_WHITELIST_PHRASES: tuple[str, ...] = tuple(w for w in MATCH_FILTER_WHITELIST if " " in w)
|
||||||
|
|
||||||
# Dienststoring-context als sterk TITEL-signaal (zie _DISRUPTION_RE_SRC).
|
|
||||||
_DISRUPTION_RE = re.compile(_DISRUPTION_RE_SRC, re.IGNORECASE)
|
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseProxyConfig):
|
class Config(BaseProxyConfig):
|
||||||
def do_update(self, helper: ConfigUpdateHelper) -> None:
|
def do_update(self, helper: ConfigUpdateHelper) -> None:
|
||||||
|
|
@ -467,111 +402,73 @@ class RSSBot(Plugin):
|
||||||
low = text.lower()
|
low = text.lower()
|
||||||
return any(ph in low for ph in _WHITELIST_PHRASES)
|
return any(ph in low for ph in _WHITELIST_PHRASES)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _disruption_hit(text: str) -> bool:
|
|
||||||
"""True als een dienststoring-zin ('sprinters rijden niet', 'metro rijdt
|
|
||||||
niet tussen Rotterdam en Schiedam') in text staat. In de TITEL telt zo'n
|
|
||||||
zin als sterk OV-signaal (+2, net als een whitelist-term); als familie
|
|
||||||
(2) scoort hij overal — zie _DISRUPTION_RE_SRC."""
|
|
||||||
return _DISRUPTION_RE.search(text) is not None
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _fam_hits(text: str) -> list[tuple[int, int]]:
|
def _fam_hits(text: str) -> list[tuple[int, int]]:
|
||||||
"""(familie-index, gewicht) per OV-signaal in text (1× per familie).
|
"""(familie-index, gewicht) voor elke OV-familie die in text voorkomt (1× per familie).
|
||||||
|
|
||||||
Gematchte woorden uit MATCH_FILTER_BLACKLIST (o.a. 'stationsstraat')
|
Gematchte woorden uit MATCH_FILTER_BLACKLIST (o.a. 'stationsstraat')
|
||||||
tellen niet mee en blokkeren de familie niet: een écht OV-woord in
|
tellen niet mee en blokkeren de familie niet: een écht OV-woord in
|
||||||
dezelfde tekst scoort gewoon verder.
|
dezelfde tekst scoort gewoon verder."""
|
||||||
|
hits: list[tuple[int, int]] = []
|
||||||
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 — 'ov-chipkaart' bevat 'chipkaart' (2+1=3),
|
|
||||||
'openbaar vervoer' bevat 'vervoer' (2+1=3), 'treinstoring' matcht ook
|
|
||||||
de zwakke \\w*trein\\w*-familie (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 idx, (family, weight) in enumerate(_compiled_match_families):
|
||||||
for m in family.finditer(text):
|
for m in family.finditer(text):
|
||||||
if m.group(0).lower() in MATCH_FILTER_BLACKLIST:
|
if m.group(0).lower() in MATCH_FILTER_BLACKLIST:
|
||||||
continue
|
continue
|
||||||
spans.append((m.start(), m.end(), idx, weight))
|
hits.append((idx, weight))
|
||||||
break
|
break
|
||||||
spans.sort()
|
return hits
|
||||||
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
|
@staticmethod
|
||||||
def _filter_passes(title: str, summary: str) -> bool:
|
def _filter_passes(title: str, summary: str) -> bool:
|
||||||
"""Driestaps-beslissing, precision-first (refactor 2026-09-13; beslissing
|
"""Gewogen familiescore: elke OV-familie telt 1× per artikel (titel + samenvatting
|
||||||
equivalent met v1.13.0, maar de drie historische uitzonderings-takken
|
samen; herhaling of beide velden = één signaal). Doorlaten bij som >= drempel.
|
||||||
reduceren tot één regel — zie stap 3):
|
(titel weegt NIET extra — één medium-hit, 'restaurant naast busstation',
|
||||||
|
is geen OV-bewijs; besluit 2026-09-11).
|
||||||
|
|
||||||
1. STERK SIGNAAL: een whitelist-term óf een dienststoring-zin
|
Sterke families (gewicht 3) halen de drempel al met één woord in titel óf
|
||||||
('sprinters rijden niet') in de titel is voldoende. Elke
|
samenvatting. Medium (2) moet combineren met een andere OV-familie.
|
||||||
whitelist-term staat óók in een familie, dus zo'n titel telt +2 én
|
Zwakke woorden (losse 'bus', 'breng', 'overweg', 'overwegen', 'sein',
|
||||||
het familie-gewicht — samen altijd >= drempel ('NS meldt storing',
|
'opwaarderen', 'kaartje', 'rails', 'pendelbus') moeten combineren met een
|
||||||
'Werkzaamheden aan perron', 'Buurtbus rijdt niet meer'). In de
|
ander OV-woord én minstens één zwak woord moet in de titel staan; dezelfde
|
||||||
samenvatting ALLEEN is zo'n term géén bewijs ('openbaar vervoer'
|
zwakke familie in titel én samenvatting is géén combinatie. Whitelist-termen
|
||||||
als bijzin in een economiebericht, besluit 2026-09-12) en scoort
|
(NS, perron, bushalte, openbaar vervoer, ...) laten alleen onvoorwaardelijk
|
||||||
die alleen via de overeenkomstige familie.
|
door BIJ EEN HIT IN DE TITEL; in de samenvatting wegen ze mee als gewogen
|
||||||
|
familie. Een medium + zwak-combinatie UITSLUITEND in de samenvatting (geen
|
||||||
2. CONTEXT: de som van unieke OV-signalen over titel + samenvatting
|
enkele hit in de titel) is geen OV-bewijs ('openbaar vervoer' + 'zakelijke
|
||||||
>= drempel (3). Eén term telt één signaal per artikel ('busstation'
|
reizigers' in een economiebericht, besluit 2026-09-12). Weert oa
|
||||||
60× of in beide velden = één keer); overlappende familie-treffers
|
'VW-bus' (bus matcht niet ná een koppelteken), 'schoolbus', 'bussen rijden
|
||||||
van dezelfde term wegen 1× met het hoogste gewicht (span-merge in
|
om' in een woningbrand-lead, het werkwoord 'brengen', het idioom 'goed met
|
||||||
_fam_hits, besluit 2026-09-13 — 'ov-chipkaart' bevat 'chipkaart'
|
elkaar overweg kunnen', 'een wijk opwaarderen', brandweerjargon
|
||||||
maar weegt als één term 2, geen 3). Geen
|
'sein brandmeester', figuratief 'weer op de rails' en een golfverslag
|
||||||
familie weegt meer dan 2 (max-2-stelsel, besluit 2026-09-12): een
|
waarin de samenvatting 'station' + 'pendelbus' + 'bus' noemt.
|
||||||
enkel sterk woord kan nooit solo doorlaten ('hermes' als
|
Bewust doorgelaten: krant 'De Metro'/café 'De Tram' (medium) en atleet-
|
||||||
achternaam passeerde vroeger op één vervoerder-hit van 3 —
|
'sprinter' in combinatie met ander OV-woord."""
|
||||||
Margraten; écht OV-nieuws combineert in de praktijk altijd met een
|
# Whitelist: een onmiskenbare OV-term in de TITEL laat altijd door (het
|
||||||
tweede signaal, gevalideerd op 205 productie-artikelen).
|
# artikel kondigt het onderwerp aan: 'NS verlengt noodregeling'). Een
|
||||||
|
# whitelist-term ALLEEN in de samenvatting is geen OV-bewijs ('openbaar
|
||||||
3. TITEL-ANKER: staat er geen enkel OV-signaal in de titel (geen
|
# vervoer' als bijzin in een economiebericht, besluit 2026-09-12) en
|
||||||
familie-hit, geen whitelist-term), dan is de score uitsluitend
|
# scoort via de gewogen families hieronder.
|
||||||
samenvatting-bewijs en mag die geen zwak/ambigue woord bevatten —
|
if RSSBot._whitelist_hit(title):
|
||||||
'station'+'pendelbus'+'bus' in een golfverslag, 'openbaar
|
return True
|
||||||
vervoer'+'zakelijke reizigers' in een economiebericht en 'sterk +
|
|
||||||
zwak uitsluitend in de samenvatting' (beide besluiten 2026-09-12)
|
|
||||||
vallen hieronder. Twee sterke termen in de samenvatting blijven
|
|
||||||
bewust door (delftopzondag 'tram 1, halte Buitenhofdreef',
|
|
||||||
besluit 2026-09-13).
|
|
||||||
|
|
||||||
Weert zo '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 cultuurstukje over een 'stationspiano'. Bewust doorgelaten:
|
|
||||||
krant 'De Metro'/café 'De Tram' (2, combineert) en atleet-'sprinter'
|
|
||||||
in combinatie met ander OV-woord."""
|
|
||||||
t_hits = RSSBot._fam_hits(title)
|
t_hits = RSSBot._fam_hits(title)
|
||||||
s_hits = RSSBot._fam_hits(summary)
|
s_hits = RSSBot._fam_hits(summary)
|
||||||
# Unieke OV-signalen over beide velden: één term telt één signaal per
|
# Eén familie telt 1× per ARTIKEL (titel én samenvatting samen): 'busstation'
|
||||||
# artikel ('busstation' in titel én samenvatting, of 60×, blijft één
|
# in beide velden, of 60× in de tekst, blijft één signaal (besluit 2026-09-11).
|
||||||
# treffer); _fam_hits levert per term al het hoogste gewicht bij
|
|
||||||
# overlappende familie-treffers.
|
|
||||||
combined = dict(t_hits + s_hits)
|
combined = dict(t_hits + s_hits)
|
||||||
score = sum(combined.values())
|
score = sum(combined.values())
|
||||||
strong_title = RSSBot._whitelist_hit(title) or RSSBot._disruption_hit(title)
|
|
||||||
if strong_title:
|
|
||||||
# Stap 1: sterk titel-signaal — +2 bovenop het familie-gewicht van
|
|
||||||
# dezelfde term, waardoor een eenduidige OV-titel alleen voldoet.
|
|
||||||
score += 2
|
|
||||||
if score < MATCH_FILTER_THRESHOLD:
|
if score < MATCH_FILTER_THRESHOLD:
|
||||||
return False
|
return False
|
||||||
if not t_hits and not strong_title and 1 in combined.values():
|
if max(combined.values()) == 1:
|
||||||
# Stap 3: geen enkel titel-signaal én een zwak woord in de score —
|
# Alleen zwakke families: dan moeten ≥2 verschillende families meetellen
|
||||||
# samenvatting-bewijs zonder titel-anker is geen OV-bewijs.
|
# é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(combined) < 2:
|
||||||
|
return False
|
||||||
|
elif not t_hits and 1 in combined.values():
|
||||||
|
# Medium + zwak uitsluitend in de samenvatting, niets in de titel: geen
|
||||||
|
# OV-bewijs. 'openbaar vervoer' + 'zakelijke reizigers' in een
|
||||||
|
# economiebericht telt anders op tot de drempel (besluit 2026-09-12).
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -579,24 +476,26 @@ class RSSBot(Plugin):
|
||||||
def _filter_summary() -> str:
|
def _filter_summary() -> str:
|
||||||
"""Mensvriendelijke weergave van het vaste gewogen match-filter."""
|
"""Mensvriendelijke weergave van het vaste gewogen match-filter."""
|
||||||
return (
|
return (
|
||||||
"**OV-matchfilter (vast in code; max-2-stelsel, precision-first):**\n"
|
"**OV-matchfilter (vast in code, gewogen families):**\n"
|
||||||
"- **Sterk signaal in de TITEL → door**: whitelist-term (NS, ProRail, Nederlandse Spoorwegen, spoorstoring, perron, "
|
"- Sterk (3): vervoerders (NS, Arriva, ProRail, Qbuzz, ...), spoorinfra (spoorlijn, spoorwegovergang, perron, spoorstoring), "
|
||||||
"bushalte, conducteur, ov-chipkaart, ovpay, openbaar vervoer, Deutsche Bahn, ICE Berlijn, ...) of een dienststoring-zin "
|
"samenstellingen als bushalte/buslijn/bushokje/busbaan/busverkeer, treinstoring/treinverkeer/treinstaking, "
|
||||||
"('sprinters rijden niet', 'metro rijdt niet') — die telt +2 bovenop het familie-gewicht. In de samenvatting alleen "
|
"intercity/streekvervoer/veerdienst/buurtbus/belbus, 'lijn 12'\n"
|
||||||
"weegt zo'n term mee als gewone familie.\n"
|
"- Medium (2): tram, metro, openbaar vervoer, lightrail, halte, pont, spoorbrug, ov-chipkaart/ovpay, ov-staking, conducteur, "
|
||||||
"- **Context (som ≥ 3)**: unieke OV-families over titel + samenvatting. Sterk (2): vervoerders (NS, Arriva, ProRail, Qbuzz, ...), "
|
"trein-/bus-/metrostation — één medium-hit volstaat niet, moet combineren met een andere OV-familie; "
|
||||||
"spoorinfra (spoorlijn, spoorstoring, perron, ertms, spoorsector), bus-samenstellingen (bushalte, buslijn, buschauffeur, busbaan), "
|
"medium + zwak uitsluitend in de samenvatting (geen hit in de titel) telt niet\n"
|
||||||
"treinstoring/treinverkeer/treinvertraging, intercity/streekvervoer/veerdienst/buurtbus/belbus, 'lijn 12', ICE-frasen, rangeer..., "
|
"- Zwak (1): losse 'bus' (niet ná een koppelteken), 'pendelbus'/'shuttlebus', 'trein', 'station(s)', 'breng', 'overweg', 'overwegen', "
|
||||||
"tram, metro, openbaar vervoer, lightrail, halte, pont, spoorbrug, ov-chipkaart/ovpay, ov-staking, conducteur, dienststoring-zin. "
|
"'sein', 'kaartje', 'opwaarderen', 'rails', 'inchecken'/'uitchecken', 'dienstregeling', 'chipkaart', 'reisproduct', 'vervoersbewijs', "
|
||||||
"Zwak (1): losse 'bus' (niet ná een koppelteken), 'pendelbus'/'shuttlebus', 'trein', 'station(s)', 'breng', 'overweg', "
|
"'ontsporen', 'reiziger', 'vervoerder', 'sprinter' — moet combineren met een ander OV-woord, met minstens één zwak woord in de titel\n\n"
|
||||||
"'overwegen', 'sein', 'kaartje', 'opwaarderen', 'rails', 'inchecken'/'uitchecken', 'dienstregeling', 'chipkaart', "
|
"**Whitelist (altijd door bij een hit in de TITEL):** een onmisbare OV-term in de titel is genoeg "
|
||||||
"'reisproduct', 'vervoersbewijs', 'ontsporen', 'reiziger'/'passagier', 'vervoerder', 'sprinter', 'spoor', 'staking', "
|
"(NS, ProRail, Nederlandse Spoorwegen, spoorstoring, perron, bushalte, conducteur, "
|
||||||
"'vervoer', 'busongeluk', 'busvervoer', ... — elke dubbelzinnige term heeft een eigen familie en moet combineren.\n"
|
"ov-chipkaart, ovpay, openbaar vervoer, ...); in de samenvatting alleen weegt zo'n term mee "
|
||||||
"- **Eén term = één signaal**: herhaling, beide velden en overlappende familie-treffers wegen niet dubbel. "
|
"als gewogen familie (medium/sterk)\n\n"
|
||||||
"Zonder enig titel-signaal (familie-hit, whitelist-term of storingszin in de titel) mag de score geen zwak woord "
|
"Uitgesloten (blacklist): woorden die wél matchen maar geen OV-waarde hebben, "
|
||||||
"bevatten — samenvatting-bewijs is geen OV-bewijs (twee sterke termen in de samenvatting blijven bewust door).\n"
|
"o.a. straatnamen rond stations ('stationsstraat', 'stationsweg', ...) en niet-OV-vormen als 'stationcar' en 'stationair'\n\n"
|
||||||
"- **Uitgesloten (blacklist)**: 'treinstraat'/'treinweg' (straatnamen rond spoorlijnen); historische stations*-gevallen "
|
"Doorlaten: som van unieke OV-families ≥ 3 — één familie telt 1× per artikel "
|
||||||
"('stationsstraat', 'stationair', 'stationcar', ...) worden al door de woordgrens-familie geweerd"
|
"(titel + samenvatting samen; herhaling of beide velden = één signaal); "
|
||||||
|
"bij uitsluitend zwakke woorden tellen alleen "
|
||||||
|
"≥2 verschillende families én een zwak woord in de titel"
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _send(
|
async def _send(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
id: nl.eddydevink.rss
|
id: nl.eddydevink.rss
|
||||||
version: 1.14.0
|
version: 1.11.0
|
||||||
modules:
|
modules:
|
||||||
- buzzrss
|
- buzzrss
|
||||||
main_class: RSSBot
|
main_class: RSSBot
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,8 @@ WANT_ASSIGN = {
|
||||||
"MATCH_FILTER_FAMILIES", "MATCH_FILTER_THRESHOLD", "MATCH_FILTER_BLACKLIST",
|
"MATCH_FILTER_FAMILIES", "MATCH_FILTER_THRESHOLD", "MATCH_FILTER_BLACKLIST",
|
||||||
"MATCH_FILTER_WHITELIST", "_compiled_match_families",
|
"MATCH_FILTER_WHITELIST", "_compiled_match_families",
|
||||||
"_WHITELIST_RE", "_WHITELIST_PHRASES",
|
"_WHITELIST_RE", "_WHITELIST_PHRASES",
|
||||||
"_DISRUPTION_RE_SRC", "_DISRUPTION_RE",
|
|
||||||
}
|
}
|
||||||
WANT_METHODS = {"_fam_hits", "_filter_passes", "_filter_text", "_whitelist_hit", "_disruption_hit"}
|
WANT_METHODS = {"_fam_hits", "_filter_passes", "_filter_text", "_whitelist_hit"}
|
||||||
|
|
||||||
nodes = []
|
nodes = []
|
||||||
for n in tree.body:
|
for n in tree.body:
|
||||||
|
|
@ -74,16 +73,12 @@ def check(cond: bool, msg: str):
|
||||||
# --- Invariant 1: dubbelzinnige woorden mogen nooit sterk/medium of whitelist zijn
|
# --- Invariant 1: dubbelzinnige woorden mogen nooit sterk/medium of whitelist zijn
|
||||||
AMBIGUOUS = [
|
AMBIGUOUS = [
|
||||||
"trein", "bus", "station", "stations", "sprinter", "sprinters",
|
"trein", "bus", "station", "stations", "sprinter", "sprinters",
|
||||||
"reiziger", "reizigers", "passagier", "passagiers", "vervoerder", "vervoerders",
|
"reiziger", "reizigers", "vervoerder", "vervoerders",
|
||||||
"inchecken", "uitchecken", "sein", "overweg", "overwegen", "breng",
|
"inchecken", "uitchecken", "sein", "overweg", "overwegen", "breng",
|
||||||
"kaartje", "opwaarderen", "rails", "dienstregeling", "chipkaart",
|
"kaartje", "opwaarderen", "rails", "dienstregeling", "chipkaart",
|
||||||
"reisproduct", "vervoersbewijs", "vervoerbewijs",
|
"reisproduct", "vervoersbewijs", "vervoerbewijs",
|
||||||
"pendelbus", "shuttlebus",
|
"pendelbus", "shuttlebus",
|
||||||
"ontspor", "ontspoor", "ontspoord", "ontspoorde", "ontsporing",
|
"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 ==")
|
print(f"== invariant 1: {len(AMBIGUOUS)} dubbelzinnige woorden -> alleen zwak (1), nooit whitelist ==")
|
||||||
for w in AMBIGUOUS:
|
for w in AMBIGUOUS:
|
||||||
|
|
@ -98,12 +93,6 @@ UNAMBIGUOUS = [
|
||||||
"conducteur", "ovpay", "ns", "prorail",
|
"conducteur", "ovpay", "ns", "prorail",
|
||||||
"treinstoring", "halte", "openbaar vervoer", "intercity", "buurtbus",
|
"treinstoring", "halte", "openbaar vervoer", "intercity", "buurtbus",
|
||||||
"veerdienst", "lijn 12", "ov-chipkaart", "spoorbrug",
|
"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 ==")
|
print(f"== invariant 2: {len(UNAMBIGUOUS)} onmiskenbare OV-termen -> sterk/medium of whitelist ==")
|
||||||
for w in UNAMBIGUOUS:
|
for w in UNAMBIGUOUS:
|
||||||
|
|
@ -111,22 +100,12 @@ for w in UNAMBIGUOUS:
|
||||||
check(whitelisted(w) or any(g >= 2 for g in gew),
|
check(whitelisted(w) or any(g >= 2 for g in gew),
|
||||||
f"{w!r} heeft whitelist of gewicht >= 2 (gewichten {sorted(gew)})")
|
f"{w!r} heeft whitelist of gewicht >= 2 (gewichten {sorted(gew)})")
|
||||||
|
|
||||||
# --- Invariant 3: patronen compileren en gewichten geldig (max-2, besluit 2026-09-12)
|
# --- Invariant 3: patronen compileren en gewichten geldig
|
||||||
print("== invariant 3: alle families compileren, gewicht in {1,2} ==")
|
print("== invariant 3: alle 24+ families compileren, gewicht in {1,2,3} ==")
|
||||||
for p, w in FAMILIES:
|
for p, w in FAMILIES:
|
||||||
check(w in (1, 2), f"familie {p[:40]!r} heeft geldig gewicht {w} (max-2-stelsel)")
|
check(w in (1, 2, 3), f"familie {p[:40]!r} heeft geldig gewicht {w}")
|
||||||
check(len(COMPILED) == len(FAMILIES), "alle families compileren")
|
check(len(COMPILED) == len(FAMILIES), "alle families compileren")
|
||||||
|
|
||||||
# --- Invariant 4 (refactor 2026-09-13): sterke-signaal-garantie + geen dode
|
|
||||||
# blacklist. Elke whitelist-term moet óók in een familie zitten (anders telt een
|
|
||||||
# whitelist-titel niet als sterk signaal 2+2) en elke zwartgelijste term moet door
|
|
||||||
# minstens één familie worden gematcht (anders is de blacklist-regel dood gewicht).
|
|
||||||
print("== invariant 4: whitelist-term in familie; blacklist-term door familie gematcht ==")
|
|
||||||
for w in sorted(ns["MATCH_FILTER_WHITELIST"]):
|
|
||||||
check(any(p.search(w) for p, _ in COMPILED), f"whitelist-term {w!r} zit ook in een familie")
|
|
||||||
for w in sorted(ns["MATCH_FILTER_BLACKLIST"]):
|
|
||||||
check(any(p.search(w) for p, _ in COMPILED), f"blacklist-term {w!r} wordt door een familie gematcht")
|
|
||||||
|
|
||||||
# --- Regressie: gedrag van _filter_passes op bekende cases (precision-first)
|
# --- Regressie: gedrag van _filter_passes op bekende cases (precision-first)
|
||||||
GOLDEN = [
|
GOLDEN = [
|
||||||
# (titel, samenvatting, verwacht_door)
|
# (titel, samenvatting, verwacht_door)
|
||||||
|
|
@ -204,78 +183,6 @@ GOLDEN = [
|
||||||
("Lijn 12 rijdt vanaf maandag een andere route", "De route gaat via het centrum.", True),
|
("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),
|
("Werkzaamheden aan perron", "Het perron wordt volgende maand opgeknapt.", True),
|
||||||
("Reactie Nederlandse Spoorwegen", "De Nederlandse Spoorwegen zeggen meer in te zetten op punctualiteit.", 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
|
|
||||||
# fix 2026-09-13: één term mag nooit via twee families dubbel wegen.
|
|
||||||
# Echte case: Omroep Brabant (13-09-2026) — politie-achtervolging van fatbikers
|
|
||||||
# 'via de busbaan' scoorde 2+2=4 omdat 'busbaan' in de bus-samenstelling-familie
|
|
||||||
# (fam 4) én in de halte/busbaan-familie (fam 13) staat. Nu één signaal (2):
|
|
||||||
# zonder tweede OV-term onder de drempel.
|
|
||||||
("Groep jonge fatbikers door Eindhoven achtervolgd na beroving met geweld",
|
|
||||||
"Agenten reden met zwaailichten achter de groep aan, onder meer via de busbaan en een fietspad.", False),
|
|
||||||
("De politie achtervolgde de groep via de busbaan", "De achtervolging duurde enkele minuten.", True), # whitelist-titel-anker: 'busbaan' in de TITEL blijft door (+2); de merge raakt alleen de teller
|
|
||||||
# recall bewaakt: echt busbaan-nieuws blijft door (whitelist-titel-anker +2) en
|
|
||||||
# twee VERSCHILLENDE termen blijven samen tellen (geen over-merge).
|
|
||||||
("Busbaan wordt afgesloten voor het doorgaande verkeer", "De omleiding duurt tot het voorjaar.", True),
|
|
||||||
("Bus rijdt om, busbaan is afgesloten", "De bussen rijden via de wijk.", True), # bus(1) + busbaan(2) = 3
|
|
||||||
# refactor 2026-09-13 — positieve gevallen die de dienststoring-context én
|
|
||||||
# sterke signalen vastleggen (prompt "Starke OV-signalen").
|
|
||||||
("NS meldt storing: sprinters rijden niet", "De storing wordt verholpen.", True), # ns-titel + 'sprinters rijden niet'-context
|
|
||||||
("Metro rijdt niet tussen Rotterdam en Schiedam", "De metro's stoppen na de storing.", True), # 'metro rijdt niet'-context (2) + metro(2)
|
|
||||||
("Buslijn 12 vervalt tijdelijk", "De vervanging rijdt via de wijk.", True), # buslijn(2) + vervoer(1)
|
|
||||||
("Werkzaamheden aan perron", "Het perron wordt nieuwe maand opgeknapt.", True), # perron-titel-anker (2+2)
|
|
||||||
("Nieuwe bushalte geopend", "De halte ligt aan de rand van de wijk.", True), # bushalte-whitelist (2+2)
|
|
||||||
("Sprinters rijden niet tussen Utrecht en Amsterdam", "De oorzaak is nog onbekend.", True), # dienststoring-context(2) + sprinter(1) = 3 — prompt: echte sprinter-context
|
|
||||||
("Stoptreinen rijden niet naar Den Haag vanwege een storing", "De NS zet bussen in.", True), # 'stoptreinen rijden niet'(2) + trein(1) + ns(2)
|
|
||||||
# refactor 2026-09-13 — negatieve gevallen (prompt): ambigue woorden en
|
|
||||||
# straatnamen mogen niet zelfstandig voldoende zijn.
|
|
||||||
("Nieuwe woning bij station", "", False), # woning naast station: station(1) < 3
|
|
||||||
("Restaurant opent nieuwe vestiging", "Het restaurant ligt tegenover het station waar dagelijks veel reizigers komen.", False), # station+reizigers als bijzin — titel zegt niets over OV
|
|
||||||
("Stationstraat krijgt nieuwe woningen", "", False), # straatnaam-inert (oudere blacklist-entries verwijderd, refactor 2026-09-13)
|
|
||||||
("Werkzaamheden aan de stationsweg", "", False),
|
|
||||||
("Het stationsplein wordt heringericht voor de markt", "", False),
|
|
||||||
("De auto stond stationair te draaien", "", False),
|
|
||||||
("Nieuwe stationwagen van het merk", "", False),
|
|
||||||
("Stationcar uit de jaren zestig", "", False),
|
|
||||||
("De Metro publiceert vandaag een interview", "De krant verschijnt wekelijks.", False), # krant 'De Metro': metro(2) alleen
|
|
||||||
("Tram is een populaire naam voor het café", "", False), # café 'De Tram': tram(2) alleen
|
|
||||||
("Schoolbus betrokken bij ongeval", "De kinderen werden opgevangen.", False), # schoolbus: 0 OV-hits
|
|
||||||
("Bus en station tijdelijk gesloten voor het feest", "", False), # bus(1)+station(1) = 2 — geen auto-pass
|
|
||||||
("Wijkbewoners klagen over het station en de bussen", "De gemeente komt langs.", False), # station(1)+bussen(1)=2 — bus+station is geen auto-pass
|
|
||||||
("Passagiers klagen over de incheckbalie", "De balie opende te laat.", False), # passagiers(1)+inchecken(1) = 2
|
|
||||||
("Gemeente verhuist naar het stationsgebouw", "Het station en het stationsgebied worden vernieuwd.", False), # semantische stapel: station-varianten tellen niet als 4 signalen — alleen station(1) in samenvatting
|
|
||||||
# combinaties (prompt): twee onafhankelijke context-signalen zijn genoeg,
|
|
||||||
# tenzij de context niet-OV is (bovenstaande negatieve cases).
|
|
||||||
("Bus en tram krijgen in december een nieuwe dienstregeling", "", True), # bus(1)+dienstregeling(1)+tram(2) = 4 — echte OV-context van bus+dienstregeling
|
|
||||||
("Tram krijgt nieuwe dienstregeling", "De wijziging geldt vanaf december.", True), # tram(2)+dienstregeling(1) = 3
|
|
||||||
("De halte wordt verplaatst voor de bus", "De nieuwe halte komt bij de rotonde.", True), # halte(2)+bus(1) = 3
|
|
||||||
("Metro rijdt niet vanwege een storing", "Er is onbekend wanneer het rijden hervat.", True), # metro(2)+storing(1) = 3 (prompt: metro+storing)
|
|
||||||
("Trein rijdt niet meer op de spoorlijn naar Den Bosch", "Werkzaamheden duren tot maart.", True), # trein(1)+spoorlijn(2) = 3 (prompt: trein+spoorlijn)
|
|
||||||
("Sprinters rijden niet; NS zet bussen in", "", True), # sprinter(1)+rijden-niet(2)+ns(2) (prompt: sprinter+NS)
|
|
||||||
("De trams rijden niet door een storing", "Er zijn vervangende bussen ingezet.", True), # trams(2)+rijden-niet(2) (prompt: tram-storing)
|
|
||||||
("Tramverkeer stilgelegd na aanrijding", "", True), # tramverkeer-whitelist (prompt: tram-storing)
|
|
||||||
("ProRail meldt wisselstoring op traject Utrecht", "Het treinverkeer ligt stil.", True), # prorail-whitelist + treinverkeer(2)
|
|
||||||
("Treinstoring tussen Den Haag en Leiden", "Reizigers moeten rekening houden met vertraging.", True), # treinstoring-whitelist (prompt: treinstoring)
|
|
||||||
("Treinvertraging op het spoor bij Utrecht", "", True), # treinvertraging-whitelist (prompt: echte treinvertraging)
|
|
||||||
("NS past de dienstregeling aan", "Door werkzaamheden rijden er minder treinen.", True), # prompt-voorbeeld: ns + treinen
|
|
||||||
]
|
]
|
||||||
print(f"== regressie: {len(GOLDEN)} cases (precision-first) ==")
|
print(f"== regressie: {len(GOLDEN)} cases (precision-first) ==")
|
||||||
for t, s, want in GOLDEN:
|
for t, s, want in GOLDEN:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue