Skip to main content

7. Oefenruns, delen en voortgang

Deze sectie beschrijft de opslag van unieke oefenruns, ontvangen gedeelde oefeningen, server-side voortgang per vraag en uniforme statistieken voor geschiedenis, detailweergave en meekijkfunctionaliteit.

7.1 ExerciseRuns

TabelnaamCategorieDoel / verantwoordelijkheidGerelateerde tabellen
ExerciseRunsOefenresultatenHoofdtabel voor een unieke oefenrun van een gebruiker, inclusief uniforme metadata, totalen, statistieken en de module-specifieke JSON/base64-payload.Users, TeacherLevels, Categories, Exercises, ExerciseModules, ExerciseRunProgress, SharedExercises
VeldnaamTypeDefaultPKFKVerwijst naarUniqueNullableIndexOpmerking
Iduniqueidentifier-JN-JNJPrimaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default.
UserIduniqueidentifier-NNUsers.IdNNJSoft link naar identity.Users.Id; gebruiker die de oefening uitvoert of heeft uitgevoerd. Geen harde database-FK vanwege modulegrens.
LevelIduniqueidentifier-NNTeacherLevels.IdNNJSoft link naar catalog.TeacherLevels.Id; actief niveau op het moment van genereren. Geen harde database-FK vanwege modulegrens.
CategoryIduniqueidentifier-NNCategories.IdNNJSoft link naar catalog.Categories.Id; centrale categoriecontext van de run. Geen harde database-FK vanwege modulegrens.
ExerciseIduniqueidentifier-NNExercises.IdNNJSoft link naar catalog.Exercises.Id; oefening waarop de run is gebaseerd. Geen harde database-FK vanwege modulegrens.
ExerciseModuleIduniqueidentifier-NNExerciseModules.IdNNJSoft link naar catalog.ExerciseModules.Id; technische moduleversie die de vraag- en antwoordstructuur bepaalt. Geen harde database-FK vanwege modulegrens.
RequestedQuestionCountint0NN-NNNAantal opgaven dat bij genereren is aangevraagd.
QuestionDataJsonBase64nvarchar(max)-NN-NNNVolledige module-specifieke payload met vraag-, antwoord- en voortgangsdata, opgeslagen als JSON in base64-vorm.
TotalQuestionsint0NN-NNNUniform totaal aantal vragen in deze run.
CorrectCountint0NN-NNNTotaal aantal goed beantwoorde vragen, berekend en daarna direct uitleesbaar.
IncorrectCountint0NN-NNNTotaal aantal fout beantwoorde vragen.
DunnoCountint0NN-NNNTotaal aantal vragen dat als “Geen idee” is gemarkeerd.
CompletedQuestionCountint0NN-NNNAantal vragen waarvan de voortgang server-side is afgerond.
AverageTimeSecondsdecimal(10,2)0NN-NNNGemiddelde tijd per vraag op basis van uniforme timingwaarden.
MedianTimeSecondsdecimal(10,2)0NN-NNNMediaan van de beantwoordingstijd per vraag.
LowerBoundSecondsdecimal(10,2)0NN-NNNOndergrens van de gebruikte statistische bandbreedte.
UpperBoundSecondsdecimal(10,2)0NN-NNNBovengrens van de gebruikte statistische bandbreedte.
LowerOutlierCountint0NN-NNNAantal waarden onder de ondergrens.
UpperOutlierCountint0NN-NNNAantal waarden boven de bovengrens.
CreatedAtUtcdatetime2sysutcdatetime()NN-NNJMoment waarop de run is aangemaakt.
StartedAtUtcdatetime2nullNN-NJNMoment waarop de eerste vraag voor het eerst is getoond.
CompletedAtUtcdatetime2nullNN-NJJMoment waarop alle vragen zijn afgerond en statistieken definitief zijn berekend.
LastActivityAtUtcdatetime2nullNN-NJJLaatste server-side update binnen de run, relevant voor live meekijken en herstel na onderbreking.
IsCompletedbit0NN-NNJGeeft aan of de run formeel is afgerond en in geschiedenis/resultaten zichtbaar mag zijn.
IsTestRunbit0NN-NNJMarkeert docent-testoefeningen die niet permanent in geschiedenis/resultaten terechtkomen.
DuplicateOfExerciseRunIduniqueidentifiernullNJExerciseRuns.IdNJJVerwijzing naar de bronrun wanneer deze run een duplicaat is met dezelfde vragen in andere volgorde.
SharedExerciseIduniqueidentifiernullNJSharedExercises.IdNJJVerwijzing naar het administratieve shared-record wanneer deze run is ontstaan doordat een ontvanger een gedeelde oefening daadwerkelijk heeft gestart.

