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,64 @@
1
+ from __future__ import annotations
2
+
3
+ from rich import box
4
+ from rich.table import Table
5
+
6
+ from reader_workbench.workbench.dop import DOP_SCHEMA, DataClassSpec, ReadySpec
7
+
8
+
9
+ def data_classes_payload(data_classes: tuple[DataClassSpec, ...]) -> dict[str, object]:
10
+ return {
11
+ "schema": DOP_SCHEMA,
12
+ "data_classes": [item.to_payload() for item in data_classes],
13
+ }
14
+
15
+
16
+ def ready_specs_payload(ready_specs: tuple[ReadySpec, ...]) -> dict[str, object]:
17
+ return {
18
+ "schema": DOP_SCHEMA,
19
+ "ready_specs": [item.to_payload() for item in ready_specs],
20
+ }
21
+
22
+
23
+ def _table(title: str) -> Table:
24
+ return Table(
25
+ title=f"[title]{title}[/title]",
26
+ title_justify="left",
27
+ header_style="bold",
28
+ box=box.ROUNDED,
29
+ expand=True,
30
+ show_lines=False,
31
+ show_edge=True,
32
+ )
33
+
34
+
35
+ def data_classes_table(data_classes: tuple[DataClassSpec, ...]) -> Table:
36
+ table = _table("DOP Data Classes")
37
+ table.add_column("ID", style="accent", overflow="fold")
38
+ table.add_column("Protocols", overflow="fold")
39
+ table.add_column("Minimum capture", overflow="fold")
40
+ table.add_column("Stop when", overflow="fold")
41
+ for item in data_classes:
42
+ table.add_row(
43
+ item.id,
44
+ ", ".join(item.protocol_candidates),
45
+ "; ".join(item.minimum_capture),
46
+ "; ".join(item.stop_conditions),
47
+ )
48
+ return table
49
+
50
+
51
+ def ready_specs_table(ready_specs: tuple[ReadySpec, ...]) -> Table:
52
+ table = _table("DOP Ready Specs")
53
+ table.add_column("ID", style="accent", overflow="fold")
54
+ table.add_column("Evidence", overflow="fold")
55
+ table.add_column("Reader states", overflow="fold")
56
+ table.add_column("Capabilities", overflow="fold")
57
+ for item in ready_specs:
58
+ table.add_row(
59
+ item.id,
60
+ "; ".join(item.required_evidence),
61
+ ", ".join(item.accepted_readiness_states) or "—",
62
+ ", ".join(item.required_capabilities) or "—",
63
+ )
64
+ return table
@@ -0,0 +1,449 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from reader_workbench.errors import RecordError
8
+ from reader_workbench.runtime import ReaderRuntime
9
+ from reader_workbench.workbench.config import ReaderSpec
10
+ from reader_workbench.workbench.decl import WorkbenchDecl
11
+ from reader_workbench.workbench.engine.setup import slice_pipeline_steps
12
+ from reader_workbench.workbench.engine.validation import validation_summary
13
+ from reader_workbench.workbench.experiment import FileResourceEntry, RecordResourceEntry, ResourceEntry
14
+ from reader_workbench.workbench.graph import resolve_workbench
15
+
16
+ from .common import (
17
+ count_visible_files,
18
+ format_relative_path,
19
+ preview_output_files,
20
+ resolve_output_subdir,
21
+ summarize_outputs_dir,
22
+ visible_relative_files,
23
+ )
24
+ from .readiness import experiment_readiness_payload
25
+ from .results import record_entries_payload
26
+ from .runtime import (
27
+ compiled_workbench_payload,
28
+ implementation_plan_payload,
29
+ pipeline_step_payload,
30
+ record_producer_map,
31
+ )
32
+ from .semantics import semantic_program_payload
33
+
34
+ EXPERIMENT_INSPECT_SECTIONS = (
35
+ "identity",
36
+ "authoring",
37
+ "semantics",
38
+ "plan",
39
+ "compiled",
40
+ "inputs",
41
+ "generated",
42
+ "readiness",
43
+ )
44
+
45
+
46
+ def _resource_entry_payload(resource_id: str, entry: ResourceEntry, *, base: Path) -> dict[str, str]:
47
+ if isinstance(entry, FileResourceEntry):
48
+ return {
49
+ "id": resource_id,
50
+ "kind": entry.kind,
51
+ "path": format_relative_path(entry.path, base=base),
52
+ }
53
+ if isinstance(entry, RecordResourceEntry):
54
+ return {
55
+ "id": resource_id,
56
+ "kind": entry.kind,
57
+ "experiment": entry.experiment_id,
58
+ "record": entry.record_id,
59
+ }
60
+ raise TypeError(f"Unsupported experiment resource entry: {type(entry).__name__}")
61
+
62
+
63
+ def experiment_authoring_payload(
64
+ *, inputs: dict[str, Any], analysis: dict[str, Any], outputs: dict[str, Any]
65
+ ) -> dict[str, object]:
66
+ return {
67
+ "inputs": deepcopy(inputs),
68
+ "analysis": deepcopy(analysis),
69
+ "outputs": deepcopy(outputs),
70
+ }
71
+
72
+
73
+ def experiment_config_authoring_payload(*, document: dict[str, Any]) -> dict[str, object]:
74
+ return deepcopy(document)
75
+
76
+
77
+ def experiment_implementation_payload(
78
+ *,
79
+ plan: dict[str, object] | None = None,
80
+ compiled: dict[str, object] | None = None,
81
+ inputs: dict[str, object] | None = None,
82
+ generated: dict[str, object] | None = None,
83
+ readiness: dict[str, object] | None = None,
84
+ ) -> dict[str, object]:
85
+ payload: dict[str, object] = {}
86
+ if plan is not None:
87
+ payload["plan"] = deepcopy(plan)
88
+ if compiled is not None:
89
+ payload["compiled"] = deepcopy(compiled)
90
+ if inputs is not None:
91
+ payload["inputs"] = deepcopy(inputs)
92
+ if generated is not None:
93
+ payload["generated"] = deepcopy(generated)
94
+ if readiness is not None:
95
+ payload["readiness"] = deepcopy(readiness)
96
+ return payload
97
+
98
+
99
+ def experiment_surface_payload(
100
+ *,
101
+ experiment: dict[str, object],
102
+ authoring: dict[str, object],
103
+ semantic_program,
104
+ implementation: dict[str, object],
105
+ ) -> dict[str, object]:
106
+ return {
107
+ "experiment": deepcopy(experiment),
108
+ "authoring": deepcopy(authoring),
109
+ "semantics": {
110
+ "program": semantic_program_payload(semantic_program, include_execution=False),
111
+ },
112
+ "implementation": deepcopy(implementation),
113
+ }
114
+
115
+
116
+ def experiment_inspect_section_payload(
117
+ payload: dict[str, object],
118
+ *,
119
+ section: str,
120
+ ) -> dict[str, object]:
121
+ """Project an inspect document through a stable semantic section name."""
122
+
123
+ if section not in EXPERIMENT_INSPECT_SECTIONS:
124
+ raise ValueError(f"Unknown experiment inspect section {section!r}")
125
+ projected = {"experiment": deepcopy(payload["experiment"])}
126
+ if section == "identity":
127
+ return projected
128
+ if section in {"authoring", "semantics"}:
129
+ projected[section] = deepcopy(payload[section])
130
+ return projected
131
+ implementation = payload["implementation"]
132
+ if not isinstance(implementation, dict) or section not in implementation:
133
+ raise ValueError(f"Experiment inspect payload does not contain section {section!r}")
134
+ projected[section] = deepcopy(implementation[section])
135
+ return projected
136
+
137
+
138
+ def experiment_identity_payload(
139
+ *,
140
+ job_path: Path,
141
+ decl: WorkbenchDecl,
142
+ protocol_id: str | None = None,
143
+ ) -> dict[str, object]:
144
+ evidence = decl.experiment_semantics.evidence
145
+ return {
146
+ "id": decl.experiment.id,
147
+ "title": decl.experiment.title,
148
+ "lifecycle": decl.experiment.lifecycle,
149
+ "protocol": protocol_id or decl.experiment_semantics.protocol.id,
150
+ "config": str(job_path),
151
+ "root": str(decl.experiment.root),
152
+ "evidence": evidence.to_payload() if evidence is not None else None,
153
+ }
154
+
155
+
156
+ def bound_experiment_surface_context(
157
+ *,
158
+ job_path: Path,
159
+ spec: ReaderSpec,
160
+ decl: WorkbenchDecl,
161
+ runtime: ReaderRuntime,
162
+ ) -> tuple[object, dict[str, object], dict[str, object]]:
163
+ bound_protocol = runtime.bind_protocol(decl.experiment_semantics.protocol)
164
+ return (
165
+ bound_protocol,
166
+ {
167
+ **experiment_identity_payload(job_path=job_path, decl=decl, protocol_id=bound_protocol.id),
168
+ "plot_profile": spec.protocol.outputs.plots.profile or bound_protocol.default_plot_profile,
169
+ },
170
+ experiment_authoring_payload(
171
+ inputs=spec.protocol.inputs,
172
+ analysis=spec.protocol.analysis,
173
+ outputs=spec.protocol.outputs.model_dump(exclude_none=True),
174
+ ),
175
+ )
176
+
177
+
178
+ def experiment_explain_payload(
179
+ *,
180
+ job_path: Path,
181
+ spec: ReaderSpec,
182
+ decl: WorkbenchDecl,
183
+ runtime: ReaderRuntime,
184
+ ) -> dict[str, object]:
185
+ bound_protocol, experiment_payload, authoring_payload = bound_experiment_surface_context(
186
+ job_path=job_path,
187
+ spec=spec,
188
+ decl=decl,
189
+ runtime=runtime,
190
+ )
191
+ workbench = resolve_workbench(decl)
192
+ pipeline_steps = list(workbench.pipeline)
193
+ plot_steps = list(workbench.plots)
194
+ export_steps = list(workbench.exports)
195
+ record_producers = record_producer_map(workbench.plugin_steps(), runtime=runtime)
196
+ semantic_program = decl.experiment_semantics.protocol_program
197
+ compiled_payload = compiled_workbench_payload(
198
+ bound_protocol=bound_protocol,
199
+ pipeline_steps=pipeline_steps,
200
+ plot_steps=plot_steps,
201
+ export_steps=export_steps,
202
+ runtime=runtime,
203
+ record_producers=record_producers,
204
+ )
205
+ compiled_payload["semantic_program"] = semantic_program_payload(semantic_program)
206
+ return experiment_surface_payload(
207
+ experiment=experiment_payload,
208
+ authoring=authoring_payload,
209
+ semantic_program=semantic_program,
210
+ implementation=experiment_implementation_payload(
211
+ plan=implementation_plan_payload(
212
+ bound_protocol=bound_protocol,
213
+ decl=decl,
214
+ pipeline_steps=pipeline_steps,
215
+ plot_steps=plot_steps,
216
+ export_steps=export_steps,
217
+ ),
218
+ compiled=compiled_payload,
219
+ ),
220
+ )
221
+
222
+
223
+ def experiment_run_dry_run_payload(
224
+ *,
225
+ job_path: Path,
226
+ spec: ReaderSpec,
227
+ decl: WorkbenchDecl,
228
+ runtime: ReaderRuntime,
229
+ resume_from: str | None,
230
+ until: str | None,
231
+ only: str | None = None,
232
+ ) -> dict[str, object]:
233
+ validation_summary(decl, check_files=False, exp_root=decl.experiment.root, runtime=runtime)
234
+ workbench = resolve_workbench(decl)
235
+ pipeline_steps = slice_pipeline_steps(
236
+ list(workbench.pipeline),
237
+ resume_from=only or resume_from,
238
+ until=only or until,
239
+ )
240
+ record_producers = record_producer_map(workbench.plugin_steps(), runtime=runtime)
241
+ payload = experiment_explain_payload(job_path=job_path, spec=spec, decl=decl, runtime=runtime)
242
+ payload["dry_run"] = True
243
+ payload["slice"] = {
244
+ "from": resume_from,
245
+ "until": until,
246
+ "only": only,
247
+ }
248
+ payload["implementation"]["plan"]["pipeline_flow"] = [step.id for step in pipeline_steps]
249
+ payload["implementation"]["plan"]["plots"] = []
250
+ payload["implementation"]["plan"]["exports"] = []
251
+ payload["implementation"]["compiled"]["pipeline"] = [
252
+ pipeline_step_payload(step, runtime=runtime, record_producers=record_producers) for step in pipeline_steps
253
+ ]
254
+ payload["implementation"]["compiled"]["plots"] = []
255
+ payload["implementation"]["compiled"]["exports"] = []
256
+ return payload
257
+
258
+
259
+ def experiment_steps_payload(
260
+ *,
261
+ job_path: Path,
262
+ spec: ReaderSpec,
263
+ decl: WorkbenchDecl,
264
+ runtime: ReaderRuntime,
265
+ ) -> dict[str, object]:
266
+ bound_protocol, experiment_payload, authoring_payload = bound_experiment_surface_context(
267
+ job_path=job_path,
268
+ spec=spec,
269
+ decl=decl,
270
+ runtime=runtime,
271
+ )
272
+ workbench = resolve_workbench(decl)
273
+ pipeline_steps = list(workbench.pipeline)
274
+ record_producers = record_producer_map(workbench.plugin_steps(), runtime=runtime)
275
+ plan_payload = implementation_plan_payload(
276
+ bound_protocol=bound_protocol,
277
+ decl=decl,
278
+ pipeline_steps=pipeline_steps,
279
+ plot_steps=[],
280
+ export_steps=[],
281
+ )
282
+ plan_payload["pipeline_count"] = len(pipeline_steps)
283
+ semantic_program = decl.experiment_semantics.protocol_program
284
+ compiled_payload = {
285
+ "pipeline": [
286
+ pipeline_step_payload(step, runtime=runtime, record_producers=record_producers) for step in pipeline_steps
287
+ ],
288
+ "plots": [],
289
+ "exports": [],
290
+ }
291
+ compiled_payload["semantic_program"] = semantic_program_payload(semantic_program)
292
+ return experiment_surface_payload(
293
+ experiment=experiment_payload,
294
+ authoring=authoring_payload,
295
+ semantic_program=semantic_program,
296
+ implementation=experiment_implementation_payload(
297
+ plan=plan_payload,
298
+ compiled=compiled_payload,
299
+ ),
300
+ )
301
+
302
+
303
+ def experiment_config_json_payload(
304
+ *,
305
+ job_path: Path,
306
+ spec: ReaderSpec,
307
+ decl: WorkbenchDecl,
308
+ runtime: ReaderRuntime,
309
+ ) -> dict[str, object]:
310
+ bound_protocol, experiment_payload, _ = bound_experiment_surface_context(
311
+ job_path=job_path,
312
+ spec=spec,
313
+ decl=decl,
314
+ runtime=runtime,
315
+ )
316
+ workbench = resolve_workbench(decl)
317
+ pipeline_steps = list(workbench.pipeline)
318
+ plot_steps = list(workbench.plots)
319
+ export_steps = list(workbench.exports)
320
+ record_producers = record_producer_map(workbench.plugin_steps(), runtime=runtime)
321
+ semantic_program = decl.experiment_semantics.protocol_program
322
+ compiled_payload = compiled_workbench_payload(
323
+ bound_protocol=bound_protocol,
324
+ pipeline_steps=pipeline_steps,
325
+ plot_steps=plot_steps,
326
+ export_steps=export_steps,
327
+ runtime=runtime,
328
+ record_producers=record_producers,
329
+ )
330
+ compiled_payload["semantic_program"] = semantic_program_payload(semantic_program)
331
+ return experiment_surface_payload(
332
+ experiment=experiment_payload,
333
+ authoring=experiment_config_authoring_payload(document=spec.model_dump(by_alias=True)),
334
+ semantic_program=semantic_program,
335
+ implementation=experiment_implementation_payload(
336
+ plan=implementation_plan_payload(
337
+ bound_protocol=bound_protocol,
338
+ decl=decl,
339
+ pipeline_steps=pipeline_steps,
340
+ plot_steps=plot_steps,
341
+ export_steps=export_steps,
342
+ ),
343
+ compiled=compiled_payload,
344
+ ),
345
+ )
346
+
347
+
348
+ def experiment_inspect_payload(
349
+ *,
350
+ job_path: Path,
351
+ spec: ReaderSpec,
352
+ decl: WorkbenchDecl,
353
+ runtime: ReaderRuntime,
354
+ ) -> dict[str, object]:
355
+ bound_protocol, experiment_payload, authoring_payload = bound_experiment_surface_context(
356
+ job_path=job_path,
357
+ spec=spec,
358
+ decl=decl,
359
+ runtime=runtime,
360
+ )
361
+ workbench = resolve_workbench(decl)
362
+ exp_root = decl.experiment.root
363
+ inputs_dir = exp_root / "inputs"
364
+ outputs_dir = decl.experiment_semantics.layout.outputs_dir
365
+ output_counts = summarize_outputs_dir(
366
+ outputs_dir,
367
+ plots_subdir=decl.experiment_semantics.layout.plots_subdir,
368
+ exports_subdir=decl.experiment_semantics.layout.exports_subdir,
369
+ notebooks_subdir=decl.experiment_semantics.layout.notebooks_subdir,
370
+ )
371
+ plots_dir = resolve_output_subdir(outputs_dir, decl.experiment_semantics.layout.plots_subdir)
372
+ exports_dir = resolve_output_subdir(outputs_dir, decl.experiment_semantics.layout.exports_subdir)
373
+ notebooks_dir = resolve_output_subdir(outputs_dir, decl.experiment_semantics.layout.notebooks_subdir)
374
+ artifacts_dir = outputs_dir / "artifacts"
375
+ store = runtime.record_store(
376
+ outputs_dir,
377
+ plots_subdir=decl.experiment_semantics.layout.plots_subdir,
378
+ exports_subdir=decl.experiment_semantics.layout.exports_subdir,
379
+ create=False,
380
+ )
381
+ input_files = visible_relative_files(inputs_dir, base=exp_root, limit=8)
382
+ input_file_count = count_visible_files(inputs_dir)
383
+ resource_rows = [
384
+ _resource_entry_payload(resource_id, entry, base=exp_root)
385
+ for resource_id, entry in sorted(decl.experiment_semantics.resources.by_id.items())
386
+ ]
387
+ record_producers = record_producer_map(workbench.plugin_steps(), runtime=runtime)
388
+ records_payload: list[dict[str, object]] = []
389
+ records_error: str | None = None
390
+ if store.catalog_exists():
391
+ try:
392
+ records_payload = record_entries_payload(
393
+ store=store,
394
+ outputs_dir=outputs_dir,
395
+ runtime=runtime,
396
+ )
397
+ except RecordError as exc:
398
+ records_error = str(exc)
399
+ pipeline_steps = list(workbench.pipeline)
400
+ plot_steps = list(workbench.plots)
401
+ export_steps = list(workbench.exports)
402
+ semantic_program = decl.experiment_semantics.protocol_program
403
+ compiled_payload = compiled_workbench_payload(
404
+ bound_protocol=bound_protocol,
405
+ pipeline_steps=pipeline_steps,
406
+ plot_steps=plot_steps,
407
+ export_steps=export_steps,
408
+ runtime=runtime,
409
+ record_producers=record_producers,
410
+ )
411
+ compiled_payload["semantic_program"] = semantic_program_payload(semantic_program)
412
+ readiness_payload = experiment_readiness_payload(job_path=job_path, decl=decl, runtime=runtime)
413
+ generated_payload: dict[str, object] = {
414
+ "counts": output_counts,
415
+ "examples": {
416
+ "records": preview_output_files(artifacts_dir, base=exp_root),
417
+ "plots": preview_output_files(plots_dir, base=exp_root),
418
+ "exports": preview_output_files(exports_dir, base=exp_root),
419
+ "notebooks": preview_output_files(notebooks_dir, base=exp_root),
420
+ },
421
+ "records": records_payload,
422
+ }
423
+ if records_error is not None:
424
+ generated_payload["records_error"] = records_error
425
+ return experiment_surface_payload(
426
+ experiment=experiment_payload,
427
+ authoring=authoring_payload,
428
+ semantic_program=semantic_program,
429
+ implementation=experiment_implementation_payload(
430
+ plan=implementation_plan_payload(
431
+ bound_protocol=bound_protocol,
432
+ decl=decl,
433
+ pipeline_steps=pipeline_steps,
434
+ plot_steps=plot_steps,
435
+ export_steps=export_steps,
436
+ ),
437
+ compiled=compiled_payload,
438
+ inputs={
439
+ "counts": {
440
+ "files": input_file_count,
441
+ "resources": len(resource_rows),
442
+ },
443
+ "files": input_files,
444
+ "resources": resource_rows,
445
+ },
446
+ generated=generated_payload,
447
+ readiness=readiness_payload,
448
+ ),
449
+ )
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from copy import deepcopy
5
+ from pathlib import Path
6
+
7
+
8
+ def _sorted_counter(values: list[str]) -> dict[str, int]:
9
+ counts = Counter(value for value in values if value)
10
+ return dict(sorted(counts.items()))
11
+
12
+
13
+ def inventory_summary_payload(entries: list[dict[str, object]]) -> dict[str, object]:
14
+ statuses = [str(entry.get("status") or "unknown") for entry in entries]
15
+ lifecycles = [str(entry.get("lifecycle") or "unknown") for entry in entries]
16
+ readiness_states = [
17
+ str(readiness.get("state"))
18
+ for readiness in (entry.get("readiness") for entry in entries)
19
+ if isinstance(readiness, dict) and readiness.get("state")
20
+ ]
21
+ protocols = [
22
+ str(protocol)
23
+ for protocol in (entry.get("protocol") for entry in entries)
24
+ if isinstance(protocol, str) and protocol
25
+ ]
26
+ with_outputs = sum(1 for entry in entries if bool(entry.get("has_outputs")))
27
+ payload = {
28
+ "experiments": len(entries),
29
+ "by_status": _sorted_counter(statuses),
30
+ "by_lifecycle": _sorted_counter(lifecycles),
31
+ "by_protocol": _sorted_counter(protocols),
32
+ "outputs": {
33
+ "with_outputs": with_outputs,
34
+ "without_outputs": len(entries) - with_outputs,
35
+ },
36
+ }
37
+ if readiness_states:
38
+ payload["by_readiness"] = _sorted_counter(readiness_states)
39
+ return payload
40
+
41
+
42
+ def inventory_surface_payload(
43
+ *,
44
+ root: Path,
45
+ include_scaffolds: bool,
46
+ details: bool,
47
+ readiness: bool,
48
+ protocol: str | None,
49
+ status: str | None,
50
+ lifecycle: str | None,
51
+ experiments: list[dict[str, object]],
52
+ ) -> dict[str, object]:
53
+ return {
54
+ "catalog": {
55
+ "kind": "experiments",
56
+ "root": str(root),
57
+ },
58
+ "selection": {
59
+ "include_scaffolds": include_scaffolds,
60
+ "details": details,
61
+ "readiness": readiness,
62
+ "protocol": protocol,
63
+ "status": status,
64
+ "lifecycle": lifecycle,
65
+ },
66
+ "summary": inventory_summary_payload(experiments),
67
+ "experiments": deepcopy(experiments),
68
+ }