Brabantzorg MoSART

← Dashboard

README.md · last modified 2026-04-14 08:22

BrabantZorg MoSART Dashboard

Dedicated web dashboard for managing and analysing MoSART soundscape measurements at BrabantZorg care locations. Built with FastAPI, HTMX and Plotly — no JavaScript frameworks required.

Quick start

cd brabantzorg_app
pip install -r requirements.txt
python start_server.py

The dashboard opens at http://127.0.0.1:8002.

Location security

The server has access to MoSART measurement data worldwide, but this application enforces strict location-based restrictions. Every data fetch — whether from the dashboard or the API proxy — is validated against an allowlist of approved locations.

How it works:
- The [security] section in config/brabantzorg.toml defines which locations may be queried and the maximum radius around each.
- Any request for coordinates outside an approved zone is rejected.
- The fetch radius is silently capped to the zone's max_km (e.g. a request for 50 km around Sint Jan is capped to 1.0 km).
- This is enforced server-side in modules/location_guard.py — it cannot be bypassed by the client.

Currently approved:

Location Coordinates Max radius
Sint Jan (Uden) 51.6604, 5.6108 1.0 km

Adding a new location — edit config/brabantzorg.toml and add a [[security.allowed_locations]] block:

[[security.allowed_locations]]
name = "Heelwijk"
latitude = 51.7341812
longitude = 5.5329213
max_km = 1.0

Heelwijk and Hofstaete are already templated (commented out) in the config for easy activation.

Two layers of access control:
1. Location guard (brabantzorg.toml[security]) — master allowlist. No data outside these zones ever leaves the server.
2. API keys (api_keys.toml) — per-client restrictions. A Chromebook key can be limited to only Sint Jan, while an internal key sees all approved locations.

Features

Implementatiebeheer (Implementation management)

Meettijdlijn (Measurement timeline)

Analyse per fase (Per-phase analysis)

Three visualisations are rendered side-by-side for Fase 1 and Fase 2:
- Kwadrantverdeling — summary table with counts and percentages per quadrant.
- Beoordelingsruimte — 2D scatter plot (pleasantness × eventfulness) with quadrant colours.
- Kwadrant-cirkeldiagram — pie chart of quadrant distribution, plus optional grouped pie charts by room group.

Opmerkingen (Remarks)

Kamersamenvatting & Bronnenanalyse

QR-codes

Implementaties vergelijken (Comparison)

Aangepaste visualisaties (Custom visualisations)

Project structure

brabantzorg_app/
├── app.py                         # FastAPI application & routes
├── start_server.py                # Startup script (port 8002)
├── requirements.txt               # Python dependencies
├── README.md
├── config/
│   ├── brabantzorg.toml           # Project configuration (single source of truth)
│   └── api_keys.toml              # API keys for external clients
├── deploy/
│   └── com.soundappraisal.brabantzorg.plist  # macOS launchd service
├── modules/
│   ├── mosart_api.py              # Self-contained MoSART API client
│   ├── config_loader.py           # Read/write TOML config
│   ├── i18n.py                    # Dutch/English localisation (t() function)
│   ├── implementation_manager.py  # Implementation CRUD, grid, fetch
│   ├── data_cache.py              # Parquet-based measurement cache
│   ├── viz_handlers.py            # HTMX endpoint handlers for visualisations
│   ├── viz_helpers.py             # Chart & table generation (matplotlib/plotly)
│   ├── comparison.py              # Multi-implementation comparison
│   ├── qr_generator.py           # QR code generation & download
│   ├── api_proxy.py               # API proxy endpoints for external clients
│   ├── access_control.py          # API key validation
│   └── location_guard.py          # Location allowlist enforcement
├── static/
│   ├── logo.png                   # BrabantZorg logo
│   ├── manifest.json              # PWA manifest
│   └── service-worker.js          # PWA service worker (static asset caching)
├── data/                          # Cached parquet files (auto-created)
├── qr_codes/                      # Generated QR code images (auto-created)
└── logs/                          # Server logs (auto-created)

Configuration

All settings live in config/brabantzorg.toml:

Switching language

Change language = "nl" to language = "en" in the TOML file and reload the page. All UI labels, buttons, table headers and status messages will switch to English.

Architecture

Deployment

The app is deployed on a Mac Mini and exposed to the internet via Tailscale Funnel.

