Files
worldmonitor/src/services/gdacs.ts
Elie Habib 0e844b7348 chore(cache): bump all sub-5min cache TTLs and polling intervals (#626)
Audit and raise every cache/poll under 5 min to reduce upstream API
pressure and Vercel function invocations across 31 files:

Frontend polling: markets 4→8min, crypto 4→8min, predictions 5→10min,
natural 5→1hr, cableHealth 5→2hr, oref 10s→2min, maritime 30s→5min,
IntelligenceGapBadge 1→3min.

Circuit breakers: PizzINT 2→30min, aviation 5→20min, seismology/weather/
outages/statuses/wildfires 5→30min, military-vessels/flights/GDACS/
maritime/polymarket/GDELT 5→10min, chokepoint 5→20min.

Server Redis: market-quotes 2→8min, stablecoins 2→8min/5→10min,
vessel-snapshot 10s→5min, earthquakes 5→30min, sector 5→10min,
predictions/crypto/commodities 5→10min, outages/statuses 5→30min,
macro-signals/chokepoints 5→15min, aviation-sim 5→15min.

CDN edge: market RPCs fast→medium, infra/seismology fast→slow.
2026-03-01 05:20:31 +04:00

115 lines
3.1 KiB
TypeScript

import { createCircuitBreaker } from '@/utils';
export interface GDACSEvent {
id: string;
eventType: 'EQ' | 'FL' | 'TC' | 'VO' | 'WF' | 'DR';
name: string;
description: string;
alertLevel: 'Green' | 'Orange' | 'Red';
country: string;
coordinates: [number, number];
fromDate: Date;
severity: string;
url: string;
}
interface GDACSFeature {
geometry: {
type: string;
coordinates: [number, number];
};
properties: {
eventtype: string;
eventid: number;
name: string;
description: string;
alertlevel: string;
country: string;
fromdate: string;
severitydata?: {
severity: number;
severitytext: string;
severityunit: string;
};
url: {
report: string;
};
};
}
interface GDACSResponse {
features: GDACSFeature[];
}
const GDACS_API = 'https://www.gdacs.org/gdacsapi/api/events/geteventlist/MAP';
const breaker = createCircuitBreaker<GDACSEvent[]>({ name: 'GDACS', cacheTtlMs: 10 * 60 * 1000, persistCache: true });
const EVENT_TYPE_NAMES: Record<string, string> = {
EQ: 'Earthquake',
FL: 'Flood',
TC: 'Tropical Cyclone',
VO: 'Volcano',
WF: 'Wildfire',
DR: 'Drought',
};
export async function fetchGDACSEvents(): Promise<GDACSEvent[]> {
return breaker.execute(async () => {
const response = await fetch(GDACS_API, {
headers: { 'Accept': 'application/json' }
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data: GDACSResponse = await response.json();
const seen = new Set<string>();
return data.features
.filter(f => {
if (!f.geometry || f.geometry.type !== 'Point') return false;
const key = `${f.properties.eventtype}-${f.properties.eventid}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
})
.filter(f => f.properties.alertlevel !== 'Green')
.slice(0, 100)
.map(f => ({
id: `gdacs-${f.properties.eventtype}-${f.properties.eventid}`,
eventType: f.properties.eventtype as GDACSEvent['eventType'],
name: f.properties.name,
description: f.properties.description || EVENT_TYPE_NAMES[f.properties.eventtype] || f.properties.eventtype,
alertLevel: f.properties.alertlevel as GDACSEvent['alertLevel'],
country: f.properties.country,
coordinates: f.geometry.coordinates,
fromDate: new Date(f.properties.fromdate),
severity: f.properties.severitydata?.severitytext || '',
url: f.properties.url?.report || '',
}));
}, []);
}
export function getGDACSStatus(): string {
return breaker.getStatus();
}
export function getEventTypeIcon(type: GDACSEvent['eventType']): string {
switch (type) {
case 'EQ': return '🌍';
case 'FL': return '🌊';
case 'TC': return '🌀';
case 'VO': return '🌋';
case 'WF': return '🔥';
case 'DR': return '☀️';
default: return '⚠️';
}
}
export function getAlertColor(level: GDACSEvent['alertLevel']): [number, number, number, number] {
switch (level) {
case 'Red': return [255, 0, 0, 200];
case 'Orange': return [255, 140, 0, 180];
default: return [255, 200, 0, 160];
}
}