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.
Files changed (293) hide show
  1. reader_workbench/__init__.py +22 -0
  2. reader_workbench/__main__.py +4 -0
  3. reader_workbench/_version.py +17 -0
  4. reader_workbench/api/__init__.py +74 -0
  5. reader_workbench/api/_record_reads.py +75 -0
  6. reader_workbench/api/artifacts.py +79 -0
  7. reader_workbench/api/facade.py +538 -0
  8. reader_workbench/api/models.py +285 -0
  9. reader_workbench/api/notebooks.py +63 -0
  10. reader_workbench/contracts/__init__.py +18 -0
  11. reader_workbench/contracts/builtins/__init__.py +36 -0
  12. reader_workbench/contracts/builtins/cytometry.py +140 -0
  13. reader_workbench/contracts/builtins/four_state_event_window.py +214 -0
  14. reader_workbench/contracts/builtins/generic.py +21 -0
  15. reader_workbench/contracts/builtins/logic.py +149 -0
  16. reader_workbench/contracts/builtins/plate_reader.py +47 -0
  17. reader_workbench/contracts/catalog.py +257 -0
  18. reader_workbench/contracts/model.py +109 -0
  19. reader_workbench/domains/__init__.py +1 -0
  20. reader_workbench/domains/cytometry/__init__.py +3 -0
  21. reader_workbench/domains/cytometry/analysis/__init__.py +29 -0
  22. reader_workbench/domains/cytometry/analysis/events.py +182 -0
  23. reader_workbench/domains/cytometry/analysis/gating.py +175 -0
  24. reader_workbench/domains/cytometry/analysis/workflow.py +248 -0
  25. reader_workbench/domains/cytometry/io/__init__.py +3 -0
  26. reader_workbench/domains/cytometry/io/fcs.py +135 -0
  27. reader_workbench/domains/cytometry/plots/__init__.py +5 -0
  28. reader_workbench/domains/cytometry/plots/diagnostic.py +155 -0
  29. reader_workbench/domains/logic/__init__.py +3 -0
  30. reader_workbench/domains/logic/crosstalk/__init__.py +3 -0
  31. reader_workbench/domains/logic/crosstalk/pairs.py +661 -0
  32. reader_workbench/domains/logic/four_state_vector/__init__.py +7 -0
  33. reader_workbench/domains/logic/four_state_vector/builder.py +321 -0
  34. reader_workbench/domains/logic/four_state_vector/collection/__init__.py +20 -0
  35. reader_workbench/domains/logic/four_state_vector/collection/checks.py +49 -0
  36. reader_workbench/domains/logic/four_state_vector/collection/constants.py +29 -0
  37. reader_workbench/domains/logic/four_state_vector/collection/model.py +19 -0
  38. reader_workbench/domains/logic/four_state_vector/collection/render.py +383 -0
  39. reader_workbench/domains/logic/four_state_vector/collection/sources.py +185 -0
  40. reader_workbench/domains/logic/four_state_vector/config.py +214 -0
  41. reader_workbench/domains/logic/four_state_vector/diagnostic.py +361 -0
  42. reader_workbench/domains/logic/four_state_vector/heatmap.py +86 -0
  43. reader_workbench/domains/logic/four_state_vector/math.py +191 -0
  44. reader_workbench/domains/logic/four_state_vector/reference.py +85 -0
  45. reader_workbench/domains/logic/four_state_vector/selection.py +228 -0
  46. reader_workbench/domains/logic/four_state_vector/treatment_semantics.py +51 -0
  47. reader_workbench/domains/logic/four_state_vector/validation.py +19 -0
  48. reader_workbench/domains/logic/logic_symmetry/__init__.py +3 -0
  49. reader_workbench/domains/logic/logic_symmetry/encodings.py +93 -0
  50. reader_workbench/domains/logic/logic_symmetry/extract_corners.py +192 -0
  51. reader_workbench/domains/logic/logic_symmetry/main.py +236 -0
  52. reader_workbench/domains/logic/logic_symmetry/metrics.py +96 -0
  53. reader_workbench/domains/logic/logic_symmetry/overlay.py +129 -0
  54. reader_workbench/domains/logic/logic_symmetry/prep.py +138 -0
  55. reader_workbench/domains/logic/logic_symmetry/render.py +356 -0
  56. reader_workbench/domains/logic/treatment_columns.py +42 -0
  57. reader_workbench/domains/plate_reader/__init__.py +1 -0
  58. reader_workbench/domains/plate_reader/analysis/__init__.py +14 -0
  59. reader_workbench/domains/plate_reader/analysis/fold_change.py +474 -0
  60. reader_workbench/domains/plate_reader/analysis/four_state_event_window/__init__.py +21 -0
  61. reader_workbench/domains/plate_reader/analysis/four_state_event_window/aggregation.py +191 -0
  62. reader_workbench/domains/plate_reader/analysis/four_state_event_window/contract_fields.py +50 -0
  63. reader_workbench/domains/plate_reader/analysis/four_state_event_window/contracts.py +320 -0
  64. reader_workbench/domains/plate_reader/analysis/four_state_event_window/design_dispositions.py +54 -0
  65. reader_workbench/domains/plate_reader/analysis/four_state_event_window/disposition_records.py +140 -0
  66. reader_workbench/domains/plate_reader/analysis/four_state_event_window/event_sensitivity.py +27 -0
  67. reader_workbench/domains/plate_reader/analysis/four_state_event_window/materialize.py +274 -0
  68. reader_workbench/domains/plate_reader/analysis/four_state_event_window/observation_resampling.py +97 -0
  69. reader_workbench/domains/plate_reader/analysis/four_state_event_window/reduction.py +62 -0
  70. reader_workbench/domains/plate_reader/analysis/four_state_event_window/seeds.py +15 -0
  71. reader_workbench/domains/plate_reader/analysis/four_state_event_window/sources.py +308 -0
  72. reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusion_validation.py +94 -0
  73. reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusions.py +54 -0
  74. reader_workbench/domains/plate_reader/analysis/timepoints.py +76 -0
  75. reader_workbench/domains/plate_reader/io/__init__.py +6 -0
  76. reader_workbench/domains/plate_reader/io/sample_map.py +65 -0
  77. reader_workbench/domains/plate_reader/io/synergy_h1/__init__.py +6 -0
  78. reader_workbench/domains/plate_reader/io/synergy_h1/_kinetic.py +141 -0
  79. reader_workbench/domains/plate_reader/io/synergy_h1/_parser.py +295 -0
  80. reader_workbench/domains/plate_reader/io/synergy_h1/_shared.py +195 -0
  81. reader_workbench/domains/plate_reader/io/synergy_h1/_snapshot.py +130 -0
  82. reader_workbench/domains/plate_reader/ordering.py +59 -0
  83. reader_workbench/domains/plate_reader/plots/__init__.py +15 -0
  84. reader_workbench/domains/plate_reader/plots/_data.py +29 -0
  85. reader_workbench/domains/plate_reader/plots/common.py +346 -0
  86. reader_workbench/domains/plate_reader/plots/distributions.py +324 -0
  87. reader_workbench/domains/plate_reader/plots/dual_reporter_triptych.py +525 -0
  88. reader_workbench/domains/plate_reader/plots/dual_reporter_triptych_render.py +195 -0
  89. reader_workbench/domains/plate_reader/plots/four_state_event_window/__init__.py +26 -0
  90. reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic.py +295 -0
  91. reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_components.py +149 -0
  92. reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_render.py +308 -0
  93. reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_style.py +51 -0
  94. reader_workbench/domains/plate_reader/plots/four_state_event_window/schema.py +8 -0
  95. reader_workbench/domains/plate_reader/plots/four_state_event_window/summary.py +140 -0
  96. reader_workbench/domains/plate_reader/plots/grouping.py +53 -0
  97. reader_workbench/domains/plate_reader/plots/panels/__init__.py +12 -0
  98. reader_workbench/domains/plate_reader/plots/panels/snapshot.py +161 -0
  99. reader_workbench/domains/plate_reader/plots/panels/snapshot_data.py +91 -0
  100. reader_workbench/domains/plate_reader/plots/panels/time_series.py +296 -0
  101. reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic.py +435 -0
  102. reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic_render.py +300 -0
  103. reader_workbench/domains/plate_reader/plots/snapshot_barplot/__init__.py +315 -0
  104. reader_workbench/domains/plate_reader/plots/snapshot_barplot/planning.py +168 -0
  105. reader_workbench/domains/plate_reader/plots/snapshot_heatmap/__init__.py +205 -0
  106. reader_workbench/domains/plate_reader/plots/snapshot_heatmap/inputs.py +103 -0
  107. reader_workbench/domains/plate_reader/plots/time_series.py +317 -0
  108. reader_workbench/domains/plate_reader/plots/ts_and_snap/__init__.py +479 -0
  109. reader_workbench/domains/plate_reader/plots/ts_and_snap/planning.py +283 -0
  110. reader_workbench/domains/time_series/__init__.py +29 -0
  111. reader_workbench/domains/time_series/aggregation.py +60 -0
  112. reader_workbench/domains/time_series/contracts.py +368 -0
  113. reader_workbench/domains/time_series/reduction.py +395 -0
  114. reader_workbench/errors.py +55 -0
  115. reader_workbench/maintenance/__init__.py +6 -0
  116. reader_workbench/maintenance/docs.py +335 -0
  117. reader_workbench/maintenance/model.py +28 -0
  118. reader_workbench/maintenance/release.py +39 -0
  119. reader_workbench/maintenance/skills.py +124 -0
  120. reader_workbench/plotting/__init__.py +20 -0
  121. reader_workbench/plotting/mpl.py +56 -0
  122. reader_workbench/plotting/sinks.py +69 -0
  123. reader_workbench/plotting/style.py +175 -0
  124. reader_workbench/plotting/utils.py +27 -0
  125. reader_workbench/plugins/__init__.py +1 -0
  126. reader_workbench/plugins/catalog.py +33 -0
  127. reader_workbench/plugins/export/__init__.py +0 -0
  128. reader_workbench/plugins/export/_paths.py +21 -0
  129. reader_workbench/plugins/export/csv.py +41 -0
  130. reader_workbench/plugins/export/xlsx.py +44 -0
  131. reader_workbench/plugins/ingest/__init__.py +0 -0
  132. reader_workbench/plugins/ingest/_discovery.py +58 -0
  133. reader_workbench/plugins/ingest/discovery_policy.py +66 -0
  134. reader_workbench/plugins/ingest/flow_cytometer.py +139 -0
  135. reader_workbench/plugins/ingest/synergy_h1.py +234 -0
  136. reader_workbench/plugins/manifests/__init__.py +1 -0
  137. reader_workbench/plugins/manifests/export.py +29 -0
  138. reader_workbench/plugins/manifests/ingest.py +29 -0
  139. reader_workbench/plugins/manifests/plot.py +161 -0
  140. reader_workbench/plugins/manifests/transform.py +172 -0
  141. reader_workbench/plugins/manifests/validator.py +18 -0
  142. reader_workbench/plugins/plot/__init__.py +0 -0
  143. reader_workbench/plugins/plot/_shared.py +55 -0
  144. reader_workbench/plugins/plot/cytometry_diagnostic.py +54 -0
  145. reader_workbench/plugins/plot/distributions.py +58 -0
  146. reader_workbench/plugins/plot/dual_reporter_triptych.py +224 -0
  147. reader_workbench/plugins/plot/four_state_event_window_diagnostic.py +133 -0
  148. reader_workbench/plugins/plot/four_state_event_window_summary.py +53 -0
  149. reader_workbench/plugins/plot/four_state_vector_collection.py +45 -0
  150. reader_workbench/plugins/plot/four_state_vector_diagnostic.py +105 -0
  151. reader_workbench/plugins/plot/four_state_vector_heatmap.py +63 -0
  152. reader_workbench/plugins/plot/logic_symmetry.py +56 -0
  153. reader_workbench/plugins/plot/single_reporter_diagnostic.py +279 -0
  154. reader_workbench/plugins/plot/snapshot_barplot.py +65 -0
  155. reader_workbench/plugins/plot/snapshot_heatmap.py +104 -0
  156. reader_workbench/plugins/plot/time_series.py +114 -0
  157. reader_workbench/plugins/plot/ts_and_snap.py +210 -0
  158. reader_workbench/plugins/transform/__init__.py +0 -0
  159. reader_workbench/plugins/transform/_four_state_vector.py +204 -0
  160. reader_workbench/plugins/transform/_labeling.py +109 -0
  161. reader_workbench/plugins/transform/alias.py +70 -0
  162. reader_workbench/plugins/transform/assay_labels.py +62 -0
  163. reader_workbench/plugins/transform/blank.py +79 -0
  164. reader_workbench/plugins/transform/crosstalk_pairs.py +180 -0
  165. reader_workbench/plugins/transform/cytometry_gating.py +120 -0
  166. reader_workbench/plugins/transform/fold_change.py +79 -0
  167. reader_workbench/plugins/transform/four_state_event_window.py +93 -0
  168. reader_workbench/plugins/transform/four_state_vector.py +62 -0
  169. reader_workbench/plugins/transform/four_state_vector_collection.py +41 -0
  170. reader_workbench/plugins/transform/logic_symmetry.py +67 -0
  171. reader_workbench/plugins/transform/outlier_filter.py +60 -0
  172. reader_workbench/plugins/transform/overflow.py +197 -0
  173. reader_workbench/plugins/transform/ratio.py +237 -0
  174. reader_workbench/plugins/transform/sample_map.py +170 -0
  175. reader_workbench/plugins/transform/sample_metadata.py +94 -0
  176. reader_workbench/plugins/validator/__init__.py +1 -0
  177. reader_workbench/plugins/validator/to_tidy_plus_map.py +155 -0
  178. reader_workbench/protocols/__init__.py +80 -0
  179. reader_workbench/protocols/_builtins_plate_reader_growth.py +179 -0
  180. reader_workbench/protocols/_builtins_plate_reader_variants.py +274 -0
  181. reader_workbench/protocols/builtins.py +1656 -0
  182. reader_workbench/protocols/compiler.py +22 -0
  183. reader_workbench/protocols/compilers/__init__.py +1 -0
  184. reader_workbench/protocols/compilers/common.py +100 -0
  185. reader_workbench/protocols/compilers/cytometry.py +87 -0
  186. reader_workbench/protocols/compilers/generic.py +14 -0
  187. reader_workbench/protocols/compilers/logic.py +245 -0
  188. reader_workbench/protocols/compilers/plate_reader.py +937 -0
  189. reader_workbench/protocols/compilers/plate_reader_pipeline.py +197 -0
  190. reader_workbench/protocols/model.py +1486 -0
  191. reader_workbench/protocols/semantic_coverage.py +234 -0
  192. reader_workbench/runtime/__init__.py +12 -0
  193. reader_workbench/runtime/builtin.py +23 -0
  194. reader_workbench/runtime/model.py +42 -0
  195. reader_workbench/workbench/__init__.py +60 -0
  196. reader_workbench/workbench/assets/__init__.py +22 -0
  197. reader_workbench/workbench/assets/types.py +118 -0
  198. reader_workbench/workbench/audit/__init__.py +5 -0
  199. reader_workbench/workbench/audit/experiments.py +307 -0
  200. reader_workbench/workbench/audit/staging.py +187 -0
  201. reader_workbench/workbench/cli/__init__.py +51 -0
  202. reader_workbench/workbench/cli/_lazy.py +9 -0
  203. reader_workbench/workbench/cli/_records_view.py +150 -0
  204. reader_workbench/workbench/cli/_surface_execution.py +443 -0
  205. reader_workbench/workbench/cli/audit.py +95 -0
  206. reader_workbench/workbench/cli/automation.py +229 -0
  207. reader_workbench/workbench/cli/demo.py +46 -0
  208. reader_workbench/workbench/cli/dop.py +91 -0
  209. reader_workbench/workbench/cli/experiments.py +635 -0
  210. reader_workbench/workbench/cli/helpers.py +232 -0
  211. reader_workbench/workbench/cli/main.py +59 -0
  212. reader_workbench/workbench/cli/maintenance.py +82 -0
  213. reader_workbench/workbench/cli/notebooks.py +260 -0
  214. reader_workbench/workbench/cli/pagination.py +117 -0
  215. reader_workbench/workbench/cli/protocols.py +336 -0
  216. reader_workbench/workbench/cli/shared.py +309 -0
  217. reader_workbench/workbench/cli/surfaces.py +534 -0
  218. reader_workbench/workbench/cli/verification.py +128 -0
  219. reader_workbench/workbench/commands.py +10 -0
  220. reader_workbench/workbench/config/__init__.py +47 -0
  221. reader_workbench/workbench/config/identity.py +13 -0
  222. reader_workbench/workbench/config/load.py +405 -0
  223. reader_workbench/workbench/config/model.py +274 -0
  224. reader_workbench/workbench/context.py +26 -0
  225. reader_workbench/workbench/decl/__init__.py +31 -0
  226. reader_workbench/workbench/decl/build.py +190 -0
  227. reader_workbench/workbench/decl/model.py +81 -0
  228. reader_workbench/workbench/dop/__init__.py +12 -0
  229. reader_workbench/workbench/dop/builtins.py +261 -0
  230. reader_workbench/workbench/dop/model.py +209 -0
  231. reader_workbench/workbench/engine/__init__.py +42 -0
  232. reader_workbench/workbench/engine/_shared.py +76 -0
  233. reader_workbench/workbench/engine/contracts.py +283 -0
  234. reader_workbench/workbench/engine/execution.py +326 -0
  235. reader_workbench/workbench/engine/file_outputs.py +260 -0
  236. reader_workbench/workbench/engine/inputs.py +161 -0
  237. reader_workbench/workbench/engine/invocations.py +507 -0
  238. reader_workbench/workbench/engine/planning.py +72 -0
  239. reader_workbench/workbench/engine/runtime.py +464 -0
  240. reader_workbench/workbench/engine/setup.py +149 -0
  241. reader_workbench/workbench/engine/validation.py +684 -0
  242. reader_workbench/workbench/experiment/__init__.py +47 -0
  243. reader_workbench/workbench/experiment/model.py +381 -0
  244. reader_workbench/workbench/experiments.py +133 -0
  245. reader_workbench/workbench/graph/__init__.py +47 -0
  246. reader_workbench/workbench/graph/nodes.py +102 -0
  247. reader_workbench/workbench/graph/normalize.py +177 -0
  248. reader_workbench/workbench/graph/refs.py +148 -0
  249. reader_workbench/workbench/input_discovery.py +19 -0
  250. reader_workbench/workbench/inspection/__init__.py +3 -0
  251. reader_workbench/workbench/inspection/catalogs.py +128 -0
  252. reader_workbench/workbench/inspection/common.py +92 -0
  253. reader_workbench/workbench/inspection/dop.py +64 -0
  254. reader_workbench/workbench/inspection/experiments.py +449 -0
  255. reader_workbench/workbench/inspection/inventory.py +68 -0
  256. reader_workbench/workbench/inspection/protocols.py +368 -0
  257. reader_workbench/workbench/inspection/readiness.py +333 -0
  258. reader_workbench/workbench/inspection/reports.py +367 -0
  259. reader_workbench/workbench/inspection/results.py +166 -0
  260. reader_workbench/workbench/inspection/runtime.py +287 -0
  261. reader_workbench/workbench/inspection/semantics.py +192 -0
  262. reader_workbench/workbench/inspection/validation.py +30 -0
  263. reader_workbench/workbench/notebooks/__init__.py +17 -0
  264. reader_workbench/workbench/notebooks/_launch_registry.py +112 -0
  265. reader_workbench/workbench/notebooks/_launch_runtime.py +104 -0
  266. reader_workbench/workbench/notebooks/components/__init__.py +21 -0
  267. reader_workbench/workbench/notebooks/components/deliverables.py +403 -0
  268. reader_workbench/workbench/notebooks/components/overview.py +119 -0
  269. reader_workbench/workbench/notebooks/eda.marimo.py.txt +153 -0
  270. reader_workbench/workbench/notebooks/launch.py +274 -0
  271. reader_workbench/workbench/notebooks/presentation.py +136 -0
  272. reader_workbench/workbench/notebooks/scaffold.py +60 -0
  273. reader_workbench/workbench/ontology.py +78 -0
  274. reader_workbench/workbench/paths.py +44 -0
  275. reader_workbench/workbench/ports/__init__.py +31 -0
  276. reader_workbench/workbench/ports/model.py +168 -0
  277. reader_workbench/workbench/records/__init__.py +44 -0
  278. reader_workbench/workbench/records/epoch.py +329 -0
  279. reader_workbench/workbench/records/evidence.py +247 -0
  280. reader_workbench/workbench/records/identity.py +87 -0
  281. reader_workbench/workbench/records/locking.py +185 -0
  282. reader_workbench/workbench/records/model.py +711 -0
  283. reader_workbench/workbench/records/sources.py +73 -0
  284. reader_workbench/workbench/records/store.py +1022 -0
  285. reader_workbench/workbench/records/verification.py +998 -0
  286. reader_workbench/workbench/registry.py +333 -0
  287. reader_workbench/workbench/spec_overrides.py +215 -0
  288. reader_workbench-1.0.0.dist-info/METADATA +91 -0
  289. reader_workbench-1.0.0.dist-info/RECORD +293 -0
  290. reader_workbench-1.0.0.dist-info/WHEEL +5 -0
  291. reader_workbench-1.0.0.dist-info/entry_points.txt +2 -0
  292. reader_workbench-1.0.0.dist-info/licenses/LICENSE +21 -0
  293. reader_workbench-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,395 @@
