koala73/worldmonitor · error
failed to build freshness snapshot
Error message
failed to build freshness snapshot
What it means
The Fastify route GET /worldmonitor/freshness returned 500 because buildFreshnessSnapshot(market) threw. The builder derives per-retailer lastRunAt, status, parse success rate and freshness minutes via the pg pool in src/db/client.ts, so the 500 wraps a database-side throw (missing DATABASE_URL, unreachable Postgres, SQL regression) or a shape expectation on run/parse stats data.
Source
Thrown at consumer-prices-core/src/api/routes/worldmonitor.ts:67
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) => {
const { market = 'ae' } = request.query as { market?: string };
try {
const data = await buildFreshnessSnapshot(market);
return reply.send(data);
} catch (err) {
fastify.log.error(err);
return reply.status(500).send({ error: 'failed to build freshness snapshot' });
}
});
fastify.get('/categories', async (request, reply) => {
const { market = 'ae', range = '30d' } = request.query as { market?: string; range?: string };
try {
const data = await buildCategoriesSnapshot(market, range);
return reply.send(data);
} catch (err) {
fastify.log.error(err);
return reply.status(500).send({ error: 'failed to build categories snapshot' });
}
});
fastify.get('/basket-series', async (request, reply) => {
const { market = 'ae', basket = 'essentials-ae', range = '30d' } = request.query as {
market?: string;
basket?: string;View on GitHub (pinned to eeab0a219f)
Solutions
- Check the fastify error log for the underlying exception
- Verify DATABASE_URL and database reachability from the API host
- Confirm the market has at least one recorded retailer run
- Reproduce with buildFreshnessSnapshot(market) directly
Defensive patterns
Strategy: retry
Validate before calling
async function freshnessDepsOk(): Promise<boolean> {
if (!process.env.DATABASE_URL) return false;
try { await query('SELECT 1'); return true; } catch { return false; }
} Try / catch
const res = await fetch('/worldmonitor/freshness?market=ae');
if (res.status === 500) {
await sleep(1_000);
return await fetch('/worldmonitor/freshness?market=ae');
}
if (!res.ok) throw new Error(`freshness: HTTP ${res.status}`); Prevention
- Freshness reads run-stat tables — make sure at least one scrape run is recorded for the market
- Cache the last freshness snapshot; it is inherently tolerant of a few minutes of staleness
- Correlate 500s here with 500s on other snapshot routes to confirm a database root cause
When it happens
Trigger: DATABASE_URL unset in the API process; Postgres unreachable or credentials wrong; a migration renaming run-stat columns the freshness query selects; a market with no recorded runs reaching throwing aggregation code.
Common situations: Fresh deployment missing the database secret; a market onboarded before any scrape runs have been recorded; local dev against a stopped database.
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 coverage snapshot
- failed to build overview snapshot
- failed to build movers snapshot
- failed to build retailer spread snapshot
- failed to build categories snapshot
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/a67c6390f8b60d2d.
Report an issue: GitHub.