Validaties / constraints

  • UserId, LevelId, CategoryId, ExerciseId en ExerciseModuleId zijn verplicht.
  • IsCompleted = 1 vereist een CompletedAtUtc-waarde en definitief berekende totalen.
  • RequestedQuestionCount moet binnen de door de oefening bepaalde minimum- en maximumgrenzen vallen, met een absoluut systeemmaximum van 100.

Business rules

  • Elke run is uniek per gebruiker.
  • DuplicateOfExerciseRunId verwijst naar dezelfde inhoud in een andere volgorde voor dezelfde gebruiker; SharedExerciseId verwijst naar een gedeelde oefening die eerst administratief is ontvangen en daarna daadwerkelijk gestart.
  • IsTestRun onderscheidt docenten-tests van reguliere leerlingruns.
  • Een run verwijst historisch naar de concrete oefening en de concrete moduleversie die op dat moment golden.
  • LevelId, CategoryId, ExerciseId en ExerciseModuleId vormen samen de historische context van de run en blijven na aanmaak of afronding ongewijzigd, ook wanneer centrale categorieën of koppelingen later migreren.

Lifecycle / gedrag

  • Bij genereren wordt een runrecord aangemaakt.
  • Na het eerste tonen van een vraag wordt StartedAtUtc gevuld.
  • Na elk antwoord schrijft de server de relevante voortgang bij in ExerciseRunProgress, worden de uniforme totalen/statistieken op ExerciseRuns bijgewerkt en deelt de actuele stand met eventuele meekijkers.
  • Bij afronden worden de definitieve statistieken éénmalig berekend en wordt IsCompleted = 1 gezet.
  • Wanneer een run ontstaat vanuit een gedeelde oefening, verwijst SharedExerciseId naar het bijbehorende administratieve shared-record.
  • Een dagelijkse TickerQ-taak ruimt niet-opgeruimde testruns op.
  • Een latere categoriemigratie herschrijft deze historische runcontext niet.

Designkeuzes

  • Module-specifieke vraag- en antwoordstructuren blijven bewust in JSON/base64 opgeslagen omdat technische modules sterk kunnen variëren in parameters en antwoordvormen, terwijl uniforme statistiekvelden juist direct relationeel en doorzoekbaar moeten zijn.

Foreign keys op databaseniveau

  • Harde foreign keys op databaseniveau: DuplicateOfExerciseRunId -> ExerciseRuns.Id; SharedExerciseId -> SharedExercises.Id.

Functionele / logische verwijzingen zonder harde FK

  • UserId is een soft link naar identity.Users.Id.
  • LevelId, CategoryId, ExerciseId en ExerciseModuleId zijn soft links naar het catalogusdomein. Deze velden worden niet als harde database-FK afgedwongen vanwege de modulegrens.
  • Deze soft links vormen samen met snapshotwaarden in payload/exportmodellen de historische runcontext. De runcontext wordt na aanmaak of afronding niet stilzwijgend herschreven door latere catalogus- of modulemigraties.
  • De velden QuestionDataJsonBase64 bevatten vrije of modulespecifieke payload en zijn bewust niet relationeel uitgesplitst naar harde foreign keys.
  • Historische runreconstructie gebruikt de opgeslagen runcontext, uniforme runvelden, ExerciseModuleId en de module-specifieke payload. Wanneer de payload een modulekey en schema-/payloadversie bevat, zoals moduleKey en schemaVersion, gebruikt de module die waarden voor backwards-compatible interpretatie. Hiervoor worden geen extra relationele kolommen zoals ConfigSchemaVersion geïntroduceerd.

FK + snapshot

  • FK + snapshot: niet van toepassing binnen deze tabel.

7.2 SharedExercises

