# De leerlingreis (gebruik)

Dit beschrijft stap voor stap wat er gebeurt wanneer een leerling met Zeg 't
oefent. "Automatisch" betekent: het gebeurt door de app, zonder dat de leerling
er iets voor hoeft te doen.

De oefenflow zit in `routers/oefenen.py` (server) en `static/js/student.js`
(browser). De ladderlogica zit in `srs.py`.

## 1. Een lijst openen

1. De leerling kiest een woordenlijst (eigen lijst, een gedeelde lijst of een
   openbare set onder "Ontdekken"). Een klik op een setkaart of thumbnail gaat
   naar de setdetailpagina (`/sets/{slug}`) met thumbnail, acties en — voor
   eigenaren/deelnemers — de woordenlijst. Oefenen start via één knop
   "Oefenen", die een modus-blad (bottom sheet) opent met Slim (aanbevolen),
   Uitspraak, Flitskaarten, Spreken, Typen en Meerkeuze — plus Gesprek als de
   gespreksmodus aan staat én de set een gespreksinstructie heeft
   (`heeft_draaiboek` via `GET /api/gesprek/status?set=`). Dezelfde knop op een
   setkaart in "Mijn sets" opent
   hetzelfde modus-blad. Een directe `/oefenen/{slug}`-URL werkt ook. Bij een
   rollenspel heet de knop "Start rollenspel" en start hij direct de
   rollenspelweergave. Wordt de woordenlijst niet getoond, dan vult de thumbnail
   de hele kaartbreedte.
2. De browser vraagt `GET /api/oefenen/volgende` (met de lijst-id).
3. De server sluit eerst eventuele verlopen oefensessies af (zie stap 12),
   verzamelt alle woorden van de lijst en bepaalt welke woorden "aan de beurt"
   zijn.
4. De server kiest één woord en geeft terug: bronwoord, doelwoord, de oefenmodus,
   eventueel meerkeuze-opties, en voortgang (trede, beheerst, nog te herhalen).

Bij het openen van het oefenscherm start de browser de microfooninitialisatie,
de statuscontrole van de uitspraakserver en het ophalen van het eerste woord
tegelijk. De geschiedenis wordt daarna apart opgehaald.

## 2. Beginnen met oefenen

Standaard staat de **"slimme" modus** aan. Alle woorden doorlopen samen dezelfde
route, in willekeurige volgorde binnen elke stap:

```text
flitskaarten (3× per woord)  →  meerkeuze  →  spreken (of typen voor moeilijke woorden)
```

De leerling kan ook een vaste oefenvorm kiezen: alleen uitspraak oefenen, alleen
spreken, alleen typen, alleen meerkeuze of alleen flitskaarten. Die keuze gebeurt
uitsluitend via het modus-blad bij het starten van het oefenen — tijdens het
oefenen staan er geen modus-pills meer in de oefen-toolbar (alleen hands-free en
de "?"-knop); wisselen kan nog wel via de knop "Oefenen met meerkeuze of
spreken" in het flitskaart-klaar-scherm, die terugzet op de slimme modus. Een
deellink met vaste oefenmodus opent nog steeds direct in die modus. In Typen
typt de leerling de vertaling van elk beschikbaar woord; dit gebruikt dezelfde
invoer als een typvraag in de slimme modus. Er is ook een hands-free modus
(spreken zonder op een knop te drukken).

In **Uitspraak oefenen** ziet en hoort de leerling alleen het doelwoord: de
bron/vertaling blijft verborgen, het voorbeeld wordt automatisch voorgelezen en
een tik op de kaart speelt het opnieuw voor. De beoordeling is streng — alleen
een voldoende goede uitspraak (oordeel "goed" of "bijna", geen sterke aarzeling,
score ≥ 75) of een exact herkend woord telt mee op de ladder; anders blijft het
woord op zijn plek en toont de feedback "Opnieuw proberen" tot het wel goed
genoeg is. De drempel staat op 75 (eerder 85, en alleen oordeel "goed"): de
foneemherkenning is op korte, vooral Franse woorden zo streng dat een correcte
uitspraak de 85 zelden haalde.

