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

  1. Validate days client-side: it must parse as a positive integer before calling the route
  2. Check the fastify error log for the underlying exception
  3. Verify DATABASE_URL and database reachability
  4. 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

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


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