docs: README voor de repo — filter v1.14.0, commando's, config en deploy
This commit is contained in:
parent
91d0c3d7fe
commit
0c5a07071c
2 changed files with 160 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,2 +1,5 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Taakbestanden die geen onderdeel zijn van de repo
|
||||||
|
Prompt voor agent*.md
|
||||||
|
|
|
||||||
157
README.md
Normal file
157
README.md
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
# 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).
|
||||||
Loading…
Reference in a new issue