Planlagte sendinger
«Send denne kl. 18:00.» Engangs, ikke gjentakende. Dette er den første delen av appen som må ligge på disk. Kø og historikk har alltid bare levd i minnet, og det har gått bra fordi en melding spilles med en gang. En melding planlagt til 18:00 må overleve at containeren startes om 17:45, så schedule.py lagrer i data/scheduled.json – skrevet til side og flyttet på plass, så fila aldri er halvskrevet. Ingen database, med vilje: dette er en håndfull meldinger på et arrangement, og en fil man kan lese med cat er verdt mer her enn indekser. En ødelagt fil hindrer ikke oppstart; da er det bedre å miste planene og si det i loggen. Modulen vet ingenting om Flask eller avspilling. Den lagrer, og den svarer på «hva skal sendes nå?». _scheduler() i announcer.py eier tråden som spør hvert 20. sekund og legger forfalte meldinger i den vanlige køen – derfra oppfører de seg nøyaktig som en melding sendt for hånd. Gikk den glipp av, spilles den ikke. due() skiller på grace_seconds (120): innenfor den sendes meldingen, utenfor markeres den missed og vises som det. Nådefristen er slingringsmonn for at tråden våkner med mellomrom og for en kjapp omstart, ikke en policy om å ta igjen etterslep – «turneringen starter om ti minutter» skal ikke ut på anlegget en time etter at maskina kom tilbake. due() endrer ingenting selv; markeringen skjer først etter at meldingen faktisk er i kø, så ingenting kan stå som «sendt» uten å ha blitt det. Klienten sender absolutt tidspunkt (epoch), så tidssoner spiller ingen rolle for selve planleggingen. TZ settes likevel fra .env, ellers står loggen i UTC mens man selv står i CEST. Planlegging er låst til samme forhåndslytt-nøkkel som «Send ut»: man planlegger bare det man har hørt. Det gir en bonus – forhåndslytt varmer cache/, så WAV-en ligger ferdig på disk, og en planlagt edge-tts-melding spiller kl. 18:00 selv om nettet er nede da. Sine egne kan man alltid avbryte. Andres krever clear, samme rett som å tømme køen, siden det er samme slags inngrep i noe andre har satt i gang. 23 nye tester. Verifisert i container: sending fyrer og spiller, overlever en omstart av containeren, og en melding som forfalt mens maskina var av blir markert missed uten å bli spilt. Merk ved oppgradering: schedule er en ny handling, og et oppsett med eksplisitt [roles] i config.toml får den ikke automatisk. Den må legges inn selv. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6c1253f54c
commit
04b3712ef5
15 changed files with 746 additions and 15 deletions
49
AGENTS.md
49
AGENTS.md
|
|
@ -18,10 +18,11 @@ Filer:
|
|||
| `announcer.py` | server, kø, TTS, jingler, ruter |
|
||||
| `auth.py` | `Principal`, leverandørene, OIDC-rutene |
|
||||
| `config.py` | `config.toml` + miljøvariabler, standardverdier |
|
||||
| `schedule.py` | planlagte sendinger, lagret i `data/scheduled.json` |
|
||||
| `index.html` | hele sida, inline CSS/JS, ingen byggesteg |
|
||||
| `config.example.toml` | kommentert mal; kopier til `config.toml` |
|
||||
| `docs/crew-integration.md` | kontrakten mot crew-panelet |
|
||||
| `tests/test_auth.py` | `uv run pytest` |
|
||||
| `tests/test_auth.py` + `test_schedule.py` | `uv run pytest` – 80 tester |
|
||||
| `Dockerfile` + `compose.yaml` | **drift** – appen kjøres i Docker |
|
||||
| `.env.example` | bruker-ID, portbinding og hemmeligheter |
|
||||
| `compose.test.yaml` | testoppsett med dummy-IdP, se under |
|
||||
|
|
@ -66,6 +67,7 @@ jingler i `data/chimes/` uten å gå inn i containeren):
|
|||
| `data/chimes/` | egne jinglefiler |
|
||||
| `data/logs/` | revisjonsloggen |
|
||||
| `data/instance/` | sesjonsnøkkelen |
|
||||
| `data/scheduled.json` | planlagte sendinger. Lesbar med `cat` |
|
||||
| `data/config.toml` | valgfri. Uten den er sida åpen |
|
||||
|
||||
### Lyden ut av containeren
|
||||
|
|
@ -201,6 +203,46 @@ veien for å prøve en endring kjapt; `uv run pytest` for testene. Men
|
|||
det er ryddet bort. Skal noe prøves med innlogging, bruk
|
||||
`compose.test.yaml`.
|
||||
|
||||
## Planlagte sendinger
|
||||
|
||||
`schedule.py` er den første delen av appen som **må** ligge på disk. Kø og
|
||||
historikk har alltid bare levd i minnet, og det går bra fordi en melding
|
||||
spilles med en gang. En melding planlagt til 18:00 må overleve at containeren
|
||||
startes om 17:45, så den lagres i `data/scheduled.json`, skrevet til side og
|
||||
flyttet på plass så fila aldri er halvskrevet.
|
||||
|
||||
Ingen database, med vilje: dette er en håndfull meldinger på et arrangement,
|
||||
og en fil man kan lese med `cat` er verdt mer her enn indekser.
|
||||
|
||||
Modulen vet ingenting om Flask eller avspilling. Den lagrer, og den svarer på
|
||||
«hva skal sendes nå?». `_scheduler()` i `announcer.py` eier tråden som spør
|
||||
hvert 20. sekund og legger forfalte meldinger i den vanlige køen – derfra
|
||||
oppfører de seg nøyaktig som en melding sendt for hånd.
|
||||
|
||||
**Bare engangs**, ikke gjentakende. Derfor sender klienten et absolutt
|
||||
tidspunkt (epoch), og hele tidssoneproblemet forsvinner: containeren kan stå
|
||||
i UTC uten at det gjør noe. `TZ` settes likevel fra `.env`, ellers står
|
||||
loggen i UTC mens du står i CEST. Skal gjentakelse inn senere, må «09:00»
|
||||
tolkes i en tidssone, og da begynner `TZ` å bety noe.
|
||||
|
||||
**Gikk den glipp av, spilles den ikke.** `due()` skiller på `grace_seconds`
|
||||
(standard 120): innenfor den sendes meldingen, utenfor markeres den `missed`.
|
||||
Nådefristen er slingringsmonn for at tråden våkner med mellomrom og for en
|
||||
kjapp omstart – ikke en policy om å ta igjen etterslep. «Turneringen starter
|
||||
om ti minutter» skal ikke ut på anlegget en time etter at maskina kom
|
||||
tilbake. `due()` endrer ingenting selv; `announcer.py` markerer først etter at
|
||||
meldingen faktisk er lagt i kø, så ingenting kan stå som «sendt» uten å ha
|
||||
blitt det.
|
||||
|
||||
Forhåndslytten gir en bonus: planlegging er låst til den samme nøkkelen som
|
||||
«Send ut», så man planlegger bare det man har hørt – og siden forhåndslytt
|
||||
varmer `cache/`, ligger WAV-en ferdig på disk. **En planlagt edge-tts-melding
|
||||
spiller altså kl. 18:00 selv om nettet er nede da.**
|
||||
|
||||
Sine egne planlagte sendinger kan man alltid avbryte. Andres krever `clear` –
|
||||
samme rett som å tømme køen, siden det er samme slags inngrep i noe andre har
|
||||
satt i gang.
|
||||
|
||||
## Hvordan det henger sammen
|
||||
|
||||
- `/api/config` er alt sida trenger for å tegne seg selv: branding, stemmer,
|
||||
|
|
@ -262,6 +304,11 @@ Vil Kent ha enda bedre lyd: ElevenLabs (nøkkel, best norsk) eller OpenAI
|
|||
- `player = Player()` står på modulnivå, så avspillingstråden starter ved
|
||||
`import announcer` – også i `install.sh`, som importerer modulen for å laste
|
||||
ned stemmer. Harmløst, men greit å vite.
|
||||
- **Oppgradering:** `schedule` er en ny handling. Har et oppsett et eksplisitt
|
||||
`[roles]` i `config.toml`, får `crew` den *ikke* automatisk – eksplisitt
|
||||
konfigurasjon overstyrer standardverdiene, og da blir svaret 403 «Rollen din
|
||||
har ikke lov til dette». Legg `"schedule"` inn selv. Dette traff
|
||||
`docker/config.test.toml` under testing.
|
||||
- `tests/conftest.py` nuller ut alle `ANNOUNCER_*`-variabler før hver test.
|
||||
Uten den arver testene maskinens miljø – systemd-enheten setter
|
||||
`ANNOUNCER_PORT`, og compose setter JWT- og OIDC-hemmeligheter, noe som
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue