bae705aa97
- 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
354 lines
11 KiB
JavaScript
354 lines
11 KiB
JavaScript
/**
|
||
* Urban Ontology Validation
|
||
*
|
||
* Testar om relationerna ger nytta med verkliga observationer.
|
||
*
|
||
* Exempel:
|
||
* - Om ett elskåp markeras som ur funktion – vilka objekt påverkas?
|
||
* - Om en gatlykta försvinner – vilka vägavsnitt tappar belysning?
|
||
* - Om ett träd växer – vilka skyltar riskerar att skymmas?
|
||
*/
|
||
|
||
const UrbanOntology = require('../urban-ontology/ontology');
|
||
|
||
class OntologyValidation {
|
||
constructor() {
|
||
this.ontology = new UrbanOntology();
|
||
this.results = [];
|
||
}
|
||
|
||
/**
|
||
* ============================================================
|
||
* TEST 1: Power Failure Impact
|
||
* ============================================================
|
||
*/
|
||
|
||
testPowerFailure() {
|
||
console.log('\n=== TEST 1: Power Failure Impact ===\n');
|
||
|
||
// Skapa scenario: Elskåp EC-001 går sönder
|
||
const cabinetId = 'cabinet_sukhumvit_001';
|
||
|
||
// Lägg till relationer
|
||
this.ontology.addRelation(cabinetId, 'lamp_sukhumvit_001', 'powers', {
|
||
voltage: 230,
|
||
capacity: 1000,
|
||
});
|
||
|
||
this.ontology.addRelation(cabinetId, 'lamp_sukhumvit_002', 'powers', {
|
||
voltage: 230,
|
||
capacity: 1000,
|
||
});
|
||
|
||
this.ontology.addRelation(cabinetId, 'lamp_sukhumvit_003', 'powers', {
|
||
voltage: 230,
|
||
capacity: 1000,
|
||
});
|
||
|
||
this.ontology.addRelation('lamp_sukhumvit_001', 'road_sukhumvit_001', 'illuminates', {
|
||
intensity: 50,
|
||
coverage: 10,
|
||
});
|
||
|
||
this.ontology.addRelation('lamp_sukhumvit_002', 'road_sukhumvit_002', 'illuminates', {
|
||
intensity: 50,
|
||
coverage: 10,
|
||
});
|
||
|
||
// Kör impact analysis
|
||
const impact = this.ontology.impactAnalysis(cabinetId, 'power');
|
||
|
||
console.log(`Source: ${impact.source}`);
|
||
console.log(`Failure type: ${impact.failureType}`);
|
||
console.log(`Impacted objects: ${impact.impactCount}`);
|
||
console.log(`Objects: ${impact.impactedObjects.join(', ')}`);
|
||
|
||
// Validering
|
||
const expectedImpacted = 4; // 3 lamps + 2 roads (via illuminates)
|
||
const passed = impact.impactCount >= 3; // Minst 3 lampor
|
||
|
||
this.results.push({
|
||
test: 'Power Failure Impact',
|
||
passed,
|
||
impactCount: impact.impactCount,
|
||
expectedMin: 3,
|
||
});
|
||
|
||
console.log(`\nResult: ${passed ? 'PASS' : 'FAIL'}`);
|
||
console.log(`Expected: At least 3 impacted objects`);
|
||
console.log(`Actual: ${impact.impactCount} impacted objects`);
|
||
|
||
return impact;
|
||
}
|
||
|
||
/**
|
||
* ============================================================
|
||
* TEST 2: Street Lamp Outage
|
||
* ============================================================
|
||
*/
|
||
|
||
testStreetLampOutage() {
|
||
console.log('\n=== TEST 2: Street Lamp Outage ===\n');
|
||
|
||
// Scenario: Gatlykta SL-042 släcks
|
||
const lampId = 'lamp_sukhumvit_042';
|
||
|
||
// Lägg till relationer
|
||
this.ontology.addRelation(lampId, 'road_sukhumvit_042', 'illuminates', {
|
||
intensity: 50,
|
||
coverage: 15,
|
||
});
|
||
|
||
this.ontology.addRelation(lampId, 'crosswalk_sukhumvit_042', 'illuminates', {
|
||
intensity: 30,
|
||
coverage: 5,
|
||
});
|
||
|
||
this.ontology.addRelation('traffic_light_sukhumvit_042', 'crosswalk_sukhumvit_042', 'regulates', {
|
||
regulationType: 'pedestrian_crossing',
|
||
});
|
||
|
||
// Kör impact analysis
|
||
const impact = this.ontology.impactAnalysis(lampId, 'lighting');
|
||
|
||
console.log(`Source: ${impact.source}`);
|
||
console.log(`Failure type: ${impact.failureType}`);
|
||
console.log(`Impacted objects: ${impact.impactCount}`);
|
||
console.log(`Objects: ${impact.impactedObjects.join(', ')}`);
|
||
|
||
// Validering
|
||
const hasRoad = impact.impactedObjects.some(id => id.includes('road'));
|
||
const hasCrosswalk = impact.impactedObjects.some(id => id.includes('crosswalk'));
|
||
const passed = hasRoad && hasCrosswalk;
|
||
|
||
this.results.push({
|
||
test: 'Street Lamp Outage',
|
||
passed,
|
||
hasRoad,
|
||
hasCrosswalk,
|
||
});
|
||
|
||
console.log(`\nResult: ${passed ? 'PASS' : 'FAIL'}`);
|
||
console.log(`Road impacted: ${hasRoad}`);
|
||
console.log(`Crosswalk impacted: ${hasCrosswalk}`);
|
||
|
||
return impact;
|
||
}
|
||
|
||
/**
|
||
* ============================================================
|
||
* TEST 3: Tree Growth Risk
|
||
* ============================================================
|
||
*/
|
||
|
||
testTreeGrowthRisk() {
|
||
console.log('\n=== TEST 3: Tree Growth Risk ===\n');
|
||
|
||
// Scenario: Träd växer och riskerar skymma skylt
|
||
const treeId = 'tree_sukhumvit_017';
|
||
|
||
// Lägg till relationer
|
||
this.ontology.addRelation(treeId, 'sign_sukhumvit_017', 'may_obscure', {
|
||
currentObscuration: 0.3,
|
||
growthRate: 0.5, // meters/year
|
||
projectedObscuration: 0.85,
|
||
});
|
||
|
||
this.ontology.addRelation(treeId, 'sign_sukhumvit_018', 'may_obscure', {
|
||
currentObscuration: 0.1,
|
||
growthRate: 0.3,
|
||
projectedObscuration: 0.45,
|
||
});
|
||
|
||
// Kör growth risk analysis
|
||
const risk = this.ontology.growthRiskAnalysis(treeId, 5);
|
||
|
||
console.log(`Vegetation: ${risk.vegetationId}`);
|
||
console.log(`Time horizon: ${risk.timeHorizonYears} years`);
|
||
console.log(`High risk count: ${risk.highRiskCount}`);
|
||
|
||
console.log('\nRisks:');
|
||
risk.risks.forEach(r => {
|
||
console.log(` ${r.targetId}: ${r.riskLevel}`);
|
||
console.log(` Current: ${r.currentObscuration}`);
|
||
console.log(` Projected: ${r.projectedObscuration}`);
|
||
console.log(` Action: ${r.recommendedAction}`);
|
||
});
|
||
|
||
// Validering
|
||
const hasHighRisk = risk.highRiskCount > 0;
|
||
const hasAction = risk.risks.some(r => r.recommendedAction.includes('Prune'));
|
||
const passed = hasHighRisk && hasAction;
|
||
|
||
this.results.push({
|
||
test: 'Tree Growth Risk',
|
||
passed,
|
||
highRiskCount: risk.highRiskCount,
|
||
hasAction,
|
||
});
|
||
|
||
console.log(`\nResult: ${passed ? 'PASS' : 'FAIL'}`);
|
||
console.log(`High risk found: ${hasHighRisk}`);
|
||
console.log(`Action recommended: ${hasAction}`);
|
||
|
||
return risk;
|
||
}
|
||
|
||
/**
|
||
* ============================================================
|
||
* TEST 4: Path Analysis
|
||
* ============================================================
|
||
*/
|
||
|
||
testPathAnalysis() {
|
||
console.log('\n=== TEST 4: Path Analysis ===\n');
|
||
|
||
// Scenario: Hitta väg från elskåp till väg
|
||
const cabinetId = 'cabinet_sukhumvit_001';
|
||
const roadId = 'road_sukhumvit_001';
|
||
|
||
// Kör path analysis
|
||
const path = this.ontology.findPath(cabinetId, roadId, 5);
|
||
|
||
console.log(`From: ${cabinetId}`);
|
||
console.log(`To: ${roadId}`);
|
||
console.log(`Found: ${path.found}`);
|
||
console.log(`Length: ${path.length}`);
|
||
|
||
if (path.found) {
|
||
console.log('\nPath:');
|
||
path.path.forEach((step, i) => {
|
||
console.log(` Step ${i + 1}: ${step.objectId} via ${step.relation?.type || 'direct'}`);
|
||
});
|
||
}
|
||
|
||
// Validering
|
||
const passed = path.found && path.length > 0;
|
||
|
||
this.results.push({
|
||
test: 'Path Analysis',
|
||
passed,
|
||
pathLength: path.length,
|
||
found: path.found,
|
||
});
|
||
|
||
console.log(`\nResult: ${passed ? 'PASS' : 'FAIL'}`);
|
||
console.log(`Path found: ${path.found}`);
|
||
console.log(`Path length: ${path.length}`);
|
||
|
||
return path;
|
||
}
|
||
|
||
/**
|
||
* ============================================================
|
||
* TEST 5: Network Analysis
|
||
* ============================================================
|
||
*/
|
||
|
||
testNetworkAnalysis() {
|
||
console.log('\n=== TEST 5: Network Analysis ===\n');
|
||
|
||
// Scenario: Hitta alla objekt i nätverket runt elskåp
|
||
const cabinetId = 'cabinet_sukhumvit_001';
|
||
|
||
// Kör network analysis
|
||
const network = this.ontology.networkAnalysis(cabinetId, { maxDepth: 3 });
|
||
|
||
console.log(`Seed: ${network.seedObjectId}`);
|
||
console.log(`Network size: ${network.networkSize}`);
|
||
console.log(`Objects: ${network.objects.join(', ')}`);
|
||
|
||
// Validering
|
||
const hasLamps = network.objects.some(id => id.includes('lamp'));
|
||
const hasRoads = network.objects.some(id => id.includes('road'));
|
||
const passed = hasLamps && hasRoads;
|
||
|
||
this.results.push({
|
||
test: 'Network Analysis',
|
||
passed,
|
||
networkSize: network.networkSize,
|
||
hasLamps,
|
||
hasRoads,
|
||
});
|
||
|
||
console.log(`\nResult: ${passed ? 'PASS' : 'FAIL'}`);
|
||
console.log(`Network size: ${network.networkSize}`);
|
||
console.log(`Has lamps: ${hasLamps}`);
|
||
console.log(`Has roads: ${hasRoads}`);
|
||
|
||
return network;
|
||
}
|
||
|
||
/**
|
||
* ============================================================
|
||
* SUMMARY
|
||
* ============================================================
|
||
*/
|
||
|
||
generateReport() {
|
||
console.log('\n╔════════════════════════════════════════════════════════════╗');
|
||
console.log('║ ONTOLOGY VALIDATION REPORT ║');
|
||
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
||
|
||
const passed = this.results.filter(r => r.passed).length;
|
||
const total = this.results.length;
|
||
|
||
console.log(`Tests run: ${total}`);
|
||
console.log(`Passed: ${passed}`);
|
||
console.log(`Failed: ${total - passed}`);
|
||
console.log(`Success rate: ${(passed / total * 100).toFixed(1)}%\n`);
|
||
|
||
console.log('Detailed Results:');
|
||
this.results.forEach((result, i) => {
|
||
console.log(`\n${i + 1}. ${result.test}`);
|
||
console.log(` Status: ${result.passed ? 'PASS' : 'FAIL'}`);
|
||
|
||
if (result.impactCount !== undefined) {
|
||
console.log(` Impact count: ${result.impactCount}`);
|
||
}
|
||
if (result.highRiskCount !== undefined) {
|
||
console.log(` High risk count: ${result.highRiskCount}`);
|
||
}
|
||
if (result.pathLength !== undefined) {
|
||
console.log(` Path length: ${result.pathLength}`);
|
||
}
|
||
if (result.networkSize !== undefined) {
|
||
console.log(` Network size: ${result.networkSize}`);
|
||
}
|
||
});
|
||
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log(`OVERALL: ${passed === total ? 'ALL TESTS PASSED' : 'SOME TESTS FAILED'}`);
|
||
console.log('='.repeat(60));
|
||
|
||
return {
|
||
total,
|
||
passed,
|
||
failed: total - passed,
|
||
successRate: passed / total,
|
||
results: this.results,
|
||
};
|
||
}
|
||
}
|
||
|
||
// Run validation
|
||
if (require.main === module) {
|
||
const validation = new OntologyValidation();
|
||
|
||
console.log('╔════════════════════════════════════════════════════════════╗');
|
||
console.log('║ URBAN ONTOLOGY VALIDATION ║');
|
||
console.log('╚════════════════════════════════════════════════════════════╝');
|
||
|
||
validation.testPowerFailure();
|
||
validation.testStreetLampOutage();
|
||
validation.testTreeGrowthRisk();
|
||
validation.testPathAnalysis();
|
||
validation.testNetworkAnalysis();
|
||
|
||
const report = validation.generateReport();
|
||
|
||
console.log('\n✅ Ontology Validation complete!');
|
||
}
|
||
|
||
module.exports = OntologyValidation;
|