Skip to content

feat(key-features): forbidden key-feature pairs carry no value (Electrification · Power) - #1045

Merged
AlexAxthelm merged 6 commits into
epic/v2from
feat/forbidden-key-features
Oct 30, 2026
Merged

AlexAxthelm merged 6 commits into
epic/v2from
feat/forbidden-key-features

Conversation

@AlexAxthelm

Copy link
Copy Markdown
Collaborator
  • reject authored values for forbidden key-feature pairs (currently just Electrification/Power)
  • Remove values existing in data
  • When a forbidden pair has a fallback value (electrification/across sectors), show that with an informative tooltip, when it doesn't show "Not Applicable"

Summary

Electrification cannot apply to the Power sector. Until now the data marked that pair with "Not Applicable" (cookbook decision 0023), and the resolver skipped the cell. A Power-only pathway with no across-sectors row then ran out of scopes, and its page said "No information", with the hover "Nothing is recorded for this pathway at any scope". That reads as if the pathway could have said something there and didn't. It affects 57 live pages, including CGS-IESR, JETP-CIPP, TransitionZero and SDSN LA/TH/MM.

This PR makes a forbidden pair carry no value at all. The pairs are declared once, in src/utils/keyFeatureRules.ts, and four places read that declaration:

  • the page decides its wording from it;
  • the resolver never reads a forbidden cell;
  • the validator rejects any entry at a forbidden pair;
  • the workbook importer refuses any cell at one.

Today the only forbidden pair is Electrification · Power.

Behaviour

Pathway Before After
Has an across-sectors value (e.g. IEA-NZE-2024, on Power) Across-sectors value + scope badge, hover "Electrification does not apply to the Power sector; showing the closest broader scope this pathway reports." Unchanged
Power-only (e.g. JETP-CIPP-2023) "No information" — "Nothing is recorded for this pathway at any scope." "Not applicable" — "Electrification does not apply to the Power sector."

"No information" keeps its meaning: the pathway could say something here and doesn't.