TabelnaamCategorieDoel / verantwoordelijkheidGerelateerde tabellen
SharedExercisesOefenresultatenAdministratieve registratie van ontvangen gedeelde oefeningen voordat de ontvanger een echte exercise run start, inclusief herkomst, snapshots en soft delete.ExerciseRuns, Users
VeldnaamTypeDefaultPKFKVerwijst naarUniqueNullableIndexOpmerking
Iduniqueidentifier-JN-JNJPrimaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default.
SourceExerciseRunIduniqueidentifier-NJExerciseRuns.IdNNJBronrun waarvan de gedeelde inhoud afkomstig is.
SharedByUserIduniqueidentifier-NNUsers.IdNNJSoft link naar identity.Users.Id; gebruiker die de oefening heeft gedeeld. Geen harde database-FK vanwege modulegrens.
SharedToUserIduniqueidentifier-NNUsers.IdNNJSoft link naar identity.Users.Id; gebruiker die de gedeelde oefening heeft ontvangen. Geen harde database-FK vanwege modulegrens.
StartedExerciseRunIduniqueidentifiernullNJExerciseRuns.IdJJJRun die ontstaat zodra de ontvanger de gedeelde oefening daadwerkelijk start.
LevelSnapshotTextnvarchar(150)-NN-NNNTekstuele snapshot van het niveau zoals zichtbaar op het moment van delen.
CategorySnapshotTextnvarchar(150)-NN-NNNTekstuele snapshot van de categorie zoals zichtbaar op het moment van delen.
ExerciseSnapshotTextnvarchar(150)-NN-NNNTekstuele snapshot van de oefening zoals zichtbaar op het moment van delen.
SharedAtUtcdatetime2sysutcdatetime()NN-NNJMoment waarop het administratieve shared-record is aangemaakt.
StartedAtUtcdatetime2nullNN-NJJMoment waarop de ontvanger de gedeelde oefening voor het eerst echt start.
CompletedAtUtcdatetime2nullNN-NJJMoment waarop de uit deze share ontstane run is afgerond, gespiegeld voor snelle overzichtsweergave.
IsDeletedbit0NN-NNJSoft delete-vlag voor verwijderen uit het eigen overzicht van de ontvanger.
DeletedAtUtcdatetime2nullNN-NJJMoment waarop de ontvanger de gedeelde oefening uit het eigen overzicht verwijdert.
DeletedByUserIduniqueidentifiernullNNUsers.IdNJNSoft link naar identity.Users.Id; gebruiker die de soft delete uitvoerde. Geen harde database-FK vanwege modulegrens.
CreatedAtUtcdatetime2sysutcdatetime()NN-NNJAanmaakmoment van het record.
UpdatedAtUtcdatetime2sysutcdatetime()NN-NNNLaatste wijzigingsmoment.

Validaties / constraints

  • SourceExerciseRunId, SharedByUserId en SharedToUserId zijn verplicht.
  • StartedExerciseRunId is uniek wanneer gevuld, zodat één shared-record maximaal tot één echte run leidt.

Business rules

CompletedAtUtc op SharedExercises is een afgeleid overzichtsveld dat het afrondmoment van de daaruit ontstane run spiegelt. Het primaire afrondmomentdomein blijft de echte exercise run.

  • Deze tabel registreert ontvangen gedeelde oefeningen vóórdat de ontvanger een echte run start.
  • Het shared-record bevat daarom zowel de herkomst-run als de deler en ontvanger, plus snapshots van niveau, categorie en oefening voor stabiele weergave.
  • Wanneer de broncategorie later administratief is gemigreerd, blijven de snapshotwaarden ongewijzigd en blijft een nieuw uit het shared-record gestart runrecord functioneel gekoppeld aan de oorspronkelijke categoriecontext van de bronrun.

Lifecycle / gedrag

  • Een afzender kan een eenmaal gedeelde oefening niet terugtrekken.
  • De ontvanger kan het shared-record wel uit het eigen overzicht verwijderen via soft delete.
  • Start- en afrondmomenten worden gevuld zodra de ontvanger daadwerkelijk een run start en voltooit.
  • Gedeelde oefeningen leven eerst in SharedExercises en pas later eventueel in een echte run.

Foreign keys op databaseniveau

  • Harde foreign keys op databaseniveau: SourceExerciseRunId -> ExerciseRuns.Id; StartedExerciseRunId -> ExerciseRuns.Id.

Functionele / logische verwijzingen zonder harde FK

  • SharedByUserId, SharedToUserId en DeletedByUserId zijn soft links naar identity.Users.Id. De deelactie en zichtbaarheid worden applicatief gevalideerd via Identity/Relationships-contracten.
  • SourceExerciseRunId en StartedExerciseRunId blijven harde FK's binnen het practice-domein.

FK + snapshot

  • FK + snapshot: niet van toepassing binnen deze tabel.

7.3 ExerciseRunProgress

