Geluidmeetdag

← Dashboard

README.md · last modified 2026-04-30 14:00

Nationale Geluidmeetdag — live dashboard

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.

Live 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.

Stack

Lokaal draaien

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.

Configuratie

config/geluidmeetdag.toml bevat per bron:

De 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.

Survey-structuurverschillen NL vs BE

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'.

Logos

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).

Dagmodi

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

Tijdzones

Alles wordt opgeslagen in UTC; bij het renderen (stats-bar, map-hover, tijdlijn-as, opmerkingen) wordt geconverteerd naar Europe/Amsterdam.

API-token verloop

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.

Data

Twee buckets in data/ (gitignored):

Bij freeze_utc wordt de eindstand vastgezet (frozen: true in meta).

Deploy op de Mac Mini

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
'

Updates uitrollen

# 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
'

Routes & path-prefix

Achter Tailscale Funnel of Cloudflare staan de routes onder /geluidmeetdag/...:

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.

Preview / test met historische data

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.

Health check

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.

Handmatige backfill

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.

Diagnostische scripts

Bronnengrafieken

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.

Branding

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.