Skip to content

Distinguish outages from data absence; add failure taxonomy - #1

Merged
13w13 merged 2 commits into
masterfrom
claude/dazzling-feynman-01rp0g
Sep 25, 2026
Merged

13w13 merged 2 commits into
masterfrom
claude/dazzling-feynman-01rp0g

Conversation

@13w13

@13w13 13w13 commented Sep 25, 2026

Copy link
Copy Markdown
Owner

Summary

This PR introduces a failure taxonomy to distinguish between provider outages (network failures, HTTP 5xx errors) and legitimate data absence (empty results, missing coverage). Previously, all failures were silently caught and returned empty lists, making outages indistinguishable from "no data for this country," which caused the pipeline to report zero records as if they were facts.

Key Changes

  • New exception hierarchy in config.py:

    • SourceUnavailable: Provider unreachable or returned server error (outage)
    • MissingCredential: Free API key not configured (skip, not zero)
    • NotCovered: Source has no coverage for this country (absence of coverage, not absence of data)
    • is_outage(): Classifies exceptions as outages vs. bugs (HTTP 5xx/429, timeouts, connection errors vs. 4xx/schema changes)
  • Pipeline status tracking:

    • Added status field to fetch results with seven states: ok, empty, partial, unavailable, error, skipped, not_covered
    • _attempt() helper: Runs individual source requests, records failures without swallowing them
    • _state(): Determines final source status from collected errors and record count
    • Failed requests now return None (printed as "n/a") instead of fake zeros
  • Output improvements:

    • fetch_summary.csv now includes status column and detailed error notes
    • data_inventory.csv tracks actual files on disk with row counts
    • SOURCE_FILES registry: Defines exact filenames each source produces; previous outputs are cleared before each run to prevent stale data
  • Client-side changes:

    • All clients now raise SourceUnavailable, MissingCredential, or NotCovered instead of returning empty lists
    • raise_unavailable() helper for consistent error handling
    • IDMC, ACLED, ACAPS, IFRC GO, WFP, HPC, INFORM, DTM, ReliefWeb updated to use new exceptions
    • get_credential() centralized in config; missing keys raise MissingCredential
  • Robustness fixes:

    • sys.stdout.reconfigure() now guarded with hasattr() check (absent in Jupyter, IDLE, captured output)
    • Period filtering: filter_by_period() keeps records with overlapping date ranges, not just start dates
    • HDX CKAN: format parameter renamed to format_filter to avoid shadowing builtin
    • HAPI: app_identifier now properly URL-encoded (base64 contains unsafe characters)
    • ReliefWeb: Appname validation; 403 errors raise MissingCredential with setup instructions
    • Liveuamap: Icon 51 corrected from "election" to "power_infrastructure" (electricity, not elections)
  • Test suite enhancements:

    • test_hardening.py: Tests 7–8 verify outage taxonomy and that network failures don't produce fake zeros
    • test_skill_50.py: rec_exc() distinguishes outages (SKIP) from bugs (GAP)
    • test_p0_e2e.py, test_dtm_e2e.py: Updated to use is_outage() for consistent failure classification
    • GitHub Actions workflow added for offline test suite
  • Documentation:

    • README updated: ReliefWeb now requires pre-approved appname (free, request at apidoc.reliefweb.int)
    • Exit codes documented: 0 (all sources answered), 1 (partial/unavailable/error), 2 (invalid arguments)
    • SECURITY.md: wgss_probe.py now uses tempfile.mkdtemp() instead of hardcoded path (was relative on Linux/macOS)

Notable Implementation Details

  • Period filtering: Checks for overlap between record's [date_start, date_end] and requested [date_from, date_to], not just

https://claude.ai/code/session_01SQ5UN2pMiamMm4HGGqKA7j

Every client now separates three failures that used to look like "no data":
an upstream outage (SourceUnavailable), a missing or refused credential
(MissingCredential) and a country the source does not cover (NotCovered).
fetch_country_data.py records one status per source in fetch_summary.csv
and exits 1 when a source failed, so an empty CSV is never mistaken for a
finding.

Correctness fixes found in review:
- INFORM: CC.INS is Institutional and CC.INF Infrastructure (were swapped);
  a missing score is None, not 0.
- ACLED: a country outside the name table raises instead of silently
  querying the wrong country; CAST filters on an exact country match.
- Liveuamap: regional feeds are refused unless allowed; South Sudan is
  marked as read from the Sudan feed; event category 51 is power
  infrastructure; timestamps are UTC.
- HDX CKAN lists are paginated; HAPI, UNHCR, IFRC GO and DTM check that
  the returned rows belong to the requested country.
- ReliefWeb 403 (unapproved appname) is reported with the fix to apply.
- report_figures: confusable-country and subnational guards (Sudan vs
  South Sudan, Gaza/West Bank), directional change parsing, PDF cache.

Safer downloads: http(s)-only redirects that drop credentials across
hosts, truncation and HTML-instead-of-file detection, atomic .part writes,
sanitised file names; wgss_probe keeps MSNA microdata in a temp directory
removed on exit.

Tests: test_hardening grows to 71 offline checks; the live suites skip
only on a real outage and fail on empty answers. New CI workflow runs the
offline suite on Linux and Windows (3.9, 3.12), pyflakes and vermin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ5UN2pMiamMm4HGGqKA7j
…ndicators

A live Sudan run from 2026-08-01 gave three misleading answers:
- HAPI kept a row only when its reference period STARTED in the window, so
  the 2026 HNO (January to December) was dropped and the summary claimed
  "no disability disaggregation" while 6,515 disability rows exist for
  2024-2025. The IPC projection for June to September 2026, the one that
  covers the window, was dropped too: only the October projection was kept.
  Rows are now kept when their period overlaps the window, the rule HAPI's
  own start_date/end_date filters apply, and every zero says what exists
  outside it: "IDPs: 0 records (19741 outside the period, covering
  2010-06-30 to 2026-06-30)".
- UNHCR has no 2026 population figures yet and the summary read
  "0 population records"; it now says why and how to rerun.
- World Bank SM.POP.REFG and SM.POP.REFG.OR moved to the WDI archives and
  answer "indicator not found", so every profile came out partial. They are
  removed; refugee figures come from UNHCR.

The offline suite gains a period-filter section (78 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ5UN2pMiamMm4HGGqKA7j
@13w13
13w13 merged commit 0414157 into master Sep 25, 2026
10 checks passed
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.

2 participants