TabelnaamCategorieDoel / verantwoordelijkheidGerelateerde tabellen
ExerciseRunProgressOefenresultatenOperationele voortgang per vraag binnen een oefenrun, bedoeld voor server-side opslag na elk antwoord, abrupt onderbreken en live meekijken.ExerciseRuns
VeldnaamTypeDefaultPKFKVerwijst naarUniqueNullableIndexOpmerking
Iduniqueidentifier-JN-JNJPrimaire sleutel. GUID wordt in de applicatiecode gegenereerd; geen database-default.
ExerciseRunIduniqueidentifier-NJExerciseRuns.IdNNJRun waar deze voortgangsregel bij hoort.
SequenceNumberint0NN-NNJVolgorde van de vraag binnen deze specifieke run.
QuestionStateJsonBase64nvarchar(max)-NN-NNNModule-specifieke toestand van de vraag, inclusief parameters, antwoordstructuur en lokale voortgang.
FirstShownAtUtcdatetime2nullNN-NJNMoment waarop de vraag voor het eerst aan de gebruiker is getoond.
AnsweredAtUtcdatetime2nullNN-NJNMoment waarop de gebruiker de vraag heeft bevestigd en de server de uitkomst verwerkt.
IsCorrectbitnullNN-NJNUniforme markering of de vraag uiteindelijk goed is beantwoord.
IsDunnobit0NN-NNNGeeft aan of de vraag als “Geen idee” is gemarkeerd.
IsCompletedbit0NN-NNJGeeft aan of de server de vraag volledig heeft verwerkt en meegerekend in de totalen.
UpdatedAtUtcdatetime2sysutcdatetime()NN-NNJLaatste wijzigingsmoment van deze voortgangsregel.

Validaties / constraints

  • Actieve combinatie ExerciseRunId + SequenceNumber is uniek.
  • QuestionStateJsonBase64 is verplicht omdat de technische module-specifieke vraag- en antwoordtoestand hierin wordt vastgelegd.
  • AnsweredAtUtc en IsCorrect mogen alleen worden gevuld nadat de server de beantwoording heeft verwerkt.

Business rules

IsCompleted op vraagniveau betekent dat de server deze specifieke vraag volledig heeft verwerkt. Dit staat los van IsCompleted op ExerciseRuns, dat de afronding van de volledige oefenrun aanduidt.

  • Na elk antwoord wordt de betreffende voortgangsregel bijgewerkt en worden de uniforme totalen/statistieken op ExerciseRuns herberekend of incrementeel aangepast.
  • Deze tabel ondersteunt abrupt onderbreken en live meekijken doordat de actuele stand per vraag centraal beschikbaar blijft.
  • De inhoud van QuestionStateJsonBase64 blijft module-specifiek, terwijl timing en juistheid uniform zijn.

Lifecycle / gedrag

  • Voor iedere gegenereerde vraag wordt een progressieregel aangemaakt.
  • Bij eerste weergave wordt FirstShownAtUtc gevuld.
  • Bij beantwoording schrijft de server AnsweredAtUtc, IsCorrect, IsDunno, IsCompleted en de bijgewerkte vraagtoestand weg.
  • De gegevens blijven historisch bewaard zolang de bijbehorende run bewaard blijft.
  • Bij accountverwijdering van de uitvoerende gebruiker blijven bestaande runs gekoppeld aan het geanonimiseerde Users-record historisch bewaard. Niet-afgeronde runs krijgen geen aparte eindstatus, blijven IsCompleted = 0 en worden functioneel niet meer hervatbaar.

Designkeuzes

  • Deze tabel vormt bewust de hybride middenweg tussen volledig relationele vraagopslag en één grote run-payload.
  • De module-inhoud blijft flexibel in JSON/base64, terwijl veel geschreven operationele voortgang apart kan worden opgeslagen voor performance, herstelbaarheid en live meekijken.

Foreign keys op databaseniveau

  • Harde foreign keys op databaseniveau: ExerciseRunId -> ExerciseRuns.Id.

Functionele / logische verwijzingen zonder harde FK

  • De velden QuestionStateJsonBase64 bevatten vrije of modulespecifieke payload en zijn bewust niet relationeel uitgesplitst naar harde foreign keys.
  • Ook voortgangspayloads mogen modulespecifieke schema-informatie bevatten wanneer dat nodig is om oudere voortgang veilig te renderen of te controleren; normale rapportage blijft gebaseerd op uniforme run- en voortgangsvelden.

FK + snapshot

  • FK + snapshot: niet van toepassing binnen deze tabel.

7.4 Relatievoorwaarden voor gedeelde oefeningen

