diff --git a/src/schema/pathwayTimeseries.v2.json b/src/schema/pathwayTimeseries.v2.json index ca7224f1..16072f6e 100644 --- a/src/schema/pathwayTimeseries.v2.json +++ b/src/schema/pathwayTimeseries.v2.json @@ -132,6 +132,8 @@ "nuclear", "biomass", "hydro", + "geothermal", + "energyStorage", "renewables", "electricVehicles", "hydrogenVehicles", @@ -298,6 +300,7 @@ "then": { "properties": { "technology": { + "$comment": "Exactly the Power technology keys of src/utils/timeseriesTaxonomy.ts, which the plots and the CSV download look every row up in; pathwayTimeseries.v2.test.ts checks the two agree. Every value must also be in the all-sector enum above, because both lists apply to a Power row: `geothermal`, `battery` and `hydrogen` used to be listed here only, so they could never validate (#977). Storage is `energyStorage` (cookbook decision 0020), matching the `Energy storage` segment.", "type": ["string", "null"], "enum": [ "coal", @@ -309,9 +312,8 @@ "solar", "biomass", "geothermal", - "hydrogen", + "energyStorage", "renewables", - "battery", "other", null ] diff --git a/src/schema/pathwayTimeseries.v2.test.ts b/src/schema/pathwayTimeseries.v2.test.ts index db2a667f..9eb8eebb 100644 --- a/src/schema/pathwayTimeseries.v2.test.ts +++ b/src/schema/pathwayTimeseries.v2.test.ts @@ -2,8 +2,56 @@ import { describe, it, expect } from "vitest"; import timeseriesJson from "./pathwayTimeseries.v2.json" with { type: "json" }; import { commonSchemas } from "./common/index.ts"; import { validateFilesBySchema } from "../utils/validateData"; +import { SECTORS_BY_KEY } from "../utils/timeseriesTaxonomy"; import ieaSteps from "../data/iea/IEA-STEPS-2024_timeseries.json" with { type: "json" }; +/** The slice of the schema these assertions read. */ +interface RowSchema { + properties: { technology: { enum: (string | null)[] } }; + then: { properties: { technology: { enum: (string | null)[] } } }; +} + +const row = ( + timeseriesJson as unknown as { + properties: { data: { items: RowSchema } }; + } +).properties.data.items; +const allSectors = row.properties.technology.enum; +const power = row.then.properties.technology.enum; + +describe("pathwayTimeseries.v2 Power technologies (#977)", () => { + it("are exactly the taxonomy's Power technology keys", () => { + // The plots and the CSV download look every row's technology up in the + // taxonomy, so the schema must not allow one it cannot resolve. + const taxonomy = Object.keys(SECTORS_BY_KEY.power.technologies ?? {}); + expect(power.filter((t) => t !== null).sort()).toEqual(taxonomy.sort()); + expect(power).toContain(null); // a sector-level row has no technology + }); + + it("are all in the all-sector enum too, since both lists apply", () => { + expect(power.filter((t) => !allSectors.includes(t))).toEqual([]); + }); + + const validate = (technology: string) => { + const doc = structuredClone(ieaSteps) as { + data: { technology: unknown }[]; + }; + doc.data = [{ ...doc.data[0], technology }]; + return validateFilesBySchema( + [{ name: "row.json", data: doc }], + [timeseriesJson, ...commonSchemas], + ); + }; + + it.each(["geothermal", "energyStorage"])("accepts a %s row", (technology) => { + expect(validate(technology).invalid).toEqual([]); + }); + + it.each(["battery", "hydrogen"])("rejects the retired %s", (technology) => { + expect(validate(technology).invalid).toHaveLength(1); + }); +}); + /** A real file with one field replaced, validated against the v2 schema. */ const validateWith = (over: Record) => validateFilesBySchema( diff --git a/src/types/pathwayTimeseries.v2.d.ts b/src/types/pathwayTimeseries.v2.d.ts index 725e004b..52a14c72 100644 --- a/src/types/pathwayTimeseries.v2.d.ts +++ b/src/types/pathwayTimeseries.v2.d.ts @@ -117,6 +117,8 @@ export interface PathwayTimeseriesV2 { | "nuclear" | "biomass" | "hydro" + | "geothermal" + | "energyStorage" | "renewables" | "electricVehicles" | "hydrogenVehicles"