# Copilot instructions for infoscreen_2025

Use this as your shared context when proposing changes. Keep edits minimal and match existing patterns referenced below.

## Big picture
- Multi-service app orchestrated by Docker Compose.
  - API: Flask + SQLAlchemy (MariaDB), in `server/` exposed on :8000 (health: `/health`).
  - Dashboard: React + Vite in `dashboard/`, dev on :5173, served via Nginx in prod.
  - MQTT broker: Eclipse Mosquitto, config in `mosquitto/config/mosquitto.conf`.
  - Listener: MQTT consumer handling discovery + heartbeats in `listener/listener.py`.
  - Scheduler: Publishes active events (per group) to MQTT retained topics in `scheduler/scheduler.py`.
  - Nginx: Reverse proxy routes `/api/*` and `/screenshots/*` to API; everything else to dashboard (`nginx.conf`).

## Service boundaries & data flow
- Database connection string is passed as `DB_CONN` (mysql+pymysql) to Python services.
  - API builds its engine in `server/database.py` (loads `.env` only in development).
  - Scheduler loads `DB_CONN` in `scheduler/db_utils.py`.
  - Listener also creates its own engine for writes to `clients`.
- MQTT topics (paho-mqtt v2, use Callback API v2):
  - Discovery: `infoscreen/discovery` (JSON includes `uuid`, hw/ip data). ACK to `infoscreen/{uuid}/discovery_ack`. See `listener/listener.py`.
  - Heartbeat: `infoscreen/{uuid}/heartbeat` updates `Client.last_alive` (UTC).
  - Event lists (retained): `infoscreen/events/{group_id}` from `scheduler/scheduler.py`.
  - Per-client group assignment (retained): `infoscreen/{uuid}/group_id` via `server/mqtt_helper.py`.
- Screenshots: server-side folders `server/received_screenshots/` and `server/screenshots/`; Nginx exposes `/screenshots/{uuid}.jpg` via `server/wsgi.py` route.

## Data model highlights (see `models/models.py`)
- Enums: `EventType` (presentation, website, video, message, webuntis), `MediaType` (file/website types), and `AcademicPeriodType` (schuljahr, semester, trimester).
- Tables: `clients`, `client_groups`, `events`, `event_media`, `users`, `academic_periods`, `school_holidays`.
- Academic periods: `academic_periods` table supports educational institution cycles (school years, semesters). Events and media can be optionally linked via `academic_period_id` (nullable for backward compatibility).
- Times are stored as timezone-aware; treat comparisons in UTC (see scheduler and routes/events).

## API patterns
- Blueprints live in `server/routes/*` and are registered in `server/wsgi.py` with `/api/...` prefixes.
- Session usage: instantiate `Session()` per request, commit when mutating, and always `session.close()` before returning.
- Examples:
  - Clients: `server/routes/clients.py` includes bulk group updates and MQTT sync (`publish_multiple_client_groups`).
  - Groups: `server/routes/groups.py` computes “alive” using a grace period that varies by `ENV`.
  - Events: `server/routes/events.py` serializes enum values to strings and normalizes times to UTC.
  - Media: `server/routes/eventmedia.py` implements a simple file manager API rooted at `server/media/`.
  - Academic periods: `server/routes/academic_periods.py` exposes:
    - `GET /api/academic_periods` — list all periods
    - `GET /api/academic_periods/active` — currently active period
    - `POST /api/academic_periods/active` — set active period (deactivates others)
    - `GET /api/academic_periods/for_date?date=YYYY-MM-DD` — period covering given date

## Frontend patterns (dashboard)
- Vite React app; proxies `/api` and `/screenshots` to API in dev (`vite.config.ts`).
- Uses Syncfusion components; Vite config pre-bundles specific packages to avoid alias issues.
- Environment: `VITE_API_URL` provided at build/run; in dev compose, proxy handles `/api` so local fetches can use relative `/api/...` paths.
 - Scheduler (appointments page): top bar includes Group and Academic Period selectors (Syncfusion DropDownList). Selecting a period calls `POST /api/academic_periods/active`, moves the calendar to today’s month/day within the period year, and refreshes a right-aligned indicator row showing:
   - Holidays present in the current view (count)
   - Period label (display_name or name) with a badge indicating whether any holidays exist in that period (overlap check)

