Skip to content

feat(displacement): seed IDMC internal displacement events from HDX (#9014) - #9444

Merged
koala73 merged 2 commits into
mainfrom
feat/9014-idmc-idu-hdx
Oct 20, 2026
Merged

koala73 merged 2 commits into
mainfrom
feat/9014-idmc-idu-hdx

Conversation

@koala73

@koala73 koala73 commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Part of #9014 (Step 2).

IDMC has not issued a Helix API client_id after two emails to ch.datainfo@idmc.ch, so this reads the same Internal Displacement Updates (IDU) from the per-country <iso3>-idmc-idu-events CSVs IDMC publishes on HDX: 116 datasets, no key, Helix column schema.

What it publishes (displacement:idmc-idu:v1)

  • events: "Recommended figure" rows from the last 180 days, with location, type (Conflict/Disaster), hazard, figure, displacement and publication dates. "Triangulation" rows cross-check the same displacement and are dropped (6,371 of 27,311 rows).
  • countries: per country (windows are exact calendar dates including today: 180 / 30 / 7), the 30-day displaced total (conflict/disaster split), the monthly baseline over the prior 150 days, figures newly reported in the last 7 days (including late reports of displacements older than the window), and surge (≥ 5,000 and ≥ 3× baseline).

Why 30 days, not same-week

The issue asked for same-week surge detection. IDMC enters a figure a median of 23 days after the displacement (p75 41, p90 65; measured across the 2026-10-20 files). Only 12 events carry a displacement date in the last 7 days. So surges use a 30-day window, and newlyReported7d tracks what IDMC published this week, whatever the displacement date.

Failure handling

  • Up to 3 unreadable country files are skipped and listed in missingCountries; more fails the run, and the previous snapshot stays (3-day TTL).
  • 4xx responses are not retried; body reads happen inside the retry, so a connection dropped mid-download is retried. A truncated HDX listing or a CSV with changed columns fails the run.
  • Resource URLs come from HDX metadata, so every hop is checked: https only, data.humdata.org or the hdx-production-filestore/ S3 path its downloads redirect to. Redirects are followed manually; anything else is refused before a request is made.
  • validate requires ≥ 30 countries. Health requires ≥ 500 events.

Wiring

  • Daily in seed-bundle-health (300 s timeout; the live run took ~28 s).
  • Standalone /api/health key idmcIduDisplacement, behind an activation marker until the first publish (same bridge as DTM). Probed-key total in the docs goes 327 → 328.
  • Content age is dated by publication (created_at), max 14 days.
  • MCP parity: deferred, since nothing serves this key yet. The RPC, map layer and CII use are follow-ups.

Licence

HDX lists these datasets as CC BY-IGO, but IDMC's API page says CC BY-NC-SA 3.0 IGO. This is being confirmed with IDMC by phone. Nothing user-facing reads this key yet, so the question gates the follow-up consumer, not this seeder.

Verification

  • tests/idmc-idu-displacement-seed.test.mjs: 26 tests covering CSV quoting and BOM, role filter, both sides of each window cutoff, surge threshold (including the rounding edge), late reports, URL allowlist and redirect refusal, S3 redirect, mid-stream retry, truncation, and partial/total download failure. Each review fix was mutation-checked: reverting it turns at least one test red.
  • Live dry run against HDX at 704ee4d (no Redis write, real HDX → S3 redirects): 116 datasets, 27,311 rows → 3,833 events in 71 countries, 2.5 MB payload, 0 missing files, 28.8 s; surges PHL, ETH, VNM, CMR, THA, GHA.
  • npm run test:data: all green after regenerating the health totals; the 6 initial failures were the count and manifest gates, rerun and passing (197/197). npm run test:railway-registry: 153/153.
  • Not verified: a production Railway tick. The seeder publishes on the bundle's next daily run after deploy.

No UI changes.

https://claude.ai/code/session_01KH6ud2RjKZ5rTa5WqmcNPt

Summary by CodeRabbit

  • New Features

    • Added IDMC internal displacement data to the health inventory.
    • Health monitoring now tracks data freshness, minimum coverage, and activation status for this source.
    • Added country-level displacement summaries, recent reporting metrics, and surge indicators.
  • Documentation

    • Updated health endpoint examples to reflect 328 monitored checks in English and Chinese documentation.
    • Added source attribution for the new displacement data.
  • Tests

    • Added coverage for data parsing, validation, freshness, downloads, and country-level calculations.

@mintlify

mintlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
WorldMonitor 🟢 Ready View Preview Oct 20, 2026, 6:03 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
worldmonitor Ready Ready Preview Oct 20, 2026 6:09am UTC

Request Review

@strix-security

strix-security Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Strix Security Review

All previously reported security findings have been resolved.

1 resolved finding
Review summary

Reviewed all 12 changed files: the new IDMC IDU HDX ingestion seeder, its health/bundle/registry wiring, the generated attribution manifest, documentation count updates, and the 17-test suite. The previously reported HDX resource-URL finding is resolved: isAllowedHdxUrl now restricts fetches to https://data.humdata.org and the HDX S3 filestore bucket, credentials/ports/schemes are rejected, redirects are followed manually with every hop re-validated, and the dataset listing only selects CSV resources that pass the same check. The tests cover the origin/redirect/off-site-resource cases. No new security issues were found in the changed code; the remaining hunks are health-registration, scheduling, attribution, documentation, and test-only changes.

Updated for 704ee4d.


Reviewed by Strix
Re-run review · Configure security review settings

@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5a80b0a5-035f-41c3-94c1-be3762a378e1
📥 Commits

Reviewing files that changed from the base of the PR and between 3de7932 and 704ee4d.

📒 Files selected for processing: 12
  • api/health.js
  • docs/api-platform.mdx
  • docs/health-endpoints.mdx
  • docs/source-attribution.mdx
  • docs/zh/api-platform.mdx
  • docs/zh/health-endpoints.mdx
  • scripts/railway-services.json
  • scripts/seed-bundle-health.mjs
  • scripts/seed-idmc-idu-displacement.mjs
  • shared/source-attribution-manifest.json
  • tests/idmc-idu-displacement-seed.test.mjs
  • tests/mcp-bootstrap-parity.test.mjs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

📝 Walkthrough

The pull request adds an IDMC internal displacement seed. It discovers and processes country datasets, computes event and country metrics, and connects the output to health monitoring and the daily seed bundle.

Changes: IDMC displacement data

Layer / File(s) Summary
Discover and normalize displacement data
scripts/seed-idmc-idu-displacement.mjs, tests/idmc-idu-displacement-seed.test.mjs
The seed discovers country datasets through HDX, validates download URLs, parses CSV resources, and normalizes eligible events. Tests cover parsing, filtering, URL validation, redirects, and retries.
Aggregate and publish seed data
scripts/seed-idmc-idu-displacement.mjs, tests/idmc-idu-displacement-seed.test.mjs
The seed computes country summaries and surge metrics, validates the result, and configures publication metadata and activation marking. Tests cover aggregation, validation, metadata, and fetch failures.
Connect the seed to health and deployment
api/health.js, scripts/seed-bundle-health.mjs, scripts/railway-services.json, docs/*, docs/zh/*, shared/source-attribution-manifest.json, tests/mcp-bootstrap-parity.test.mjs
Health monitoring registers the displacement key and activation marker. The seed bundle and watched files include the new script. Health examples and source attribution are updated, and MCP parity tests exclude the displacement key with a deferred-consumer reason.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant SeedBundle
  participant IDMCSeed
  participant HDX
  participant Upstash
  SeedBundle->>IDMCSeed: Run the daily IDMC displacement seed
  IDMCSeed->>HDX: Discover datasets and fetch country CSV resources
  IDMCSeed->>IDMCSeed: Normalize events, aggregate countries, and publish data
  IDMCSeed->>Upstash: Write the activation marker after publication
Loading

Suggested reviewers: shinzoxd

Merge Risk: ⚪ Minimal · up to 704ee

This change adds a new daily IDMC displacement data feed and registers it in health monitoring without changing existing behavior or the UI. Downloads are restricted to the HDX host and its S3 filestore, which current HDX redirects use. No merge-blocking issues remain.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Description check Warning The description provides detailed implementation, failure-handling, wiring, licensing, and verification information, but it does not follow the required template or complete its required sections and … Add the required Summary, Type of change, Affected areas, Checklist, Documentation Alignment Checklist, and Screenshots sections. Mark applicable checklist items, provide the required documentation-alignment evidence for the changed health …
Docstring Coverage Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 5 files. (7 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly identifies the main change: adding an IDMC internal displacement event seeder from HDX.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description provides detailed implementation, failure-handling, wiring, licensing, and verification information, but it does not follow the required template or complete its required sections and checklists.

Resolution

Add the required Summary, Type of change, Affected areas, Checklist, Documentation Alignment Checklist, and Screenshots sections. Mark applicable checklist items, provide the required documentation-alignment evidence for the changed health and data claims, and mark non-applicable items explicitly.

Full details: Docstring Coverage

Explanation

Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 5 files. (7 skipped: 7 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@strix-security strix-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strix flagged a new security finding below. See the pinned summary comment for the full PR status.

Comment thread scripts/seed-idmc-idu-displacement.mjs Outdated
Comment thread scripts/seed-idmc-idu-displacement.mjs Outdated
Comment thread tests/idmc-idu-displacement-seed.test.mjs Outdated
@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The change is contained to displacement data publishing and its health monitoring.

The PR appears safe to merge, with non-blocking fixes recommended for stored totals and download retries.

Findings

  1. P2 Late reports disappear ▶
  2. P2 Totals include an extra day ▶
  3. P2 Interrupted downloads bypass retries ▶

Summary

Adds a daily IDMC displacement worker that reads country CSVs from HDX and stores events, country totals, and surge flags in Redis.

  • The daily job publishes IDMC displacement events and country surge totals.
  • Health checks track the IDMC displacement snapshot after its first publish.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Daily health bundle] --> B[HDX dataset listing]
  B --> C[Download country CSVs]
  C --> D[Keep recommended figures]
  D --> E[Filter displacement dates]
  E --> F[Build country totals and surge flags]
  F --> G[Validate and publish to Redis]
  G --> H[Write metadata and activation marker]
  H --> I[Health checks]
Loading

Reviews (1) · Last reviewed commit: "feat(displacement): seed IDMC internal d..." · Reviewed by Greptile

Comment thread scripts/seed-idmc-idu-displacement.mjs Outdated
Comment on lines +219 to +220
const events = buildEvents(rows, { nowMs });
const countries = summarizeCountries(events, { nowMs });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Late reports disappear

buildEvents drops displacements older than 180 days before summarizeCountries counts newlyReported7d. A recommended figure published this week for an older displacement contributes nothing, despite the stated requirement to count new reports regardless of displacement date.

Count newly published figures from all valid rows. Keep the 180-day limit for event history and surge totals, and add a test for an older displacement published this week.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 704ee4d. summarizeCountries now takes every normalized event; a figure published in the last 7 dates counts toward newlyReported7d even when the displacement predates the 180-day window, and stays out of the events list and the baseline. Test: counts a figure published this week for a displacement older than the window....

Comment thread scripts/seed-idmc-idu-displacement.mjs Outdated
Comment on lines +153 to +155
const recentFrom = daysAgo(nowMs, RECENT_DAYS);
const reportedFrom = daysAgo(nowMs, NEWLY_REPORTED_DAYS);
const baselineMonths = (WINDOW_DAYS - RECENT_DAYS) / 30;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Totals include an extra day

On October 20, recentFrom is September 20. The inclusive comparison makes displaced30d cover 31 calendar dates, while newlyReported7d covers eight. The baseline still represents 30-day months, so the wider recent window can also produce a false surge.

Use consistent calendar-day windows and test both sides of each cutoff.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 704ee4d. Windows now cover exactly 180 / 30 / 7 calendar dates including today (windowStart = daysAgo(days - 1)), so the 30 recent dates and 150 baseline dates split at 2026-09-21 for 2026-10-20. Tests pin both sides of each cutoff.

Comment thread scripts/seed-idmc-idu-displacement.mjs Outdated
Comment on lines +83 to +86
const resp = await getWithTimeout(url);
const length = Number(resp.headers.get('content-length'));
if (Number.isFinite(length) && length > MAX_CSV_BYTES) throw new Error(`HDX ${url}: ${length} bytes exceeds ${MAX_CSV_BYTES}`);
const text = await resp.text();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Interrupted downloads bypass retries

getWithTimeout returns before the response body is read, so withRetry never sees failures from resp.text() or the listing's .json(). A connection reset during a country download immediately drops that country. More than three failed files abort the run without using the configured retries.

Read each body inside the retry callback. Keep CSV schema checks outside it.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 704ee4d. The body read (resp.json() / resp.text()) now runs inside the withRetry callback; size-cap failures are non-retryable, schema checks stay outside. Test: retries a download whose body breaks mid-stream.

…9014)

IDMC has not issued a Helix API client_id, so read the same Internal
Displacement Updates from the per-country <iso3>-idmc-idu-events CSVs that
IDMC publishes on HDX (116 datasets, no key).

- Keep "Recommended figure" rows from the last 180 days; "Triangulation" rows
  cross-check the same displacement and are dropped.
- Per country: 30-day displaced total (conflict/disaster split), monthly
  baseline over the prior 150 days, figures newly reported in the last 7 days,
  and a surge flag (>= 5,000 and >= 3x baseline). IDMC enters figures a median
  of 23 days after displacement (p90 65), so a same-week window would be empty.
- Skip up to 3 unreadable country files and report them; more fails the run.
- Daily in seed-bundle-health; standalone /api/health key idmcIduDisplacement
  behind an activation marker until the first publish.

Claude-Session: https://claude.ai/code/session_01KH6ud2RjKZ5rTa5WqmcNPt
…t windows (#9014)

Review fixes on the IDMC IDU seeder:
- Resource URLs come from HDX metadata, so every hop is checked: https only,
  data.humdata.org or the hdx-production-filestore S3 path its download links
  redirect to. Redirects are followed by hand; anything else is refused.
- Read response bodies inside the retry, so a connection dropped mid-download
  is retried instead of dropping the country.
- newlyReported7d counts figures published this week even when the
  displacement predates the 180-day window; such figures stay out of the
  events list and the baseline.
- Windows cover exactly 180 / 30 / 7 calendar dates including today
  (displaced30d spanned 31 dates, newlyReported7d 8).

Claude-Session: https://claude.ai/code/session_01KH6ud2RjKZ5rTa5WqmcNPt

This branch was successfully deployed

2 active deployments
Preview — 704ee4d2 Deployed Oct 20, 2026 by vercel[bot]
staging - docs — 704ee4d2 Deployed Oct 20, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant