Repository navigation
feat(reporting): add TimeWindowEvent for fixed-duration windows per container - #110
Merged
Merged
Conversation
…ows per container Introduce `TimeWindowEvent`, which splits each measurement container into consecutive fixed-duration windows (one event instance per slice), complementing the existing `ContainerEvent` (one instance per container). - Add `TumblingWindowsExpression` in the query engine to derive window boundaries from `container_metrics.start_ts` / `stop_ts` with no channel selectors; final window is clamped to `stop_ts`. - Add `TimeWindowEvent` reporting class wired into `EventType`, producing `event_instance_fact` rows and `event_dimension` metadata with `window_length` surfaced in attributes. - Update event reference docs with `TimeWindowEvent` usage, parameters, and comparison table. - Add unit tests for `TumblingWindowsExpression` and `TimeWindowEvent`, plus integration tests verifying end-to-end report behavior and aggregation joins.
…TumblingWindowsExpression Replace the locally-defined `_START_TS_COL` / `_STOP_TS_COL` literals in `TumblingWindowsExpression` with `SolverConfig.start_ts_col` / `stop_ts_col` from a default `SolverConfig` instance. This keeps the internal container-metric timestamp column names consistent with the rest of the query engine and avoids duplicating config-invariant literals.
…wExpression Rename the query-engine expression class and module from `TumblingWindowsExpression` to `TimeWindowExpression` to align with the reporting `TimeWindowEvent` naming. Update all imports, exports, docstrings, and tests accordingly.
Generate the `time_window_event` API reference page and register it in the pydoc loader and API sidebar. Update the events skill README and SKILL.md to include `TimeWindowEvent` alongside the other event types.
…oat for stable hashes Store `window_length` as a float in `TimeWindowExpression` so the string representation (and downstream event definition hash) is identical whether an int or float is passed. This prevents spurious full recomputes in incremental mode when the same window is described as `10` vs `10.0`. Remove the now-unreachable `window_count <= 0` guard and add unit tests covering hash stability across int/float window lengths.
…ner_metrics Introduce `ContainerBoundaryEvent` as a shared base for `ContainerEvent` and `TimeWindowEvent`, routing both through the solver's filter pipeline instead of the centralized channel solve. `TimeWindowEvent` now materializes windows natively in Spark from `container_metrics.start_ts` / `stop_ts` via `window_intervals_col`, so every filtered container gets windows regardless of channel coverage. Keep the query-engine `TimeWindowExpression` bit-identical to the new Spark helper by casting boundaries to double before computing window counts and clamping. Normalize `TimeWindowEvent.window_length` through the expression so int and float inputs produce identical attributes and hashes. Update docs, skills, and tests to reflect that `TimeWindowEvent` no longer requires a co-solved aggregation and that scoped aggregations still match by `event_instance_id` even with rounded double boundaries.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #110 +/- ##
==========================================
+ Coverage 89.82% 90.13% +0.31%
==========================================
Files 62 66 +4
Lines 5770 5982 +212
Branches 726 750 +24
==========================================
+ Hits 5183 5392 +209
- Misses 464 468 +4
+ Partials 123 122 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…d drop redundant interval guard Validate that `TimeWindowEvent` and `TimeWindowExpression` require a finite, strictly positive `window_length`, rejecting `inf`, `-inf`, and `nan` to prevent bogus or crashing Spark window generation. Remove the now-unnecessary `start_ts < end_ts` filter from `TimeWindowEvent.determine_events` since finite positive windows guarantee valid intervals. Rename `container_event_cls` to `boundary_event_cls` in `dispatch_events` for clarity. Update docs, skills, and tests accordingly.
… via solver_config.epoch_unit Add an opt-in `solver_config.epoch_unit` setting (`"s"`, `"ms"`, `"us"`, `"ns"`) that converts `TIMESTAMP`-typed `container_metrics.start_ts` / `stop_ts` into epoch numbers. This lets `ContainerEvent` and `TimeWindowEvent` share the same time base as the channel sample timestamps, which is required for `TimeWindowEvent` when boundaries are `TIMESTAMP` columns. - Implement `SolverConfig.normalize_container_boundaries` and `require_epoch_boundaries` for conversion and fail-fast validation. - Apply normalization in `ContainerBoundaryEvent` event resolution and in the default solver's container metadata path. - Raise a clear `TypeError` in `TimeWindowExpression` when unconverted datetime boundaries reach pandas. - Add unit and integration tests covering all epoch units, timezone independence, TIMESTAMP_NTZ/DATE rejection, and `TimeWindowEvent` aggregation joins with TIMESTAMP boundaries. - Update configuration, schema, API, and event reference docs.
…on, include epoch_unit in definition hashes, and add per-container window limit Change `TimeWindowEvent` `event_instance_id` generation to hash the window's position (`container_id::event_name::window_index`) instead of its boundaries, so event facts and scoped aggregations join reliably despite double rounding of epoch timestamps. Propagate `solver_config.epoch_unit` into `ContainerBoundaryEvent` subclasses and fold it into the definition hashes of `ContainerEvent`, `TimeWindowEvent`, and aggregations scoped to a `TimeWindowEvent`, forcing a full recompute when the unit changes in incremental mode. Add `max_windows_per_container` (default 1,000,000) to `TimeWindowEvent` and `TimeWindowExpression` to fail fast when `window_length` is in the wrong unit for the boundaries, and reject non-finite container boundaries by yielding no windows. Update docs, skills, and tests.
…owEvent and clarify epoch_unit semantics Extend `TimeWindowEvent` integration tests to cover `data_type=RAW` with both `Rle` and `Interval` raw encoders, including TIMESTAMP container boundaries converted via `epoch_unit="us"`. Clarify in docs and code that `epoch_unit` describes the existing unit of channel sample timestamps (`tstart`/`tend` or `timestamp` for RAW data) and that channel timestamps are never converted themselves.
…er_container and strengthen multi-event tests Expose `validate_max_windows` from `TimeWindowExpression` with a configurable parameter name so `TimeWindowEvent` can validate `max_windows_per_container` with an error message that names the caller's parameter. Harden the `test_multiple_time_window_events_coexist` integration test to verify that two events with different window lengths each tile every container, that scoped stats join only to their own event's windows, and that stats values match the underlying samples. Reuse the tiling assertion across other TimeWindowEvent tests.
…oundaryEvent Move `get_id`, `as_spark_row`, and `determine_metadata_df` from `ContainerEvent` and `TimeWindowEvent` into their common base class `ContainerBoundaryEvent` to eliminate duplication. Remove redundant `window_length` and `Intervals` validation from `TimeWindowEvent` now handled by `TimeWindowExpression`. Update API docs to reflect the removed methods on subclasses.
…ope boundary helper Refactor `test_time_window_event_in_report` to reuse the shared `_assert_windows_for_all_containers` helper. Update `_container_boundaries` to filter on the report's `vehicle_key` scope so boundary expectations match the containers actually processed. Strengthen `_assert_windows_tile_containers` to compute exact expected windows using the same double arithmetic as the event, covering edge cases like zero-span containers and fractional windows, and drop the `itertools.pairwise` dependency. Remove a redundant `event_instance_id` subset check in the aggregation coverage test.
…r_metrics start_ts/stop_ts Clarify across configuration docs, API reference, skills, and docstrings that `solver_config.epoch_unit` is the epoch unit of the `channels` table timestamps (which are never converted) and that only `TIMESTAMP`-typed `container_metrics.start_ts`/`stop_ts` are converted to epoch numbers. Update the impulse-config skill example to use `start_ts`/`stop_ts` column names and add the `epoch_unit` description. Tighten the TimeWindowExpression error message to name the converted columns explicitly.
…nit/channel_time_origin for TimeWindowEvent Rename `solver_config.epoch_unit` to `channel_time_unit` and add `channel_time_origin` (`"epoch"` default, `"container_start"`). The new settings describe the time frame of channel sample timestamps and are used only to compute `TimeWindowEvent` windows from `container_metrics.start_ts`/`stop_ts`; the raw boundary columns are no longer converted in place and remain visible unchanged to `ContainerEvent`, `measurement_dimension`, and UDFs. - Add `SolverConfig.with_window_bounds` to derive prefixed `__window_start`/`__window_stop` columns in the channel time frame, supporting both absolute epoch and container-start-relative origins. - Remove boundary conversion from `ContainerBoundaryEvent` and `ContainerEvent`; drop `epoch_unit` from their definition hashes. - Update `TimeWindowExpression` and `TimeWindowEvent` to read the derived window-bound columns and include the channel time frame in definition hashes. - Update configuration docs, API references, skills, and tests.
…for numeric boundaries Add `solver_config.container_time_unit` to convert numeric `container_metrics.start_ts`/`stop_ts` into the channel time unit used by `TimeWindowEvent` windows. This supports cases like epoch-ms boundaries with µs channel samples. The setting requires `channel_time_unit`, is rejected for `TIMESTAMP` boundaries, and is included in `TimeWindowEvent` definition hashes. Update docs, API references, skills, and tests.
…on in a single pandas UDF-backed function Replace the native-Spark `window_intervals_col` with a scalar pandas UDF (`window_intervals_udf`) that delegates to a shared `tile_windows` implementation. This ensures the reporting event fact and the solve-side `TimeWindowExpression` produce identical windows from the same `container_metrics` bounds, keeping `event_instance_id` values consistent. Update docstrings, API references, and tests to reflect the new UDF and shared tiling logic.
… by window boundaries Revert `TimeWindowEvent` `event_instance_id` generation from the window-index hash back to the standard interval-event hash (`container_id::event_name::start_ts::end_ts`). Since the reporting event fact and the solve-side `TimeWindowExpression` now share the same `tile_windows` implementation and read the same Spark-computed window-bound columns, they produce identical windows and matching ids without relying on positional indexing. - Remove the `TimeWindowEvent` special case from `generate_event_instance_id_column` and drop the `window_index_col` parameter. - Stop emitting `interval_index` in `StatsAggregator` and remove the `posexplode`/`window_index` column from `TimeWindowEvent.determine_events`. - Update docstrings, API references, skills, and tests to describe boundary-based ids and remove references to window position/index hashing. - Strengthen `TimeWindowEvent` unit and integration tests to verify stats values join to the correct windows under the new id scheme.
…lverConfig to a standalone utility Move `SolverConfig.with_window_bounds` into a new `solvers.utils.window_bounds` module as a standalone function that takes a `SolverConfig` argument. This decouples the window-bound computation from the configuration model and makes it easier to share between the query engine solve path and the reporting event fact. Update all call sites, docstrings, API references, and tests to reference the new location.
…s to ContainerBoundaryEvent Hoist `as_dict`, `required_channels`, and shared SHA-256 hashing/normalization helpers into `ContainerBoundaryEvent` to remove duplication between `ContainerEvent` and `TimeWindowEvent`. Subclasses now only customize `description`, `attributes`, and `required_channels` initialization. Update API reference docs to drop the subclass `as_dict` entries that are now inherited.
…s references in TimeWindowEvent docs Update configuration docs, event reference, and skills to consistently refer to `container_metrics.start_ts`/`stop_ts` when describing the source of `TimeWindowEvent` container boundaries and the columns that remain unchanged for `ContainerEvent`, `measurement_dimension`, and UDFs.
…vent int overflow When converting integer container boundaries from a coarser unit to a finer channel unit (e.g. epoch seconds to ms), multiply by a long-typed factor so Spark widens the result instead of keeping int*int as int. This avoids ARITHMETIC_OVERFLOW under ANSI mode and silent wrapping otherwise. Doubles and decimals retain their original types. Add unit tests covering int overflow behavior and double fraction preservation.
…nal window lengths Add a note to the TimeWindowEvent docs explaining that when `window_length` is not a whole number in the channel unit or not a multiple of 256 ns for nanosecond epochs, rounding can produce a final window only a few ulps long.
Replace hard-coded timestamps in window_bounds tests with the shared basic_narrow_db fixture's container_metrics boundaries. This makes the tests data-driven and consistent with the rest of the suite while still covering TIMESTAMP, numeric, and null boundary cases.
…w_intervals_udf Change the pandas UDF return type from `array<array<double>>` to `struct<starts: array<double>, ends: array<double>>` so each row reliably carries one array per column, even when all rows have the same window count. Update `TimeWindowEvent.determine_events` to zip the struct fields and adjust unit tests accordingly.
…sage When `with_window_bounds` cannot find required `container_metrics` columns, include the unmapped physical column name in the error and point users to `solver_config.container_metrics.column_name_mapping`. Add a unit test verifying the message for an unmapped `stop_ts` physical name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
TimeWindowEvent, which splits each container into consecutive fixed-length windows (the last one clamped to the container end) based oncontainer_metrics.start_ts/stop_ts. It needs no expression. An aggregation scoped to the event, e.g.StatsAggregator(..., event=...), yields one result per window.Changes
TimeWindowEvent(name, window_length, max_windows_per_container=1_000_000)with the event typeTIME_WINDOW_EVENT.ContainerBoundaryEventbase withContainerEvent.TimeWindowExpressionevaluates toIntervals.tile_windowsis the single window implementation, used by both the solve and the event fact (window_intervals_udf). The two sides therefore produce identical windows, and theevent_instance_ids, which hash the window boundaries, match.solver_configfields:channel_time_unit(s/ms/us/ns),channel_time_origin(epoch/container_start) andcontainer_time_unit.solvers/utils/window_bounds.with_window_boundsconverts the container boundaries into the channels' time frame for the windows only. The rawstart_ts/stop_tsstay unchanged for UDFs,ContainerEventandmeasurement_dimension.window_lengthand the channel time frame, so changing either triggers a full recompute.channel_time_unitare rejected. Containers with null, NaN or infinite boundaries get no windows.Impact
TimeWindowEvent.measurement_dimension.config_hashchanges for reports that set asolver_config, because of the new fields. The value is written but never compared.Testing
event_instance_idacross 7 time bases, including ns epochs beyond 2^53, TIMESTAMP boundaries, relative seconds and RAW data.Follow-ups
#113 event identity validation · #114 report-level validation step · #117 StatsAggregator O(windows × samples) · #118 mapInArrow for the event fact