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,234 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from reader_workbench.protocols.model import ProtocolSemanticExecution, ProtocolSemanticProgram
6
+
7
+
8
+ def _semantic_program(
9
+ protocol: Any,
10
+ *,
11
+ overrides: dict[str, ProtocolSemanticExecution],
12
+ active_profile: str | None = None,
13
+ ) -> ProtocolSemanticProgram:
14
+ return protocol.semantic_program(active_profile=active_profile, execution_overrides=overrides)
15
+
16
+
17
+ def _plate_reader_semantic_program(
18
+ protocol: Any,
19
+ *,
20
+ include_crosstalk_pairs: bool,
21
+ include_fold_change: bool,
22
+ ) -> ProtocolSemanticProgram:
23
+ active_profile = _dual_reporter_semantic_profile(
24
+ include_fold_change=include_fold_change,
25
+ include_crosstalk_pairs=include_crosstalk_pairs,
26
+ )
27
+ overrides: dict[str, ProtocolSemanticExecution] = {
28
+ "OD": ProtocolSemanticExecution(
29
+ status="compiled",
30
+ step_ids=("ingest",),
31
+ plugin_ids=("ingest/synergy_h1",),
32
+ record_ids=("ingest/df",),
33
+ note="Raw OD600 values are materialized on the ingest dataframe.",
34
+ ),
35
+ }
36
+ overrides.update(
37
+ {
38
+ "CFP": ProtocolSemanticExecution(
39
+ status="compiled",
40
+ step_ids=("ingest",),
41
+ plugin_ids=("ingest/synergy_h1",),
42
+ record_ids=("ingest/df",),
43
+ note="Raw CFP values are materialized on the ingest dataframe.",
44
+ ),
45
+ "YFP": ProtocolSemanticExecution(
46
+ status="compiled",
47
+ step_ids=("ingest",),
48
+ plugin_ids=("ingest/synergy_h1",),
49
+ record_ids=("ingest/df",),
50
+ note="Raw YFP values are materialized on the ingest dataframe.",
51
+ ),
52
+ "CFP_OD": ProtocolSemanticExecution(
53
+ status="compiled",
54
+ step_ids=("ratio_cfp_od600",),
55
+ plugin_ids=("transform/ratio",),
56
+ record_ids=("ratio_cfp_od600/df",),
57
+ note="The CFP/OD600 support channel is materialized as a ratio step output.",
58
+ ),
59
+ "YFP_OD": ProtocolSemanticExecution(
60
+ status="compiled",
61
+ step_ids=("ratio_yfp_od600",),
62
+ plugin_ids=("transform/ratio",),
63
+ record_ids=("ratio_yfp_od600/df",),
64
+ note="The YFP/OD600 support channel is materialized as a ratio step output.",
65
+ ),
66
+ "Ratio": ProtocolSemanticExecution(
67
+ status="compiled",
68
+ step_ids=("ratio_yfp_cfp",),
69
+ plugin_ids=("transform/ratio",),
70
+ record_ids=("ratio_yfp_cfp/df",),
71
+ note="The primary YFP/CFP ratio is materialized as a ratio step output.",
72
+ ),
73
+ }
74
+ )
75
+ if include_fold_change:
76
+ fold_change_step_id = "fold_change__yfp_over_cfp"
77
+ fold_change_record_id = "fold_change__yfp_over_cfp/table"
78
+ fold_change_note = "Nearest-time fold-change summaries are materialized from the primary ratio channel."
79
+ overrides.update(
80
+ {
81
+ "FC": ProtocolSemanticExecution(
82
+ status="compiled",
83
+ step_ids=(fold_change_step_id,),
84
+ plugin_ids=("transform/fold_change",),
85
+ record_ids=(fold_change_record_id,),
86
+ note=fold_change_note,
87
+ ),
88
+ "log2FC": ProtocolSemanticExecution(
89
+ status="compiled",
90
+ step_ids=(fold_change_step_id,),
91
+ plugin_ids=("transform/fold_change",),
92
+ record_ids=(fold_change_record_id,),
93
+ note=fold_change_note,
94
+ ),
95
+ }
96
+ )
97
+ if include_crosstalk_pairs:
98
+ overrides["ranking"] = ProtocolSemanticExecution(
99
+ status="compiled",
100
+ step_ids=("crosstalk_pairs",),
101
+ plugin_ids=("transform/crosstalk_pairs",),
102
+ record_ids=("crosstalk_pairs/table",),
103
+ config_paths=("protocol.analysis.crosstalk_pairs",),
104
+ note="When crosstalk pair analysis is enabled, pair selection is compiled from fold-change output.",
105
+ )
106
+ return _semantic_program(protocol, overrides=overrides, active_profile=active_profile)
107
+
108
+
109
+ def _plate_reader_single_reporter_semantic_program(
110
+ protocol: Any,
111
+ *,
112
+ reporter_channel: str,
113
+ normalizer_channel: str,
114
+ include_fold_change: bool,
115
+ ) -> ProtocolSemanticProgram:
116
+ ratio_label = _single_reporter_ratio_label(
117
+ reporter_channel=reporter_channel,
118
+ normalizer_channel=normalizer_channel,
119
+ )
120
+ ratio_note = f"The primary {ratio_label} ratio is materialized as a ratio step output."
121
+ fold_change_note = f"Nearest-time fold-change summaries are materialized from the primary {ratio_label} channel."
122
+ overrides: dict[str, ProtocolSemanticExecution] = {
123
+ "Normalizer": ProtocolSemanticExecution(
124
+ status="compiled",
125
+ step_ids=("ingest",),
126
+ plugin_ids=("ingest/synergy_h1",),
127
+ record_ids=("ingest/df",),
128
+ note=f"Raw {normalizer_channel} values are materialized on the ingest dataframe.",
129
+ ),
130
+ "Reporter": ProtocolSemanticExecution(
131
+ status="compiled",
132
+ step_ids=("ingest",),
133
+ plugin_ids=("ingest/synergy_h1",),
134
+ record_ids=("ingest/df",),
135
+ note=f"Raw {reporter_channel} values are materialized on the ingest dataframe.",
136
+ ),
137
+ "Reporter_Normalizer": ProtocolSemanticExecution(
138
+ status="compiled",
139
+ step_ids=("ratio_reporter_normalizer",),
140
+ plugin_ids=("transform/ratio",),
141
+ record_ids=("ratio_reporter_normalizer/df",),
142
+ note=ratio_note,
143
+ ),
144
+ }
145
+ if include_fold_change:
146
+ overrides.update(
147
+ {
148
+ "FC": ProtocolSemanticExecution(
149
+ status="compiled",
150
+ step_ids=("fold_change__single_reporter",),
151
+ plugin_ids=("transform/fold_change",),
152
+ record_ids=("fold_change__single_reporter/table",),
153
+ note=fold_change_note,
154
+ ),
155
+ "log2FC": ProtocolSemanticExecution(
156
+ status="compiled",
157
+ step_ids=("fold_change__single_reporter",),
158
+ plugin_ids=("transform/fold_change",),
159
+ record_ids=("fold_change__single_reporter/table",),
160
+ note=fold_change_note,
161
+ ),
162
+ }
163
+ )
164
+ return _semantic_program(
165
+ protocol, overrides=overrides, active_profile=_single_reporter_semantic_profile(include_fold_change)
166
+ )
167
+
168
+
169
+ def _plate_reader_growth_semantic_program(
170
+ protocol: Any,
171
+ *,
172
+ growth_channel: str,
173
+ ) -> ProtocolSemanticProgram:
174
+ return _semantic_program(
175
+ protocol,
176
+ overrides={
177
+ "Growth": ProtocolSemanticExecution(
178
+ status="compiled",
179
+ step_ids=("ingest",),
180
+ plugin_ids=("ingest/synergy_h1",),
181
+ record_ids=("ingest/df",),
182
+ note=f"Raw {growth_channel} values are materialized on the ingest dataframe.",
183
+ )
184
+ },
185
+ active_profile="growth_raw",
186
+ )
187
+
188
+
189
+ def _logic_semantic_program(protocol: Any, *, include_four_state_vector: bool) -> ProtocolSemanticProgram:
190
+ overrides: dict[str, ProtocolSemanticExecution] = {}
191
+ if include_four_state_vector:
192
+ vector_binding = ProtocolSemanticExecution(
193
+ status="compiled",
194
+ step_ids=("four_state_vector",),
195
+ plugin_ids=("transform/four_state_vector",),
196
+ record_ids=("four_state_vector/vector",),
197
+ config_paths=(
198
+ "protocol.inputs.reference",
199
+ "protocol.inputs.design_by",
200
+ "protocol.inputs.state_map_ref",
201
+ "protocol.inputs.time_mode",
202
+ "protocol.inputs.target_time_h",
203
+ "protocol.inputs.time_tolerance_h",
204
+ ),
205
+ note="The four-state vector transform materializes the protocol control rule, summary window, and measurement vector.",
206
+ )
207
+ overrides.update(
208
+ {
209
+ "logic_corner_map": vector_binding,
210
+ "summary_timepoint": vector_binding,
211
+ "four_state_vector": vector_binding,
212
+ }
213
+ )
214
+ return _semantic_program(protocol, overrides=overrides)
215
+
216
+
217
+ def _cytometry_semantic_program(protocol: Any) -> ProtocolSemanticProgram:
218
+ return _semantic_program(protocol, overrides={})
219
+
220
+
221
+ def _dual_reporter_semantic_profile(*, include_fold_change: bool, include_crosstalk_pairs: bool) -> str:
222
+ if include_crosstalk_pairs:
223
+ return "yfp_cfp_crosstalk"
224
+ if include_fold_change:
225
+ return "yfp_cfp_fold_change"
226
+ return "yfp_cfp_raw"
227
+
228
+
229
+ def _single_reporter_semantic_profile(include_fold_change: bool) -> str:
230
+ return "single_reporter_fold_change" if include_fold_change else "single_reporter_raw"
231
+
232
+
233
+ def _single_reporter_ratio_label(*, reporter_channel: str, normalizer_channel: str) -> str:
234
+ return f"{reporter_channel}/{normalizer_channel}"
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import import_module
4
+
5
+ from .model import ReaderRuntime
6
+
7
+
8
+ def builtin_runtime() -> ReaderRuntime:
9
+ return import_module("reader_workbench.runtime.builtin").builtin_runtime()
10
+
11
+
12
+ __all__ = ["ReaderRuntime", "builtin_runtime"]
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import cache
4
+
5
+ from reader_workbench.contracts import builtin_contract_catalog
6
+ from reader_workbench.plotting.mpl import ensure_mpl_cache_dir
7
+ from reader_workbench.plugins.catalog import builtin_plugin_descriptors
8
+ from reader_workbench.protocols.builtins import builtin_protocol_catalog
9
+ from reader_workbench.workbench.registry import load_plugin_catalog
10
+
11
+ from .model import ReaderRuntime
12
+
13
+
14
+ @cache
15
+ def builtin_runtime() -> ReaderRuntime:
16
+ ensure_mpl_cache_dir()
17
+ contracts = builtin_contract_catalog()
18
+ protocols = builtin_protocol_catalog()
19
+ plugins = load_plugin_catalog(
20
+ contracts=contracts,
21
+ builtin_descriptors=builtin_plugin_descriptors(),
22
+ )
23
+ return ReaderRuntime(contracts=contracts, protocols=protocols, plugins=plugins)
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from importlib import import_module
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from reader_workbench.contracts import ContractCatalog
10
+ from reader_workbench.protocols.model import BoundProtocol, ProtocolBinding, ProtocolCatalog
11
+ from reader_workbench.workbench.records.store import RecordStore
12
+ from reader_workbench.workbench.registry import Registry
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class ReaderRuntime:
17
+ """Single composed runtime for contracts, protocols, plugins, and record stores."""
18
+
19
+ contracts: ContractCatalog
20
+ protocols: ProtocolCatalog
21
+ plugins: Registry
22
+
23
+ def bind_protocol(self, binding: ProtocolBinding) -> BoundProtocol:
24
+ return self.protocols.bind(binding)
25
+
26
+ def record_store(
27
+ self,
28
+ outputs_dir: Path,
29
+ *,
30
+ plots_subdir: str | None = "plots",
31
+ exports_subdir: str | None = "exports",
32
+ experiment_root: Path | None = None,
33
+ create: bool = True,
34
+ ) -> RecordStore:
35
+ return import_module("reader_workbench.workbench.records.store").RecordStore(
36
+ outputs_dir,
37
+ contracts=self.contracts,
38
+ plots_subdir=plots_subdir,
39
+ exports_subdir=exports_subdir,
40
+ experiment_root=experiment_root,
41
+ create=create,
42
+ )
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+
5
+ _EXPORTS = {
6
+ "graph": {
7
+ "FileRef",
8
+ "InputRef",
9
+ "OutputRef",
10
+ "PluginStep",
11
+ "ProvenanceInput",
12
+ "RecipeSource",
13
+ "RecordRef",
14
+ "ResourceRef",
15
+ "Workbench",
16
+ "ensure_unique_workbench_ids",
17
+ "input_ref_display",
18
+ "input_ref_from_dict",
19
+ "input_ref_to_dict",
20
+ "materialize_workbench",
21
+ "output_ref_display",
22
+ "output_ref_from_dict",
23
+ "output_ref_to_dict",
24
+ "provenance_input_from_dict",
25
+ "provenance_input_to_dict",
26
+ "resolve_workbench",
27
+ "select_workbench_specs",
28
+ },
29
+ "ontology": {
30
+ "PluginCategory",
31
+ "PluginSemantics",
32
+ "WorkbenchItemKind",
33
+ "WorkbenchPluginStepKind",
34
+ "WorkbenchProducerKind",
35
+ "WorkbenchRecordKind",
36
+ "WorkbenchSurfaceSemantics",
37
+ "get_workbench_surface_semantics",
38
+ },
39
+ "records": {
40
+ "DataFrameArtifactRecord",
41
+ "FileBundleRecord",
42
+ "RecordProducer",
43
+ "RecordRecipeSource",
44
+ "RecordStore",
45
+ "WorkbenchRecord",
46
+ "record_from_dict",
47
+ "record_paths",
48
+ "record_to_dict",
49
+ },
50
+ }
51
+
52
+ __all__ = tuple(sorted({name for names in _EXPORTS.values() for name in names}))
53
+
54
+
55
+ def __getattr__(name: str):
56
+ for module_name, names in _EXPORTS.items():
57
+ if name in names:
58
+ module = importlib.import_module(f"reader_workbench.workbench.{module_name}")
59
+ return getattr(module, name)
60
+ raise AttributeError(name)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+
5
+ _EXPORTS = {
6
+ "types": {
7
+ "AssetCatalog",
8
+ "AssetDescriptor",
9
+ "build_plugin_asset",
10
+ "plugin_category_from_id",
11
+ },
12
+ }
13
+
14
+ __all__ = tuple(sorted({name for names in _EXPORTS.values() for name in names}))
15
+
16
+
17
+ def __getattr__(name: str):
18
+ for module_name, names in _EXPORTS.items():
19
+ if name in names:
20
+ module = importlib.import_module(f"reader_workbench.workbench.assets.{module_name}")
21
+ return getattr(module, name)
22
+ raise AttributeError(name)
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, cast
5
+
6
+ from reader_workbench.errors import ConfigError
7
+ from reader_workbench.workbench.ontology import (
8
+ PluginCategory,
9
+ PluginDomain,
10
+ PluginSemantics,
11
+ validate_plugin_domain,
12
+ )
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class AssetDescriptor:
17
+ name: str
18
+ domain: PluginDomain
19
+ family: str
20
+ summary: str
21
+ plugin_cls: type[Any]
22
+ tags: tuple[str, ...] = ()
23
+
24
+ def __post_init__(self) -> None:
25
+ object.__setattr__(self, "domain", validate_plugin_domain(self.domain))
26
+ if not isinstance(self.name, str) or not self.name.strip():
27
+ raise ValueError("AssetDescriptor.name must be a non-empty string.")
28
+ plugin_category_from_id(self.name)
29
+ if not isinstance(self.plugin_cls, type):
30
+ raise ValueError(f"Plugin asset {self.name!r} must declare a plugin class.")
31
+
32
+ @property
33
+ def plugin(self) -> str:
34
+ return self.name
35
+
36
+ @property
37
+ def plugin_id(self) -> str:
38
+ return self.plugin
39
+
40
+ @property
41
+ def key(self) -> str:
42
+ return self.name.split("/", 1)[1] if "/" in self.name else self.name
43
+
44
+ @property
45
+ def category(self) -> PluginCategory:
46
+ return plugin_category_from_id(self.name)
47
+
48
+ @property
49
+ def cls(self) -> type[Any]:
50
+ return self.plugin_cls
51
+
52
+
53
+ class AssetCatalog:
54
+ def __init__(self, descriptors: list[AssetDescriptor]):
55
+ self._descriptors = tuple(sorted(descriptors, key=lambda item: (item.domain, item.family, item.name)))
56
+ by_name: dict[str, AssetDescriptor] = {}
57
+ for item in self._descriptors:
58
+ if item.name in by_name:
59
+ raise ConfigError(f"Duplicate plugin {item.name!r}.")
60
+ by_name[item.name] = item
61
+ self._by_name = by_name
62
+
63
+ def all(self) -> tuple[AssetDescriptor, ...]:
64
+ return self._descriptors
65
+
66
+ def filter(
67
+ self,
68
+ *,
69
+ category: str | None = None,
70
+ domain: str | None = None,
71
+ family: str | None = None,
72
+ tag: str | None = None,
73
+ ) -> tuple[AssetDescriptor, ...]:
74
+ return tuple(
75
+ descriptor
76
+ for descriptor in self._descriptors
77
+ if (category is None or descriptor.category == category)
78
+ and (domain is None or descriptor.domain == domain)
79
+ and (family is None or descriptor.family == family)
80
+ and (tag is None or tag in descriptor.tags)
81
+ )
82
+
83
+ def resolve(self, name: str) -> AssetDescriptor:
84
+ try:
85
+ return self._by_name[name]
86
+ except KeyError:
87
+ opts = ", ".join(sorted(item.name for item in self._descriptors))
88
+ raise ConfigError(f"Unknown plugin {name!r}. Available plugins: {opts}") from None
89
+
90
+ def list(
91
+ self,
92
+ *,
93
+ domain: str | None = None,
94
+ family: str | None = None,
95
+ ) -> list[tuple[str, str]]:
96
+ items = self.filter(domain=domain, family=family)
97
+ return [(item.name, item.summary) for item in items]
98
+
99
+
100
+ def plugin_category_from_id(plugin_id: str) -> PluginCategory:
101
+ if not isinstance(plugin_id, str) or "/" not in plugin_id:
102
+ raise ValueError(f"Plugin id must be 'category/key', got {plugin_id!r}")
103
+ category = plugin_id.split("/", 1)[0]
104
+ if category not in {"ingest", "transform", "plot", "export", "validator"}:
105
+ raise ValueError(f"Unknown plugin category {category!r} in plugin id {plugin_id!r}")
106
+ return cast(PluginCategory, category)
107
+
108
+
109
+ def build_plugin_asset(*, plugin_id: str, semantics: PluginSemantics, plugin_cls: type[Any]) -> AssetDescriptor:
110
+ plugin_category_from_id(plugin_id)
111
+ return AssetDescriptor(
112
+ name=plugin_id,
113
+ domain=semantics.domain,
114
+ family=semantics.family,
115
+ summary=semantics.summary,
116
+ plugin_cls=plugin_cls,
117
+ tags=tuple(semantics.tags),
118
+ )
@@ -0,0 +1,5 @@
1
+ """Operator audits for experiment workbenches."""
2
+
3
+ from .experiments import AuditResult, audit_experiments
4
+
5
+ __all__ = ["AuditResult", "audit_experiments"]