koala73/worldmonitor · error

Comparison measurement requires the captured snapshot date

Error message

Comparison measurement requires the captured snapshot date

What it means

renderMeasurement embeds a 'How World Monitor measures this' section with a snapshot date that must be an ISO YYYY-MM-DD string. The snapshot date anchors the measurement claims to a captured data date; if snapshotDate is missing, null, or not in the exact format, the build throws 'Comparison measurement requires the captured snapshot date'.

Solutions

  1. Pass the capturedAt value from the frozen live-pulse snapshot, formatted exactly YYYY-MM-DD.
  2. If you have a full timestamp, slice the date part: capturedAt.slice(0, 10) (valid only if it starts with YYYY-MM-DD).
  3. Check the caller/config supplying snapshotDate — a missing env/config value becomes undefined here.

Example fix

// before
renderMeasurement(slug, snapshot.capturedAt, escapeHtml); // '2026-09-15T04:12:00Z'
// after
renderMeasurement(slug, snapshot.capturedAt.slice(0, 10), escapeHtml); // '2026-09-15'
Defensive patterns

Strategy: validation

Validate before calling

if (!/^\d{4}-\d{2}-\d{2}$/.test(snapshotDate ?? '')) throw new Error('snapshotDate must be YYYY-MM-DD');

Type guard

const isIsoDate = (v) => typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v);

Prevention

When it happens

Trigger: renderMeasurement invoked (directly or via page rendering) with snapshotDate undefined/null or a string failing /^\d{4}-\d{2}-\d{2}$/ — e.g. '2026/09/15', '2026-9-1', or a full ISO timestamp '2026-09-15T00:00:00Z'.

Common situations: A caller passes the whole snapshot object instead of its capturedAt field; a config file stores the date with slashes or a full timestamp; the date constant was removed during a refactor and undefined flows through.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/179ba01a3e0327e1. Report an issue: GitHub.

Appendix: source

Thrown at scripts/build-comparison-pages.mjs:580

function comparisonWebPageLd({ name, description, url, lastmod, faqId, baseUrl }) {
  return {
    '@context': 'https://schema.org',
    '@type': 'WebPage',
    '@id': url + '#webpage',
    name,
    description,
    url,
    inLanguage: 'en-US',
    dateModified: lastmod,
    isPartOf: { '@id': new URL('/#website', baseUrl).href },
    breadcrumb: { '@id': url + '#breadcrumb' },
    mainEntity: { '@id': faqId },
  };
}

function renderMeasurement(slug, snapshotDate, escapeHtml) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(snapshotDate ?? '')) {
    throw new Error('Comparison measurement requires the captured snapshot date');
  }
  const paragraphs = COMPARISON_MEASUREMENTS[slug];
  if (!paragraphs?.length) throw new Error(slug + ' needs a World Monitor measurement explanation');
  const maritime = slug === 'chokepoint-monitoring-tools';
  const snapshotPath = maritime ? '/chokepoints/status.json' : '/country-instability-index/cii-ranking.json';
  return [
    '      <h2>How World Monitor measures this</h2>',
    ...renderParagraphs(paragraphs, escapeHtml),
    '      <p>Coverage: the <a href="/sources/">source catalog</a> lists ' + escapeHtml(formatCount(WORLD_MONITOR_PROVIDER_COUNT)) + ' active providers across the product, not per metric. Availability varies by source and location.</p>',
    '      <p data-measurement-snapshot><a href="' + snapshotPath + '">Published ' + (maritime ? 'chokepoint status' : 'country instability') + ' snapshot</a>, captured <time datetime="' + escapeHtml(snapshotDate) + '">' + escapeHtml(snapshotDate) + '</time>. This reference remains dated when live values change.</p>',
  ];
}

function renderComparePage(page, { tpl, baseUrl, lastmod, snapshotDate }) {
  const { escapeHtml, breadcrumbLd, pageDocument } = tpl;
  const pageUrl = new URL(page.path, baseUrl).href;
  const description = page.metaDescription ?? (page.h1
    + ' - ' + page.claim

View on GitHub (pinned to 7d06c8633d)