diff --git "a/index.html" "b/index.html" --- "a/index.html" +++ "b/index.html" @@ -1,49 +1,471 @@ - -Reakt × Antes | DVO ORCHESTRA - -
- -
CONTROL CENTER

Multi-Agent Orchestration ●

-
DVO ORCHESTRA v0.1 · BRONBESTAND IN BROWSERRealisatie DVO Antes · kwartaalmonitorUpload alleen lokaal · geen Excelbestanden in Hugging Face-repository · automatisch alle vier tabbladen beoordelen
Kies het bestand ‘Realisatie DVO dagbesteding Antes ...xlsx’. De gegevens blijven in deze browserpagina.
-
MASTER ORCHESTRATORTHE CONDUCTORGereed voor analyse
●   Lokale browseranalyseWacht op bronbestand0 / 5 agents voltooid
-
Netto cliënturen—Bronbestand vereist
Realisatie DVO—Bronbestand vereist
Bruto geregistreerd—Bronbestand vereist
Wmo-correctie—Bronbestand vereist
TRENDANALYSE

Registratie per maand

Nog geen data
Selecteer het DVO-Excelbestand en start de analyse. Hier verschijnt een maandgrafiek met exact geregistreerde bruto cliënturen.
KWALITEITSBORGING

Verification Agent

Interne broncontrole
Na het inlezen vergelijken we automatisch realisatie, correcties en DVO-afspraken tussen de tabbladen.
Geen formele validatie door de businesscontroller. DVO-verrekening is nog te bevestigen.
AGENT ACTIVITY LOG

Transparant analyseproces

Stand-by
◌   Wacht op Excelbestand.
- -
- +Reakt | ORCHESTRA + +
+
+
REAKT / AI ANALYTICS PLATFORM

Inloop Intelligence ORCHESTRA

Maak complexe informatie begrijpelijk en toegankelijk voor iedereen.

ORCHESTRAby Marcel Ooms
LOKALE ANALYSE
+
CONTROL CENTER

Agent Command Center

+
+
ORCHESTRA NEXT · BROWSER-LOKAALScanregistratie 2026 + referentie 2025Browser-lokale analyse · alle bronlocaties · geen bestanden in Space-repository
+ + + + + Kies eerst het rapportagebestand 2026 +
◴ Historie 2025 nog niet geladen. Zonder deze referentie zijn instroomcijfers voorlopig.
+
+
+

Van ruwe scangegevens naar helder inzicht · volg taakverdeling, gegevensoverdracht en controles onder de motorkap.

MASTER ORCHESTRATORTHE CONDUCTORGereed voor analyse
+ +
+
01 / DATASTANDBY
▤

Data Agent

Structuur & kwaliteit

0%
+
04 / ANALYSESTANDBY
⌁

Analysis Agent

Trends & vergelijkingen

▁ ▃ ▂ ▅ ▄ ▆ ▇
0%
+
06 / TRENDSTANDBY
✦

Trend Agent

Maandpatronen & duiding

◎ ◉ ◎
0%
+
07 / CONTROLESTANDBY
⬡

Verification Agent

Schaduwberekeningen

✓   ✓   !   ✓
0%
+
09 / RAPPORTSTANDBY
▧

Report Agent

Infographic & export

▰▰▰▰▰
▰▰▰▰
▰▰▰
0%
+
02 / IDENTITEITSTANDBY
⛓

Identity Agent

Pas- en cliëntkoppelingen

◎ ↔ ◎
0%
+
03 / HISTORIESTANDBY
◷

History Agent

Referentie 2025

2025 ↔ 2026
0%
+
08 / PRIVACYSTANDBY
◇

Privacy Agent

Lokale verwerkingsroute

◈ ⛨ ◈
0%
+
10 / KWALITEITSTANDBY
✧

Quality Agent

Rapportagestatus

✓ / !
0%
+
+ +
+
+
+
+
+
ⓘWat betekenen deze cliëntcijfers?Rekenregels per inlooplocatie
+
+
Unieke cliënten

Iedere cliënt die op deze locatie binnen de gekozen rapportageperiode minimaal één geldige scan heeft.

+
Nieuwe instroom

Cliënten met hun allereerste geldige scan op deze locatie in de gekozen rapportageperiode.

+
Actieve cliënten

Cliënten die in de afgelopen drie maanden t/m de peildatum minimaal één keer op deze locatie hebben gescand.

+
+
Een verloren of vervangen scanpas telt niet als een nieuwe cliënt: meerdere passen van dezelfde cliënt worden samengevoegd.
+
+
TRENDANALYSE

Bezoeken per locatie

Jul – Sep 2026
Soeda Rietdijk Vredehof Gouda
+
KWALITEITSBORGING

Verificatie Businesscontroller

DEMO
⚠ 1 fictieve afwijking. Rapport niet gevalideerd.
+ +
+
EXTERNE SCHADUWCONTROLE · FICTIEVE TEST

Vergelijk met businesscontroller

NIET AANGELEVERD
+

Vul uitsluitend fictieve, geaggregeerde controlecijfers in voor exact dezelfde locatie, periode en definities. Geen cliëntgegevens. Invoer blijft in deze browserpagina en wordt niet opgeslagen.

+
Analyseer eerst het fictieve Excel-bestand en kies een locatie en kwartaal.
+
+ + + + +
+
+
Nog geen onafhankelijke controlecijfers vergeleken.
+ +
+
REAL-TIME PROCESS

Agent activity log

Stand-by
◌   Klik op ‘Start demo-analyse’ om de workflow te bekijken.
+
+ +
+ +
+
+ + + - + +// v0.8 — rapport wordt opgebouwd uit lokaal berekende, geaggregeerde testgegevens. +// Er is geen LLM, er wordt niet geüpload en ontbrekende velden worden NIET ingevuld. +const reportNL=n=>Number(n).toLocaleString('nl-NL'); +function reportChartBars(names,values,color='#7935cf',asWeek=false){ + const W=440,H=195,left=38,right=12,top=17,bottom=31,max=Math.max(1,...values); + let out=``; + for(let i=0;i<=4;i++){let y=top+(H-top-bottom)*i/4;out+=``} + const band=(W-left-right)/Math.max(1,names.length),bar=Math.min(38,band*.7); + values.forEach((v,i)=>{const bh=(H-top-bottom-10)*v/max,x=left+band*i+(band-bar)/2,y=H-bottom-bh; + out+=`${v}${escapeHTML(names[i])}`}); + return out+''; +} +function reportChartClientMonth(names,values){ + // Alleen presentatie: dezelfde berekende maandwaarden; iedere cliënt eenmaal per maand. + const W=480,H=220,left=24,right=9,top=19,bottom=34; + const max=Math.max(1,...values),span=H-top-bottom; + const band=(W-left-right)/Math.max(1,names.length),bar=Math.min(46,band*.82); + let out=``; + for(let i=0;i<=4;i++){ + const y=top+span*i/4; + out+=``; + } + values.forEach((v,i)=>{ + const height=(span-15)*v/max; + const x=left+band*i+(band-bar)/2,y=H-bottom-height; + out+=``; + out+=`${v}`; + out+=`${escapeHTML(names[i])}`; + }); + return out+''; +} +function reportChartLine(names,values){ + if(!values.length)return '
Geen gegevens beschikbaar
'; + const W=440,H=195,L=40,T=24,B=34,R=15,max=Math.max(1,...values)*1.16,span=W-L-R; + let out=``; + for(let i=0;i<=4;i++){const y=T+(H-T-B)*i/4;out+=`${Math.round(max*(4-i)/4)}`} + const dots=values.map((v,i)=>[L+(values.length===1?span/2:span*i/(values.length-1)),H-B-v/max*(H-T-B-7)]); + out+=``; + dots.forEach(([x,y],i)=>{out+=`${values[i]}${escapeHTML(names[i])}`}); + return out+''; +} +function getReportData(){ + if(!importedData?.last) return null; + const result=importedData.last,site=result.site,period=result.period; + const history=importedData.all.filter(r=>(site==='*'||r.site===site)&&r.date<=period.peildatum); + const quarter=history.filter(r=>r.date>=period.begin); + const counts=new Map(),uniqueDates=new Set(),byMonth=new Map(period.months.map(m=>[m,0])),clientsByMonth=new Map(period.months.map(m=>[m,new Set()])),daysWithScans=new Set(),byWeek=[0,0,0,0,0,0,0]; + for(const r of quarter){ + const key=JSON.stringify([r.site,r.id,r.date]);if(uniqueDates.has(key))continue; + uniqueDates.add(key); + const mk=r.date.slice(0,7);byMonth.set(mk,(byMonth.get(mk)||0)+1); + const wk=new Date(r.date+'T12:00:00Z').getUTCDay();byWeek[(wk+6)%7]++; + const ck=JSON.stringify([r.site,r.id]);counts.set(ck,(counts.get(ck)||0)+1); + clientsByMonth.get(mk)?.add(ck); + daysWithScans.add(r.date); + } + const intensities=[0,0,0,0,0],daysPerClient=[...counts.values()]; + daysPerClient.forEach(c=>{intensities[c<=5?0:c<=20?1:c<=50?2:c<=100?3:4]++}); + const sorted=daysPerClient.sort((a,b)=>a-b),median=sorted.length?(sorted.length%2?sorted[(sorted.length-1)/2]:(sorted[sorted.length/2-1]+sorted[sorted.length/2])/2):0; + const monthLabels=['jan','feb','mrt','apr','mei','jun','jul','aug','sep','okt','nov','dec']; + const months=period.months.map(key=>({key,label:monthLabels[Number(key.slice(5))-1],visits:byMonth.get(key),clients:clientsByMonth.get(key)?.size||0,newIntake:result.instroomByMonth.get(key)||0})); + return {site:site==='*'?'Alle locaties':site,period,months,byWeek,intensities, + visits:uniqueDates.size,unique:result.unique,quarterClients:result.quarterClients,newIntake:result.newIntake,active:result.active, + average:counts.size?uniqueDates.size/counts.size:0,averageClientsPerDay:daysWithScans.size?uniqueDates.size/daysWithScans.size:0,daysWithScans:daysWithScans.size,median,maxDays:Math.max(0,...daysPerClient), + earliest:period.begin,latest:period.peildatum,issues:result.passConflicts+result.identityConflicts+result.unmatchedPasses,hasShadow:false, + historyLoaded:result.historyLoaded,correctedExisting:result.correctedExisting,correctedJanuary:result.correctedJanuary}; +} +function buildWhiteReport(){ + const d=getReportData();if(!d)return; + const range=d.earliest===d.latest?d.earliest:`${d.earliest} t/m ${d.latest}`; + const avgDay=d.averageClientsPerDay.toLocaleString('nl-NL',{minimumFractionDigits:1,maximumFractionDigits:1}); + const activeWindow=readableDate(d.period.activeFrom)+' t/m '+readableDate(d.period.peildatum); + const mon=d.months.map(m=>m.label),values=d.months.map(m=>m.visits),clientsPerMonth=d.months.map(m=>m.clients),newPerMonth=d.months.map(m=>m.newIntake),week=['Ma','Di','Wo','Do','Vr','Za','Zo']; + const weekdayMax=Math.max(...d.byWeek),weekdayMin=Math.min(...d.byWeek),busiest=week[d.byWeek.indexOf(weekdayMax)],quietest=week[d.byWeek.indexOf(weekdayMin)]; + const summary=[ + `In ${d.period.label} registreerde ${escapeHTML(d.site)} ${reportNL(d.unique)} unieke cliënten en ${reportNL(d.visits)} bezoekdagen.`, + `Op ${reportNL(d.daysWithScans)} dagen met geregistreerde scans kwamen gemiddeld ${avgDay} cliënten per dag. De meeste bezoekdagen vielen op ${busiest} (${reportNL(weekdayMax)}).`, + `Nieuwe instroom: ${reportNL(d.newIntake)} cliënten. Actieve cliënten in de afgelopen drie maanden: ${reportNL(d.active)}.${d.historyLoaded?' Na vergelijking met 2025 bleken '+reportNL(d.correctedExisting)+' aanvankelijk nieuwe cliënten al eerder geregistreerd.':' Historie over 2025 ontbreekt; de instroom is voorlopig.'}` + ]; + const kpis=[{klass:'purple',icon:'♟',label:'Unieke cliënten',value:reportNL(d.unique),sub:''}, + {klass:'green',icon:'+',label:'Nieuwe instroom',value:reportNL(d.newIntake),sub:'Eerste geldige scan op locatie in '+d.period.label}, + {klass:'purple',icon:'♙',label:'Actieve cliënten',value:reportNL(d.active),sub:'Minimaal 1 scan in de afgelopen drie maanden.'}, + {klass:'orange',icon:'▦',label:'Bezoekdagen',value:reportNL(d.visits),sub:'Eén cliënt per locatie per kalenderdag'}]; + const kpiHTML=kpis.map(k=>`
${k.icon}
${k.label}
${k.value}
${k.sub}
`).join(''); + $('reportSheet').innerHTML=` +
+