Commits

  1. feat(key-features): say "Not applicable" where a feature cannot apply. This is the page change on its own, and it works against the old data.
    • Restores FORBIDDEN_KEY_FEATURE_SECTORS / isForbiddenKeyFeatureSector, which 693e3a7 removed.
    • The resolver drops a forbidden sector from the chain without reading it, and sets notApplicableSector on both resolved and unavailable results.
    • KeyFeatures shows the "Not applicable" badge in the unavailable case.
    • scopeAvailability ignores entries at forbidden pairs.
  2. feat(validation): reject any value at a forbidden key-feature pair.
    • validateScopedEntries (npm run schema:check) rejects any entry at a forbidden pair, whatever its value.
    • "Not Applicable" leaves the electrification enum, and the types are regenerated.
    • import-pathway-data treats a non-blank cell at a forbidden pair as an authoring error, so nothing is written. This is the same pattern as the unmapped-region refusal (Fix/single country regions #1035).
    • The v1→v2 codemod emits no Electrification for a Power-only pathway.
    • The v2 test fixture's Power Electrification entry moves to Steel.
  3. fix(data): drop Electrification values for the Power sector.
    • Removes 701 entries from 71 files: 695 "Not Applicable" and 6 "No information" (SDSN Singapore). Nothing else in those files changes.

Commit 2 on its own fails schema:check on src/data, and commit 3 fixes it, so please keep those two together. Commit 1 stands alone.

Workbook follow-up

src/data is regenerated from the workbook, so the workbook has to change too. Every Electrification cell on a Power row in draft_key_features must be blank: 701 cells across 71 pathways. Until then the importer refuses to write anything, which is deliberate, so a re-import can't undo commit 3. Cookbook decision 0023's text needs the matching update. The details are in the workbook issue.

Testing

  • npx vitest run --exclude '**/.claude/**': 92 files, 1487 tests, all passing.
  • npm run schema:check: 129 valid items, 0 invalid.
  • ESLint and Prettier are clean on the changed files.
  • tsc -p tsconfig.app.json reports 202 errors, against 201 on epic/v2. The new one is TS5097 on the .ts-extension import of keyFeatureRules.ts in validateScopes.ts, the same error the existing timeseriesTaxonomy.ts import raises; ts-node needs the extension.
  • Checked in the browser against the dev server: JETP-CIPP-2023 shows "Not applicable" with the new hover, and IEA-NZE-2024 on Power shows the across-sectors value with its badge and hover.

🤖 Generated with Claude Code

repro and others added 3 commits October 29, 2026 15:48
A Power-only pathway (JETP-CIPP-2023, CGS-IESR, TZ, SDSN LA/TH/MM) has no
across sectors row for Electrification to inherit. The resolver ran out of
scopes and the page said "No information" with "Nothing is recorded for this
pathway at any scope", as if the pathway could have said something there and
didn't. Electrification of Power cannot exist, so the page now says that.

The forbidden field x sector pairs are declared once again, in
keyFeatureRules, and the resolver reads the declaration rather than a
"Not Applicable" cell. A forbidden pair is to carry no value at all. The
validator change that enforces this follows separately.

* restore FORBIDDEN_KEY_FEATURE_SECTORS / isForbiddenKeyFeatureSector
  (removed in 693e3a7) and drop MEASURE_NOT_APPLICABLE
* resolver: leave a forbidden sector out of the chain without reading it,
  and set notApplicableSector on unavailable results as well as resolved
* KeyFeatures: an unavailable result with notApplicableSector shows a
  "Not applicable" badge, hover "<Feature> does not apply to the <Sector>
  sector."; "No information" keeps its meaning
* scopeAvailability: ignore entries at a forbidden pair, whatever they hold
* tests: rules, resolver, KeyFeatures, scopeAvailability, and a Power-only
  fixture in PathwayDetailPage.scope

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Electrification x Power now has no value at all, so the data can no longer
say "Not Applicable" or "No information" there. The pairs are declared once,
in keyFeatureRules; the CI check and the workbook importer both read that
declaration, so neither the importer nor a hand-edit can bring a value back.

This makes schema:check fail on src/data until the 701 existing entries are
removed (next commit).

* validateScopes: any keyFeatures entry at a forbidden field x sector is an
  error, whatever its value
* schema: drop "Not Applicable" from the electrification enum and note the
  rule in its description; regenerate pathwayMetadata.v2.d.ts
* import-pathway-data: a non-blank cell at a forbidden pair is an authoring
  error (nothing written, as for unmapped regions), one per pathway naming its
  rows; retire decision 0023's "Not Applicable" in the isAbsent comment
* codemod-v1-to-v2: emit no electrification for a Power-only pathway
* testdata: move the v2 fixture's Power electrification entry to Steel
* tests: validateScopes, importer buildKeyFeatures and keyFeaturesFromV1,
  codemod, schema and KeyFeatures allow-lists

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Electrification cannot apply to Power, and a forbidden pair now carries no
value at all (previous commit). Remove the 701 entries the data still had
there: 695 "Not Applicable" (cookbook decision 0023's marker, now retired)
and 6 "No information" in the SDSN Singapore files.

Nothing else in these files changes. The pages are unaffected: the resolver
already never read these cells. Power-only pathways with no across-sectors
row now show "Not applicable", and the others keep inheriting their
across-sectors value.

* 71 files across CGS-IESR, IEA, JETP, JRC, NGFS, PHDOE, TransitionZero,
  UN SDSN/CW, UTS-ISF and EREA/DEA
* 23 of them are left with an empty electrification array

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings October 29, 2026 15:12
@AlexAxthelm
AlexAxthelm changed the base branch from main to epic/v2 October 29, 2026 15:14
@RMI RMI deleted a comment from github-actions Bot Oct 29, 2026
@github-actions

Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://proud-glacier-0f640931e-1045.westus2.2.azurestaticapps.net

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Two unresolved moderate findings remain: a focus side effect inside a state updater in useDropdown, and a useElementWidth observer that never attaches when the page switches to the Power sector, leaving those charts at the fallback size.

No open issues 🎉

What changed in this PR

The PR description focuses on one rule: a key-feature pair that cannot apply (Electrification for the Power sector) should carry no value. It should show "Not applicable" instead of "No information", and the validator, resolver, and importer should enforce that. The diff under review is broader. It also adds the v2 timeseries and sector-segment schemas, the comparison page's scope, ribbon, dependency and plot work, geography and region cleanup, plot year capping, a design-token build with a CI check, and README import docs.

Changes:

  • Adds v2 schemas and types for scoped key features, sector segments, data availability, and timeseries. Regions must have at least two members.
  • Adds comparison-page scope handling: shared sector and per-column geography, ribbon, plots, and dependencies. Tray restore drops incompatible selections.
  • Adds reusable hooks and components for URL state, dropdowns, element width, tooltips with a button mode, and scope badges.
  • Adds timeseries build and CSV changes, schema-check cross-file validation, and a design-token generator with a CI job.
File Description
testdata/​valid/​pathwayMetadata_v2_minimal.json Minimal v2 fixture for schema tests.
testdata/​valid/​pathwayMetadata_sample_03.json Region now has real member countries.
testdata/​valid/​pathwayMetadata_sample_02.json Region now has real member countries.
testdata/​valid/​pathwayMetadata_minimal.json Region now has real member countries.
src/​utils/​validateTimeseries.ts Cross-file checks for timeseries geography and segments.
src/​utils/​tooltipUtils.ts Sector-segment tooltips from the taxonomy; adds SMR tooltip.
src/​utils/​tooltipUtils.test.tsx Tests segment tooltips against the taxonomy.
src/​utils/​timeseriesSegments.ts Derives plotted segments for a series.
src/​utils/​timeseriesSegments.test.ts Tests segment derivation and ordering.
src/​utils/​timeseriesIndex.ts Adds a synchronous index getter.
src/​utils/​timeseriesAvailability.ts Exact geography matching; tolerates missing metrics axis.
src/​utils/​timeseriesAvailability.test.ts Tests exact matching and missing metrics axis.
src/​utils/​searchUtils.ts Scope-aware filters for emissions trajectory and policy ambition.
src/​utils/​searchUtils.test.tsx Updates region fixtures to two or more members.
src/​utils/​scopeLabel.ts Names the active scope in prose.
src/​utils/​scopeLabel.test.ts Tests scope labels.
src/​utils/​scopeAvailability.ts Decides which scope tokens are selectable.
src/​utils/​scopeAvailability.test.ts Tests scope availability rules.
src/​utils/​plotYears.ts Caps plots at 2050 and reports overflow.
src/​utils/​plotYears.test.ts Tests the 2050 cap.
src/​utils/​loadData.ts Extracts a dev-mode check, isViteDev.
src/​utils/​loadData.test.tsx Tests isViteDev.
src/​utils/​geographyUtils.ts Updates disclaimer wording; adds spelling-fold key.
src/​utils/​geographyUtils.test.tsx Updates fixtures to two-member regions.
src/​utils/​geographyFallback.ts Resolves the geography a plot falls back to.
src/​utils/​filterRegions.ts Doc updates for the resolver's source of region membership.
src/​types/​pathwayTimeseries.v2.d.ts Generated type for v2 timeseries.
src/​types/​pathwayMetadata.v1.d.ts Generated type: pathway type doc and new technologies.
src/​types/​index.ts Exposes v2 metadata and scope selection types.
src/​types/​common/​technology.v1.d.ts Adds new technology display names.
src/​types/​common/​sectorSegment.v1.d.ts Generated type for sector segments.
src/​types/​common/​scopeSector.v2.d.ts Generated type for scope sector.
src/​types/​common/​scopeGeography.v2.d.ts Generated type for scope geography.
src/​types/​common/​metric.v1.d.ts Drops sectorScope from the type.
src/​types/​common/​geography.v1.d.ts Generated type: regions need two or more members.
src/​types/​common/​dataAvailabilityMetric.v1.d.ts Generated type for data-availability metrics.
src/​types/​common/​dataAvailability.v1.d.ts Generated type for data-availability vocabularies.
src/​test/​mockIntersectionObserver.ts Test mock accepts a bounding rectangle.
src/​schema/​pathwayTimeseries.v2.test.ts Tests v2 timeseries schema rules.
src/​schema/​pathwayMetadata.v1.json Doc wording fix.
src/​schema/​common/​technology.v1.json Adds new technology names.
src/​schema/​common/​sectorSegment.v1.json New sector-segment vocabulary.
src/​schema/​common/​scopeSector.v2.json New scope-sector vocabulary.
src/​schema/​common/​scopeGeography.v2.json New scope-geography rule.
src/​schema/​common/​metric.v1.json Removes the sectorScope field.
src/​schema/​common/​index.ts Registers the new common schemas.
src/​schema/​common/​geography.v1.json Regions need at least two members.
src/​schema/​common/​dataAvailabilityMetric.v1.json New data-availability metric schema.
src/​schema/​common/​dataAvailability.v1.json New data-availability vocabularies.
src/​pages/​resources/​ResourcesMethodologyPage.tsx Updates the region note and new feature lists.
src/​pages/​resources/​ResourcesMethodologyPage.test.tsx Tests the updated region note.
src/​pages/​PathwaySearch.test.tsx Uses scoped key-feature entries in fixtures.
src/​pages/​PathwayDetailPage.test.tsx Deep-links to the scope tab; removes unmapped-region test.
src/​pages/​PathwayDetailPage.disclaimer.test.tsx Deep-links to the scope tab; uses two-member region.
src/​pages/​LandingPage.tsx Copy fix.
src/​pages/​ComparisonPage.disclaimer.test.tsx Shares a sector across fixtures; raises timeouts.
src/​index.css Adds a rose colour family for segment badges.
src/​hooks/​useUrlParamState.ts URL-backed selection state hook.
src/​hooks/​useUrlParamState.test.tsx Tests the URL state hook.
src/​hooks/​useSessionFlag.ts Session-scoped on/off preference hook.
src/​hooks/​useSessionFlag.test.tsx Tests the session flag hook.
src/​hooks/​useElementWidth.ts Measures element width for charts.
src/​hooks/​useElementWidth.test.tsx Tests element width measurement.
src/​hooks/​useDropdown.ts Shared dropdown open, close, and escape handling.
src/​data/​pathwayMetadata.ts Warns in dev about unmigrated v1 metadata.
src/​context/​ComparisonContext.tsx Restores the tray and drops incompatible selections.
src/​context/​ComparisonContext.test.tsx Uses real pathway ids in restore tests.
src/​context/​ComparisonContext.restore.test.tsx Tests tray restore rules with mocked data.
src/​components/​TextWithTooltip.tsx Adds a button trigger mode.
src/​components/​TextWithTooltip.test.tsx Tests the button trigger mode.
src/​components/​Tabs.test.tsx Tests tabs and URL-backed active tab.
src/​components/​SectionIntro.tsx Short intro text under section headings.
src/​components/​ScopeFilterNotice.tsx Says how many rows a scope hides.
src/​components/​ScopeAxisRow.tsx Shared scope axis row with a show-more control.
src/​components/​RegionMembersTooltip.tsx Always shows a country count.
src/​components/​RegionMembersTooltip.test.tsx Updates tests for the count wording.
src/​components/​PlotSelector.tsx Adds segment field to plot data points.
src/​components/​PlotSelector.test.tsx Adds segment field to fixtures.
src/​components/​PathwayCard.tsx Blocks adding pathways with no shared sector.
src/​components/​PathwayCard.test.tsx Updates fixtures; tests stale ids vs. limit.
src/​components/​PathwayCard.comparison.test.tsx Tests add-to-comparison blocking rules.
src/​components/​NormalizedStackedAreaChart.tsx Adds geothermal colour; removes renewables.
src/​components/​NormalizedStackedAreaChart.test.ts Checks colour coverage for power technologies.
src/​components/​GeographyDivergenceNotice.tsx Explains when comparison columns differ.
src/​components/​DropdownFacetShell.tsx Uses the shared dropdown hook.
src/​components/​DownloadDataset.tsx Notes when a download runs past 2050.
src/​components/​DownloadDataset.test.tsx Tests the past-2050 note.
src/​components/​CoreDrivers.tsx Renders core drivers, including "not a driver".
src/​components/​CoreDrivers.test.tsx Tests core driver rendering.
src/​components/​ComparisonRibbon.tsx Carries scope into the comparison URL.
src/​components/​ComparisonKeyFeatures.tsx Adds core drivers to the comparison grid.
src/​components/​ComparisonDependencies.tsx New dependencies grid for comparisons.
src/​components/​Badge.tsx Adds segment and scope variants; fixes -pub colours.
src/​components/​Badge.test.tsx Tests new variants and theme colour tokens.
scripts/​timeseries-csv.ts CSV helpers for timeseries download.
scripts/​timeseries-csv.test.ts Tests CSV helpers.
scripts/​schema-check-files.ts Adds v2 and cross-file checks; fixes annotation paths.
scripts/​codemod-timeseries-v1-to-v2.test.ts Tests the timeseries v1 to v2 codemod.
scripts/​build-timeseries-files.ts Writes segment columns and CSV via shared helpers.
scripts/​build-design-tokens.ts Generates colour tokens from the stylesheet.
scripts/​build-design-tokens.test.ts Tests token generation.
README.md Documents adding pathways and benchmark data.
package.json Adds tokens:build script and exceljs dev dependency.
docs/​design/​README.md Describes the design token and UI kit files.
.gitignore Ignores .venv/ and TypeScript build info.
.github/​workflows/​node-build-committed-files.yml Adds CI check for design tokens.

Review effort: Lite

@AlexAxthelm
AlexAxthelm marked this pull request as ready for review October 29, 2026 19:31
Copilot AI lite review requested due to automatic review settings October 29, 2026 19:31
@github-actions

Copy link
Copy Markdown

Expected version change and release notes

🚨 WARNING: This PR is not expected to trigger a new version

To trigger a version bump, use at least one conventional commit message in this branch. See: https://www.conventionalcommits.org/en/v1.0.0/

@github-actions

Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://proud-glacier-0f640931e-1045.westus2.2.azurestaticapps.net

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The comparison grid in ComparisonKeyFeatures.tsx still shows "No information" with no tooltip for forbidden sector pairs, so it doesn't match the new "Not applicable" handling in KeyFeatures.tsx.

2 open issues

Review effort: Lite

Comment thread src/components/KeyFeatures.tsx Resolved
Comment thread src/utils/validateScopes.ts Resolved
The header called Electrification x Power a sibling-dependent rule beyond
draft-07. It reads only the entry's own sector, so the schema could express
it per item. It lives in validateScopes so the validator, importer and page
share the one declaration in keyFeatureRules.ts.
@github-actions

Copy link
Copy Markdown

Expected version change and release notes

🚨 WARNING: This PR is not expected to trigger a new version

To trigger a version bump, use at least one conventional commit message in this branch. See: https://www.conventionalcommits.org/en/v1.0.0/

@github-actions

Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://proud-glacier-0f640931e-1045.westus2.2.azurestaticapps.net

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The importer refusal depends on Electrification cells in an external workbook that cannot be checked from this repo, and the browser checks were manual while the cookbook 0023 text sits outside the diff, so the key behavior cannot be verified from the review evidence.

No open issues 🎉

2 resolved since last review

Review effort: Lite

@jacobvjk

Copy link
Copy Markdown
Collaborator

@AlexAxthelm wouldn't it be possible to expect that power x electrification have a specific value ("Not applicable") rather than expecting it to be empty? The new expectation requires an update of the cookbook and updates to all authored data sets. Expecting "Not applicable" would require zero updates

Cookbook decision 0023 has authors write "Not Applicable" for
Electrification x Power. Accept it there, as well as a blank cell, and
write no entry either way, so the workbook keeps 0023's convention while
the data still carries no value at the pair. Any other value there,
"No information" included, is still refused.
Copilot AI lite review requested due to automatic review settings October 30, 2026 10:51
@AlexAxthelm

Copy link
Copy Markdown
Collaborator Author

@AlexAxthelm wouldn't it be possible to expect that power x electrification have a specific value ("Not applicable") rather than expecting it to be empty? The new expectation requires an update of the cookbook and updates to all authored data sets. Expecting "Not applicable" would require zero updates

Sounds good. I have the imported accept it, and then not write it to the imported JSON files, so we can keep it explicit in the workbooks, but then use the normal fallback behavior that we're introducing here.

@github-actions

Copy link
Copy Markdown

Expected version change and release notes

🚨 WARNING: This PR is not expected to trigger a new version

To trigger a version bump, use at least one conventional commit message in this branch. See: https://www.conventionalcommits.org/en/v1.0.0/

@github-actions

Copy link
Copy Markdown

Azure Static Web Apps: Your stage site is ready! Visit it here: https://proud-glacier-0f640931e-1045.westus2.2.azurestaticapps.net

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

A moderate-severity hidden finding in scripts/codemod-v1-to-v2.ts shows that a non-blank electrification value can be dropped from Power-only v1 files without any note in the run report, so a human reviewer needs to assess this undisclosed data-loss risk before the change can be approved.

No open issues 🎉

Review effort: Lite

@jacobvjk jacobvjk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm with one nb nit

feature: FeatureConfig,
notApplicableSector: string,
): string {
return `${feature.label} does not apply to the ${notApplicableSector} sector.`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb: would be easier to understand if we explained why, but this may introduce some complexity. E.g. in this case: "Does not apply to the Power sector: Power is not an end-use sector, so Electrification cannot be measured."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flagged as #1050

@AlexAxthelm
AlexAxthelm merged commit 0a1d0bd into epic/v2 Oct 30, 2026
12 checks passed
@AlexAxthelm
AlexAxthelm deleted the feat/forbidden-key-features branch October 30, 2026 11:09
AlexAxthelm pushed a commit that referenced this pull request Oct 30, 2026
Memoizes the grid's per-column resolvers, so a page re-render for state
the grid ignores no longer rebuilds every column's geography chain. Adds
a grid test for Electrification on a Power-only pathway reading "Not
applicable" (#1045), and makes the page test check each column's cell
rather than finding the value anywhere on the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

3 participants