README.md · last modified 2026-04-30 14:00
Publiek dashboard dat tijdens de Nationale Geluidmeetdag (29 april 2026) laat zien hoe Nederland en België klinken, op basis van binnenkomende MoSART-metingen via de Klankbord (NL) en Oorzaak (BE) apps.
/healthz toe aan beide URLs.De Cloudflare URL is gegenereerd door
cloudflared tunnel --url …zonder Cloudflare-account. Het random subdomein blijft hetzelfde zolang het cloudflared-proces draait, maar wijzigt na een mini-reboot of na het herstarten van cloudflared.
tomllib)cd geluidmeetdag_app
python3.12 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# Secret voor de API (gedeeld voor NL en BE — external_soundappraisal client)
export GMD_CLIENT_SECRET="…"
python start_server.py
Beschikbaar op http://127.0.0.1:8003. URL_PREFIX is lokaal leeg, dus paden zijn /... zonder prefix.
config/geluidmeetdag.toml bevat per bron:
client_id = "external_soundappraisal"client_secret_env = "GMD_CLIENT_SECRET" (env-var, niet in code)data_path = "/SoundAppraisalData"scope = "variant.soundappraisal variant.klankbord measurement.read global.read"scope = "variant.soundappraisal variant.oorzaak measurement.read global.read"bbox voor geografisch filterenDe parser in modules/mosart_api.py matcht survey-vragen op id-substring (pleasant, livel/vibran, calm, event), zodat zowel SoundAppraisal- als Klankbord- als Oorzaak-shapes worden ondersteund.
NL (Klankbord) en BE (Oorzaak) hanteren een andere vraagopbouw voor geluidsbronnen:
- NL — één multi-select vraag: nad_noises → ['Geluid door mensen', 'Natuurgeluiden']
- BE — één Likert-vraag per categorie: nad_noises[verkeer] → 'Helemaal niet', nad_noises[natuurgeluiden] → 'Overheersend', etc.
_extract_response_list detecteert de Likert-stijl automatisch en verzamelt de categorieën waarvan het antwoord ≠ 'Helemaal niet'.
Plaats in static/:
- soundappraisal_logo.png — header linksboven
- geluidmeetdag_logo.png — header rechtsboven en in Plotly-PNG-downloads
- favicon.png — tab-icoon
Zonder deze bestanden werkt het dashboard, maar de afbeeldingen blijven leeg (404 in de console).
Bepaald door [event_window] in de TOML:
| modus | venster | gedrag |
|---|---|---|
| pre | vóór start_utc |
countdown + 2025-preview, poller uit |
| live | start_utc ≤ nu < freeze_utc |
alle secties, HTMX auto-refresh, poller aan |
| post | vanaf freeze_utc |
eindresultaat, poller stopt, geen updates |
⚠️ De poller wordt alleen gestart bij service-start wanneer de modus al live is. Wanneer je in pre-modus draaide en start_utc daarna passeert, herstart eenmalig:
launchctl unload ~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist
launchctl load ~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist
Alles wordt opgeslagen in UTC; bij het renderen (stats-bar, map-hover, tijdlijn-as, opmerkingen) wordt geconverteerd naar Europe/Amsterdam.
De MoSARTClient haalt bij opstart automatisch een OAuth-token op. Wanneer het token verloopt (HTTP 401), reset de client de sessie en authenticeert eenmalig opnieuw voor de retry — zonder dat de poller stopt of de service herstart hoeft te worden.
Controleer logs/stdout.log als de tijdlijn stopt met bijwerken:
ssh soundappraisal-dev-server 'grep -E "\[NL\]|\[BE\]|401|🔄" /opt/services/geluidmeetdag_app/logs/stdout.log | tail -20'
Herhaalde ❌ data fetch failed: 401 zonder tussenliggende 🔄 token expired wijst op een verkeerd of verlopen secret in de plist.
Twee buckets in data/ (gitignored):
live: measurements_NL.parquet, measurements_BE.parquet (+ *_meta.json) — poller schrijft elke 2 min, dashboard leest.preview: preview_NL.parquet, preview_BE.parquet (+ meta) — handmatig gevuld met historische data, getoond in pre-modus.Bij freeze_utc wordt de eindstand vastgezet (frozen: true in meta).
Geluidmeetdag-app gebruikt git pull i.p.v. rsync.
# Eenmalig op de mini
ssh soundappraisal-dev-server '
cd /opt/services
git clone https://soundappraisal.app.codey.ch/Tjeerd/geluidmeetdag_app.git
cd geluidmeetdag_app
/opt/homebrew/bin/python3.12 -m venv venv
curl -sS https://bootstrap.pypa.io/get-pip.py | venv/bin/python3
venv/bin/pip install -r requirements.txt
'
# Plist installeren en env-vars injecteren
ssh soundappraisal-dev-server "
cp /opt/services/geluidmeetdag_app/deploy/com.soundappraisal.geluidmeetdag.plist \
~/Library/LaunchAgents/
plutil -replace EnvironmentVariables.GMD_CLIENT_SECRET -string '\$GMD_CLIENT_SECRET' \
~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist
plutil -insert EnvironmentVariables.URL_PREFIX -string /geluidmeetdag \
~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist 2>/dev/null || true
launchctl load ~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist
"
# Tailscale Funnel
ssh soundappraisal-dev-server '/opt/homebrew/bin/tailscale funnel --bg --set-path /geluidmeetdag http://localhost:8003'
# Cloudflare back-up tunnel (optioneel)
ssh soundappraisal-dev-server 'brew install cloudflared'
ssh soundappraisal-dev-server '
nohup /opt/homebrew/bin/cloudflared tunnel --url http://localhost:8003 --no-autoupdate \
> /opt/services/geluidmeetdag_app/logs/cloudflared.log 2>&1 &
sleep 6
grep -oE "https://[a-z0-9-]+\.trycloudflare\.com" \
/opt/services/geluidmeetdag_app/logs/cloudflared.log | head -1
'
# lokaal
git push
# op de mini (auto-reload pakt code-wijzigingen op binnen seconden)
ssh soundappraisal-dev-server 'cd /opt/services/geluidmeetdag_app && git pull'
# bij wijzigingen in requirements.txt of de plist:
ssh soundappraisal-dev-server '
cd /opt/services/geluidmeetdag_app && venv/bin/pip install -r requirements.txt
launchctl unload ~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist
launchctl load ~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist
'
Achter Tailscale Funnel of Cloudflare staan de routes onder /geluidmeetdag/...:
GET / of GET /geluidmeetdag/ — homepageGET /healthz — JSON-statusGET /stats?sources=NL,BE — live stats-regelGET /viz/<chart>?sources=NL,BE — chart-fragment (map, appraisal, pie, timeline, sources, remarks)GET /download/<chart>.png?sources=NL,BE — Plotly PNG-downloadGET /download/data.csv?sources=NL,BE — ruwe CSV&preview=1 toe om de preview-bucket te raadplegen.Een middleware in app.py strips automatisch URL_PREFIX van inkomende requests, zodat zowel /healthz als /geluidmeetdag/healthz werken. Tailscale Funnel strept de prefix zelf; Cloudflare Quick-Tunnel niet — de middleware handelt allebei af.
Pre-pagina toont een extra "Vorige Geluidmeetdag — 30 april 2025"-sectie zodra data/preview_<sid>.parquet bestaat.
export GMD_CLIENT_SECRET="…"
python scripts/fetch_preview.py # default: 2025-04-30
python scripts/fetch_preview.py 2025-04-30 2025-05-01
python scripts/fetch_preview.py --sources NL
python scripts/fetch_preview.py --clear # verwijder preview-parquets
De preview-bucket staat los van live; de poller raakt 'm niet aan.
curl https://soundappraisal-dev-server.tail184f99.ts.net/geluidmeetdag/healthz
curl https://cook-shake-flight-working.trycloudflare.com/healthz
Geeft per bron row_count, last_fetched, last_poll_ok, frozen, en de huidige mode.
Als de poller een tijdlang heeft gefaald (bijv. door token-verloop) en er een gat in de data zit, kan de ontbrekende periode alsnog worden opgehaald. De MoSART API levert ook historische data terug.
# Secret ophalen uit de LaunchAgent-plist en backfill draaien
ssh soundappraisal-dev-server '
SECRET=$(plutil -extract EnvironmentVariables.GMD_CLIENT_SECRET raw \
~/Library/LaunchAgents/com.soundappraisal.geluidmeetdag.plist)
GMD_CLIENT_SECRET="$SECRET" \
/opt/services/geluidmeetdag_app/venv/bin/python3 -c "
import sys; sys.path.insert(0, "/opt/services/geluidmeetdag_app")
from modules.mosart_api import MoSARTClient
from modules.config_loader import get_sources
from modules import data_store
gap_start = "2026-04-29T10:25:00.000Z" # aanpassen
gap_end = "2026-04-29T11:58:00.000Z" # aanpassen
for src in get_sources(enabled_only=True):
client = MoSARTClient(src)
df = client.fetch_window(gap_start, gap_end)
added = data_store.append(client.source_id, df)
print(f"{client.source_id}: +{added} nieuwe rijen")
"
'
Geef gap_start en gap_end als ISO8601 UTC (eindigend op Z). Bestaande records worden gededupliceerd op (sensorId, timeStamp), dus dubbel draaien is veilig.
scripts/diagnose_api.py 2025-04-30 2025-05-01 --source NL — raw API-response inspectie.scripts/probe_scope.py --source NL — alle scope-combinaties aftasten.scripts/fetch_preview.py — historische data-fetch (zie hierboven).De sectie "Geluidsbronnen" toont twee aparte horizontale bar-charts boven elkaar — één per bron:
- NL (magenta) — top 10 Klankbord-bronnen (vrije multi-select)
- BE (teal) — top 5 Oorzaak-categorieën (Likert-gebaseerd)
Limieten zijn instelbaar via SOURCE_TOP_N bovenin modules/viz_plotly.py.
Dashboardkleuren uit het Nationale-Geluidmeetdag-logo: magenta #E6187D, teal #2BB9E2, paars #3A1E7A.
Kwadrantkleuren (SA-conventie): Kalm groen, Saai donkerblauw, Chaotisch rood, Levendig oranje, Centrum grijs.
© SoundAppraisal BV.