## Local development
- Compose: development is `docker-compose.yml` + `docker-compose.override.yml`.
  - API (dev): `server/Dockerfile.dev` with debugpy on 5678, Flask app `wsgi:app` on :8000.
  - Dashboard (dev): `dashboard/Dockerfile.dev` exposes :5173 and waits for API via `dashboard/wait-for-backend.sh`.
  - Mosquitto: allows anonymous in dev; WebSocket on :9001.
- Common env vars: `DB_CONN`, `DB_USER`, `DB_PASSWORD`, `DB_HOST=db`, `DB_NAME`, `ENV`, `MQTT_USER`, `MQTT_PASSWORD`.
- Alembic: prod compose runs `alembic ... upgrade head` and `server/init_defaults.py` before gunicorn.
  - Use `server/init_academic_periods.py` to populate default Austrian school years after migration.

## Production
- `docker-compose.prod.yml` uses prebuilt images (`ghcr.io/robbstarkaustria/*`).
- Nginx serves dashboard and proxies API; TLS certs expected in `certs/` and mounted to `/etc/nginx/certs`.

## Environment variables (reference)
- DB_CONN — Preferred DB URL for services. Example: `mysql+pymysql://${DB_USER}:${DB_PASSWORD}@db/${DB_NAME}`
- DB_USER, DB_PASSWORD, DB_NAME, DB_HOST — Used to assemble DB_CONN in dev if missing; inside containers `DB_HOST=db`.
- ENV — `development` or `production`; in development, `server/database.py` loads `.env`.
- MQTT_BROKER_HOST, MQTT_BROKER_PORT — Defaults `mqtt` and `1883`; MQTT_USER/MQTT_PASSWORD optional (dev often anonymous per Mosquitto config).
- VITE_API_URL — Dashboard build-time base URL (prod); in dev the Vite proxy serves `/api` to `server:8000`.
- HEARTBEAT_GRACE_PERIOD_DEV / HEARTBEAT_GRACE_PERIOD_PROD — Groups “alive” window (defaults ~15s dev / 180s prod).
- REFRESH_SECONDS — Optional scheduler republish interval; `0` disables periodic refresh.

## Conventions & gotchas
- Always compare datetimes in UTC; some DB values may be naive—normalize before comparing (see `routes/events.py`).
- Use retained MQTT messages for state that clients must recover after reconnect (events per group, client group_id).
- In-container DB host is `db`; do not use `localhost` inside services.
- No separate dev vs prod secret conventions: use the same env var keys across environments (e.g., `DB_CONN`, `MQTT_USER`, `MQTT_PASSWORD`).
- When adding a new route:
  1) Create a Blueprint in `server/routes/...`,
  2) Register it in `server/wsgi.py`,
  3) Manage `Session()` lifecycle, and
  4) Return JSON-safe values (serialize enums and datetimes).
- When extending media types, update `MediaType` and any logic in `eventmedia` and dashboard that depends on it.
- Academic periods: Events/media can be optionally associated with periods for educational organization. Only one period should be active at a time (`is_active=True`).

## Quick examples
- Add client description persists to DB and publishes group via MQTT: see `PUT /api/clients/<uuid>/description` in `routes/clients.py`.
- Bulk group assignment emits retained messages for each client: `PUT /api/clients/group`.
- Listener heartbeat path: `infoscreen/<uuid>/heartbeat` → sets `clients.last_alive`.

Questions or unclear areas? Tell us if you need: exact devcontainer debugging steps, stricter Alembic workflow, or a seed dataset beyond `init_defaults.py`.

## Academic Periods System
- **Purpose**: Organize events and media by educational cycles (school years, semesters, trimesters).
- **Design**: Fully backward compatible - existing events/media continue to work without period assignment.
- **Usage**: New events/media can optionally reference `academic_period_id` for better organization and filtering.
- **Constraints**: Only one period can be active at a time; use `init_academic_periods.py` for Austrian school year setup.
 - **UI Integration**: The dashboard highlights the currently selected period and whether a holiday plan exists within that date range. Holiday linkage currently uses date overlap with `school_holidays`; an explicit `academic_period_id` on `school_holidays` can be added later if tighter association is required.
