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

  1. Check the fastify error log for the underlying exception
  2. Verify DATABASE_URL and database reachability from the API host
  3. Confirm the market has at least one recorded retailer run
  4. 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

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


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/a67c6390f8b60d2d. Report an issue: GitHub.