Context
PR #117 (new-format-translator) is the omnibus/baseline implementation of the new-format pipeline, including the timeslice work. It is being brought onto main as a series of small, separately-reviewable PRs rather than merged wholesale.
PR #121 stages the templater half (decoding AEMO's PLEXOS calendar into one window pattern per reference year). While compiling it, we made/validated several decisions that belong to the translator half and so land in later staging PRs. This issue records them so they aren't lost between PRs.
Established along the way: within each region and reference year the shipped v7.5 windows form an exact partition of the year (every day covered by exactly one timeslice — verified across all 75 region×reference-year groups). winter_reference is already the contiguous complement of peak+summer (a fixed 04-01 → 11-01 block for every reference year), with the weather variation carried by the peak/summer windows. The cycle boundary is 1 November; consecutive reference years abut there. The downstream "gap fallback" is therefore defensive code the real data never exercises. The items below lean on that structure holding.
Enforcement, as of #121
- Tiling is enforced inline:
_template_timeslices calls _raise_unless_windows_tile_the_year before returning, so a future calendar that fails to tile fails at template time. The check is boundary-based (sorted by start, each window must end where the next begins, the last wrapping to the first) — no day counting, leap-safe.
- Two structural assumptions underpin the reference-year attribution, and are checked by standalone guards, not inline:
_raise_unless_only_winter_crosses_financial_year (only winter may span 1 July, so weather-varying timeslices stay within one reference year) and _raise_unless_winter_is_constant_per_region (the one boundary-crossing timeslice is identical across reference years, so its attribution can't blur). These run on the real calendar inside test_shipped_calendar_decodes, so they're enforced per workbook version rather than on every decode — deliberately, to keep the focused decoder tests on minimal data.
- The
timeslices schema custom_validation rules (no-overlap, full-coverage) remain inert until the schema-validation layer is built; the guards above are the active enforcement until then.
To action in later 117 staging PRs
-
Re-check coverage/non-overlap after pattern expansion. The templater's check is leap-safe (it verifies the windows' month-day boundaries chain, without counting days), and the templater deliberately preserves 02-29 (clamping is the translator's job — covered by test_convert_windows_to_month_days_preserves_leap_day). The translator re-expands patterns into concrete model years where a 02-29 boundary must be clamped to 02-28 in non-leap years. This is concrete, not hypothetical: reference year 2024 has a 29 February hot day (NSW and VIC) in the shipped calendar, so the clamp fires whenever the configured cycle places 2024 in a non-leap model year. Re-assert the partition on the expanded snapshot mapping, where that clamp could open a one-day seam.
-
Add a round-trip test: decode → re-expand → original calendar. Decoding timeslice_RefYear5000.csv to per-reference-year patterns and re-expanding with the original reference_year_sequence reproduces the kept windows exactly (verified on v7.5: 1460/1460 windows match, start and end). This is the strongest correctness check on the decode — it catches mis-paired windows, reference-year mis-attribution, and any non-invertible decode, none of which the consistency guard catches (AEMO's cycle order is fixed, so a same-reference-year contamination would be identical across occurrences and pass consistency silently). It belongs in the translator PR because it needs the translator's _place_pattern_in_financial_year — run it against the real re-expansion, not a test-local copy.
-
Use max per-direction capacity as link p_nom, not winter_reference. Removes the str.endswith(\"_winter_reference\") special-case in _extract_static_limits and keeps p_max_pu <= 1. Verify winter is the max for every path: if so this is a no-op rename; if not, the current code already emits p_max_pu > 1 for that path (a latent over-crediting bug this fixes).
-
Remove the now-dead gap fallback in _build_link_pu_overrides. Once coverage is enforced and guaranteed, every snapshot is set by exactly one timeslice — drop the static-default seed (the 1.0 / static p_min_pu init) and instead assert the series is fully covered. Constraints need no change: the partition makes the additive "one instance per timeslice, scoped to its snapshots" model exactly-once-per-snapshot already.
-
(Optional) Forbid mixing static (NaN) and timeslice-tagged rows for one entity in the network_transmission_path_limits and custom_constraints_rhs schemas. Each entity is either fully static or fully timeslice-resolved — never a partial mix.
Context
PR #117 (
new-format-translator) is the omnibus/baseline implementation of the new-format pipeline, including the timeslice work. It is being brought ontomainas a series of small, separately-reviewable PRs rather than merged wholesale.PR #121 stages the templater half (decoding AEMO's PLEXOS calendar into one window pattern per reference year). While compiling it, we made/validated several decisions that belong to the translator half and so land in later staging PRs. This issue records them so they aren't lost between PRs.
Established along the way: within each region and reference year the shipped v7.5 windows form an exact partition of the year (every day covered by exactly one timeslice — verified across all 75 region×reference-year groups).
winter_referenceis already the contiguous complement of peak+summer (a fixed04-01 → 11-01block for every reference year), with the weather variation carried by the peak/summer windows. The cycle boundary is 1 November; consecutive reference years abut there. The downstream "gap fallback" is therefore defensive code the real data never exercises. The items below lean on that structure holding.Enforcement, as of #121
_template_timeslicescalls_raise_unless_windows_tile_the_yearbefore returning, so a future calendar that fails to tile fails at template time. The check is boundary-based (sorted by start, each window must end where the next begins, the last wrapping to the first) — no day counting, leap-safe._raise_unless_only_winter_crosses_financial_year(only winter may span 1 July, so weather-varying timeslices stay within one reference year) and_raise_unless_winter_is_constant_per_region(the one boundary-crossing timeslice is identical across reference years, so its attribution can't blur). These run on the real calendar insidetest_shipped_calendar_decodes, so they're enforced per workbook version rather than on every decode — deliberately, to keep the focused decoder tests on minimal data.timeslicesschemacustom_validationrules (no-overlap, full-coverage) remain inert until the schema-validation layer is built; the guards above are the active enforcement until then.To action in later 117 staging PRs
Re-check coverage/non-overlap after pattern expansion. The templater's check is leap-safe (it verifies the windows' month-day boundaries chain, without counting days), and the templater deliberately preserves
02-29(clamping is the translator's job — covered bytest_convert_windows_to_month_days_preserves_leap_day). The translator re-expands patterns into concrete model years where a02-29boundary must be clamped to02-28in non-leap years. This is concrete, not hypothetical: reference year 2024 has a 29 February hot day (NSW and VIC) in the shipped calendar, so the clamp fires whenever the configured cycle places 2024 in a non-leap model year. Re-assert the partition on the expanded snapshot mapping, where that clamp could open a one-day seam.Add a round-trip test: decode → re-expand → original calendar. Decoding
timeslice_RefYear5000.csvto per-reference-year patterns and re-expanding with the originalreference_year_sequencereproduces the kept windows exactly (verified on v7.5: 1460/1460 windows match, start and end). This is the strongest correctness check on the decode — it catches mis-paired windows, reference-year mis-attribution, and any non-invertible decode, none of which the consistency guard catches (AEMO's cycle order is fixed, so a same-reference-year contamination would be identical across occurrences and pass consistency silently). It belongs in the translator PR because it needs the translator's_place_pattern_in_financial_year— run it against the real re-expansion, not a test-local copy.Use max per-direction capacity as link
p_nom, notwinter_reference. Removes thestr.endswith(\"_winter_reference\")special-case in_extract_static_limitsand keepsp_max_pu <= 1. Verify winter is the max for every path: if so this is a no-op rename; if not, the current code already emitsp_max_pu > 1for that path (a latent over-crediting bug this fixes).Remove the now-dead gap fallback in
_build_link_pu_overrides. Once coverage is enforced and guaranteed, every snapshot is set by exactly one timeslice — drop the static-default seed (the1.0/ staticp_min_puinit) and instead assert the series is fully covered. Constraints need no change: the partition makes the additive "one instance per timeslice, scoped to its snapshots" model exactly-once-per-snapshot already.(Optional) Forbid mixing static (NaN) and timeslice-tagged rows for one entity in the
network_transmission_path_limitsandcustom_constraints_rhsschemas. Each entity is either fully static or fully timeslice-resolved — never a partial mix.