Upload index.html
Browse files- index.html +200 -2
index.html
CHANGED
|
@@ -20,12 +20,27 @@
|
|
| 20 |
@keyframes brainBreath {0%,100% {opacity:.78;transform:scale(.94)}50% {opacity:1;transform:scale(1.08)}}
|
| 21 |
@media (prefers-reduced-motion:reduce) { .conductor-icon::before,.conductor-icon::after,.conductor-icon .brain-symbol {animation:none!important} .connections .signal-layer {display:none} }
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
</style></head>
|
| 24 |
<body>
|
| 25 |
<div class="app">
|
| 26 |
<main class="main">
|
| 27 |
<header><div><div class="eyebrow">REAKT / AI ANALYTICS PLATFORM</div><h1>Inloop <span>Intelligence</span> <em>ORCHESTRA</em></h1><p>Een intelligente blik op ontwikkeling, kwaliteit en herstelondersteunende inloop</p></div><div class="header-actions"><span class="pill"><i></i> DEMO MODE</span><span class="clock" id="clock"></span></div></header>
|
| 28 |
<div class="topbar"><div><span class="section-kicker">CONTROL CENTER</span><h2>Multi-Agent Orchestration <span class="live-dot"></span></h2></div><div class="actions"><button id="resetBtn" class="ghost">↺ Reset</button><button id="startBtn" class="primary">▶ Start demo-analyse</button></div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
<section class="orchestra panel">
|
| 30 |
<div class="mesh"></div><div class="conductor-wrap"><div class="conductor" id="conductor"><div class="conductor-glow"></div><div class="conductor-icon" aria-hidden="true"><svg class="brain-symbol" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5c-2.7-3.2-7.4-.9-7.1 2.4C2 9 2 13.6 5 15.2c-.7 4.4 4.9 6.8 7 2.6M12 5c2.7-3.2 7.4-.9 7.1 2.4 2.9 1.6 2.9 6.2-.1 7.8.7 4.4-4.9 6.8-7 2.6M12 5v13"/><path d="M6.6 10.1 9 12v3M17.4 10.1 15 12v3"/><circle cx="9" cy="15.5" r=".9" fill="currentColor" stroke="none"/><circle cx="15" cy="15.5" r=".9" fill="currentColor" stroke="none"/></svg></div><div><small>MASTER ORCHESTRATOR</small><strong>THE CONDUCTOR</strong><span id="conductorStatus">Gereed voor analyse</span></div><div class="conductor-indicator"></div></div></div>
|
| 31 |
<svg class="connections" viewBox="0 0 1000 128" preserveAspectRatio="none" aria-hidden="true"><path class="stem" d="M500 0 V30 H100 V90 M500 30 H300 V90 M500 30 V90 M500 30 H700 V90 M500 30 H900 V90"/><path class="flowline" d="M500 0 V30 H100 V90 M500 30 H300 V90 M500 30 V90 M500 30 H700 V90 M500 30 H900 V90"/><g class="signal-layer" aria-hidden="true"></g></svg>
|
|
@@ -48,7 +63,7 @@
|
|
| 48 |
<aside class="right-content"><section class="panel chatpanel"><div class="panel-heading"><div><span class="section-kicker">MANAGEMENT ASSISTENT</span><h3><span class="chat-icon">✧</span> Vraag het aan Inloop AI</h3></div><span class="demo-badge">DEMO</span></div><div class="chat-messages" id="messages"><div class="message assistant"><small>INLOOP AI · DEMONSTRATIE</small><p>Welkom! Start de demo-analyse en stel daarna een vraag over de fictieve locatiecijfers.</p></div></div><div class="quick-questions"><button data-q="Welke locatie groeit het sterkst?">Grootste groei?</button><button data-q="Zijn de cijfers gevalideerd?">Validatiestatus?</button><button data-q="Maak een managementsamenvatting">Samenvatting?</button></div><form id="chatForm" class="chat-form"><input id="chatInput" autocomplete="off" placeholder="Stel een vraag over de demo..." aria-label="Vraag aan Inloop AI"><button aria-label="Verstuur vraag">➤</button></form><div class="chat-disclaimer">Vooraf ingestelde demo-antwoorden · geen echte AI</div></section>
|
| 49 |
<section class="panel preview"><div class="panel-heading"><div><span class="section-kicker">OUTPUT</span><h3>Infographic preview</h3></div><span class="warn-label">CONCEPT</span></div><div class="paper"><div class="paper-top"><strong>REAKT<span>.</span></strong><small>INLOOPMONITOR / Q3 2026</small></div><h4>Inloop in beeld</h4><p>Illustratieve managementrapportage</p><div class="paper-kpis"><div><strong>12.480</strong><small>Bezoeken</small></div><div><strong>1.286</strong><small>Uniek</small></div><div><strong>164</strong><small>Instroom</small></div></div><div class="paper-bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div><div class="paper-watermark">FICTIEVE DATA · NIET GEVALIDEERD</div></div><button class="export" id="exportBtn">↓ Print demo-infographic</button></section></aside>
|
| 50 |
</div>
|
| 51 |
-
<footer>REAKT INLOOP INTELLIGENCE <span>ORCHESTRA PROTOTYPE · FICTIEVE
|
| 52 |
</main>
|
| 53 |
</div><script type="application/json" id="orchestra-demo-data">{"mode": "DEMO", "period": "Q3 2026 · fictief", "kpis": [{"label": "Bezoeken", "value": "12.480", "change": "+8,4%"}, {"label": "Unieke bezoekers", "value": "1.286", "change": "+3,2%"}, {"label": "Nieuwe instroom", "value": "164", "change": "+5,1%"}, {"label": "Verificatie", "value": "4 / 5", "change": "1 afwijking"}], "locations": [{"name": "Soeda", "visits": [350, 395, 428], "unique": 121}, {"name": "Rietdijk", "visits": [285, 305, 334], "unique": 108}, {"name": "Vredehof", "visits": [245, 268, 294], "unique": 93}, {"name": "Gouda", "visits": [320, 343, 365], "unique": 115}], "checks": [{"label": "Bezoeken", "ours": 2845, "shadow": 2845}, {"label": "Unieke bezoekers", "ours": 386, "shadow": 381}, {"label": "Nieuwe instroom", "ours": 42, "shadow": 42}, {"label": "Bezoeken vorig jaar", "ours": 2610, "shadow": 2610}, {"label": "Actieve locaties", "ours": 4, "shadow": 4}], "steps": [{"agent": "data", "text": "Demo-gegevens ingelezen en structuur gecontroleerd"}, {"agent": "analysis", "text": "Maandtrends en locatievergelijkingen berekend"}, {"agent": "insight", "text": "Opvallende ontwikkelingen samengevat (demo)"}, {"agent": "verify", "text": "Schaduwberekeningen vergeleken: 1 afwijking"}, {"agent": "report", "text": "Conceptinfographic voorbereid · NIET GEVALIDEERD"}]}</script>
|
| 54 |
<script>
|
|
@@ -85,10 +100,193 @@ function signalFor(agent){
|
|
| 85 |
}
|
| 86 |
}
|
| 87 |
function reset(){if(running)return;clearSignal();$('conductor').classList.remove('attention');completed=0;$('completed').textContent='0';$('conductorStatus').textContent='Gereed voor analyse';$('flowState').textContent='Wacht op startcommando';$('logStatus').textContent='Stand-by';$('logs').innerHTML='<div class="logitem muted">◌ Klik op ‘Start demo-analyse’ om de workflow te bekijken.</div>';$('orchestra-root');document.querySelector('.orchestra').classList.remove('running');$('conductor').classList.remove('running');ids.forEach(id=>{let e=$('agent-'+id);e.classList.remove('active','done');e.querySelector('.status').textContent='STANDBY';e.querySelector('.track i').style.width='0%';e.querySelector('.percent').textContent='0%'});}
|
| 88 |
-
async function start(){if(running)return;clearSignal();$('conductor').classList.remove('attention');running=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;completed=0;ids.forEach(id=>{const e=$('agent-'+id);e.classList.remove('done','active');e.querySelector('.status').textContent='STANDBY';e.querySelector('.track i').style.width='0%';e.querySelector('.percent').textContent='0%'});$('logs').innerHTML='';$('completed').textContent='0';$('conductor').classList.add('running');document.querySelector('.orchestra').classList.add('running');$('conductorStatus').textContent='Workflow wordt aangestuurd';$('logStatus').textContent='Actief';log('Dirigent start de gesimuleerde workflow');try{const response=await Promise.resolve({ok:true,json:async()=>JSON.parse(document.getElementById('orchestra-demo-data').textContent)});if(!response.ok)throw Error('API reageert niet');demoData=await response.json();renderKPIs(demoData);renderChecks(demoData);drawChart(demoData);for(let i=0;i<demoData.steps.length;i++){const step=demoData.steps[i];const e=$('agent-'+step.agent);signalFor(step.agent);e.classList.add('active');e.querySelector('.status').textContent='ACTIEF';$('flowState').textContent=step.text;log('Start: '+e.querySelector('h3').textContent);for(let p=0;p<=100;p+=5){e.querySelector('.track i').style.width=p+'%';e.querySelector('.percent').textContent=p+'%';await sleep(36);}clearSignal();e.classList.remove('active');e.classList.add('done');if(step.agent==='verify' && demoData.checks.some(c=>c.ours!==c.shadow))$('conductor').classList.add('attention');e.querySelector('.status').textContent=step.agent==='verify'?'AFWIJKING':'GEREED';completed++;$('completed').textContent=String(completed);log(step.text);await sleep(280)}$('conductorStatus').textContent='Analyse voltooid · controle vereist';$('flowState').textContent='Concept gereed · NIET GEVALIDEERD';$('logStatus').textContent='Afgerond';log('Eindstatus: concept beschikbaar, niet vrijgegeven');}catch(e){log('FOUT: '+e.message);$('flowState').textContent='Verwerking mislukt';$('conductorStatus').textContent='Fout gedetecteerd';}finally{clearSignal();running=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;$('conductor').classList.remove('running');document.querySelector('.orchestra').classList.remove('running')}}
|
| 89 |
function reply(question){const q=question.toLowerCase();if(!completed)return 'Start eerst de demo-analyse. Daarna kan ik vragen beantwoorden over de fictieve cijfers.';if(/valid|wouter|controle|betrouw|verschil|akkoord/.test(q))return 'In deze fictieve demo wijken de unieke bezoekers af: monitor 386 tegenover schaduwberekening 381, een verschil van 5. De oorzaak is niet vastgesteld. Daarom blijft de infographic NIET GEVALIDEERD.';if(/groei|stijg|locatie|soeda|rietdijk|vredehof|gouda/.test(q))return 'In de fictieve reeks juli–september stijgt Soeda van 350 naar 428 bezoeken (+22,3%). Rietdijk stijgt van 285 naar 334 (+17,2%), Vredehof van 245 naar 294 (+20,0%) en Gouda van 320 naar 365 (+14,1%). Soeda heeft de grootste relatieve groei.';if(/samenvat|rapport|infographic|management/.test(q))return 'Conceptmanagementsamenvatting (fictief): alle vier getoonde locaties laten in juli–september groei in bezoeken zien. Er is een afwijking van 5 unieke bezoekers bij de schaduwcontrole. Publicatie is niet toegestaan totdat dit verschil is onderzocht en beoordeeld.';if(/bezoek|uniek|instroom|kwartaal|cijfer/.test(q))return 'De fictieve KPI-kaarten tonen 12.480 bezoeken, 1.286 unieke bezoekers en 164 nieuwe instromers. Deze totaalcijfers zijn illustratief en vormen geen optelsom van de vier voorbeeldlocaties in de grafiek.';return 'Dit is een demo-chat zonder AI-API. Ik kan alleen vooraf gedefinieerde vragen over de fictieve locatiegroei, KPI’s, verificatie en managementsamenvatting beantwoorden.'}
|
| 90 |
function addMessage(role,text){const e=document.createElement('div');e.className='message '+role;if(role==='assistant'){const s=document.createElement('small');s.textContent='INLOOP AI · DEMONSTRATIE';e.appendChild(s)}const p=document.createElement('p');p.textContent=text;e.appendChild(p);$('messages').appendChild(e);$('messages').scrollTop=$('messages').scrollHeight;}
|
| 91 |
function ask(q){if(!q.trim())return;addMessage('user',q);addMessage('assistant',reply(q));}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
$('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',()=>window.print());
|
| 93 |
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'));
|
| 94 |
|
|
|
|
| 20 |
@keyframes brainBreath {0%,100% {opacity:.78;transform:scale(.94)}50% {opacity:1;transform:scale(1.08)}}
|
| 21 |
@media (prefers-reduced-motion:reduce) { .conductor-icon::before,.conductor-icon::after,.conductor-icon .brain-symbol {animation:none!important} .connections .signal-layer {display:none} }
|
| 22 |
|
| 23 |
+
|
| 24 |
+
/* v0.4 – compacte invoerbalk; bestaande dashboardstijl bewust ongewijzigd */
|
| 25 |
+
.import-panel{display:flex;align-items:center;gap:13px;flex-wrap:wrap;padding:12px 17px;margin-bottom:13px;border-color:#305278;background:linear-gradient(135deg,#10263c,#0d192f)}
|
| 26 |
+
.import-intro{display:flex;flex-direction:column;gap:4px;flex:1;min-width:250px}.import-intro strong{font-size:14px}.import-intro small{font-size:10px;color:#9bb0cd}
|
| 27 |
+
.import-file-label{padding:10px 14px;border:1px solid #3666a4;border-radius:9px;background:#162c49;color:#dcf1ff;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap}
|
| 28 |
+
.import-file-label span{color:#53d9bf;font-size:16px;margin-right:7px}
|
| 29 |
+
#scanFile{display:none}.site-label{font-size:11px;color:#a6bad8}#siteSelect{background:#132947;border:1px solid #325172;border-radius:8px;color:#eaf2ff;max-width:185px;padding:9px;font:inherit;font-size:12px}
|
| 30 |
+
#siteSelect:disabled{opacity:.55}.import-state{color:#78e0c3;font-size:11px;max-width:210px;line-height:1.4}
|
| 31 |
+
@media(max-width:760px){.import-panel{align-items:start}.import-intro{flex-basis:100%}.import-state{flex-basis:100%;max-width:none}}
|
| 32 |
</style></head>
|
| 33 |
<body>
|
| 34 |
<div class="app">
|
| 35 |
<main class="main">
|
| 36 |
<header><div><div class="eyebrow">REAKT / AI ANALYTICS PLATFORM</div><h1>Inloop <span>Intelligence</span> <em>ORCHESTRA</em></h1><p>Een intelligente blik op ontwikkeling, kwaliteit en herstelondersteunende inloop</p></div><div class="header-actions"><span class="pill"><i></i> DEMO MODE</span><span class="clock" id="clock"></span></div></header>
|
| 37 |
<div class="topbar"><div><span class="section-kicker">CONTROL CENTER</span><h2>Multi-Agent Orchestration <span class="live-dot"></span></h2></div><div class="actions"><button id="resetBtn" class="ghost">↺ Reset</button><button id="startBtn" class="primary">▶ Start demo-analyse</button></div></div>
|
| 38 |
+
<section class="import-panel panel" aria-label="Excel-proef met fictieve gegevens">
|
| 39 |
+
<div class="import-intro"><span class="section-kicker">VOLGENDE STAP · EXCEL-PROEF</span><strong>Lokale scandata-test</strong><small>Alleen fictieve of volledig geanonimiseerde testbestanden · geen upload naar een server</small></div>
|
| 40 |
+
<label class="import-file-label" for="scanFile"><span>▤</span> Kies testbestand (.xlsx)</label><input type="file" id="scanFile" accept=".xlsx" />
|
| 41 |
+
<label class="site-label" for="siteSelect">Locatie</label><select id="siteSelect" disabled><option value="*">Alle locaties</option></select>
|
| 42 |
+
<span id="importState" class="import-state">Demo staat klaar</span>
|
| 43 |
+
</section>
|
| 44 |
<section class="orchestra panel">
|
| 45 |
<div class="mesh"></div><div class="conductor-wrap"><div class="conductor" id="conductor"><div class="conductor-glow"></div><div class="conductor-icon" aria-hidden="true"><svg class="brain-symbol" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.55" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5c-2.7-3.2-7.4-.9-7.1 2.4C2 9 2 13.6 5 15.2c-.7 4.4 4.9 6.8 7 2.6M12 5c2.7-3.2 7.4-.9 7.1 2.4 2.9 1.6 2.9 6.2-.1 7.8.7 4.4-4.9 6.8-7 2.6M12 5v13"/><path d="M6.6 10.1 9 12v3M17.4 10.1 15 12v3"/><circle cx="9" cy="15.5" r=".9" fill="currentColor" stroke="none"/><circle cx="15" cy="15.5" r=".9" fill="currentColor" stroke="none"/></svg></div><div><small>MASTER ORCHESTRATOR</small><strong>THE CONDUCTOR</strong><span id="conductorStatus">Gereed voor analyse</span></div><div class="conductor-indicator"></div></div></div>
|
| 46 |
<svg class="connections" viewBox="0 0 1000 128" preserveAspectRatio="none" aria-hidden="true"><path class="stem" d="M500 0 V30 H100 V90 M500 30 H300 V90 M500 30 V90 M500 30 H700 V90 M500 30 H900 V90"/><path class="flowline" d="M500 0 V30 H100 V90 M500 30 H300 V90 M500 30 V90 M500 30 H700 V90 M500 30 H900 V90"/><g class="signal-layer" aria-hidden="true"></g></svg>
|
|
|
|
| 63 |
<aside class="right-content"><section class="panel chatpanel"><div class="panel-heading"><div><span class="section-kicker">MANAGEMENT ASSISTENT</span><h3><span class="chat-icon">✧</span> Vraag het aan Inloop AI</h3></div><span class="demo-badge">DEMO</span></div><div class="chat-messages" id="messages"><div class="message assistant"><small>INLOOP AI · DEMONSTRATIE</small><p>Welkom! Start de demo-analyse en stel daarna een vraag over de fictieve locatiecijfers.</p></div></div><div class="quick-questions"><button data-q="Welke locatie groeit het sterkst?">Grootste groei?</button><button data-q="Zijn de cijfers gevalideerd?">Validatiestatus?</button><button data-q="Maak een managementsamenvatting">Samenvatting?</button></div><form id="chatForm" class="chat-form"><input id="chatInput" autocomplete="off" placeholder="Stel een vraag over de demo..." aria-label="Vraag aan Inloop AI"><button aria-label="Verstuur vraag">➤</button></form><div class="chat-disclaimer">Vooraf ingestelde demo-antwoorden · geen echte AI</div></section>
|
| 64 |
<section class="panel preview"><div class="panel-heading"><div><span class="section-kicker">OUTPUT</span><h3>Infographic preview</h3></div><span class="warn-label">CONCEPT</span></div><div class="paper"><div class="paper-top"><strong>REAKT<span>.</span></strong><small>INLOOPMONITOR / Q3 2026</small></div><h4>Inloop in beeld</h4><p>Illustratieve managementrapportage</p><div class="paper-kpis"><div><strong>12.480</strong><small>Bezoeken</small></div><div><strong>1.286</strong><small>Uniek</small></div><div><strong>164</strong><small>Instroom</small></div></div><div class="paper-bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div><div class="paper-watermark">FICTIEVE DATA · NIET GEVALIDEERD</div></div><button class="export" id="exportBtn">↓ Print demo-infographic</button></section></aside>
|
| 65 |
</div>
|
| 66 |
+
<footer>REAKT INLOOP INTELLIGENCE <span>ORCHESTRA PROTOTYPE · ALLEEN FICTIEVE TESTGEGEVENS · GEEN PRODUCTIEOMGEVING</span></footer>
|
| 67 |
</main>
|
| 68 |
</div><script type="application/json" id="orchestra-demo-data">{"mode": "DEMO", "period": "Q3 2026 · fictief", "kpis": [{"label": "Bezoeken", "value": "12.480", "change": "+8,4%"}, {"label": "Unieke bezoekers", "value": "1.286", "change": "+3,2%"}, {"label": "Nieuwe instroom", "value": "164", "change": "+5,1%"}, {"label": "Verificatie", "value": "4 / 5", "change": "1 afwijking"}], "locations": [{"name": "Soeda", "visits": [350, 395, 428], "unique": 121}, {"name": "Rietdijk", "visits": [285, 305, 334], "unique": 108}, {"name": "Vredehof", "visits": [245, 268, 294], "unique": 93}, {"name": "Gouda", "visits": [320, 343, 365], "unique": 115}], "checks": [{"label": "Bezoeken", "ours": 2845, "shadow": 2845}, {"label": "Unieke bezoekers", "ours": 386, "shadow": 381}, {"label": "Nieuwe instroom", "ours": 42, "shadow": 42}, {"label": "Bezoeken vorig jaar", "ours": 2610, "shadow": 2610}, {"label": "Actieve locaties", "ours": 4, "shadow": 4}], "steps": [{"agent": "data", "text": "Demo-gegevens ingelezen en structuur gecontroleerd"}, {"agent": "analysis", "text": "Maandtrends en locatievergelijkingen berekend"}, {"agent": "insight", "text": "Opvallende ontwikkelingen samengevat (demo)"}, {"agent": "verify", "text": "Schaduwberekeningen vergeleken: 1 afwijking"}, {"agent": "report", "text": "Conceptinfographic voorbereid · NIET GEVALIDEERD"}]}</script>
|
| 69 |
<script>
|
|
|
|
| 100 |
}
|
| 101 |
}
|
| 102 |
function reset(){if(running)return;clearSignal();$('conductor').classList.remove('attention');completed=0;$('completed').textContent='0';$('conductorStatus').textContent='Gereed voor analyse';$('flowState').textContent='Wacht op startcommando';$('logStatus').textContent='Stand-by';$('logs').innerHTML='<div class="logitem muted">◌ Klik op ‘Start demo-analyse’ om de workflow te bekijken.</div>';$('orchestra-root');document.querySelector('.orchestra').classList.remove('running');$('conductor').classList.remove('running');ids.forEach(id=>{let e=$('agent-'+id);e.classList.remove('active','done');e.querySelector('.status').textContent='STANDBY';e.querySelector('.track i').style.width='0%';e.querySelector('.percent').textContent='0%'});}
|
| 103 |
+
async function start(){if(importedData)return startImported();if(running)return;clearSignal();$('conductor').classList.remove('attention');running=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;completed=0;ids.forEach(id=>{const e=$('agent-'+id);e.classList.remove('done','active');e.querySelector('.status').textContent='STANDBY';e.querySelector('.track i').style.width='0%';e.querySelector('.percent').textContent='0%'});$('logs').innerHTML='';$('completed').textContent='0';$('conductor').classList.add('running');document.querySelector('.orchestra').classList.add('running');$('conductorStatus').textContent='Workflow wordt aangestuurd';$('logStatus').textContent='Actief';log('Dirigent start de gesimuleerde workflow');try{const response=await Promise.resolve({ok:true,json:async()=>JSON.parse(document.getElementById('orchestra-demo-data').textContent)});if(!response.ok)throw Error('API reageert niet');demoData=await response.json();renderKPIs(demoData);renderChecks(demoData);drawChart(demoData);for(let i=0;i<demoData.steps.length;i++){const step=demoData.steps[i];const e=$('agent-'+step.agent);signalFor(step.agent);e.classList.add('active');e.querySelector('.status').textContent='ACTIEF';$('flowState').textContent=step.text;log('Start: '+e.querySelector('h3').textContent);for(let p=0;p<=100;p+=5){e.querySelector('.track i').style.width=p+'%';e.querySelector('.percent').textContent=p+'%';await sleep(36);}clearSignal();e.classList.remove('active');e.classList.add('done');if(step.agent==='verify' && demoData.checks.some(c=>c.ours!==c.shadow))$('conductor').classList.add('attention');e.querySelector('.status').textContent=step.agent==='verify'?'AFWIJKING':'GEREED';completed++;$('completed').textContent=String(completed);log(step.text);await sleep(280)}$('conductorStatus').textContent='Analyse voltooid · controle vereist';$('flowState').textContent='Concept gereed · NIET GEVALIDEERD';$('logStatus').textContent='Afgerond';log('Eindstatus: concept beschikbaar, niet vrijgegeven');}catch(e){log('FOUT: '+e.message);$('flowState').textContent='Verwerking mislukt';$('conductorStatus').textContent='Fout gedetecteerd';}finally{clearSignal();running=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;$('conductor').classList.remove('running');document.querySelector('.orchestra').classList.remove('running')}}
|
| 104 |
function reply(question){const q=question.toLowerCase();if(!completed)return 'Start eerst de demo-analyse. Daarna kan ik vragen beantwoorden over de fictieve cijfers.';if(/valid|wouter|controle|betrouw|verschil|akkoord/.test(q))return 'In deze fictieve demo wijken de unieke bezoekers af: monitor 386 tegenover schaduwberekening 381, een verschil van 5. De oorzaak is niet vastgesteld. Daarom blijft de infographic NIET GEVALIDEERD.';if(/groei|stijg|locatie|soeda|rietdijk|vredehof|gouda/.test(q))return 'In de fictieve reeks juli–september stijgt Soeda van 350 naar 428 bezoeken (+22,3%). Rietdijk stijgt van 285 naar 334 (+17,2%), Vredehof van 245 naar 294 (+20,0%) en Gouda van 320 naar 365 (+14,1%). Soeda heeft de grootste relatieve groei.';if(/samenvat|rapport|infographic|management/.test(q))return 'Conceptmanagementsamenvatting (fictief): alle vier getoonde locaties laten in juli–september groei in bezoeken zien. Er is een afwijking van 5 unieke bezoekers bij de schaduwcontrole. Publicatie is niet toegestaan totdat dit verschil is onderzocht en beoordeeld.';if(/bezoek|uniek|instroom|kwartaal|cijfer/.test(q))return 'De fictieve KPI-kaarten tonen 12.480 bezoeken, 1.286 unieke bezoekers en 164 nieuwe instromers. Deze totaalcijfers zijn illustratief en vormen geen optelsom van de vier voorbeeldlocaties in de grafiek.';return 'Dit is een demo-chat zonder AI-API. Ik kan alleen vooraf gedefinieerde vragen over de fictieve locatiegroei, KPI’s, verificatie en managementsamenvatting beantwoorden.'}
|
| 105 |
function addMessage(role,text){const e=document.createElement('div');e.className='message '+role;if(role==='assistant'){const s=document.createElement('small');s.textContent='INLOOP AI · DEMONSTRATIE';e.appendChild(s)}const p=document.createElement('p');p.textContent=text;e.appendChild(p);$('messages').appendChild(e);$('messages').scrollTop=$('messages').scrollHeight;}
|
| 106 |
function ask(q){if(!q.trim())return;addMessage('user',q);addMessage('assistant',reply(q));}
|
| 107 |
+
// v0.4: veilige browser-lokale Excel-proef, uitsluitend met fictieve / geanonimiseerde voorbeelden.
|
| 108 |
+
// Er vindt GEEN netwerkverzoek, bestandsoverdracht of externe AI-aanroep plaats.
|
| 109 |
+
let importedData=null;
|
| 110 |
+
const excelLimit=2*1024*1024; // om echte grote (en mogelijk cliëntgevoelige) dumps nog niet te verwerken
|
| 111 |
+
const excelNS='http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
| 112 |
+
const decoder=new TextDecoder('utf-8');
|
| 113 |
+
function bytesText(a){return decoder.decode(a)}
|
| 114 |
+
function zipDirectory(buffer){
|
| 115 |
+
const bytes=new Uint8Array(buffer), view=new DataView(buffer);
|
| 116 |
+
let eocd=-1;
|
| 117 |
+
for(let i=bytes.length-22;i>=Math.max(0,bytes.length-65557);i--){if(view.getUint32(i,true)===0x06054b50){eocd=i;break}}
|
| 118 |
+
if(eocd<0)throw Error('Geen geldig XLSX-/ZIP-bestand');
|
| 119 |
+
const count=view.getUint16(eocd+10,true), offset=view.getUint32(eocd+16,true);
|
| 120 |
+
if(count>1000)throw Error('Dit testbestand heeft te veel onderdelen');
|
| 121 |
+
const entries=new Map();let pos=offset;
|
| 122 |
+
for(let i=0;i<count;i++){
|
| 123 |
+
if(view.getUint32(pos,true)!==0x02014b50)throw Error('ZIP-structuur ongeldig');
|
| 124 |
+
const method=view.getUint16(pos+10,true), size=view.getUint32(pos+20,true),len=view.getUint16(pos+28,true),extra=view.getUint16(pos+30,true),comment=view.getUint16(pos+32,true),local=view.getUint32(pos+42,true);
|
| 125 |
+
const name=bytesText(bytes.subarray(pos+46,pos+46+len));
|
| 126 |
+
if(name.startsWith('xl/'))entries.set(name,{method,size,local});
|
| 127 |
+
pos+=46+len+extra+comment;
|
| 128 |
+
}
|
| 129 |
+
return {view,bytes,entries};
|
| 130 |
+
}
|
| 131 |
+
async function xmlEntry(zip,name){
|
| 132 |
+
const entry=zip.entries.get(name);if(!entry)return '';
|
| 133 |
+
const {view,bytes}=zip, off=entry.local;
|
| 134 |
+
if(view.getUint32(off,true)!==0x04034b50)throw Error('ZIP-bestandskop ongeldig');
|
| 135 |
+
const start=off+30+view.getUint16(off+26,true)+view.getUint16(off+28,true);
|
| 136 |
+
const compressed=bytes.slice(start,start+entry.size);
|
| 137 |
+
if(entry.method===0)return bytesText(compressed);
|
| 138 |
+
if(entry.method!==8)throw Error('Niet-ondersteunde Excel-compressie');
|
| 139 |
+
if(typeof DecompressionStream!=='function')throw Error('Deze browser ondersteunt lokale Excel-decompressie niet');
|
| 140 |
+
return await new Response(new Blob([compressed]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).text();
|
| 141 |
+
}
|
| 142 |
+
function xmlDoc(str){
|
| 143 |
+
const doc=new DOMParser().parseFromString(str,'application/xml');
|
| 144 |
+
if(doc.getElementsByTagName('parsererror').length)throw Error('Excel-XML niet leesbaar');return doc;
|
| 145 |
+
}
|
| 146 |
+
function cellsToDict(row,shared){
|
| 147 |
+
const values={};
|
| 148 |
+
for(const c of row.getElementsByTagNameNS(excelNS,'c')){
|
| 149 |
+
const key=(c.getAttribute('r')||'').match(/^[A-Z]+/);if(!key)continue;
|
| 150 |
+
const v=c.getElementsByTagNameNS(excelNS,'v')[0],is=c.getElementsByTagNameNS(excelNS,'is')[0];let value=v?.textContent??'';
|
| 151 |
+
if(c.getAttribute('t')==='s')value=shared[Number(value)]??'';
|
| 152 |
+
else if(c.getAttribute('t')==='inlineStr')value=is?.textContent??'';
|
| 153 |
+
values[key[0]]=value;
|
| 154 |
+
}return values;
|
| 155 |
+
}
|
| 156 |
+
function scanDate(v){
|
| 157 |
+
if(v===''||v===undefined||v===null)return '';
|
| 158 |
+
const numeric=Number(v);
|
| 159 |
+
if(v!==''&&Number.isFinite(numeric)&&numeric>30000&&numeric<80000){
|
| 160 |
+
const d=new Date(Date.UTC(1899,11,30)+Math.floor(numeric)*86400000);
|
| 161 |
+
return d.toISOString().slice(0,10);
|
| 162 |
+
}
|
| 163 |
+
const str=String(v).trim(),iso=str.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
| 164 |
+
if(iso)return `${iso[1]}-${iso[2]}-${iso[3]}`;
|
| 165 |
+
const nl=str.match(/^(\d{1,2})[-/](\d{1,2})[-/](\d{4})/);
|
| 166 |
+
if(nl)return `${nl[3]}-${nl[2].padStart(2,'0')}-${nl[1].padStart(2,'0')}`;
|
| 167 |
+
return '';
|
| 168 |
+
}
|
| 169 |
+
async function readExcelLocal(file){
|
| 170 |
+
if(!file.name.toLowerCase().endsWith('.xlsx'))throw Error('Kies een XLSX-testbestand');
|
| 171 |
+
if(file.size>excelLimit)throw Error('Dit is een grote productiedump. Gebruik voorlopig uitsluitend het bijgeleverde kleine fictieve testbestand.');
|
| 172 |
+
const buf=await file.arrayBuffer(),zip=zipDirectory(buf);
|
| 173 |
+
const shared=[];
|
| 174 |
+
const sharedXML=await xmlEntry(zip,'xl/sharedStrings.xml');
|
| 175 |
+
if(sharedXML){
|
| 176 |
+
const doc=xmlDoc(sharedXML);
|
| 177 |
+
for(const si of doc.getElementsByTagNameNS(excelNS,'si'))shared.push(si.textContent??'');
|
| 178 |
+
}
|
| 179 |
+
const sheetXML=await xmlEntry(zip,'xl/worksheets/sheet1.xml');
|
| 180 |
+
if(!sheetXML)throw Error('Werkblad Data dump niet gevonden');
|
| 181 |
+
const doc=xmlDoc(sheetXML),rows=doc.getElementsByTagNameNS(excelNS,'row');
|
| 182 |
+
if(rows.length<2)throw Error('Geen scanregels gevonden');
|
| 183 |
+
if(rows.length>18000)throw Error('Voor deze eerste proef maximaal 18.000 testregels');
|
| 184 |
+
const header=cellsToDict(rows[0],shared),lookup={};
|
| 185 |
+
for(const [col,headerText] of Object.entries(header))lookup[String(headerText).toLowerCase().trim()]=col;
|
| 186 |
+
for(const mandatory of ['scantijd','locatie','clientnummer'])if(!lookup[mandatory])throw Error('Verplichte kolom ontbreekt: '+mandatory);
|
| 187 |
+
const all=[],invalid={datum:0,locatie:0,client:0};
|
| 188 |
+
for(let i=1;i<rows.length;i++){
|
| 189 |
+
const c=cellsToDict(rows[i],shared),site=String(c[lookup.locatie]??'').trim(),id=String(c[lookup.clientnummer]??'').trim(),date=scanDate(c[lookup.scantijd]);
|
| 190 |
+
if(!date)invalid.datum++;
|
| 191 |
+
if(!site)invalid.locatie++;
|
| 192 |
+
if(!id)invalid.client++;
|
| 193 |
+
if(!site||!id||!date)continue;
|
| 194 |
+
all.push({date,site,id});
|
| 195 |
+
}
|
| 196 |
+
if(!all.length)throw Error('Geen bruikbare scanregels');
|
| 197 |
+
const months=[...new Set(all.map(r=>r.date.slice(0,7)))].sort();
|
| 198 |
+
const sites=[...new Set(all.map(r=>r.site))].sort((a,b)=>a.localeCompare(b,'nl'));
|
| 199 |
+
return {all,sites,months,rawCount:rows.length-1,invalid};
|
| 200 |
+
}
|
| 201 |
+
function calculateImported(site='*'){
|
| 202 |
+
const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),monthSite=new Map();
|
| 203 |
+
for(const r of all){
|
| 204 |
+
const k=r.site+'|'+r.date+'|'+r.id;
|
| 205 |
+
if(keyset.has(k))continue;
|
| 206 |
+
keyset.add(k);ids.add(r.id);
|
| 207 |
+
const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
|
| 208 |
+
}
|
| 209 |
+
const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
|
| 210 |
+
const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
|
| 211 |
+
return {site,rows:all.length,visits:keyset.size,unique:ids.size,duplicates:all.length-keyset.size,months:chosen,locations,invalid:importedData.invalid};
|
| 212 |
+
}
|
| 213 |
+
function drawImportedChart(result){
|
| 214 |
+
const cvs=$('chart'),ctx=cvs.getContext('2d'),w=cvs.width,h=cvs.height;
|
| 215 |
+
ctx.clearRect(0,0,w,h);ctx.font='13px Segoe UI';
|
| 216 |
+
const series=result.locations.slice(0,4),max=Math.max(10,...series.flatMap(x=>x.visits))*1.15;
|
| 217 |
+
ctx.strokeStyle='#29405a';ctx.fillStyle='#8aa4c3';ctx.textAlign='left';
|
| 218 |
+
for(let i=0;i<=4;i++){const y=25+i*45;ctx.beginPath();ctx.moveTo(44,y);ctx.lineTo(w-14,y);ctx.stroke();ctx.fillText(String(Math.round(max*(4-i)/4)),7,y+4)}
|
| 219 |
+
const colors=['#3ee1b6','#ffc66a','#5b9cff','#ad88ff'];
|
| 220 |
+
series.forEach((loc,li)=>{
|
| 221 |
+
ctx.strokeStyle=colors[li];ctx.lineWidth=3;ctx.beginPath();
|
| 222 |
+
loc.visits.forEach((v,j)=>{const x=90+j*235,y=205-(v/max)*180;j===0?ctx.moveTo(x,y):ctx.lineTo(x,y)});ctx.stroke();
|
| 223 |
+
loc.visits.forEach((v,j)=>{const x=90+j*235,y=205-(v/max)*180;ctx.fillStyle=colors[li];ctx.beginPath();ctx.arc(x,y,4,0,Math.PI*2);ctx.fill()});
|
| 224 |
+
});ctx.fillStyle='#a6b9d0';ctx.textAlign='center';
|
| 225 |
+
result.months.forEach((m,i)=>ctx.fillText(m,90+i*235,239));
|
| 226 |
+
const legend=document.querySelector('.legend');
|
| 227 |
+
legend.innerHTML=series.map((loc,i)=>`<span><i style="background:${colors[i]}"></i> ${escapeHTML(loc.name)}</span>`).join('');
|
| 228 |
+
document.querySelector('.chart-panel .subtle').textContent=result.months.join(' · ')+(result.locations.length>4?' · 4 van '+result.locations.length+' locaties':'');
|
| 229 |
+
}
|
| 230 |
+
function renderImported(result){
|
| 231 |
+
const num=n=>n.toLocaleString('nl-NL');
|
| 232 |
+
const kpis=[{label:'Bezoeken (voorlopig)',value:num(result.visits),change:'Unieke cliënt / dag / locatie'},{label:'Unieke cliëntnummers',value:num(result.unique),change:'Binnen geselecteerde periode'},{label:'Scanregels',value:num(result.rows),change:`${num(result.duplicates)} herhaalde scans`},{label:'Verificatie',value:'—',change:'Extern niet uitgevoerd'}];
|
| 233 |
+
renderKPIs({kpis});drawImportedChart(result);
|
| 234 |
+
$('checks').innerHTML='<div class="check-row"><span>Dubbele scans (voorlopige regel)</span><span>'+num(result.duplicates)+'</span></div><div class="check-row"><span>Unieke cliëntnummers</span><span>'+num(result.unique)+'</span></div><div class="check-row"><span>Schaduwberekening Businesscontroller</span><span>NIET AANGELEVERD</span></div>';
|
| 235 |
+
document.querySelector('.verification .warning').textContent='⚠ Proefberekening. Definities en externe controle nog niet gevalideerd.';
|
| 236 |
+
document.querySelector('.paper-watermark').textContent='FICTIEVE TESTGEGEVENS · NIET GEVALIDEERD';
|
| 237 |
+
document.querySelector('.paper p').textContent='Lokale Excel-proef · niet voor publicatie';
|
| 238 |
+
const pk=document.querySelectorAll('.paper-kpis strong');
|
| 239 |
+
[num(result.visits),num(result.unique),'—'].forEach((x,i)=>{if(pk[i])pk[i].textContent=x});
|
| 240 |
+
const pl=document.querySelectorAll('.paper-kpis small');if(pl[2])pl[2].textContent='Instroom niet bepaald';
|
| 241 |
+
}
|
| 242 |
+
async function startImported(){
|
| 243 |
+
if(running)return;
|
| 244 |
+
running=true;$('startBtn').disabled=true;$('resetBtn').disabled=true;completed=0;clearSignal();
|
| 245 |
+
$('logs').innerHTML='';$('completed').textContent='0';$('conductor').classList.remove('attention');
|
| 246 |
+
$('conductor').classList.add('running');document.querySelector('.orchestra').classList.add('running');
|
| 247 |
+
$('conductorStatus').textContent='Lokale Excel-proef actief';$('logStatus').textContent='Actief';
|
| 248 |
+
for(const id of ids){let el=$('agent-'+id);el.classList.remove('active','done');el.querySelector('.status').textContent='STANDBY';el.querySelector('.track i').style.width='0%';el.querySelector('.percent').textContent='0%'}
|
| 249 |
+
const result=calculateImported($('siteSelect').value);importedData.last=result;
|
| 250 |
+
const steps=[
|
| 251 |
+
{agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans aangetroffen`},
|
| 252 |
+
{agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke cliëntnummers berekend`},
|
| 253 |
+
{agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
|
| 254 |
+
{agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
|
| 255 |
+
{agent:'report',text:'Conceptpreview bijgewerkt · NIET GEVALIDEERD'}];
|
| 256 |
+
try{
|
| 257 |
+
renderImported(result);
|
| 258 |
+
for(const step of steps){const el=$('agent-'+step.agent);signalFor(step.agent);el.classList.add('active');el.querySelector('.status').textContent='ACTIEF';$('flowState').textContent=step.text;log('Start: '+el.querySelector('h3').textContent);
|
| 259 |
+
for(let p=0;p<=100;p+=5){el.querySelector('.track i').style.width=p+'%';el.querySelector('.percent').textContent=p+'%';await sleep(30)}
|
| 260 |
+
clearSignal();el.classList.remove('active');el.classList.add('done');el.querySelector('.status').textContent=step.agent==='verify'?'NIET EXTERN':'GEREED';completed++;$('completed').textContent=String(completed);log(step.text);await sleep(200)
|
| 261 |
+
}
|
| 262 |
+
$('conductor').classList.add('attention');$('flowState').textContent='Concept · NIET GEVALIDEERD';$('conductorStatus').textContent='Proefberekening voltooid';$('logStatus').textContent='Afgerond';
|
| 263 |
+
}catch(err){log('FOUT: '+err.message);$('flowState').textContent='Proefberekening mislukt';}finally{clearSignal();running=false;$('startBtn').disabled=false;$('resetBtn').disabled=false;$('conductor').classList.remove('running');document.querySelector('.orchestra').classList.remove('running')}
|
| 264 |
+
}
|
| 265 |
+
$('scanFile').addEventListener('change',async e=>{
|
| 266 |
+
importedData=null;const file=e.target.files?.[0];if(!file)return;
|
| 267 |
+
const state=$('importState');state.textContent='Bestand wordt lokaal gelezen...';$('startBtn').disabled=true;
|
| 268 |
+
try{
|
| 269 |
+
importedData=await readExcelLocal(file);
|
| 270 |
+
const sel=$('siteSelect');sel.innerHTML='<option value="*">Alle locaties</option>';
|
| 271 |
+
importedData.sites.forEach(loc=>{const opt=document.createElement('option');opt.value=loc;opt.textContent=loc;sel.appendChild(opt)});
|
| 272 |
+
sel.disabled=false;
|
| 273 |
+
state.textContent=`${importedData.rawCount} testregels · ${importedData.sites.length} locaties`;
|
| 274 |
+
$('startBtn').textContent='▶ Analyseer testbestand';
|
| 275 |
+
$('flowState').textContent='Testbestand lokaal ingelezen · start de analyse';
|
| 276 |
+
log('Fictief Excel-testbestand is lokaal ingelezen; nog geen berekeningen gepubliceerd');
|
| 277 |
+
}catch(err){$('startBtn').textContent='▶ Start demo-analyse';state.textContent='⚠ '+err.message;$('siteSelect').disabled=true;log('Excel-proef: '+err.message)}finally{$('startBtn').disabled=false}
|
| 278 |
+
});
|
| 279 |
+
// Bestaande chat blijft demo-gericht. Voor echte Excel-proef antwoorden we uitsluitend met controleerbare berekende cijfers.
|
| 280 |
+
const originalReply=reply;
|
| 281 |
+
reply=function(q){
|
| 282 |
+
if(!importedData)return originalReply(q);
|
| 283 |
+
const r=importedData.last;
|
| 284 |
+
if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
|
| 285 |
+
if(/valid|controle|wouter|business|betrouw|verschil|akkoord/.test(q.toLowerCase()))return 'Deze Excel-proef is NIET extern gevalideerd. De schaduwberekening van de businesscontroller ontbreekt en de rekenregels moeten nog worden afgestemd.';
|
| 286 |
+
if(/bezoek|uniek|scan|groei|locatie|samenvat|rapport|analyse|kwartaal|cijfer/.test(q.toLowerCase()))return `Testbestand (${r.site==='*'?'alle locaties':r.site}): ${r.rows} scanregels, ${r.visits} voorlopige bezoeken na ${r.duplicates} herhaalde scans, en ${r.unique} unieke cliëntnummers. Dit is een lokale proef zonder AI-API en zonder externe verificatie.`;
|
| 287 |
+
return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
|
| 288 |
+
};
|
| 289 |
+
|
| 290 |
$('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',()=>window.print());
|
| 291 |
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'));
|
| 292 |
|