Marcel0123 commited on
Commit
121973f
·
verified ·
1 Parent(s): bcd5e16

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +25 -10
index.html CHANGED
@@ -124,13 +124,13 @@ function stepDescriptions(agent, result, fake){
124
  const sites=result?.sites??0, rows=result?.rows??0, repeat=result?.duplicates??0;
125
  const provisional=result?.visits??0, unique=result?.unique??0;
126
  if(agent==='data')return fake?['Demobron geladen','4 voorbeeldlocaties','Fictieve scanregels','Democontrole afgerond','Overdracht aan analyse']:[
127
- 'Excel-bestand ingelezen',`${sites} locaties herkend`,`${n(rows)} scanregels bekeken`,`${n(repeat)} herhaalde scans gevonden`,'Overdracht aan Analysis Agent'];
128
  if(agent==='analysis')return fake?['Voorbeeld-KPI’s geladen','Locaties vergeleken','Trends weergegeven','Groei samengevat','Overdracht aan inzicht']:[
129
  'Dubbele scans uitgesloten',`${n(provisional)} bezoeken voorlopig`,`${n(unique)} unieke cliëntnummers`,'Maandtrends weergegeven','Overdracht aan Insight Agent'];
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','Dubbeltellingen gemarkeerd','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
  }
@@ -236,6 +236,7 @@ async function readExcelLocal(file){
236
  const header=cellsToDict(rows[0],shared),lookup={};
237
  for(const [col,headerText] of Object.entries(header))lookup[String(headerText).toLowerCase().trim()]=col;
238
  for(const mandatory of ['scantijd','locatie','clientnummer'])if(!lookup[mandatory])throw Error('Verplichte kolom ontbreekt: '+mandatory);
 
239
  const all=[],invalid={datum:0,locatie:0,client:0};
240
  for(let i=1;i<rows.length;i++){
241
  const c=cellsToDict(rows[i],shared),site=String(c[lookup.locatie]??'').trim(),id=String(c[lookup.clientnummer]??'').trim(),date=scanDate(c[lookup.scantijd]);
@@ -243,24 +244,35 @@ async function readExcelLocal(file){
243
  if(!site)invalid.locatie++;
244
  if(!id)invalid.client++;
245
  if(!site||!id||!date)continue;
246
- all.push({date,site,id});
247
  }
248
  if(!all.length)throw Error('Geen bruikbare scanregels');
249
  const months=[...new Set(all.map(r=>r.date.slice(0,7)))].sort();
250
  const sites=[...new Set(all.map(r=>r.site))].sort((a,b)=>a.localeCompare(b,'nl'));
251
- return {all,sites,months,rawCount:rows.length-1,invalid};
252
  }
253
  function calculateImported(site='*'){
254
  const all=importedData.all.filter(r=>site==='*'||r.site===site),keyset=new Set(),ids=new Set(),monthSite=new Map();
 
255
  for(const r of all){
 
 
 
 
 
 
 
 
 
256
  const k=r.site+'|'+r.date+'|'+r.id;
257
  if(keyset.has(k))continue;
258
  keyset.add(k);ids.add(r.id);
259
  const mk=r.site+'|'+r.date.slice(0,7);monthSite.set(mk,(monthSite.get(mk)||0)+1);
260
  }
 
261
  const ordered=[...new Set(all.map(x=>x.date.slice(0,7)))].sort(),chosen=ordered.slice(-3);
262
  const locations=(site==='*'?importedData.sites:[site]).map(name=>({name,visits:chosen.map(m=>monthSite.get(name+'|'+m)||0)}));
263
- return {site,rows:all.length,visits:keyset.size,unique:ids.size,duplicates:all.length-keyset.size,months:chosen,locations,invalid:importedData.invalid};
264
  }
265
  function drawImportedChart(result){
266
  const cvs=$('chart'),ctx=cvs.getContext('2d'),w=cvs.width,h=cvs.height;
@@ -283,8 +295,11 @@ function renderImported(result){
283
  const num=n=>n.toLocaleString('nl-NL');
284
  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'}];
285
  renderKPIs({kpis});drawImportedChart(result);
286
- $('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>';
287
- document.querySelector('.verification .warning').textContent='⚠ Proefberekening. Definities en externe controle nog niet gevalideerd.';
 
 
 
288
  document.querySelector('.paper-watermark').textContent='FICTIEVE TESTGEGEVENS · NIET GEVALIDEERD';
289
  document.querySelector('.paper p').textContent='Lokale Excel-proef · niet voor publicatie';
290
  const pk=document.querySelectorAll('.paper-kpis strong');
@@ -300,7 +315,7 @@ async function startImported(){
300
  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%'}
301
  const result=calculateImported($('siteSelect').value);importedData.last=result;
302
  const steps=[
303
- {agent:'data',text:`${result.rows.toLocaleString('nl-NL')} scanregels gecontroleerd; ${result.duplicates} herhaalde scans aangetroffen`},
304
  {agent:'analysis',text:`${result.visits} bezoeken (voorlopig) en ${result.unique} unieke cliëntnummers berekend`},
305
  {agent:'insight',text:'Maandtrends berekend; AI-duiding nog niet verbonden'},
306
  {agent:'verify',text:'Interne telling gecontroleerd; geen externe schaduwberekening beschikbaar'},
@@ -322,7 +337,7 @@ $('scanFile').addEventListener('change',async e=>{
322
  const sel=$('siteSelect');sel.innerHTML='<option value="*">Alle locaties</option>';
323
  importedData.sites.forEach(loc=>{const opt=document.createElement('option');opt.value=loc;opt.textContent=loc;sel.appendChild(opt)});
324
  sel.disabled=false;
325
- state.textContent=`${importedData.rawCount} testregels · ${importedData.sites.length} locaties`;
326
  $('startBtn').textContent='▶ Analyseer testbestand';
327
  $('flowState').textContent='Testbestand lokaal ingelezen · start de analyse';
328
  log('Fictief Excel-testbestand is lokaal ingelezen; nog geen berekeningen gepubliceerd');
@@ -335,7 +350,7 @@ reply=function(q){
335
  const r=importedData.last;
336
  if(!r)return 'Klik op Start demo-analyse om de lokaal ingelezen testgegevens te berekenen.';
337
  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.';
338
- 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.`;
339
  return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
340
  };
341
 
 
124
  const sites=result?.sites??0, rows=result?.rows??0, repeat=result?.duplicates??0;
125
  const provisional=result?.visits??0, unique=result?.unique??0;
126
  if(agent==='data')return fake?['Demobron geladen','4 voorbeeldlocaties','Fictieve scanregels','Democontrole afgerond','Overdracht aan analyse']:[
127
+ 'Excel-bestand ingelezen',`${sites} locaties herkend`,`${n(rows)} scanregels bekeken`,`${n(repeat)} herhaalde scans gevonden`,result?.passChecked?`${n(result.multiPassClients)} cliënten met meerdere passen`:'Pascontrole niet beschikbaar'];
128
  if(agent==='analysis')return fake?['Voorbeeld-KPI’s geladen','Locaties vergeleken','Trends weergegeven','Groei samengevat','Overdracht aan inzicht']:[
129
  'Dubbele scans uitgesloten',`${n(provisional)} bezoeken voorlopig`,`${n(unique)} unieke cliëntnummers`,'Maandtrends weergegeven','Overdracht aan Insight Agent'];
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?'Pascontrole vastgelegd':'Geen pasnummerkolom','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
  }
 
236
  const header=cellsToDict(rows[0],shared),lookup={};
237
  for(const [col,headerText] of Object.entries(header))lookup[String(headerText).toLowerCase().trim()]=col;
238
  for(const mandatory of ['scantijd','locatie','clientnummer'])if(!lookup[mandatory])throw Error('Verplichte kolom ontbreekt: '+mandatory);
239
+ const passColumn=lookup['pasnummer']||lookup['scanpasnummer']||lookup['pas nummer']||lookup['pasnr']||null;
240
  const all=[],invalid={datum:0,locatie:0,client:0};
241
  for(let i=1;i<rows.length;i++){
242
  const c=cellsToDict(rows[i],shared),site=String(c[lookup.locatie]??'').trim(),id=String(c[lookup.clientnummer]??'').trim(),date=scanDate(c[lookup.scantijd]);
 
244
  if(!site)invalid.locatie++;
245
  if(!id)invalid.client++;
246
  if(!site||!id||!date)continue;
247
+ all.push({date,site,id,pass:passColumn?String(c[passColumn]??'').trim():''});
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,months,rawCount:rows.length-1,invalid,hasPassColumn:!!passColumn};
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(), clientByPass=new Map();let missingPass=0,passConflicts=0;
257
  for(const r of all){
258
+ if(importedData.hasPassColumn){
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
+ const multiPassClients=[...passByClient.values()].filter(p=>p.size>1).length;
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,invalid:importedData.invalid,passChecked:importedData.hasPassColumn,multiPassClients,passConflicts,missingPass};
276
  }
277
  function drawImportedChart(result){
278
  const cvs=$('chart'),ctx=cvs.getContext('2d'),w=cvs.width,h=cvs.height;
 
295
  const num=n=>n.toLocaleString('nl-NL');
296
  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'}];
297
  renderKPIs({kpis});drawImportedChart(result);
298
+ $('checks').innerHTML='<div class="check-row"><span>Herhaalde scans</span><span>'+num(result.duplicates)+'</span></div><div class="check-row"><span>Unieke cliëntnummers</span><span>'+num(result.unique)+'</span></div>'+
299
+ '<div class="check-row"><span>Meerdere passen per cliënt</span><span>'+(result.passChecked?num(result.multiPassClients):'NIET TE CONTROLEREN')+'</span></div>'+
300
+ (result.passChecked?'<div class="check-row"><span>Pasnummer gekoppeld aan meerdere cliënten</span><span>'+num(result.passConflicts)+'</span></div>':'')+
301
+ '<div class="check-row"><span>Verificatie Businesscontroller</span><span>NIET AANGELEVERD</span></div>';
302
+ document.querySelector('.verification .warning').textContent=result.passChecked?(result.passConflicts?'⚠ Mogelijk conflicterende pas-cliëntkoppelingen: nader onderzoeken. Niet gevalideerd.':'⚠ Pascontrole uitgevoerd op basis van aangeleverde kolommen. Externe verificatie ontbreekt.'): '⚠ Geen pasnummerkolom: meerdere passen per cliënt niet afzonderlijk controleerbaar. Niet gevalideerd.';
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');
 
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; pascontrole: ${result.passChecked?'beschikbaar':'niet beschikbaar'}`},
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
  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 · pascontrole ${importedData.hasPassColumn?'beschikbaar':'niet beschikbaar'}`;
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
  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. Pascontrole: ${r.passChecked?r.multiPassClients+' cliënten met meerdere passen, '+r.passConflicts+' pasconflicten':'niet beschikbaar (geen pasnummerkolom)'}. Dit is een lokale proef zonder AI-API en zonder externe verificatie.`;
354
  return 'De AI-API is nog niet gekoppeld. Ik kan voorlopig alleen de gecontroleerde basisgetallen van het fictieve Excel-testbestand toelichten.';
355
  };
356