7.4.1 Delen vereist actieve relatie

  • Het aanmaken van een nieuw SharedExercises-record vereist server-side controle dat de deler en ontvanger op dat moment een actieve relatie hebben die delen toestaat.
  • Voor leerling-naar-leerling delen is dit in de uitgewerkte relatie-usecases de actieve Friendship-relatie.
  • Frontend-zichtbaarheid van een deelknop of ontvangerlijst is geen autorisatie; de relatie wordt opnieuw gecontroleerd bij het daadwerkelijk delen.

7.4.2 Gevolgen van ontkoppelen

  • Het ontkoppelen van een relatie verwijdert bestaande SharedExercises-records niet.
  • Een ontvanger behoudt bestaande ontvangen gedeelde oefeningen en eventueel daaruit gestarte oefenruns, tenzij een afzonderlijke verwijder- of retentieregel van toepassing is.
  • Na ontkoppeling mogen geen nieuwe gedeelde oefeningen tussen dezelfde gebruikers worden aangemaakt op basis van de gedeactiveerde relatie.
  • Historische runs en snapshots blijven ongewijzigd, ook wanneer de relatie later wordt ontkoppeld.

7.4.3 Ouder/voogdrelaties en oefenen

  • Een GuardianStudent-relatie geeft ouder/voogd-inzage en waar toegestaan live meekijken, maar geeft geen recht om namens de leerling oefenruns te starten, te beantwoorden of te wijzigen.
  • Oefenrunmutaties blijven gekoppeld aan de uitvoerende leerlinggebruiker.

7.5 Account-lifecycle binnen oefenruns en delen

7.5.1 Accountverwijdering en open oefenruns

Bij accountverwijdering of anonimisering worden open oefenruns van de gebruiker niet afgerond. Zij blijven administratief niet-afgerond, maar zijn niet langer hervatbaar en verschijnen niet in normale geschiedenis- of statistiekweergaven.

Afgeronde oefenruns, resultaten en historie blijven waar nodig beschikbaar onder geanonimiseerde identiteit, zodat historische overzichten, audit en rapportages niet worden herschreven of hard verwijderd.

7.5.2 Logout en oefenruns

Uitloggen tijdens een niet-afgeronde oefenrun geldt niet als afronding van die oefening. De run blijft alleen hervatbaar wanneer de gebruiker later opnieuw met een geldig actief account en geldige context inlogt. Logout mag geen oefenrun, resultaat, relatie, ticket, systeembericht of systeemnotificatie aanmaken of inhoudelijk wijzigen.

7.6 Ouder-/voogd- en beheerdercontext bij runs

OnderwerpAanscherping
Ouder-/voogdresultatenOuder-/voogdresultaatinzage leest dezelfde historische ExerciseRuns, voortgang, totalen en statistiekvelden als leerling- en docentweergaven. Er ontstaat geen aparte ouderresultaatdata.
Alle niveausEen ouder/voogd mag resultaten en geschiedenis van alle niveaus van gekoppelde kinderen raadplegen, zolang de ouder-/voogdrelatie actief is.
Geen oefenstartEen ouder/voogd kan geen oefening genereren, starten, hervatten of afronden namens het kind.
Resultaat-PDFPDF-export binnen oudercontext gebruikt dezelfde historische runcontext als leerling- en docentexport, maar wordt server-side begrensd door de actieve ouder-/voogdrelatie.
Live meekijkenLive meekijken is read-only en gebruikt server-side opgeslagen voortgang als bron. SignalR is transport, niet de bron van waarheid.
Categorie- en modulemigratieCategorie- of modulemigraties mogen historische runs, gedeelde oefeningen en bestaande resultaten niet herschrijven of onleesbaar maken.
Read-only resultaatflowsSamenvatting, geschiedenis, filters, detail, statistieken en PDF-export wijzigen geen ExerciseRuns, ExerciseRunProgress, antwoorden, scores of statistiekvelden.
Lege toestandenGeen afgeronde runs of lege filterresultaten zijn geldige readmodeltoestanden en veroorzaken geen foutstatus of datamutatie.
Actieve live voortgangLive meekijken leest ExerciseRunProgress en uniforme runvelden na server-side verwerking van leerlingacties. Ouder/voogd-publicatie van updates schrijft zelf geen voortgang.
Browse-modusDoor vragen bladeren tijdens live meekijken is lokale UI-state; de actuele vraag blijft afgeleid uit de opgeslagen voortgang.
VerbindingsverliesReconnect en definitieve verbreking beïnvloeden geen oefenrun. Alleen de live audit/subscription wordt beëindigd of hervat.