docs/methodology/fri.md

Facility Resilience Index (FRI) — Methodology

Version: v0.1.0 (matches lib/weights.json) Status: Draft — refined throughout the demo sprint, peer-reviewed in grant phase License: CC-BY 4.0

The Facility Resilience Index is a single 0–100 score per facility that captures combined climate and power-continuity risk, weighted by service criticality, children at risk, and cross-sector dependency. This document explains exactly how the score is computed, what the inputs mean, and the assumptions that underlie each component.


1. The formula

FRI = (W_power · PowerVulnerability
     + W_climate · ClimateExposure
     + W_water · WaterContinuity
     + W_aq · AirQualityBurden)
     × ServiceCriticalityMultiplier
     × ChildrenAtRiskWeight
     × CrossSectorDependencyAmplifier

The final FRI is bounded to [0, 100]. Higher means higher priority for intervention.

Bands (used to colour-grade the public map):

BandRange
Low0–29.99
Moderate30–54.99
High55–79.99
Critical80–100

2. Top-level weights

WeightDefaultRationale
W_power0.40Power is the dominant resilience risk for child-critical facilities in Nigeria today
W_climate0.25Climate stress is rising but at facility level less variable than power within Nigeria
W_water0.20Water continuity is critical for cold chain and sanitation but already correlates with power
W_aq0.15Air quality is a slow-acting risk; included for completeness and future-proofing

These weights sum to 1.0 by design. They are loaded from lib/weights.json and versioned. Any change requires bumping the version and updating this document.


3. Sub-component definitions

3.1 PowerVulnerability (0–100)

A weighted sum of four power-related signals:

PowerVulnerability = 0.20·outage_score
                   + 0.30·diesel_runtime_score
                   + 0.30·unbacked_outage_score
                   + 0.20·fuel_cost_score

Where:

  • outage_score = min(monthly outage hours / 720 × 100, 100)
  • diesel_runtime_score = diesel runtime ratio × 100 (ratio is gen-on hours / total operating hours)
  • unbacked_outage_score = min(monthly unbacked outage hours / 720 × 100, 100)
  • fuel_cost_score = min(fuel cost share of opex × 100, 100)

Why these four: outages are the headline signal, diesel runtime captures how dependent the facility is on a non-renewable backup, unbacked outages capture moments when even diesel doesn't cover service, fuel cost share captures economic burden.

Data sources (v0 → grant phase):

  • v0: synthetic risk profile values seeded per facility in data/risk-profiles-nigeria.json
  • Grant phase: ESP32 sensor telemetry (mains+gen current, AC voltage); facility opex self-report

3.2 ClimateExposure (0–100)

A weighted sum of three climate-related signals:

ClimateExposure = 0.40·flood_score
                + 0.30·heatwave_score
                + 0.30·indoor_heat_score

Where:

  • flood_score = (flood_risk_score - 1) / 4 × 100 (maps WRI Aqueduct 1–5 to 0–100)
  • heatwave_score = min(annual days >38°C / 90 × 100, 100) (saturates at 90 days/year)
  • indoor_heat_score = min(monthly indoor temp exceedance hours above 32°C / 720 × 100, 100)

Why these three: flood is the highest-impact acute event, heatwave is the rising slow-moving driver of cold-chain stress and patient/student discomfort, indoor heat captures building-level exposure.

Data sources:

  • v0: synthetic
  • Grant phase: WRI Aqueduct (flood), MODIS LST + ERA5 (heat normals + heatwave days), in-facility temperature sensors (indoor heat)

3.3 WaterContinuity (0–100)

A summed contribution model — three independent factors, capped at 100:

WaterContinuity = (pump_grid_dependent ? 60 : 0)
                + (monthly water outage hours / 720 × 20)
                + (no_alternative_water_source ? 20 : 0)

Why these weights: if water depends on a grid-electric pump, that's the dominant continuity risk (60). Outage-driven water unavailability adds (up to 20). Lack of any alternative source compounds (20).

Data sources:

  • v0: synthetic
  • Grant phase: facility self-report (pump source), water-pump current sensor (outage hours), site survey (alternatives)

3.4 AirQualityBurden (0–100)

A summed contribution model:

AirQualityBurden = (monthly indoor PM2.5 exceedance hours above WHO 24h / 720 × 50)
                 + (outdoor PM2.5 index × 50)

Where outdoor_pm25_index is a 0–1 normalised value derived from Sentinel-5P NO₂/aerosol products (grant phase) or seeded from regional air-quality typology (v0).

Data sources:

  • v0: synthetic
  • Grant phase: in-facility PMS5003 / SDS011 PM2.5 sensors; Sentinel-5P satellite for outdoor

4. Multipliers

4.1 ServiceCriticalityMultiplier

The highest applicable flag's multiplier from this list (not multiplicative — one wins):

Service flagMultiplier
neonatal_ward1.6
cold_chain_active1.5
maternity_ward1.4
paediatric_ward1.4
operating_room1.3
dialysis1.3
water_pumping1.3
boarding_school1.3
eccd_centre1.2
primary_school1.1
(no flags)1.0

