Skip to content

fix(storage): preserve the EIA-930 fuel mix in the degenerate write path - #30

Merged
oldhero5 merged 1 commit into
mainfrom
marty/ecstatic-tu-8a2826
Jul 17, 2026
Merged

fix(storage): preserve the EIA-930 fuel mix in the degenerate write path#30
oldhero5 merged 1 commit into
mainfrom
marty/ecstatic-tu-8a2826

Conversation

@oldhero5

Copy link
Copy Markdown
Owner

Summary

power.generation_by_fuel was silently losing ~90% of its data on every hourly ingest: _canonicalize and write_bars in src/energex/core/storage.py deduplicated on the valid_time index alone, while the EIA-930 generation-by-fuel connector emits ~10 rows per (BA, hour) distinguished only by the fuel_type column (schema POWER_GEN_BY_FUEL requires uniqueness over [instrument_id, valid_time, fuel_type]). Only one arbitrary fuel per hour survived.

Fix

  • New _dedup_key() helper: the Datetime index, extended to a (Datetime, fuel_type) MultiIndex when a fuel_type column is present.
  • _canonicalize and both write_bars paths (new-row check, interior-insert dedup) key on it.
  • Stable sort_index: ArcticDB validate_index=True only requires a sorted index — duplicate timestamps across fuels are fine.
  • Frames without fuel_type behave exactly as before; keep="last"/append-only semantics unchanged.

Tests

New tests/test_write_bars_fuel_mix.py (written first, all 6 failed on the old code): multi-fuel roundtrip, idempotent rewrite, interior sparse insert, append-after-tail, new fuel at an existing hour, canonicalize-level dedup. Full suite: 268 passed (test_write_bars_sparse.py and test_storage_roundtrip.py untouched and green).

End-to-end verification

Materialized the real eia930_generation_by_fuel Dagster asset (live EIA API → quality gate → write_bars) into a scratch lmdb Arctic store: 72 BA symbols, 33,386 rows, 15 distinct fuel types; CISO stores 9 fuels/hour, ERCO/MISO 8 (previously 1). Re-materializing the same partition changed nothing (idempotent), no duplicate (hour, fuel) pairs, all indexes sorted.

Follow-up

  • Backfill: hours ingested before this fix hold one arbitrary fuel each. EIA-930 retains history, so re-materializing eia930_generation_by_fuel over the affected window recovers the full mix; re-ingest adds missing fuels without touching stored rows.
  • _observed_fuel_types on feat/entity-graph needs no change — it heals automatically once the store carries the mix. The doc caveat in website/docs/entity-graph.md (that branch) has been updated separately.

🤖 Generated with Claude Code

… fuel-mix data loss

The EIA-930 generation-by-fuel connector emits ~10 rows per (BA, hour)
distinguished only by the fuel_type column, but _canonicalize and
write_bars deduplicated on the valid_time index alone, so a single
arbitrary fuel per hour survived in power.generation_by_fuel on every
hourly ingest.

Dedup now keys on a (Datetime, fuel_type) MultiIndex whenever a
fuel_type column is present (_dedup_key), in _canonicalize and in both
write_bars paths (new-row check and interior-insert). Sorting is a
stable sort_index: ArcticDB validate_index=True only requires a sorted
index, so duplicate timestamps across fuels are fine. Frames without
fuel_type behave exactly as before; keep="last" semantics unchanged.

Verified end-to-end by materializing eia930_generation_by_fuel against
a scratch lmdb store: 72 BAs / 33,386 rows / 15 fuel types stored,
8-9 fuels per hour for CISO/ERCO/MISO (previously 1), re-pull idempotent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@oldhero5
oldhero5 merged commit c908882 into main Jul 17, 2026
10 checks passed
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