# Over Zeg 't

## Wat is Zeg 't?

Zeg 't (spreek uit: "zeg het") is een Nederlandse webapp waarmee je uitspraak
oefent door woorden hardop te zeggen. De leerling ziet een woord of een
vertaling, spreekt het antwoord in de microfoon en krijgt meteen feedback op
uitspraak, nauwkeurigheid en vloeiendheid. Een ingebouwd spaced-repetition
systeem ("de ladder") zorgt dat lastige woorden vaker terugkomen en beheerste
woorden steeds minder vaak — zo oefen je precies wat je nog niet kent.

Zeg 't is een webapp: er is geen app om te installeren. Hij werkt in elke
moderne browser met een microfoon, op desktop en mobiel. De app is volledig
Nederlandstalig; alle feedback en uitleg aan de gebruiker is Nederlands.

## Welk probleem lost het op?

Uitspraak oefenen vraagt normaal om een luisteraar die direct feedback geeft.
Zeg 't doet dat automatisch: spraakherkenning beoordeelt elke opname meteen,
zonder dat er een docent naast hoeft te zitten. Leerlingen kunnen daardoor
zelfstandig en in hun eigen tempo oefenen; docenten kunnen woordenschat en
uitspraak van een hele klas volgen zonder elke opname zelf te beluisteren.

Daarnaast lost de "ladder" het klassieke leerprobleem op dat je iets wat je
net kende toch weer vergeet: gespreid herhalen plant herhalingen nét voordat
je een woord zou vergeten, met steeds grotere tussenpozen.

## Doelgroep

- **Basisonderwijs (PO)** — dicteewoorden en woordenschat hardop oefenen,
  inclusief een spellingscheck.
- **Voortgezet onderwijs (VO)** — woordenlijsten per vak of hoofdstuk; de
  docent volgt de voortgang per leerling.
- **NT2 en nieuwkomers** — Nederlandse uitspraak oefenen in eigen tempo, met
  directe feedback.
- **Vreemde talen** — uitspraak van Engelse, Franse, Duitse of Spaanse woorden
  (en meer) oefenen.
- **Thuisgebruik** — ook buiten de school, op elk apparaat.

## Rollen: leerling en docent

Er zijn twee rollen, vastgelegd in één tabel (`leerlingen.rol`):

- **Leerling (`student`)** — oefent woordenlijsten en ziet de eigen voortgang.
- **Docent (`docent`)** — maakt woordenlijsten, maakt klassen en bekijkt de
  voortgang van leerlingen.

Elke zelfregistratie (e-mail/wachtwoord of via OAuth) maakt een **leerling**-
account dat direct kan inloggen. Wie docent wil worden, vraagt dat zelf aan via
`POST /api/instellingen/docent-aanvragen`; een beheerder keurt de aanvraag goed
en zet de rol dan op `docent` (`leerling.docent_aangevraagd` → `rol="docent"`).

## Leerlinggebruik

Een leerling:

1. logt in (of gebruikt een gastaccount via een deellink),
2. opent een woordenlijst,
3. oefent — in de standaard "slimme" modus doorloopt elk woord dezelfde route:
   eerst een paar keer als flitskaart, dan een meerkeuze-vraag, daarna spreken
   (en voor door de docent als "moeilijk" gemarkeerde woorden ook een typvraag),
4. krijgt na elke spreekopname direct scores en uitleg,
5. ziet woorden door de ladder klimmen en beheerst ze uiteindelijk.

De leerling kan ook een vaste oefenvorm kiezen (uitspraak oefenen, alleen
spreken, typen, meerkeuze of flitskaarten) en er is een hands-free modus voor
spreken. In de zelfstandige Typen-modus kan de leerling ook nieuwe en
niet-moeilijke woorden typen; de typvraag en spellingscheck in de slimme route
blijven aan de moeilijk-markering gebonden. Zie `/llm/gebruik.md` voor de
complete reis.

## Docentgebruik

Een docent kan:

- **woordenlijsten maken en beheren** — handmatig, via plak-tekst of via een
  foto (foto-import, zie hieronder);
- **woorden als "moeilijk" markeren** — die woorden krijgen in de slimme route
  een extra typvraag en bovenaan de ladder een verplichte spellingscheck;
- **klassen aanmaken** en daar leerlingen aan koppelen;
- **docent-aanvragen goedkeuren of weigeren** — leerlingen die zelf docent
  willen worden, krijgen die rechten pas na goedkeuring;