┌─────────────────────┐     HTTPS      ┌────────────────────────┐
│  Browser / PWA      │ ──────────────▶ │  Tailscale Funnel      │
│  (Chromebook, etc.) │                 │  (relay + TLS)         │
└─────────────────────┘                 └───────────┬────────────┘
                                                    │
                                                    ▼
                                        ┌────────────────────────┐
                                        │  Mac Mini              │
                                        │  /opt/services/        │
                                        │    brabantzorg_app/    │
                                        │  FastAPI :8002         │
                                        └───────────┬────────────┘
                                                    │
                                                    ▼ server-side
                                        ┌────────────────────────┐
                                        │  MoSART API (Azure)    │
                                        │  sor-mob-prd-backend   │
                                        └────────────────────────┘

Key points:
- The browser never contacts the MoSART API directly — all data fetching happens server-side on the Mini.
- API credentials are stored as an environment variable (MOSART_CLIENT_SECRET) on the Mini, never sent to the client.
- Tailscale Funnel provides public HTTPS with automatic Let's Encrypt certificates.

Public URL

https://soundappraisal-dev-server.tail184f99.ts.net/mosart_bz

Deploying updates

From your development machine, sync changes to the Mini:

rsync -av --exclude='__pycache__' --exclude='venv/' --exclude='logs/' --exclude='data/' \
  brabantzorg_app/ soundappraisal-dev-server:/opt/services/brabantzorg_app/

The app has auto-reload enabled — changes take effect within seconds.

Service management

The app runs as a macOS LaunchAgent (com.soundappraisal.brabantzorg) that auto-starts on boot.

# View status
launchctl list | grep soundappraisal

# Stop
launchctl unload ~/Library/LaunchAgents/com.soundappraisal.brabantzorg.plist

# Start
launchctl load ~/Library/LaunchAgents/com.soundappraisal.brabantzorg.plist

# View logs
tail -f /opt/services/brabantzorg_app/logs/stderr.log

Tailscale Funnel

# Enable (runs in background, persists across reboots)
/opt/homebrew/bin/tailscale funnel --bg --set-path /mosart_bz http://localhost:8002

# Disable
/opt/homebrew/bin/tailscale funnel --set-path /mosart_bz off

# Check status
/opt/homebrew/bin/tailscale serve status

Environment variables

Variable Description Where set
MOSART_CLIENT_SECRET MoSART API client secret launchd plist (on the Mini)

API proxy

The app exposes proxy endpoints for external/standalone clients that need programmatic access to MoSART data. The dashboard itself does not use these — it calls MoSARTAnalyzer directly server-side.

Endpoints

GET /api/health — health check, no authentication required.

curl https://soundappraisal-dev-server.tail184f99.ts.net/mosart_bz/api/health
# {"status": "ok"}

POST /api/fetch — fetch MoSART measurement data. Requires API key.

curl -X POST https://soundappraisal-dev-server.tail184f99.ts.net/mosart_bz/api/fetch \
  -H "Content-Type: application/json" \
  -H "X-API-Key: bz-chromebook-2026" \
  -d '{"latitude": 51.6604, "longitude": 5.6108, "start_date": "2025-10-20", "end_date": "2025-11-16", "distance_km": 2.5}'

Response: {"records": 123, "data": [...]}

Authentication

API keys are defined in config/api_keys.toml. Each key can restrict which locations a client may query.

[keys.brabantzorg_chromebook]
secret = "bz-chromebook-2026"
allowed_locations = ["Sint Jan"]

[keys.brabantzorg_internal]
secret = "bz-internal-2026"
allowed_locations = ["*"]   # unrestricted

Pass the key via X-API-Key header or ?key= query parameter.

Chromebook PWA

The app can be installed as a Progressive Web App on ChromeOS (or any device with Chrome).

Installation

  1. Open Chrome on the Chromebook.
  2. Navigate to https://soundappraisal-dev-server.tail184f99.ts.net/mosart_bz.
  3. Click the install icon in the address bar (or Menu → App installeren).
  4. The app appears in the ChromeOS launcher as BZ MoSART.
  5. Opens fullscreen with BrabantZorg branding — no browser chrome.

How it works

Dependencies

Key Python packages: FastAPI, uvicorn, pandas, numpy, plotly, matplotlib, qrcode, tomli-w, requests.

Requires Python 3.11+ (for tomllib).

Branding

The dashboard uses BrabantZorg's visual identity:
- Colours: BZ Pink (#c7007d) as primary, BZ Dark Blue (#00203b) as accent.
- Font: Inter (loaded from Google Fonts).
- Logo: static/logo.png (from brabantzorg.eu).

Author & License

Author: Tjeerd Andringa
© SoundAppraisal BV