Files
boc/quixzoom-capture-pipeline/dataset-manager/coverage-analyzer.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

300 lines
8.4 KiB
JavaScript

/**
* QUIXZOOM Coverage Gap Analyzer
*
* Analyserar täckningsgap och genererar uppdrag.
*/
class CoverageGapAnalyzer {
constructor(datasetManager) {
this.manager = datasetManager;
}
/**
* Analysera gap för en stad
*/
analyzeCity(cityId) {
const city = this.manager.getReferenceCity(cityId);
if (!city) return null;
const report = this.manager.getCoverageReport(cityId);
if (!report) return null;
const analysis = {
city: city.name,
overallCoverage: report.overall,
criticalGaps: [],
warnings: [],
opportunities: [],
missions: [],
};
// Analysera varje kategori
for (const [category, coverage] of Object.entries(report.categories)) {
const percentage = parseFloat(coverage.percentage);
// Critical: < 50%
if (percentage < 50) {
analysis.criticalGaps.push({
category,
coverage: percentage,
message: `${category}: Only ${percentage}% coverage. Critical gap.`,
});
// Generera uppdrag
analysis.missions.push(this.generateMission(city, category, coverage, 'critical'));
}
// Warning: 50-80%
else if (percentage < 80) {
analysis.warnings.push({
category,
coverage: percentage,
message: `${category}: ${percentage}% coverage. Below target.`,
});
analysis.missions.push(this.generateMission(city, category, coverage, 'warning'));
}
// Analysera tidpunkter
if (coverage.byTimeOfDay) {
const nightCoverage = coverage.byTimeOfDay.night || 0;
const totalNight = (coverage.byTimeOfDay.night || 0) + (coverage.byTimeOfDay.day || 0) + (coverage.byTimeOfDay.evening || 0);
const nightPercentage = totalNight > 0 ? (nightCoverage / totalNight * 100).toFixed(1) : 0;
if (nightPercentage < 20) {
analysis.opportunities.push({
category,
type: 'night_coverage',
message: `${category}: Only ${nightPercentage}% night coverage. Collect night observations.`,
potentialValue: 'high',
});
analysis.missions.push(this.generateTimeMission(city, category, 'night'));
}
}
// Analysera väder
if (coverage.byWeather) {
const rainCoverage = coverage.byWeather.rain || 0;
if (rainCoverage < 50) {
analysis.opportunities.push({
category,
type: 'weather_coverage',
message: `${category}: Only ${rainCoverage} rain observations. Collect rainy weather data.`,
potentialValue: 'medium',
});
}
}
}
// Sortera uppdrag efter prioritet
analysis.missions.sort((a, b) => {
const priorityOrder = { critical: 0, warning: 1, opportunity: 2 };
return priorityOrder[a.priority] - priorityOrder[b.priority];
});
return analysis;
}
/**
* Generera uppdrag för gap
*/
generateMission(city, category, coverage, priority) {
const needed = Math.ceil((100 - parseFloat(coverage.percentage)) * 10); // 10 obs per %
return {
id: `mission_${city.id}_${category}_${Date.now()}`,
city: city.name,
category,
priority,
type: 'coverage_gap',
description: `Collect ${needed} ${category} observations in ${city.name}`,
target: {
count: needed,
coverage: 100,
},
current: {
count: coverage.found,
coverage: parseFloat(coverage.percentage),
},
conditions: {
timeOfDay: this.suggestTimeOfDay(coverage),
weather: this.suggestWeather(coverage),
season: this.suggestSeason(coverage),
},
reward: this.calculateReward(priority, needed),
expiresAt: new Date(Date.now() + 7 * 86400000).toISOString(), // 7 dagar
};
}
/**
* Generera tidsbaserat uppdrag
*/
generateTimeMission(city, category, timeOfDay) {
return {
id: `mission_${city.id}_${category}_${timeOfDay}_${Date.now()}`,
city: city.name,
category,
priority: 'opportunity',
type: 'time_coverage',
description: `Collect ${category} observations during ${timeOfDay} in ${city.name}`,
target: {
count: 50,
timeOfDay,
},
conditions: {
timeOfDay,
weather: 'any',
},
reward: this.calculateReward('opportunity', 50),
expiresAt: new Date(Date.now() + 14 * 86400000).toISOString(), // 14 dagar
};
}
/**
* Föreslå tid på dygnet
*/
suggestTimeOfDay(coverage) {
const times = coverage.byTimeOfDay || {};
const min = Math.min(times.day || 0, times.evening || 0, times.night || 0);
if (min === times.night || 0) return 'night';
if (min === times.evening || 0) return 'evening';
return 'day';
}
/**
* Föreslå väder
*/
suggestWeather(coverage) {
const weather = coverage.byWeather || {};
const min = Math.min(weather.clear || 0, weather.cloudy || 0, weather.rain || 0);
if (min === weather.rain || 0) return 'rain';
if (min === weather.cloudy || 0) return 'cloudy';
return 'any';
}
/**
* Föreslå säsong
*/
suggestSeason(coverage) {
const seasons = coverage.bySeason || {};
const min = Math.min(seasons.summer || 0, seasons.winter || 0, seasons.spring || 0, seasons.fall || 0);
if (min === seasons.winter || 0) return 'winter';
if (min === seasons.spring || 0) return 'spring';
if (min === seasons.fall || 0) return 'fall';
return 'any';
}
/**
* Beräkna belöning
*/
calculateReward(priority, count) {
const baseReward = {
critical: 10,
warning: 5,
opportunity: 3,
};
return baseReward[priority] * count;
}
/**
* Generera rapport för alla städer
*/
generateGlobalReport() {
const cities = this.manager.listReferenceCities();
const report = {
generatedAt: new Date().toISOString(),
totalCities: cities.length,
cities: [],
globalGaps: [],
recommendedMissions: [],
};
for (const city of cities) {
const analysis = this.analyzeCity(city.id);
if (analysis) {
report.cities.push(analysis);
report.recommendedMissions.push(...analysis.missions);
}
}
// Identifiera globala gap (saknas i alla städer)
const categoryCoverage = {};
for (const city of report.cities) {
for (const gap of city.criticalGaps) {
if (!categoryCoverage[gap.category]) {
categoryCoverage[gap.category] = [];
}
categoryCoverage[gap.category].push(city.city);
}
}
for (const [category, cities] of Object.entries(categoryCoverage)) {
if (cities.length === report.totalCities) {
report.globalGaps.push({
category,
affectedCities: cities,
message: `${category} is critically underrepresented in all cities`,
});
}
}
// Sortera uppdrag
report.recommendedMissions.sort((a, b) => b.reward - a.reward);
return report;
}
}
module.exports = CoverageGapAnalyzer;
// Demo
if (require.main === module) {
const UrbanFoundationDatasetManager = require('./manager');
const manager = new UrbanFoundationDatasetManager();
// Lägg till städer med låg coverage
const bangkok = manager.addReferenceCity({
name: 'Bangkok',
country: 'TH',
climate: 'tropical',
type: 'tropical_megacity',
location: { lat: 13.7563, lng: 100.5018 },
});
// Lägg till coverage med gap
manager.updateCoverage(bangkok.id, 'street_lamp', {
total: 1000,
found: 940,
byTimeOfDay: { day: 500, evening: 300, night: 140 },
});
manager.updateCoverage(bangkok.id, 'traffic_sign', {
total: 500,
found: 200,
byTimeOfDay: { day: 150, evening: 40, night: 10 },
});
manager.updateCoverage(bangkok.id, 'manhole', {
total: 300,
found: 80,
byTimeOfDay: { day: 60, evening: 15, night: 5 },
});
// Analysera
const analyzer = new CoverageGapAnalyzer(manager);
const analysis = analyzer.analyzeCity(bangkok.id);
console.log('\n=== BANGKOK GAP ANALYSIS ===');
console.log(JSON.stringify(analysis, null, 2));
// Generera global rapport
const globalReport = analyzer.generateGlobalReport();
console.log('\n=== GLOBAL REPORT ===');
console.log(`Total missions: ${globalReport.recommendedMissions.length}`);
console.log(`Top mission: ${globalReport.recommendedMissions[0]?.description}`);
}