- **voortgang volgen** via het dashboard: per klas, per woordenlijst en per
  leerling (aantal geoefend, correct, beheerst, gemiddelde score, laatste
  activiteit);
- **woordenlijsten delen** met leerlingen via een deellink.

## Thuisgebruik

Omdat Zeg 't een webapp is, werkt oefenen overal waar een browser en microfoon
zijn. Leerlingen oefenen thuis op dezelfde woordenlijsten als op school; de
voortgang staat per leerling in de database en is apparaat-onafhankelijk.

## Ondersteunde talen

Een woordenlijst heeft een **brontaal** en een **doeltaal**, opgeslagen als
Azure-locale (bijv. `nl-NL` → `en-US`). De app zelf legt geen vaste taallijst
op: elke taal die Azure Speech ondersteunt kan als bron- of doeltaal worden
gebruikt (TTS en het Azure-vangnet werken daarmee).

De **eigen uitspraak-engine** is beperkter: die beoordeelt alleen `en`, `fr`,
`es`, `de`, `it`, `pt` en `nl`. Voor andere talen (of bij een storing) beoordeelt
Azure, mits de leerling Azure zelf heeft aangezet (opt-in; nooit voor
docent-accounts, accounts onder de 16 en gastleerlingen). Zie
`/llm/uitspraak-engine.md`.

## Belangrijkste functies

- **Woordenlijsten** — sets met woordparen (bron → doel), met alternatieven.
- **Rollenspel** — een setsoort met een vaste catalog gespreksbeurten: de
  leerling ziet de Nederlandse zin en zegt de doeltaal hardop; de beurt van de
  tegenstander wordt voorgelezen.
- **Oefenen** — de slimme route (flitskaarten → meerkeuze → spreken/typen) of
  een vaste oefenvorm.
- **Uitspraakbeoordeling** — directe score (0-100) op uitspraak, nauwkeurigheid,
  volledigheid en vloeiendheid, plus fonetische hints in gewoon Nederlands.
- **Typen en spelling** — zelfstandige typmodus voor alle beschikbare woorden;
  de typvraag in de slimme route en de spellingscheck bovenaan de ladder zijn
  voor moeilijke woorden.
- **Meerkeuze** — herkenningsvraag met afleiders (anti-gokken-detectie).
- **Flitskaarten** — kennismaking zonder oordeel.
- **De ladder** — spaced repetition per (leerling, woord).
- **Delen** — een docent deelt een woordenlijst via een link.
- **Klassen** — groeperen van leerlingen voor het dashboard.
- **Dashboard** — voortgang per klas/lijst/leerling voor de docent.
- **Foto-import** — van één foto maakt Mistral automatisch woordparen (bij
  woordjessets) of dialoogregels met rol (bij rollenspelsets), letterlijk
  getranscribeerd.
- **TTS (voorlezen)** — woorden hardop laten voorlezen (Piper, lokaal; Azure alleen als vangnet).

## Foto-import

Een docent kan één foto uploaden (bijv. van een lesmethode). De app:

1. controleert de afbeelding op ongepaste inhoud (Azure Content Safety),
2. stuurt de volledige afbeelding naar Mistral (`pixtral-12b-2409`), die
   letterlijk transcribeert wat er staat: bij woordjessets de woordparen
   (zonder te vertalen), bij rollenspelsets de gesproken dialoogregels met rol
   (leerling/tegenstander),
3. laat lokaal (fastText) bij woordjessets de doeltaal detecteren.

Bij een rollenspel vullen de geïmporteerde regels direct de beurten in de
editor; de Nederlandse vertalingen worden daarna automatisch door Mistral
gemaakt (letterlijke batchvertaling) en zijn handmatig aanpasbaar. De
gevonden woordparen en beurten kunnen door de docent worden gecontroleerd en
aangepast. Limiet: 20 verzoeken per uur per gebruiker; afbeeldingen maximaal
8 MB.

## TTS (voorlezen)

Elk doelwoord kan worden voorgelezen (Piper TTS, lokaal), zodat de leerling de
goede uitspraak hoort. Voor talen zonder lokale stem valt het voorlezen terug
op Azure Speech TTS. Wordt gebruikt als hulpmiddel tijdens het oefenen en
automatisch afgespeeld na een fout antwoord.