Reakt Inloop Monitor ${escapeHTML(d.site)}

Inzicht in bereik, betrokkenheid en bezoekpatronen

+
▦Periode${escapeHTML(range)}
⌖Locatie${escapeHTML(d.site)}
↻Peildatum${escapeHTML(d.latest)}
+
${kpiHTML}
+
+
Zo leest u de cliëntcijfers
+
+
Unieke cliëntenIedere cliënt met minimaal één geldige scan op deze locatie binnen de gekozen rapportageperiode.
+
Nieuwe instroomDe eerste geldige scan van een cliënt op deze locatie (binnen de beschikbare historie) valt in de rapportageperiode.
+
Actieve cliëntenMinimaal één scan op deze locatie tussen ${escapeHTML(activeWindow)}.
+
+
Een vervangende scanpas telt niet als nieuwe cliënt.
+
+
+

Instroom per maand (nieuwe cliënten)

${reportChartBars(mon,newPerMonth,'#288e28')}
+

Bezoekdagen per maand

${reportChartLine(mon,values)}
+

Bezoekdagen per weekdag

${reportChartBars(week,d.byWeek,'#ff6410',true)}
+
+
+

Kerncijfers locatie

LocatieUnieke cliëntenBezoekdagenGem. cliënten per dag*
⌖ ${escapeHTML(d.site)}${reportNL(d.unique)}${reportNL(d.visits)}${avgDay}
Totaal / gemiddelde${reportNL(d.unique)}${reportNL(d.visits)}${avgDay}
${avgDay}Gemiddeld aantal cliënten per dag
* Berekend over ${reportNL(d.daysWithScans)} dagen met geregistreerde scans.
+

