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.
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:
This is one mechanism applied on two hierarchical axes:
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 eithermatches 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
{sector, geography, value}entries.axis, resolved to its sector through the existing
sectors[].technologies[]nesting."Global"value; widest sector uses a new"cross-sector"sentinel ("cross-region"reserved for amulti-region non-global aggregate). The resolver compares fully-specified coordinates.
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.
pathwayMetadata.v2.json+ a one-time v1→v2 codemod of the ~60 in-repodata files. v1 and v2 coexist via
$schema$idrouting invalidateData.ts.Children
Define filter regions #798, Disclaimer about region mappings #800, update pathways to reflect new pathway region geography mapping #801. Delivers ISO-overlap matching = the geography half of the resolver.
pathwayDescription.
axis needs.
Dependency order
Out of scope
metricfield (separate future release; structure TBD).Acceptance (epic-level)
Asia" or "Global" scope, ranked below exact matches, each showing a scope badge for the inherited
value.
src/data/**validate against v2; type-check + test suites green.