File size: 5,245 Bytes
ec8acdf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
PRIORITY_COUNTRIES,
EXPOSURE_CENTROIDS,
computeBoundedExposure,
computeExposure,
getRadiusForEventType,
listCountryPopulations,
MAX_EXPOSURE_RADIUS_KM,
} from '../shared/analysis-population-exposure.ts';
import { getPopulationExposure } from '../server/worldmonitor/displacement/v1/get-population-exposure.ts';
import {
Z_THRESHOLD_LOW,
Z_THRESHOLD_MEDIUM,
Z_THRESHOLD_HIGH,
getBaselineSeverity,
} from '../shared/analysis-temporal-severity.ts';
describe('analysis-population-exposure core', () => {
it('keeps the priority table and centroids aligned', () => {
const countryCodes = Object.keys(PRIORITY_COUNTRIES).sort();
const centroidCodes = Object.keys(EXPOSURE_CENTROIDS).sort();
assert.deepEqual(countryCodes, centroidCodes);
assert.equal(countryCodes.length, 20);
for (const [code, [lat, lon]] of Object.entries(EXPOSURE_CENTROIDS)) {
assert.ok(lat >= -90 && lat <= 90, `${code} lat in range`);
assert.ok(lon >= -180 && lon <= 180, `${code} lon in range`);
}
});
it('picks the nearest centroid and applies density * area', () => {
// Point right on Israel's centroid.
const exposure = computeExposure(31.0, 34.8, 50);
assert.equal(exposure.nearestCountry, 'ISR');
const density = PRIORITY_COUNTRIES.ISR.pop / PRIORITY_COUNTRIES.ISR.area;
assert.equal(exposure.densityPerKm2, Math.round(density));
assert.equal(exposure.exposedPopulation, Math.round(density * Math.PI * 50 * 50));
assert.equal(exposure.exposureRadiusKm, 50);
});
it('resolves interior points, not just exact centroids', () => {
// Kyiv is nearer the Ukraine centroid than any other.
const exposure = computeExposure(50.45, 30.52, 100);
assert.equal(exposure.nearestCountry, 'UKR');
});
it('maps event types to radii with a 50km default', () => {
assert.equal(getRadiusForEventType('earthquake'), 100);
assert.equal(getRadiusForEventType('flood'), 100);
assert.equal(getRadiusForEventType('wildfire'), 30);
assert.equal(getRadiusForEventType('fire'), 30);
assert.equal(getRadiusForEventType('battle'), 50);
assert.equal(getRadiusForEventType('state-based'), 50);
assert.equal(getRadiusForEventType('unknown-thing'), 50);
});
it('lists country populations with rounded densities', () => {
const countries = listCountryPopulations();
assert.equal(countries.length, 20);
const india = countries.find((c) => c.code === 'IND');
assert.ok(india);
assert.equal(india.population, PRIORITY_COUNTRIES.IND.pop);
assert.equal(
india.densityPerKm2,
Math.round(PRIORITY_COUNTRIES.IND.pop / PRIORITY_COUNTRIES.IND.area),
);
});
});
describe('analysis-temporal-severity core', () => {
it('exports the canonical z-score thresholds', () => {
assert.equal(Z_THRESHOLD_LOW, 1.5);
assert.equal(Z_THRESHOLD_MEDIUM, 2.0);
assert.equal(Z_THRESHOLD_HIGH, 3.0);
});
it('classifies severity bands exactly at the boundaries', () => {
assert.equal(getBaselineSeverity(3.0), 'critical');
assert.equal(getBaselineSeverity(4.7), 'critical');
assert.equal(getBaselineSeverity(2.999), 'high');
assert.equal(getBaselineSeverity(2.0), 'high');
assert.equal(getBaselineSeverity(1.999), 'medium');
assert.equal(getBaselineSeverity(1.5), 'medium');
assert.equal(getBaselineSeverity(1.499), 'normal');
assert.equal(getBaselineSeverity(0), 'normal');
});
});
describe('exposure radius clamping', () => {
it('caps free-form agent radii so an absurd request cannot return an absurd population', () => {
// Unclamped, radius 20000 returned ~5.6e11 people — roughly 69x the world
// population — because density is multiplied across the whole disc.
const wild = computeBoundedExposure(31.0, 34.8, 20000);
assert.equal(wild.exposureRadiusKm, MAX_EXPOSURE_RADIUS_KM, 'result reports the radius actually used');
const capped = computeExposure(31.0, 34.8, MAX_EXPOSURE_RADIUS_KM);
assert.equal(wild.exposedPopulation, capped.exposedPopulation);
assert.ok(wild.exposedPopulation < 8.1e9 * 10, 'stays within an order of magnitude of world population');
});
it('leaves in-range radii untouched', () => {
for (const r of [1, 30, 50, 100, MAX_EXPOSURE_RADIUS_KM]) {
assert.equal(computeExposure(31.0, 34.8, r).exposureRadiusKm, r);
}
});
it('treats non-finite and negative radii as zero rather than NaN', () => {
for (const bad of [Number.NaN, Number.POSITIVE_INFINITY, -5]) {
const result = computeBoundedExposure(31.0, 34.8, bad);
assert.equal(result.exposedPopulation, 0);
assert.equal(result.exposureRadiusKm, 0);
}
});
});
describe('v1 population-exposure compatibility', () => {
it('preserves the existing unbounded REST radius contract', async () => {
const radius = MAX_EXPOSURE_RADIUS_KM + 1;
const result = await getPopulationExposure(
/** @type {never} */ ({}),
{ mode: 'exposure', lat: 31.0, lon: 34.8, radius },
);
assert.equal(result.exposure?.exposureRadiusKm, radius);
assert.deepEqual(result.exposure, computeExposure(31.0, 34.8, radius));
});
});
|