Aantal cliënten per maand (iedere cliënt maximaal één keer per maand)

${reportChartClientMonth(mon,clientsPerMonth)}
+

Managementanalyse

${summary.map(t=>`

${t}

`).join('')}
+
+
Bron: scanregistratie 2026${d.historyLoaded?' en historisch referentiebestand 2025':''}. Rapportageperiode en peildatum staan bovenaan vermeld.
`; +} +function openWhiteReport(){ + if(!importedData?.last){alert('Upload eerst een Excelbestand en rond de analyse af. Deze witte rapportage gebruikt geen verzonnen demo-KPI’s.');return;} + if(importedData.last.site==='*'){alert('Kies eerst één locatie en voer daarna de analyse opnieuw uit. De witte monitor is in v0.8 een rapport per locatie.');return;} + buildWhiteReport();$('reportOverlay').classList.add('open');$('reportOverlay').setAttribute('aria-hidden','false');document.body.classList.add('report-open');$('reportOverlay').scrollTop=0; +} +function closeWhiteReport(){$('reportOverlay').classList.remove('open');$('reportOverlay').setAttribute('aria-hidden','true');document.body.classList.remove('report-open')} + +async function downloadReportImage(type='png'){ + const overlay=$('reportOverlay'); + if(!overlay.classList.contains('open')) openWhiteReport(); + const target=$('reportSheet'); + const pngBtn=$('reportPng'), jpgBtn=$('reportJpg'); + const activeBtn=type==='png'?pngBtn:jpgBtn; + if(!window.html2canvas){ + alert('De afbeeldingsexport kon niet starten. Vernieuw de pagina en probeer opnieuw.'); + return; + } + const prev=activeBtn.textContent; + try{ + activeBtn.disabled=true; activeBtn.textContent='Bezig met exporteren...'; + await new Promise(r=>setTimeout(r,80)); + const canvas=await html2canvas(target,{backgroundColor:'#ffffff',scale:2.5,useCORS:true,logging:false}); + const mime=type==='jpg'?'image/jpeg':'image/png'; + const quality=type==='jpg'?0.94:1; + const dataUrl=canvas.toDataURL(mime,quality); + const link=document.createElement('a'); + const site=(importedData?.last?.site||'monitor').toLowerCase().replace(/[^a-z0-9]+/gi,'_'); + link.href=dataUrl; + link.download=`reakt_inloop_monitor_${site}.${type==='jpg'?'jpg':'png'}`; + document.body.appendChild(link); + link.click(); + link.remove(); + }catch(err){ + console.error(err); + alert('Het downloaden van de afbeelding is niet gelukt. Probeer het opnieuw.'); + }finally{ + activeBtn.disabled=false; activeBtn.textContent=prev; + } +} +$('reportClose').addEventListener('click',closeWhiteReport); +$('reportPrint').addEventListener('click',()=>window.print()); +$('reportPng').addEventListener('click',()=>downloadReportImage('png')); +$('reportJpg').addEventListener('click',()=>downloadReportImage('jpg')); +document.addEventListener('keydown',e=>{if(e.key==='Escape'&&$('reportOverlay').classList.contains('open'))closeWhiteReport()}); + +$('startBtn').addEventListener('click',start);$('resetBtn').addEventListener('click',reset);$('chatForm').addEventListener('submit',e=>{e.preventDefault();const q=$('chatInput').value;$('chatInput').value='';ask(q)});document.querySelectorAll('[data-q]').forEach(b=>b.addEventListener('click',()=>ask(b.dataset.q)));$('exportBtn').addEventListener('click',openWhiteReport); +Promise.resolve({ok:true,json:async()=>JSON.parse(document.getElementById('orchestra-demo-data').textContent)}).then(r=>r.json()).then(d=>{demoData=d;renderKPIs(d);renderChecks(d);drawChart(d)}).catch(()=>log('Demo-gegevens konden niet worden geladen')); + + +// v1.2 — expliciete, handmatig ingevoerde onafhankelijke fictieve aggregaten. +// Resultaten worden NIET opgeslagen; andere locatie/kwartaal/bestand maakt vergelijking ongeldig. +const controllerDefs=[['unique','ctrlUnique','Unieke cliënten (periode)'],['newIntake','ctrlIntake','Nieuwe instroom'],['active','ctrlActive','Actieve cliënten'],['visits','ctrlVisits','Unieke bezoekdagen']]; +let controllerComparison=null; +function resetControllerComparison(clearInputs=false){ + controllerComparison=null; + $('controllerBadge').textContent='NIET AANGELEVERD'; + $('controllerResult').textContent='Nog geen onafhankelijke controlecijfers vergeleken.'; + if(clearInputs)controllerDefs.forEach(([,id])=>{$(id).value=''}); +} +function updateControllerContext(){ + const r=importedData?.last; + $('controllerContext').textContent=r?`ORCHESTRA: ${r.site==='*'?'Alle locaties (som van locaties)':r.site} · ${r.period.label} · peildatum ${readableDate(r.period.peildatum)}. Gebruik precies deze selectie voor de controle.`:'Analyseer eerst het Excelbestand en kies een locatie en rapportageperiode.'; +} +function compareController(){ + const r=importedData?.last; + if(!r){$('controllerResult').textContent='⚠ Analyseer eerst een fictief Excel-testbestand.';return} + const values={}; + for(const [key,id] of controllerDefs){const el=$(id),v=el.value.trim();if(!/^\d+$/.test(v)||!Number.isSafeInteger(Number(v))){$('controllerResult').textContent='⚠ Vul alle vier de controlewaarden in als geldige, niet-negatieve gehele getallen.';el.focus();return}values[key]=Number(v)} + const rows=controllerDefs.map(([key,,label])=>({label,ours:r[key],controller:values[key],delta:r[key]-values[key]})); + const count=rows.filter(x=>x.delta!==0).length; + controllerComparison={site:r.site,period:r.period.label,rows,count}; + const fmt=x=>Number(x).toLocaleString('nl-NL'); + const rendered=rows.map(x=>`${escapeHTML(x.label)}${fmt(x.ours)}${fmt(x.controller)}${x.delta?(x.delta>0?'+':'')+fmt(x.delta):'✓ 0'}`).join(''); + $('controllerResult').innerHTML=`
${count?`⚠ ${count} van 4 indicatoren wijken af. Onderzoek de oorzaak voordat de cijfers worden gebruikt.`:'✓ Alle 4 ingevoerde controlegetallen komen overeen. Dit is nog geen formele validatie.'}
${rendered}
IndicatorORCHESTRAControleVerschil
`; + $('controllerBadge').textContent=count?`${count} VERSCHIL(LEN)`:'4 / 4 GELIJK'; + log('Fictieve businesscontrollervergelijking: '+count+' verschil(len) op 4 indicatoren; geen formele vrijgave'); +} +$('controllerCompare').addEventListener('click',compareController); +$('controllerClear').addEventListener('click',()=>resetControllerComparison(true)); +controllerDefs.forEach(([,id])=>$(id).addEventListener('input',()=>{if(controllerComparison)resetControllerComparison(false)})); + +// ORCHESTRA NEXT — echte statusovergangen gekoppeld aan bestaande berekeningen. +// Geen nieuwe externe AI-call en geen wijziging aan XLSX-, KPI- of rapportagerekenregels. +const nextRoutes=[['data','identity'],['data','history'],['identity','analysis'],['history','analysis'],['analysis','insight'],['analysis','verify'],['insight','report'],['verify','report'],['privacy','quality'],['report','quality'],['verify','analysis'],['identity','history'],['conductor','privacy']]; +const nextAgents={data:'Bronregels beschikbaar',identity:'Pas- en cliëntkoppelingen',history:'Historische referentie',analysis:'KPI-berekeningen',insight:'Trendgegevens',verify:'Interne onafhankelijke telling',privacy:'Lokale browserroute',report:'Dashboard en rapportage',quality:'Status en kwaliteitsmelding'}; +function nextWorkflowDelay(){return $('paceSelect').value==='fast'?650:3300;} +function nextSignalDuration(){return $('paceSelect').value==='fast'?'0.9s':'1.8s';} +let nextActiveRoutes=[],nextCompletedRoutes=[],nextGraphReady=false,nextTransferTimer=null; +function nextEl(id){return id==='conductor'?$('conductor'):$('agent-'+id)} +function nextSetConductorTransfer(active){const c=$('conductor');if(!c)return;c.classList.toggle('transfering',!!active);if(active){clearTimeout(nextTransferTimer);nextTransferTimer=setTimeout(()=>c.classList.remove('transfering'),1050)}} +function nextRememberRoutes(routes=[]){for(const route of routes.map(r=>r.join('>'))){if(!nextCompletedRoutes.includes(route))nextCompletedRoutes.push(route)}} +function nextGraph(){ + const host=document.querySelector('.orchestra'),svg=$('networkSvg'),edgeLayer=$('networkEdges');if(!host||!svg||!edgeLayer)return; + const bounds=host.getBoundingClientRect();svg.setAttribute('viewBox',`0 0 ${bounds.width} ${bounds.height}`);edgeLayer.replaceChildren(); + for(const [from,to] of nextRoutes){ + const a=nextEl(from)?.getBoundingClientRect(),b=nextEl(to)?.getBoundingClientRect();if(!a||!b)continue; + const ax=a.left+a.width/2-bounds.left,ay=a.bottom-bounds.top,bx=b.left+b.width/2-bounds.left,by=b.top-bounds.top; + const sameRow=Math.abs((a.top+a.height/2)-(b.top+b.height/2))<45; + const curveLift=Math.min(82,Math.max(38,Math.abs(bx-ax)*.16)); + const d=sameRow + ?`M${ax} ${ay-a.height*.38} C${ax} ${ay-curveLift},${bx} ${by-curveLift},${bx} ${by-b.height*.38}` + :`M${ax} ${ay} C${ax} ${ay+curveLift},${bx} ${by-curveLift},${bx} ${by}`; + const routeId=from+'>'+to; + const p=document.createElementNS('http://www.w3.org/2000/svg','path'); + p.setAttribute('d',d);p.dataset.route=routeId; + const classes=[];if(nextCompletedRoutes.includes(routeId))classes.push('route-done');if(nextActiveRoutes.includes(routeId))classes.push('transfer'); + if(classes.length)p.setAttribute('class',classes.join(' ')); + edgeLayer.appendChild(p); + } + nextBuildSignals(); + nextGraphReady=true; +} +function nextBuildSignals(){ + const layer=$('networkSignals');if(!layer)return;layer.replaceChildren(); + if(window.matchMedia('(prefers-reduced-motion: reduce)').matches)return; + const ns='http://www.w3.org/2000/svg'; + const dur=nextSignalDuration(); + document.querySelectorAll('#networkEdges path.transfer').forEach((path,index)=>{ + const d=path.getAttribute('d'); + [[10,'signal-halo',`${index*.12}s`,.18],[3.7,'signal-core',`${.24+index*.12}s`,1]].forEach(([radius,cls,begin,opacity])=>{ + const circle=document.createElementNS(ns,'circle');circle.setAttribute('r',radius);circle.setAttribute('class',cls);circle.setAttribute('fill','#9ef7ff');circle.setAttribute('opacity',opacity); + const motion=document.createElementNS(ns,'animateMotion');motion.setAttribute('path',d);motion.setAttribute('dur',dur);motion.setAttribute('repeatCount','indefinite');motion.setAttribute('begin',begin); + circle.appendChild(motion);layer.appendChild(circle); + }); + }) +} +function nextRoutesActive(routes=[]){nextActiveRoutes=routes.map(r=>r.join('>'));if(routes.length)nextRememberRoutes(routes);if(!nextGraphReady)nextGraph();else{document.querySelectorAll('#networkEdges path').forEach(p=>{const route=p.dataset.route;const classes=[];if(nextCompletedRoutes.includes(route))classes.push('route-done');if(nextActiveRoutes.includes(route))classes.push('transfer');p.setAttribute('class',classes.join(' '));});nextBuildSignals();}nextSetConductorTransfer(nextActiveRoutes.length>0);} +function nextAgentState(id,state,detail){const el=$('agent-'+id);if(!el)return; + el.classList.toggle('active',state==='active');el.classList.toggle('done',state==='done'||state==='warning'); + el.querySelector('.status').textContent=state==='active'?'ACTIEF':state==='done'?'GEREED':state==='warning'?'LET OP':state==='skipped'?'NIET BESCHIKBAAR':'STANDBY'; + const percent=state==='done'||state==='warning'?100:state==='active'?65:0; + el.querySelector('.track i').style.width=percent+'%';el.querySelector('.percent').textContent=percent+'%'; + if(detail){el.classList.add('has-steps');const wrap=el.querySelector('.agent-steps');wrap.replaceChildren();const line=document.createElement('div');line.className='agent-step '+(state==='active'?'current':state==='skipped'?'skipped':'complete');const mark=document.createElement('span');mark.className='step-mark';mark.textContent=state==='warning'?'!':state==='active'?'◌':'✓';const text=document.createElement('span');text.textContent=detail;line.append(mark,text);wrap.append(line)} +} +function nextFinish(){completed=ids.filter(id=>$('agent-'+id)?.classList.contains('done')).length;$('completed').textContent=String(completed)} +function nextLog(id,message,state='done',routes=[]){nextAgentState(id,state,message);nextRoutesActive(routes);log(nextEl(id)?.querySelector('h3')?.textContent+': '+message);$('flowState').textContent=message;nextFinish()} +function nextResetUI(){nextActiveRoutes=[];nextCompletedRoutes=[];nextSetConductorTransfer(false);const signal=$('networkSignals');if(signal)signal.replaceChildren();nextGraphReady=false;requestAnimationFrame(nextGraph);} +const nextOriginalReset=reset; +reset=function(){nextOriginalReset();nextResetUI();}; +const nextOriginalStart=start; +start=async function(){if(!importedData){return nextOriginalStart()};return nextStartImported()}; +$('startBtn').removeEventListener('click',nextOriginalStart);$('startBtn').addEventListener('click',start); +$('resetBtn').removeEventListener('click',nextOriginalReset);$('resetBtn').addEventListener('click',reset); +async function nextStartImported(){ + if(running)return; + running=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;completed=0;nextActiveRoutes=[];nextCompletedRoutes=[]; + $('logs').replaceChildren();$('conductor').classList.remove('attention');$('conductor').classList.add('running'); + document.querySelector('.orchestra').classList.add('running');$('conductorStatus').textContent='Verwerkingsstappen en overdrachten';$('logStatus').textContent='Actief'; + for(const id of ids){nextAgentState(id,'standby');const e=$('agent-'+id);e.classList.remove('has-steps');e.querySelector('.agent-steps').replaceChildren()} + $('completed').textContent='0';nextGraph(); + const modeDelay=nextWorkflowDelay(); + const t0=performance.now(); + let calculationMs=0; // Houd echte verwerkingstijd gescheiden van de gekozen presentatieduur. + try{ + nextLog('data',`${importedData.all.length.toLocaleString('nl-NL')} bruikbare scans eerder lokaal ingelezen`,'done',[]);await sleep(modeDelay); + // Privacy-agent beschrijft alleen de feitelijke implementatieroute; geen juridische certificering. + nextLog('privacy','Rekenwerk in deze browser; geen AI-API. Privacytoetsing blijft organisatieverantwoordelijkheid.','done',[['conductor','privacy']]);await sleep(modeDelay); + nextAgentState('identity','active','Koppelingen worden in bestaande telroute toegepast');nextRoutesActive([['data','identity']]); + // Zelfde oorspronkelijke rekenfunctie: 2025-historie, pasregels, datums en KPI-definities blijven ongewijzigd. + const calcStart=performance.now(); + const result=calculateImported($('siteSelect').value); + calculationMs+=performance.now()-calcStart; + nextLog('identity',`${result.multiPassClients} cliënten met meerdere passen gesignaleerd`,'done',[['data','identity'],['identity','history']]);await sleep(modeDelay); + if(result.historyLoaded)nextLog('history',`${result.correctedExisting} eerdere cliënten vanuit 2025 herkend`,'done',[['data','history'],['history','analysis']]); + else nextLog('history','Geen 2025-referentie: instroom voorlopig','warning',[['data','history']]); + await sleep(modeDelay); + nextLog('analysis',`${result.visits.toLocaleString('nl-NL')} unieke bezoekdagen en ${result.newIntake} nieuwe instromers berekend`,'done',[['identity','analysis'],['history','analysis']]);await sleep(modeDelay); + nextLog('insight',`${result.months.length} maandperioden beschikbaar; geen automatisch gegenereerde AI-duiding`,'done',[['analysis','insight']]);await sleep(modeDelay); + nextAgentState('verify','active','Onafhankelijke tweede telroute loopt');nextRoutesActive([['analysis','verify']]); + const verifyStart=performance.now(); + result.verification=independentlyVerify(result);importedData.last=result; + calculationMs+=performance.now()-verifyStart; + nextLog('verify',`${result.verification.checks.length} interne controles; ${result.verification.mismatches} telverschillen`,result.verification.mismatches?'warning':'done',[['analysis','verify'],['verify','analysis']]);await sleep(modeDelay); + resetControllerComparison(true);updateControllerContext(); + const renderStart=performance.now(); + renderImported(result); + calculationMs+=performance.now()-renderStart; + nextLog('report','Bestaand dashboard en rapportagepreview bijgewerkt','done',[['verify','report'],['insight','report']]);await sleep(modeDelay); + const concern=result.verification.mismatches>0||!result.historyLoaded; + nextLog('quality',concern?'Concept: controle of historische referentie vraagt aandacht':'Intern gecontroleerd; niet extern gevalideerd',concern?'warning':'done',[['report','quality'],['privacy','quality']]);await sleep(modeDelay); + $('conductor').classList.add('attention');$('conductorStatus').textContent='Verwerking afgerond'; + $('flowState').textContent='Concept beschikbaar · NIET EXTERN GEVALIDEERD';$('logStatus').textContent='Afgerond'; + log(`Echte KPI-berekening, verificatie en dashboardopbouw: ${(calculationMs/1000).toFixed(2)} sec. Totale workflowweergave: ${((performance.now()-t0)/1000).toFixed(1)} sec (exclusief XLSX-import).`); + }catch(err){log('FOUT: '+err.message);$('flowState').textContent='Verwerking mislukt';$('conductorStatus').textContent='Procesfout'} + finally{nextRoutesActive([]);running=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;$('conductor').classList.remove('running');document.querySelector('.orchestra').classList.remove('running')} +} +// Voorbeeldmodus blijft herkenbaar als demonstratie van fictieve data; geen echte berekeningen claimen. +const nextDemoStart=nextOriginalStart; +// Maak ook de vier extra agenten zichtbaar in de voorbeeldmodus zonder de bestaande demonstratiegegevens te veranderen. +const nextOldDemoStarter=start; +start=async function(){if(importedData)return nextStartImported();nextActiveRoutes=[];nextCompletedRoutes=[];nextResetUI();for(const id of ['identity','history','privacy','quality'])nextAgentState(id,'skipped','Voorbeeldmodus · geen broncontrole');return nextDemoStart()}; +$('startBtn').removeEventListener('click',nextOldDemoStarter);$('startBtn').addEventListener('click',start); +window.addEventListener('resize',()=>requestAnimationFrame(nextGraph)); +if(typeof ResizeObserver!=='undefined')new ResizeObserver(()=>requestAnimationFrame(nextGraph)).observe(document.querySelector('.orchestra')); +requestAnimationFrame(nextGraph); + +