Skip to content

Epic: Best-effort / inheritance search #860

Description

@AlexAxthelm

Rewrite of #860 from a single implementation ticket into the umbrella epic for this body of work.
The resolver implementation that used to live here moves to a dedicated child (#869).

Summary

Introduce a best-effort / inheritance model to pathway search. When a user narrows their search to
a specific scope (e.g. "Solar power in Thailand"), show the most specific value each pathway actually
has; if a pathway lacks that exact scope, fall back to the closest broader scope it does have,
rather than dropping the pathway or showing nothing:

Solar · Power · Thailand   (exact)
  → Power · Thailand       (drop technology)
  → Power · South East Asia (loosen geography)
  → Power · Global          (loosen geography)

This is one mechanism applied on two hierarchical axes:

  • Geography — country ⊆ region ⊆ global, matched by ISO-3166 code containment.
  • Sector / technology — technology ⊆ sector ⊆ cross-sector.

Inheritance affects three things: which pathways appear (broader-scope matches are included
instead of filtered out), ranking (exact scope ranks above fallback), and the value + scope
displayed
(the card/detail shows the inherited value with a badge naming the scope it came from).

Motivation

Today filterPathways (src/utils/searchUtils.ts:192) is strict boolean pass/fail — a pathway either
matches every active facet or is dropped, and zero matches yields a blank page. Region definitions vary
across publications and few pathways carry country-level detail, so exact-match search hides pathways
that genuinely have relevant (if broader) data. Best-effort matching surfaces that data with honest
scope labelling.

This epic unifies two efforts from the data-model redesign that were scoped separately: the geography
restructuring (Release 1 / #783) and the metadata restructuring (Release 2 / #858 / #859). They are two
axes of the same scoped-matching engine.

Design decisions

  • Scoped fields are per-field arrays of {sector, geography, value} entries.
  • keyFeatures vary by sector + geography only (no technology tag). Technology stays a search
    axis, resolved to its sector through the existing sectors[].technologies[] nesting.
  • Explicit scope sentinels. Every entry names both axes. Widest geography reuses the real
    "Global" value; widest sector uses a new "cross-sector" sentinel ("cross-region" reserved for a
    multi-region non-global aggregate). The resolver compares fully-specified coordinates.
  • On-disk order is irrelevant — priority is recomputed every time from the cost model.
  • Fallback ranking = config-driven per-transition cost. Default precedence sector > geography >
    technology
    ("drop technology first; keep the sector until geography is exhausted at Global").
    Implemented as a single editable cost object so the geography-vs-technology ordering can be flipped in
    one line. Explicitly expected to change once we see it in front of users.
  • Schema versioning: new pathwayMetadata.v2.json + a one-time v1→v2 codemod of the ~60 in-repo
    data files. v1 and v2 coexist via $schema $id routing in validateData.ts.

Children

Dependency order

#783 (+PR #836) ─┐
#858 ─────────────├─▶ #869 ─▶ #859
#461 ─────────────┘
#870 ── parallel        #597 ── with #858

Out of scope

Acceptance (epic-level)

  • Searching a country (e.g. Thailand) surfaces pathways whose only relevant data is at "South East
    Asia" or "Global" scope, ranked below exact matches, each showing a scope badge for the inherited
    value.
  • The blank-search view (no sector/geography filter active) is unchanged.
  • All src/data/** validate against v2; type-check + test suites green.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions