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,285 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from copy import deepcopy
5
+ from dataclasses import asdict, dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any, Literal
8
+
9
+ import pandas as pd
10
+
11
+ from reader_workbench.runtime import ReaderRuntime
12
+ from reader_workbench.workbench.config import ReaderSpec
13
+ from reader_workbench.workbench.decl import WorkbenchDecl
14
+
15
+
16
+ def _json_ready(value: Any) -> Any:
17
+ if isinstance(value, Path):
18
+ return str(value)
19
+ if isinstance(value, tuple):
20
+ return [_json_ready(item) for item in value]
21
+ if isinstance(value, list):
22
+ return [_json_ready(item) for item in value]
23
+ if isinstance(value, dict):
24
+ return {str(key): _json_ready(item) for key, item in value.items()}
25
+ return deepcopy(value)
26
+
27
+
28
+ class ApiResult:
29
+ """Base for typed public results with a JSON-ready projection."""
30
+
31
+ def to_dict(self) -> dict[str, object]:
32
+ return _json_ready(asdict(self))
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class ExperimentEvidence(ApiResult):
37
+ data_class: str
38
+ data_class_reason: str
39
+ replicate_kind: str
40
+ replicate_identity_field: str | None = None
41
+
42
+
43
+ @dataclass(frozen=True)
44
+ class ExperimentIdentity(ApiResult):
45
+ id: str
46
+ title: str
47
+ lifecycle: str
48
+ protocol: str
49
+ config: str
50
+ root: str
51
+ evidence: ExperimentEvidence | None = None
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class Experiment:
56
+ """A loaded, protocol-bound experiment handle for public API operations."""
57
+
58
+ config_path: Path
59
+ _spec: ReaderSpec = field(repr=False)
60
+ _declaration: WorkbenchDecl = field(repr=False)
61
+ _runtime: ReaderRuntime = field(repr=False)
62
+
63
+ @property
64
+ def identity(self) -> ExperimentIdentity:
65
+ decl = self._declaration
66
+ semantic_evidence = decl.experiment_semantics.evidence
67
+ return ExperimentIdentity(
68
+ id=decl.experiment.id,
69
+ title=decl.experiment.title,
70
+ lifecycle=decl.experiment.lifecycle,
71
+ protocol=decl.experiment_semantics.protocol.id,
72
+ config=str(self.config_path),
73
+ root=str(decl.experiment.root),
74
+ evidence=(
75
+ ExperimentEvidence(
76
+ data_class=semantic_evidence.data_class,
77
+ data_class_reason=semantic_evidence.data_class_reason,
78
+ replicate_kind=semantic_evidence.replicate_kind,
79
+ replicate_identity_field=semantic_evidence.replicate_identity_field,
80
+ )
81
+ if semantic_evidence is not None
82
+ else None
83
+ ),
84
+ )
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class InspectionResult(ApiResult):
89
+ experiment: ExperimentIdentity
90
+ authoring: Mapping[str, object]
91
+ semantics: Mapping[str, object]
92
+ implementation: Mapping[str, object]
93
+
94
+
95
+ @dataclass(frozen=True)
96
+ class ValidationResult(ApiResult):
97
+ experiment: ExperimentIdentity
98
+ check_files: bool
99
+ status: str
100
+ summary: Mapping[str, object]
101
+ validation: Mapping[str, object]
102
+
103
+
104
+ @dataclass(frozen=True)
105
+ class PlanResult(ApiResult):
106
+ experiment: ExperimentIdentity
107
+ plan: Mapping[str, object]
108
+ compiled: Mapping[str, object]
109
+ semantics: Mapping[str, object]
110
+
111
+
112
+ @dataclass(frozen=True)
113
+ class NotebookResult(ApiResult):
114
+ experiment: ExperimentIdentity
115
+ path: str
116
+ template: str
117
+ created: bool
118
+
119
+
120
+ @dataclass(frozen=True)
121
+ class SurfaceCatalogResult(ApiResult):
122
+ experiment: ExperimentIdentity
123
+ kind: str
124
+ protocol: str
125
+ selection: Mapping[str, object]
126
+ summary: Mapping[str, object]
127
+ entries: tuple[Mapping[str, object], ...]
128
+
129
+
130
+ @dataclass(frozen=True)
131
+ class RecordCatalogResult(ApiResult):
132
+ experiment: ExperimentIdentity
133
+ catalog_exists: bool
134
+ catalog: Mapping[str, object]
135
+ include_history: bool
136
+ summary: Mapping[str, object]
137
+ entries: tuple[Mapping[str, object], ...]
138
+
139
+
140
+ @dataclass(frozen=True)
141
+ class VerificationResult(ApiResult):
142
+ experiment: ExperimentIdentity
143
+ status: str
144
+ summary: Mapping[str, object]
145
+ issues: tuple[Mapping[str, object], ...]
146
+ records: tuple[Mapping[str, object], ...]
147
+
148
+
149
+ @dataclass(frozen=True)
150
+ class SelectedSteps(ApiResult):
151
+ pipeline: tuple[str, ...]
152
+ plots: tuple[str, ...]
153
+ exports: tuple[str, ...]
154
+
155
+
156
+ @dataclass(frozen=True)
157
+ class RecordRevision(ApiResult):
158
+ record_id: str
159
+ revision: int
160
+ revision_digest: str
161
+
162
+
163
+ @dataclass(frozen=True)
164
+ class DataFrameRecordResult(ApiResult):
165
+ """One content-digest-verified, contract-validated dataframe record."""
166
+
167
+ experiment: ExperimentIdentity
168
+ record: RecordRevision
169
+ contract_id: str
170
+ content_digest: str
171
+ dataframe: pd.DataFrame = field(repr=False, compare=False)
172
+
173
+ def __post_init__(self) -> None:
174
+ object.__setattr__(self, "dataframe", self.dataframe.copy(deep=True))
175
+
176
+ def to_dict(self) -> dict[str, object]:
177
+ """Project JSON-ready metadata without serializing dataframe values."""
178
+
179
+ return {
180
+ "experiment": self.experiment.to_dict(),
181
+ "record": self.record.to_dict(),
182
+ "contract_id": self.contract_id,
183
+ "content_digest": self.content_digest,
184
+ "dataframe": {
185
+ "rows": int(self.dataframe.shape[0]),
186
+ "columns": [str(column) for column in self.dataframe.columns],
187
+ },
188
+ }
189
+
190
+
191
+ @dataclass(frozen=True)
192
+ class ArtifactFileResult(ApiResult):
193
+ """One digest-verified file from an exact file-bundle record revision."""
194
+
195
+ experiment: ExperimentIdentity
196
+ record: RecordRevision
197
+ relative_path: str
198
+ media_type: str
199
+ content_digest: str
200
+ size_bytes: int
201
+ content: bytes = field(repr=False, compare=False)
202
+
203
+ def to_dict(self) -> dict[str, object]:
204
+ """Project JSON-ready evidence without serializing artifact bytes."""
205
+
206
+ return {
207
+ "experiment": self.experiment.to_dict(),
208
+ "record": self.record.to_dict(),
209
+ "relative_path": self.relative_path,
210
+ "media_type": self.media_type,
211
+ "content_digest": self.content_digest,
212
+ "size_bytes": self.size_bytes,
213
+ }
214
+
215
+
216
+ @dataclass(frozen=True)
217
+ class RunResult(ApiResult):
218
+ experiment: ExperimentIdentity
219
+ invocation_id: str | None
220
+ provenance_epoch_id: str | None
221
+ operation: Literal["run", "plot", "export", "mixed"]
222
+ status: Literal["planned", "succeeded"]
223
+ dry_run: bool
224
+ selected_steps: SelectedSteps
225
+ produced_record_revisions: tuple[RecordRevision, ...]
226
+ ledger_path: str | None
227
+
228
+
229
+ @dataclass(frozen=True)
230
+ class PluginSummary(ApiResult):
231
+ plugin: str
232
+ key: str
233
+ category: str
234
+ domain: str
235
+ family: str
236
+ summary: str
237
+ implementation: str
238
+
239
+
240
+ @dataclass(frozen=True)
241
+ class PluginCatalogResult(ApiResult):
242
+ selection: Mapping[str, object]
243
+ summary: Mapping[str, object]
244
+ plugins: tuple[PluginSummary, ...]
245
+
246
+
247
+ @dataclass(frozen=True)
248
+ class PluginPort(ApiResult):
249
+ name: str
250
+ kind: str
251
+ optional: bool
252
+ contract: str | None
253
+ contract_surface: Mapping[str, object] | None = None
254
+
255
+
256
+ @dataclass(frozen=True)
257
+ class PluginDescriptorResult(ApiResult):
258
+ plugin: PluginSummary
259
+ config_schema: Mapping[str, object]
260
+ input_ports: tuple[PluginPort, ...]
261
+ output_ports: tuple[PluginPort, ...]
262
+
263
+
264
+ __all__ = [
265
+ "ApiResult",
266
+ "ArtifactFileResult",
267
+ "DataFrameRecordResult",
268
+ "Experiment",
269
+ "ExperimentEvidence",
270
+ "ExperimentIdentity",
271
+ "InspectionResult",
272
+ "NotebookResult",
273
+ "PlanResult",
274
+ "PluginCatalogResult",
275
+ "PluginDescriptorResult",
276
+ "PluginPort",
277
+ "PluginSummary",
278
+ "RecordCatalogResult",
279
+ "RecordRevision",
280
+ "RunResult",
281
+ "SelectedSteps",
282
+ "SurfaceCatalogResult",
283
+ "ValidationResult",
284
+ "VerificationResult",
285
+ ]
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from reader_workbench.workbench.graph import resolve_workbench
7
+ from reader_workbench.workbench.notebooks.components import (
8
+ NotebookDeliverables,
9
+ NotebookOverview,
10
+ build_notebook_deliverable_selector,
11
+ build_notebook_overview,
12
+ collect_notebook_deliverables,
13
+ render_notebook_deliverable_viewport,
14
+ render_notebook_overview_panel,
15
+ )
16
+
17
+ from .facade import open_experiment
18
+ from .models import Experiment
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class NotebookContext:
23
+ """Stable experiment context used by Reader-generated notebooks."""
24
+
25
+ experiment: Experiment
26
+ experiment_root: Path
27
+ outputs_dir: Path
28
+ notebooks_dir: Path
29
+ pipeline_step_ids: tuple[str, ...]
30
+
31
+
32
+ def load_notebook_context(start: str | Path) -> NotebookContext:
33
+ """Open the owning experiment for a generated notebook path."""
34
+
35
+ experiment = open_experiment(start)
36
+ decl = experiment._declaration
37
+ layout = decl.experiment_semantics.layout
38
+ notebooks_dir = (
39
+ layout.outputs_dir
40
+ if layout.notebooks_subdir in {"", ".", "./"}
41
+ else layout.outputs_dir / layout.notebooks_subdir
42
+ )
43
+ workbench = resolve_workbench(decl)
44
+ return NotebookContext(
45
+ experiment=experiment,
46
+ experiment_root=decl.experiment.root,
47
+ outputs_dir=layout.outputs_dir,
48
+ notebooks_dir=notebooks_dir,
49
+ pipeline_step_ids=tuple(step.id for step in workbench.pipeline),
50
+ )
51
+
52
+
53
+ __all__ = [
54
+ "NotebookContext",
55
+ "NotebookDeliverables",
56
+ "NotebookOverview",
57
+ "build_notebook_deliverable_selector",
58
+ "build_notebook_overview",
59
+ "collect_notebook_deliverables",
60
+ "load_notebook_context",
61
+ "render_notebook_deliverable_viewport",
62
+ "render_notebook_overview_panel",
63
+ ]
@@ -0,0 +1,18 @@
1
+ """Explicit dataframe-contract kernel and built-in contract catalog."""
2
+
3
+ from .builtins import BUILTIN_CONTRACTS, builtin_contract_catalog
4
+ from .catalog import ContractCatalog, OutputContractSurface
5
+ from .model import ColumnRule, ContractId, ContractToken, DataFrameContract, DType, validate_df
6
+
7
+ __all__ = [
8
+ "BUILTIN_CONTRACTS",
9
+ "ColumnRule",
10
+ "ContractCatalog",
11
+ "ContractId",
12
+ "ContractToken",
13
+ "DType",
14
+ "DataFrameContract",
15
+ "OutputContractSurface",
16
+ "builtin_contract_catalog",
17
+ "validate_df",
18
+ ]
@@ -0,0 +1,36 @@
1
+ """Explicit built-in dataframe contract catalog."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import cache
6
+
7
+ from ..catalog import ContractCatalog
8
+ from .cytometry import CONTRACTS as CYTOMETRY_CONTRACTS
9
+ from .four_state_event_window import CONTRACTS as FOUR_STATE_EVENT_WINDOW_CONTRACTS
10
+ from .generic import CONTRACTS as GENERIC_CONTRACTS
11
+ from .logic import CONTRACTS as LOGIC_CONTRACTS
12
+ from .plate_reader import CONTRACTS as PLATE_READER_CONTRACTS
13
+
14
+ BUILTIN_CONTRACTS = (
15
+ *GENERIC_CONTRACTS,
16
+ *PLATE_READER_CONTRACTS,
17
+ *FOUR_STATE_EVENT_WINDOW_CONTRACTS,
18
+ *LOGIC_CONTRACTS,
19
+ *CYTOMETRY_CONTRACTS,
20
+ )
21
+
22
+
23
+ @cache
24
+ def builtin_contract_catalog() -> ContractCatalog:
25
+ return ContractCatalog.from_contracts(BUILTIN_CONTRACTS)
26
+
27
+
28
+ __all__ = [
29
+ "BUILTIN_CONTRACTS",
30
+ "CYTOMETRY_CONTRACTS",
31
+ "GENERIC_CONTRACTS",
32
+ "LOGIC_CONTRACTS",
33
+ "PLATE_READER_CONTRACTS",
34
+ "FOUR_STATE_EVENT_WINDOW_CONTRACTS",
35
+ "builtin_contract_catalog",
36
+ ]
@@ -0,0 +1,140 @@
1
+ """Cytometry-domain dataframe contracts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..model import ColumnRule, DataFrameContract
6
+
7
+ CONTRACTS: tuple[DataFrameContract, ...] = (
8
+ DataFrameContract(
9
+ id="cytometer.channels.v1",
10
+ description="Per-sample cytometer channel metadata from FCS headers.",
11
+ columns=[
12
+ ColumnRule("sample_id", "string"),
13
+ ColumnRule("channel_index", "int"),
14
+ ColumnRule("channel_name", "string"),
15
+ ColumnRule("pns", "string", required=False, allow_nan=True),
16
+ ColumnRule("pnn", "string", required=False, allow_nan=True),
17
+ ColumnRule("pnt", "string", required=False, allow_nan=True),
18
+ ColumnRule("pnf", "string", required=False, allow_nan=True),
19
+ ColumnRule("pnl", "string", required=False, allow_nan=True),
20
+ ColumnRule("pnr", "float", required=False, allow_nan=True),
21
+ ColumnRule("pnb", "float", required=False, allow_nan=True),
22
+ ColumnRule("png", "float", required=False, allow_nan=True),
23
+ ColumnRule("pne_decades", "float", required=False, allow_nan=True),
24
+ ColumnRule("pne_zero", "float", required=False, allow_nan=True),
25
+ ],
26
+ unique_keys=[],
27
+ domain="cytometry",
28
+ kind="channel-metadata",
29
+ ),
30
+ DataFrameContract(
31
+ id="cytometry.gate_definition.v1",
32
+ description="One resolved cytometry gate, threshold, grouping, and QC definition.",
33
+ columns=[
34
+ ColumnRule("definition_id", "string"),
35
+ ColumnRule("cells_enabled", "bool"),
36
+ ColumnRule("cells_x_channel", "string"),
37
+ ColumnRule("cells_x_min", "float"),
38
+ ColumnRule("cells_x_max", "float"),
39
+ ColumnRule("cells_y_channel", "string"),
40
+ ColumnRule("cells_y_min", "float"),
41
+ ColumnRule("cells_y_max", "float"),
42
+ ColumnRule("singlets_enabled", "bool"),
43
+ ColumnRule("singlet_x_channel", "string"),
44
+ ColumnRule("singlet_y_channel", "string"),
45
+ ColumnRule("singlet_ratio_min", "float"),
46
+ ColumnRule("singlet_ratio_max", "float"),
47
+ ColumnRule("fluorescence_channel", "string"),
48
+ ColumnRule("threshold_mode", "string", allowed_values=("manual", "from_control_quantile")),
49
+ ColumnRule("threshold_value", "float"),
50
+ ColumnRule("threshold_group_column", "string", allow_nan=True),
51
+ ColumnRule("threshold_control_value", "string", allow_nan=True),
52
+ ColumnRule("threshold_quantile", "float", allow_nan=True),
53
+ ColumnRule("group_column", "string", allow_nan=True),
54
+ ColumnRule("minimum_final_events", "int", nonnegative=True),
55
+ ColumnRule("minimum_final_percent", "float", nonnegative=True),
56
+ ColumnRule("maximum_nonpositive_percent", "float", nonnegative=True),
57
+ ColumnRule("nonpositive_scope", "string", allowed_values=("all_events", "gated_events")),
58
+ ],
59
+ unique_keys=[["definition_id"]],
60
+ domain="cytometry",
61
+ kind="gate-definition",
62
+ ),
63
+ DataFrameContract(
64
+ id="cytometry.gated_events.v1",
65
+ description="Event-wide cytometry rows that passed the resolved sequential gates.",
66
+ columns=[
67
+ ColumnRule("sample_id", "string"),
68
+ ColumnRule("event_index", "int", nonnegative=True),
69
+ ],
70
+ unique_keys=[["sample_id", "event_index"]],
71
+ domain="cytometry",
72
+ kind="gated-events",
73
+ ),
74
+ DataFrameContract(
75
+ id="cytometry.sample_stats.v1",
76
+ description="Per-sample cytometry gate counts and fluorescence summaries.",
77
+ columns=[
78
+ ColumnRule("sample_id", "string"),
79
+ ColumnRule("group_column", "string", allow_nan=True),
80
+ ColumnRule("group_value", "string", allow_nan=True),
81
+ ColumnRule("n_total_events", "int", nonnegative=True),
82
+ ColumnRule("n_cells_gate", "int", nonnegative=True),
83
+ ColumnRule("n_singlets", "int", nonnegative=True),
84
+ ColumnRule("pct_cells", "float", nonnegative=True),
85
+ ColumnRule("pct_singlets_of_cells", "float", nonnegative=True),
86
+ ColumnRule("pct_final", "float", nonnegative=True),
87
+ ColumnRule("fluor_median", "float", allow_nan=True),
88
+ ColumnRule("fluor_mean", "float", allow_nan=True),
89
+ ColumnRule("fluor_geomean", "float", allow_nan=True),
90
+ ColumnRule("fluor_p90", "float", allow_nan=True),
91
+ ColumnRule("fluor_p99", "float", allow_nan=True),
92
+ ColumnRule("pct_positive", "float", nonnegative=True, allow_nan=True),
93
+ ColumnRule("fluorescence_channel", "string"),
94
+ ColumnRule("threshold_value", "float"),
95
+ ],
96
+ unique_keys=[["sample_id"]],
97
+ domain="cytometry",
98
+ kind="sample-statistics",
99
+ ),
100
+ DataFrameContract(
101
+ id="cytometry.group_stats.v1",
102
+ description="Explicitly grouped cytometry sample summaries; empty when grouping is disabled.",
103
+ columns=[
104
+ ColumnRule("group_column", "string"),
105
+ ColumnRule("group_value", "string"),
106
+ ColumnRule("n_samples", "int", nonnegative=True),
107
+ ColumnRule("fluor_median_mean", "float", allow_nan=True),
108
+ ColumnRule("fluor_median_std", "float", allow_nan=True),
109
+ ColumnRule("fluor_geomean_mean", "float", allow_nan=True),
110
+ ColumnRule("pct_positive_mean", "float", nonnegative=True, allow_nan=True),
111
+ ],
112
+ unique_keys=[["group_column", "group_value"]],
113
+ domain="cytometry",
114
+ kind="group-statistics",
115
+ ),
116
+ DataFrameContract(
117
+ id="cytometry.qc.v1",
118
+ description="Per-sample cytometry QC evaluated against explicit retention and signal policies.",
119
+ columns=[
120
+ ColumnRule("sample_id", "string"),
121
+ ColumnRule("n_total_events", "int", nonnegative=True),
122
+ ColumnRule("n_cells_gate", "int", nonnegative=True),
123
+ ColumnRule("n_singlets", "int", nonnegative=True),
124
+ ColumnRule("pct_final", "float", nonnegative=True),
125
+ ColumnRule("pct_nonpositive", "float", nonnegative=True),
126
+ ColumnRule("minimum_final_events", "int", nonnegative=True),
127
+ ColumnRule("minimum_final_percent", "float", nonnegative=True),
128
+ ColumnRule("maximum_nonpositive_percent", "float", nonnegative=True),
129
+ ColumnRule("nonpositive_scope", "string", allowed_values=("all_events", "gated_events")),
130
+ ColumnRule("passes_final_events", "bool"),
131
+ ColumnRule("passes_final_percent", "bool"),
132
+ ColumnRule("passes_nonpositive", "bool"),
133
+ ColumnRule("qc_pass", "bool"),
134
+ ColumnRule("qc_status", "string", allowed_values=("pass", "fail")),
135
+ ],
136
+ unique_keys=[["sample_id"]],
137
+ domain="cytometry",
138
+ kind="quality-control",
139
+ ),
140
+ )