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,1656 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import cache
4
+
5
+ from reader_workbench.workbench.input_discovery import (
6
+ DEFAULT_INPUT_EXCLUDE as DEFAULT_EXCLUDE,
7
+ )
8
+ from reader_workbench.workbench.input_discovery import (
9
+ DEFAULT_WORKBOOK_INCLUDE as DEFAULT_INCLUDE,
10
+ )
11
+
12
+ from ._builtins_plate_reader_growth import build_plate_reader_growth_protocol
13
+ from ._builtins_plate_reader_variants import build_plate_reader_variant_protocol
14
+ from .compiler import (
15
+ compile_cytometry_flow_panel,
16
+ compile_generic_protocol,
17
+ compile_logic_four_state_vector_collection,
18
+ compile_logic_four_state_vector_screen,
19
+ compile_plate_reader_dual_reporter_screen,
20
+ compile_plate_reader_four_state_event_window,
21
+ )
22
+ from .model import (
23
+ ProtocolArtifactSpec,
24
+ ProtocolCatalog,
25
+ ProtocolConfigFieldSpec,
26
+ ProtocolControlRule,
27
+ ProtocolDescriptor,
28
+ ProtocolExecutionPlan,
29
+ ProtocolFactorSpec,
30
+ ProtocolFigureSpec,
31
+ ProtocolMetricSpec,
32
+ ProtocolPlotProfileSpec,
33
+ ProtocolPluginDefaultsSpec,
34
+ ProtocolRankingSpec,
35
+ ProtocolResourceSpec,
36
+ ProtocolSemanticProfileSpec,
37
+ ProtocolWindowSpec,
38
+ binding_value,
39
+ )
40
+
41
+ _MISSING = object()
42
+ _DUAL_REPORTER_CHANNEL_MAP = {
43
+ "OD600:600": "OD600",
44
+ "CFP:433,475": "CFP",
45
+ "YFP:500,530": "YFP",
46
+ }
47
+
48
+
49
+ def _field(
50
+ key: str,
51
+ summary: str,
52
+ *,
53
+ kind: str = "mapping",
54
+ required: bool = False,
55
+ allow_none: bool = False,
56
+ choices: tuple[str, ...] = (),
57
+ children: tuple[ProtocolConfigFieldSpec, ...] = (),
58
+ allow_unknown: bool = False,
59
+ default: object = _MISSING,
60
+ example: object = _MISSING,
61
+ ) -> ProtocolConfigFieldSpec:
62
+ kwargs: dict[str, object] = {}
63
+ if default is not _MISSING:
64
+ kwargs["default"] = default
65
+ if example is not _MISSING:
66
+ kwargs["example"] = example
67
+ return ProtocolConfigFieldSpec(
68
+ key=key,
69
+ summary=summary,
70
+ kind=kind,
71
+ required=required,
72
+ allow_none=allow_none,
73
+ choices=choices,
74
+ children=children,
75
+ allow_unknown=allow_unknown,
76
+ **kwargs,
77
+ )
78
+
79
+
80
+ BUILTIN_PROTOCOLS: tuple[ProtocolDescriptor, ...] = (
81
+ ProtocolDescriptor(
82
+ protocol="workbench/generic",
83
+ domain="generic",
84
+ family="general_workbench",
85
+ summary="Generic explicit protocol binding for experiments that do not yet fit a domain-specific protocol.",
86
+ tags=("generic", "explicit_binding"),
87
+ input_fields=(),
88
+ analysis_fields=(),
89
+ factors=(
90
+ ProtocolFactorSpec(name="sample", role="sample", summary="Primary experimental unit."),
91
+ ProtocolFactorSpec(
92
+ name="replicate",
93
+ role="replicate",
94
+ summary="Source-declared replicate grouping axis.",
95
+ required=False,
96
+ ),
97
+ ProtocolFactorSpec(name="time", role="time", summary="Observation axis when present.", required=False),
98
+ ),
99
+ execution=ProtocolExecutionPlan(
100
+ compiler=compile_generic_protocol,
101
+ ),
102
+ ),
103
+ ProtocolDescriptor(
104
+ protocol="logic/four_state_vector_collection",
105
+ domain="logic",
106
+ family="record_collection",
107
+ summary="Collect four-state logic-intensity vectors from declared Reader experiments with exact revision provenance.",
108
+ tags=("logic", "collection", "records"),
109
+ input_fields=(
110
+ _field(
111
+ "record_resources",
112
+ "Ordered resources.by_id keys that identify source dataframe records.",
113
+ kind="string_list",
114
+ default=[],
115
+ ),
116
+ ),
117
+ figures=(
118
+ ProtocolFigureSpec(
119
+ id="four_state_vector_heatmap",
120
+ kind="summary",
121
+ summary="Cross-experiment heatmap over the collected vector records.",
122
+ primary=True,
123
+ ),
124
+ ),
125
+ plot_profiles=(
126
+ ProtocolPlotProfileSpec(
127
+ id="collection_overview",
128
+ summary="Primary cross-experiment vector heatmap.",
129
+ figures=("four_state_vector_heatmap",),
130
+ ),
131
+ ),
132
+ default_plot_profile="collection_overview",
133
+ artifacts=(ProtocolArtifactSpec(id="vector_table", summary="CSV table of the collected vector rows."),),
134
+ execution=ProtocolExecutionPlan(
135
+ compiler=compile_logic_four_state_vector_collection,
136
+ ),
137
+ ),
138
+ ProtocolDescriptor(
139
+ protocol="plate_reader/four_state_event_window",
140
+ domain="plate_reader",
141
+ family="event_relative_record_collection",
142
+ summary="Materialize a declared-event, four-state profile from aligned records owned by Reader experiments.",
143
+ tags=("plate_reader", "aggregate", "event", "window", "records"),
144
+ input_fields=tuple(
145
+ _field(
146
+ key,
147
+ summary,
148
+ kind="string_list",
149
+ default=[],
150
+ )
151
+ for key, summary in (
152
+ ("response_records", "Ordered record resources for the response signal."),
153
+ ("magnitude_records", "Ordered record resources for the magnitude signal."),
154
+ ("trajectory_records", "Ordered record resources for the trajectory signal."),
155
+ )
156
+ ),
157
+ analysis_fields=(
158
+ _field(
159
+ "source",
160
+ "Signal, reference, and state-value bindings.",
161
+ allow_unknown=True,
162
+ default={
163
+ "response_channel": "response",
164
+ "magnitude_channel": "magnitude",
165
+ "growth_channel": "growth",
166
+ "reference_design_id": "reference",
167
+ "state_column": "state",
168
+ "state_values": {"00": "00", "10": "10", "01": "01", "11": "11"},
169
+ "state_values_case_sensitive": True,
170
+ },
171
+ ),
172
+ _field(
173
+ "event",
174
+ "Declared event and acquisition-segment semantics.",
175
+ allow_unknown=True,
176
+ default={
177
+ "event_id": "event",
178
+ "event_kind": "declared_transition",
179
+ "segment_column": "segment",
180
+ "pre_segment_index": 0,
181
+ "post_segment_index": 1,
182
+ "estimate_method": "segment_gap_midpoint",
183
+ "declaration": "Transition between declared acquisition segments.",
184
+ },
185
+ ),
186
+ _field(
187
+ "reductions",
188
+ "Event-relative reduction definitions.",
189
+ kind="mapping_list",
190
+ default=[
191
+ {
192
+ "id": "primary",
193
+ "window_start_event_h": 0.0,
194
+ "window_end_event_h": 1.0,
195
+ "method": "geometric_time_mean",
196
+ "response_basis": "post_window",
197
+ "role": "primary",
198
+ }
199
+ ],
200
+ ),
201
+ _field(
202
+ "aggregation",
203
+ "Within-experiment observation aggregation and descriptive resampling settings.",
204
+ allow_unknown=True,
205
+ default={
206
+ "observation_stat": "median",
207
+ "descriptive_resampling_draws": 100,
208
+ "descriptive_interval_mass": 0.95,
209
+ "random_seed": 0,
210
+ },
211
+ ),
212
+ _field(
213
+ "quality",
214
+ "Trace support and value-quality requirements.",
215
+ allow_unknown=True,
216
+ default={
217
+ "positive_floor": 1e-09,
218
+ "max_interior_gap_h": 1.0,
219
+ "min_observations_per_state": 2,
220
+ },
221
+ ),
222
+ ),
223
+ figures=(
224
+ ProtocolFigureSpec(
225
+ id="four_state_event_window_summary",
226
+ kind="summary",
227
+ summary="Primary event-relative response and anchored-magnitude components.",
228
+ primary=True,
229
+ ),
230
+ ProtocolFigureSpec(
231
+ id="four_state_event_window_diagnostic",
232
+ kind="kinetics",
233
+ summary="Focused trajectories and reduced components for one source design.",
234
+ ),
235
+ ),
236
+ plot_profiles=(
237
+ ProtocolPlotProfileSpec(
238
+ id="four_state_event_window_overview",
239
+ summary="Primary event-relative summary view.",
240
+ figures=("four_state_event_window_summary",),
241
+ ),
242
+ ),
243
+ default_plot_profile="four_state_event_window_overview",
244
+ artifacts=(
245
+ ProtocolArtifactSpec(id="designs_table", summary="CSV of design-level four-state event-window summaries."),
246
+ ProtocolArtifactSpec(id="events_table", summary="CSV of source event intervals."),
247
+ ),
248
+ execution=ProtocolExecutionPlan(
249
+ compiler=compile_plate_reader_four_state_event_window,
250
+ ),
251
+ ),
252
+ ProtocolDescriptor(
253
+ protocol="plate_reader/dual_reporter_screen",
254
+ domain="plate_reader",
255
+ family="screen_analysis",
256
+ summary=(
257
+ "Dual-reporter plate-reader panel protocol with compiled ratio/fold-change summaries and "
258
+ "optional crosstalk pair selection."
259
+ ),
260
+ tags=("plate_reader", "dual_reporter", "screen", "ratio", "fold_change"),
261
+ resources=(
262
+ ProtocolResourceSpec(
263
+ id="sample_map",
264
+ path="./inputs/metadata.xlsx",
265
+ summary="Well-to-sample metadata for the plate-reader workbook.",
266
+ ),
267
+ ),
268
+ input_fields=(
269
+ _field(
270
+ "ingest",
271
+ "Plate-reader ingest settings and workbook selection.",
272
+ children=(
273
+ _field(
274
+ "mode",
275
+ "Ingest mode for Synergy H1 parsing.",
276
+ kind="string",
277
+ choices=("snapshot_only", "kinetic_only", "mixed"),
278
+ ),
279
+ _field(
280
+ "channel_map",
281
+ "Raw workbook label to canonical channel mapping; required for snapshot or mixed parsing.",
282
+ kind="mapping",
283
+ allow_unknown=True,
284
+ allow_none=True,
285
+ default=dict(_DUAL_REPORTER_CHANNEL_MAP),
286
+ ),
287
+ _field(
288
+ "sheet_names", "Optional workbook sheet names to parse.", kind="string_list", allow_none=True
289
+ ),
290
+ _field(
291
+ "time_round_decimals",
292
+ "Rounding precision for parsed time values.",
293
+ kind="integer",
294
+ allow_none=True,
295
+ default=12,
296
+ ),
297
+ _field("time_step_h", "Override time-step spacing in hours.", kind="number", allow_none=True),
298
+ _field(
299
+ "time_offset_h",
300
+ "Hours added to parsed measurement times when assay age is known outside the workbook.",
301
+ kind="number",
302
+ default=0.0,
303
+ ),
304
+ _field(
305
+ "auto_roots",
306
+ "Directories to scan for workbook auto-discovery.",
307
+ kind="string_list",
308
+ allow_none=True,
309
+ ),
310
+ _field(
311
+ "auto_include",
312
+ "Filename globs to include during auto-discovery.",
313
+ kind="string_list",
314
+ default=list(DEFAULT_INCLUDE),
315
+ ),
316
+ _field(
317
+ "auto_exclude",
318
+ "Filename globs to exclude during auto-discovery.",
319
+ kind="string_list",
320
+ default=list(DEFAULT_EXCLUDE),
321
+ ),
322
+ _field(
323
+ "auto_pick",
324
+ "Multi-file selection policy for auto-discovery.",
325
+ kind="string",
326
+ choices=("single", "latest"),
327
+ default="single",
328
+ ),
329
+ _field(
330
+ "auto_recursive",
331
+ "Recurse into child directories when discovering workbooks.",
332
+ kind="bool",
333
+ default=False,
334
+ ),
335
+ _field("print_summary", "Print an ingest summary to the log.", kind="bool", default=True),
336
+ ),
337
+ ),
338
+ _field(
339
+ "fold_change",
340
+ "Fold-change summary inputs for screen-style comparisons.",
341
+ children=(
342
+ _field(
343
+ "report_times",
344
+ "Explicit report times in hours for fold-change snapshots.",
345
+ kind="number_list",
346
+ ),
347
+ _field("time_tolerance", "Nearest-time tolerance in hours.", kind="number", default=0.51),
348
+ _field(
349
+ "observation_stat",
350
+ "Observation aggregation statistic.",
351
+ kind="string",
352
+ choices=("median", "mean"),
353
+ default="median",
354
+ ),
355
+ _field("treatment_column", "Treatment-state column name.", kind="string", default="treatment"),
356
+ _field(
357
+ "group_by",
358
+ "Grouping columns for comparison baselines.",
359
+ kind="string_list",
360
+ default=["design_id"],
361
+ ),
362
+ _field(
363
+ "expected_treatments",
364
+ "Optional complete treatment set required in every fold-change cohort.",
365
+ kind="string_list",
366
+ default=[],
367
+ ),
368
+ _field(
369
+ "use_global_baseline",
370
+ "Use one shared baseline instead of per-group baselines.",
371
+ kind="bool",
372
+ default=False,
373
+ ),
374
+ _field(
375
+ "global_baseline_value",
376
+ "Explicit global baseline label when global mode is enabled.",
377
+ kind="string",
378
+ allow_none=True,
379
+ ),
380
+ _field(
381
+ "overrides",
382
+ "Explicit baseline overrides keyed by group columns.",
383
+ kind="mapping_list",
384
+ default=[],
385
+ ),
386
+ _field("fc_column", "Output fold-change column name.", kind="string", default="FC"),
387
+ _field("log2fc_column", "Output log2 fold-change column name.", kind="string", default="log2FC"),
388
+ ),
389
+ ),
390
+ ),
391
+ analysis_fields=(
392
+ _field("include_fold_change", "Build the fold-change comparison table.", kind="bool", default=False),
393
+ _field(
394
+ "preprocessing",
395
+ "Pre-ingest cleanup policy for blanks and overflow.",
396
+ children=(
397
+ _field(
398
+ "blank",
399
+ "Blank-correction policy.",
400
+ children=(
401
+ _field(
402
+ "method",
403
+ "Blank handling strategy.",
404
+ kind="string",
405
+ choices=("disregard", "subtract"),
406
+ default="disregard",
407
+ ),
408
+ _field(
409
+ "capture_blanks",
410
+ "Emit a blanks side table for downstream QC.",
411
+ kind="bool",
412
+ default=True,
413
+ ),
414
+ ),
415
+ ),
416
+ _field(
417
+ "overflow",
418
+ "Overflow handling policy for saturated channels.",
419
+ children=(
420
+ _field(
421
+ "action",
422
+ "Overflow action.",
423
+ kind="string",
424
+ choices=("max", "drop", "nan", "none"),
425
+ default="max",
426
+ ),
427
+ _field("clip_quantile", "Quantile cap used when action=max.", kind="number", default=0.999),
428
+ _field(
429
+ "cap_strategy",
430
+ "How per-channel caps are determined when action=max.",
431
+ kind="string",
432
+ choices=("provided", "infer", "quantile"),
433
+ default="quantile",
434
+ ),
435
+ _field(
436
+ "per_channel_caps",
437
+ "Explicit per-channel caps when cap_strategy=provided.",
438
+ kind="mapping",
439
+ allow_unknown=True,
440
+ allow_none=True,
441
+ ),
442
+ _field(
443
+ "flag_column",
444
+ "Column used to mark overflowed wells before capping.",
445
+ kind="string",
446
+ default="overflow",
447
+ ),
448
+ _field(
449
+ "treat_inf_as_overflow",
450
+ "Treat infinite values as overflowed rows.",
451
+ kind="bool",
452
+ default=True,
453
+ ),
454
+ ),
455
+ ),
456
+ ),
457
+ ),
458
+ _field(
459
+ "crosstalk_pairs",
460
+ "Pair-selection analysis over fold-change tables.",
461
+ children=(
462
+ _field("enabled", "Compute crosstalk-safe pair candidates.", kind="bool", default=False),
463
+ _field("export", "Export the crosstalk pairs table when present.", kind="bool", default=False),
464
+ _field("value_column", "Value column to score.", kind="string", default="log2FC"),
465
+ _field("value_scale", "Scale of the value column.", kind="string", default="log2"),
466
+ _field("time_mode", "Time-selection mode.", kind="string", default="all"),
467
+ _field("design_column", "Design/grouping column.", kind="string", default="design_id"),
468
+ _field("treatment_column", "Treatment-state column.", kind="string", default="treatment"),
469
+ _field("mapping_mode", "Pair mapping mode.", kind="string", default="explicit"),
470
+ _field("require_self_treatment", "Require a self-treatment pair.", kind="bool", default=True),
471
+ _field("require_self_is_top1", "Require self-treatment to rank first.", kind="bool", default=True),
472
+ _field("min_self", "Minimum on-target score.", kind="number", default=1.0),
473
+ _field("max_cross", "Maximum tolerated cross-score.", kind="number", default=0.5),
474
+ _field(
475
+ "min_selectivity_delta",
476
+ "Minimum on-target minus off-target margin.",
477
+ kind="number",
478
+ default=1.0,
479
+ ),
480
+ _field(
481
+ "design_treatment_map",
482
+ "Explicit design -> self-treatment mapping when mapping_mode=explicit.",
483
+ kind="mapping",
484
+ allow_unknown=True,
485
+ ),
486
+ ),
487
+ ),
488
+ ),
489
+ factors=(
490
+ ProtocolFactorSpec(name="design_id", role="construct", summary="Primary construct or design grouping."),
491
+ ProtocolFactorSpec(name="treatment", role="condition", summary="Primary treatment or assay condition."),
492
+ ProtocolFactorSpec(name="position", role="observation", summary="Observed well or sample position."),
493
+ ProtocolFactorSpec(name="time", role="time", summary="Time after assay start."),
494
+ ProtocolFactorSpec(name="plate_id", role="plate", summary="Plate-local normalization boundary."),
495
+ ),
496
+ semantic_profiles=(
497
+ ProtocolSemanticProfileSpec(
498
+ id="yfp_cfp_raw",
499
+ family="dual_reporter_panel",
500
+ summary="Generic dual-reporter panel semantics over raw/support ratio traces.",
501
+ primary_metric="Ratio",
502
+ primary_readout="YFP / CFP",
503
+ tags=("dual_reporter", "ratio", "panel"),
504
+ ),
505
+ ProtocolSemanticProfileSpec(
506
+ id="yfp_cfp_fold_change",
507
+ family="dual_reporter_panel",
508
+ summary="Dual-reporter panel semantics with compiled fold-change summaries.",
509
+ primary_metric="log2FC",
510
+ primary_readout="YFP / CFP",
511
+ tags=("dual_reporter", "ratio", "panel", "fold_change"),
512
+ ),
513
+ ProtocolSemanticProfileSpec(
514
+ id="yfp_cfp_crosstalk",
515
+ family="dual_reporter_crosstalk",
516
+ summary="Dual-reporter panel semantics with compiled crosstalk pair selection.",
517
+ primary_metric="log2FC",
518
+ primary_readout="YFP / CFP",
519
+ tags=("dual_reporter", "ratio", "crosstalk"),
520
+ ),
521
+ ),
522
+ control_rules=(),
523
+ windows=(),
524
+ metrics=(
525
+ ProtocolMetricSpec(
526
+ id="OD",
527
+ stage="raw",
528
+ summary="Raw OD600 trace.",
529
+ formula="OD600",
530
+ profiles=(
531
+ "yfp_cfp_raw",
532
+ "yfp_cfp_fold_change",
533
+ "yfp_cfp_crosstalk",
534
+ ),
535
+ ),
536
+ ProtocolMetricSpec(
537
+ id="CFP",
538
+ stage="raw",
539
+ summary="Raw CFP trace.",
540
+ formula="CFP",
541
+ profiles=("yfp_cfp_raw", "yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
542
+ ),
543
+ ProtocolMetricSpec(
544
+ id="YFP",
545
+ stage="raw",
546
+ summary="Raw YFP trace.",
547
+ formula="YFP",
548
+ profiles=("yfp_cfp_raw", "yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
549
+ ),
550
+ ProtocolMetricSpec(
551
+ id="YFP_OD",
552
+ stage="support",
553
+ summary="Supporting YFP per biomass proxy.",
554
+ formula="YFP / OD600",
555
+ depends_on=("YFP", "OD"),
556
+ value_space="linear_ratio",
557
+ unit="ratio",
558
+ comparable_group="support_ratio_linear",
559
+ profiles=("yfp_cfp_raw", "yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
560
+ ),
561
+ ProtocolMetricSpec(
562
+ id="CFP_OD",
563
+ stage="support",
564
+ summary="Supporting CFP per biomass proxy.",
565
+ formula="CFP / OD600",
566
+ depends_on=("CFP", "OD"),
567
+ value_space="linear_ratio",
568
+ unit="ratio",
569
+ comparable_group="support_ratio_linear",
570
+ profiles=("yfp_cfp_raw", "yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
571
+ ),
572
+ ProtocolMetricSpec(
573
+ id="Ratio",
574
+ stage="derived",
575
+ summary="Primary within-well assay ratio.",
576
+ formula="YFP / CFP",
577
+ depends_on=("YFP", "CFP"),
578
+ value_space="linear_ratio",
579
+ unit="ratio",
580
+ comparable_group="primary_ratio_linear",
581
+ profiles=("yfp_cfp_raw", "yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
582
+ ),
583
+ ProtocolMetricSpec(
584
+ id="FC",
585
+ stage="summary",
586
+ summary="Nearest-time fold-change relative to the configured baseline treatment.",
587
+ formula="Ratio(t*) / baseline(Ratio)",
588
+ depends_on=("Ratio",),
589
+ value_space="fold_change_ratio",
590
+ unit="ratio",
591
+ comparable_group="fold_change_linear",
592
+ profiles=("yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
593
+ ),
594
+ ProtocolMetricSpec(
595
+ id="log2FC",
596
+ stage="summary",
597
+ summary="Log2 fold-change relative to the configured baseline treatment.",
598
+ formula="log2(FC)",
599
+ depends_on=("FC",),
600
+ value_space="log2_fold_change",
601
+ unit="log2_ratio",
602
+ comparable_group="fold_change_log2",
603
+ profiles=("yfp_cfp_fold_change", "yfp_cfp_crosstalk"),
604
+ ),
605
+ ),
606
+ effect_signs=(),
607
+ figures=(
608
+ ProtocolFigureSpec(
609
+ id="raw_kinetics",
610
+ kind="qc",
611
+ summary="Raw kinetics view over OD600 and reporter channels.",
612
+ primary=True,
613
+ ),
614
+ ProtocolFigureSpec(
615
+ id="dual_reporter_triptych",
616
+ kind="kinetics",
617
+ summary="Per-design growth and reporter-ratio kinetics with an explicitly timed endpoint summary.",
618
+ ),
619
+ ProtocolFigureSpec(
620
+ id="endpoint_by_condition",
621
+ kind="summary",
622
+ summary="Endpoint comparison grouped by treatment/condition.",
623
+ primary=True,
624
+ ),
625
+ ProtocolFigureSpec(
626
+ id="endpoint_by_design",
627
+ kind="summary",
628
+ summary="Endpoint comparison grouped by construct/design.",
629
+ primary=True,
630
+ ),
631
+ ProtocolFigureSpec(
632
+ id="state_summary",
633
+ kind="summary",
634
+ summary="2x2 state summary using alias-mapped treatment states.",
635
+ primary=True,
636
+ ),
637
+ ProtocolFigureSpec(
638
+ id="intensity_overview",
639
+ kind="kinetics",
640
+ summary="Combined time-series and endpoint view of the intensity/support channel.",
641
+ primary=True,
642
+ ),
643
+ ProtocolFigureSpec(
644
+ id="ratio_overview",
645
+ kind="kinetics",
646
+ summary="Combined time-series and endpoint view of the primary ratio channel.",
647
+ ),
648
+ ProtocolFigureSpec(
649
+ id="value_distributions",
650
+ kind="qc",
651
+ summary="Distribution view of the primary measurement channel.",
652
+ ),
653
+ ProtocolFigureSpec(
654
+ id="ratio_heatmap",
655
+ kind="summary",
656
+ summary="Endpoint heatmap over the primary ratio channel.",
657
+ ),
658
+ ProtocolFigureSpec(
659
+ id="support_heatmap",
660
+ kind="summary",
661
+ summary="Endpoint heatmap over the supporting CFP/OD600 channel.",
662
+ ),
663
+ ),
664
+ plot_profiles=(
665
+ ProtocolPlotProfileSpec(
666
+ id="screen_overview",
667
+ summary="Endpoint screen view; each selected endpoint time must be authored explicitly.",
668
+ figures=("raw_kinetics", "endpoint_by_condition", "endpoint_by_design", "intensity_overview"),
669
+ ),
670
+ ProtocolPlotProfileSpec(
671
+ id="ratio_screen",
672
+ summary="Ratio-centric screen view for dual-reporter comparisons.",
673
+ figures=("raw_kinetics", "state_summary", "ratio_overview"),
674
+ ),
675
+ ProtocolPlotProfileSpec(
676
+ id="kinetics_qc",
677
+ summary="Kinetics-first QC view with raw traces and distributions.",
678
+ figures=("raw_kinetics", "value_distributions"),
679
+ ),
680
+ ProtocolPlotProfileSpec(
681
+ id="heatmap_review",
682
+ summary="Endpoint heatmap review across primary and supporting channels.",
683
+ figures=("ratio_heatmap", "support_heatmap"),
684
+ ),
685
+ ),
686
+ default_plot_profile="kinetics_qc",
687
+ artifacts=(
688
+ ProtocolArtifactSpec(
689
+ id="crosstalk_pairs_table",
690
+ summary="CSV export of crosstalk-safe pair candidates.",
691
+ ),
692
+ ),
693
+ ranking=ProtocolRankingSpec(
694
+ primary_metric="log2FC",
695
+ direction="higher_is_better",
696
+ supporting_metrics=("FC",),
697
+ summary=(
698
+ "When crosstalk selection is enabled, rank self-pairs by compiled fold-change and the configured "
699
+ "selectivity thresholds."
700
+ ),
701
+ profiles=("yfp_cfp_crosstalk",),
702
+ ),
703
+ execution=ProtocolExecutionPlan(
704
+ plugin_defaults=(
705
+ ProtocolPluginDefaultsSpec(
706
+ plugin="ingest/synergy_h1",
707
+ summary="Dual-reporter screens parse one explicitly selected Synergy workbook in mixed mode.",
708
+ with_={
709
+ "mode": binding_value("ingest.mode", "mixed"),
710
+ "channels": binding_value("ingest.channels", ["OD600", "CFP", "YFP"]),
711
+ "channel_map": binding_value("ingest.channel_map", dict(_DUAL_REPORTER_CHANNEL_MAP)),
712
+ "sheet_names": binding_value("ingest.sheet_names", None),
713
+ "time_round_decimals": binding_value("ingest.time_round_decimals", 12),
714
+ "time_step_h": binding_value("ingest.time_step_h", None),
715
+ "time_offset_h": binding_value("ingest.time_offset_h", 0.0),
716
+ "auto_roots": binding_value("ingest.auto_roots", None),
717
+ "auto_include": binding_value("ingest.auto_include", list(DEFAULT_INCLUDE)),
718
+ "auto_exclude": binding_value("ingest.auto_exclude", list(DEFAULT_EXCLUDE)),
719
+ "auto_pick": binding_value("ingest.auto_pick", "single"),
720
+ "auto_recursive": binding_value("ingest.auto_recursive", False),
721
+ "print_summary": binding_value("ingest.print_summary", True),
722
+ },
723
+ ),
724
+ ProtocolPluginDefaultsSpec(
725
+ plugin="transform/fold_change",
726
+ summary="Shared fold-change defaults keep plate-reader comparisons and naming consistent.",
727
+ with_={
728
+ "target": binding_value("fold_change.target", "YFP/CFP"),
729
+ "report_times": binding_value("fold_change.report_times"),
730
+ "time_tolerance": binding_value("fold_change.time_tolerance", 0.51),
731
+ "observation_stat": binding_value("fold_change.observation_stat", "median"),
732
+ "treatment_column": binding_value("fold_change.treatment_column", "treatment"),
733
+ "group_by": binding_value("fold_change.group_by", ["design_id"]),
734
+ "expected_treatments": binding_value("fold_change.expected_treatments", []),
735
+ "use_global_baseline": binding_value("fold_change.use_global_baseline", False),
736
+ "global_baseline_value": binding_value("fold_change.global_baseline_value", None),
737
+ "overrides": binding_value("fold_change.overrides", []),
738
+ "fc_column": binding_value("fold_change.fc_column", "FC"),
739
+ "log2fc_column": binding_value("fold_change.log2fc_column", "log2FC"),
740
+ },
741
+ ),
742
+ ),
743
+ compiler=compile_plate_reader_dual_reporter_screen,
744
+ ),
745
+ ),
746
+ ProtocolDescriptor(
747
+ protocol="logic/four_state_vector_screen",
748
+ domain="logic",
749
+ family="logic_summary",
750
+ summary="Dual-reporter plate-reader adapter for four-state logic-intensity measurement vectors.",
751
+ tags=("logic", "four_state_vector", "screen", "dual_reporter", "plate_reader"),
752
+ resources=(
753
+ ProtocolResourceSpec(
754
+ id="sample_map",
755
+ path="./inputs/metadata.xlsx",
756
+ summary="Well-to-sample metadata for the logic-screen workbook.",
757
+ ),
758
+ ),
759
+ example_annotations={
760
+ "ordered_state_spaces": {
761
+ "induction_logic": {
762
+ "column": "treatment",
763
+ "state_order": ["00", "10", "01", "11"],
764
+ "values": {
765
+ "00": "-input-a/-input-b",
766
+ "10": "+input-a/-input-b",
767
+ "01": "-input-a/+input-b",
768
+ "11": "+input-a/+input-b",
769
+ },
770
+ "case_sensitive": True,
771
+ }
772
+ }
773
+ },
774
+ input_fields=(
775
+ _field(
776
+ "ingest",
777
+ "Plate-reader ingest settings for the logic screen.",
778
+ children=(
779
+ _field(
780
+ "mode",
781
+ "Ingest mode for Synergy H1 parsing.",
782
+ kind="string",
783
+ choices=("snapshot_only", "kinetic_only", "mixed"),
784
+ ),
785
+ _field("channels", "Ordered channel names to keep from the workbook.", kind="string_list"),
786
+ _field(
787
+ "channel_map",
788
+ "Raw workbook label to canonical channel mapping; required for snapshot or mixed parsing.",
789
+ kind="mapping",
790
+ allow_unknown=True,
791
+ allow_none=True,
792
+ default=dict(_DUAL_REPORTER_CHANNEL_MAP),
793
+ ),
794
+ _field(
795
+ "sheet_names", "Optional workbook sheet names to parse.", kind="string_list", allow_none=True
796
+ ),
797
+ _field(
798
+ "time_round_decimals",
799
+ "Rounding precision for parsed time values.",
800
+ kind="integer",
801
+ allow_none=True,
802
+ default=12,
803
+ ),
804
+ _field("time_step_h", "Override time-step spacing in hours.", kind="number", allow_none=True),
805
+ _field(
806
+ "time_offset_h",
807
+ "Hours added to parsed measurement times when assay age is known outside the workbook.",
808
+ kind="number",
809
+ default=0.0,
810
+ ),
811
+ _field(
812
+ "auto_roots",
813
+ "Directories to scan for workbook auto-discovery.",
814
+ kind="string_list",
815
+ allow_none=True,
816
+ ),
817
+ _field(
818
+ "auto_include",
819
+ "Filename globs to include during auto-discovery.",
820
+ kind="string_list",
821
+ default=list(DEFAULT_INCLUDE),
822
+ ),
823
+ _field(
824
+ "auto_exclude",
825
+ "Filename globs to exclude during auto-discovery.",
826
+ kind="string_list",
827
+ default=list(DEFAULT_EXCLUDE),
828
+ ),
829
+ _field(
830
+ "auto_pick",
831
+ "Multi-file selection policy for auto-discovery.",
832
+ kind="string",
833
+ choices=("single", "latest"),
834
+ default="single",
835
+ ),
836
+ _field(
837
+ "auto_recursive",
838
+ "Recurse into child directories when discovering workbooks.",
839
+ kind="bool",
840
+ default=False,
841
+ ),
842
+ _field("print_summary", "Print an ingest summary to the log.", kind="bool", default=True),
843
+ ),
844
+ ),
845
+ _field(
846
+ "fold_change",
847
+ "Optional fold-change summary inputs used before vector export.",
848
+ children=(
849
+ _field("target", "Primary fold-change channel.", kind="string", default="YFP/CFP"),
850
+ _field(
851
+ "report_times",
852
+ "Explicit report times in hours for fold-change snapshots.",
853
+ kind="number_list",
854
+ ),
855
+ _field("time_tolerance", "Nearest-time tolerance in hours.", kind="number", default=0.51),
856
+ _field(
857
+ "observation_stat",
858
+ "Observation aggregation statistic.",
859
+ kind="string",
860
+ choices=("median", "mean"),
861
+ default="median",
862
+ ),
863
+ _field("treatment_column", "Treatment-state column name.", kind="string", default="treatment"),
864
+ _field(
865
+ "group_by",
866
+ "Grouping columns for comparison baselines.",
867
+ kind="string_list",
868
+ default=["design_id"],
869
+ ),
870
+ _field(
871
+ "use_global_baseline",
872
+ "Use one shared baseline instead of per-group baselines.",
873
+ kind="bool",
874
+ default=True,
875
+ ),
876
+ _field(
877
+ "global_baseline_value",
878
+ "Explicit global baseline label when global mode is enabled.",
879
+ kind="string",
880
+ allow_none=True,
881
+ ),
882
+ _field(
883
+ "overrides",
884
+ "Explicit baseline overrides keyed by group columns.",
885
+ kind="mapping_list",
886
+ default=[],
887
+ ),
888
+ _field("fc_column", "Output fold-change column name.", kind="string", default="FC"),
889
+ _field("log2fc_column", "Output log2 fold-change column name.", kind="string", default="log2FC"),
890
+ ),
891
+ ),
892
+ _field(
893
+ "reference",
894
+ "Reference design and aggregation policy for vector normalization.",
895
+ children=(
896
+ _field("design_id", "Reference design id.", kind="string", default="REF"),
897
+ _field("observation_stat", "Reference observation statistic.", kind="string", default="mean"),
898
+ ),
899
+ ),
900
+ _field("design_by", "Grouping columns for logic designs.", kind="string_list", default=["design_id"]),
901
+ _field("time_column", "Column containing assay time in hours.", kind="string", default="time"),
902
+ _field(
903
+ "time_mode",
904
+ "Time-selection mode for vector extraction.",
905
+ kind="string",
906
+ choices=("nearest", "last_before", "first_after", "exact"),
907
+ default="nearest",
908
+ ),
909
+ _field("target_time_h", "Target timepoint for vector extraction.", kind="number", allow_none=True),
910
+ _field("time_tolerance_h", "Nearest-time tolerance in hours.", kind="number", default=0.5),
911
+ _field(
912
+ "state_map_ref",
913
+ "Reference to an annotations.ordered_state_spaces entry.",
914
+ kind="string",
915
+ default="induction_logic",
916
+ ),
917
+ _field(
918
+ "promote",
919
+ "Promotion settings for tidy_plus_map conversion.",
920
+ children=(
921
+ _field("synthesize_batch", "Add a synthetic batch column when missing.", kind="bool", default=True),
922
+ _field(
923
+ "drop_where_null_in",
924
+ "Drop rows with NULL in these columns before promotion.",
925
+ kind="string_list",
926
+ default=["treatment", "design_id"],
927
+ ),
928
+ ),
929
+ ),
930
+ _field(
931
+ "require_all_corners_per_design",
932
+ "Require each design to expose all logic corners before vector export.",
933
+ kind="bool",
934
+ default=True,
935
+ ),
936
+ _field(
937
+ "exclude_reference_from_output",
938
+ "Drop the reference design from the final vector output.",
939
+ kind="bool",
940
+ default=True,
941
+ ),
942
+ _field(
943
+ "carry_metadata",
944
+ "Metadata columns to carry through vector output.",
945
+ kind="string_list",
946
+ default=["sequence", "id"],
947
+ ),
948
+ ),
949
+ analysis_fields=(
950
+ _field("include_fold_change", "Build the fold-change comparison table.", kind="bool", default=False),
951
+ _field("include_four_state_vector", "Build the vector summary table.", kind="bool", default=True),
952
+ _field("include_export", "Emit the workbook export when vector is present.", kind="bool", default=True),
953
+ _field(
954
+ "logic_symmetry",
955
+ "Logic-symmetry summary settings used when that deliverable is selected.",
956
+ children=(
957
+ _field("batch_col", "Observation-batch column.", kind="string", default="batch"),
958
+ _field(
959
+ "treatment_column",
960
+ "Optional treatment column override; otherwise the ordered state space owns it.",
961
+ kind="string",
962
+ allow_none=True,
963
+ ),
964
+ _field(
965
+ "observation_stat",
966
+ "Observation aggregation statistic for each state corner.",
967
+ kind="string",
968
+ choices=("mean", "median"),
969
+ default="mean",
970
+ ),
971
+ _field(
972
+ "prep",
973
+ "Optional time-selection policy before corner aggregation.",
974
+ children=(
975
+ _field("enable", "Apply explicit time selection.", kind="bool", default=False),
976
+ _field(
977
+ "mode",
978
+ "Time-selection mode.",
979
+ kind="string",
980
+ choices=("first", "last", "median", "exact", "nearest"),
981
+ default="last",
982
+ ),
983
+ _field(
984
+ "target_time",
985
+ "Target time for exact or nearest selection.",
986
+ kind="number",
987
+ allow_none=True,
988
+ ),
989
+ _field("tolerance", "Allowed time distance.", kind="number", default=0.51),
990
+ _field(
991
+ "align_corners",
992
+ "Use one shared time anchor across all four states.",
993
+ kind="bool",
994
+ default=False,
995
+ ),
996
+ _field(
997
+ "case_sensitive_treatments",
998
+ "Optional case-sensitivity override for source state labels.",
999
+ kind="bool",
1000
+ allow_none=True,
1001
+ ),
1002
+ _field("time_column", "Column containing time values.", kind="string", default="time"),
1003
+ ),
1004
+ ),
1005
+ ),
1006
+ ),
1007
+ _field(
1008
+ "four_state_vector",
1009
+ "Four-state logic-intensity vector transform settings.",
1010
+ children=(
1011
+ _field(
1012
+ "intensity_log2_offset_delta",
1013
+ "Non-negative log2 offset applied to the intensity channel.",
1014
+ kind="number",
1015
+ default=0.0,
1016
+ ),
1017
+ ),
1018
+ ),
1019
+ _field(
1020
+ "preprocessing",
1021
+ "Pre-ingest cleanup policy for blanks and overflow.",
1022
+ children=(
1023
+ _field(
1024
+ "blank",
1025
+ "Blank-correction policy.",
1026
+ children=(
1027
+ _field(
1028
+ "method",
1029
+ "Blank handling strategy.",
1030
+ kind="string",
1031
+ choices=("disregard", "subtract"),
1032
+ default="disregard",
1033
+ ),
1034
+ _field(
1035
+ "capture_blanks",
1036
+ "Emit a blanks side table for downstream QC.",
1037
+ kind="bool",
1038
+ default=True,
1039
+ ),
1040
+ ),
1041
+ ),
1042
+ _field(
1043
+ "overflow",
1044
+ "Overflow handling policy for saturated channels.",
1045
+ children=(
1046
+ _field(
1047
+ "action",
1048
+ "Overflow action.",
1049
+ kind="string",
1050
+ choices=("max", "drop", "nan", "none"),
1051
+ default="max",
1052
+ ),
1053
+ _field("clip_quantile", "Quantile cap used when action=max.", kind="number", default=0.999),
1054
+ _field(
1055
+ "cap_strategy",
1056
+ "How per-channel caps are determined when action=max.",
1057
+ kind="string",
1058
+ choices=("provided", "infer", "quantile"),
1059
+ default="quantile",
1060
+ ),
1061
+ _field(
1062
+ "per_channel_caps",
1063
+ "Explicit per-channel caps when cap_strategy=provided.",
1064
+ kind="mapping",
1065
+ allow_unknown=True,
1066
+ allow_none=True,
1067
+ ),
1068
+ _field(
1069
+ "flag_column",
1070
+ "Column used to mark overflowed wells before capping.",
1071
+ kind="string",
1072
+ default="overflow",
1073
+ ),
1074
+ _field(
1075
+ "treat_inf_as_overflow",
1076
+ "Treat infinite values as overflowed rows.",
1077
+ kind="bool",
1078
+ default=True,
1079
+ ),
1080
+ ),
1081
+ ),
1082
+ ),
1083
+ ),
1084
+ ),
1085
+ factors=(
1086
+ ProtocolFactorSpec(name="design_id", role="design", summary="Design grouping for logic comparison."),
1087
+ ProtocolFactorSpec(name="time", role="time", summary="Selected summary timepoint."),
1088
+ ProtocolFactorSpec(
1089
+ name="ordered_state_space",
1090
+ role="mapping",
1091
+ summary="Ordered 00/10/01/11 treatment-to-corner state space.",
1092
+ ),
1093
+ ProtocolFactorSpec(
1094
+ name="reference_design", role="control", summary="Reference design for intensity normalization."
1095
+ ),
1096
+ ),
1097
+ control_rules=(
1098
+ ProtocolControlRule(
1099
+ id="logic_corner_map",
1100
+ summary="Resolve treatment states to 00/10/01/11 corners through the configured ordered state space.",
1101
+ control_selector="state_map_ref",
1102
+ ),
1103
+ ),
1104
+ windows=(
1105
+ ProtocolWindowSpec(
1106
+ id="summary_timepoint",
1107
+ summary="Select a single summary timepoint by nearest/exact/neighbor rules.",
1108
+ anchor="analysis_time",
1109
+ selector="time_mode",
1110
+ params={"default_mode": "nearest"},
1111
+ ),
1112
+ ),
1113
+ metrics=(
1114
+ ProtocolMetricSpec(
1115
+ id="four_state_vector",
1116
+ stage="summary",
1117
+ summary="Eight-value measurement vector over four-state logic shape and reference-normalized intensity.",
1118
+ formula="v00,v10,v01,v11,y00_star,y10_star,y01_star,y11_star",
1119
+ ),
1120
+ ),
1121
+ figures=(
1122
+ ProtocolFigureSpec(
1123
+ id="raw_kinetics",
1124
+ kind="qc",
1125
+ summary="Raw kinetics view over OD600 and reporter channels.",
1126
+ primary=True,
1127
+ ),
1128
+ ProtocolFigureSpec(
1129
+ id="endpoint_by_condition",
1130
+ kind="summary",
1131
+ summary="Endpoint comparison grouped by treatment/condition.",
1132
+ primary=True,
1133
+ ),
1134
+ ProtocolFigureSpec(
1135
+ id="endpoint_by_design",
1136
+ kind="summary",
1137
+ summary="Endpoint comparison grouped by sample or design.",
1138
+ primary=True,
1139
+ ),
1140
+ ProtocolFigureSpec(
1141
+ id="intensity_overview",
1142
+ kind="kinetics",
1143
+ summary="Combined time-series and endpoint view of the intensity channel.",
1144
+ primary=True,
1145
+ ),
1146
+ ProtocolFigureSpec(
1147
+ id="logic_symmetry",
1148
+ kind="summary",
1149
+ summary="Logic symmetry geometry over the configured response channel.",
1150
+ ),
1151
+ ProtocolFigureSpec(
1152
+ id="four_state_vector_diagnostic",
1153
+ kind="summary",
1154
+ summary=(
1155
+ "Per-design growth and response trajectories beside the persisted vector components "
1156
+ "at the persisted selection time."
1157
+ ),
1158
+ ),
1159
+ ProtocolFigureSpec(
1160
+ id="four_state_vector_heatmap",
1161
+ kind="summary",
1162
+ summary="Heatmap over per-design logic shape and reference-normalized intensity coordinates.",
1163
+ ),
1164
+ ),
1165
+ plot_profiles=(
1166
+ ProtocolPlotProfileSpec(
1167
+ id="kinetics_qc",
1168
+ summary="Time-series QC without selecting a scientific endpoint.",
1169
+ figures=("raw_kinetics",),
1170
+ ),
1171
+ ProtocolPlotProfileSpec(
1172
+ id="logic_overview",
1173
+ summary="Logic kinetics and endpoint summaries with explicitly authored plot times.",
1174
+ figures=(
1175
+ "raw_kinetics",
1176
+ "endpoint_by_condition",
1177
+ "endpoint_by_design",
1178
+ "intensity_overview",
1179
+ ),
1180
+ ),
1181
+ ProtocolPlotProfileSpec(
1182
+ id="logic_geometry",
1183
+ summary="Geometry-only logic symmetry review.",
1184
+ figures=("logic_symmetry",),
1185
+ ),
1186
+ ProtocolPlotProfileSpec(
1187
+ id="logic_diagnostic",
1188
+ summary="Record-driven per-design trajectory and vector review.",
1189
+ figures=("four_state_vector_diagnostic",),
1190
+ ),
1191
+ ProtocolPlotProfileSpec(
1192
+ id="logic_full",
1193
+ summary="Full logic review with kinetics and symmetry geometry.",
1194
+ figures=(
1195
+ "raw_kinetics",
1196
+ "endpoint_by_condition",
1197
+ "endpoint_by_design",
1198
+ "intensity_overview",
1199
+ "logic_symmetry",
1200
+ "four_state_vector_diagnostic",
1201
+ "four_state_vector_heatmap",
1202
+ ),
1203
+ ),
1204
+ ),
1205
+ default_plot_profile="kinetics_qc",
1206
+ artifacts=(
1207
+ ProtocolArtifactSpec(
1208
+ id="logic_summary_workbook",
1209
+ summary="Workbook export of the four-state logic-intensity vector.",
1210
+ ),
1211
+ ),
1212
+ execution=ProtocolExecutionPlan(
1213
+ plugin_defaults=(
1214
+ ProtocolPluginDefaultsSpec(
1215
+ plugin="ingest/synergy_h1",
1216
+ summary="Logic screens default to CFP/YFP/OD600 Synergy H1 ingest with protocol-bound channel policy.",
1217
+ with_={
1218
+ "mode": binding_value("ingest.mode", "mixed"),
1219
+ "channels": binding_value("ingest.channels", ["OD600", "CFP", "YFP"]),
1220
+ "channel_map": binding_value("ingest.channel_map", dict(_DUAL_REPORTER_CHANNEL_MAP)),
1221
+ "sheet_names": binding_value("ingest.sheet_names", None),
1222
+ "time_round_decimals": binding_value("ingest.time_round_decimals", 12),
1223
+ "time_step_h": binding_value("ingest.time_step_h", None),
1224
+ "time_offset_h": binding_value("ingest.time_offset_h", 0.0),
1225
+ "auto_roots": binding_value("ingest.auto_roots", None),
1226
+ "auto_include": binding_value("ingest.auto_include", list(DEFAULT_INCLUDE)),
1227
+ "auto_exclude": binding_value("ingest.auto_exclude", list(DEFAULT_EXCLUDE)),
1228
+ "auto_pick": binding_value("ingest.auto_pick", "single"),
1229
+ "auto_recursive": binding_value("ingest.auto_recursive", False),
1230
+ "print_summary": binding_value("ingest.print_summary", True),
1231
+ },
1232
+ ),
1233
+ ProtocolPluginDefaultsSpec(
1234
+ plugin="validator/to_tidy_plus_map",
1235
+ summary="Promote plate-reader tidy data into the annotated table shape required by the vector transform.",
1236
+ with_={
1237
+ "synthesize_batch": binding_value("promote.synthesize_batch", True),
1238
+ "drop_where_null_in": binding_value(
1239
+ "promote.drop_where_null_in",
1240
+ ["treatment", "design_id"],
1241
+ ),
1242
+ },
1243
+ ),
1244
+ ProtocolPluginDefaultsSpec(
1245
+ plugin="transform/fold_change",
1246
+ summary="Shared logic-screen fold-change defaults keep target and baseline policy in one place.",
1247
+ with_={
1248
+ "target": binding_value("fold_change.target", "YFP/CFP"),
1249
+ "report_times": binding_value("fold_change.report_times"),
1250
+ "time_tolerance": binding_value("fold_change.time_tolerance", 0.51),
1251
+ "observation_stat": binding_value("fold_change.observation_stat", "median"),
1252
+ "treatment_column": binding_value("fold_change.treatment_column", "treatment"),
1253
+ "group_by": binding_value("fold_change.group_by", ["design_id"]),
1254
+ "use_global_baseline": binding_value("fold_change.use_global_baseline", True),
1255
+ "global_baseline_value": binding_value("fold_change.global_baseline_value", None),
1256
+ "overrides": binding_value("fold_change.overrides", []),
1257
+ "fc_column": binding_value("fold_change.fc_column", "FC"),
1258
+ "log2fc_column": binding_value("fold_change.log2fc_column", "log2FC"),
1259
+ },
1260
+ ),
1261
+ ProtocolPluginDefaultsSpec(
1262
+ plugin="transform/four_state_vector",
1263
+ summary="Bind the dual-reporter assay channels to the four-state vector transform.",
1264
+ with_={
1265
+ "response": {
1266
+ "logic_channel": "YFP/CFP",
1267
+ "intensity_channel": "YFP/OD600",
1268
+ },
1269
+ "design_by": binding_value("design_by", ["design_id"]),
1270
+ "time_column": binding_value("time_column", "time"),
1271
+ "time_mode": binding_value("time_mode", "nearest"),
1272
+ "target_time_h": binding_value("target_time_h", None),
1273
+ "time_tolerance_h": binding_value("time_tolerance_h", 0.5),
1274
+ "state_map_ref": binding_value("state_map_ref", "induction_logic"),
1275
+ "reference": {
1276
+ "design_id": binding_value("reference.design_id", "REF"),
1277
+ "observation_stat": binding_value("reference.observation_stat", "mean"),
1278
+ },
1279
+ "require_all_corners_per_design": binding_value("require_all_corners_per_design", True),
1280
+ "exclude_reference_from_output": binding_value("exclude_reference_from_output", True),
1281
+ "carry_metadata": binding_value("carry_metadata", ["sequence", "id"]),
1282
+ },
1283
+ ),
1284
+ ),
1285
+ compiler=compile_logic_four_state_vector_screen,
1286
+ ),
1287
+ ),
1288
+ ProtocolDescriptor(
1289
+ protocol="cytometry/flow_panel",
1290
+ domain="cytometry",
1291
+ family="panel_analysis",
1292
+ summary="Flow-cytometry panel protocol for gated event tables and channel-level summaries.",
1293
+ tags=("cytometry", "fcs", "panel"),
1294
+ resources=(
1295
+ ProtocolResourceSpec(
1296
+ id="metadata",
1297
+ path="./inputs/metadata.csv",
1298
+ summary="Sample metadata joined to imported flow-cytometry events.",
1299
+ ),
1300
+ ),
1301
+ input_fields=(
1302
+ _field(
1303
+ "ingest",
1304
+ "Flow-cytometer ingest and channel naming settings.",
1305
+ children=(
1306
+ _field("auto_roots", "Directories to scan for .fcs files.", kind="string_list", allow_none=True),
1307
+ _field(
1308
+ "auto_include", "Filename globs to include.", kind="string_list", default=["*.fcs", "*.FCS"]
1309
+ ),
1310
+ _field(
1311
+ "auto_exclude", "Filename globs to exclude.", kind="string_list", default=list(DEFAULT_EXCLUDE)
1312
+ ),
1313
+ _field(
1314
+ "auto_pick",
1315
+ "Multi-file selection policy.",
1316
+ kind="string",
1317
+ choices=("single", "latest", "merge"),
1318
+ default="merge",
1319
+ ),
1320
+ _field(
1321
+ "auto_recursive",
1322
+ "Recurse into child directories when discovering files.",
1323
+ kind="bool",
1324
+ default=False,
1325
+ ),
1326
+ _field(
1327
+ "channel_name_field", "FCS metadata field used as channel label.", kind="string", default="pns"
1328
+ ),
1329
+ _field(
1330
+ "channel_map",
1331
+ "Optional channel rename mapping.",
1332
+ kind="mapping",
1333
+ allow_unknown=True,
1334
+ allow_none=True,
1335
+ ),
1336
+ _field("drop_channels", "Channels to drop after ingest.", kind="string_list", allow_none=True),
1337
+ _field(
1338
+ "sample_id_from",
1339
+ "How sample ids are derived from filenames.",
1340
+ kind="string",
1341
+ choices=("stem", "name"),
1342
+ default="stem",
1343
+ ),
1344
+ _field("time_value", "Time value applied to snapshot cytometry rows.", kind="number", default=0.0),
1345
+ _field("print_summary", "Print an ingest summary to the log.", kind="bool", default=True),
1346
+ ),
1347
+ ),
1348
+ _field(
1349
+ "metadata",
1350
+ "Metadata merge requirements for the cytometry panel.",
1351
+ children=(
1352
+ _field("key", "Join key between metadata and sample rows.", kind="string", default="sample_id"),
1353
+ _field(
1354
+ "require_columns",
1355
+ "Metadata columns that must exist after merge.",
1356
+ kind="string_list",
1357
+ default=[],
1358
+ ),
1359
+ _field(
1360
+ "require_non_null",
1361
+ "Require merged metadata columns to be non-null.",
1362
+ kind="bool",
1363
+ default=False,
1364
+ ),
1365
+ ),
1366
+ ),
1367
+ _field(
1368
+ "gating",
1369
+ "Explicit cytometry gating, fluorescence threshold, grouping, and QC policy.",
1370
+ required=True,
1371
+ children=(
1372
+ _field(
1373
+ "cells_enabled",
1374
+ "Whether to apply the rectangular cells gate.",
1375
+ kind="bool",
1376
+ required=True,
1377
+ example=False,
1378
+ ),
1379
+ _field(
1380
+ "cells_x_channel",
1381
+ "X channel for the cells gate.",
1382
+ kind="string",
1383
+ required=True,
1384
+ example="<cells-x-channel>",
1385
+ ),
1386
+ _field(
1387
+ "cells_y_channel",
1388
+ "Y channel for the cells gate.",
1389
+ kind="string",
1390
+ required=True,
1391
+ example="<cells-y-channel>",
1392
+ ),
1393
+ _field(
1394
+ "cells_x_range",
1395
+ "Closed two-value range for the cells-gate X channel.",
1396
+ kind="number_list",
1397
+ required=True,
1398
+ example=[0.0, 1.0],
1399
+ ),
1400
+ _field(
1401
+ "cells_y_range",
1402
+ "Closed two-value range for the cells-gate Y channel.",
1403
+ kind="number_list",
1404
+ required=True,
1405
+ example=[0.0, 1.0],
1406
+ ),
1407
+ _field(
1408
+ "singlets_enabled",
1409
+ "Whether to apply the singlet-ratio gate after the cells gate.",
1410
+ kind="bool",
1411
+ required=True,
1412
+ example=False,
1413
+ ),
1414
+ _field(
1415
+ "singlet_x_channel",
1416
+ "Denominator channel for the singlet ratio (Y / X).",
1417
+ kind="string",
1418
+ required=True,
1419
+ example="<singlet-denominator-channel>",
1420
+ ),
1421
+ _field(
1422
+ "singlet_y_channel",
1423
+ "Numerator channel for the singlet ratio (Y / X).",
1424
+ kind="string",
1425
+ required=True,
1426
+ example="<singlet-numerator-channel>",
1427
+ ),
1428
+ _field(
1429
+ "singlet_ratio_range",
1430
+ "Closed two-value range for the singlet ratio.",
1431
+ kind="number_list",
1432
+ required=True,
1433
+ example=[0.0, 1.0],
1434
+ ),
1435
+ _field(
1436
+ "fluorescence_channel",
1437
+ "Fluorescence channel summarized after gating.",
1438
+ kind="string",
1439
+ required=True,
1440
+ example="<fluorescence-channel>",
1441
+ ),
1442
+ _field(
1443
+ "threshold_mode",
1444
+ "Positive-event threshold policy.",
1445
+ kind="string",
1446
+ choices=("manual", "from_control_quantile"),
1447
+ required=True,
1448
+ example="manual",
1449
+ ),
1450
+ _field(
1451
+ "threshold_value",
1452
+ "Manual fluorescence threshold; null for control-quantile policy.",
1453
+ kind="number",
1454
+ required=True,
1455
+ allow_none=True,
1456
+ example=0.0,
1457
+ ),
1458
+ _field(
1459
+ "threshold_group_column",
1460
+ "Metadata column containing the threshold control; null for manual policy.",
1461
+ kind="string",
1462
+ required=True,
1463
+ allow_none=True,
1464
+ example=None,
1465
+ ),
1466
+ _field(
1467
+ "threshold_control_value",
1468
+ "Exact control value used for threshold estimation; null for manual policy.",
1469
+ kind="string",
1470
+ required=True,
1471
+ allow_none=True,
1472
+ example=None,
1473
+ ),
1474
+ _field(
1475
+ "threshold_quantile",
1476
+ "Control quantile used as the fluorescence threshold; null for manual policy.",
1477
+ kind="number",
1478
+ required=True,
1479
+ allow_none=True,
1480
+ example=None,
1481
+ ),
1482
+ _field(
1483
+ "group_column",
1484
+ "Metadata column for group summaries; explicit null disables group summaries.",
1485
+ kind="string",
1486
+ required=True,
1487
+ allow_none=True,
1488
+ example=None,
1489
+ ),
1490
+ _field(
1491
+ "minimum_final_events",
1492
+ "Minimum retained singlet events required per sample.",
1493
+ kind="integer",
1494
+ required=True,
1495
+ example=0,
1496
+ ),
1497
+ _field(
1498
+ "minimum_final_percent",
1499
+ "Minimum percentage of input events retained per sample.",
1500
+ kind="number",
1501
+ required=True,
1502
+ example=0.0,
1503
+ ),
1504
+ _field(
1505
+ "maximum_nonpositive_percent",
1506
+ "Maximum allowed percentage of nonpositive fluorescence values per sample.",
1507
+ kind="number",
1508
+ required=True,
1509
+ example=100.0,
1510
+ ),
1511
+ _field(
1512
+ "nonpositive_scope",
1513
+ "Event population used for nonpositive-fluorescence QC.",
1514
+ kind="string",
1515
+ choices=("all_events", "gated_events"),
1516
+ required=True,
1517
+ example="all_events",
1518
+ ),
1519
+ ),
1520
+ ),
1521
+ ),
1522
+ analysis_fields=(),
1523
+ factors=(
1524
+ ProtocolFactorSpec(name="sample", role="sample", summary="Sample/run identifier."),
1525
+ ProtocolFactorSpec(name="condition", role="condition", summary="Experimental condition."),
1526
+ ProtocolFactorSpec(name="gate", role="gate", summary="Gate or subset definition.", required=False),
1527
+ ),
1528
+ figures=(
1529
+ ProtocolFigureSpec(
1530
+ id="gating_diagnostic",
1531
+ kind="diagnostic",
1532
+ summary="Configured cells, singlets, fluorescence, and final-retention diagnostics.",
1533
+ primary=True,
1534
+ ),
1535
+ ),
1536
+ plot_profiles=(
1537
+ ProtocolPlotProfileSpec(
1538
+ id="gating_review",
1539
+ summary="Primary record-driven cytometry gating and fluorescence review.",
1540
+ figures=("gating_diagnostic",),
1541
+ ),
1542
+ ),
1543
+ default_plot_profile="gating_review",
1544
+ artifacts=(
1545
+ ProtocolArtifactSpec(
1546
+ id="gate_definition_table",
1547
+ summary="CSV projection of the resolved gate and threshold policy.",
1548
+ default=True,
1549
+ ),
1550
+ ProtocolArtifactSpec(
1551
+ id="sample_stats_table",
1552
+ summary="CSV projection of per-sample cytometry statistics.",
1553
+ default=True,
1554
+ ),
1555
+ ProtocolArtifactSpec(
1556
+ id="group_stats_table",
1557
+ summary="CSV projection of configured group-level cytometry statistics.",
1558
+ default=True,
1559
+ ),
1560
+ ProtocolArtifactSpec(
1561
+ id="qc_table",
1562
+ summary="CSV projection of per-sample cytometry QC decisions.",
1563
+ default=True,
1564
+ ),
1565
+ ProtocolArtifactSpec(
1566
+ id="gated_events_table",
1567
+ summary="Optional CSV projection of retained cytometry events.",
1568
+ ),
1569
+ ),
1570
+ execution=ProtocolExecutionPlan(
1571
+ plugin_defaults=(
1572
+ ProtocolPluginDefaultsSpec(
1573
+ plugin="ingest/flow_cytometer",
1574
+ summary="Flow-panel ingest defaults come from protocol parameters instead of per-experiment plugin blobs.",
1575
+ with_={
1576
+ "auto_roots": binding_value("ingest.auto_roots", None),
1577
+ "auto_include": binding_value("ingest.auto_include", ["*.fcs", "*.FCS"]),
1578
+ "auto_exclude": binding_value("ingest.auto_exclude", list(DEFAULT_EXCLUDE)),
1579
+ "auto_pick": binding_value("ingest.auto_pick", "merge"),
1580
+ "auto_recursive": binding_value("ingest.auto_recursive", False),
1581
+ "channel_name_field": binding_value("ingest.channel_name_field", "pns"),
1582
+ "channel_map": binding_value("ingest.channel_map", None),
1583
+ "drop_channels": binding_value("ingest.drop_channels", None),
1584
+ "sample_id_from": binding_value("ingest.sample_id_from", "stem"),
1585
+ "time_value": binding_value("ingest.time_value", 0.0),
1586
+ "print_summary": binding_value("ingest.print_summary", True),
1587
+ },
1588
+ ),
1589
+ ProtocolPluginDefaultsSpec(
1590
+ plugin="transform/sample_metadata",
1591
+ summary="Metadata merge requirements are protocol parameters, not raw plugin overrides.",
1592
+ with_={
1593
+ "key": binding_value("metadata.key", "sample_id"),
1594
+ "require_columns": binding_value("metadata.require_columns", []),
1595
+ "require_non_null": binding_value("metadata.require_non_null", False),
1596
+ },
1597
+ ),
1598
+ ProtocolPluginDefaultsSpec(
1599
+ plugin="transform/cytometry_gating",
1600
+ summary="Cytometry gates and QC are explicit experiment parameters compiled into typed records.",
1601
+ with_={
1602
+ "cells_enabled": binding_value("gating.cells_enabled"),
1603
+ "cells_x_channel": binding_value("gating.cells_x_channel"),
1604
+ "cells_y_channel": binding_value("gating.cells_y_channel"),
1605
+ "cells_x_range": binding_value("gating.cells_x_range"),
1606
+ "cells_y_range": binding_value("gating.cells_y_range"),
1607
+ "singlets_enabled": binding_value("gating.singlets_enabled"),
1608
+ "singlet_x_channel": binding_value("gating.singlet_x_channel"),
1609
+ "singlet_y_channel": binding_value("gating.singlet_y_channel"),
1610
+ "singlet_ratio_range": binding_value("gating.singlet_ratio_range"),
1611
+ "fluorescence_channel": binding_value("gating.fluorescence_channel"),
1612
+ "threshold_mode": binding_value("gating.threshold_mode"),
1613
+ "threshold_value": binding_value("gating.threshold_value"),
1614
+ "threshold_group_column": binding_value("gating.threshold_group_column"),
1615
+ "threshold_control_value": binding_value("gating.threshold_control_value"),
1616
+ "threshold_quantile": binding_value("gating.threshold_quantile"),
1617
+ "group_column": binding_value("gating.group_column"),
1618
+ "minimum_final_events": binding_value("gating.minimum_final_events"),
1619
+ "minimum_final_percent": binding_value("gating.minimum_final_percent"),
1620
+ "maximum_nonpositive_percent": binding_value("gating.maximum_nonpositive_percent"),
1621
+ "nonpositive_scope": binding_value("gating.nonpositive_scope"),
1622
+ },
1623
+ ),
1624
+ ),
1625
+ compiler=compile_cytometry_flow_panel,
1626
+ ),
1627
+ ),
1628
+ )
1629
+
1630
+ _DUAL_REPORTER_PROTOCOL = next(
1631
+ item for item in BUILTIN_PROTOCOLS if item.protocol == "plate_reader/dual_reporter_screen"
1632
+ )
1633
+ _PLATE_READER_SINGLE_REPORTER_PROTOCOL = build_plate_reader_variant_protocol(
1634
+ dual_reporter_protocol=_DUAL_REPORTER_PROTOCOL,
1635
+ field_builder=_field,
1636
+ )
1637
+ _PLATE_READER_GROWTH_PROTOCOL = build_plate_reader_growth_protocol(
1638
+ dual_reporter_protocol=_DUAL_REPORTER_PROTOCOL,
1639
+ field_builder=_field,
1640
+ )
1641
+
1642
+ BUILTIN_PROTOCOLS = (
1643
+ next(item for item in BUILTIN_PROTOCOLS if item.protocol == "workbench/generic"),
1644
+ _DUAL_REPORTER_PROTOCOL,
1645
+ _PLATE_READER_SINGLE_REPORTER_PROTOCOL,
1646
+ _PLATE_READER_GROWTH_PROTOCOL,
1647
+ next(item for item in BUILTIN_PROTOCOLS if item.protocol == "plate_reader/four_state_event_window"),
1648
+ next(item for item in BUILTIN_PROTOCOLS if item.protocol == "logic/four_state_vector_screen"),
1649
+ next(item for item in BUILTIN_PROTOCOLS if item.protocol == "logic/four_state_vector_collection"),
1650
+ next(item for item in BUILTIN_PROTOCOLS if item.protocol == "cytometry/flow_panel"),
1651
+ )
1652
+
1653
+
1654
+ @cache
1655
+ def builtin_protocol_catalog() -> ProtocolCatalog:
1656
+ return ProtocolCatalog(list(BUILTIN_PROTOCOLS))