reader-workbench 1.0.0__py3-none-any.whl
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.
- reader_workbench/__init__.py +22 -0
- reader_workbench/__main__.py +4 -0
- reader_workbench/_version.py +17 -0
- reader_workbench/api/__init__.py +74 -0
- reader_workbench/api/_record_reads.py +75 -0
- reader_workbench/api/artifacts.py +79 -0
- reader_workbench/api/facade.py +538 -0
- reader_workbench/api/models.py +285 -0
- reader_workbench/api/notebooks.py +63 -0
- reader_workbench/contracts/__init__.py +18 -0
- reader_workbench/contracts/builtins/__init__.py +36 -0
- reader_workbench/contracts/builtins/cytometry.py +140 -0
- reader_workbench/contracts/builtins/four_state_event_window.py +214 -0
- reader_workbench/contracts/builtins/generic.py +21 -0
- reader_workbench/contracts/builtins/logic.py +149 -0
- reader_workbench/contracts/builtins/plate_reader.py +47 -0
- reader_workbench/contracts/catalog.py +257 -0
- reader_workbench/contracts/model.py +109 -0
- reader_workbench/domains/__init__.py +1 -0
- reader_workbench/domains/cytometry/__init__.py +3 -0
- reader_workbench/domains/cytometry/analysis/__init__.py +29 -0
- reader_workbench/domains/cytometry/analysis/events.py +182 -0
- reader_workbench/domains/cytometry/analysis/gating.py +175 -0
- reader_workbench/domains/cytometry/analysis/workflow.py +248 -0
- reader_workbench/domains/cytometry/io/__init__.py +3 -0
- reader_workbench/domains/cytometry/io/fcs.py +135 -0
- reader_workbench/domains/cytometry/plots/__init__.py +5 -0
- reader_workbench/domains/cytometry/plots/diagnostic.py +155 -0
- reader_workbench/domains/logic/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/pairs.py +661 -0
- reader_workbench/domains/logic/four_state_vector/__init__.py +7 -0
- reader_workbench/domains/logic/four_state_vector/builder.py +321 -0
- reader_workbench/domains/logic/four_state_vector/collection/__init__.py +20 -0
- reader_workbench/domains/logic/four_state_vector/collection/checks.py +49 -0
- reader_workbench/domains/logic/four_state_vector/collection/constants.py +29 -0
- reader_workbench/domains/logic/four_state_vector/collection/model.py +19 -0
- reader_workbench/domains/logic/four_state_vector/collection/render.py +383 -0
- reader_workbench/domains/logic/four_state_vector/collection/sources.py +185 -0
- reader_workbench/domains/logic/four_state_vector/config.py +214 -0
- reader_workbench/domains/logic/four_state_vector/diagnostic.py +361 -0
- reader_workbench/domains/logic/four_state_vector/heatmap.py +86 -0
- reader_workbench/domains/logic/four_state_vector/math.py +191 -0
- reader_workbench/domains/logic/four_state_vector/reference.py +85 -0
- reader_workbench/domains/logic/four_state_vector/selection.py +228 -0
- reader_workbench/domains/logic/four_state_vector/treatment_semantics.py +51 -0
- reader_workbench/domains/logic/four_state_vector/validation.py +19 -0
- reader_workbench/domains/logic/logic_symmetry/__init__.py +3 -0
- reader_workbench/domains/logic/logic_symmetry/encodings.py +93 -0
- reader_workbench/domains/logic/logic_symmetry/extract_corners.py +192 -0
- reader_workbench/domains/logic/logic_symmetry/main.py +236 -0
- reader_workbench/domains/logic/logic_symmetry/metrics.py +96 -0
- reader_workbench/domains/logic/logic_symmetry/overlay.py +129 -0
- reader_workbench/domains/logic/logic_symmetry/prep.py +138 -0
- reader_workbench/domains/logic/logic_symmetry/render.py +356 -0
- reader_workbench/domains/logic/treatment_columns.py +42 -0
- reader_workbench/domains/plate_reader/__init__.py +1 -0
- reader_workbench/domains/plate_reader/analysis/__init__.py +14 -0
- reader_workbench/domains/plate_reader/analysis/fold_change.py +474 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/__init__.py +21 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/aggregation.py +191 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contract_fields.py +50 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contracts.py +320 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/design_dispositions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/disposition_records.py +140 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/event_sensitivity.py +27 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/materialize.py +274 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/observation_resampling.py +97 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/reduction.py +62 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/seeds.py +15 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/sources.py +308 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusion_validation.py +94 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/timepoints.py +76 -0
- reader_workbench/domains/plate_reader/io/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/sample_map.py +65 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_kinetic.py +141 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_parser.py +295 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_shared.py +195 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_snapshot.py +130 -0
- reader_workbench/domains/plate_reader/ordering.py +59 -0
- reader_workbench/domains/plate_reader/plots/__init__.py +15 -0
- reader_workbench/domains/plate_reader/plots/_data.py +29 -0
- reader_workbench/domains/plate_reader/plots/common.py +346 -0
- reader_workbench/domains/plate_reader/plots/distributions.py +324 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych.py +525 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych_render.py +195 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/__init__.py +26 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic.py +295 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_components.py +149 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_render.py +308 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_style.py +51 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/schema.py +8 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/summary.py +140 -0
- reader_workbench/domains/plate_reader/plots/grouping.py +53 -0
- reader_workbench/domains/plate_reader/plots/panels/__init__.py +12 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot.py +161 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot_data.py +91 -0
- reader_workbench/domains/plate_reader/plots/panels/time_series.py +296 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic.py +435 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic_render.py +300 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/__init__.py +315 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/planning.py +168 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/__init__.py +205 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/inputs.py +103 -0
- reader_workbench/domains/plate_reader/plots/time_series.py +317 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/__init__.py +479 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/planning.py +283 -0
- reader_workbench/domains/time_series/__init__.py +29 -0
- reader_workbench/domains/time_series/aggregation.py +60 -0
- reader_workbench/domains/time_series/contracts.py +368 -0
- reader_workbench/domains/time_series/reduction.py +395 -0
- reader_workbench/errors.py +55 -0
- reader_workbench/maintenance/__init__.py +6 -0
- reader_workbench/maintenance/docs.py +335 -0
- reader_workbench/maintenance/model.py +28 -0
- reader_workbench/maintenance/release.py +39 -0
- reader_workbench/maintenance/skills.py +124 -0
- reader_workbench/plotting/__init__.py +20 -0
- reader_workbench/plotting/mpl.py +56 -0
- reader_workbench/plotting/sinks.py +69 -0
- reader_workbench/plotting/style.py +175 -0
- reader_workbench/plotting/utils.py +27 -0
- reader_workbench/plugins/__init__.py +1 -0
- reader_workbench/plugins/catalog.py +33 -0
- reader_workbench/plugins/export/__init__.py +0 -0
- reader_workbench/plugins/export/_paths.py +21 -0
- reader_workbench/plugins/export/csv.py +41 -0
- reader_workbench/plugins/export/xlsx.py +44 -0
- reader_workbench/plugins/ingest/__init__.py +0 -0
- reader_workbench/plugins/ingest/_discovery.py +58 -0
- reader_workbench/plugins/ingest/discovery_policy.py +66 -0
- reader_workbench/plugins/ingest/flow_cytometer.py +139 -0
- reader_workbench/plugins/ingest/synergy_h1.py +234 -0
- reader_workbench/plugins/manifests/__init__.py +1 -0
- reader_workbench/plugins/manifests/export.py +29 -0
- reader_workbench/plugins/manifests/ingest.py +29 -0
- reader_workbench/plugins/manifests/plot.py +161 -0
- reader_workbench/plugins/manifests/transform.py +172 -0
- reader_workbench/plugins/manifests/validator.py +18 -0
- reader_workbench/plugins/plot/__init__.py +0 -0
- reader_workbench/plugins/plot/_shared.py +55 -0
- reader_workbench/plugins/plot/cytometry_diagnostic.py +54 -0
- reader_workbench/plugins/plot/distributions.py +58 -0
- reader_workbench/plugins/plot/dual_reporter_triptych.py +224 -0
- reader_workbench/plugins/plot/four_state_event_window_diagnostic.py +133 -0
- reader_workbench/plugins/plot/four_state_event_window_summary.py +53 -0
- reader_workbench/plugins/plot/four_state_vector_collection.py +45 -0
- reader_workbench/plugins/plot/four_state_vector_diagnostic.py +105 -0
- reader_workbench/plugins/plot/four_state_vector_heatmap.py +63 -0
- reader_workbench/plugins/plot/logic_symmetry.py +56 -0
- reader_workbench/plugins/plot/single_reporter_diagnostic.py +279 -0
- reader_workbench/plugins/plot/snapshot_barplot.py +65 -0
- reader_workbench/plugins/plot/snapshot_heatmap.py +104 -0
- reader_workbench/plugins/plot/time_series.py +114 -0
- reader_workbench/plugins/plot/ts_and_snap.py +210 -0
- reader_workbench/plugins/transform/__init__.py +0 -0
- reader_workbench/plugins/transform/_four_state_vector.py +204 -0
- reader_workbench/plugins/transform/_labeling.py +109 -0
- reader_workbench/plugins/transform/alias.py +70 -0
- reader_workbench/plugins/transform/assay_labels.py +62 -0
- reader_workbench/plugins/transform/blank.py +79 -0
- reader_workbench/plugins/transform/crosstalk_pairs.py +180 -0
- reader_workbench/plugins/transform/cytometry_gating.py +120 -0
- reader_workbench/plugins/transform/fold_change.py +79 -0
- reader_workbench/plugins/transform/four_state_event_window.py +93 -0
- reader_workbench/plugins/transform/four_state_vector.py +62 -0
- reader_workbench/plugins/transform/four_state_vector_collection.py +41 -0
- reader_workbench/plugins/transform/logic_symmetry.py +67 -0
- reader_workbench/plugins/transform/outlier_filter.py +60 -0
- reader_workbench/plugins/transform/overflow.py +197 -0
- reader_workbench/plugins/transform/ratio.py +237 -0
- reader_workbench/plugins/transform/sample_map.py +170 -0
- reader_workbench/plugins/transform/sample_metadata.py +94 -0
- reader_workbench/plugins/validator/__init__.py +1 -0
- reader_workbench/plugins/validator/to_tidy_plus_map.py +155 -0
- reader_workbench/protocols/__init__.py +80 -0
- reader_workbench/protocols/_builtins_plate_reader_growth.py +179 -0
- reader_workbench/protocols/_builtins_plate_reader_variants.py +274 -0
- reader_workbench/protocols/builtins.py +1656 -0
- reader_workbench/protocols/compiler.py +22 -0
- reader_workbench/protocols/compilers/__init__.py +1 -0
- reader_workbench/protocols/compilers/common.py +100 -0
- reader_workbench/protocols/compilers/cytometry.py +87 -0
- reader_workbench/protocols/compilers/generic.py +14 -0
- reader_workbench/protocols/compilers/logic.py +245 -0
- reader_workbench/protocols/compilers/plate_reader.py +937 -0
- reader_workbench/protocols/compilers/plate_reader_pipeline.py +197 -0
- reader_workbench/protocols/model.py +1486 -0
- reader_workbench/protocols/semantic_coverage.py +234 -0
- reader_workbench/runtime/__init__.py +12 -0
- reader_workbench/runtime/builtin.py +23 -0
- reader_workbench/runtime/model.py +42 -0
- reader_workbench/workbench/__init__.py +60 -0
- reader_workbench/workbench/assets/__init__.py +22 -0
- reader_workbench/workbench/assets/types.py +118 -0
- reader_workbench/workbench/audit/__init__.py +5 -0
- reader_workbench/workbench/audit/experiments.py +307 -0
- reader_workbench/workbench/audit/staging.py +187 -0
- reader_workbench/workbench/cli/__init__.py +51 -0
- reader_workbench/workbench/cli/_lazy.py +9 -0
- reader_workbench/workbench/cli/_records_view.py +150 -0
- reader_workbench/workbench/cli/_surface_execution.py +443 -0
- reader_workbench/workbench/cli/audit.py +95 -0
- reader_workbench/workbench/cli/automation.py +229 -0
- reader_workbench/workbench/cli/demo.py +46 -0
- reader_workbench/workbench/cli/dop.py +91 -0
- reader_workbench/workbench/cli/experiments.py +635 -0
- reader_workbench/workbench/cli/helpers.py +232 -0
- reader_workbench/workbench/cli/main.py +59 -0
- reader_workbench/workbench/cli/maintenance.py +82 -0
- reader_workbench/workbench/cli/notebooks.py +260 -0
- reader_workbench/workbench/cli/pagination.py +117 -0
- reader_workbench/workbench/cli/protocols.py +336 -0
- reader_workbench/workbench/cli/shared.py +309 -0
- reader_workbench/workbench/cli/surfaces.py +534 -0
- reader_workbench/workbench/cli/verification.py +128 -0
- reader_workbench/workbench/commands.py +10 -0
- reader_workbench/workbench/config/__init__.py +47 -0
- reader_workbench/workbench/config/identity.py +13 -0
- reader_workbench/workbench/config/load.py +405 -0
- reader_workbench/workbench/config/model.py +274 -0
- reader_workbench/workbench/context.py +26 -0
- reader_workbench/workbench/decl/__init__.py +31 -0
- reader_workbench/workbench/decl/build.py +190 -0
- reader_workbench/workbench/decl/model.py +81 -0
- reader_workbench/workbench/dop/__init__.py +12 -0
- reader_workbench/workbench/dop/builtins.py +261 -0
- reader_workbench/workbench/dop/model.py +209 -0
- reader_workbench/workbench/engine/__init__.py +42 -0
- reader_workbench/workbench/engine/_shared.py +76 -0
- reader_workbench/workbench/engine/contracts.py +283 -0
- reader_workbench/workbench/engine/execution.py +326 -0
- reader_workbench/workbench/engine/file_outputs.py +260 -0
- reader_workbench/workbench/engine/inputs.py +161 -0
- reader_workbench/workbench/engine/invocations.py +507 -0
- reader_workbench/workbench/engine/planning.py +72 -0
- reader_workbench/workbench/engine/runtime.py +464 -0
- reader_workbench/workbench/engine/setup.py +149 -0
- reader_workbench/workbench/engine/validation.py +684 -0
- reader_workbench/workbench/experiment/__init__.py +47 -0
- reader_workbench/workbench/experiment/model.py +381 -0
- reader_workbench/workbench/experiments.py +133 -0
- reader_workbench/workbench/graph/__init__.py +47 -0
- reader_workbench/workbench/graph/nodes.py +102 -0
- reader_workbench/workbench/graph/normalize.py +177 -0
- reader_workbench/workbench/graph/refs.py +148 -0
- reader_workbench/workbench/input_discovery.py +19 -0
- reader_workbench/workbench/inspection/__init__.py +3 -0
- reader_workbench/workbench/inspection/catalogs.py +128 -0
- reader_workbench/workbench/inspection/common.py +92 -0
- reader_workbench/workbench/inspection/dop.py +64 -0
- reader_workbench/workbench/inspection/experiments.py +449 -0
- reader_workbench/workbench/inspection/inventory.py +68 -0
- reader_workbench/workbench/inspection/protocols.py +368 -0
- reader_workbench/workbench/inspection/readiness.py +333 -0
- reader_workbench/workbench/inspection/reports.py +367 -0
- reader_workbench/workbench/inspection/results.py +166 -0
- reader_workbench/workbench/inspection/runtime.py +287 -0
- reader_workbench/workbench/inspection/semantics.py +192 -0
- reader_workbench/workbench/inspection/validation.py +30 -0
- reader_workbench/workbench/notebooks/__init__.py +17 -0
- reader_workbench/workbench/notebooks/_launch_registry.py +112 -0
- reader_workbench/workbench/notebooks/_launch_runtime.py +104 -0
- reader_workbench/workbench/notebooks/components/__init__.py +21 -0
- reader_workbench/workbench/notebooks/components/deliverables.py +403 -0
- reader_workbench/workbench/notebooks/components/overview.py +119 -0
- reader_workbench/workbench/notebooks/eda.marimo.py.txt +153 -0
- reader_workbench/workbench/notebooks/launch.py +274 -0
- reader_workbench/workbench/notebooks/presentation.py +136 -0
- reader_workbench/workbench/notebooks/scaffold.py +60 -0
- reader_workbench/workbench/ontology.py +78 -0
- reader_workbench/workbench/paths.py +44 -0
- reader_workbench/workbench/ports/__init__.py +31 -0
- reader_workbench/workbench/ports/model.py +168 -0
- reader_workbench/workbench/records/__init__.py +44 -0
- reader_workbench/workbench/records/epoch.py +329 -0
- reader_workbench/workbench/records/evidence.py +247 -0
- reader_workbench/workbench/records/identity.py +87 -0
- reader_workbench/workbench/records/locking.py +185 -0
- reader_workbench/workbench/records/model.py +711 -0
- reader_workbench/workbench/records/sources.py +73 -0
- reader_workbench/workbench/records/store.py +1022 -0
- reader_workbench/workbench/records/verification.py +998 -0
- reader_workbench/workbench/registry.py +333 -0
- reader_workbench/workbench/spec_overrides.py +215 -0
- reader_workbench-1.0.0.dist-info/METADATA +91 -0
- reader_workbench-1.0.0.dist-info/RECORD +293 -0
- reader_workbench-1.0.0.dist-info/WHEEL +5 -0
- reader_workbench-1.0.0.dist-info/entry_points.txt +2 -0
- reader_workbench-1.0.0.dist-info/licenses/LICENSE +21 -0
- reader_workbench-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Strict contracts for assay-neutral temporal reductions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
TimeBasis = Literal["absolute", "event_relative"]
|
|
11
|
+
EndpointMode = Literal["exact", "nearest"]
|
|
12
|
+
IntervalBoundary = Literal["inclusive"]
|
|
13
|
+
TemporalReductionMethod = Literal[
|
|
14
|
+
"identity",
|
|
15
|
+
"observed_mean",
|
|
16
|
+
"observed_median",
|
|
17
|
+
"geometric_time_mean",
|
|
18
|
+
"integrated_linear_mean",
|
|
19
|
+
]
|
|
20
|
+
OutputSpace = Literal["linear", "log2"]
|
|
21
|
+
BoundarySupport = Literal["none", "covered", "observed"]
|
|
22
|
+
CensoredValuePolicy = Literal["allow", "reject"]
|
|
23
|
+
PositiveValueScope = Literal["selected_support", "entire_trace"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _mapping(value: object, *, context: str) -> dict[str, Any]:
|
|
27
|
+
if not isinstance(value, Mapping):
|
|
28
|
+
raise ValueError(f"{context} must be a mapping")
|
|
29
|
+
return {str(key): item for key, item in value.items()}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _exact_fields(
|
|
33
|
+
value: object,
|
|
34
|
+
*,
|
|
35
|
+
context: str,
|
|
36
|
+
required: set[str],
|
|
37
|
+
optional: set[str] | None = None,
|
|
38
|
+
) -> dict[str, Any]:
|
|
39
|
+
payload = _mapping(value, context=context)
|
|
40
|
+
allowed = required | (optional or set())
|
|
41
|
+
unknown = sorted(set(payload) - allowed)
|
|
42
|
+
if unknown:
|
|
43
|
+
raise ValueError(f"{context} has unknown fields: {unknown}")
|
|
44
|
+
missing = sorted(required - set(payload))
|
|
45
|
+
if missing:
|
|
46
|
+
raise ValueError(f"{context} is missing required fields: {missing}")
|
|
47
|
+
return payload
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _choice(value: object, *, context: str, allowed: set[str]) -> str:
|
|
51
|
+
if not isinstance(value, str) or value not in allowed:
|
|
52
|
+
options = ", ".join(sorted(allowed))
|
|
53
|
+
raise ValueError(f"{context} must be one of: {options}")
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _finite(value: object, *, context: str) -> float:
|
|
58
|
+
if isinstance(value, bool) or not isinstance(value, int | float):
|
|
59
|
+
raise ValueError(f"{context} must be a finite number")
|
|
60
|
+
result = float(value)
|
|
61
|
+
if not math.isfinite(result):
|
|
62
|
+
raise ValueError(f"{context} must be a finite number")
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class EndpointSelection:
|
|
68
|
+
"""One recorded or event-relative endpoint."""
|
|
69
|
+
|
|
70
|
+
time_basis: TimeBasis
|
|
71
|
+
time_h: float
|
|
72
|
+
mode: EndpointMode
|
|
73
|
+
tolerance_h: float
|
|
74
|
+
|
|
75
|
+
def __post_init__(self) -> None:
|
|
76
|
+
if self.time_basis not in {"absolute", "event_relative"}:
|
|
77
|
+
raise ValueError("endpoint.time_basis must be 'absolute' or 'event_relative'")
|
|
78
|
+
if self.mode not in {"exact", "nearest"}:
|
|
79
|
+
raise ValueError("endpoint.mode must be 'exact' or 'nearest'")
|
|
80
|
+
if not math.isfinite(float(self.time_h)):
|
|
81
|
+
raise ValueError("endpoint.time_h must be finite")
|
|
82
|
+
if not math.isfinite(float(self.tolerance_h)) or float(self.tolerance_h) < 0.0:
|
|
83
|
+
raise ValueError("endpoint.tolerance_h must be finite and non-negative")
|
|
84
|
+
if self.mode == "exact" and float(self.tolerance_h) != 0.0:
|
|
85
|
+
raise ValueError("endpoint.tolerance_h must be zero when mode is 'exact'")
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_mapping(cls, value: object) -> EndpointSelection:
|
|
89
|
+
payload = _exact_fields(
|
|
90
|
+
value,
|
|
91
|
+
context="temporal_reduction.selection",
|
|
92
|
+
required={"kind", "time_basis", "time_h", "mode", "tolerance_h"},
|
|
93
|
+
)
|
|
94
|
+
if payload["kind"] != "endpoint":
|
|
95
|
+
raise ValueError("temporal_reduction.selection.kind must be 'endpoint'")
|
|
96
|
+
return cls(
|
|
97
|
+
time_basis=_choice(
|
|
98
|
+
payload["time_basis"],
|
|
99
|
+
context="temporal_reduction.selection.time_basis",
|
|
100
|
+
allowed={"absolute", "event_relative"},
|
|
101
|
+
), # type: ignore[arg-type]
|
|
102
|
+
time_h=_finite(payload["time_h"], context="temporal_reduction.selection.time_h"),
|
|
103
|
+
mode=_choice(
|
|
104
|
+
payload["mode"],
|
|
105
|
+
context="temporal_reduction.selection.mode",
|
|
106
|
+
allowed={"exact", "nearest"},
|
|
107
|
+
), # type: ignore[arg-type]
|
|
108
|
+
tolerance_h=_finite(
|
|
109
|
+
payload["tolerance_h"],
|
|
110
|
+
context="temporal_reduction.selection.tolerance_h",
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def to_mapping(self) -> dict[str, object]:
|
|
115
|
+
return {
|
|
116
|
+
"kind": "endpoint",
|
|
117
|
+
"time_basis": self.time_basis,
|
|
118
|
+
"time_h": self.time_h,
|
|
119
|
+
"mode": self.mode,
|
|
120
|
+
"tolerance_h": self.tolerance_h,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class IntervalSelection:
|
|
126
|
+
"""One closed recorded or event-relative interval."""
|
|
127
|
+
|
|
128
|
+
time_basis: TimeBasis
|
|
129
|
+
start_h: float
|
|
130
|
+
end_h: float
|
|
131
|
+
boundary: IntervalBoundary = "inclusive"
|
|
132
|
+
|
|
133
|
+
def __post_init__(self) -> None:
|
|
134
|
+
if self.time_basis not in {"absolute", "event_relative"}:
|
|
135
|
+
raise ValueError("interval.time_basis must be 'absolute' or 'event_relative'")
|
|
136
|
+
if self.boundary != "inclusive":
|
|
137
|
+
raise ValueError("interval.boundary must be 'inclusive'")
|
|
138
|
+
if not math.isfinite(float(self.start_h)) or not math.isfinite(float(self.end_h)):
|
|
139
|
+
raise ValueError("interval bounds must be finite")
|
|
140
|
+
if float(self.end_h) <= float(self.start_h):
|
|
141
|
+
raise ValueError("interval requires start_h < end_h")
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_mapping(cls, value: object) -> IntervalSelection:
|
|
145
|
+
payload = _exact_fields(
|
|
146
|
+
value,
|
|
147
|
+
context="temporal_reduction.selection",
|
|
148
|
+
required={"kind", "time_basis", "start_h", "end_h", "boundary"},
|
|
149
|
+
)
|
|
150
|
+
if payload["kind"] != "interval":
|
|
151
|
+
raise ValueError("temporal_reduction.selection.kind must be 'interval'")
|
|
152
|
+
return cls(
|
|
153
|
+
time_basis=_choice(
|
|
154
|
+
payload["time_basis"],
|
|
155
|
+
context="temporal_reduction.selection.time_basis",
|
|
156
|
+
allowed={"absolute", "event_relative"},
|
|
157
|
+
), # type: ignore[arg-type]
|
|
158
|
+
start_h=_finite(payload["start_h"], context="temporal_reduction.selection.start_h"),
|
|
159
|
+
end_h=_finite(payload["end_h"], context="temporal_reduction.selection.end_h"),
|
|
160
|
+
boundary=_choice(
|
|
161
|
+
payload["boundary"],
|
|
162
|
+
context="temporal_reduction.selection.boundary",
|
|
163
|
+
allowed={"inclusive"},
|
|
164
|
+
), # type: ignore[arg-type]
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def to_mapping(self) -> dict[str, object]:
|
|
168
|
+
return {
|
|
169
|
+
"kind": "interval",
|
|
170
|
+
"time_basis": self.time_basis,
|
|
171
|
+
"start_h": self.start_h,
|
|
172
|
+
"end_h": self.end_h,
|
|
173
|
+
"boundary": self.boundary,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
type TemporalSelection = EndpointSelection | IntervalSelection
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def parse_temporal_selection(value: object) -> TemporalSelection:
|
|
181
|
+
payload = _mapping(value, context="temporal_reduction.selection")
|
|
182
|
+
kind = payload.get("kind")
|
|
183
|
+
if kind == "endpoint":
|
|
184
|
+
return EndpointSelection.from_mapping(payload)
|
|
185
|
+
if kind == "interval":
|
|
186
|
+
return IntervalSelection.from_mapping(payload)
|
|
187
|
+
raise ValueError("temporal_reduction.selection.kind must be 'endpoint' or 'interval'")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@dataclass(frozen=True)
|
|
191
|
+
class TemporalSupportPolicy:
|
|
192
|
+
"""Trace support and value-provenance requirements."""
|
|
193
|
+
|
|
194
|
+
boundary_support: BoundarySupport
|
|
195
|
+
minimum_observations: int
|
|
196
|
+
maximum_interior_gap_h: float | None
|
|
197
|
+
positive_floor: float | None
|
|
198
|
+
positive_value_scope: PositiveValueScope
|
|
199
|
+
censored_values: CensoredValuePolicy
|
|
200
|
+
|
|
201
|
+
def __post_init__(self) -> None:
|
|
202
|
+
if self.boundary_support not in {"none", "covered", "observed"}:
|
|
203
|
+
raise ValueError("support.boundary_support must be 'none', 'covered', or 'observed'")
|
|
204
|
+
if (
|
|
205
|
+
isinstance(self.minimum_observations, bool)
|
|
206
|
+
or not isinstance(self.minimum_observations, int)
|
|
207
|
+
or self.minimum_observations < 0
|
|
208
|
+
):
|
|
209
|
+
raise ValueError("support.minimum_observations must be a non-negative integer")
|
|
210
|
+
if self.maximum_interior_gap_h is not None and (
|
|
211
|
+
not math.isfinite(float(self.maximum_interior_gap_h)) or float(self.maximum_interior_gap_h) <= 0.0
|
|
212
|
+
):
|
|
213
|
+
raise ValueError("support.maximum_interior_gap_h must be null or a positive finite number")
|
|
214
|
+
if self.positive_floor is not None and (
|
|
215
|
+
not math.isfinite(float(self.positive_floor)) or float(self.positive_floor) <= 0.0
|
|
216
|
+
):
|
|
217
|
+
raise ValueError("support.positive_floor must be null or a positive finite number")
|
|
218
|
+
if self.positive_value_scope not in {"selected_support", "entire_trace"}:
|
|
219
|
+
raise ValueError("support.positive_value_scope must be 'selected_support' or 'entire_trace'")
|
|
220
|
+
if self.censored_values not in {"allow", "reject"}:
|
|
221
|
+
raise ValueError("support.censored_values must be 'allow' or 'reject'")
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
def from_mapping(cls, value: object) -> TemporalSupportPolicy:
|
|
225
|
+
payload = _exact_fields(
|
|
226
|
+
value,
|
|
227
|
+
context="temporal_reduction.support",
|
|
228
|
+
required={
|
|
229
|
+
"boundary_support",
|
|
230
|
+
"minimum_observations",
|
|
231
|
+
"maximum_interior_gap_h",
|
|
232
|
+
"positive_floor",
|
|
233
|
+
"positive_value_scope",
|
|
234
|
+
"censored_values",
|
|
235
|
+
},
|
|
236
|
+
)
|
|
237
|
+
minimum = payload["minimum_observations"]
|
|
238
|
+
if isinstance(minimum, bool) or not isinstance(minimum, int):
|
|
239
|
+
raise ValueError("temporal_reduction.support.minimum_observations must be an integer")
|
|
240
|
+
maximum_gap = payload["maximum_interior_gap_h"]
|
|
241
|
+
positive_floor = payload["positive_floor"]
|
|
242
|
+
return cls(
|
|
243
|
+
boundary_support=_choice(
|
|
244
|
+
payload["boundary_support"],
|
|
245
|
+
context="temporal_reduction.support.boundary_support",
|
|
246
|
+
allowed={"none", "covered", "observed"},
|
|
247
|
+
), # type: ignore[arg-type]
|
|
248
|
+
minimum_observations=minimum,
|
|
249
|
+
maximum_interior_gap_h=(
|
|
250
|
+
None
|
|
251
|
+
if maximum_gap is None
|
|
252
|
+
else _finite(maximum_gap, context="temporal_reduction.support.maximum_interior_gap_h")
|
|
253
|
+
),
|
|
254
|
+
positive_floor=(
|
|
255
|
+
None
|
|
256
|
+
if positive_floor is None
|
|
257
|
+
else _finite(positive_floor, context="temporal_reduction.support.positive_floor")
|
|
258
|
+
),
|
|
259
|
+
positive_value_scope=_choice(
|
|
260
|
+
payload["positive_value_scope"],
|
|
261
|
+
context="temporal_reduction.support.positive_value_scope",
|
|
262
|
+
allowed={"selected_support", "entire_trace"},
|
|
263
|
+
), # type: ignore[arg-type]
|
|
264
|
+
censored_values=_choice(
|
|
265
|
+
payload["censored_values"],
|
|
266
|
+
context="temporal_reduction.support.censored_values",
|
|
267
|
+
allowed={"allow", "reject"},
|
|
268
|
+
), # type: ignore[arg-type]
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def to_mapping(self) -> dict[str, object]:
|
|
272
|
+
return {
|
|
273
|
+
"boundary_support": self.boundary_support,
|
|
274
|
+
"minimum_observations": self.minimum_observations,
|
|
275
|
+
"maximum_interior_gap_h": self.maximum_interior_gap_h,
|
|
276
|
+
"positive_floor": self.positive_floor,
|
|
277
|
+
"positive_value_scope": self.positive_value_scope,
|
|
278
|
+
"censored_values": self.censored_values,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass(frozen=True)
|
|
283
|
+
class TemporalReductionSpec:
|
|
284
|
+
"""One selection, numerical method, value space, and support policy."""
|
|
285
|
+
|
|
286
|
+
selection: TemporalSelection
|
|
287
|
+
method: TemporalReductionMethod
|
|
288
|
+
output_space: OutputSpace
|
|
289
|
+
support: TemporalSupportPolicy
|
|
290
|
+
|
|
291
|
+
def __post_init__(self) -> None:
|
|
292
|
+
methods = {
|
|
293
|
+
"identity",
|
|
294
|
+
"observed_mean",
|
|
295
|
+
"observed_median",
|
|
296
|
+
"geometric_time_mean",
|
|
297
|
+
"integrated_linear_mean",
|
|
298
|
+
}
|
|
299
|
+
if self.method not in methods:
|
|
300
|
+
raise ValueError(f"temporal_reduction.method is unsupported: {self.method!r}")
|
|
301
|
+
if self.output_space not in {"linear", "log2"}:
|
|
302
|
+
raise ValueError("temporal_reduction.output_space must be 'linear' or 'log2'")
|
|
303
|
+
if isinstance(self.selection, EndpointSelection):
|
|
304
|
+
if self.method != "identity":
|
|
305
|
+
raise ValueError("endpoint temporal reductions require method 'identity'")
|
|
306
|
+
if self.support.boundary_support != "none" or self.support.maximum_interior_gap_h is not None:
|
|
307
|
+
raise ValueError("endpoint temporal reductions cannot declare interval boundary or gap support")
|
|
308
|
+
if self.support.minimum_observations != 1:
|
|
309
|
+
raise ValueError("endpoint temporal reductions require exactly one minimum observation")
|
|
310
|
+
elif self.method == "identity":
|
|
311
|
+
raise ValueError("interval temporal reductions cannot use method 'identity'")
|
|
312
|
+
if self.method in {"observed_mean", "observed_median"} and self.support.minimum_observations < 1:
|
|
313
|
+
raise ValueError(f"{self.method} requires at least one minimum observation")
|
|
314
|
+
if self.method in {"geometric_time_mean", "integrated_linear_mean"}:
|
|
315
|
+
if self.output_space != "log2":
|
|
316
|
+
raise ValueError(f"{self.method} requires output_space 'log2'")
|
|
317
|
+
if self.support.boundary_support == "none":
|
|
318
|
+
raise ValueError(f"{self.method} requires covered or observed interval boundaries")
|
|
319
|
+
if self.support.maximum_interior_gap_h is None or self.support.positive_floor is None:
|
|
320
|
+
raise ValueError(f"{self.method} requires maximum_interior_gap_h and positive_floor")
|
|
321
|
+
if self.output_space == "log2" and self.support.positive_floor is None:
|
|
322
|
+
raise ValueError("log2 temporal reductions require support.positive_floor")
|
|
323
|
+
|
|
324
|
+
@classmethod
|
|
325
|
+
def from_mapping(cls, value: object) -> TemporalReductionSpec:
|
|
326
|
+
payload = _exact_fields(
|
|
327
|
+
value,
|
|
328
|
+
context="temporal_reduction",
|
|
329
|
+
required={"selection", "method", "output_space", "support"},
|
|
330
|
+
)
|
|
331
|
+
return cls(
|
|
332
|
+
selection=parse_temporal_selection(payload["selection"]),
|
|
333
|
+
method=_choice(
|
|
334
|
+
payload["method"],
|
|
335
|
+
context="temporal_reduction.method",
|
|
336
|
+
allowed={
|
|
337
|
+
"identity",
|
|
338
|
+
"observed_mean",
|
|
339
|
+
"observed_median",
|
|
340
|
+
"geometric_time_mean",
|
|
341
|
+
"integrated_linear_mean",
|
|
342
|
+
},
|
|
343
|
+
), # type: ignore[arg-type]
|
|
344
|
+
output_space=_choice(
|
|
345
|
+
payload["output_space"],
|
|
346
|
+
context="temporal_reduction.output_space",
|
|
347
|
+
allowed={"linear", "log2"},
|
|
348
|
+
), # type: ignore[arg-type]
|
|
349
|
+
support=TemporalSupportPolicy.from_mapping(payload["support"]),
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
def to_mapping(self) -> dict[str, object]:
|
|
353
|
+
return {
|
|
354
|
+
"selection": self.selection.to_mapping(),
|
|
355
|
+
"method": self.method,
|
|
356
|
+
"output_space": self.output_space,
|
|
357
|
+
"support": self.support.to_mapping(),
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
__all__ = [
|
|
362
|
+
"EndpointSelection",
|
|
363
|
+
"IntervalSelection",
|
|
364
|
+
"TemporalReductionSpec",
|
|
365
|
+
"TemporalSelection",
|
|
366
|
+
"TemporalSupportPolicy",
|
|
367
|
+
"parse_temporal_selection",
|
|
368
|
+
]
|