1
+ """Pure temporal trace reduction with explicit selection and support policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+ from typing import Literal
8
+
9
+ import numpy as np
10
+
11
+ from .contracts import EndpointSelection, IntervalSelection, TemporalReductionSpec
12
+
13
+ ValueBoundKind = Literal["exact", "lower", "upper", "indeterminate"]
14
+ _EXACT_TIME_ATOL_H = 1.0e-9
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class TemporalReductionResult:
19
+ value: float
20
+ observed_point_count: int
21
+ evaluation_point_count: int
22
+ max_interior_gap_h: float
23
+ policy_clipped_point_count: int
24
+ instrument_overflow_point_count: int
25
+ bound_kind: ValueBoundKind
26
+
27
+
28
+ def reduce_temporal_trace(
29
+ times: np.ndarray,
30
+ values: np.ndarray,
31
+ *,
32
+ spec: TemporalReductionSpec,
33
+ trace_id: str,
34
+ origin_h: float | None = None,
35
+ policy_clipped: np.ndarray | None = None,
36
+ instrument_overflow: np.ndarray | None = None,
37
+ bound_kinds: np.ndarray | None = None,
38
+ ) -> TemporalReductionResult:
39
+ """Reduce one trace without extrapolation or implicit time semantics."""
40
+
41
+ x = np.asarray(times, dtype=float)
42
+ y = np.asarray(values, dtype=float)
43
+ if x.ndim != 1 or y.ndim != 1 or x.size != y.size or x.size == 0:
44
+ raise ValueError(f"{trace_id} must contain aligned one-dimensional time and value arrays")
45
+ if not np.isfinite(x).all() or not np.isfinite(y).all():
46
+ raise ValueError(f"{trace_id} contains non-finite time or signal values")
47
+
48
+ order = np.argsort(x, kind="mergesort")
49
+ x = x[order]
50
+ y = y[order]
51
+ if len(np.unique(x)) != len(x):
52
+ raise ValueError(f"{trace_id} contains duplicate time coordinates")
53
+ policy, overflow, bounds = _value_provenance(
54
+ size=len(x),
55
+ order=order,
56
+ policy_clipped=policy_clipped,
57
+ instrument_overflow=instrument_overflow,
58
+ bound_kinds=bound_kinds,
59
+ trace_id=trace_id,
60
+ )
61
+ origin = _resolved_origin(spec=spec, origin_h=origin_h)
62
+ if isinstance(spec.selection, EndpointSelection):
63
+ return _reduce_endpoint(
64
+ x,
65
+ y,
66
+ policy=policy,
67
+ overflow=overflow,
68
+ bounds=bounds,
69
+ spec=spec,
70
+ endpoint_h=origin + spec.selection.time_h,
71
+ trace_id=trace_id,
72
+ )
73
+ return _reduce_interval(
74
+ x,
75
+ y,
76
+ policy=policy,
77
+ overflow=overflow,
78
+ bounds=bounds,
79
+ spec=spec,
80
+ window_start_h=origin + spec.selection.start_h,
81
+ window_end_h=origin + spec.selection.end_h,
82
+ trace_id=trace_id,
83
+ )
84
+
85
+
86
+ def _resolved_origin(*, spec: TemporalReductionSpec, origin_h: float | None) -> float:
87
+ selection = spec.selection
88
+ if selection.time_basis == "absolute":
89
+ if origin_h is not None and not math.isclose(float(origin_h), 0.0, abs_tol=0.0):
90
+ raise ValueError("absolute temporal reductions cannot declare a nonzero origin_h")
91
+ return 0.0
92
+ if origin_h is None or not math.isfinite(float(origin_h)):
93
+ raise ValueError("event-relative temporal reductions require a finite origin_h")
94
+ return float(origin_h)
95
+
96
+
97
+ def _reduce_endpoint(
98
+ x: np.ndarray,
99
+ y: np.ndarray,
100
+ *,
101
+ policy: np.ndarray,
102
+ overflow: np.ndarray,
103
+ bounds: np.ndarray,
104
+ spec: TemporalReductionSpec,
105
+ endpoint_h: float,
106
+ trace_id: str,
107
+ ) -> TemporalReductionResult:
108
+ selection = spec.selection
109
+ assert isinstance(selection, EndpointSelection)
110
+ delta = np.abs(x - endpoint_h)
111
+ if selection.mode == "exact":
112
+ candidates = np.flatnonzero(delta <= _EXACT_TIME_ATOL_H)
113
+ missing_message = f"{trace_id} has no exact observation at {endpoint_h:g} h"
114
+ else:
115
+ minimum = float(delta.min())
116
+ candidates = np.flatnonzero(np.isclose(delta, minimum, rtol=0.0, atol=1.0e-12))
117
+ missing_message = (
118
+ f"{trace_id} has no endpoint observation within {selection.tolerance_h:g} h of {endpoint_h:g} h"
119
+ )
120
+ if minimum > selection.tolerance_h:
121
+ candidates = np.asarray([], dtype=int)
122
+ if len(candidates) == 0:
123
+ raise ValueError(missing_message)
124
+ if len(candidates) != 1:
125
+ raise ValueError(f"{trace_id} endpoint selection is ambiguous at {endpoint_h:g} h")
126
+ index = int(candidates[0])
127
+ positive_values = y if spec.support.positive_value_scope == "entire_trace" else y[index : index + 1]
128
+ _require_positive(positive_values, spec=spec, trace_id=trace_id)
129
+ _reject_censored(
130
+ policy=policy[index : index + 1],
131
+ overflow=overflow[index : index + 1],
132
+ spec=spec,
133
+ trace_id=trace_id,
134
+ )
135
+ value = _project_output(float(y[index]), spec=spec, trace_id=trace_id)
136
+ return TemporalReductionResult(
137
+ value=value,
138
+ observed_point_count=1,
139
+ evaluation_point_count=1,
140
+ max_interior_gap_h=0.0,
141
+ policy_clipped_point_count=int(policy[index]),
142
+ instrument_overflow_point_count=int(overflow[index]),
143
+ bound_kind=combine_bound_kinds(bounds[index]),
144
+ )
145
+
146
+
147
+ def _reduce_interval(
148
+ x: np.ndarray,
149
+ y: np.ndarray,
150
+ *,
151
+ policy: np.ndarray,
152
+ overflow: np.ndarray,
153
+ bounds: np.ndarray,
154
+ spec: TemporalReductionSpec,
155
+ window_start_h: float,
156
+ window_end_h: float,
157
+ trace_id: str,
158
+ ) -> TemporalReductionResult:
159
+ selection = spec.selection
160
+ assert isinstance(selection, IntervalSelection)
161
+ if window_end_h <= window_start_h:
162
+ raise ValueError(f"{trace_id} interval resolves to a non-increasing absolute window")
163
+
164
+ support = spec.support
165
+ reduction_x = _normalized_interval_times(
166
+ x,
167
+ window_start_h=window_start_h,
168
+ window_end_h=window_end_h,
169
+ boundary_support=support.boundary_support,
170
+ trace_id=trace_id,
171
+ )
172
+ selected = (reduction_x >= window_start_h) & (reduction_x <= window_end_h)
173
+ selected_indexes = np.flatnonzero(selected)
174
+ if len(selected_indexes) < support.minimum_observations:
175
+ raise ValueError(
176
+ f"{trace_id} has {len(selected_indexes)} observations in the selected interval; "
177
+ f"at least {support.minimum_observations} are required"
178
+ )
179
+ left_index, right_index = _support_bounds(
180
+ reduction_x,
181
+ selected_indexes=selected_indexes,
182
+ window_start_h=window_start_h,
183
+ window_end_h=window_end_h,
184
+ boundary_support=support.boundary_support,
185
+ trace_id=trace_id,
186
+ )
187
+ support_slice = slice(left_index, right_index + 1)
188
+ support_x = reduction_x[support_slice]
189
+ observed_max_gap = float(np.max(np.diff(support_x))) if len(support_x) > 1 else 0.0
190
+ if (
191
+ support.maximum_interior_gap_h is not None
192
+ and observed_max_gap > support.maximum_interior_gap_h + _EXACT_TIME_ATOL_H
193
+ ):
194
+ raise ValueError(f"{trace_id} interior gap {observed_max_gap:g} h exceeds {support.maximum_interior_gap_h:g} h")
195
+ _reject_censored(
196
+ policy=policy[support_slice],
197
+ overflow=overflow[support_slice],
198
+ spec=spec,
199
+ trace_id=trace_id,
200
+ )
201
+ positive_values = y if support.positive_value_scope == "entire_trace" else y[support_slice]
202
+ _require_positive(positive_values, spec=spec, trace_id=trace_id)
203
+
204
+ method = spec.method
205
+ evaluation_count: int
206
+ with np.errstate(divide="ignore", invalid="ignore", over="ignore"):
207
+ if method == "observed_mean":
208
+ reduced = float(np.mean(y[selected]))
209
+ evaluation_count = len(selected_indexes)
210
+ elif method == "observed_median":
211
+ reduced = float(np.median(y[selected]))
212
+ evaluation_count = len(selected_indexes)
213
+ else:
214
+ inside = (reduction_x > window_start_h) & (reduction_x < window_end_h)
215
+ window_x = np.concatenate(([window_start_h], reduction_x[inside], [window_end_h]))
216
+ duration = float(window_end_h - window_start_h)
217
+ if method == "geometric_time_mean":
218
+ log_values = np.log2(y)
219
+ window_values = np.concatenate(
220
+ (
221
+ [np.interp(window_start_h, reduction_x, log_values)],
222
+ log_values[inside],
223
+ [np.interp(window_end_h, reduction_x, log_values)],
224
+ )
225
+ )
226
+ reduced = float(np.trapezoid(window_values, window_x) / duration)
227
+ elif method == "integrated_linear_mean":
228
+ window_values = np.concatenate(
229
+ (
230
+ [np.interp(window_start_h, reduction_x, y)],
231
+ y[inside],
232
+ [np.interp(window_end_h, reduction_x, y)],
233
+ )
234
+ )
235
+ reduced = float(np.log2(float(np.trapezoid(window_values, window_x) / duration)))
236
+ else: # pragma: no cover - TemporalReductionSpec validates this pair.
237
+ raise ValueError(f"unsupported interval temporal reduction method: {method!r}")
238
+ evaluation_count = len(window_x)
239
+ if method in {"observed_mean", "observed_median"}:
240
+ reduced = _project_output(reduced, spec=spec, trace_id=trace_id)
241
+ if not math.isfinite(reduced):
242
+ raise ValueError(f"{trace_id} temporal reduction produced a non-finite value")
243
+
244
+ return TemporalReductionResult(
245
+ value=reduced,
246
+ observed_point_count=len(selected_indexes),
247
+ evaluation_point_count=evaluation_count,
248
+ max_interior_gap_h=observed_max_gap,
249
+ policy_clipped_point_count=int(policy[support_slice].sum()),
250
+ instrument_overflow_point_count=int(overflow[support_slice].sum()),
251
+ bound_kind=combine_bound_kinds(*bounds[support_slice]),
252
+ )
253
+
254
+
255
+ def _normalized_interval_times(
256
+ x: np.ndarray,
257
+ *,
258
+ window_start_h: float,
259
+ window_end_h: float,
260
+ boundary_support: str,
261
+ trace_id: str,
262
+ ) -> np.ndarray:
263
+ if boundary_support != "observed":
264
+ return x
265
+ normalized = x.copy()
266
+ start_indexes = np.flatnonzero(np.isclose(x, window_start_h, rtol=0.0, atol=_EXACT_TIME_ATOL_H))
267
+ end_indexes = np.flatnonzero(np.isclose(x, window_end_h, rtol=0.0, atol=_EXACT_TIME_ATOL_H))
268
+ if len(start_indexes) > 1:
269
+ raise ValueError(f"{trace_id} has ambiguous observations at the interval start {window_start_h:g} h")
270
+ if len(end_indexes) > 1:
271
+ raise ValueError(f"{trace_id} has ambiguous observations at the interval end {window_end_h:g} h")
272
+ if len(start_indexes) == 1 and len(end_indexes) == 1 and start_indexes[0] == end_indexes[0]:
273
+ raise ValueError(f"{trace_id} has one observation matching both interval boundaries")
274
+ if len(start_indexes) == 1:
275
+ normalized[start_indexes[0]] = window_start_h
276
+ if len(end_indexes) == 1:
277
+ normalized[end_indexes[0]] = window_end_h
278
+ return normalized
279
+
280
+
281
+ def _support_bounds(
282
+ x: np.ndarray,
283
+ *,
284
+ selected_indexes: np.ndarray,
285
+ window_start_h: float,
286
+ window_end_h: float,
287
+ boundary_support: str,
288
+ trace_id: str,
289
+ ) -> tuple[int, int]:
290
+ if boundary_support == "covered":
291
+ if x[0] > window_start_h or x[-1] < window_end_h:
292
+ raise ValueError(f"{trace_id} does not cover [{window_start_h:g}, {window_end_h:g}] without extrapolation")
293
+ left_index = max(int(np.searchsorted(x, window_start_h, side="right")) - 1, 0)
294
+ right_index = min(int(np.searchsorted(x, window_end_h, side="left")), len(x) - 1)
295
+ return left_index, right_index
296
+ if boundary_support == "observed" and (
297
+ not np.any(np.isclose(x, window_start_h, rtol=0.0, atol=_EXACT_TIME_ATOL_H))
298
+ or not np.any(np.isclose(x, window_end_h, rtol=0.0, atol=_EXACT_TIME_ATOL_H))
299
+ ):
300
+ raise ValueError(
301
+ f"{trace_id} requires observed interval boundaries at {window_start_h:g} and {window_end_h:g} h"
302
+ )
303
+ if len(selected_indexes) == 0:
304
+ raise ValueError(f"{trace_id} selected interval contains no observations")
305
+ return int(selected_indexes[0]), int(selected_indexes[-1])
306
+
307
+
308
+ def _project_output(value: float, *, spec: TemporalReductionSpec, trace_id: str) -> float:
309
+ if spec.output_space == "linear":
310
+ return value
311
+ floor = spec.support.positive_floor
312
+ assert floor is not None
313
+ if value <= floor:
314
+ raise ValueError(f"{trace_id} reduced value is at or below the positive floor {floor:g}")
315
+ return float(np.log2(value))
316
+
317
+
318
+ def _require_positive(values: np.ndarray, *, spec: TemporalReductionSpec, trace_id: str) -> None:
319
+ floor = spec.support.positive_floor
320
+ if floor is not None and np.any(values <= floor):
321
+ raise ValueError(
322
+ f"{trace_id} contains values at or below the positive floor {floor:g}; "
323
+ "log-space reduction requires strictly positive values"
324
+ )
325
+
326
+
327
+ def _reject_censored(
328
+ *,
329
+ policy: np.ndarray,
330
+ overflow: np.ndarray,
331
+ spec: TemporalReductionSpec,
332
+ trace_id: str,
333
+ ) -> None:
334
+ if spec.support.censored_values == "reject" and np.any(policy | overflow):
335
+ raise ValueError(f"{trace_id} selected support contains clipped or overflowed observations")
336
+
337
+
338
+ def _value_provenance(
339
+ *,
340
+ size: int,
341
+ order: np.ndarray,
342
+ policy_clipped: np.ndarray | None,
343
+ instrument_overflow: np.ndarray | None,
344
+ bound_kinds: np.ndarray | None,
345
+ trace_id: str,
346
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
347
+ policy = np.zeros(size, dtype=bool) if policy_clipped is None else np.asarray(policy_clipped)
348
+ overflow = np.zeros(size, dtype=bool) if instrument_overflow is None else np.asarray(instrument_overflow)
349
+ bounds = np.full(size, "exact", dtype=object) if bound_kinds is None else np.asarray(bound_kinds, dtype=object)
350
+ for name, values in (("policy clipping", policy), ("instrument overflow", overflow), ("bounds", bounds)):
351
+ if values.ndim != 1 or len(values) != size:
352
+ raise ValueError(f"{trace_id} {name} provenance must align with time and value arrays")
353
+ if policy.dtype.kind != "b" or overflow.dtype.kind != "b":
354
+ raise ValueError(f"{trace_id} clipping and overflow provenance must be boolean")
355
+ allowed = {"exact", "lower", "upper", "indeterminate"}
356
+ unknown = sorted(set(map(str, bounds)) - allowed)
357
+ if unknown:
358
+ raise ValueError(f"{trace_id} contains unsupported value bounds: {unknown}")
359
+ if np.any((policy | overflow) & (bounds.astype(str) == "exact")):
360
+ raise ValueError(f"{trace_id} marks clipped or overflowed observations as exact")
361
+ return policy[order], overflow[order], bounds[order]
362
+
363
+
364
+ def combine_bound_kinds(*kinds: object) -> ValueBoundKind:
365
+ active = {str(kind) for kind in kinds} - {"exact"}
366
+ if not active:
367
+ return "exact"
368
+ if "indeterminate" in active or len(active) > 1:
369
+ return "indeterminate"
370
+ result = active.pop()
371
+ if result not in {"lower", "upper"}: # pragma: no cover - source validation owns this boundary.
372
+ raise ValueError(f"unsupported value bound kind: {result!r}")
373
+ return result # type: ignore[return-value]
374
+
375
+
376
+ def invert_bound_kind(kind: object) -> ValueBoundKind:
377
+ inverse: dict[str, ValueBoundKind] = {
378
+ "exact": "exact",
379
+ "lower": "upper",
380
+ "upper": "lower",
381
+ "indeterminate": "indeterminate",
382
+ }
383
+ try:
384
+ return inverse[str(kind)]
385
+ except KeyError as exc: # pragma: no cover - source validation owns this boundary.
386
+ raise ValueError(f"unsupported value bound kind: {kind!r}") from exc
387
+
388
+
389
+ __all__ = [
390
+ "TemporalReductionResult",
391
+ "ValueBoundKind",
392
+ "combine_bound_kinds",
393
+ "invert_bound_kind",
394
+ "reduce_temporal_trace",
395
+ ]
@@ -0,0 +1,55 @@
1
+ """Package-wide exception taxonomy."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class ReaderError(Exception): ...
7
+
8
+
9
+ class ConfigError(ReaderError): ...
10
+
11
+
12
+ class RegistryError(ReaderError): ...
13
+
14
+
15
+ class ContractError(ReaderError): ...
16
+
17
+
18
+ class RecordError(ReaderError): ...
19
+
20
+
21
+ class ProvenanceEpochChangedError(RecordError):
22
+ """A bound record-store operation observed a different provenance epoch."""
23
+
24
+
25
+ class ExecutionError(ReaderError): ...
26
+
27
+
28
+ class InvocationFinalizationError(ExecutionError):
29
+ """Execution committed records but could not confirm its terminal ledger event."""
30
+
31
+ def __init__(
32
+ self,
33
+ message: str,
34
+ *,
35
+ invocation_id: str,
36
+ produced_record_revisions: tuple[dict[str, object], ...],
37
+ ) -> None:
38
+ super().__init__(message)
39
+ self.invocation_id = invocation_id
40
+ self.produced_record_revisions = produced_record_revisions
41
+
42
+
43
+ class ParseError(ReaderError): ...
44
+
45
+
46
+ class MergeError(ReaderError): ...
47
+
48
+
49
+ class TransformError(ReaderError): ...
50
+
51
+
52
+ class PlotError(ReaderError): ...
53
+
54
+
55
+ class FourStateVectorError(ReaderError): ...
@@ -0,0 +1,6 @@
1
+ """Repository-maintenance checks owned by Reader source."""
2
+
3
+ from .docs import check_docs
4
+ from .skills import check_skills
5
+
6
+ __all__ = ["check_docs", "check_skills"]