Skip to content

feat: generic multi-file simulation combining via BaseSimulation.from_paths - #41

Open
micahpw wants to merge 1 commit into
v0.2.0from
v0.2.0-multifile-combining
Open

feat: generic multi-file simulation combining via BaseSimulation.from_paths#41
micahpw wants to merge 1 commit into
v0.2.0from
v0.2.0-multifile-combining

Conversation

@micahpw

@micahpw micahpw commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Part of the v0.2.0 effort. This lands before the PLEXOS H5-removal work (currently paused) per the plan -- it doesn't conflict with that removal, but the maintainer wanted the multi-file contract settled first since both touch related file-resolution territory.

What this adds

A generic way to treat N partition files (weekly/monthly chunks, submitted in parallel) as one logical simulation, so format extensions get it for free without reimplementing the combining logic -- while formats whose combining mechanism is fundamentally different can still override it.

  • BaseSimulation.from_paths (src/gat/interfaces.py) -- non-abstract classmethod. Default: one cls(path, **kwargs) per file, combined via the new MultiFileSimulation (src/gat/simulations/multi_file.py), built on the existing combine_overlapping_frames primitive.
  • PlexosDuckDBSimulation.from_paths overrides trivially -- PlexosDuckDBSource already combines multiple files via SQL ATTACH, no need to route through the per-file-object model.
  • SiennaSimulation.from_paths overrides to read the across-file merge direction from the already-existing per-model SiennaModelConfig.merge field (previously only used for within-file block dedup), instead of a single hardcoded default. A UC/decision model and an emulation model can legitimately want different behavior at a partition seam -- confirmed against real fixture data (example_data/sienna/v4, default model "Emulator", merge_strategy correctly read as "right").
  • SimulationAggregator (legacy scenariohandlers world): additive parser_kwargs param only, fully backward compatible, future-proofs it for parser classes needing more than a bare path. SiennaScenario/tests/handlers/test_sienna_multifile.py untouched otherwise.
  • simulations/utils.py: dedup_slices no longer silently drops a block when two partitions share an identical start timestamp (was keyed by a plain start-time dict -- a real, if rare, data-loss bug). combine_overlapping_frames gains self-documenting "earlier_wins"/"later_wins" aliases for the confusing "right"/"left" naming (old names kept, backward compatible). New tests pin gap handling, differing columns, and timezone-mismatch behavior.
  • server/ingest.py -- fixes two real, previously-undetected bugs found while wiring this up:
    • Sienna branch silently used only the first of multiple simulation_paths (comment admitted it). Now uses from_paths.
    • PLEXOS branch imported gat.simulations.plexos, a module that has never existed -- this branch has always raised ImportError. Now uses the duckdb backend (PlexosDuckDBSystem/PlexosDuckDBSimulation), which needs no separate system file.

Explicitly out of scope (documented, not fixed here)

  • The ~9% generation undercount bug tied to the legacy combine_frames_skip_prev (still "mid-investigation" per docs/source/architecture/v1_migration_pattern.md). This design sidesteps it by construction (everything here is built on combine_overlapping_frames, which doesn't share that defect) but doesn't fix the legacy function. Not deleted -- still has a live consumer at datahelpers/sienna.py:28.
  • Differing frequencies between partition files (hourly vs. 5-minute) -- genuinely separate scope (resampling policy).

Verification

  • Full suite: 257 passed, 57 skipped, 0 failed (excludes test_plexos_regression.py, confirmed pre-existing-broken on this machine from a local fixture mismatch, unrelated to this change).
  • tests/handlers/test_sienna_multifile.py and tests/simulations/test_combine_frames_aggregator.py pass unchanged -- proves the legacy path is genuinely untouched.
  • SiennaSimulation.from_paths exercised end-to-end against real fixture data (not just mocks) for both single- and multi-file (duplicated-file) cases.
  • black --check / ruff check clean on all touched files.

🤖 Generated with Claude Code

…_paths

Adds a shared, extensible way to treat N partition files (weekly/monthly
chunks submitted in parallel) as one logical simulation, so extension
authors get it for free without reimplementing the combining logic
themselves -- while formats that combine through a fundamentally
different mechanism can still override it.

- interfaces.py: BaseSimulation.from_paths, a non-abstract classmethod.
  Default: one cls(path, **kwargs) per file, combined via the new
  MultiFileSimulation (simulations/multi_file.py) using the existing
  combine_overlapping_frames primitive.
- PlexosDuckDBSimulation.from_paths overrides trivially -- its
  PlexosDuckDBSource already combines multiple files via SQL ATTACH, no
  need to route through the per-file-object model.
- SiennaSimulation.from_paths overrides to read the across-file merge
  direction from the already-existing per-model SiennaModelConfig.merge
  field (previously only used for within-file block dedup), instead of
  a single hardcoded default -- a UC/decision model and an emulation
  model can legitimately want different behavior at a partition seam.
- generic_aggregator.SimulationAggregator: additive parser_kwargs param
  only, backward compatible, future-proofs it for parser classes that
  need more than a bare path.
- simulations/utils.py: dedup_slices no longer silently drops a block
  when two partitions share an identical start timestamp (was keyed by
  a plain start-time dict); combine_overlapping_frames gains
  self-documenting "earlier_wins"/"later_wins" aliases for the
  confusing "right"/"left" naming (kept for backward compatibility).
  New tests pin gap handling, differing columns, and timezone-mismatch
  behavior.
- server/ingest.py: fixes two real, previously-undetected bugs found
  while wiring this up -- the Sienna branch silently used only the
  first of multiple simulation_paths (now uses from_paths); the PLEXOS
  branch imported a module that has never existed
  (gat.simulations.plexos), so it has always raised ImportError (now
  uses the duckdb backend, which needs no separate system file).

tests/handlers/test_sienna_multifile.py and
tests/simulations/test_combine_frames_aggregator.py pass unchanged,
proving the legacy SimulationAggregator/scenariohandlers path is
untouched. Full suite: 257 passed, 57 skipped, 0 failed (excluding
test_plexos_regression.py, pre-existing-broken on this machine due to
a local fixture mismatch, unrelated to this change).
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.

1 participant