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,22 @@
1
+ """Public compiler entry points, organized internally by assay family."""
2
+
3
+ from .compilers.cytometry import compile_cytometry_flow_panel
4
+ from .compilers.generic import compile_generic_protocol
5
+ from .compilers.logic import compile_logic_four_state_vector_collection, compile_logic_four_state_vector_screen
6
+ from .compilers.plate_reader import (
7
+ compile_plate_reader_dual_reporter_screen,
8
+ compile_plate_reader_four_state_event_window,
9
+ compile_plate_reader_growth_screen,
10
+ compile_plate_reader_single_reporter_screen,
11
+ )
12
+
13
+ __all__ = [
14
+ "compile_cytometry_flow_panel",
15
+ "compile_generic_protocol",
16
+ "compile_logic_four_state_vector_screen",
17
+ "compile_logic_four_state_vector_collection",
18
+ "compile_plate_reader_dual_reporter_screen",
19
+ "compile_plate_reader_four_state_event_window",
20
+ "compile_plate_reader_growth_screen",
21
+ "compile_plate_reader_single_reporter_screen",
22
+ ]
@@ -0,0 +1 @@
1
+ """Assay-owned protocol compiler implementations."""
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from typing import Any
5
+
6
+ from reader_workbench.errors import ConfigError
7
+ from reader_workbench.workbench.decl.model import (
8
+ PluginStepDecl,
9
+ RecipeSourceDecl,
10
+ )
11
+
12
+
13
+ def _analysis_options(protocol: Any) -> dict[str, Any]:
14
+ raw = getattr(protocol, "analysis", {}) or {}
15
+ if not isinstance(raw, dict):
16
+ raise ConfigError(f"protocol.analysis for {protocol.id!r} must be a mapping")
17
+ return dict(raw)
18
+
19
+
20
+ def _analysis_mapping(raw: dict[str, Any], *, key: str) -> dict[str, Any]:
21
+ value = raw.get(key, {})
22
+ if value is None:
23
+ return {}
24
+ if not isinstance(value, dict):
25
+ raise ConfigError(f"protocol.analysis.{key} must be a mapping")
26
+ return dict(value)
27
+
28
+
29
+ def _analysis_bool(raw: dict[str, Any], *, key: str, default: bool) -> bool:
30
+ value = raw.get(key, default)
31
+ if isinstance(value, bool):
32
+ return value
33
+ raise ConfigError(f"protocol.analysis.{key} must be true or false")
34
+
35
+
36
+ def _analysis_choice(raw: dict[str, Any], *, key: str, default: str, allowed: set[str]) -> str:
37
+ value = raw.get(key, default)
38
+ if not isinstance(value, str) or value not in allowed:
39
+ options = ", ".join(sorted(allowed))
40
+ raise ConfigError(f"protocol.analysis.{key} must be one of: {options}")
41
+ return value
42
+
43
+
44
+ def _analysis_channel(raw: dict[str, Any], *, key: str, default: str) -> str:
45
+ value = raw.get(key, default)
46
+ if not isinstance(value, str) or not value.strip():
47
+ raise ConfigError(f"protocol.analysis.{key} must be a non-empty string")
48
+ return value.strip()
49
+
50
+
51
+ def _input_mapping(protocol: Any, *, key: str) -> dict[str, Any]:
52
+ raw = getattr(protocol, "inputs", {}) or {}
53
+ if not isinstance(raw, dict):
54
+ raise ConfigError(f"protocol.inputs for {protocol.id!r} must be a mapping")
55
+ value = raw.get(key, {})
56
+ if value is None:
57
+ return {}
58
+ if not isinstance(value, dict):
59
+ raise ConfigError(f"protocol.inputs.{key} for {protocol.id!r} must be a mapping")
60
+ return dict(value)
61
+
62
+
63
+ def _config_bool(raw: dict[str, Any], *, key: str, default: bool) -> bool:
64
+ value = raw.get(key, default)
65
+ if isinstance(value, bool):
66
+ return value
67
+ raise ConfigError(f"{key} must be true or false")
68
+
69
+
70
+ def _deep_merge(*mappings: dict[str, Any]) -> dict[str, Any]:
71
+ merged: dict[str, Any] = {}
72
+ for mapping in mappings:
73
+ for key, value in (mapping or {}).items():
74
+ if key in merged and isinstance(merged[key], dict) and isinstance(value, dict):
75
+ merged[key] = _deep_merge(merged[key], value)
76
+ continue
77
+ merged[key] = deepcopy(value)
78
+ return merged
79
+
80
+
81
+ def _step(
82
+ *,
83
+ id: str,
84
+ plugin: str,
85
+ reads: dict[str, Any] | None = None,
86
+ writes: dict[str, Any] | None = None,
87
+ with_: dict[str, Any] | None = None,
88
+ source_recipe: str | None = None,
89
+ source_recipe_with: dict[str, Any] | None = None,
90
+ ) -> PluginStepDecl:
91
+ return PluginStepDecl(
92
+ id=id,
93
+ plugin=plugin,
94
+ reads=dict(reads or {}),
95
+ writes=dict(writes or {}),
96
+ with_=dict(with_ or {}),
97
+ source_recipe=(
98
+ RecipeSourceDecl(recipe=source_recipe, with_=dict(source_recipe_with or {})) if source_recipe else None
99
+ ),
100
+ )
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from reader_workbench.protocols.model import CompiledProtocolPlan
6
+ from reader_workbench.protocols.semantic_coverage import _cytometry_semantic_program
7
+ from reader_workbench.workbench.decl.model import RecordInputDecl, RecordOutputDecl, ResourceInputDecl
8
+
9
+ from .common import _deep_merge, _step
10
+
11
+ CYTOMETRY_PLOT_OUTPUTS = {"gating_diagnostic"}
12
+ CYTOMETRY_EXPORT_OUTPUTS = {
13
+ "gate_definition_table": ("cytometry_gating/gate_definition", "cytometry_gate_definition.csv"),
14
+ "sample_stats_table": ("cytometry_gating/sample_stats", "cytometry_sample_stats.csv"),
15
+ "group_stats_table": ("cytometry_gating/group_stats", "cytometry_group_stats.csv"),
16
+ "qc_table": ("cytometry_gating/qc", "cytometry_qc.csv"),
17
+ "gated_events_table": ("cytometry_gating/gated_events", "cytometry_gated_events.csv"),
18
+ }
19
+
20
+
21
+ def compile_cytometry_flow_panel(protocol: Any):
22
+ selected_plots = protocol.select_plot_outputs(allowed=CYTOMETRY_PLOT_OUTPUTS)
23
+ selected_exports = protocol.select_export_outputs(
24
+ defaults=("gate_definition_table", "sample_stats_table", "group_stats_table", "qc_table"),
25
+ allowed=set(CYTOMETRY_EXPORT_OUTPUTS),
26
+ )
27
+ pipeline = (
28
+ _step(
29
+ id="ingest_cytometer",
30
+ plugin="ingest/flow_cytometer",
31
+ writes={"df": RecordOutputDecl(record_id="ingest/df")},
32
+ ),
33
+ _step(
34
+ id="merge_metadata",
35
+ plugin="transform/sample_metadata",
36
+ reads={
37
+ "df": RecordInputDecl(record_id="ingest/df"),
38
+ "metadata": ResourceInputDecl(resource_id="metadata"),
39
+ },
40
+ writes={"df": RecordOutputDecl(record_id="merged/df")},
41
+ ),
42
+ _step(
43
+ id="cytometry_gating",
44
+ plugin="transform/cytometry_gating",
45
+ reads={"events": RecordInputDecl(record_id="merged/df")},
46
+ writes={
47
+ "gate_definition": RecordOutputDecl(record_id="cytometry_gating/gate_definition"),
48
+ "gated_events": RecordOutputDecl(record_id="cytometry_gating/gated_events"),
49
+ "sample_stats": RecordOutputDecl(record_id="cytometry_gating/sample_stats"),
50
+ "group_stats": RecordOutputDecl(record_id="cytometry_gating/group_stats"),
51
+ "qc": RecordOutputDecl(record_id="cytometry_gating/qc"),
52
+ },
53
+ ),
54
+ )
55
+ plots = tuple(
56
+ _step(
57
+ id="gating_diagnostic",
58
+ plugin="plot/cytometry_diagnostic",
59
+ reads={
60
+ "original_events": RecordInputDecl(record_id="merged/df"),
61
+ "gate_definition": RecordInputDecl(record_id="cytometry_gating/gate_definition"),
62
+ "gated_events": RecordInputDecl(record_id="cytometry_gating/gated_events"),
63
+ },
64
+ with_=protocol.plot_view_config(figure_id="gating_diagnostic"),
65
+ )
66
+ for _ in selected_plots
67
+ )
68
+ exports = tuple(_cytometry_export_output(protocol, output_id=output_id) for output_id in selected_exports)
69
+ return CompiledProtocolPlan(
70
+ pipeline=pipeline,
71
+ plots=plots,
72
+ exports=exports,
73
+ semantic_program=_cytometry_semantic_program(protocol),
74
+ )
75
+
76
+
77
+ def _cytometry_export_output(protocol: Any, *, output_id: str):
78
+ record_id, default_path = CYTOMETRY_EXPORT_OUTPUTS[output_id]
79
+ return _step(
80
+ id=output_id,
81
+ plugin="export/csv",
82
+ reads={"df": RecordInputDecl(record_id=record_id)},
83
+ with_=_deep_merge(
84
+ {"path": default_path},
85
+ protocol.export_artifact_config(artifact_id=output_id),
86
+ ),
87
+ )
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from reader_workbench.protocols.model import CompiledProtocolPlan
6
+
7
+
8
+ def compile_generic_protocol(protocol: Any):
9
+ return CompiledProtocolPlan(
10
+ pipeline=(),
11
+ plots=(),
12
+ exports=(),
13
+ semantic_program=protocol.descriptor.semantic_program(),
14
+ )
@@ -0,0 +1,245 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from reader_workbench.errors import ConfigError
6
+ from reader_workbench.protocols.model import CompiledProtocolPlan
7
+ from reader_workbench.protocols.semantic_coverage import _logic_semantic_program
8
+ from reader_workbench.workbench.decl.model import (
9
+ PluginStepDecl,
10
+ RecordCollectionInputDecl,
11
+ RecordInputDecl,
12
+ RecordOutputDecl,
13
+ )
14
+
15
+ from .common import (
16
+ _analysis_bool,
17
+ _analysis_mapping,
18
+ _analysis_options,
19
+ _deep_merge,
20
+ _step,
21
+ )
22
+ from .plate_reader import (
23
+ _configured_fold_change_enabled,
24
+ _configured_fold_change_report_times,
25
+ _configured_fold_change_target,
26
+ _configured_ingest_channels,
27
+ _plate_reader_fold_change_step,
28
+ _plate_reader_plot_output,
29
+ )
30
+ from .plate_reader_pipeline import compose_dual_reporter_pipeline
31
+
32
+ LOGIC_EXPORT_OUTPUTS = {"logic_summary_workbook"}
33
+ FOUR_STATE_VECTOR_SCREEN_LOGIC_CHANNEL = "YFP/CFP"
34
+
35
+
36
+ def compile_logic_four_state_vector_collection(protocol: Any):
37
+ resource_ids = tuple(protocol.effective_inputs().get("record_resources", ()))
38
+ pipeline = (
39
+ _step(
40
+ id="four_state_vector_collection",
41
+ plugin="transform/four_state_vector_collection",
42
+ reads={"sources": RecordCollectionInputDecl(resource_ids=resource_ids)},
43
+ writes={"vectors": RecordOutputDecl(record_id="four_state_vector_collection/vectors")},
44
+ ),
45
+ )
46
+ selected_plots = protocol.select_plot_outputs(allowed={"four_state_vector_heatmap"})
47
+ plots = tuple(
48
+ _step(
49
+ id="four_state_vector_heatmap",
50
+ plugin="plot/four_state_vector_collection",
51
+ reads={"vectors": RecordInputDecl(record_id="four_state_vector_collection/vectors")},
52
+ with_=protocol.plot_view_config(figure_id="four_state_vector_heatmap"),
53
+ )
54
+ for _ in selected_plots
55
+ )
56
+ selected_exports = protocol.select_export_outputs(defaults=("vector_table",), allowed={"vector_table"})
57
+ exports = tuple(
58
+ _step(
59
+ id="vector_table",
60
+ plugin="export/csv",
61
+ reads={"df": RecordInputDecl(record_id="four_state_vector_collection/vectors")},
62
+ with_=_deep_merge(
63
+ {"path": "four_state_vector_collection.csv"},
64
+ protocol.export_artifact_config(artifact_id="vector_table"),
65
+ ),
66
+ )
67
+ for _ in selected_exports
68
+ )
69
+ return CompiledProtocolPlan(
70
+ pipeline=pipeline,
71
+ plots=plots,
72
+ exports=exports,
73
+ semantic_program=protocol.descriptor.semantic_program(),
74
+ )
75
+
76
+
77
+ def compile_logic_four_state_vector_screen(protocol: Any):
78
+ analysis = _analysis_options(protocol)
79
+ include_fold_change = _configured_fold_change_enabled(protocol, analysis=analysis)
80
+ include_four_state_vector = _analysis_bool(analysis, key="include_four_state_vector", default=True)
81
+ preprocessing = _analysis_mapping(analysis, key="preprocessing")
82
+ blank_cfg = _analysis_mapping(preprocessing, key="blank")
83
+ overflow_cfg = _analysis_mapping(preprocessing, key="overflow")
84
+
85
+ ingest_channels = _configured_ingest_channels(protocol, required=("OD600", "CFP", "YFP"))
86
+ pipeline = list(
87
+ compose_dual_reporter_pipeline(
88
+ ingest_channels=ingest_channels,
89
+ blank_config=blank_cfg,
90
+ overflow_config=overflow_cfg,
91
+ )
92
+ )
93
+ if include_fold_change:
94
+ _configured_fold_change_target(protocol, expected=FOUR_STATE_VECTOR_SCREEN_LOGIC_CHANNEL)
95
+ _configured_fold_change_report_times(protocol)
96
+ pipeline.append(_plate_reader_fold_change_step(measurement="yfp_cfp"))
97
+ default_exports = (
98
+ ("logic_summary_workbook",)
99
+ if include_four_state_vector
100
+ and _analysis_bool(analysis, key="include_export", default=include_four_state_vector)
101
+ else ()
102
+ )
103
+ selected_exports = protocol.select_export_outputs(
104
+ defaults=default_exports,
105
+ allowed=LOGIC_EXPORT_OUTPUTS,
106
+ )
107
+ selected_plot_ids = protocol.select_plot_outputs(
108
+ allowed={
109
+ "raw_kinetics",
110
+ "endpoint_by_condition",
111
+ "endpoint_by_design",
112
+ "intensity_overview",
113
+ "logic_symmetry",
114
+ "four_state_vector_diagnostic",
115
+ "four_state_vector_heatmap",
116
+ },
117
+ )
118
+ requires_four_state_vector = (
119
+ include_four_state_vector
120
+ or "four_state_vector_diagnostic" in selected_plot_ids
121
+ or "four_state_vector_heatmap" in selected_plot_ids
122
+ or "logic_summary_workbook" in selected_exports
123
+ )
124
+ requires_logic_symmetry = "logic_symmetry" in selected_plot_ids
125
+ requires_promoted_df = requires_four_state_vector or requires_logic_symmetry
126
+ if requires_promoted_df:
127
+ pipeline.append(_four_state_vector_promote_step())
128
+ if requires_logic_symmetry:
129
+ pipeline.append(_logic_symmetry_step(protocol))
130
+ if requires_four_state_vector:
131
+ pipeline.append(_four_state_vector_step(protocol))
132
+
133
+ plots = [_logic_plot_output(protocol, output_id=output_id) for output_id in selected_plot_ids]
134
+ exports = [_logic_export_output(protocol, output_id=output_id) for output_id in selected_exports]
135
+
136
+ return CompiledProtocolPlan(
137
+ pipeline=tuple(pipeline),
138
+ plots=tuple(plots),
139
+ exports=tuple(exports),
140
+ semantic_program=_logic_semantic_program(protocol, include_four_state_vector=requires_four_state_vector),
141
+ )
142
+
143
+
144
+ def _four_state_vector_promote_step() -> PluginStepDecl:
145
+ return _step(
146
+ id="promote_to_tidy_plus_map",
147
+ plugin="validator/to_tidy_plus_map",
148
+ reads={"df": RecordInputDecl(record_id="ratio_yfp_od600/df")},
149
+ writes={"df": RecordOutputDecl(record_id="promote_to_tidy_plus_map/df")},
150
+ )
151
+
152
+
153
+ def _four_state_vector_step(protocol: Any) -> PluginStepDecl:
154
+ return _step(
155
+ id="four_state_vector",
156
+ plugin="transform/four_state_vector",
157
+ reads={"df": RecordInputDecl(record_id="promote_to_tidy_plus_map/df")},
158
+ with_={"log2_offset_delta": _four_state_vector_delta(protocol)},
159
+ writes={"vector": RecordOutputDecl(record_id="four_state_vector/vector")},
160
+ )
161
+
162
+
163
+ def _logic_symmetry_step(protocol: Any) -> PluginStepDecl:
164
+ inputs = protocol.effective_inputs()
165
+ settings = _analysis_mapping(protocol.effective_analysis(), key="logic_symmetry")
166
+ return _step(
167
+ id="logic_symmetry_summary",
168
+ plugin="transform/logic_symmetry",
169
+ reads={"df": RecordInputDecl(record_id="promote_to_tidy_plus_map/df")},
170
+ with_=_deep_merge(
171
+ {
172
+ "response_channel": FOUR_STATE_VECTOR_SCREEN_LOGIC_CHANNEL,
173
+ "design_by": inputs.get("design_by", ["design_id"]),
174
+ "state_map_ref": inputs.get("state_map_ref", "induction_logic"),
175
+ },
176
+ settings,
177
+ ),
178
+ writes={"table": RecordOutputDecl(record_id="logic_symmetry/table")},
179
+ )
180
+
181
+
182
+ def _four_state_vector_delta(protocol: Any) -> float:
183
+ settings = _analysis_mapping(_analysis_options(protocol), key="four_state_vector")
184
+ return float(settings.get("intensity_log2_offset_delta", 0.0))
185
+
186
+
187
+ def _four_state_vector_heatmap_defaults(protocol: Any) -> dict[str, Any]:
188
+ return dict(_analysis_mapping(_analysis_options(protocol), key="four_state_vector_heatmap"))
189
+
190
+
191
+ def _logic_plot_output(protocol: Any, *, output_id: str) -> PluginStepDecl:
192
+ settings = protocol.plot_view_config(figure_id=output_id)
193
+ if output_id == "four_state_vector_diagnostic":
194
+ reserved = {"growth_channel", "response_channel", "state_map_ref", "time_column"}
195
+ overridden = sorted(reserved.intersection(settings))
196
+ if overridden:
197
+ raise ConfigError(
198
+ "protocol.outputs.plots.views.four_state_vector_diagnostic cannot override compiler-owned settings: "
199
+ + ", ".join(overridden)
200
+ )
201
+ inputs = protocol.effective_inputs()
202
+ return _step(
203
+ id="four_state_vector_diagnostic",
204
+ plugin="plot/four_state_vector_diagnostic",
205
+ reads={
206
+ "df": RecordInputDecl(record_id="promote_to_tidy_plus_map/df"),
207
+ "vector": RecordInputDecl(record_id="four_state_vector/vector"),
208
+ },
209
+ with_=_deep_merge(
210
+ {
211
+ "growth_channel": "OD600",
212
+ "response_channel": FOUR_STATE_VECTOR_SCREEN_LOGIC_CHANNEL,
213
+ "state_map_ref": inputs.get("state_map_ref", "induction_logic"),
214
+ "time_column": inputs.get("time_column", "time"),
215
+ },
216
+ settings,
217
+ ),
218
+ )
219
+ if output_id == "four_state_vector_heatmap":
220
+ return _step(
221
+ id="four_state_vector_heatmap",
222
+ plugin="plot/four_state_vector_heatmap",
223
+ reads={"vector": RecordInputDecl(record_id="four_state_vector/vector")},
224
+ with_=_deep_merge(_four_state_vector_heatmap_defaults(protocol), settings),
225
+ )
226
+ if output_id == "logic_symmetry":
227
+ return _step(
228
+ id="logic_symmetry",
229
+ plugin="plot/logic_symmetry",
230
+ reads={"table": RecordInputDecl(record_id="logic_symmetry/table")},
231
+ with_=settings,
232
+ )
233
+ return _plate_reader_plot_output(protocol, output_id=output_id, measurement="yfp_cfp")
234
+
235
+
236
+ def _logic_export_output(protocol: Any, *, output_id: str) -> PluginStepDecl:
237
+ if output_id == "logic_summary_workbook":
238
+ settings = protocol.export_artifact_config(artifact_id=output_id)
239
+ return _step(
240
+ id="logic_summary_workbook",
241
+ plugin="export/xlsx",
242
+ reads={"df": RecordInputDecl(record_id="four_state_vector/vector")},
243
+ with_=_deep_merge({"path": "four_state_vector/vector.xlsx", "sheet_name": "vector"}, settings),
244
+ )
245
+ raise ConfigError(f"Unknown logic export output {output_id!r}")