## Accounts, aanmelden en inloggen

- **Registratie** — met e-mail en wachtwoord (optionele e-mailverificatie via
  een 24-uurs link; inloggen kan ook zonder) of via **Google** of **GitHub**
  (OAuth). Google/GitHub-adressen zijn al door de provider geverifieerd; er
  volgt dan geen aparte verificatiemail.
- **Direct inloggen** — na zelfregistratie ben je meteen ingelogd
  (`goedgekeurd` staat standaard aan). Het veld `goedgekeurd` wordt alleen door
  een beheerder uitgezet om een account te blokkeren.
- **Docent worden** — via `POST /api/instellingen/docent-aanvragen`; een
  beheerder keurt de aanvraag goed.
- **Wachtwoord vergeten** — resetlink (1 uur geldig) naar het e-mailadres.
- **E-mail wijzigen** — via een bevestigingslink (24 uur geldig).
- **Account verwijderen** — twee stappen (aanvragen via link, daarna
  bevestigen); voortgang en eigen lijsten gaan dan verloren, maar gedeelde
  lijsten blijven voor anderen bestaan (de maker wordt losgekoppeld).
- **Tweestapsverificatie** — instelbaar via een authenticator-app (TOTP), een
  e-mailcode of een passkey (WebAuthn). Het TOTP-geheim wordt versleuteld
  bewaard en backup-codes alleen gehasht. Het beheerdersaccount moet 2FA
  hebben; gevoelige beheeracties (accountdetail bekijken, wijzigen,
  verwijderen) vragen een verse tweede-factorbevestiging.

Zie ook `/llm/api.md` voor de technische details (sessie, CSRF, rate limits).

## Gastaccounts

Een leerling zonder account kan een gedeelde woordenlijst gebruiken via een
deellink. Bij het claimen wordt dan automatisch een **gastaccount** aangemaakt
(alleen een weergavenaam, `anonieme_id = gast_<hex>`, rol `student`). Zo kan
een klas dezelfde lijst gebruiken zonder dat elke leerling zich hoeft te
registreren.

## Delen (de flow)

```text
docent
  ↓
maakt een deellink voor een woordenlijst  (POST /api/woordenlijsten/{id}/deellink,
                                           optioneel met een vaste oefenmodus)
  ↓
deelt de link met leerlingen              (https://…/delen/{token})
  ↓
leerling opent de link
  ↓
is er een bestaande sessie?
  ├─ ja → koppelen aan bestaand account (LijstDeelname)
  └─ nee → naam invullen → gastaccount → LijstDeelname
  ↓
de leerling kan de lijst nu oefenen
```

Details die de code vastlegt:

- Een deellink is **onbeperkt herbruikbaar** en blijft actief totdat de docent
  hem deactiveert. Elke claim verhoogt `aantal_gebruikt`.
- Een docent kan per set meerdere links beheren, elk met een eigen vaste
  oefenmodus (`uitspraak`, `spreken`, `meerkeuze`, `typen` of `flitskaarten`;
  `slim` of leeg = de standaard route). Zo'n link opent direct in die modus.
- Het claimen maakt een `LijstDeelname` (uniek per lijst + leerling); bij een
  tweede bezoek wordt geen dubbele deelname aangemaakt.
- De docent kan een link op elk moment deactiveren
  (`DELETE /api/woordenlijsten/{id}/deellink/{link_id}`), waarna de link 404
  geeft; `DELETE /api/woordenlijsten/{id}/deellink` zet alle links in één keer
  uit.

Zie ook `/llm/woordenlijsten.md`.

## Hoe data door het systeem stroomt (privacy)

```text
microfoon
  ↓
browser (MediaRecorder → PCM/WAV)
  ↓
backend (FastAPI)
  ↓
  OpenPronounce (eigen engine, primair)  ── (alleen bij storing/timeout/taal én na opt-in) ──→  Azure Speech
  ↓
feedback (scores, hints)
  ↓
voortgang / ladder (database)
```

Feiten die uit de code en het privacybeleid blijken:

- Audio wordt tijdens het oefenen **alleen tijdelijk verwerkt** om te beoordelen
  en wordt niet blijvend door Zeg 't opgeslagen. Alleen scores en voortgang
  worden bewaard.