## 3. Een nieuw woord krijgen

Een woord zonder eerdere voortgang (`SrsVoortgang` ontbreekt) is "nieuw" en
staat op trede 0. In de slimme modus begint het als flitskaart. Het bronwoord
wordt getoond; het doelwoord moet de leerling zelf bedenken.

## 4. Een flitskaart krijgen

1. De leerling ziet het bronwoord en denkt na over de vertaling.
2. De leerling draait de kaart om en ziet het doelwoord — door op de kaart of
   de knop te klikken, of met Spatie/Enter. Is de kaart al omgedraaid, dan
   gaat Spatie/Enter naar de volgende kaart.
3. **Automatisch:** de browser meldt de flitskaart via
   `POST /api/oefenen/flitskaarten/{id}`. Er wordt alleen geregistreerd dat de
   kaart is voorbijgekomen — er is geen oordeel en de ladder beweegt niet.
4. Elk nieuw woord komt zo **3×** als flitskaart voorbij (verspreid door de
   sessie), daarna stroomt het door naar de meerkeuze-vraag.

## 5. Een meerkeuzevraag krijgen

1. De leerling ziet het bronwoord en moet het juiste doelwoord kiezen uit
   meestal 4 opties (het goede antwoord plus maximaal 3 afleiders uit dezelfde
   of andere lijsten). Elke optie toont zijn toets (1–4): kiezen kan met de
   muis of met de toetsen 1–4 / A–D.
2. De browser meet hoe lang de leerling over de keuze doet (`reactietijd_ms`).
3. **Automatisch:** de server beoordeelt binair (goed = 100, fout = 0). Blijkt
   uit de reactietijden dat er gegokt wordt (meerdere antwoorden sneller dan
   1 seconde in een venster van 5), dan telt het antwoord als fout.
4. De ladder wordt bijgewerkt (zie `/llm/herhaalsysteem.md`).

## 6. Een woord spreken

1. De leerling drukt op opnemen (of houdt de spatiebalk ingedrukt als
   push-to-talk) of spreekt in hands-free modus, en zegt het doelwoord hardop.
2. De browser neemt de microfoon op (MediaRecorder → PCM/WAV, 16 kHz mono).
3. De opname gaat als `POST /api/oefenen/spreken/{id}` naar de server.
4. **Automatisch:** de server beoordeelt de opname — primair met de eigen
   engine, met Azure als vangnet. Azure wordt alleen gebruikt als de leerling
   dat zelf in Instellingen heeft aangezet (standaard staat dit op `niets`; pas
   na e-mailverificatie, en nooit voor docenten, accounts onder de 16 of
   gasten). Zie `/llm/uitspraak-engine.md`.

## 7. Feedback krijgen

Direct na de opname ziet de leerling:

