Voeg Module 4 experts, waardebepalingspagina en projectverbeteringen toe

Experts (Module 4)
- Pagina Experts (CRM): experts uitnodigen per e-mail (zonder SMTP wordt de
  link getoond), rondes koppelen, opnieuw uitnodigen, verwijderen en
  "Inloggen als" (alleen lezen) om mee te kijken
- Uitnodigingspagina waar een expert zijn wachtwoord instelt (token 7 dagen
  geldig, eenmalig, alleen als hash opgeslagen)
- Expertpagina: projecten in waardebepaling uit de eigen rondes, project plan
  lezen en eigen beoordeling geven (3 scores, advies Bonus/Malus, toelichting)
- Rollen admin/expert: alle beheerpagina's en -acties controleren de rol
- Rikx calculatie gebruikt de beoordelingen van de experts in plaats van de
  handmatige expertkolommen; experts koppelen ook vanaf de rondepagina

Waardebepaling projecten
- Nieuwe pagina met projecten in waardebepaling op kosten/impact (laag naar
  hoog), filter op ronde, Rikx Waarde-veld dat live doorrekent en opslaat in
  de ronde, en een 75%-lijn bij een gekozen ronde

Projecten
- Rikx calculatie als tab, zichtbaar vanaf status Waardebepaling, met keuze
  rekenmethode (Arbeid; Jeugd nog niet beschikbaar) en beoordeling
  goedgekeurd/afgekeurd met reden
- Impactmaker gekoppeld aan een sociaal ondernemer
- Opslaan-knop bovenaan; ongeldige velden op een verborgen tab worden getoond
- Project toevoegen maakt direct een project aan; aparte pagina verwijderd
- Waarderingssystematiek Rikx juni 2023 toegevoegd aan _doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 14:42:22 +02:00
parent 9f0bd87c40
commit 53bf667f54
41 changed files with 2075 additions and 357 deletions
+57 -5
View File
@@ -1,4 +1,5 @@
// Rikx-rekenmethode, overgenomen uit _doc/Rikx rekenmethode uitleg.xlsx (tabblad "Uitleg").
// Rikx Waardeberekening Arbeid, overgenomen uit _doc/Rikx rekenmethode uitleg.xlsx (tabblad "Uitleg")
// en _doc/Waarderingssystematiek Rikx juni 2023.pdf.
// Pure functies zonder database-toegang, zodat het formulier live kan rekenen.
export const BONUS_MALUS_OPTIONS = [
@@ -10,13 +11,35 @@ export const BONUS_MALUS_OPTIONS = [
{ id: "--", label: "--", factor: 0.8 },
] as const;
// Schalen volgens _doc/Waarderingssystematiek Rikx juni 2023.pdf ("Inhoudelijke focus")
export const EXPERT_CRITERIA = [
{ id: "duur_impact", label: "Duur van impact" },
{ id: "levensdomeinen", label: "Aantal levensdomeinen" },
{ id: "basissituatie", label: "Basissituatie doelgroep" },
{ id: "duur_impact", label: "Duur van impact", schaal: "kortstondig → levenslang" },
{ id: "levensdomeinen", label: "Aantal levensdomeinen", schaal: "één levensdomein → alle levensdomeinen" },
{ id: "basissituatie", label: "Basissituatie doelgroep", schaal: "relatief eenvoudig → relatief zwaar" },
] as const;
export const EXPERT_COUNT = 3;
// Rikx kent twee rekenmethodes. Alleen "Arbeid" is uitgewerkt (de formule hieronder);
// "Jeugd" is zichtbaar maar nog niet te kiezen.
export const REKENMETHODES = [
{
id: "arbeid",
label: "Rikx Waardeberekening Arbeid",
omschrijving: "Impact op de arbeidsmarkt: uitstroom naar werk en het opdoen van skills.",
beschikbaar: true,
},
{
id: "jeugd",
label: "Rikx Waardeberekening Jeugd",
omschrijving: "Nog niet beschikbaar.",
beschikbaar: false,
},
] as const;
export const DEFAULT_REKENMETHODE = "arbeid";
export function isBeschikbareRekenmethode(value: unknown): value is string {
return REKENMETHODES.some((m) => m.id === value && m.beschikbaar);
}
export type ExpertCriterium = (typeof EXPERT_CRITERIA)[number]["id"];
export type ExpertScore = Record<ExpertCriterium, number | null>;
@@ -116,3 +139,32 @@ export function calculateRikx(input: RikxInput): RikxResult {
export function defaultRikxPrijs(rikxWaarde: number): number {
return rikxWaarde / 10;
}
// Bouwt de invoer voor calculateRikx uit een opgeslagen project, de beoordelingen van de experts
// (elk aantal; Impactscore M = gemiddelde over de experts) en zijn Rikx Ronde.
export function rikxInputFromProject(
project: {
projectkosten: number | null;
deelnemers_werk: number | null;
deelnemers_skills: number | null;
financiering_extern_pct: number | null;
bonus_malus: string | null;
},
beoordelingen: { duur_impact: number | null; levensdomeinen: number | null; basissituatie: number | null }[],
ronde: { rikx_waarde: number | null; rikx_prijs: number | null } | null
): RikxInput {
return {
projectkosten: project.projectkosten,
deelnemers_werk: project.deelnemers_werk,
deelnemers_skills: project.deelnemers_skills,
financiering_extern_pct: project.financiering_extern_pct,
bonus_malus: project.bonus_malus,
rikx_waarde: ronde?.rikx_waarde ?? null,
rikx_prijs: ronde?.rikx_prijs ?? null,
expert_scores: beoordelingen.map((b) => ({
duur_impact: b.duur_impact,
levensdomeinen: b.levensdomeinen,
basissituatie: b.basissituatie,
})),
};
}