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__/
|
||||
*.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