terrine 0.1.0.dev1__tar.gz → 0.1.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/CHANGELOG.md +159 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/PKG-INFO +43 -23
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/README.md +42 -22
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/run_overhead.py +12 -12
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/workloads.py +8 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/backend-compatibility.md +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/candidate-anchors.md +5 -4
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/common-feature-patterns.md +25 -40
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/common-stack-interop.md +6 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/dataset-agent-tools.md +6 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/design-specs.md +8 -4
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/design-validation.md +58 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/first-dataset.md +28 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/increment-compatibility.md +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/index.md +6 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/library-handoffs.md +128 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/limitations.md +2 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/materialization-runtime.md +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/ml-staging.md +3 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/public-api.md +56 -17
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/semantic-layer-interop.md +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/sequential-panels.md +23 -19
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/state-panels.md +15 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/temporal-attributes.md +8 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/temporal-engine.md +232 -91
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/trajectories-survival.md +22 -22
- terrine-0.1.0.dev2/docs/reference/refusals.md +576 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/evals/agent_dataset_tools.yaml +2 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/bigquery/dataset.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/common_stack_interop.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/dataset_agent_tools.py +8 -11
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/event_sequences.py +5 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/increment_parity.py +14 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/materialization_runtime.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/ml_staging.py +8 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/nonadditive_metrics.py +13 -13
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/postgres/dataset.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/retention_and_winsorization.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/retention_metrics.py +5 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/sequential_trials.py +11 -13
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/snowflake/dataset.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/state_panel.py +12 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/table_first_dataset.py +19 -18
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/trajectories_survival.py +18 -19
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/unverified_backend.py +5 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/.test_durations +352 -255
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/agent/test_dataset_tool_runtime.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/backends/test_generic.py +19 -19
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compat/test_native_catalog.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_boolean_outputs.py +8 -8
- terrine-0.1.0.dev2/integration/compile/test_design_describe.py +299 -0
- terrine-0.1.0.dev2/integration/compile/test_frame_runtime_window_export.py +350 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_retention.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_time_to_event.py +4 -4
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/conformance/test_engines.py +16 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/execution/test_generic_provider.py +4 -4
- terrine-0.1.0.dev2/integration/handoffs/test_causal_grid.py +208 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/state_panels/test_state_panel_workflows.py +14 -14
- terrine-0.1.0.dev2/integration/test_model_from_tables.py +123 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_backend_capabilities.py +10 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_bigquery_admission.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_incremental_equivalence.py +31 -31
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_ml_staging_duckdb.py +94 -23
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_admission.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_execution.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_shapes.py +15 -15
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_windows.py +21 -21
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_api.py +15 -15
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_saved_plan_restart.py +16 -16
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snapshot_capture_bounds.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snapshot_partition_consistency.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snowflake_admission.py +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snowflake_parity.py +13 -13
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/pyproject.toml +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tach.toml +7 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/__init__.py +18 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/adapters/increment.py +22 -18
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/adapters/opentelemetry.py +9 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/definitions.py +12 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/design_schema.py +37 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/policy.py +35 -13
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/schemas.py +5 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/tools.py +30 -17
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/dialect.py +18 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/duckdb.py +88 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/numeric.py +14 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/timestamps.py +11 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/_asof.py +7 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/anchors.py +53 -34
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/columns.py +117 -34
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/cost.py +11 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/dispatch.py +2 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/layers.py +11 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/materialize.py +2 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/provenance.py +12 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/ranges.py +5 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/retention.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/_observations.py +7 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/calendar_comparison.py +20 -19
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/cohorts.py +4 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/event_sequence.py +8 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/ltv.py +4 -4
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/sequential.py +31 -33
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/state_panel.py +12 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/time_to_event.py +9 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/data.py +334 -74
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/export.py +10 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/manifest.py +43 -38
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/reference.py +32 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/splits.py +115 -89
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/errors.py +30 -4
- terrine-0.1.0.dev2/terrine/execution/gates.py +340 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/inputs.py +2 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/diff_diff.py +9 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/lifelines.py +17 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/preflight.py +26 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/surrogate.py +22 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/frame.py +171 -57
- terrine-0.1.0.dev2/terrine/metrics.py +154 -0
- terrine-0.1.0.dev2/terrine/model.py +746 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/orchestration/airflow.py +26 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/orchestration/dagster.py +29 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/recipes.py +105 -48
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/api.py +46 -26
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/backends/duckdb.py +3 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/control.py +7 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/graph.py +5 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/leases.py +9 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/planner.py +14 -6
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/planning.py +19 -15
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/saved_plan.py +18 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/snapshot.py +19 -12
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/state.py +2 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/bind.py +53 -50
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/io.py +31 -4
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/models.py +19 -19
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/rules.py +14 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/sql.py +2 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/baseline.yaml +11 -11
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/scale.py +25 -8
- terrine-0.1.0.dev2/terrine/spec/anchors.py +249 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/dataset.py +54 -13
- terrine-0.1.0.dev2/terrine/spec/describe.py +735 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/design.py +80 -41
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/expressions.py +161 -89
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/io.py +86 -56
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/panels.py +149 -68
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/sequence.py +26 -21
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/state_panel.py +100 -48
- terrine-0.1.0.dev2/terrine/spec/trajectory.py +174 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/transforms.py +5 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/validate.py +346 -129
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/aggregate.py +164 -72
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/attributes.py +36 -32
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/daily.py +34 -43
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/first.py +3 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/lookup.py +15 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/nonadditive.py +22 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/prefix.py +26 -20
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/readiness.py +8 -2
- terrine-0.1.0.dev2/terrine/windows.py +84 -0
- terrine-0.1.0.dev2/tests/.test_durations +2033 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/_worlds.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/adapters/test_airflow.py +24 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/adapters/test_dagster.py +27 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/fixtures/catalog.yaml +7 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_inspect_design.py +57 -17
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_materialize.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_plan_authorization.py +3 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_policy.py +56 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_schemas.py +8 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_numeric.py +53 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_timestamps.py +30 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/catalog.yaml +23 -23
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/test_increment_semantics.py +9 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_anchors.py +55 -24
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_columns.py +87 -20
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_completeness_columns.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_event_sequence.py +18 -18
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_ltv.py +11 -11
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_matched_t0.py +10 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_nonadditive.py +24 -24
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_panels.py +76 -40
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_persisted_layers.py +8 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_range_scaling.py +11 -10
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_retention.py +14 -11
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_state_panel.py +45 -25
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_time_to_event.py +14 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/conftest.py +14 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_definition.py +21 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_manifest.py +71 -3
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_runtime_staging.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_splits.py +30 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_staging.py +18 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_training_view.py +57 -14
- terrine-0.1.0.dev2/tests/execution/test_gates.py +392 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_inputs.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_preflight.py +14 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_surrogate.py +25 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_warehouse_preflight.py +19 -0
- terrine-0.1.0.dev2/tests/fixtures/definition_v2.json +1 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/interop.py +9 -9
- terrine-0.1.0.dev2/tests/fixtures/readme.py +73 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/winsorization.py +14 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/study_api_smoke.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/parity/test_increment_frames.py +14 -14
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/test_event_sequences.py +5 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_api.py +8 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_backend_capabilities.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_control_ipc.py +3 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_graph.py +1 -1
- terrine-0.1.0.dev2/tests/runtime/test_input_refusals.py +194 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_layers.py +44 -11
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_planning.py +1 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_saved_plan.py +9 -9
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_snapshot.py +10 -10
- terrine-0.1.0.dev2/tests/semantic/test_catalog.py +280 -0
- terrine-0.1.0.dev2/tests/semantic/test_from_tables.py +686 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/test_rules_import.py +2 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/simulate/test_scale_generator.py +44 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_calendar_comparison.py +24 -19
- terrine-0.1.0.dev2/tests/spec/test_describe.py +358 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_design_io.py +148 -80
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_expressions.py +0 -13
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_nonadditive_spec.py +6 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_panels.py +18 -26
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_recipes.py +213 -40
- terrine-0.1.0.dev2/tests/spec/test_refusals.py +477 -0
- terrine-0.1.0.dev2/tests/spec/test_retention.py +51 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_sequence.py +4 -1
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_state_panel.py +22 -15
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_validate.py +198 -76
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_nonadditive.py +4 -4
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_prefix_lookup.py +80 -5
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_readiness.py +5 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_scd2_attributes.py +15 -7
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_semantic_binding.py +45 -45
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sparse_state.py +10 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_window_aggregates.py +128 -8
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_errors.py +0 -22
- terrine-0.1.0.dev2/tests/test_frame.py +211 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_frame_runtime.py +27 -2
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_metadata.py +13 -9
- terrine-0.1.0.dev2/tests/test_public_docstrings.py +41 -0
- terrine-0.1.0.dev2/tests/test_windows.py +39 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/uv.lock +1 -1
- terrine-0.1.0.dev1/docs/reference/refusals.md +0 -500
- terrine-0.1.0.dev1/integration/compile/test_frame_runtime_window_export.py +0 -34
- terrine-0.1.0.dev1/integration/test_model_from_tables.py +0 -177
- terrine-0.1.0.dev1/terrine/execution/gates.py +0 -90
- terrine-0.1.0.dev1/terrine/model.py +0 -468
- terrine-0.1.0.dev1/terrine/spec/anchors.py +0 -122
- terrine-0.1.0.dev1/terrine/spec/trajectory.py +0 -144
- terrine-0.1.0.dev1/terrine/windows.py +0 -54
- terrine-0.1.0.dev1/tests/.test_durations +0 -1226
- terrine-0.1.0.dev1/tests/semantic/test_catalog.py +0 -458
- terrine-0.1.0.dev1/tests/spec/test_retention.py +0 -27
- terrine-0.1.0.dev1/tests/test_frame.py +0 -74
- terrine-0.1.0.dev1/tests/test_windows.py +0 -28
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/CLA.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/ci.yml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/cla.yml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/release.yml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/scale.yml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.github/workflows/warehouse-backends.yml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.gitignore +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.kata.toml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.pre-commit-config.yaml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.python-version +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/.roborev.toml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/AGENTS.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/CLAUDE.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/CONTRIBUTING.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/LICENSE +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/Makefile +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/NOTICE +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/THIRD_PARTY_NOTICES.txt +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/README.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/report.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/results/duckdb-scale-v2-small.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/run_duckdb.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/run_live.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/benchmarks/workloads/small.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/capabilities.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/cost-and-residency.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/orchestrators.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/shared-dataset-contract.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/guides/synthetic-worlds.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/index.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/edge-case-designs.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/landscape.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/ohdsi-study-design.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/docs/research/point-in-time-feature-apis.md +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/batch_scoring.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/first_dataset.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/orchestrators/airflow_materialization.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/orchestrators/dagster_materialization.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/examples/orchestrators/dbt_core_after_build.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/agent/test_dataset_tool_example.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/agent/test_dataset_tool_flow.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/backends/test_engine_registry.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/backends/test_numeric.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_column_runtime_contracts.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/compile/test_ltv.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/conftest.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/conftest.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/test_diff_diff.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/test_library_handoffs.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/handoffs/test_lifelines.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/parity/test_increment_panel_parity.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/temporal/test_retention_bands.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/temporal/test_window_quotient_composition.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/test_independence.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/bigquery_fixtures.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/postgres_fixtures.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/snowflake_fixtures.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_bigquery_execution.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_bigquery_parity.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_duckdb_compiler_scale.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_duckdb_runtime_recovery.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_duckdb_scale.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_live_warehouse_scale.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_materialization_example.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_ml_staging_examples.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_orchestrator_adapters.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_planning.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_postgres_timestamps.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_request_replay_process.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_cli.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_control.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_runtime_cost_budget.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_snowflake_execution.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/integration/warehouse_execution/test_winsorization_definition.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/mkdocs.yml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/scripts/mutate.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/_time.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/adapters/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/agent/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/capabilities.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/bigquery.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/postgres.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/engines/snowflake.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/extrema.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/joins.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/registry.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/backends/sequences.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/_predicates.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/event_panel.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/life_table.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/compile/shapes/matched_t0.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/training.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/dataset/transforms.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_bigquery.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_generic.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_metadata.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_native.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_postgres.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_publish.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/_snowflake.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/execution/backend.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/export/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/orchestration/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/py.typed +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/arrow.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/backends/base.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/changes.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/cli.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/cost.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/events.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/execute.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/gc.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/layers.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/materialize.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/owner.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/status.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/runtime/synchronization.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/semantic/time.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/generate.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/simulate/world.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/spec/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/ranges.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/retention.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/sequence.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/sessionize.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/terrine/temporal/sketches.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/_timing.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/adapters/test_dbt_core_after_build.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/conftest.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/agent/test_requests.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_engine_registry.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_generic.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/backends/test_sequences.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/increment.yaml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/same-name-collision.yaml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/fixtures/unsupported-metric.yaml +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compat/test_semantic_interop.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_cohorts.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_cost_estimates.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_survival_bounds.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/compile/test_winsorization.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_label_support.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/dataset/test_winsorization_definition.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/documentation.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_backend_provider.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_generic.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_snowflake_session.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/execution/test_sql_verification.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/conftest.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_diff_diff.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/export/test_lifelines.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/baseline.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/materialized_frame_v1.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_definition.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_fitted_definition.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_fitted_transform.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_invocation.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_manifest.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_plan_request.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/pre_rename_saved_plan.json +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/fixtures/test_baseline.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/distribution_smoke.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/postgres_scratch.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/test_benchmark_failure_reporting.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/integration/test_live_scale_gate.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/parity/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/baseline.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/nonadditive.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/panels.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/ranges.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/retention.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/scale_metrics.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/test_baseline.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/reference/test_panels.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_arrow_output.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_batch_cleanup.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_cancel_publication.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_cli.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_control_recovery.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_cost_estimates.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_duckdb_publication.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_execute.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_gc.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_imports.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_invalidation.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_leases.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_opentelemetry.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_planner.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_recovery_lease.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_request_replay.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_run_events.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_run_status.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_snapshot_lifecycle.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/runtime/test_state.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/test_metric_rules.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/semantic/test_sql.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/simulate/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/simulate/test_world.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/spec/test_sketch_refusal.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/__init__.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_first_occurrence.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_range_scaling.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_retention.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sequence.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sessionize.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/temporal/test_sketches.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_common_stack_interop.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_distribution.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_documentation.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_engine_dispatch.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_mutation_runner.py +0 -0
- {terrine-0.1.0.dev1 → terrine-0.1.0.dev2}/tests/test_optional_dependencies.py +0 -0
|
@@ -1,5 +1,164 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0.dev2
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
|
|
7
|
+
- Run a build's integrity checks, eligibility summary and output in one
|
|
8
|
+
statement. Window checks are boolean flag columns of the study's audit
|
|
9
|
+
relation instead of one statement per check that recomputed the metric
|
|
10
|
+
from the source tables; the deferred checks form one schedule in
|
|
11
|
+
registration order, folded into gate statements, and the final run's
|
|
12
|
+
counts and the eligibility summary ride on the output statement as a
|
|
13
|
+
one-row cross join that is decoded before the first batch leaves. Refusal
|
|
14
|
+
codes, their context (`failed_count` included) and the first refusal for a
|
|
15
|
+
given dataset are unchanged; an empty output still refuses a failing check
|
|
16
|
+
on an excluded candidate, and `Budget.max_rows` and `max_output_bytes`
|
|
17
|
+
count output rows and bytes only. On the repro (DuckDB 1.5.6; 10,000
|
|
18
|
+
units, 300,000 events, ten snapshots, three metrics; Apple M3 Pro, 12
|
|
19
|
+
cores) a warm build went from 14.0 s and eight statements over the source
|
|
20
|
+
tables to 0.8 s and one. Designs with as-of attributes or event-history
|
|
21
|
+
checks keep one statement per such check between gate statements.
|
|
22
|
+
Composition for runtime staging runs the schedule once and each staging
|
|
23
|
+
task reads its output with the summary alone;
|
|
24
|
+
`preview(..., bounded_count=N)` still runs every check unrestricted and
|
|
25
|
+
caps only its eligibility read.
|
|
26
|
+
- Compare DuckDB lookup timestamps natively: a microsecond cutoff and state
|
|
27
|
+
are their own as-of keys, a naive side is attached to UTC explicitly so
|
|
28
|
+
the session `TimeZone` cannot shift it, and a nanosecond axis keeps the
|
|
29
|
+
structured order key. Hold counts in native integers there: `BIGINT` 0/1
|
|
30
|
+
terms accumulated in `HUGEINT` and narrowed by `TRY_CAST`, so a count
|
|
31
|
+
beyond `int64` refuses `terrine.temporal.exact_overflow` rather than
|
|
32
|
+
wrapping; integer, decimal and float sums keep their `BIGNUM`
|
|
33
|
+
coefficients. A window's state carries only the fields its measure reads,
|
|
34
|
+
and an unfiltered measure's state no longer holds the five constant
|
|
35
|
+
unknown-filter counters. PostgreSQL, BigQuery, Snowflake and the portable
|
|
36
|
+
policy keep their numeric and timestamp state; their compiled statements
|
|
37
|
+
change only in the gate shape, the pruned state columns and integer count
|
|
38
|
+
literals.
|
|
39
|
+
|
|
40
|
+
### Breaking changes
|
|
41
|
+
|
|
42
|
+
- Catalog documents are `schema_version: 2`. Sources use `time`, `units` and
|
|
43
|
+
`events`; the root event mapping is `events`; dimensions use `unit` and `key`,
|
|
44
|
+
measures use `event`, and metrics use `unit`. Version 1 catalogs refuse with
|
|
45
|
+
`terrine.catalog.invalid_definition` before other field diagnostics.
|
|
46
|
+
- Design and `DatasetDefinition` documents are version 2. Older saved designs,
|
|
47
|
+
definitions, and manifests or saved plans embedding them refuse; reconstruct
|
|
48
|
+
and save the design again. Manifest and saved-plan envelope versions stay 1.
|
|
49
|
+
- `Events(table, *, time, key, unit=None, metrics=None, labels=(), complete=None)`
|
|
50
|
+
replaces `keys=` and `values=` with one unit key and user-named metrics.
|
|
51
|
+
Generated metric names are removed; without `metrics=`, only the event is
|
|
52
|
+
declared. Labels, histories and events share a namespace, `__` is reserved
|
|
53
|
+
in labels, and `<source>_<column>` value-event names are reserved against
|
|
54
|
+
event, label and history declarations.
|
|
55
|
+
- `Model.from_tables` drops `readiness=` and takes completeness from each
|
|
56
|
+
`Events.complete` and `History.complete`; `con` is optional for in-memory
|
|
57
|
+
tables. `Model.from_catalog` retains `readiness=`.
|
|
58
|
+
- Event selectors and returned sequence metadata use `event`, not `fact`.
|
|
59
|
+
`Initiation` and `PersistentAdoption` take an event or label name without
|
|
60
|
+
`source`; its declaring source is resolved from the model.
|
|
61
|
+
- Calendar-date `Schedule.end` and `AnchorRange.end` are inclusive, so the same
|
|
62
|
+
literal bounds can produce additional anchors. Aware datetime ends remain
|
|
63
|
+
exact exclusive instants; relative windows and `TimeSplit` are unchanged.
|
|
64
|
+
- `Schedule.every` is `int | None`; calendar schedules set `months` instead.
|
|
65
|
+
Serialized schedules carry both fields with exactly one non-null value.
|
|
66
|
+
- Every recipe and `Design` can omit `unit` for a single-unit model.
|
|
67
|
+
`life_table(group_by={name: expression})` replaces `features=`.
|
|
68
|
+
`state_panel(periods=None)` derives whole periods from the schedule; an
|
|
69
|
+
explicit period count is a cap, not permission to truncate.
|
|
70
|
+
- Bound descriptors use `time_column`, `units`, `events`, `unit`,
|
|
71
|
+
`unit_columns`, `event` and `key`. Definition-hash payloads use the same
|
|
72
|
+
vocabulary, changing feature definition hashes and `Model.model_hash`.
|
|
73
|
+
- The refusal codes `terrine.model.generated_metric_collision` and
|
|
74
|
+
`terrine.model.multiple_event_units` are removed.
|
|
75
|
+
- Invalid constructor inputs now raise coded `TerrineError` (still a
|
|
76
|
+
`ValueError`) rather than bare `ValueError`, `TypeError`, `RuntimeError` or
|
|
77
|
+
`FileExistsError`. Context names the caller's parameter in `field` and uses
|
|
78
|
+
stable `reason` tokens; document decoders preserve a reconstructed record's
|
|
79
|
+
refusal. Pydantic records retain `ValidationError`. Agent tools expose the
|
|
80
|
+
same design and split refusal codes as direct callers.
|
|
81
|
+
- DuckDB output order may differ from anchor order for small studies; row
|
|
82
|
+
order is not a dataset contract. Sort explicitly when needed.
|
|
83
|
+
- New DuckDB runtime prefix layers store counts as Arrow `decimal128(38, 0)`
|
|
84
|
+
and cannot be read by older versions; older layers still restore and compose.
|
|
85
|
+
|
|
86
|
+
### Public API
|
|
87
|
+
|
|
88
|
+
- Declare metrics with `tr.count`, `tr.sum`, `tr.mean` (per event or
|
|
89
|
+
`per="day"`), `tr.min`, `tr.max`, `tr.count_distinct` and `tr.ratio`.
|
|
90
|
+
Their `where=` mappings accept declared labels, scalar equality, list
|
|
91
|
+
membership, and `__ne`, `__in`, `__not_in`, `__gt`, `__gte`, `__lt`,
|
|
92
|
+
`__lte` and `__between`; multiple keys are a conjunction.
|
|
93
|
+
- `Events(labels=...)` binds typed event-time properties for filters,
|
|
94
|
+
`event_time`, `asof` and treatment. `History` binds as-of attributes through
|
|
95
|
+
range validity (`valid_from`, optional `valid_to`) or a change log
|
|
96
|
+
(`changed_at`) in `Model.from_tables(history=...)`.
|
|
97
|
+
- `UnitSource`, `Events` and `History` accept pandas/Polars DataFrames and
|
|
98
|
+
Arrow tables. Without a connection, `Model.from_tables` registers them in a
|
|
99
|
+
private in-process DuckDB under their mapping names; nothing is uploaded.
|
|
100
|
+
These models materialize only. Durable staging, reopening and refresh need
|
|
101
|
+
a file-backed DuckDB; refusals provide that route.
|
|
102
|
+
DataFrame detection accepts classes exposed from the public package module
|
|
103
|
+
as well as submodules, including pandas 3.1.
|
|
104
|
+
- `Model.to_catalog()` writes equivalent version-2 YAML for reuse with
|
|
105
|
+
`Model.from_catalog`.
|
|
106
|
+
- Unit inference applies wherever a Design meets a Model, including validation,
|
|
107
|
+
preview, build, snapshot, planning, staging and agent tools. Multiple units
|
|
108
|
+
require an explicit choice; inferred and explicit units share resolved dataset
|
|
109
|
+
identity.
|
|
110
|
+
- `Schedule`, `time_series` and `trial_panel` accept `"1mo"`, `"3mo"` and
|
|
111
|
+
`"1y"` calendar steps. Anchors advance from the original start and clamp to
|
|
112
|
+
month-end without cumulative drift. `count`, `instant` and `instants` expose
|
|
113
|
+
this arithmetic. Windows and other elapsed fields still require seconds,
|
|
114
|
+
minutes, hours, days or weeks; state and calendar-comparison panels require
|
|
115
|
+
a fixed elapsed cadence.
|
|
116
|
+
Normalized calendar steps must fit in 19 decimal digits, so accepted year
|
|
117
|
+
steps remain readable from their serialized month representation.
|
|
118
|
+
- `Frame.to_pandas(values_only=True)` and `to_polars(values_only=True)` retain
|
|
119
|
+
identity, role and structural value columns while dropping completeness,
|
|
120
|
+
reason and bookkeeping columns. Incomplete values remain null.
|
|
121
|
+
`training_set(...).to_pandas(values_only=True)` yields the in-memory feature
|
|
122
|
+
and label table. Observation metadata cannot be requested with this flag;
|
|
123
|
+
Arrow conversions remain lossless.
|
|
124
|
+
- `Design.describe(model)` validates and summarizes rows, anchors, expressions,
|
|
125
|
+
shape, treatment, completeness and output columns without querying source
|
|
126
|
+
rows. Agent design validation/proposal responses include the same summary.
|
|
127
|
+
- Unknown-name refusals include sorted `available` declarations, including
|
|
128
|
+
`model.metrics` for unknown metrics. Refusal messages identify the user's
|
|
129
|
+
input and a route forward; temporal errors identify the output and offending
|
|
130
|
+
bounds. New codes are `terrine.model.ambiguous_unit`,
|
|
131
|
+
`terrine.dataset.invalid_split`, `terrine.budget.invalid_limit`,
|
|
132
|
+
`terrine.runtime.invalid_configuration`, `terrine.export.invalid_input` and
|
|
133
|
+
`terrine.simulate.invalid_config`.
|
|
134
|
+
- Public constructors, recipes and conversion methods include concise
|
|
135
|
+
docstrings ending in an executable one-line example.
|
|
136
|
+
- Warehouse exports retain scoring rows when there are no outcome windows.
|
|
137
|
+
|
|
138
|
+
### Documentation
|
|
139
|
+
|
|
140
|
+
- The README builds a customer spend forecasting table directly from DataFrames,
|
|
141
|
+
with user-named metrics, inclusive monthly snapshots, `describe` and executed
|
|
142
|
+
printed output; in-memory materialization and file-backed staging are distinct.
|
|
143
|
+
- The library hand-off guide builds a catalog-backed treatment/outcome grid of
|
|
144
|
+
trial panels for DoubleML, preserving repeated-unit clusters and distinct
|
|
145
|
+
nullable event labels; DoWhy's one-row-per-unit and weight restrictions remain
|
|
146
|
+
explicit. Terrine makes no causal claims.
|
|
147
|
+
- Guides cover `History`, metric constructors, catalog export, unit inference,
|
|
148
|
+
inclusive date bounds, calendar schedules, values-only views, training sets
|
|
149
|
+
and coded refusals.
|
|
150
|
+
|
|
151
|
+
### Tests
|
|
152
|
+
|
|
153
|
+
- Catalog/design round trips, date-bound and calendar arithmetic oracles,
|
|
154
|
+
DataFrame/timezone parity, metric/filter/history contracts, inferred-unit
|
|
155
|
+
identity, values-only columns, metadata-only summaries, coded constructor
|
|
156
|
+
refusals and public docstring examples cover the API.
|
|
157
|
+
- Acceptance tests execute the README and file-backed staging tutorial and all
|
|
158
|
+
four causal-grid pairs, check distinct treated populations and outcome values,
|
|
159
|
+
and construct clustered `DoubleMLData` when the extra is installed.
|
|
160
|
+
- Refresh unit and integration duration records for balanced CI groups.
|
|
161
|
+
|
|
3
162
|
## 0.1.0.dev1
|
|
4
163
|
|
|
5
164
|
### Backend compatibility
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: terrine
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev2
|
|
4
4
|
Summary: Typed dataset construction and analytical workflows
|
|
5
5
|
Project-URL: Homepage, https://github.com/kylejcaron/terrine
|
|
6
6
|
Project-URL: Documentation, https://github.com/kylejcaron/terrine/tree/main/docs/guides
|
|
@@ -57,6 +57,9 @@ Description-Content-Type: text/markdown
|
|
|
57
57
|
|
|
58
58
|
# terrine
|
|
59
59
|
|
|
60
|
+
> [!WARNING]
|
|
61
|
+
> Terrine is an experimental idea for simplifying dataset curation. It is pre-alpha: the API will change without notice, and it is not ready for production use.
|
|
62
|
+
|
|
60
63
|
**Build datasets directly from event logs.**
|
|
61
64
|
|
|
62
65
|
You have event tables: orders, sessions, payments. You want a modeling table, for example one row per customer per snapshot date, with features computed from the past and labels from the future. Terrine builds that table in your warehouse (DuckDB, PostgreSQL, BigQuery, Snowflake, and more) from a few lines of Python.
|
|
@@ -84,7 +87,6 @@ pip install --pre "terrine[duckdb,pandas]"
|
|
|
84
87
|
|
|
85
88
|
<!-- --8<-- [start:first-dataset] -->
|
|
86
89
|
```python
|
|
87
|
-
import ibis
|
|
88
90
|
import pandas as pd
|
|
89
91
|
import terrine as tr
|
|
90
92
|
|
|
@@ -97,32 +99,28 @@ orders = pd.DataFrame({
|
|
|
97
99
|
"amount": [30.0, 45.0, 25.0, 60.0, 80.0],
|
|
98
100
|
})
|
|
99
101
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
con.create_table("orders", orders)
|
|
103
|
-
|
|
104
|
-
# Tell Terrine who the rows are about and which dates the orders table covers.
|
|
102
|
+
# Who the rows are about, which table holds the events, what to measure,
|
|
103
|
+
# and which calendar days (inclusive) the orders table is fully loaded for.
|
|
105
104
|
model = tr.Model.from_tables(
|
|
106
|
-
|
|
107
|
-
units={"customer": tr.UnitSource("customers", "customer_id")},
|
|
105
|
+
units={"customer": tr.UnitSource(customers, "customer_id")},
|
|
108
106
|
events={"orders": tr.Events(
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
orders, time="ordered_at", key="customer_id",
|
|
108
|
+
metrics={"orders": tr.count(), "spend": tr.sum("amount")},
|
|
109
|
+
complete=("2025-01-01", "2025-03-20"),
|
|
111
110
|
)},
|
|
112
111
|
)
|
|
113
112
|
|
|
114
|
-
# One row per customer
|
|
115
|
-
# spend in the next 30 days as the target.
|
|
113
|
+
# One row per customer on the first of each month, February through March:
|
|
114
|
+
# the last 30 days as features, spend in the next 30 days as the target.
|
|
116
115
|
design = tr.recipes.time_series(
|
|
117
|
-
|
|
116
|
+
every="1mo", start="2025-02-01", end="2025-03-01",
|
|
118
117
|
features={
|
|
119
|
-
"orders_30d": tr.agg("
|
|
120
|
-
"spend_30d": tr.agg("
|
|
121
|
-
},
|
|
122
|
-
outcomes={
|
|
123
|
-
"spend_next_30d": tr.agg("orders_amount_sum", window=("0s", "30d")),
|
|
118
|
+
"orders_30d": tr.agg("orders", window=("-30d", "0s")),
|
|
119
|
+
"spend_30d": tr.agg("spend", window=("-30d", "0s")),
|
|
124
120
|
},
|
|
121
|
+
outcomes={"spend_next_30d": tr.agg("spend", window=("0s", "30d"))},
|
|
125
122
|
)
|
|
123
|
+
print(design.describe(model))
|
|
126
124
|
|
|
127
125
|
data = design.build(model).to_pandas()
|
|
128
126
|
columns = ["unit", "t0", "orders_30d", "spend_30d", "spend_next_30d", "spend_next_30d__reason"]
|
|
@@ -130,7 +128,29 @@ print(data[columns].assign(t0=data["t0"].dt.date).sort_values(["t0", "unit"]).to
|
|
|
130
128
|
```
|
|
131
129
|
<!-- --8<-- [end:first-dataset] -->
|
|
132
130
|
|
|
131
|
+
`describe` explains the rows, windows and declared completeness without querying
|
|
132
|
+
the source tables ([sample summary](https://github.com/kylejcaron/terrine/blob/main/docs/guides/design-validation.md#describing-a-design)).
|
|
133
|
+
Running the example prints:
|
|
134
|
+
|
|
133
135
|
```text
|
|
136
|
+
One row per customer (table customer, key customer_id) at each snapshot.
|
|
137
|
+
Snapshots: every 1 month from 2025-02-01 through 2025-03-01 (2 dates: 2025-02-01, 2025-03-01), at
|
|
138
|
+
00:00 UTC.
|
|
139
|
+
Features (before t0):
|
|
140
|
+
orders_30d: count of orders in [t0 - 30d, t0)
|
|
141
|
+
spend_30d: sum of orders.amount in [t0 - 30d, t0)
|
|
142
|
+
Outcomes (after t0):
|
|
143
|
+
spend_next_30d: sum of orders.amount in [t0, t0 + 30d)
|
|
144
|
+
Completeness: orders is declared complete for 2025-01-01 through 2025-03-20 (UTC days).
|
|
145
|
+
A metric window outside declared source coverage is null with reason incomplete_coverage; unit
|
|
146
|
+
observation bounds use incomplete_observation.
|
|
147
|
+
Empty complete metric windows (no matching events):
|
|
148
|
+
orders_30d: 0 (no reason)
|
|
149
|
+
spend_30d: 0 (no reason)
|
|
150
|
+
spend_next_30d: 0 (no reason)
|
|
151
|
+
Columns: unit, anchor_id, t0, orders_30d, orders_30d__complete, orders_30d__reason, spend_30d,
|
|
152
|
+
spend_30d__complete, spend_30d__reason, spend_next_30d, spend_next_30d__complete,
|
|
153
|
+
spend_next_30d__reason.
|
|
134
154
|
unit t0 orders_30d spend_30d spend_next_30d spend_next_30d__reason
|
|
135
155
|
alice 2025-02-01 2 75.0 25.0 <NA>
|
|
136
156
|
bob 2025-02-01 1 80.0 0.0 <NA>
|
|
@@ -148,15 +168,15 @@ Reading the output:
|
|
|
148
168
|
- **Alice's February 10 order** is in her February 1 target and her March 1 features. The same order is the future for one row and the past for the next.
|
|
149
169
|
- **The March targets are unknown.** Their 30-day window runs past March 20, where the orders data ends. Drop them, keep them for scoring, or handle them however suits your model.
|
|
150
170
|
|
|
151
|
-
|
|
171
|
+
You name the metrics: `tr.count()` counts events, `tr.sum("amount")` adds a column, and `tr.count(where={"status": "refunded"})` counts only matching events once `status` is declared in `labels=`. Every value has a `<name>__complete` flag and a `<name>__reason` column, so you can filter or impute deliberately.
|
|
152
172
|
|
|
153
173
|
## The four ideas
|
|
154
174
|
|
|
155
175
|
| Idea | In the example |
|
|
156
176
|
| --- | --- |
|
|
157
177
|
| **Unit:** who each row is about, read from a table so nobody silently drops out | `customers.customer_id` |
|
|
158
|
-
| **t0:** the snapshot time of each row | every
|
|
159
|
-
| **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0 | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
|
|
178
|
+
| **t0:** the snapshot time of each row. `every` is an elapsed duration (`"7d"`, `"4w"`) or a calendar step (`"1mo"`, `"3mo"`, `"1y"`); a monthly schedule from the 31st lands on Jan 31, Feb 28, Mar 31 | `every="1mo"`: the first of each month from 2025-02-01 through 2025-03-01, both days included |
|
|
179
|
+
| **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0. Windows are elapsed time: `"-1mo"` is refused, use `"-30d"` or `"-4w"` | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
|
|
160
180
|
| **Completeness:** the calendar days, inclusive, that you declare fully loaded for each event table (not just the first and last event you happen to have). Windows outside them are unknown, not zero | `complete=("2025-01-01", "2025-03-20")` |
|
|
161
181
|
|
|
162
182
|
## Other datasets
|
|
@@ -177,7 +197,7 @@ More feature recipes are in [common feature patterns](https://github.com/kylejca
|
|
|
177
197
|
|
|
178
198
|
## Where it runs
|
|
179
199
|
|
|
180
|
-
Queries run where your data lives; nothing is uploaded. Results come back as pandas, Polars or Arrow.
|
|
200
|
+
Queries run where your data lives; nothing is uploaded. DataFrames and Arrow tables, as above, are queried in a private in-process DuckDB. For a warehouse, pass its Ibis connection first and name the tables: `tr.Model.from_tables(con, units={"customer": tr.UnitSource("customers", "customer_id")}, events={"orders": tr.Events("orders", ...)})`. Results come back as pandas, Polars or Arrow.
|
|
181
201
|
|
|
182
202
|
| Backend | Status |
|
|
183
203
|
| --- | --- |
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# terrine
|
|
2
2
|
|
|
3
|
+
> [!WARNING]
|
|
4
|
+
> Terrine is an experimental idea for simplifying dataset curation. It is pre-alpha: the API will change without notice, and it is not ready for production use.
|
|
5
|
+
|
|
3
6
|
**Build datasets directly from event logs.**
|
|
4
7
|
|
|
5
8
|
You have event tables: orders, sessions, payments. You want a modeling table, for example one row per customer per snapshot date, with features computed from the past and labels from the future. Terrine builds that table in your warehouse (DuckDB, PostgreSQL, BigQuery, Snowflake, and more) from a few lines of Python.
|
|
@@ -27,7 +30,6 @@ pip install --pre "terrine[duckdb,pandas]"
|
|
|
27
30
|
|
|
28
31
|
<!-- --8<-- [start:first-dataset] -->
|
|
29
32
|
```python
|
|
30
|
-
import ibis
|
|
31
33
|
import pandas as pd
|
|
32
34
|
import terrine as tr
|
|
33
35
|
|
|
@@ -40,32 +42,28 @@ orders = pd.DataFrame({
|
|
|
40
42
|
"amount": [30.0, 45.0, 25.0, 60.0, 80.0],
|
|
41
43
|
})
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
con.create_table("orders", orders)
|
|
46
|
-
|
|
47
|
-
# Tell Terrine who the rows are about and which dates the orders table covers.
|
|
45
|
+
# Who the rows are about, which table holds the events, what to measure,
|
|
46
|
+
# and which calendar days (inclusive) the orders table is fully loaded for.
|
|
48
47
|
model = tr.Model.from_tables(
|
|
49
|
-
|
|
50
|
-
units={"customer": tr.UnitSource("customers", "customer_id")},
|
|
48
|
+
units={"customer": tr.UnitSource(customers, "customer_id")},
|
|
51
49
|
events={"orders": tr.Events(
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
orders, time="ordered_at", key="customer_id",
|
|
51
|
+
metrics={"orders": tr.count(), "spend": tr.sum("amount")},
|
|
52
|
+
complete=("2025-01-01", "2025-03-20"),
|
|
54
53
|
)},
|
|
55
54
|
)
|
|
56
55
|
|
|
57
|
-
# One row per customer
|
|
58
|
-
# spend in the next 30 days as the target.
|
|
56
|
+
# One row per customer on the first of each month, February through March:
|
|
57
|
+
# the last 30 days as features, spend in the next 30 days as the target.
|
|
59
58
|
design = tr.recipes.time_series(
|
|
60
|
-
|
|
59
|
+
every="1mo", start="2025-02-01", end="2025-03-01",
|
|
61
60
|
features={
|
|
62
|
-
"orders_30d": tr.agg("
|
|
63
|
-
"spend_30d": tr.agg("
|
|
64
|
-
},
|
|
65
|
-
outcomes={
|
|
66
|
-
"spend_next_30d": tr.agg("orders_amount_sum", window=("0s", "30d")),
|
|
61
|
+
"orders_30d": tr.agg("orders", window=("-30d", "0s")),
|
|
62
|
+
"spend_30d": tr.agg("spend", window=("-30d", "0s")),
|
|
67
63
|
},
|
|
64
|
+
outcomes={"spend_next_30d": tr.agg("spend", window=("0s", "30d"))},
|
|
68
65
|
)
|
|
66
|
+
print(design.describe(model))
|
|
69
67
|
|
|
70
68
|
data = design.build(model).to_pandas()
|
|
71
69
|
columns = ["unit", "t0", "orders_30d", "spend_30d", "spend_next_30d", "spend_next_30d__reason"]
|
|
@@ -73,7 +71,29 @@ print(data[columns].assign(t0=data["t0"].dt.date).sort_values(["t0", "unit"]).to
|
|
|
73
71
|
```
|
|
74
72
|
<!-- --8<-- [end:first-dataset] -->
|
|
75
73
|
|
|
74
|
+
`describe` explains the rows, windows and declared completeness without querying
|
|
75
|
+
the source tables ([sample summary](https://github.com/kylejcaron/terrine/blob/main/docs/guides/design-validation.md#describing-a-design)).
|
|
76
|
+
Running the example prints:
|
|
77
|
+
|
|
76
78
|
```text
|
|
79
|
+
One row per customer (table customer, key customer_id) at each snapshot.
|
|
80
|
+
Snapshots: every 1 month from 2025-02-01 through 2025-03-01 (2 dates: 2025-02-01, 2025-03-01), at
|
|
81
|
+
00:00 UTC.
|
|
82
|
+
Features (before t0):
|
|
83
|
+
orders_30d: count of orders in [t0 - 30d, t0)
|
|
84
|
+
spend_30d: sum of orders.amount in [t0 - 30d, t0)
|
|
85
|
+
Outcomes (after t0):
|
|
86
|
+
spend_next_30d: sum of orders.amount in [t0, t0 + 30d)
|
|
87
|
+
Completeness: orders is declared complete for 2025-01-01 through 2025-03-20 (UTC days).
|
|
88
|
+
A metric window outside declared source coverage is null with reason incomplete_coverage; unit
|
|
89
|
+
observation bounds use incomplete_observation.
|
|
90
|
+
Empty complete metric windows (no matching events):
|
|
91
|
+
orders_30d: 0 (no reason)
|
|
92
|
+
spend_30d: 0 (no reason)
|
|
93
|
+
spend_next_30d: 0 (no reason)
|
|
94
|
+
Columns: unit, anchor_id, t0, orders_30d, orders_30d__complete, orders_30d__reason, spend_30d,
|
|
95
|
+
spend_30d__complete, spend_30d__reason, spend_next_30d, spend_next_30d__complete,
|
|
96
|
+
spend_next_30d__reason.
|
|
77
97
|
unit t0 orders_30d spend_30d spend_next_30d spend_next_30d__reason
|
|
78
98
|
alice 2025-02-01 2 75.0 25.0 <NA>
|
|
79
99
|
bob 2025-02-01 1 80.0 0.0 <NA>
|
|
@@ -91,15 +111,15 @@ Reading the output:
|
|
|
91
111
|
- **Alice's February 10 order** is in her February 1 target and her March 1 features. The same order is the future for one row and the past for the next.
|
|
92
112
|
- **The March targets are unknown.** Their 30-day window runs past March 20, where the orders data ends. Drop them, keep them for scoring, or handle them however suits your model.
|
|
93
113
|
|
|
94
|
-
|
|
114
|
+
You name the metrics: `tr.count()` counts events, `tr.sum("amount")` adds a column, and `tr.count(where={"status": "refunded"})` counts only matching events once `status` is declared in `labels=`. Every value has a `<name>__complete` flag and a `<name>__reason` column, so you can filter or impute deliberately.
|
|
95
115
|
|
|
96
116
|
## The four ideas
|
|
97
117
|
|
|
98
118
|
| Idea | In the example |
|
|
99
119
|
| --- | --- |
|
|
100
120
|
| **Unit:** who each row is about, read from a table so nobody silently drops out | `customers.customer_id` |
|
|
101
|
-
| **t0:** the snapshot time of each row | every
|
|
102
|
-
| **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0 | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
|
|
121
|
+
| **t0:** the snapshot time of each row. `every` is an elapsed duration (`"7d"`, `"4w"`) or a calendar step (`"1mo"`, `"3mo"`, `"1y"`); a monthly schedule from the 31st lands on Jan 31, Feb 28, Mar 31 | `every="1mo"`: the first of each month from 2025-02-01 through 2025-03-01, both days included |
|
|
122
|
+
| **Windows:** relative to t0, start included, end excluded. Features must end at or before t0; outcomes must start at or after t0. Windows are elapsed time: `"-1mo"` is refused, use `"-30d"` or `"-4w"` | `("-30d", "0s")` is the 30 days before t0; `("0s", "30d")` is the 30 days after |
|
|
103
123
|
| **Completeness:** the calendar days, inclusive, that you declare fully loaded for each event table (not just the first and last event you happen to have). Windows outside them are unknown, not zero | `complete=("2025-01-01", "2025-03-20")` |
|
|
104
124
|
|
|
105
125
|
## Other datasets
|
|
@@ -120,7 +140,7 @@ More feature recipes are in [common feature patterns](https://github.com/kylejca
|
|
|
120
140
|
|
|
121
141
|
## Where it runs
|
|
122
142
|
|
|
123
|
-
Queries run where your data lives; nothing is uploaded. Results come back as pandas, Polars or Arrow.
|
|
143
|
+
Queries run where your data lives; nothing is uploaded. DataFrames and Arrow tables, as above, are queried in a private in-process DuckDB. For a warehouse, pass its Ibis connection first and name the tables: `tr.Model.from_tables(con, units={"customer": tr.UnitSource("customers", "customer_id")}, events={"orders": tr.Events("orders", ...)})`. Results come back as pandas, Polars or Arrow.
|
|
124
144
|
|
|
125
145
|
| Backend | Status |
|
|
126
146
|
| --- | --- |
|
|
@@ -79,20 +79,20 @@ WORKLOADS: tuple[Workload, ...] = (
|
|
|
79
79
|
def _catalog() -> Catalog:
|
|
80
80
|
return Catalog.model_validate(
|
|
81
81
|
{
|
|
82
|
-
"schema_version":
|
|
82
|
+
"schema_version": 2,
|
|
83
83
|
"day_boundary": "UTC",
|
|
84
84
|
"dialect": "duckdb",
|
|
85
85
|
"sources": {
|
|
86
86
|
"purchases": {
|
|
87
87
|
"relation": {"kind": "table", "name": "events"},
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
88
|
+
"time": "occurred_at",
|
|
89
|
+
"units": {"user": "user_id"},
|
|
90
|
+
"events": ["purchase", "amount", "price"],
|
|
91
91
|
"properties": [],
|
|
92
92
|
"dimensions": [],
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
|
-
"
|
|
95
|
+
"events": {
|
|
96
96
|
"purchase": {"source": "purchases", "column": None},
|
|
97
97
|
"amount": {"source": "purchases", "column": "amount"},
|
|
98
98
|
"price": {"source": "purchases", "column": "price"},
|
|
@@ -100,17 +100,17 @@ def _catalog() -> Catalog:
|
|
|
100
100
|
"properties": {},
|
|
101
101
|
"dimensions": {},
|
|
102
102
|
"measures": {
|
|
103
|
-
"purchase_count": {"
|
|
104
|
-
"amount_total": {"
|
|
105
|
-
"price_avg": {"
|
|
103
|
+
"purchase_count": {"event": "purchase", "aggregation": "count"},
|
|
104
|
+
"amount_total": {"event": "amount", "aggregation": "sum"},
|
|
105
|
+
"price_avg": {"event": "price", "aggregation": "avg_event"},
|
|
106
106
|
},
|
|
107
107
|
"metrics": {
|
|
108
|
-
"purchases": {"kind": "mean", "
|
|
109
|
-
"spend": {"kind": "mean", "
|
|
110
|
-
"avg_price": {"kind": "mean", "
|
|
108
|
+
"purchases": {"kind": "mean", "unit": "user", "measure": "purchase_count"},
|
|
109
|
+
"spend": {"kind": "mean", "unit": "user", "measure": "amount_total"},
|
|
110
|
+
"avg_price": {"kind": "mean", "unit": "user", "measure": "price_avg"},
|
|
111
111
|
"spend_per_purchase": {
|
|
112
112
|
"kind": "ratio",
|
|
113
|
-
"
|
|
113
|
+
"unit": "user",
|
|
114
114
|
"numerator": "amount_total",
|
|
115
115
|
"denominator": "purchase_count",
|
|
116
116
|
},
|
|
@@ -48,20 +48,20 @@ def _catalog(dialect: str) -> Catalog:
|
|
|
48
48
|
}
|
|
49
49
|
return Catalog.model_validate(
|
|
50
50
|
{
|
|
51
|
-
"schema_version":
|
|
51
|
+
"schema_version": 2,
|
|
52
52
|
"day_boundary": "UTC",
|
|
53
53
|
"dialect": dialect,
|
|
54
54
|
"sources": {
|
|
55
55
|
"events": {
|
|
56
56
|
"relation": {"kind": "table", "name": "events"},
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
57
|
+
"time": "event_time",
|
|
58
|
+
"units": {"user": "unit_id"},
|
|
59
|
+
"events": ["value", "event"],
|
|
60
60
|
"properties": ["adopted", "event_name"],
|
|
61
61
|
"dimensions": [],
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
-
"
|
|
64
|
+
"events": {
|
|
65
65
|
"value": {"source": "events", "column": "value"},
|
|
66
66
|
"event": {"source": "events", "column": None},
|
|
67
67
|
},
|
|
@@ -76,11 +76,11 @@ def _catalog(dialect: str) -> Catalog:
|
|
|
76
76
|
},
|
|
77
77
|
"dimensions": {},
|
|
78
78
|
"measures": {
|
|
79
|
-
name: {"
|
|
79
|
+
name: {"event": "value", "aggregation": aggregation}
|
|
80
80
|
for name, aggregation in aggregations.items()
|
|
81
81
|
},
|
|
82
82
|
"metrics": {
|
|
83
|
-
name: {"kind": "mean", "
|
|
83
|
+
name: {"kind": "mean", "unit": "user", "measure": measure}
|
|
84
84
|
for name, measure in (*((name, name) for name in aggregations), ("value", "total"))
|
|
85
85
|
},
|
|
86
86
|
}
|
|
@@ -137,7 +137,7 @@ def designs(config: ScaleConfig) -> dict[str, Design]:
|
|
|
137
137
|
Schedule(
|
|
138
138
|
"1d",
|
|
139
139
|
(_EPOCH + timedelta(days=config.window_days)).date(),
|
|
140
|
-
(_EPOCH + timedelta(days=config.window_days + config.anchor_periods)).date(),
|
|
140
|
+
(_EPOCH + timedelta(days=config.window_days + config.anchor_periods - 1)).date(),
|
|
141
141
|
),
|
|
142
142
|
features=features,
|
|
143
143
|
)
|
|
@@ -205,7 +205,7 @@ precision and refuses rather than narrows.
|
|
|
205
205
|
magnitude sum (`γₖ = k · u / (1 − k · u)`), so a partition is admitted iff
|
|
206
206
|
`B < limit · (1 − e)`. Otherwise its values are withheld before the backend
|
|
207
207
|
adds them and every consumer of that state refuses: sums, averages and
|
|
208
|
-
ratios with `float64_precision` (`bound_overflow`) for float
|
|
208
|
+
ratios with `float64_precision` (`bound_overflow`) for float event values or
|
|
209
209
|
`terrine.temporal.exact_overflow` for coefficients, cohort totals and means
|
|
210
210
|
and cumulative trajectories with their existing overflow reasons. This
|
|
211
211
|
bound is deliberately conservative: opposite-sign history whose magnitudes
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Use `design.build(model)` to compile a lazy Frame; materialization and staging run the required integrity checks through the owned execution path. Its candidate rows retain `unit`, `anchor_id`, exact `t0`, observation bounds and anchor provenance. Building does not execute source-data queries. Anchor integrity refusals expose stable `check_key` values such as `null_roster_key`, `duplicate_roster_key`, `null_event_id`, `duplicate_event_id`, `null_event_unit`, `null_event_timestamp`, `tied_event_timestamps` and `ambiguous_event_identity`. Correct the named source contract instead of silently deduplicating candidates.
|
|
4
4
|
|
|
5
|
-
`FixedDate` and `Schedule` use the explicitly declared `UnitSource` roster for the design's logical unit. Roster entry/exit bounds are membership bounds evaluated as `[entered_at, exited_at)` at each candidate `t0`. Observation bounds are copied independently; a missing bound remains null and roster exit is not used as censoring. Fixed-date calendar bounds resolve through the Catalog's declared day boundary. Schedules advance by elapsed seconds from start
|
|
5
|
+
`FixedDate` and `Schedule` use the explicitly declared `UnitSource` roster for the design's logical unit. Roster entry/exit bounds are membership bounds evaluated as `[entered_at, exited_at)` at each candidate `t0`. Observation bounds are copied independently; a missing bound remains null and roster exit is not used as censoring. Fixed-date calendar bounds resolve through the Catalog's declared day boundary. Schedules advance by elapsed seconds from the start day through the end day, which is included: the anchors are every `t0 = day(start) + k * every` before `day(end + 1 day)`.
|
|
6
6
|
|
|
7
|
-
`EventAnchor` reads the
|
|
7
|
+
`EventAnchor` reads the named event's bound source and physical unit mapping. Event values are not used as event-presence predicates: a row is an event even when its event column is null. The source's `Model.event_ids` column is retained from raw relation rows for stable tie ordering and identity. Aware timestamp components of fallback or configured event identities are rendered after explicit UTC normalization, independent of the session timezone. `first` and `nth` are selected over full source history before `Design.anchor_range` filtering; `each` retains every event. A range starts at its `start` bound; a calendar-date `end` keeps the whole of that local day, resolved through Catalog day-boundary semantics, while an aware datetime `end` remains an exact exclusive UTC instant.
|
|
8
8
|
|
|
9
9
|
## Landmark event anchors
|
|
10
10
|
|
|
11
11
|
`EventAnchor("purchase", offset="1d")` sets `t0` to the selected purchase
|
|
12
12
|
instant plus one elapsed day. Offsets default to zero and accept non-negative
|
|
13
13
|
integral seconds or `s`/`m`/`h`/`d`/`w` durations. Negative offsets refuse at
|
|
14
|
-
construction with `terrine.design.invalid
|
|
14
|
+
construction with `terrine.design.invalid` (`reason="negative"`,
|
|
15
|
+
`field="offset"`). Selection and `anchor_range` use
|
|
15
16
|
the **original event time**, not the landmark; the shift happens afterward.
|
|
16
17
|
Roster membership and all feature/outcome windows use the resulting `t0`.
|
|
17
18
|
|
|
@@ -43,7 +44,7 @@ Frame materialization or a full rebuild instead.
|
|
|
43
44
|
## Source integrity
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
An event anchor must name
|
|
47
|
+
An event anchor must name an event declared by its bound source, not merely
|
|
47
48
|
present elsewhere in the Catalog; missing references use the same refusal
|
|
48
49
|
as `Design.validate`. Unrelated source columns cannot change generated
|
|
49
50
|
identities, including columns whose names differ only by case from a
|