koala73/worldmonitor · error
failed to build movers snapshot
Error message
failed to build movers snapshot
What it means
The Fastify route GET /worldmonitor/movers returned 500 because buildMoversSnapshot(market, days) threw. Two inputs feed the builder: the market code (default 'ae') and days (default '30'), parsed with parseInt(days, 10) — a non-numeric days yields NaN that flows into the snapshot query. The rest of the failure surface is the same as the other snapshot routes: the pg pool in src/db/client.ts throwing on missing DATABASE_URL, connection failure, or a SQL regression.
Source
Thrown at consumer-prices-core/src/api/routes/worldmonitor.ts:42
fastify.get('/overview', async (request, reply) => {
const { market = 'ae' } = request.query as { market?: string };
try {
const data = await buildOverviewSnapshot(market);
return reply.send(data);
} catch (err) {
fastify.log.error(err);
return reply.status(500).send({ error: 'failed to build overview snapshot' });
}
});
fastify.get('/movers', async (request, reply) => {
const { market = 'ae', days = '30' } = request.query as { market?: string; days?: string };
try {
const data = await buildMoversSnapshot(market, parseInt(days, 10));
return reply.send(data);
} catch (err) {
fastify.log.error(err);
return reply.status(500).send({ error: 'failed to build movers snapshot' });
}
});
fastify.get('/retailer-spread', async (request, reply) => {
const { market = 'ae', basket = 'essentials-ae' } = request.query as {
market?: string;
basket?: string;
};
try {
const data = await buildRetailerSpreadSnapshot(market, basket);
return reply.send(data);
} catch (err) {
fastify.log.error(err);
return reply.status(500).send({ error: 'failed to build retailer spread snapshot' });
}
});
fastify.get('/freshness', async (request, reply) => {View on GitHub (pinned to eeab0a219f)
Solutions
- Validate days client-side: it must parse as a positive integer before calling the route
- Check the fastify error log for the underlying exception
- Verify DATABASE_URL and database reachability
- Reproduce with buildMoversSnapshot(market, n) using the exact values to isolate SQL vs parameter issues
Example fix
// before — NaN flows into the snapshot builder
const data = await buildMoversSnapshot(market, parseInt(days, 10));
// after — reject bad input before it reaches SQL
const n = parseInt(days, 10);
if (!Number.isInteger(n) || n < 1) {
return reply.status(400).send({ error: 'days must be a positive integer' });
}
const data = await buildMoversSnapshot(market, n); Defensive patterns
Strategy: validation
Validate before calling
const raw = params.get('days') ?? '30';
const days = Number.parseInt(raw, 10);
if (!Number.isInteger(days) || days < 1 || days > 365) {
throw new Error(`invalid days '${raw}' — must be an integer 1-365`);
}
const res = await fetch(`/worldmonitor/movers?market=ae&days=${days}`); Type guard
function isValidDays(v: unknown): v is number {
return typeof v === 'number' && Number.isInteger(v) && v >= 1 && v <= 365;
} Try / catch
try {
const res = await fetch(`/worldmonitor/movers?market=${market}&days=${days}`);
if (!res.ok) throw new Error(`movers: HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (/HTTP 500/.test(String(err))) return readCachedMovers(market, days); // fall back to last good snapshot
throw err;
} Prevention
- Never pass a raw query string into days — parseInt('30d') is NaN and flows straight into the SQL layer
- Bound the window (1-365) client-side so the builder never sees extreme ranges
- Distinguish 400 (param bug) from 500 (backend) in clients; only 500s merit retry
When it happens
Trigger: Calling /movers?days=abc so parseInt returns NaN and the builder receives an invalid range; DATABASE_URL unset; Postgres unreachable; a SQL regression in the movers query; a market with no price observations in the window hitting a throwing expectation.
Common situations: Client passing days as an empty string or typo'd value ('30d' instead of '30'); deployment missing the DB secret; the movers window extending before any collected data.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- failed to build basket series snapshot
- failed to build coverage snapshot
- failed to build overview snapshot
- failed to build retailer spread snapshot
- failed to build freshness snapshot
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/f577d46892800d14.
Report an issue: GitHub.