A small toolkit to monitor the Swiss Parliament (Curia Vista parliamentary items) through its undocumented OData API. It tracks a curated watchlist of items, detects changes (Federal Council responses, status changes, votes), and discovers new items matching your keywords — notifying you when something moves.
Two independent scripts:
parl_sync.py— re-fetches every item in your watchlist, records changes to a per-item changelog, and notifies on editorially relevant changes.parl_discover.py— searches for new items matching your keywords since the last run, and notifies with a ready-to-paste YAML snippet.
The most reusable piece is probably docs/swiss-parliament-odata.md:
field-level notes on the OData endpoint (collections, filters, /Date(ms)/
timestamps, the apostrophe-escaping gotcha, the mandatory User-Agent,
language filtering to avoid multilingual duplicates) — the kind of thing that is
otherwise nowhere to be found.
Status: working version, early and opinionated. The default keywords target financial-crime topics; change them to fit yours.
python3 -m venv venv
venv/bin/pip install -r requirements.txt
cp watchlist.example.yaml watchlist.yaml # then edit itDependencies: swissparlpy (OData
client for the Swiss Parliament) and ruamel.yaml (comment-preserving YAML).
# Sync the watchlist against the API
./sync.sh # via the cron-ready runner (logs to logs/)
./parl_sync.py --dry-run # show diffs without writing or notifying
./parl_sync.py --item 26.3313 # a single item
./parl_sync.py --no-notify # sync + write, but no notifications
# Discover new items matching the keywords
./discover.sh # normal run (updates state/)
./parl_discover.py --dry-run # show hits without writing state or notifying
./parl_discover.py --since 2026-01-01The runners (sync.sh, discover.sh) create a venv-aware wrapper with dated
logs and 30-day rotation — handy for cron:
0 7 * * * /path/to/parl/sync.sh # daily sync
0 8 * * 1 /path/to/parl/discover.sh # weekly discovery (Monday 8am)Both scripts read two environment variables:
| Variable | Default | Purpose |
|---|---|---|
PARL_WATCHLIST |
./watchlist.yaml |
Path to your watchlist file |
PARL_NOTIFY_CMD |
(unset) | Command used to dispatch notifications. The message is appended as the final argument. If unset, notifications are printed to stdout. |
Example — wire notifications to any CLI notifier:
export PARL_NOTIFY_CMD="/path/to/notify.sh --channel parl --message"
./parl_sync.pyWith PARL_NOTIFY_CMD unset, messages just print to stdout, so the tool is
usable out of the box and easy to pipe.
The watch keywords live at the top of parl_discover.py (KEYWORDS_TITLE,
KEYWORDS_DEEP). They are queried against the French-language API, so they are
in French.
watchlist.yaml is the source of truth for the items you track. See
watchlist.example.yaml for the full field glossary.
In short, each entry holds:
- stable fields you fill in by hand (
odata_id,type,titre,auteur,themes,note, ...) - synced fields refreshed from OData (
statut,cf_proposition,cf_date,modified_odata) - a
changes[]changelog thatparl_sync.pyappends to automatically
Your live watchlist.yaml is git-ignored — only the example is committed — so
your editorial notes never end up in the repo.
venv/bin/python -m unittest tests.test_odata_escapeCovers OData string escaping (the délit d'initié → délit d''initié fix) and
filter construction.
docs/swiss-parliament-odata.md— field-level reference for the undocumented Curia Vista OData endpoint (the source the scripts use today).docs/lobbywatch-api.md— the Lobbywatch dataIF API, an optional source for the declared interests of item authors. Not wired in yet; includes an integration plan.docs/openparldata.md— evaluation of the OpenParlData API (federal + cantonal + municipal). Not adopted yet (two blockers documented); worth re-evaluating as it matures.docs/voting-integration.md— design note for adding roll-call vote tracking via the ODataVotingcollection.
Data comes from the Swiss Parliament's Curia Vista service via its OData API. Per the Parliament's terms of use, any reproduction must:
- attribute "Services du Parlement de l'Assemblée fédérale, Berne";
- keep the data content unchanged and show the download date;
- not be presented as an official publication.
This is an independent project, not affiliated with or endorsed by the Swiss Parliament. The OData endpoint is undocumented and may change without notice.
MIT.