v0.9.5: The Voltron Update — modular architecture, stable IDs, parallelized boot

- Parallelized startup (60s → 15s) via ThreadPoolExecutor
- Adaptive polling engine with ETag caching (no more bbox interrupts)
- useCallback optimization for interpolation functions
- Sliding LAYERS/INTEL edge panels replace bulky Record Panel
- Modular fetcher architecture (flights, geo, infrastructure, financial, earth_observation)
- Stable entity IDs for GDELT & News popups (PR #63, credit @csysp)
- Admin auth (X-Admin-Key), rate limiting (slowapi), auto-updater
- Docker Swarm secrets support, env_check.py validation
- 85+ vitest tests, CI pipeline, geoJSON builder extraction
- Server-side viewport bbox filtering reduces payloads 80%+

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Former-commit-id: f2883150b5bc78ebc139d89cc966a76f7d7c0408
This commit is contained in:
anoracleofra-code
2026-03-14 14:01:54 -06:00
parent 60c90661d4
commit 90c2e90e2c
63 changed files with 6015 additions and 2756 deletions
@@ -0,0 +1,96 @@
import { describe, it, expect } from 'vitest';
import { classifyAircraft, HELI_TYPES, TURBOPROP_TYPES, BIZJET_TYPES } from '@/utils/aircraftClassification';
describe('classifyAircraft', () => {
// ─── Helicopter classification ────────────────────────────────────────────
it('classifies known helicopter types', () => {
const heliModels = ['R22', 'R44', 'B407', 'S76', 'EC35', 'H145', 'UH60', 'AH64', 'CH47'];
for (const model of heliModels) {
expect(classifyAircraft(model)).toBe('heli');
}
});
it('classifies as heli when category hint is "heli"', () => {
expect(classifyAircraft('UNKNOWN', 'heli')).toBe('heli');
});
it('category hint "heli" overrides model-based classification', () => {
// B738 would normally be airliner, but category says heli
expect(classifyAircraft('B738', 'heli')).toBe('heli');
});
// ─── Business jet classification ──────────────────────────────────────────
it('classifies known bizjet types', () => {
const bizjetModels = ['C25A', 'C680', 'CL60', 'GLEX', 'GLF5', 'LJ45', 'FA7X'];
for (const model of bizjetModels) {
expect(classifyAircraft(model)).toBe('bizjet');
}
});
// ─── Turboprop classification ─────────────────────────────────────────────
it('classifies known turboprop types', () => {
const turbopropModels = ['AT72', 'C208', 'DHC6', 'DH8D', 'PC12', 'TBM9', 'C130'];
for (const model of turbopropModels) {
expect(classifyAircraft(model)).toBe('turboprop');
}
});
// ─── Airliner default ────────────────────────────────────────────────────
it('defaults to airliner for unknown types', () => {
expect(classifyAircraft('B738')).toBe('airliner');
expect(classifyAircraft('A320')).toBe('airliner');
expect(classifyAircraft('B77W')).toBe('airliner');
});
it('defaults to airliner for empty model string', () => {
expect(classifyAircraft('')).toBe('airliner');
});
// ─── Case insensitivity ──────────────────────────────────────────────────
it('handles lowercase model codes', () => {
expect(classifyAircraft('r22')).toBe('heli');
expect(classifyAircraft('c25a')).toBe('bizjet');
expect(classifyAircraft('at72')).toBe('turboprop');
});
it('handles mixed case model codes', () => {
expect(classifyAircraft('Dh8D')).toBe('turboprop');
expect(classifyAircraft('Glf5')).toBe('bizjet');
});
// ─── Priority order ──────────────────────────────────────────────────────
it('prioritizes heli over bizjet (if type appears in both sets)', () => {
// heli check comes first in the function
for (const model of ['B06', 'S92', 'H225']) {
expect(classifyAircraft(model)).toBe('heli');
}
});
it('prioritizes bizjet over turboprop', () => {
// PC24 appears in both BIZJET_TYPES and TURBOPROP_TYPES
// bizjet check comes before turboprop in the function
if (BIZJET_TYPES.has('PC24') && TURBOPROP_TYPES.has('PC24')) {
expect(classifyAircraft('PC24')).toBe('bizjet');
}
});
// ─── Set integrity ───────────────────────────────────────────────────────
it('HELI_TYPES set has expected minimum entries', () => {
expect(HELI_TYPES.size).toBeGreaterThan(50);
});
it('TURBOPROP_TYPES set has expected minimum entries', () => {
expect(TURBOPROP_TYPES.size).toBeGreaterThan(80);
});
it('BIZJET_TYPES set has expected minimum entries', () => {
expect(BIZJET_TYPES.size).toBeGreaterThan(50);
});
});
@@ -0,0 +1,116 @@
import { describe, it, expect } from 'vitest';
import { interpolatePosition } from '@/utils/positioning';
describe('interpolatePosition', () => {
// ─── No-op cases ──────────────────────────────────────────────────────────
it('returns same position when speed is zero', () => {
const [lat, lng] = interpolatePosition(40, -74, 90, 0, 10);
expect(lat).toBe(40);
expect(lng).toBe(-74);
});
it('returns same position when speed is negative', () => {
const [lat, lng] = interpolatePosition(40, -74, 90, -50, 10);
expect(lat).toBe(40);
expect(lng).toBe(-74);
});
it('returns same position when dt is zero', () => {
const [lat, lng] = interpolatePosition(40, -74, 90, 100, 0);
expect(lat).toBe(40);
expect(lng).toBe(-74);
});
it('returns same position when dt is negative', () => {
const [lat, lng] = interpolatePosition(40, -74, 90, 100, -5);
expect(lat).toBe(40);
expect(lng).toBe(-74);
});
// ─── Cardinal directions ─────────────────────────────────────────────────
it('moves north when heading is 0°', () => {
const [lat, lng] = interpolatePosition(40, -74, 0, 100, 10);
expect(lat).toBeGreaterThan(40);
expect(lng).toBeCloseTo(-74, 4); // longitude should barely change
});
it('moves south when heading is 180°', () => {
const [lat, lng] = interpolatePosition(40, -74, 180, 100, 10);
expect(lat).toBeLessThan(40);
expect(lng).toBeCloseTo(-74, 4);
});
it('moves east when heading is 90°', () => {
const [lat, lng] = interpolatePosition(40, -74, 90, 100, 10);
expect(lat).toBeCloseTo(40, 4);
expect(lng).toBeGreaterThan(-74);
});
it('moves west when heading is 270°', () => {
const [lat, lng] = interpolatePosition(40, -74, 270, 100, 10);
expect(lat).toBeCloseTo(40, 4);
expect(lng).toBeLessThan(-74);
});
// ─── Distance proportionality ────────────────────────────────────────────
it('doubles distance when speed doubles', () => {
const [lat1] = interpolatePosition(0, 0, 0, 100, 10);
const [lat2] = interpolatePosition(0, 0, 0, 200, 10);
const dist1 = lat1; // distance from origin going north
const dist2 = lat2;
expect(dist2).toBeCloseTo(dist1 * 2, 4);
});
it('doubles distance when time doubles', () => {
const [lat1] = interpolatePosition(0, 0, 0, 100, 10);
const [lat2] = interpolatePosition(0, 0, 0, 100, 20);
const dist1 = lat1;
const dist2 = lat2;
expect(dist2).toBeCloseTo(dist1 * 2, 4);
});
// ─── Clamping ────────────────────────────────────────────────────────────
it('clamps time to maxDt (prevents drift on stale data)', () => {
// maxDt=65 by default, so dt=1000 should give same result as dt=65
const [lat1] = interpolatePosition(0, 0, 0, 100, 65);
const [lat2] = interpolatePosition(0, 0, 0, 100, 1000);
expect(lat1).toBeCloseTo(lat2, 6);
});
it('clamps distance to maxDist when specified', () => {
// At 100 knots for 60 seconds = ~3086m, maxDist=1000 should cap it
const [lat1] = interpolatePosition(0, 0, 0, 100, 60, 1000);
const [lat2] = interpolatePosition(0, 0, 0, 100, 60, 0); // no cap
expect(lat1).toBeLessThan(lat2);
});
// ─── Known calculation ───────────────────────────────────────────────────
it('produces correct magnitude for known speed/time', () => {
// 1 knot = 1 NM/hr = 1852 m/hr ≈ 0.5144 m/s
// 100 knots for 10 seconds = 514.4 meters
// At equator, 1° lat ≈ 111,320m, so 514.4m ≈ 0.00462°
const [lat] = interpolatePosition(0, 0, 0, 100, 10);
const expectedDegrees = (100 * 0.5144 * 10) / 111320;
expect(lat).toBeCloseTo(expectedDegrees, 4);
});
// ─── Edge cases ──────────────────────────────────────────────────────────
it('handles positions near the poles', () => {
const [lat, lng] = interpolatePosition(89.9, 0, 0, 10, 5);
expect(lat).toBeGreaterThan(89.9);
expect(Number.isFinite(lat)).toBe(true);
expect(Number.isFinite(lng)).toBe(true);
});
it('handles positions near the dateline', () => {
const [lat, lng] = interpolatePosition(0, 179.99, 90, 100, 10);
expect(Number.isFinite(lat)).toBe(true);
expect(Number.isFinite(lng)).toBe(true);
});
});
@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest';
import { computeNightPolygon } from '@/utils/solarTerminator';
/** Extract polygon ring from result (type-narrowing helper) */
function getRing(result: GeoJSON.FeatureCollection): number[][] {
const geom = result.features[0].geometry;
if (geom.type !== 'Polygon') throw new Error('Expected Polygon geometry');
return geom.coordinates[0];
}
describe('computeNightPolygon', () => {
// ─── Structure validation ────────────────────────────────────────────────
it('returns a valid GeoJSON FeatureCollection', () => {
const result = computeNightPolygon();
expect(result.type).toBe('FeatureCollection');
expect(result.features).toHaveLength(1);
expect(result.features[0].type).toBe('Feature');
expect(result.features[0].geometry.type).toBe('Polygon');
});
it('polygon has at least 360 vertices (one per degree of longitude)', () => {
const ring = getRing(computeNightPolygon());
// 361 terminator points + 2 closing corners + 1 ring-close = ≥364
expect(ring.length).toBeGreaterThanOrEqual(364);
});
it('polygon ring is closed (first and last points match)', () => {
const ring = getRing(computeNightPolygon());
expect(ring[ring.length - 1]).toEqual(ring[0]);
});
// ─── Coordinate bounds ───────────────────────────────────────────────────
it('all coordinates are within valid lat/lng bounds', () => {
const ring = getRing(computeNightPolygon());
for (const [lng, lat] of ring) {
expect(lng).toBeGreaterThanOrEqual(-180);
expect(lng).toBeLessThanOrEqual(180);
expect(lat).toBeGreaterThanOrEqual(-85);
expect(lat).toBeLessThanOrEqual(85);
}
});
// ─── Deterministic for same input ────────────────────────────────────────
it('returns identical result for the same date', () => {
const date = new Date('2024-06-21T12:00:00Z');
const result1 = computeNightPolygon(date);
const result2 = computeNightPolygon(date);
expect(result1).toEqual(result2);
});
// ─── Seasonal behavior ──────────────────────────────────────────────────
it('equinox produces roughly symmetric polygon', () => {
const equinox = new Date('2024-03-20T12:00:00Z');
const ring = getRing(computeNightPolygon(equinox));
const lats = ring.map(([, lat]: number[]) => lat);
const maxLat = Math.max(...lats);
const minLat = Math.min(...lats);
expect(maxLat).toBeGreaterThan(50);
expect(minLat).toBeLessThan(-50);
});
it('summer solstice shifts night polygon southward', () => {
const summer = new Date('2024-06-21T00:00:00Z');
const ring = getRing(computeNightPolygon(summer));
const terminatorLats = ring
.filter(([lng]: number[]) => lng >= -180 && lng <= 180)
.slice(0, 361)
.map(([, lat]: number[]) => lat);
const avgLat = terminatorLats.reduce((a: number, b: number) => a + b, 0) / terminatorLats.length;
expect(avgLat).toBeLessThan(15);
});
// ─── Different times produce different results ──────────────────────────
it('produces different polygons for different times of day', () => {
const morning = new Date('2024-06-21T06:00:00Z');
const evening = new Date('2024-06-21T18:00:00Z');
const ringM = getRing(computeNightPolygon(morning));
const ringE = getRing(computeNightPolygon(evening));
expect(ringM[0]).not.toEqual(ringE[0]);
});
});