- Uitzondering is de losstaande publieke **spraaktest `/praat`**: alléén na
  expliciet akkoord ("Ik ga akkoord") én alléén als het model het doelwoord
  overtuigend herkende (oordeel "goed", score >= 85, `nauwkeurigheid_incl`
  >= 80) wordt de opname als trainingsvoorbeeld voor de
  uitspraakherkenning bewaard — met alleen een id (geen IP, account of naam).
  Twijfelgevallen worden niet bewaard. Zie `/llm/api.md`.
- Audio gaat voor de **uitspraakbeoordeling** alleen naar Azure Speech als de
  leerling dat zelf heeft aangezet: als **vangnet** (`fallback`) of bij de
  stille **kwaliteitssteekproef** (`steekproef`, ~1 op de 10). Dit is per
  leerling instelbaar via `leerling.azure_modus` in Instellingen
  (`/api/instellingen/azure-modus`). **Nieuwe accounts** beginnen op `niets`
  (Azure wordt nooit gebruikt); bestaande voorkeuren blijven staan en Azure is
  pas beschikbaar na e-mailverificatie.
- Azure staat **altijd uit** voor **docent-accounts**, **accounts onder de 16**
  (geboortejaar bekend en jonger dan 16) en **gastleerlingen** (via een deellink,
  zonder account). De server dwingt dit ook bij het spreken af.
- **Eén uitzondering:** het beheerdersaccount (`ADMIN_EMAIL`) mag in de
  uitspraakmodus Azure als **tweede mening** gebruiken wanneer de eigen engine
  afkeurt — en alleen als dat account Azure zelf heeft aangezet
  (`azure_modus` ≠ `niets`). Dat dient alleen om de engine te controleren; voor
  leerlingen verandert er niets en er gaat voor hen geen extra audio naar Azure.
- De **voorleesstem (TTS)** staat hier los van: die draait primair lokaal
  (Piper) en valt alleen bij talen zonder lokale stem terug op Azure TTS; dat is
  niet per leerling instelbaar en niet aan rol of leeftijd gebonden.
- Voor foto-import, de automatische Nederlandse vertalingen in de
  rollenspel-maker en het sorteren van woordparen wordt de inhoud van
  afbeeldingen/woordenlijsten naar **Mistral AI** gestuurd, met **Zero Data
  Retention** aan en met de trainingsuitsluiting aan (de gegevens worden niet
  voor modeltraining gebruikt). In de **gespreksmodus** (testfase) gaat ook de
  gesprekstekst naar Mistral: de docent-chat die de systeemprompt ontwerpt, de
  antwoorden van de AI en de hints, inclusief de **transcripties van de
  leerling** (de spraak wordt eerst door de eigen engine omgezet naar tekst).
  Taaldetectie gebeurt lokaal (fastText).
- Afbeeldingen (thumbnails, gescande pagina's) worden door **Azure Content
  Safety** op ongepaste inhoud gecontroleerd.
- Opgeslagen accountdata: naam/weergavenaam, e-mailadres, versleuteld
  wachtwoord (scrypt, nooit leesbaar), woordenlijsten, voortgang en oefenscores.
- Een account kan via Instellingen worden verwijderd (twee bevestigingsstappen);
  eigen voortgang en lijsten gaan dan verloren, gedeelde lijsten blijven voor
  anderen bestaan.

Zie het publieke privacybeleid op `/privacy` voor de volledige tekst.

## Beperkingen en uitzonderingen

Zie `/llm/faq.md` en `/llm/uitspraak-engine.md`. Kort:

- De eigen uitspraak-engine ondersteunt alleen `en/fr/es/de/it/pt/nl`; andere
  talen vallen terug op Azure (alleen als de leerling Azure heeft aangezet).
- Bij een storing of timeout van de eigen engine beoordeelt Azure (zie
  engine-doc), mits de leerling Azure heeft aangezet en nooit voor
  docent-accounts, accounts onder de 16 of gasten. De leerling ziet dan een
  banner dat de reserve-engine is gebruikt.
- Meerkeuze vereist voldoende woorden: met minder dan 2 antwoordopties valt
  een meerkeuze-vraag terug op spreken.
- Foto-import werkt het best met een goed leesbare foto: bij woordjes meestal
  twee kolommen (bron links, doel rechts), bij een rollenspel een duidelijke
  dialoog; onduidelijk leesbare tekst valt weg.
- Oefenen vereist een browser met microfoontoegang.
