Scoring & Data Architecture
Visual breakdown of the scoring methodology and data infrastructure powering Relocate Index — from raw institutional data to real-time personalised country rankings.
System Overview: 3-Layer Scoring Architecture
Layer 1 — Raw Data (Ingestion)
Institutional data stored per country per source in a raw data table. Each record carries the source name, indicator code, year, and raw value. Sources range from API-accessible (World Bank, Open-Meteo) to manually seeded from published reports (IMD, InterNations, Pew, PISA).
Layer 2 — Normalised Scores (Transformation)
All raw values converted to a 0-100 scale via min-max normalisation against the 60-country dataset, with higher always meaning better. Inversions happen at normalisation time — GPI raw scores run 1.0–3.5 (lower = more peaceful), inverted to 0-100 (higher = more peaceful). Min-max boundaries stored in a parameters table and recalculated when countries are added. Output stored in a normalised scores table.
Layer 3 — Composite (Client-Side)
Weighted sum computed entirely in the browser. Users set sliders 0–100 per dimension. Weights normalise internally to sum to 1.0. Slider at 0 excludes the dimension; remaining weights re-normalise. All sliders at 0 triggers an error state. NULL dimensions excluded from composite with automatic re-normalisation.
Decision: All scoring is client-side. Data volume is 60 × 10 = 600 scores — fits in a single small JSON payload. Fetched once on page load from a materialised view. Recomputed on every slider change with zero API round-trips. This architecture choice makes real-time re-ranking free.
Dimension Deep-Dives
Healthcare (V2) — Three-Pillar Formula
healthcare = weighted_blend(UHC, HAQ, capacity)
capacity = weighted_blend(physicians_norm, beds_norm, nurses_norm)
- ◆ Does the system exist? → WHO Universal Health Coverage Service Coverage Index
- ◆ Does it produce good outcomes? → IHME GBD Healthcare Access & Quality Index (amenable mortality across 32 causes)
- ◆ Does it have structural resources? → OECD/WHO physicians, hospital beds, nurses per 1,000 population
Decision: This formula went through 3 iterations. The UHC-only version ranked the USA #1 (UHC score 90 — services exist, but access requires insurance). Adding OOP% didn't fix it (11% of $4.5T = $1,472/person, but 11% looks deceptively cheap as a percentage). The three-pillar formula correctly differentiates: services existing (UHC) ≠ outcomes being good (HAQ) ≠ resources being sufficient (capacity). If HAQ or capacity data is NULL, the formula falls back gracefully — remaining pillars absorb the weight.
Civic Culture — Two-Layer Governance + Street Safety
civic_culture = weighted_blend(governance, street_safety) — governance-heavy
governance = weighted_blend(WGI_rule_of_law, WGI_corruption_control)
street_safety = 100 − numbeo_crime_index
- ◆ Institutional quality → World Bank Worldwide Governance Indicators (drawn from 35 cross-country data sources — surveys, expert assessments, NGO reports). Rule of Law measures contract enforcement, property rights, police/court quality. Corruption Control measures the exercise of public power for private gain.
- ◆ Lived safety → Numbeo Crime Index (free, scraped). Captures the day-to-day experience of personal safety in public spaces that WGI's institutional lens misses.
Decision: The governance-heavy split reflects that institutional governance quality is a deeper, more durable signal than street-level crime perception (which can shift with a single high-profile incident). But a country with excellent institutions and high street crime should not score as well as one where both layers align.
Climate — Discrete Profiles with Calibrated Parameters
climate = f(avg_temp, rain_days, sunshine_hours, profile_params)
Five profiles — Warm & Sunny, Hot & Tropical, Mild & Green, Cold & Crisp, No Preference — each with a distinct reference temperature, temperature penalty curve, rain threshold, and sunshine threshold. The profiles are calibrated so that penalty multipliers match the user's actual tolerance: a tropical profile tolerates high rainfall with minimal penalty, while a sunny profile penalises the same rainfall heavily.
Decision: Discrete profiles instead of continuous sliders. A tropical user genuinely doesn't mind 200+ rain days — the profile penalises only 5 points. A warm-and-sunny user loses 15 points for the same rainfall. This asymmetry encodes domain knowledge. A continuous slider with a single penalty multiplier cannot capture it. Raw climate components are stored in the database; profiles are applied entirely client-side with no additional queries.
Automated Data Pipeline
4 Supabase Edge Functions · pg_cron · Monthly Refresh
- ◆ World Bank refresh — pulls latest WGI, price level, and GDP PPP indicators via API
- ◆ WHO refresh — pulls UHC and OOP health expenditure data via GHO API
- ◆ Climate refresh — fetches Open-Meteo annual averages (temp, rain, sunshine) per country capital
- ◆ Score recompute — re-runs min-max normalisation, applies all dimension formulas (including Healthcare V2), and refreshes the materialised view
Decision: Monthly refresh for API-accessible sources. Annual sources (IMD, InterNations, EF EPI, GPI, Pew, IHME GBD, OECD capacity, PISA) update when new editions are published — manually seeded via seed scripts because the underlying data changes once per year (PISA: every 3 years). Automating their ingestion would add complexity for zero practical benefit. Service role key stored in Supabase Vault. The frontend never touches the service key — it uses only the anon key for read-only access.
NULL Handling & Data Quality
- ◆ NULL dimension scores are excluded from composite calculation. Remaining weights re-normalise to sum to 1.0.
- ◆ Countries with too many NULL dimensions receive a "limited data" flag in the UI.
- ◆ Warmth mismatch flag: when the gap between Hofstede IVR and InterNations exceeds a calibrated threshold, the UI flags the discrepancy. Sweden: high IVR, low InterNations — culturally warm internally, high social wall for foreigners. Portugal: low IVR, high InterNations — restrained culture, genuinely welcoming.
- ◆ English-native countries (UK, Ireland, Australia, New Zealand, Canada, US) hardcoded to 100 for English proficiency regardless of EF EPI coverage.
- ◆ Healthcare system type badges (Covered / Must buy insurance / Tied to employer / Budget for private) add non-scoring context about what each country's system means for a relocator's budget.
Decision: NULL handling is not a fallback — it is a product feature. A country with excellent scores on 7 dimensions and no data on 3 should still rank correctly on the 7 it has. The alternative — excluding countries with any missing data — would eliminate some strong candidates (Taiwan: 7 indicators NULL due to World Bank non-recognition) and penalise data availability rather than country quality.
Onboarding → Weight Mapping
6 questions — covering household situation, income source, civic culture importance, warmth importance, climate preference, and religious community needs — each mapping to dimension weight adjustments based on the user's profile. The mappings are designed so that each answer naturally elevates the dimensions most relevant to that user's situation (e.g., families see school culture and healthcare rise; remote workers see purchasing power rise).
Skip on any question applies default weights. The entire flow runs in Zustand state — no server round-trips — and transfers seamlessly to the ranking view.
Decision: The onboarding is a discovery tool, not a configuration tool. Most users arrive thinking about salary and cost of living. The 6 questions expose them to civic culture, warmth, school environment, religious freedom, and climate as potential priorities they hadn't considered — before the ranking shows them anything. The product's first job is expanding what the user thinks to evaluate.