Upload 2 files
Browse files- REAKT_TEST_TWEE_WERKBLADEN_FICTIEF.xlsx +0 -0
- index.html +76 -47
REAKT_TEST_TWEE_WERKBLADEN_FICTIEF.xlsx
ADDED
|
Binary file (11.3 kB). View file
|
|
|
index.html
CHANGED
|
@@ -21,7 +21,7 @@
|
|
| 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.
|
| 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}
|
|
@@ -130,7 +130,7 @@ function stepDescriptions(agent, result, fake){
|
|
| 130 |
if(agent==='insight')return fake?['Voorbeeldtrend gelezen','Locatieverschillen bekeken','Fictieve inzichten verzameld','Conceptduiding opgesteld','Overdracht aan verificatie']:[
|
| 131 |
'Maandgegevens beschikbaar','Locaties in grafiek gezet','Signalen zonder causaliteit','AI-duiding niet aangesloten','Overdracht aan controle'];
|
| 132 |
if(agent==='verify')return fake?['Demovergelijking geopend','5 indicatoren bekeken','1 fictieve afwijking','Rapport niet gevalideerd','Overdracht met waarschuwing']:[
|
| 133 |
-
'Interne telling beschikbaar',result?.passChecked?'
|
| 134 |
return fake?['Concept gestart','Voorbeeldcijfers verwerkt','Illustratie bijgewerkt','Niet gevalideerd gemarkeerd','Concept gereed']:[
|
| 135 |
'Concept gestart','KPI-preview bijgewerkt','Instroom nog niet bepaald','Niet gevalideerd gemarkeerd','Concept gereed'];
|
| 136 |
}
|
|
@@ -220,59 +220,81 @@ function scanDate(v){
|
|
| 220 |
}
|
| 221 |
async function readExcelLocal(file){
|
| 222 |
if(!file.name.toLowerCase().endsWith('.xlsx'))throw Error('Kies een XLSX-testbestand');
|
| 223 |
-
if(file.size>excelLimit)throw Error('
|
| 224 |
-
const
|
| 225 |
-
const shared=[];
|
| 226 |
const sharedXML=await xmlEntry(zip,'xl/sharedStrings.xml');
|
| 227 |
-
if(sharedXML){
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
const
|
| 232 |
-
if(!sheetXML)throw Error('Werkblad Data dump niet gevonden');
|
| 233 |
-
const doc=xmlDoc(sheetXML),rows=doc.getElementsByTagNameNS(excelNS,'row');
|
| 234 |
if(rows.length<2)throw Error('Geen scanregels gevonden');
|
| 235 |
-
if(rows.length>18000)throw Error('Voor deze eerste proef maximaal 18.000
|
| 236 |
const header=cellsToDict(rows[0],shared),lookup={};
|
| 237 |
-
for(const [col,
|
| 238 |
-
|
| 239 |
-
const passColumn=lookup
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
for(let i=1;i<rows.length;i++){
|
| 242 |
-
const
|
| 243 |
-
|
| 244 |
-
if(
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
if(!site||!id||!date)continue;
|
| 247 |
-
all.push({date,site,id,pass
|
| 248 |
}
|
| 249 |
if(!all.length)throw Error('Geen bruikbare scanregels');
|
| 250 |
-
const months=[...new Set(all.map(r=>r.date.slice(0,7)))].sort();
|
| 251 |
const sites=[...new Set(all.map(r=>r.site))].sort((a,b)=>a.localeCompare(b,'nl'));
|
| 252 |
-
return {all,sites,
|
|
|
|
|
|
|
|
|
|
| 253 |
}
|
| 254 |
function calculateImported(site='*'){
|
| 255 |
const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),monthSite=new Map();
|
| 256 |
-
const passByClient=new Map()
|
| 257 |
for(const r of all){
|
| 258 |
-
if(
|
| 259 |
-
if(!r.pass)missingPass++;
|
| 260 |
-
else {
|
| 261 |
-
if(!passByClient.has(r.id))passByClient.set(r.id,new Set());
|
| 262 |
-
passByClient.get(r.id).add(r.pass);
|
| 263 |
-
if(clientByPass.has(r.pass)&&clientByPass.get(r.pass)!==r.id)passConflicts++;
|
| 264 |
-
else clientByPass.set(r.pass,r.id);
|
| 265 |
-
}
|
| 266 |
-
}
|
| 267 |
const k=r.site+'|'+r.date+'|'+r.id;
|
| 268 |
if(keyset.has(k))continue;
|
| 269 |
keyset.add(k);ids.add(r.id);
|
| 270 |
const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
|
| 271 |
}
|
| 272 |
-
|
|
|
|
|
|
|
| 273 |
const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
|
| 274 |
const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
|
| 275 |
-
return {site,rows:all.length,visits:keyset.size,unique:ids.size,duplicates:all.length-keyset.size,months:chosen,locations,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
}
|
| 277 |
function drawImportedChart(result){
|
| 278 |
const cvs=$('chart'),ctx=cvs.getContext('2d'),w=cvs.width,h=cvs.height;
|
|
@@ -293,17 +315,24 @@ function drawImportedChart(result){
|
|
| 293 |
}
|
| 294 |
function renderImported(result){
|
| 295 |
const num=n=>n.toLocaleString('nl-NL');
|
| 296 |
-
const kpis=[{label:'Bezoeken (voorlopig)',value:num(result.visits),change:'Unieke cliënt / dag / locatie'},
|
|
|
|
|
|
|
|
|
|
| 297 |
renderKPIs({kpis});drawImportedChart(result);
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
document.querySelector('.paper-watermark').textContent='FICTIEVE TESTGEGEVENS · NIET GEVALIDEERD';
|
| 304 |
document.querySelector('.paper p').textContent='Lokale Excel-proef · niet voor publicatie';
|
| 305 |
-
const pk=document.querySelectorAll('.paper-kpis strong');
|
| 306 |
-
[num(result.visits),num(result.unique),'—'].forEach((x,i)=>{if(pk[i])pk[i].textContent=x});
|
| 307 |
const pl=document.querySelectorAll('.paper-kpis small');if(pl[2])pl[2].textContent='Instroom niet bepaald';
|
| 308 |
}
|
| 309 |
async function startImported(){
|
|
@@ -315,7 +344,7 @@ async function startImported(){
|
|
| 315 |
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%'}
|
| 316 |
const result=calculateImported($('siteSelect').value);importedData.last=result;
|
| 317 |
const steps=[
|
| 318 |
-
{agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans;
|
| 319 |
{agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke cliëntnummers berekend`},
|
| 320 |
{agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
|
| 321 |
{agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
|
|
@@ -337,7 +366,7 @@ $('scanFile').addEventListener('change',async e=>{
|
|
| 337 |
const sel=$('siteSelect');sel.innerHTML='<option value="*">Alle locaties</option>';
|
| 338 |
importedData.sites.forEach(loc=>{const opt=document.createElement('option');opt.value=loc;opt.textContent=loc;sel.appendChild(opt)});
|
| 339 |
sel.disabled=false;
|
| 340 |
-
state.textContent=`${importedData.rawCount} testregels · ${importedData.sites.length} locaties ·
|
| 341 |
$('startBtn').textContent='▶ Analyseer testbestand';
|
| 342 |
$('flowState').textContent='Testbestand lokaal ingelezen · start de analyse';
|
| 343 |
log('Fictief Excel-testbestand is lokaal ingelezen; nog geen berekeningen gepubliceerd');
|
|
@@ -350,7 +379,7 @@ reply=function(q){
|
|
| 350 |
const r=importedData.last;
|
| 351 |
if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
|
| 352 |
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.';
|
| 353 |
-
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.
|
| 354 |
return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
|
| 355 |
};
|
| 356 |
|
|
|
|
| 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.7 – lokale Excel-proef met Scanpassen; 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}
|
|
|
|
| 130 |
if(agent==='insight')return fake?['Voorbeeldtrend gelezen','Locatieverschillen bekeken','Fictieve inzichten verzameld','Conceptduiding opgesteld','Overdracht aan verificatie']:[
|
| 131 |
'Maandgegevens beschikbaar','Locaties in grafiek gezet','Signalen zonder causaliteit','AI-duiding niet aangesloten','Overdracht aan controle'];
|
| 132 |
if(agent==='verify')return fake?['Demovergelijking geopend','5 indicatoren bekeken','1 fictieve afwijking','Rapport niet gevalideerd','Overdracht met waarschuwing']:[
|
| 133 |
+
'Interne telling beschikbaar',result?.passSheetFound?'Scanpassen-werkblad verwerkt':result?.passChecked?'Paskolom bekeken':'Pascontrole niet mogelijk','Businesscontroller: geen bestand','Externe controle overgeslagen','Status: niet gevalideerd'];
|
| 134 |
return fake?['Concept gestart','Voorbeeldcijfers verwerkt','Illustratie bijgewerkt','Niet gevalideerd gemarkeerd','Concept gereed']:[
|
| 135 |
'Concept gestart','KPI-preview bijgewerkt','Instroom nog niet bepaald','Niet gevalideerd gemarkeerd','Concept gereed'];
|
| 136 |
}
|
|
|
|
| 220 |
}
|
| 221 |
async function readExcelLocal(file){
|
| 222 |
if(!file.name.toLowerCase().endsWith('.xlsx'))throw Error('Kies een XLSX-testbestand');
|
| 223 |
+
if(file.size>excelLimit)throw Error('Gebruik voorlopig uitsluitend het kleine fictieve testbestand (maximaal 2 MB).');
|
| 224 |
+
const zip=zipDirectory(await file.arrayBuffer()),shared=[];
|
|
|
|
| 225 |
const sharedXML=await xmlEntry(zip,'xl/sharedStrings.xml');
|
| 226 |
+
if(sharedXML){for(const si of xmlDoc(sharedXML).getElementsByTagNameNS(excelNS,'si'))shared.push(si.textContent??'')}
|
| 227 |
+
// Deze eerste proef verwacht het klassieke Reakt-testformat met Data dump op sheet1 en Scanpassen op sheet2.
|
| 228 |
+
const sheet=await xmlEntry(zip,'xl/worksheets/sheet1.xml');
|
| 229 |
+
if(!sheet)throw Error('Werkblad Data dump (sheet1) ontbreekt');
|
| 230 |
+
const rows=xmlDoc(sheet).getElementsByTagNameNS(excelNS,'row');
|
|
|
|
|
|
|
| 231 |
if(rows.length<2)throw Error('Geen scanregels gevonden');
|
| 232 |
+
if(rows.length>18000)throw Error('Voor deze eerste proef maximaal 18.000 scanregels');
|
| 233 |
const header=cellsToDict(rows[0],shared),lookup={};
|
| 234 |
+
for(const [col,name] of Object.entries(header))lookup[String(name).toLowerCase().trim()]=col;
|
| 235 |
+
const pick=(l,...names)=>names.map(n=>l[n]).find(Boolean)||null;
|
| 236 |
+
const dateCol=pick(lookup,'scantijd','scandatum'),siteCol=pick(lookup,'locatie'),idCol=pick(lookup,'clientnummer','cliëntnummer'),passColumn=pick(lookup,'pasnummer','scanpasnummer','pas nummer','pasnr');
|
| 237 |
+
if(!dateCol||!siteCol||(!idCol&&!passColumn))throw Error('Kolommen scantijd, locatie en cliëntnummer of pasnummer ontbreken');
|
| 238 |
+
// Werkblad 2 bevat een onafhankelijke pas-cliëntregistratie. Nooit identificeren op naam.
|
| 239 |
+
const passXML=await xmlEntry(zip,'xl/worksheets/sheet2.xml');
|
| 240 |
+
const passToClient=new Map(),clientToPasses=new Map(),passConflicts=new Set();let passRows=0,passMissing=0;
|
| 241 |
+
if(passXML){
|
| 242 |
+
const pr=xmlDoc(passXML).getElementsByTagNameNS(excelNS,'row');
|
| 243 |
+
if(pr.length>20000)throw Error('Voor deze proef maximaal 20.000 scanpassen');
|
| 244 |
+
if(pr.length){
|
| 245 |
+
const ph=cellsToDict(pr[0],shared),pl={};for(const [col,v] of Object.entries(ph))pl[String(v).toLowerCase().trim()]=col;
|
| 246 |
+
const pc=pick(pl,'pasnummer','scanpasnummer','pas nummer','pasnr'),ic=pick(pl,'clientnummer','cliëntnummer');
|
| 247 |
+
if(!pc||!ic)throw Error('Scanpassen mist kolommen Pasnummer en Clientnummer');
|
| 248 |
+
for(let i=1;i<pr.length;i++){
|
| 249 |
+
const r=cellsToDict(pr[i],shared),p=String(r[pc]??'').trim(),c=String(r[ic]??'').trim();passRows++;
|
| 250 |
+
if(!p||!c){passMissing++;continue}
|
| 251 |
+
if(passToClient.has(p)&&passToClient.get(p)!==c)passConflicts.add(p);
|
| 252 |
+
else passToClient.set(p,c);
|
| 253 |
+
if(!clientToPasses.has(c))clientToPasses.set(c,new Set());clientToPasses.get(c).add(p);
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
const all=[],invalid={datum:0,locatie:0,client:0},unmatchedPasses=new Set(),identityConflicts=new Set();let linkedByPass=0,rowsWithPass=0;
|
| 258 |
for(let i=1;i<rows.length;i++){
|
| 259 |
+
const r=cellsToDict(rows[i],shared),site=String(r[siteCol]??'').trim(),date=scanDate(r[dateCol]);
|
| 260 |
+
const pass=passColumn?String(r[passColumn]??'').trim():'',directId=idCol?String(r[idCol]??'').trim():'';
|
| 261 |
+
if(pass)rowsWithPass++;
|
| 262 |
+
const resolved=pass&&!passConflicts.has(pass)?passToClient.get(pass):null;
|
| 263 |
+
if(pass&&passXML&&!resolved)unmatchedPasses.add(pass);
|
| 264 |
+
if(directId&&resolved&&directId!==resolved)identityConflicts.add(pass);
|
| 265 |
+
if(!directId&&resolved)linkedByPass++;
|
| 266 |
+
const id=directId||resolved||'';
|
| 267 |
+
if(!date)invalid.datum++;if(!site)invalid.locatie++;if(!id)invalid.client++;
|
| 268 |
if(!site||!id||!date)continue;
|
| 269 |
+
all.push({date,site,id,pass});
|
| 270 |
}
|
| 271 |
if(!all.length)throw Error('Geen bruikbare scanregels');
|
|
|
|
| 272 |
const sites=[...new Set(all.map(r=>r.site))].sort((a,b)=>a.localeCompare(b,'nl'));
|
| 273 |
+
return {all,sites,rawCount:rows.length-1,invalid,hasPassColumn:!!passColumn,
|
| 274 |
+
passSheetFound:!!passXML,passRows,passMissing,passToClient,clientToPasses,
|
| 275 |
+
passConflicts:passConflicts.size,identityConflicts:identityConflicts.size,
|
| 276 |
+
unmatchedPasses:unmatchedPasses.size,linkedByPass,rowsWithPass};
|
| 277 |
}
|
| 278 |
function calculateImported(site='*'){
|
| 279 |
const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),monthSite=new Map();
|
| 280 |
+
const passByClient=new Map();let missingPass=0;
|
| 281 |
for(const r of all){
|
| 282 |
+
if(r.pass){if(!passByClient.has(r.id))passByClient.set(r.id,new Set());passByClient.get(r.id).add(r.pass)}else missingPass++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
const k=r.site+'|'+r.date+'|'+r.id;
|
| 284 |
if(keyset.has(k))continue;
|
| 285 |
keyset.add(k);ids.add(r.id);
|
| 286 |
const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
|
| 287 |
}
|
| 288 |
+
let multiPassClients=0;
|
| 289 |
+
if(importedData.passSheetFound){for(const [client,passes] of importedData.clientToPasses)if(ids.has(client)&&passes.size>1)multiPassClients++}
|
| 290 |
+
else multiPassClients=[...passByClient.values()].filter(p=>p.size>1).length;
|
| 291 |
const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
|
| 292 |
const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
|
| 293 |
+
return {site,rows:all.length,visits:keyset.size,unique:ids.size,duplicates:all.length-keyset.size,months:chosen,locations,
|
| 294 |
+
invalid:importedData.invalid,passChecked:importedData.hasPassColumn||importedData.passSheetFound,
|
| 295 |
+
passSheetFound:importedData.passSheetFound,multiPassClients,passConflicts:importedData.passConflicts,
|
| 296 |
+
identityConflicts:importedData.identityConflicts,unmatchedPasses:importedData.unmatchedPasses,
|
| 297 |
+
linkedByPass:importedData.linkedByPass,missingPass,passRows:importedData.passRows};
|
| 298 |
}
|
| 299 |
function drawImportedChart(result){
|
| 300 |
const cvs=$('chart'),ctx=cvs.getContext('2d'),w=cvs.width,h=cvs.height;
|
|
|
|
| 315 |
}
|
| 316 |
function renderImported(result){
|
| 317 |
const num=n=>n.toLocaleString('nl-NL');
|
| 318 |
+
const kpis=[{label:'Bezoeken (voorlopig)',value:num(result.visits),change:'Unieke cliënt / dag / locatie'},
|
| 319 |
+
{label:'Unieke cliëntnummers',value:num(result.unique),change:'Binnen geselecteerde periode'},
|
| 320 |
+
{label:'Scanregels',value:num(result.rows),change:`${num(result.duplicates)} herhaalde scans`},
|
| 321 |
+
{label:'Verificatie',value:'—',change:'Extern niet uitgevoerd'}];
|
| 322 |
renderKPIs({kpis});drawImportedChart(result);
|
| 323 |
+
const lines=[['Herhaalde scans',num(result.duplicates)],['Unieke cliëntnummers',num(result.unique)],
|
| 324 |
+
['Scanpassen: apart werkblad',result.passSheetFound?num(result.passRows)+' registraties':'NIET AANWEZIG'],
|
| 325 |
+
['Cliënten met meerdere passen',result.passChecked?num(result.multiPassClients):'NIET TE CONTROLEREN'],
|
| 326 |
+
['Scanregels via pas gekoppeld',num(result.linkedByPass)],
|
| 327 |
+
['Niet-herkende passen',num(result.unmatchedPasses)],['Tegenstrijdige pasregistraties',num(result.passConflicts)],
|
| 328 |
+
['Verschil scan/registratie',num(result.identityConflicts)],['Businesscontroller','NIET AANGELEVERD']];
|
| 329 |
+
$('checks').innerHTML=lines.map(([name,val])=>'<div class="check-row"><span>'+escapeHTML(name)+'</span><span>'+escapeHTML(val)+'</span></div>').join('');
|
| 330 |
+
const issue=result.passConflicts||result.identityConflicts||result.unmatchedPasses;
|
| 331 |
+
document.querySelector('.verification .warning').textContent=issue?'⚠ Inconsistente of ontbrekende pas-koppelingen. Eerst onderzoeken. NIET GEVALIDEERD.':
|
| 332 |
+
(result.passSheetFound?'⚠ Scanpassen gecontroleerd; definities en externe verificatie ontbreken. NIET GEVALIDEERD.':'⚠ Geen apart Scanpassen-werkblad; externe verificatie ontbreekt. NIET GEVALIDEERD.');
|
| 333 |
document.querySelector('.paper-watermark').textContent='FICTIEVE TESTGEGEVENS · NIET GEVALIDEERD';
|
| 334 |
document.querySelector('.paper p').textContent='Lokale Excel-proef · niet voor publicatie';
|
| 335 |
+
const pk=document.querySelectorAll('.paper-kpis strong');[num(result.visits),num(result.unique),'—'].forEach((x,i)=>{if(pk[i])pk[i].textContent=x});
|
|
|
|
| 336 |
const pl=document.querySelectorAll('.paper-kpis small');if(pl[2])pl[2].textContent='Instroom niet bepaald';
|
| 337 |
}
|
| 338 |
async function startImported(){
|
|
|
|
| 344 |
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%'}
|
| 345 |
const result=calculateImported($('siteSelect').value);importedData.last=result;
|
| 346 |
const steps=[
|
| 347 |
+
{agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans; Scanpassen: ${result.passSheetFound?result.passRows+' registraties':'geen apart werkblad'}`},
|
| 348 |
{agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke cliëntnummers berekend`},
|
| 349 |
{agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
|
| 350 |
{agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
|
|
|
|
| 366 |
const sel=$('siteSelect');sel.innerHTML='<option value="*">Alle locaties</option>';
|
| 367 |
importedData.sites.forEach(loc=>{const opt=document.createElement('option');opt.value=loc;opt.textContent=loc;sel.appendChild(opt)});
|
| 368 |
sel.disabled=false;
|
| 369 |
+
state.textContent=`${importedData.rawCount} testregels · ${importedData.sites.length} locaties · Scanpassen ${importedData.passSheetFound?importedData.passRows+' regels':'ontbreekt'}`;
|
| 370 |
$('startBtn').textContent='▶ Analyseer testbestand';
|
| 371 |
$('flowState').textContent='Testbestand lokaal ingelezen · start de analyse';
|
| 372 |
log('Fictief Excel-testbestand is lokaal ingelezen; nog geen berekeningen gepubliceerd');
|
|
|
|
| 379 |
const r=importedData.last;
|
| 380 |
if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
|
| 381 |
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.';
|
| 382 |
+
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. Scanpassen: ${r.passSheetFound?r.passRows+' registraties': 'geen apart werkblad'}; cliënten met meerdere passen: ${r.multiPassClients}; tegenstrijdige koppelingen: ${r.passConflicts+r.identityConflicts}. Dit is een lokale proef zonder AI-API en zonder externe verificatie.`;
|
| 383 |
return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
|
| 384 |
};
|
| 385 |
|