perf: fetches parallèles + /api/health expose vigilance freshness
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
Some checks are pending
Deploy info-canicule / deploy (push) Waiting to run
- /departement/[code] : Promise.allSettled sur les 3 fetches externes (vigilance MF, climato data.gouv, hourly SYNOP). Avant : ~15-20s sériel cold-fetch. Après : ~10s max (= temps du plus lent = climato). - normales (3 ranges) : Promise.all aussi, économise ~30 ms. - /api/health enrichi avec vigilance.productDatetime + ageSeconds pour permettre au cron freshness de checker sans /api/vigilance (qui a été supprimé en public). Pré-requis pour le cron warmup côté infra repo (cf. scripts/cron-warmup-info-canicule.sh). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ecac3fbf8a
commit
b342ea7375
2 changed files with 55 additions and 33 deletions
|
|
@ -1,22 +1,40 @@
|
|||
import type { APIRoute } from 'astro';
|
||||
import { pingCache } from '../../lib/cache';
|
||||
import { getVigilanceSnapshot } from '../../lib/vigilance';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
// Endpoint d'usage interne : UptimeRobot (uptime) + cron HC.io freshness.
|
||||
// Pas de CORS (pas destiné aux clients tiers).
|
||||
export const GET: APIRoute = async () => {
|
||||
const cacheOk = await pingCache();
|
||||
|
||||
// Best-effort : récupère le snapshot Vigilance pour exposer la fraîcheur.
|
||||
// Si KO, on dégrade sans casser le health (l'uptime reste vert).
|
||||
let vigilance: { productDatetime: string | null; ageSeconds: number | null } | null = null;
|
||||
try {
|
||||
const snap = await getVigilanceSnapshot();
|
||||
const pd = snap.productDatetime;
|
||||
vigilance = {
|
||||
productDatetime: pd,
|
||||
ageSeconds: pd ? Math.floor((Date.now() - new Date(pd).getTime()) / 1000) : null,
|
||||
};
|
||||
} catch {
|
||||
vigilance = null;
|
||||
}
|
||||
|
||||
const body = {
|
||||
status: cacheOk ? 'ok' : 'degraded',
|
||||
cache: cacheOk,
|
||||
time: new Date().toISOString(),
|
||||
vigilance,
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(body), {
|
||||
status: cacheOk ? 200 : 503,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store',
|
||||
// Pas de CORS : endpoint d'usage interne (UptimeRobot + cron HC.io freshness).
|
||||
// Pas destiné aux clients tiers.
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,50 +23,54 @@ if (!dept) {
|
|||
|
||||
const drom = isDrom(dept.code);
|
||||
|
||||
// Fetch en parallèle des 3 sources externes (vigilance MF, climato data.gouv, hourly MF SYNOP).
|
||||
// Cold-fetch climato = ~10s (4 MB de previous CSV.GZ), parallel ramène le total à max(individuel).
|
||||
const [snapshotR, climatoR, hourlyR] = drom
|
||||
? [Promise.resolve(null), Promise.resolve(null), Promise.resolve(null)] as const
|
||||
: ([
|
||||
getVigilanceSnapshot(),
|
||||
getClimatoForDepartement(dept.code),
|
||||
getHourlyForDepartement(dept.code, 24),
|
||||
] as const);
|
||||
|
||||
const settled = await Promise.allSettled([snapshotR, climatoR, hourlyR]);
|
||||
let snapshot;
|
||||
let error: string | null = null;
|
||||
if (!drom) {
|
||||
try {
|
||||
snapshot = await getVigilanceSnapshot();
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
}
|
||||
}
|
||||
|
||||
let climato = null;
|
||||
let anomaly = null;
|
||||
let hourly = null;
|
||||
|
||||
if (settled[0].status === 'fulfilled') snapshot = settled[0].value as Awaited<ReturnType<typeof getVigilanceSnapshot>> | null;
|
||||
else error = (settled[0].reason as Error).message;
|
||||
|
||||
if (settled[1].status === 'fulfilled') climato = settled[1].value as Awaited<ReturnType<typeof getClimatoForDepartement>> | null;
|
||||
else console.warn('climato fetch failed for', dept.code, (settled[1].reason as Error).message);
|
||||
|
||||
if (settled[2].status === 'fulfilled') hourly = settled[2].value as Awaited<ReturnType<typeof getHourlyForDepartement>> | null;
|
||||
else console.warn('hourly fetch failed for', dept.code, (settled[2].reason as Error).message);
|
||||
|
||||
let anomaly = null;
|
||||
let normales7: Array<{ tx: number | null; tn: number | null }> = [];
|
||||
let normales30: Array<{ tx: number | null; tn: number | null }> = [];
|
||||
let normales365: Array<{ tx: number | null; tn: number | null }> = [];
|
||||
let normaleHourly: { tx: number | null; tn: number | null } | null = null;
|
||||
if (!drom) {
|
||||
try {
|
||||
climato = await getClimatoForDepartement(dept.code);
|
||||
if (climato?.days?.length) {
|
||||
anomaly = await computeAnomaly(dept.code, climato.days);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('climato fetch failed for', dept.code, (e as Error).message);
|
||||
}
|
||||
try {
|
||||
hourly = await getHourlyForDepartement(dept.code, 24);
|
||||
} catch (e) {
|
||||
console.warn('hourly fetch failed for', dept.code, (e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
const last7 = climato?.days?.slice(-7) ?? [];
|
||||
const last30 = climato?.days?.slice(-30) ?? [];
|
||||
const last365 = climato?.days?.slice(-365) ?? [];
|
||||
let normales365: Array<{ tx: number | null; tn: number | null }> = [];
|
||||
|
||||
if (!drom) {
|
||||
const series7 = await normalesForRange(dept.code, last7.map((d) => d.date));
|
||||
normales7 = series7.map((n) => ({ tx: n?.tx ?? null, tn: n?.tn ?? null }));
|
||||
const series30 = await normalesForRange(dept.code, last30.map((d) => d.date));
|
||||
normales30 = series30.map((n) => ({ tx: n?.tx ?? null, tn: n?.tn ?? null }));
|
||||
const series365 = await normalesForRange(dept.code, last365.map((d) => d.date));
|
||||
normales365 = series365.map((n) => ({ tx: n?.tx ?? null, tn: n?.tn ?? null }));
|
||||
// Tous les lookups normales sont locaux (JSON statique) — pas besoin de paralléliser plus.
|
||||
if (climato?.days?.length) {
|
||||
anomaly = await computeAnomaly(dept.code, climato.days);
|
||||
}
|
||||
const [s7, s30, s365] = await Promise.all([
|
||||
normalesForRange(dept.code, last7.map((d) => d.date)),
|
||||
normalesForRange(dept.code, last30.map((d) => d.date)),
|
||||
normalesForRange(dept.code, last365.map((d) => d.date)),
|
||||
]);
|
||||
normales7 = s7.map((n) => ({ tx: n?.tx ?? null, tn: n?.tn ?? null }));
|
||||
normales30 = s30.map((n) => ({ tx: n?.tx ?? null, tn: n?.tn ?? null }));
|
||||
normales365 = s365.map((n) => ({ tx: n?.tx ?? null, tn: n?.tn ?? null }));
|
||||
normaleHourly = await normaleForDate(dept.code, new Date());
|
||||
if (normaleHourly) normaleHourly = { tx: normaleHourly.tx, tn: normaleHourly.tn };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue