Skip to content

Re-sequence new-format timeslice patterns onto snapshots - #130

Merged
nick-gorman merged 7 commits into
mainfrom
resequence-timeslices-onto-snapshots
Aug 23, 2026
Merged

Re-sequence new-format timeslice patterns onto snapshots#130
nick-gorman merged 7 commits into
mainfrom
resequence-timeslices-onto-snapshots

Conversation

@nick-gorman

@nick-gorman nick-gorman commented Jul 8, 2026

Copy link
Copy Markdown
Member

Adds the translator-side timeslice module. It maps each templated timeslice window pattern onto the model's snapshots, producing a timeslice_snapshots table (timeslice_id, investment_periods, snapshots) that later functionality will use to apply per-timeslice link limits and to scope the temporal custom constraints.

src/ispypsa/translator/
└── timeslices.py                        → match snapshots to timeslice windows on month-day
src/ispypsa/validation/schemas/
└── timeslices.yaml                      → month-day window semantics; config-to-table validation entry
tests/test_translator/
└── test_timeslice_snapshots.py          → one realistic multi-region/multi-year case plus one test per edge rule

🤖 Generated with Claude Code

The translator assigns a reference year to each model financial year
with the identical construct_reference_year_mapping call the trace
pipeline makes, so timeslice windows and demand/VRE traces always come
from the same weather years — the binding interaction between hot-day
limits and hot-day demand survives any configured cycle. The year before
the first model year also gets a pattern (the cycle's last reference
year, cycle-consistent) because winter windows run April-October and
must cover the first model year's July-September snapshots.

Month-day boundaries of 29 February (reference year 2024 carries them)
clamp to 28 February in non-leap model years. The wrap decision for
exclusive ends compares the month-day strings rather than the placed
dates, so clamping a one-day 02-28..02-29 window collapses it to empty
instead of stretching it out a year. A configured cycle requesting
reference years the timeslices table has no patterns for raises rather
than silently never binding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/ispypsa/translator/timeslices.py 100.00% <100.00%> (ø)

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

nick-gorman and others added 6 commits August 17, 2026 12:20
…ed windows

The first cut re-sequenced patterns by placing each month-day window into
absolute dates within its financial year and joining snapshots onto the
result. That needed a prior-year pattern to cover the first model year's
July-October (winter starts in the previous financial year), a rule for
which calendar year a start lands in, leap-day clamping for 29 February
boundaries landing in non-leap years, and a subtle string-vs-date wrap
comparison to keep the clamp from stretching a one-day window out a year.
It also relied on the boundary-crossing window (winter) being identical
across reference years so the seams between model years tiled cleanly.

Comparing each snapshot's month-day directly against its model year's
pattern removes all of that. A snapshot is in a window when its month-day
lies in the window's range (an end at or before its start wraps past New
Year), so no dates are constructed, 29 February simply never matches in a
non-leap year, and each model year is self-contained: it takes every one of
its dates from its own reference year's pattern, so switching pattern at
the model-year boundary can never leave a gap or an overlap whatever the
two patterns look like. That also drops the fy-only restriction — the only
year_type-specific step is which calendar year a snapshot's model year is.

Snapshots are stamped with their interval's end time, so model year and
month-day are read off the interval's last instant (one second before the
stamp): the snapshot stamped 1 July 00:00 is the last interval of the
financial year just ended, and one stamped 1 November 00:00 falls in the
window ending 1 November rather than the one starting there.

The schema's column descriptions lose the placement and clamping rules,
and gain a config-to-table validation entry recording that the configured
reference_year_cycle must have patterns (enforced by the translator).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Whether every reference year in the configured reference_year_cycle has a
timeslice pattern is a config-to-table check, which belongs in the
validation layer once it has the model config in hand. The schema entry
now specifies it (skipped when the table is absent or empty, since no
patterns at all is a valid configuration) and the translator no longer
raises. A reference year with no pattern simply tags none of its model
year's snapshots; tagging is now a cross join of each model year's
snapshots with its pattern filtered by a vectorised month-day mask, so an
empty pattern falls out naturally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cstring

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Series.where reads back to front (self is the true branch); naming the two
cases and combining them with plain boolean algebra reads as the sentence
the docstring states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… config

The mapping was built inside the function from the capacity-expansion
reference_year_cycle, which silently diverged from the trace pipeline for
operational-phase snapshots (that pipeline picks the cycle by model phase).
Taking reference_year_mapping and year_type directly — the same arguments
the trace timeseries functions take — lets the caller pass the one mapping
it built for the traces, so timeslices and traces come from the same
weather years by construction. The module no longer depends on the config
or isp_trace_parser, and the tests pass a literal mapping instead of
mutating the fixture config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@EllieKallmier EllieKallmier left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah this all seems pretty buttoned up to me :)

A general translator-related thought comes up - that I think we've discussed before but I can't remember the conclusion/if there was one - around validating/checking translator outputs and whether we're going to adopt the same approach here as for the templater outputs. Or even just whether there's value in writing up translator output schemas in a similar way as a mid-term reference or something?

But no notes on this PR from me, happy to go with this as is :)

@nick-gorman
nick-gorman merged commit ef0ae3d into main Aug 23, 2026
15 checks passed
@nick-gorman
nick-gorman deleted the resequence-timeslices-onto-snapshots branch August 23, 2026 23:19
@nick-gorman

Copy link
Copy Markdown
Member Author

Yeah this all seems pretty buttoned up to me :)

A general translator-related thought comes up - that I think we've discussed before but I can't remember the conclusion/if there was one - around validating/checking translator outputs and whether we're going to adopt the same approach here as for the templater outputs. Or even just whether there's value in writing up translator output schemas in a similar way as a mid-term reference or something?

But no notes on this PR from me, happy to go with this as is :)

@ellie, sorry forgot to reply to this. Yep, I think translator output schema make a lot of sense and once we build the infrastructure for running validation on the templater output, re-using it for the translator would be pretty easy. So yeah, I think we could be writing those schema going forward if we wanted to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module: translator Covers contents of `translator` module type: feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants