koala73/worldmonitor · error

failed to build categories snapshot

Error message

failed to build categories snapshot

What it means

The Fastify route GET /worldmonitor/categories returned 500 because buildCategoriesSnapshot(market, range) threw. Inputs are market (default 'ae') and range (default '30d'); the builder computes per-category WoW/MoM deltas and sparklines via the pg pool in src/db/client.ts. The 500 wraps a database-side throw — missing DATABASE_URL, connection failure, SQL regression — or a range value the date-bucketing logic cannot handle.

Source

Thrown at consumer-prices-core/src/api/routes/worldmonitor.ts:78

  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;
      range?: string;
    };
    try {
      const data = await buildBasketSeriesSnapshot(market, basket, range);
      return reply.send(data);
    } catch (err) {
      fastify.log.error(err);
      return reply.status(500).send({ error: 'failed to build basket series snapshot' });
    }
  });
}

View on GitHub (pinned to eeab0a219f)

Solutions

  1. Check the fastify error log for the underlying exception
  2. Retry with the default range ('30d') to determine whether the range value or the database is at fault
  3. Verify DATABASE_URL and database reachability
  4. Reproduce with buildCategoriesSnapshot(market, range) directly
Defensive patterns

Strategy: retry

Validate before calling

const VALID_RANGES = new Set(['7d', '30d', '90d']);
const range = params.get('range') ?? '30d';
if (!VALID_RANGES.has(range)) {
  throw new Error(`range '${range}' unsupported — use one of ${[...VALID_RANGES].join(', ')}`);
}
const res = await fetch(`/worldmonitor/categories?market=ae&range=${range}`);

Try / catch

const res = await fetch(`/worldmonitor/categories?market=${market}&range=${range}`);
if (res.status === 500) {
  // first rule out a bad range by retrying the default
  const fallbackRes = await fetch(`/worldmonitor/categories?market=${market}&range=30d`);
  if (fallbackRes.ok) return await fallbackRes.json();
  throw new Error('categories snapshot backend down');
}
if (!res.ok) throw new Error(`categories: HTTP ${res.status}`);

Prevention

When it happens

Trigger: DATABASE_URL unset; Postgres unreachable; a range string like '7d'/'90d' hitting unsupported date math in the builder; a migration breaking the category aggregate; pool exhaustion.

Common situations: Client sending an ad-hoc range value the builder never anticipated; deployment missing the DB secret; category stats absent for a newly onboarded market.

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/07f5c52b3624f6e3. Report an issue: GitHub.