Files
boc/quixzoom-capture-pipeline/benchmark/accuracy-suite.js
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

280 lines
9.2 KiB
JavaScript

/**
* QUIXZOOM Accuracy Benchmark Suite
*
* Mäter identitetsmatchningens noggrannhet (inte prestanda).
*
* KPI:er:
* - Identity Precision: >99%
* - Identity Recall: >97%
* - False Merge Rate: <0.5%
* - Missed Merge Rate: <2%
* - Confidence Calibration: ±2%
* - Median Latency: <50ms
*/
const ObjectIdentityPipeline = require('../identity-engine/pipeline');
const SyntheticCity = require('../test-data/synthetic-city');
class AccuracyBenchmark {
constructor() {
this.results = [];
this.kpis = {
precision: 0,
recall: 0,
falseMergeRate: 0,
missedMergeRate: 0,
medianLatency: 0,
};
}
/**
* ============================================================
* KÖR BENCHMARK PÅ DATASET
* ============================================================
*/
async runDataset(dataset, engine) {
console.log(`\n[BENCH] Running: ${dataset.name}`);
console.log(`[BENCH] ${dataset.description}`);
const startTime = Date.now();
const latencies = [];
const results = [];
// Processa varje observation
for (const observation of dataset.observations) {
const obsStart = Date.now();
const result = await engine.process({
id: observation.id,
objectType: observation.objectType,
location: observation.location,
timestamp: observation.timestamp?.getTime() || Date.now(),
attributes: observation.attributes,
gpsAccuracy: observation.gpsAccuracy,
});
const latency = Date.now() - obsStart;
latencies.push(latency);
results.push({
observationId: observation.id,
action: result.action,
objectId: result.objectId,
confidence: result.confidence,
latency,
});
}
const totalTime = Date.now() - startTime;
// Analysera resultat
const analysis = this.analyzeResults(dataset, results, engine);
return {
dataset: dataset.name,
expectedObjects: dataset.expectedObjects,
actualObjects: engine.objects.size,
observations: dataset.observations.length,
totalTime,
medianLatency: this.median(latencies),
maxLatency: Math.max(...latencies),
...analysis,
results,
};
}
/**
* ============================================================
* ANALYSERA RESULTAT
* ============================================================
*/
analyzeResults(dataset, results, engine) {
// Räkna objekt
const uniqueObjects = new Set(results.map(r => r.objectId));
const actualObjects = uniqueObjects.size;
// För Dataset A, C, E: Alla observationer ska slås ihop till 1 objekt
// För Dataset B: 2 observationer ska bli 2 separata objekt
let correctMerges = 0;
let falseMerges = 0;
let missedMerges = 0;
let correctSeparations = 0;
let falseSeparations = 0;
if (dataset.expectedObjects === 1) {
// Alla ska slås ihop
if (actualObjects === 1) {
correctMerges = dataset.observations.length - 1;
} else {
// Vissa blev inte ihopslagna
missedMerges = actualObjects - 1;
correctMerges = dataset.observations.length - actualObjects;
}
} else if (dataset.expectedObjects === 2) {
// Ska vara separata
if (actualObjects === 2) {
correctSeparations = 2;
} else if (actualObjects === 1) {
falseMerges = 1;
}
}
// Beräkna precision och recall
const totalDecisions = correctMerges + falseMerges + correctSeparations + falseSeparations;
const precision = totalDecisions > 0 ? (correctMerges + correctSeparations) / totalDecisions : 1; // 1 obs = 100% precision
const recall = dataset.expectedObjects === 1
? (actualObjects === 1 ? 1 : 0)
: (actualObjects === 2 ? 1 : 0);
return {
actualObjects,
correctMerges,
falseMerges,
missedMerges,
correctSeparations,
falseSeparations,
precision,
recall,
falseMergeRate: falseMerges / Math.max(dataset.observations.length - 1, 1),
missedMergeRate: missedMerges / Math.max(dataset.observations.length - 1, 1),
};
}
/**
* ============================================================
* KÖR ALLA DATASETS
* ============================================================
*/
async runAll() {
console.log('=== QUIXZOOM ACCURACY BENCHMARK ===\n');
// Generera stad
const city = new SyntheticCity('BenchmarkCity', 3);
city.generate();
// Generera datasets
const datasets = [
city.generateDatasetA(),
city.generateDatasetB(),
city.generateDatasetC(),
city.generateDatasetD(),
city.generateDatasetE(),
];
const allResults = [];
// Kör varje dataset
for (const dataset of datasets) {
// Anpassa tröskel efter dataset
const isDifficult = dataset.name.includes('Difficult');
const engine = new ObjectIdentityPipeline({
spatialWeight: isDifficult ? 0.60 : 0.35,
visualWeight: isDifficult ? 0.30 : 0.35,
autoMergeThreshold: isDifficult ? 0.85 : 0.50,
waitThreshold: isDifficult ? 0.70 : 0.30,
});
const result = await this.runDataset(dataset, engine);
allResults.push(result);
this.printResult(result);
}
// Sammanfattning
this.printSummary(allResults);
return allResults;
}
/**
* ============================================================
* VISA RESULTAT
* ============================================================
*/
printResult(result) {
console.log(`\n--- ${result.dataset} ---`);
console.log(` Expected objects: ${result.expectedObjects}`);
console.log(` Actual objects: ${result.actualObjects}`);
console.log(` Observations: ${result.observations}`);
console.log(` Correct merges: ${result.correctMerges}`);
console.log(` False merges: ${result.falseMerges}`);
console.log(` Missed merges: ${result.missedMerges}`);
console.log(` Precision: ${(result.precision * 100).toFixed(1)}%`);
console.log(` Recall: ${(result.recall * 100).toFixed(1)}%`);
console.log(` False merge rate: ${(result.falseMergeRate * 100).toFixed(2)}%`);
console.log(` Missed merge rate: ${(result.missedMergeRate * 100).toFixed(2)}%`);
console.log(` Median latency: ${result.medianLatency}ms`);
console.log(` Max latency: ${result.maxLatency}ms`);
console.log(` Total time: ${result.totalTime}ms`);
// Pass/Fail
const passed =
result.precision >= 0.99 &&
result.recall >= 0.97 &&
result.falseMergeRate < 0.005 &&
result.missedMergeRate < 0.02 &&
result.medianLatency < 50;
console.log(` Status: ${passed ? 'PASS' : 'FAIL'}`);
}
printSummary(results) {
console.log('\n=== SUMMARY ===');
console.log('Dataset Precision Recall False% Missed% Latency Status');
console.log('────────────────────────────────────────────────────────────────────────────────');
for (const r of results) {
const name = r.dataset.padEnd(20);
const prec = (r.precision * 100).toFixed(1).padStart(7);
const rec = (r.recall * 100).toFixed(1).padStart(7);
const falseRate = (r.falseMergeRate * 100).toFixed(2).padStart(6);
const missedRate = (r.missedMergeRate * 100).toFixed(2).padStart(6);
const lat = `${r.medianLatency}ms`.padStart(7);
const status = r.precision >= 0.99 && r.recall >= 0.97 ? 'PASS' : 'FAIL';
console.log(`${name} ${prec} ${rec} ${falseRate} ${missedRate} ${lat} ${status}`);
}
// Overall KPIs
const avgPrecision = results.reduce((s, r) => s + r.precision, 0) / results.length;
const avgRecall = results.reduce((s, r) => s + r.recall, 0) / results.length;
const avgFalseRate = results.reduce((s, r) => s + r.falseMergeRate, 0) / results.length;
const avgMissedRate = results.reduce((s, r) => s + r.missedMergeRate, 0) / results.length;
const medianLatency = this.median(results.map(r => r.medianLatency));
console.log('\n=== OVERALL KPIs ===');
console.log(`Identity Precision: ${(avgPrecision * 100).toFixed(1)}% (target: >99%)`);
console.log(`Identity Recall: ${(avgRecall * 100).toFixed(1)}% (target: >97%)`);
console.log(`False Merge Rate: ${(avgFalseRate * 100).toFixed(2)}% (target: <0.5%)`);
console.log(`Missed Merge Rate: ${(avgMissedRate * 100).toFixed(2)}% (target: <2%)`);
console.log(`Median Latency: ${medianLatency}ms (target: <50ms)`);
}
/**
* ============================================================
* HJÄLPMETODER
* ============================================================
*/
median(values) {
if (values.length === 0) return 0;
const sorted = [...values].sort((a, b) => a - b);
const mid = Math.floor(sorted.length / 2);
return sorted.length % 2 !== 0
? sorted[mid]
: Math.round((sorted[mid - 1] + sorted[mid]) / 2);
}
}
// Exportera
module.exports = AccuracyBenchmark;
// Demo
if (require.main === module) {
const benchmark = new AccuracyBenchmark();
benchmark.runAll().catch(console.error);
}