- of het goed of fout was,
- de scores (uitspraak, nauwkeurigheid, vloeiendheid, elk 0-100),
- wat er "gehoord" is (alleen als de herkenning betrouwbaar lijkt),
- fonetische hints in gewoon Nederlands (bijv. "er klonk de 's' in plaats van
  de 'z'") — alleen van de eigen engine,
- wat er op de ladder gebeurt ("nog 2 keer deze sessie", "een trede omhoog",
  "beheerst", …),
- bij een fout wordt het doelwoord automatisch voorgelezen (TTS).

De browser hergebruikt een opgehaalde, genormaliseerde TTS-audio zolang hetzelfde
woord actief is en deelt een gelijktijdig TTS-verzoek; bij het laden van het
volgende woord wordt deze tijdelijke audio-cache vrijgegeven.

## 8. Een woord fout hebben

- De uitspraakscore is te laag, of er is een verkeerd/onherkenbaar woord gezegd.
- **Automatisch:** het woord **blijft op zijn trede** en komt in deze sessie nog
  **2×** terug. Er is nooit een reset naar beneden.
- De leerling krijgt een vriendelijke uitleg (bijv. "Nog niet goed genoeg …
  blijft op zijn trede en komt in deze sessie nog 2 keer terug").
- Zegt de leerling maar een deel van het woord, dan krijgt die specifiek te
  horen wát er ontbrak ("Ik hoorde het begin, maar miste het laatste woordje").

## 9. Een woord goed hebben

- De uitspraak is voldoende en het juiste woord is herkend.
- **Automatisch:** dit telt als een goede poging. Bij de eerste goede poging
  van een ronde bepaalt de score hoe vaak het woord nog terugkomt: **1×** bij
  score > 80, **2×** bij score 55–80. Daarna telt elke goede herhaling er één af.

## 10. Een woord meerdere keren goed hebben

- Elke goede herhaling verlaagt `herhalingen_nog` met 1.
- **Automatisch:** als alle benodigde herhalingen goed zijn, gaat het woord een
  trede omhoog en krijgt het een langere interval (zie stap 11).

## 11. Een woord naar een volgende trede gaan

- Zodra de benodigde herhalingen binnen de sessie goed zijn, stijgt het woord
  één trede.
- De interval van de nieuwe trede geldt pas vanaf het einde van de sessie
  (batch-principe): alle woorden van één oefenronde komen samen terug, niet elk
  op zijn eigen voltooiingstijd.
- **Automatisch:** de volgende herhaling wordt gepland op
  `einde van de sessie + interval van de nieuwe trede`.

## 12. Later terugkomen

- Trede 1 = morgen, trede 2 = over 3 dagen, trede 3 = een week, trede 4 =
  twee weken, trede 5 = na 30 dagen (de spellingscheck voor moeilijke woorden).
- **Automatisch:** een woord is "aan de beurt" zodra de datum-interval is
  verstreken. De selectie (`_kies_willekeurig_woord`) geeft voorrang aan:
  1. openstaande herhalingen in deze sessie,
  2. woorden die volgens het dagschema aan de beurt zijn,
  3. nieuwe woorden,
  4. woorden die nét (< 90 seconden geleden) zijn geoefend (pas als er niets
     anders is).
- Is er helemaal niets aan de beurt, dan toont de app een pauzescherm met de
  wachttijd tot het eerstvolgende woord ("Toch doorgaan" negeert het schema één
  ronde; de server ondersteunt daarnaast `?toch=true` op `GET /volgende`, dat de
  wachttijd omzeilt en altijd een woord oplevert in plaats van het
  pauzescherm).

## 13. Typen en moeilijke woorden

De zelfstandige modus **Typen** is een keuze naast Slim, Spreken, Meerkeuze en
Flitskaarten. De leerling kan daarmee ook nieuwe en niet als moeilijk
gemarkeerde woorden typen. De browser vraagt het volgende woord op met
`modus=typen` en verstuurt het antwoord naar `POST /api/oefenen/spellen/{id}`
met `praktijk: "typen"`. Een gewone typbeurt wordt volgens de normale
SRS-ladderregels verwerkt: goed telt als een poging vooruit, fout laat het woord
op zijn trede en plant twee herhalingen in dezelfde sessie. Is een woord
beheerst of op de hoogste trede zonder open herhalingen, dan gebruikt de
server ook bij Typen de aparte spellingscheckafhandeling.

De typvraag en spellingscheck in de **slimme route** gelden alleen voor woorden
die de docent als "moeilijk" heeft gemarkeerd:

- In de **slimme route** krijgt zo'n woord na de flitskaarten en meerkeuze in de
  spreekfase een **typvraag** (net als een spreekvraag een ladderpoging). De
  losse typvraag in die route blijft dus beperkt tot moeilijke woorden en wordt
  niet door `praktijk: "typen"` vrijgeschakeld.
- **Bovenaan de ladder** (trede 5, of bij een beheerst woord tijdens de
  maandelijkse controle) volgt in de slimme route de **spellingscheck**: de
  leerling typt het woord.
  - Goed gespeld → het woord is **beheerst** en komt over 30 dagen terug als
    controle.
  - Fout gespeld → het woord blijft beheerst (of op zijn trede) en komt in
    deze sessie nog **2×** terug.

## Wat gebeurt er automatisch (samenvatting)

- Sessiebeheer: een pauze langer dan 45 minuten sluit de sessie; pas dan worden
  de nieuwe intervallen van de gestegen woorden ingepland.
- Ladder-updates na elke spreken-/meerkeuze-/typ-poging.
- Anti-gokken-detectie bij meerkeuze.
- Uitspraakbeoordeling en score-berekening.
- Voorlezen (TTS) na een fout.
- Selectie van het volgende woord (prioriteitsgroepen + willekeur).

## Rollenspel (nieuw setsoort)

Een rollenspel is een set met een vaste catalog gespreksbeurten in plaats van
losse woordparen. De leerling oefent het op `/rollenspel/{slug}`
(`static/js/rollenspel.js`, view `#view-rollenspel`); op het setdetail heet de
startknop "Start rollenspel".

1. De leerling ziet per beurt de Nederlandse zin. Bij een **eigen beurt**
   (`rol = "leerling"`) blijft de doeltaal verborgen: de leerling houdt de
   microfoonknop ingedrukt en zegt de doeltaal hardop. De opname gaat naar
   `POST /api/rollenspel/sets/{slug}/beurt/{beurt_id}/spraak`, wordt door de
   lokale engine tegen de verborgen doeltaalzin beoordeeld en levert een score,
   goed/bijna en een korte tip (`goed` = score ≥ 60).
2. Bij een **tegenstander-beurt** (`rol = "tegenstander"`) wordt de doeltaalzin
   voorgelezen (TTS); daarna gaat het spel automatisch verder (of via
   "Volgende").
3. Aan het eind ziet de leerling hoeveel beurten goed gingen en het gemiddelde.

De rollenspelweergave is geen chat en raakt de SRS-ladder niet: elke beurt wordt
los beoordeeld en er worden geen oefenpogingen of voortgang vastgelegd.

## Gespreksmodus "Gesprek" (testfase)

Dit is nog geen openbare functie: de beheerder zet hem aan, waarna **elke
ingelogde leerling** een uitgeschreven gesprek kan voeren. Het *ontwerpen* is
beheerder-only. De code zit in `routers/gesprek.py`, `static/js/gesprek.js` en
de Mistral-functies `gesprek_ontwerp`, `gesprek_antwoord`, `gesprek_hint` en
`screenshot_naar_tekst` in `mistral_client.py`.

1. De beheerder zet de schakelaar aan in de beheer-UI ("Gespreksmodus
   (testfase)", `GET/POST /api/admin/gesprek`). Dit is een rij in de database
   (`app_instellingen.gesprek_aan`), niet een `.env`-instelling; standaard staat
   hij uit.
2. In het oefenmodus-blad op het setdetail verschijnt nu de optie "Gesprek"
   (voor ingelogde gebruikers). De **systeemprompt** ontwerp je in het
   **bewerkscherm** van de set (kaart "Gesprek ontwerpen"): je chat in het
   Nederlands met de AI, die meedenkt en de prompt stap voor stap bijstelt. Je
   kunt ook een screenshot van
   het lesmateriaal sturen (Pixtral haalt de tekst eruit). Je rondt pas af als
   je tevreden bent, of klikt op "Stel definitieve prompt voor". Bij afronden
   worden de prompt én alle setwoorden (incl. alternatieven) als oefenwoorden
   opgeslagen; die oefenwoorden worden tijdens het gesprek automatisch achter de
   prompt gezet.
3. Daarna start het gesprek: de AI zegt een openingszin (via TTS) in de
   doeltaal en de leerling antwoordt **handsfree met de microfoon** of door te
   typen. De eigen engine levert een open transcriptie
   (`POST /api/gesprek/sessie/{id}/spraak`) en de AI reageert natuurlijk in de
   doeltaal; de volledige chat blijft zichtbaar. De leerling kan een hint
   opvragen en het gesprek zelf beëindigen ("Gesprek beëindigen"). Alle
   setwoorden doen mee; de server houdt per beurt bij welke woorden erin
   voorkwamen.
4. Aan het eind ziet de leerling welke setwoorden er in het gesprek zijn
   gebruikt en welke nog te oefenen zijn.

Er zijn geen geautomatiseerde tests; dit is een testfase.
