Report drill-down: per-event-type accuracy and error counts per algorithm
2 changed files+39−7
scripts/smoke-browser.tsmodified+1−1View file
@@ -47,7 +47,7 @@ try {
4747 undefined,
4848 { timeout: 180000 },
4949 );
50- const rows = await page.$$eval('table.results tr', (trs) =>
50+ const rows = await page.$$eval('#results > table.results tr', (trs) =>
5151 trs.slice(1).map((tr) => [...tr.children].map((td) => td.textContent).join(' | ')));
5252 console.log('results:');
5353 for (const r of rows) console.log(' ', r);
src/main.tsmodified+38−6View file
@@ -572,7 +572,8 @@ function renderResults(): void {
572572 }
573573 host.appendChild(table);
574574
575- // one confusion matrix per algorithm, summed over runs
575+ // per-algorithm drill-down: per-type breakdown and confusion matrix,
576+ // both summed over runs (per-type accuracy also as mean +- std over runs)
576577 for (const algo of b.algos) {
577578 const rs = b.results.filter((r) => r.algorithmId === algo.id && !r.error);
578579 if (!rs.length) {
@@ -586,15 +587,46 @@ function renderResults(): void {
586587 continue;
587588 }
588589 const n = b.names.length;
589- const conf = Array.from({ length: n + 1 }, () => new Array<number>(n + 1).fill(0));
590- for (const r of rs) {
591- for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) conf[i][j] += r.score.confusion[i][j];
592- }
593590 const det = document.createElement('details');
594591 const summary = document.createElement('summary');
595- summary.textContent = `${algo.name}: confusion matrix (all runs)`;
592+ summary.textContent = `${algo.name}: per-type breakdown and confusion matrix (all runs)`;
596593 summary.style.cssText = 'cursor:pointer;font-size:0.85rem;margin-top:0.5rem';
597594 det.appendChild(summary);
595+
596+ // per-type table
597+ const swatch = (k: number) =>
598+ `<span style="display:inline-block;width:0.65em;height:0.65em;border-radius:2px;background:${b.colors[k]};margin-right:0.4em"></span>`;
599+ const typeAcc = (pt: { correct: number; misclassified: number; missed: number; falsePositives: number }) => {
600+ const d = pt.correct + pt.misclassified + pt.missed + pt.falsePositives;
601+ return d > 0 ? pt.correct / d : 1;
602+ };
603+ const pt = document.createElement('table');
604+ pt.className = 'results';
605+ pt.style.marginTop = '0.4rem';
606+ pt.innerHTML = `<tr>
607+ <th>event type</th><th>accuracy</th><th>correct</th><th>misclassified</th>
608+ <th>missed</th><th>false pos.</th><th>truth</th></tr>`;
609+ for (let k = 0; k < n; k++) {
610+ const perRun = rs.map((r) => r.score.perType[k]);
611+ const accs = perRun.map(typeAcc);
612+ const sum = (f: (p: typeof perRun[number]) => number) => perRun.reduce((a, p) => a + f(p), 0);
613+ const tr = document.createElement('tr');
614+ tr.innerHTML = `<td>${swatch(k)}${b.names[k]}</td>
615+ <td>${mean(accs).toFixed(3)} ± ${std(accs).toFixed(3)}</td>
616+ <td>${sum((p) => p.correct)}</td>
617+ <td>${sum((p) => p.misclassified)}</td>
618+ <td>${sum((p) => p.missed)}</td>
619+ <td>${sum((p) => p.falsePositives)}</td>
620+ <td>${sum((p) => p.nTruth)}</td>`;
621+ pt.appendChild(tr);
622+ }
623+ det.appendChild(pt);
624+
625+ // confusion matrix
626+ const conf = Array.from({ length: n + 1 }, () => new Array<number>(n + 1).fill(0));
627+ for (const r of rs) {
628+ for (let i = 0; i <= n; i++) for (let j = 0; j <= n; j++) conf[i][j] += r.score.confusion[i][j];
629+ }
598630 const t = document.createElement('table');
599631 t.className = 'confusion';
600632 const head = ['true \\ detected', ...b.names, 'missed'];