Rationale: these multipliers encode "where service disruption matters most for children." Neonatal care is the highest (≤24h disruption can be fatal); cold chain immediately follows (vaccine spoilage); maternity, paediatric, and overnight-occupancy schools follow.

4.2 ChildrenAtRiskWeight

ChildrenAtRiskWeight = clamp(1.0 + (children_served / regional_median - 1) × 0.3, 0.7, 1.5)

Facilities serving more children than the regional median get amplified up to 1.5×; those serving fewer get attenuated down to 0.7×. The 0.3 slope was chosen so that a facility serving 2× the median lands at 1.3×, and 3.67× the median saturates at 1.5×.

Regional median: the median of children_served_estimated across the full facility cohort (currently 1,100 in the Nigerian seed dataset).

4.3 CrossSectorDependencyAmplifier

CrossSectorDependencyAmplifier = clamp(1.0 + 0.05 × outbound_dependency_edges, 1.0, 1.3)

A facility that other facilities depend on (e.g., a borehole supplying a school + clinic + ECCD centre) gets its FRI amplified by 0.05 per outbound edge, capped at 1.3 (6 or more outbound edges). This is the formal mechanism by which the cross-sector model influences scoring: failure points that cascade to multiple sectors are surfaced as higher priority.

v0 limitation: outbound edges are manually curated in data/dependencies.json. Grant phase adds the Dependency Discovery Agent (spec section 8a) to propose edges from public datasets + operator interviews automatically, with human confirmation.


5. Worked example — crv-h-005 (rural Cross River community health post)

Inputs (from data/risk-profiles-nigeria.json):

monthly_outage_hours: 360
diesel_runtime_ratio: 0.70
monthly_unbacked_outage_hours: 260
fuel_cost_share_of_opex: 0.35
flood_risk_score: 4
annual_heatwave_days_above_38c: 18
monthly_indoor_temp_exceedance_hours: 280
pump_grid_dependent: true
monthly_water_outage_hours: 120
no_alternative_water_source: true
monthly_indoor_pm25_exceedance_hours: 220
outdoor_pm25_index: 0.30
services: [cold_chain_active]
children_served: 520
outbound_dependencies: 1  (crv-h-005 → crv-w-001)

Computation:

PowerVulnerability = 0.20 × min(360/720·100, 100)        = 0.20 × 50.0  = 10.0
                   + 0.30 × 0.70·100                       = 0.30 × 70.0  = 21.0
                   + 0.30 × min(260/720·100, 100)          = 0.30 × 36.11 = 10.83
                   + 0.20 × min(0.35·100, 100)              = 0.20 × 35.0  = 7.0
                   = 48.83 ✓

ClimateExposure   = 0.40 × (4-1)/4·100                    = 0.40 × 75.0  = 30.0
                   + 0.30 × min(18/90·100, 100)            = 0.30 × 20.0  = 6.0
                   + 0.30 × min(280/720·100, 100)          = 0.30 × 38.89 = 11.67
                   = 47.67 ✓

WaterContinuity   = (true ? 60 : 0)                       = 60
                   + 120/720 × 20                          = 3.33
                   + (true ? 20 : 0)                       = 20
                   = 83.33 ✓

AirQualityBurden  = 220/720 × 50                          = 15.28
                   + 0.30 × 50                             = 15.0
                   = 30.28 ✓

weighted_base = 0.40·48.83 + 0.25·47.67 + 0.20·83.33 + 0.15·30.28
              = 19.53 + 11.92 + 16.67 + 4.54
              = 52.66 ✓

ServiceCriticality = 1.5 (cold_chain_active)
ChildrenAtRisk     = clamp(1.0 + (520/1100 - 1)·0.3, 0.7, 1.5) = 0.842
CrossSectorAmp     = clamp(1.0 + 1·0.05, 1.0, 1.3)             = 1.05

FRI = 52.66 × 1.5 × 0.842 × 1.05 = 69.82 ✓ band: "high"

Anyone with the formula and the inputs can reproduce this score. That is the reproducibility commitment.


6. Versioning policy

  • Every FRI response includes weights_version and computed_at.
  • Changes to weights or formula structure require a version bump and a notes entry in lib/weights.json's updated_at field.
  • Methodology peer review (grant phase) is committed to be co-published with a Nigerian research partner.

7. Known v0 limitations

  1. Risk profile inputs are synthetic. They are realistic by construction but not measured. The same FRI engine accepts real telemetry in grant phase without code change.
  2. Sub-weights are research-informed but not empirically validated. The grant-phase peer review will adjust them against real outcome data.
  3. Outdoor PM2.5 index is a single proxy value per facility. In grant phase this becomes a time-series from Sentinel-5P.
  4. Cross-sector edges are manually curated. The Dependency Discovery Agent in grant phase proposes edges from public registries + satellite imagery, with human confirmation.

8. References

  • Spec v4.1 sections 6 (data model) and 7 (FRI definition)
  • lib/weights.json — current weights, versioned
  • lib/fri.ts — engine implementation (MIT-licensed, auditable)
  • lib/__tests__/fri.test.ts — boundary condition tests
  • WRI Aqueduct (flood) — https://www.wri.org/aqueduct
  • WHO PM2.5 24h thresholds (15 µg/m³ guideline)
  • IPCC stationary combustion factors (diesel CO₂)