Skip to content

Large-session hardening: stream week-long sessions + new analysis features (v0.5.0) - #1

Merged
GrumpyTanker merged 12 commits into
mainfrom
feat/large-session-hardening
Jun 3, 2026
Merged

Large-session hardening: stream week-long sessions + new analysis features (v0.5.0)#1
GrumpyTanker merged 12 commits into
mainfrom
feat/large-session-hardening

Conversation

@GrumpyTanker

Copy link
Copy Markdown
Owner

Large-session hardening + new analysis features (v0.5.0)

Motivated by a real ~6.8-day, 438 MB, 589,877-record session that the previous design could not process (it OOM'd partway — the pipeline materialized every record in memory multiple times). This PR re-architects parsing/analysis to stream, and adds the analysis features needed to make sense of multi-day captures.

Core hardening

  • store.ColumnStore — single streaming pass into ~21 array.array('f') columns + FILETIME ticks, with optional lazy time_shift. Peak memory on the full week is ~59 MB (was >1 GB).
  • Refactored events, snapshots, insights to consume the store — removes the repeated list(iter_records()) materializations and per-rule column copies.
  • snapshots rolling-stdev is now O(N) prefix-sum (parity-tested against the old O(N×window) implementation).
  • parser.export_csv_multi — one pass writes the full + 1-min CSVs and builds the store; --max-csv-rows guard auto-raises the stride and logs the downsample (no silent cap).
  • iter_records_safe — tolerant parser: resyncs on bad magic, handles truncated tails, flags non-finite floats (real device dumps contain these).

New CLI

  • --anchor-start / --anchor-end — clock correction for loggers with a wrong RTC (shifts the whole timeline to a known real start/end).
  • --split-by hour|day|week|<duration> — partitions the single analysis into time buckets; each bucket gets a full report, plus a roll-up.
  • --mark / --marks — overlay known external events (e.g. an equipment trip) and correlate them to detected events.
  • --tod-profile [HH:MM-HH:MM] — time-of-day (diurnal) avg/min/max profile with per-day overlay.
  • --no-stats toggle.

New analysis (analysis.py)

  • Whole-session statistics (streaming Welford + histogram percentiles + threshold-time).
  • ITIC / CBEMA ride-through classification for every dip/outage/swell.
  • Per-bucket summary table (buckets_summary.csv).
  • Marker correlation + time-of-day profile primitives.

Reporting / web

  • XLSX gains Statistics and Time-of-Day sheets (gnuplot-free).
  • Web app: min/max chart decimation for large sessions with a "decimated" notice; CSV export keeps full resolution.

Docs

  • Version → 0.5.0; CHANGELOG, ROADMAP (shipped + deferred web items), README CLI table, new docs/ITIC.md.

Tests

  • 258 passing176 Python (was 109), 82 JS (was 76). Built incrementally test-first; spec/field_map.json unchanged so the cross-language parity test still holds.

Deferred to ROADMAP

Web typed-array record storage, chunked/streamed parse, and IndexedDB large-session verification.

🤖 Generated with Claude Code

Bill Bai and others added 12 commits June 2, 2026 22:04
One streaming pass builds ~20 array.array('f') columns + FILETIME ticks
instead of materializing 590K Record objects. Optional time_shift corrects
wrong-RTC sessions lazily without losing raw ticks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces O(N*window) rolling pstdev (177M ops for a week) with prefix
sums of P and P^2. pick_snapshots now accepts a ColumnStore or Records.
Parity test confirms identical results to the old algorithm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
detect_events accepts a ColumnStore or Records; columns are array.array
views instead of 15 full-length parallel list comprehensions over a
materialized list(records).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
All seven rules read array.array columns from the store; the N-entry
time->index dict and records[-1]/[0] duration calc now come from the
store. analyze() accepts a ColumnStore or Records.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
export_csv_multi walks trend.bin once, fanning records to full CSV,
1-min CSV, and the ColumnStore (full resolution). --max-csv-rows guard
raises the full-CSV stride and logs exactly what was downsampled.
iter_records_safe skips/logs bad magic (resync), truncated tails, and
flags non-finite floats instead of aborting. time_shift + reverse_cts
applied consistently to CSV and store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CLI now parses trend.bin once via export_csv_multi, threading the
ColumnStore through detect/snapshots/insights (no more list(iter_records)
double materialization). Adds --anchor-start/--anchor-end clock
correction (mutually exclusive), --max-csv-rows guard, and registers
--split-by/--mark/--marks/--tod-profile/--no-stats flags. Window filter
operates on the store. _run_extra_analyses hook stubbed for round-2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New analysis.py with tested pure functions: whole_session_stats
(streaming Welford mean/var + histogram percentiles), classify_itic /
event_itic (ITIC/CBEMA ride-through), parse_period/bucket_key/
assign_buckets/slice_store (--split-by partitioning), correlate_markers
(--mark/--marks nearest-event), time_of_day_profile (diurnal envelope),
bucket_summary_row (per-bucket table). 31 new tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_run_extra_analyses now emits stats.json/csv, markers.json (--mark/
--marks), time_of_day_profile.csv (--tod-profile), augments events.json
with ITIC, and on --split-by writes a full per-bucket report (CSV +
events + summary + xlsx) plus buckets_summary.csv roll-up. Per-bucket
events filed under t_start bucket; boundary-spanners flagged. 8 e2e tests
incl. union-of-events == whole-session parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
write_xlsx accepts stats + tod_rows and adds a Statistics sheet
(per-channel percentiles + threshold time) and a Time of Day sheet with
an avg/min/max line chart. CLI threads both into the top-level report so
the diurnal profile and whole-session stats are visible without gnuplot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
uPlot chokes well before a week of per-second points; decimateSeries
buckets the filtered series to ~plot-width and emits per-bucket min+max
so dips/spikes/outages survive. CSV download keeps full resolution. A
'large session — chart decimated' notice appears on affected charts.
6 node tests. Typed-array record storage + chunked parse deferred to
ROADMAP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump pyproject to 0.5.0. CHANGELOG entry for large-session hardening +
round-2 features (257 tests). README CLI table documents the new flags.
New docs/ITIC.md explains the dip classification. ROADMAP records the
shipped work and the deferred web typed-array/chunked-parse items.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real ES.004 data has a single NaN in P_total_avg_W (record 417073) that
poisoned one day's kWh roll-up (nan). bucket_summary_row now skips
non-finite V/I/P/PF samples in min/avg/max/kWh. Regression test added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@GrumpyTanker
GrumpyTanker merged commit f32356f into main Jun 3, 2026
2 checks passed
@GrumpyTanker
GrumpyTanker deleted the feat/large-session-hardening branch June 3, 2026 04:09
@GrumpyTanker
GrumpyTanker restored the feat/large-session-hardening branch June 10, 2026 15:23
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