@morscherlab/mint-sdk 1.0.0-rc.2 → 1.0.0-rc.5

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 (443) hide show
  1. package/dist/__tests__/components/AppTopBar.navigation.test.d.ts +1 -0
  2. package/dist/__tests__/components/DoseCalculatorVolumeField.test.d.ts +1 -0
  3. package/dist/__tests__/components/PlateMapEditorToolbarInternal.test.d.ts +1 -0
  4. package/dist/__tests__/components/PluginWorkspaceView.controls.test.d.ts +1 -0
  5. package/dist/__tests__/components/PluginWorkspaceView.navigation.test.d.ts +1 -0
  6. package/dist/__tests__/components/PluginWorkspaceView.shell.test.d.ts +1 -0
  7. package/dist/__tests__/components/ProtocolStep.presentation.test.d.ts +1 -0
  8. package/dist/__tests__/components/ProtocolStepEditor.state.test.d.ts +1 -0
  9. package/dist/__tests__/components/ProtocolStepParameterField.test.d.ts +1 -0
  10. package/dist/__tests__/components/ReagentList.presentation.test.d.ts +1 -0
  11. package/dist/__tests__/components/SampleSelector.colors.test.d.ts +1 -0
  12. package/dist/__tests__/components/SampleSelector.drag.test.d.ts +1 -0
  13. package/dist/__tests__/components/SampleSelector.groups.test.d.ts +1 -0
  14. package/dist/__tests__/components/SampleSelector.selection.test.d.ts +1 -0
  15. package/dist/__tests__/components/SampleSelectorSampleRow.test.d.ts +1 -0
  16. package/dist/__tests__/components/ScheduleCalendar.test.d.ts +1 -0
  17. package/dist/__tests__/components/SettingsModal.schema.test.d.ts +1 -0
  18. package/dist/__tests__/components/WellPlate.colors.test.d.ts +1 -0
  19. package/dist/__tests__/components/WellPlate.conditions.test.d.ts +1 -0
  20. package/dist/__tests__/components/WellPlate.geometry.test.d.ts +1 -0
  21. package/dist/__tests__/components/WellPlate.interaction.test.d.ts +1 -0
  22. package/dist/__tests__/components/WellPlate.legend.test.d.ts +1 -0
  23. package/dist/__tests__/components/WellPlate.rendering.test.d.ts +1 -0
  24. package/dist/__tests__/components/WellPlate.sampleDrop.test.d.ts +1 -0
  25. package/dist/__tests__/composables/autoGroup/classify.test.d.ts +1 -0
  26. package/dist/__tests__/composables/autoGroup/columns.test.d.ts +1 -0
  27. package/dist/__tests__/composables/autoGroup/compose.test.d.ts +1 -0
  28. package/dist/__tests__/composables/autoGroup/cooccurrence.test.d.ts +1 -0
  29. package/dist/__tests__/composables/autoGroup/fingerprint.test.d.ts +1 -0
  30. package/dist/__tests__/composables/autoGroup/integration.test.d.ts +1 -0
  31. package/dist/__tests__/composables/autoGroup/template.test.d.ts +1 -0
  32. package/dist/__tests__/composables/autoGroup/tokenize.test.d.ts +1 -0
  33. package/dist/__tests__/composables/useAutoGroupInputSources.test.d.ts +1 -0
  34. package/dist/__tests__/composables/useScheduleCalendarLayout.test.d.ts +1 -0
  35. package/dist/__tests__/docs/extractDocsComponents.test.d.ts +1 -0
  36. package/dist/__tests__/docs/extractDocsExports.test.d.ts +1 -0
  37. package/dist/__tests__/docs/extractDocsParsing.test.d.ts +1 -0
  38. package/dist/__tests__/docs/extractDocsTemplates.test.d.ts +1 -0
  39. package/dist/__tests__/docs/extractDocsTheme.test.d.ts +1 -0
  40. package/dist/components/AppTopBar.navigation.d.ts +11 -0
  41. package/dist/components/BaseButton.vue.d.ts +1 -1
  42. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  43. package/dist/components/BaseInput.vue.d.ts +2 -2
  44. package/dist/components/BasePill.vue.d.ts +1 -1
  45. package/dist/components/BaseRadioGroup.vue.d.ts +1 -1
  46. package/dist/components/BaseSelect.vue.d.ts +1 -1
  47. package/dist/components/BaseSlider.vue.d.ts +2 -2
  48. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  49. package/dist/components/BaseToggle.vue.d.ts +1 -1
  50. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  51. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  52. package/dist/components/ColorSlider.vue.d.ts +2 -2
  53. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  54. package/dist/components/DatePicker.vue.d.ts +1 -1
  55. package/dist/components/DateTimePicker.vue.d.ts +2 -2
  56. package/dist/components/DoseCalculatorVolumeField.vue.d.ts +15 -0
  57. package/dist/components/DropdownButton.vue.d.ts +1 -1
  58. package/dist/components/FileUploader.vue.d.ts +2 -2
  59. package/dist/components/FormulaInput.vue.d.ts +2 -2
  60. package/dist/components/IconButton.vue.d.ts +1 -1
  61. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  62. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  63. package/dist/components/MultiSelect.vue.d.ts +1 -1
  64. package/dist/components/NumberInput.vue.d.ts +1 -1
  65. package/dist/components/PlateMapEditor.vue.d.ts +6 -6
  66. package/dist/components/PluginWorkspaceView.controls.d.ts +28 -0
  67. package/dist/components/PluginWorkspaceView.navigation.d.ts +29 -0
  68. package/dist/components/PluginWorkspaceView.props.d.ts +151 -0
  69. package/dist/components/PluginWorkspaceView.shell.d.ts +19 -0
  70. package/dist/components/PluginWorkspaceView.vue.d.ts +46 -195
  71. package/dist/components/ProgressBar.vue.d.ts +1 -1
  72. package/dist/components/ProtocolStep.presentation.d.ts +4 -0
  73. package/dist/components/ProtocolStepEditor.state.d.ts +18 -0
  74. package/dist/components/ProtocolStepParameterField.vue.d.ts +12 -0
  75. package/dist/components/ReagentList.presentation.d.ts +16 -0
  76. package/dist/components/ResourceCard.vue.d.ts +1 -1
  77. package/dist/components/SampleSelector.colors.d.ts +13 -0
  78. package/dist/components/SampleSelector.drag.d.ts +24 -0
  79. package/dist/components/SampleSelector.groups.d.ts +15 -0
  80. package/dist/components/SampleSelector.selection.d.ts +26 -0
  81. package/dist/components/SampleSelector.vue.d.ts +4 -1
  82. package/dist/components/SampleSelectorSampleRow.vue.d.ts +21 -0
  83. package/dist/components/SegmentedControl.vue.d.ts +1 -1
  84. package/dist/components/SequenceInput.vue.d.ts +2 -2
  85. package/dist/components/SequenceProgressBar.vue.d.ts +1 -1
  86. package/dist/components/SettingsModal.schema.d.ts +9 -0
  87. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  88. package/dist/components/TagsInput.vue.d.ts +2 -2
  89. package/dist/components/TimePicker.vue.d.ts +2 -2
  90. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  91. package/dist/components/UnitInput.vue.d.ts +2 -2
  92. package/dist/components/WellPlate.colors.d.ts +9 -0
  93. package/dist/components/WellPlate.conditions.d.ts +26 -0
  94. package/dist/components/WellPlate.geometry.d.ts +23 -0
  95. package/dist/components/WellPlate.interaction.d.ts +71 -0
  96. package/dist/components/WellPlate.legend.d.ts +2 -0
  97. package/dist/components/WellPlate.rendering.d.ts +24 -0
  98. package/dist/components/WellPlate.sampleDrop.d.ts +8 -0
  99. package/dist/components/WellPlate.vue.d.ts +1 -1
  100. package/dist/components/index.js +2 -2
  101. package/dist/components/internal/ActionItemInternal.vue.d.ts +1 -1
  102. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +28 -0
  103. package/dist/{components-BhK-dW99.js → components-DtHA2bgp.js} +3754 -2991
  104. package/dist/components-DtHA2bgp.js.map +1 -0
  105. package/dist/composables/autoGroup/classKey.d.ts +4 -0
  106. package/dist/composables/autoGroup/classify.d.ts +28 -0
  107. package/dist/composables/autoGroup/colors.d.ts +2 -0
  108. package/dist/composables/autoGroup/columns.d.ts +10 -0
  109. package/dist/composables/autoGroup/compose.d.ts +8 -0
  110. package/dist/composables/autoGroup/cooccurrence.d.ts +2 -0
  111. package/dist/composables/autoGroup/csv-shim.d.ts +2 -0
  112. package/dist/composables/autoGroup/fingerprint.d.ts +3 -0
  113. package/dist/composables/autoGroup/index.d.ts +16 -0
  114. package/dist/composables/autoGroup/replicatePreGroup.d.ts +38 -0
  115. package/dist/composables/autoGroup/template.d.ts +15 -0
  116. package/dist/composables/autoGroup/tokenize.d.ts +8 -0
  117. package/dist/composables/autoGroupConstants.d.ts +1 -0
  118. package/dist/composables/autoGroupGrouping.d.ts +3 -0
  119. package/dist/composables/controlComponentBindings.d.ts +7 -0
  120. package/dist/composables/controlSchemaAdapters.d.ts +20 -0
  121. package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
  122. package/dist/composables/controlSchemaFormFields.d.ts +3 -0
  123. package/dist/composables/controlSchemaLayout.d.ts +7 -0
  124. package/dist/composables/controlSchemaModel.d.ts +5 -0
  125. package/dist/composables/controlSchemaNormalize.d.ts +15 -0
  126. package/dist/composables/controlSchemaTypes.d.ts +305 -0
  127. package/dist/composables/controlSchemaUtils.d.ts +9 -0
  128. package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
  129. package/dist/composables/formBuilderSchema.d.ts +18 -0
  130. package/dist/composables/index.js +3 -3
  131. package/dist/composables/pluginEndpointBuilder.d.ts +13 -0
  132. package/dist/composables/protocolTemplateCatalog.d.ts +26 -0
  133. package/dist/composables/useAutoGroup.d.ts +61 -74
  134. package/dist/composables/useAutoGroupInputSources.d.ts +32 -0
  135. package/dist/composables/useBioTemplateControls.d.ts +1 -1
  136. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +1 -1
  137. package/dist/composables/useBioTemplateWorkspace.d.ts +1 -1
  138. package/dist/composables/useControlSchema.d.ts +8 -346
  139. package/dist/composables/useControlWorkspace.d.ts +5 -0
  140. package/dist/composables/useForm.d.ts +2 -33
  141. package/dist/composables/useFormBuilder.d.ts +2 -9
  142. package/dist/composables/useFormValidation.d.ts +34 -0
  143. package/dist/composables/usePluginClient.d.ts +1 -4
  144. package/dist/composables/useProtocolTemplates.d.ts +2 -24
  145. package/dist/composables/useScheduleCalendarLayout.d.ts +49 -0
  146. package/dist/{composables-Bg7CFuNz.js → composables-Dlg8jenH.js} +33 -31
  147. package/dist/composables-Dlg8jenH.js.map +1 -0
  148. package/dist/index.js +4 -4
  149. package/dist/install.js +2 -2
  150. package/dist/styles.css +547 -516
  151. package/dist/templates/adapters.d.ts +14 -47
  152. package/dist/templates/assayLookups.d.ts +3 -0
  153. package/dist/templates/assayMatrixAdapters.d.ts +6 -0
  154. package/dist/templates/assayMatrixBuilder.d.ts +2 -0
  155. package/dist/templates/assayNormalizers.d.ts +4 -0
  156. package/dist/templates/builderDefaults.d.ts +1 -0
  157. package/dist/templates/builderIdUtils.d.ts +4 -0
  158. package/dist/templates/builderPresetControls.d.ts +10 -0
  159. package/dist/templates/builderReadUtils.d.ts +8 -0
  160. package/dist/templates/builders.d.ts +26 -67
  161. package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
  162. package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
  163. package/dist/templates/calibrationNormalizers.d.ts +5 -0
  164. package/dist/templates/componentBindingCatalog.d.ts +25 -0
  165. package/dist/templates/componentBindingHelpers.d.ts +17 -0
  166. package/dist/templates/componentDoseResponseProps.d.ts +3 -0
  167. package/dist/templates/componentGenericProps.d.ts +8 -0
  168. package/dist/templates/componentPlateHelpers.d.ts +5 -0
  169. package/dist/templates/componentPlateMapProps.d.ts +3 -0
  170. package/dist/templates/componentPropsFactory.d.ts +3 -0
  171. package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
  172. package/dist/templates/componentTargetResolvers.d.ts +5 -0
  173. package/dist/templates/componentTemplateProps.d.ts +3 -0
  174. package/dist/templates/controlSchemaClone.d.ts +4 -0
  175. package/dist/templates/controlSchemaConstants.d.ts +10 -0
  176. package/dist/templates/controlSchemaMerge.d.ts +3 -0
  177. package/dist/templates/controlSchemaTargets.d.ts +17 -0
  178. package/dist/templates/controlSchemaTypes.d.ts +4 -0
  179. package/dist/templates/controlSchemas.d.ts +4 -4
  180. package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
  181. package/dist/templates/doseResponseAdapters.d.ts +4 -0
  182. package/dist/templates/doseResponseBuilder.d.ts +2 -0
  183. package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
  184. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
  185. package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
  186. package/dist/templates/flowNormalizers.d.ts +8 -0
  187. package/dist/templates/flowPanelAdapters.d.ts +4 -0
  188. package/dist/templates/index.js +1 -1
  189. package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
  190. package/dist/templates/instrumentRunAdapters.d.ts +8 -0
  191. package/dist/templates/instrumentRunBuilder.d.ts +2 -0
  192. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
  193. package/dist/templates/plateGeometry.d.ts +4 -0
  194. package/dist/templates/plateMapAdapters.d.ts +3 -0
  195. package/dist/templates/plateMapBuilder.d.ts +2 -0
  196. package/dist/templates/presetControlSchemas.d.ts +534 -0
  197. package/dist/templates/protocolAdapters.d.ts +5 -0
  198. package/dist/templates/protocolNormalizers.d.ts +6 -0
  199. package/dist/templates/protocolStepsBuilder.d.ts +2 -0
  200. package/dist/templates/qpcrAdapters.d.ts +5 -0
  201. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
  202. package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
  203. package/dist/templates/reagentAdapters.d.ts +5 -0
  204. package/dist/templates/reagentListBuilder.d.ts +2 -0
  205. package/dist/templates/runNormalizers.d.ts +10 -0
  206. package/dist/templates/sampleNormalizers.d.ts +4 -0
  207. package/dist/templates/samplePrepAdapters.d.ts +4 -0
  208. package/dist/templates/samplePrepBuilder.d.ts +2 -0
  209. package/dist/templates/sampleSheetAdapters.d.ts +5 -0
  210. package/dist/templates/sampleSheetBuilder.d.ts +2 -0
  211. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
  212. package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
  213. package/dist/templates/templateAdapterTypes.d.ts +48 -0
  214. package/dist/templates/templateControlSchemas.d.ts +400 -0
  215. package/dist/templates/templateCreateOptions.d.ts +165 -0
  216. package/dist/templates/templateEnvelopes.d.ts +9 -0
  217. package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
  218. package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
  219. package/dist/templates/templateQpcrTypes.d.ts +42 -0
  220. package/dist/templates/templateValidators.d.ts +13 -0
  221. package/dist/templates/timeCourseAdapters.d.ts +5 -0
  222. package/dist/templates/timeCourseBuilder.d.ts +2 -0
  223. package/dist/templates/types.d.ts +5 -250
  224. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
  225. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
  226. package/dist/{templates-BorLR_7p.js → templates-DtdUvJ4c.js} +3565 -3411
  227. package/dist/templates-DtdUvJ4c.js.map +1 -0
  228. package/dist/types/auto-group.d.ts +79 -9
  229. package/dist/types/componentLabTypes.d.ts +161 -0
  230. package/dist/types/componentWorkflowTypes.d.ts +150 -0
  231. package/dist/types/components.d.ts +2 -311
  232. package/dist/{useProtocolTemplates-n6AJqSqv.js → useProtocolTemplates-Bm5vyH4_.js} +1220 -454
  233. package/dist/useProtocolTemplates-Bm5vyH4_.js.map +1 -0
  234. package/package.json +1 -1
  235. package/src/__tests__/components/AppTopBar.navigation.test.ts +70 -0
  236. package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +53 -0
  237. package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +54 -0
  238. package/src/__tests__/components/PluginWorkspaceView.controls.test.ts +156 -0
  239. package/src/__tests__/components/PluginWorkspaceView.navigation.test.ts +102 -0
  240. package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +41 -0
  241. package/src/__tests__/components/ProtocolStep.presentation.test.ts +31 -0
  242. package/src/__tests__/components/ProtocolStepEditor.state.test.ts +165 -0
  243. package/src/__tests__/components/ProtocolStepParameterField.test.ts +44 -0
  244. package/src/__tests__/components/ReagentList.presentation.test.ts +68 -0
  245. package/src/__tests__/components/SampleSelector.colors.test.ts +49 -0
  246. package/src/__tests__/components/SampleSelector.drag.test.ts +100 -0
  247. package/src/__tests__/components/SampleSelector.groups.test.ts +81 -0
  248. package/src/__tests__/components/SampleSelector.selection.test.ts +70 -0
  249. package/src/__tests__/components/SampleSelector.test.ts +32 -0
  250. package/src/__tests__/components/SampleSelectorSampleRow.test.ts +37 -0
  251. package/src/__tests__/components/ScheduleCalendar.test.ts +44 -0
  252. package/src/__tests__/components/SettingsModal.schema.test.ts +97 -0
  253. package/src/__tests__/components/WellPlate.colors.test.ts +28 -0
  254. package/src/__tests__/components/WellPlate.conditions.test.ts +68 -0
  255. package/src/__tests__/components/WellPlate.geometry.test.ts +54 -0
  256. package/src/__tests__/components/WellPlate.interaction.test.ts +171 -0
  257. package/src/__tests__/components/WellPlate.legend.test.ts +13 -0
  258. package/src/__tests__/components/WellPlate.rendering.test.ts +122 -0
  259. package/src/__tests__/components/WellPlate.sampleDrop.test.ts +70 -0
  260. package/src/__tests__/composables/autoGroup/classify.test.ts +107 -0
  261. package/src/__tests__/composables/autoGroup/columns.test.ts +135 -0
  262. package/src/__tests__/composables/autoGroup/compose.test.ts +227 -0
  263. package/src/__tests__/composables/autoGroup/cooccurrence.test.ts +91 -0
  264. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +50 -0
  265. package/src/__tests__/composables/autoGroup/integration.test.ts +79 -0
  266. package/src/__tests__/composables/autoGroup/template.test.ts +70 -0
  267. package/src/__tests__/composables/autoGroup/tokenize.test.ts +33 -0
  268. package/src/__tests__/composables/useAutoGroup.test.ts +129 -625
  269. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +107 -0
  270. package/src/__tests__/composables/useControlSchema.test.ts +23 -0
  271. package/src/__tests__/composables/useScheduleCalendarLayout.test.ts +89 -0
  272. package/src/__tests__/docs/extractDocsComponents.test.ts +142 -0
  273. package/src/__tests__/docs/extractDocsExports.test.ts +77 -0
  274. package/src/__tests__/docs/extractDocsParsing.test.ts +69 -0
  275. package/src/__tests__/docs/extractDocsTemplates.test.ts +54 -0
  276. package/src/__tests__/docs/extractDocsTheme.test.ts +89 -0
  277. package/src/__tests__/docs/frontendDocsCatalog.test.ts +1 -1
  278. package/src/__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt +187 -0
  279. package/src/components/AppSidebar.vue +2 -6
  280. package/src/components/AppTopBar.navigation.ts +62 -0
  281. package/src/components/AppTopBar.vue +17 -44
  282. package/src/components/AutoGroupModal.story.vue +50 -0
  283. package/src/components/AutoGroupModal.vue +441 -158
  284. package/src/components/ControlWorkspaceView.vue +2 -6
  285. package/src/components/DoseCalculator.vue +13 -73
  286. package/src/components/DoseCalculatorVolumeField.vue +61 -0
  287. package/src/components/ExperimentTimeline.vue +6 -31
  288. package/src/components/FormBuilder.vue +2 -7
  289. package/src/components/PlateMapEditor.vue +32 -106
  290. package/src/components/PluginWorkspaceView.controls.ts +182 -0
  291. package/src/components/PluginWorkspaceView.navigation.ts +106 -0
  292. package/src/components/PluginWorkspaceView.props.ts +174 -0
  293. package/src/components/PluginWorkspaceView.shell.ts +66 -0
  294. package/src/components/PluginWorkspaceView.vue +85 -404
  295. package/src/components/ProtocolStep.presentation.ts +31 -0
  296. package/src/components/ProtocolStepEditor.state.ts +104 -0
  297. package/src/components/ProtocolStepEditor.vue +48 -179
  298. package/src/components/ProtocolStepParameterField.vue +134 -0
  299. package/src/components/ReagentList.presentation.ts +105 -0
  300. package/src/components/ReagentList.vue +16 -79
  301. package/src/components/SampleSelector.colors.ts +43 -0
  302. package/src/components/SampleSelector.drag.ts +164 -0
  303. package/src/components/SampleSelector.groups.ts +109 -0
  304. package/src/components/SampleSelector.selection.ts +103 -0
  305. package/src/components/SampleSelector.vue +82 -349
  306. package/src/components/SampleSelectorSampleRow.vue +64 -0
  307. package/src/components/ScheduleCalendar.vue +44 -199
  308. package/src/components/SettingsModal.schema.ts +71 -0
  309. package/src/components/SettingsModal.vue +16 -46
  310. package/src/components/WellPlate.colors.ts +56 -0
  311. package/src/components/WellPlate.conditions.ts +100 -0
  312. package/src/components/WellPlate.geometry.ts +91 -0
  313. package/src/components/WellPlate.interaction.ts +272 -0
  314. package/src/components/WellPlate.legend.ts +8 -0
  315. package/src/components/WellPlate.rendering.ts +105 -0
  316. package/src/components/WellPlate.sampleDrop.ts +73 -0
  317. package/src/components/WellPlate.vue +102 -550
  318. package/src/components/internal/PlateMapEditorToolbarInternal.vue +128 -0
  319. package/src/composables/autoGroup/classKey.ts +5 -0
  320. package/src/composables/autoGroup/classify.ts +205 -0
  321. package/src/composables/autoGroup/colors.ts +6 -0
  322. package/src/composables/autoGroup/columns.ts +226 -0
  323. package/src/composables/autoGroup/compose.ts +156 -0
  324. package/src/composables/autoGroup/cooccurrence.ts +46 -0
  325. package/src/composables/autoGroup/csv-shim.ts +44 -0
  326. package/src/composables/autoGroup/fingerprint.ts +49 -0
  327. package/src/composables/autoGroup/index.ts +20 -0
  328. package/src/composables/autoGroup/replicatePreGroup.ts +90 -0
  329. package/src/composables/autoGroup/template.ts +126 -0
  330. package/src/composables/autoGroup/tokenize.ts +41 -0
  331. package/src/composables/autoGroup/vocab.json +67 -0
  332. package/src/composables/autoGroupConstants.ts +4 -0
  333. package/src/composables/autoGroupGrouping.ts +148 -0
  334. package/src/composables/controlComponentBindings.ts +80 -0
  335. package/src/composables/controlSchemaAdapters.ts +196 -0
  336. package/src/composables/controlSchemaDoseDesign.ts +215 -0
  337. package/src/composables/controlSchemaFormFields.ts +61 -0
  338. package/src/composables/controlSchemaLayout.ts +59 -0
  339. package/src/composables/controlSchemaModel.ts +163 -0
  340. package/src/composables/controlSchemaNormalize.ts +101 -0
  341. package/src/composables/controlSchemaTypes.ts +364 -0
  342. package/src/composables/controlSchemaUtils.ts +36 -0
  343. package/src/composables/controlWorkspaceOptions.ts +21 -0
  344. package/src/composables/formBuilderSchema.ts +153 -0
  345. package/src/composables/pluginEndpointBuilder.ts +203 -0
  346. package/src/composables/protocolTemplateCatalog.ts +325 -0
  347. package/src/composables/useAutoGroup.ts +395 -549
  348. package/src/composables/useAutoGroupInputSources.ts +147 -0
  349. package/src/composables/useBioTemplateControls.ts +1 -1
  350. package/src/composables/useBioTemplatePresetWorkspace.ts +1 -1
  351. package/src/composables/useBioTemplateWorkspace.ts +1 -1
  352. package/src/composables/useControlSchema.ts +64 -1312
  353. package/src/composables/useControlWorkspace.ts +201 -0
  354. package/src/composables/useForm.ts +5 -187
  355. package/src/composables/useFormBuilder.ts +11 -153
  356. package/src/composables/useFormValidation.ts +154 -0
  357. package/src/composables/usePluginClient.ts +10 -193
  358. package/src/composables/useProtocolTemplates.ts +10 -328
  359. package/src/composables/useScheduleCalendarLayout.ts +287 -0
  360. package/src/styles/components/auto-group-modal.css +248 -310
  361. package/src/templates/adapters.ts +89 -930
  362. package/src/templates/assayLookups.ts +33 -0
  363. package/src/templates/assayMatrixAdapters.ts +78 -0
  364. package/src/templates/assayMatrixBuilder.ts +59 -0
  365. package/src/templates/assayNormalizers.ts +34 -0
  366. package/src/templates/builderDefaults.ts +11 -0
  367. package/src/templates/builderIdUtils.ts +20 -0
  368. package/src/templates/builderPresetControls.ts +165 -0
  369. package/src/templates/builderReadUtils.ts +57 -0
  370. package/src/templates/builders.ts +122 -2350
  371. package/src/templates/calibrationCurveAdapters.ts +59 -0
  372. package/src/templates/calibrationCurveBuilder.ts +99 -0
  373. package/src/templates/calibrationNormalizers.ts +60 -0
  374. package/src/templates/componentBindingCatalog.ts +90 -0
  375. package/src/templates/componentBindingHelpers.ts +93 -0
  376. package/src/templates/componentBindings.ts +12 -461
  377. package/src/templates/componentDoseResponseProps.ts +42 -0
  378. package/src/templates/componentGenericProps.ts +77 -0
  379. package/src/templates/componentPlateHelpers.ts +29 -0
  380. package/src/templates/componentPlateMapProps.ts +32 -0
  381. package/src/templates/componentPropsFactory.ts +21 -0
  382. package/src/templates/componentQpcrPlateProps.ts +28 -0
  383. package/src/templates/componentTargetResolvers.ts +69 -0
  384. package/src/templates/componentTemplateProps.ts +78 -0
  385. package/src/templates/controlSchemaClone.ts +32 -0
  386. package/src/templates/controlSchemaConstants.ts +11 -0
  387. package/src/templates/controlSchemaMerge.ts +40 -0
  388. package/src/templates/controlSchemaTargets.ts +87 -0
  389. package/src/templates/controlSchemaTypes.ts +20 -0
  390. package/src/templates/controlSchemas.ts +22 -704
  391. package/src/templates/defaultBioTemplateBuilder.ts +124 -0
  392. package/src/templates/doseResponseAdapters.ts +45 -0
  393. package/src/templates/doseResponseBuilder.ts +44 -0
  394. package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
  395. package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
  396. package/src/templates/flowCytometryPanelBuilder.ts +53 -0
  397. package/src/templates/flowNormalizers.ts +58 -0
  398. package/src/templates/flowPanelAdapters.ts +58 -0
  399. package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
  400. package/src/templates/instrumentRunAdapters.ts +163 -0
  401. package/src/templates/instrumentRunBuilder.ts +97 -0
  402. package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
  403. package/src/templates/plateGeometry.ts +62 -0
  404. package/src/templates/plateMapAdapters.ts +36 -0
  405. package/src/templates/plateMapBuilder.ts +43 -0
  406. package/src/templates/presetControlSchemas.ts +258 -0
  407. package/src/templates/protocolAdapters.ts +69 -0
  408. package/src/templates/protocolNormalizers.ts +37 -0
  409. package/src/templates/protocolStepsBuilder.ts +36 -0
  410. package/src/templates/qpcrAdapters.ts +104 -0
  411. package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
  412. package/src/templates/qpcrPlateBuilder.ts +96 -0
  413. package/src/templates/reagentAdapters.ts +77 -0
  414. package/src/templates/reagentListBuilder.ts +30 -0
  415. package/src/templates/runNormalizers.ts +63 -0
  416. package/src/templates/sampleNormalizers.ts +58 -0
  417. package/src/templates/samplePrepAdapters.ts +63 -0
  418. package/src/templates/samplePrepBuilder.ts +51 -0
  419. package/src/templates/sampleSheetAdapters.ts +75 -0
  420. package/src/templates/sampleSheetBuilder.ts +23 -0
  421. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
  422. package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
  423. package/src/templates/templateAdapterTypes.ts +58 -0
  424. package/src/templates/templateControlSchemas.ts +320 -0
  425. package/src/templates/templateCreateOptions.ts +208 -0
  426. package/src/templates/templateEnvelopes.ts +137 -0
  427. package/src/templates/templatePackCollectionBuilder.ts +23 -0
  428. package/src/templates/templatePresetCollectionBuilder.ts +139 -0
  429. package/src/templates/templateQpcrTypes.ts +48 -0
  430. package/src/templates/templateValidators.ts +414 -0
  431. package/src/templates/timeCourseAdapters.ts +73 -0
  432. package/src/templates/timeCourseBuilder.ts +64 -0
  433. package/src/templates/types.ts +79 -275
  434. package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
  435. package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
  436. package/src/types/auto-group.ts +107 -9
  437. package/src/types/componentLabTypes.ts +235 -0
  438. package/src/types/componentWorkflowTypes.ts +190 -0
  439. package/src/types/components.ts +74 -424
  440. package/dist/components-BhK-dW99.js.map +0 -1
  441. package/dist/composables-Bg7CFuNz.js.map +0 -1
  442. package/dist/templates-BorLR_7p.js.map +0 -1
  443. package/dist/useProtocolTemplates-n6AJqSqv.js.map +0 -1
@@ -1,2350 +1,122 @@
1
- import type {
2
- AssayFeature,
3
- AssayMatrixTemplate,
4
- AssayMatrixTemplateData,
5
- AssaySample,
6
- BioTemplateEnvelope,
7
- CalibrationAcceptance,
8
- CalibrationCurveTemplate,
9
- CalibrationCurveTemplateData,
10
- CalibrationPoint,
11
- CalibrationPointInput,
12
- CompoundDoseSeries,
13
- ControlDefinition,
14
- CreateBioTemplatePackCollectionOptions,
15
- CreateElisaAssayCollectionOptions,
16
- CreateLcmsBatchCollectionOptions,
17
- CreateTargetedMetabolomicsCollectionOptions,
18
- CreateAssayMatrixTemplateOptions,
19
- CreateCalibrationCurveTemplateOptions,
20
- CreateDoseResponseTemplateOptions,
21
- CreateFlowCytometryAssayCollectionOptions,
22
- CreateWesternBlotAssayCollectionOptions,
23
- CreateFlowCytometryPanelTemplateOptions,
24
- CreateInstrumentRunTemplateOptions,
25
- CreatePlateMapTemplateOptions,
26
- CreateProtocolStepsTemplateOptions,
27
- CreateQpcrExpressionCollectionOptions,
28
- CreateQpcrPlateTemplateOptions,
29
- CreateReagentListTemplateOptions,
30
- CreateSampleSheetTemplateOptions,
31
- CreateSamplePrepTemplateOptions,
32
- CreateTimeCourseTemplateOptions,
33
- CreateWellPlateScreenCollectionOptions,
34
- DoseResponseTemplate,
35
- DoseResponseTemplateData,
36
- FlowCytometryPanelTemplate,
37
- FlowCytometryPanelTemplateData,
38
- FlowPanelControl,
39
- FlowPanelMarker,
40
- InstrumentMethod,
41
- InstrumentRunItem,
42
- InstrumentRunItemInput,
43
- InstrumentRunTemplate,
44
- InstrumentRunTemplateData,
45
- PlateMapTemplate,
46
- PlateMapTemplateData,
47
- ProtocolStepRecord,
48
- ProtocolStepsTemplate,
49
- ProtocolStepsTemplateData,
50
- QpcrPlateTemplate,
51
- QpcrPlateTemplateData,
52
- QpcrReaction,
53
- QpcrSample,
54
- QpcrTarget,
55
- ReagentListTemplate,
56
- ReagentListTemplateData,
57
- ReagentRecord,
58
- ReagentTemplateInput,
59
- TimeCourseCondition,
60
- TimeCourseTemplate,
61
- TimeCourseTemplateData,
62
- TimePoint,
63
- SampleRecord,
64
- SampleSheetTemplate,
65
- SampleSheetTemplateData,
66
- SamplePrepStep,
67
- SamplePrepStepInput,
68
- SamplePrepTemplate,
69
- SamplePrepTemplateData,
70
- TemplateCollection,
71
- TemplateCollectionEnvelope,
72
- TemplateId,
73
- TemplatePackId,
74
- TemplatePresetId,
75
- TemplateSample,
76
- } from './types'
77
- import type { WellPlateFormat } from '../types'
78
- import { getBioTemplateInfo } from './catalog'
79
- import { getBioTemplatePackInfo } from './packs'
80
- import { getBioTemplatePresetInfo } from './presets'
81
-
82
- export const TEMPLATE_COLLECTION_KEY = 'templates'
83
-
84
- const DEFAULT_SAMPLE_COLORS = [
85
- '#3B82F6',
86
- '#10B981',
87
- '#EF4444',
88
- '#F59E0B',
89
- '#8B5CF6',
90
- '#F97316',
91
- '#06B6D4',
92
- '#14B8A6',
93
- '#6B7280',
94
- ]
95
-
96
- const PLATE_DIMENSIONS: Record<number, readonly [number, number]> = {
97
- 6: [2, 3],
98
- 12: [3, 4],
99
- 24: [4, 6],
100
- 48: [6, 8],
101
- 54: [6, 9],
102
- 96: [8, 12],
103
- 384: [16, 24],
104
- }
105
-
106
- export function createTemplateEnvelope<TData>(
107
- templateId: TemplateId,
108
- data: TData,
109
- metadata: Record<string, unknown> = {},
110
- templateVersion = '1.0'
111
- ): BioTemplateEnvelope<TData> {
112
- return {
113
- template_id: templateId,
114
- template_version: templateVersion,
115
- kind: 'experiment-design',
116
- data,
117
- metadata,
118
- }
119
- }
120
-
121
- export function createPlateMapTemplate(
122
- options: CreatePlateMapTemplateOptions = {}
123
- ): PlateMapTemplate {
124
- const {
125
- id = 'plate-1',
126
- name = 'Plate 1',
127
- format = 96,
128
- samples = [],
129
- metadata = {},
130
- } = options
131
-
132
- const sampleDefinitions = samples.map((sample, index): TemplateSample => {
133
- if (typeof sample !== 'string') return sample
134
- return {
135
- id: sampleIdFromName(sample, index),
136
- name: sample,
137
- color: DEFAULT_SAMPLE_COLORS[index % DEFAULT_SAMPLE_COLORS.length],
138
- }
139
- })
140
-
141
- return createTemplateEnvelope('plate-map', {
142
- plates: [
143
- {
144
- id,
145
- name,
146
- format,
147
- wells: {},
148
- },
149
- ],
150
- samples: sampleDefinitions,
151
- activePlateId: id,
152
- selectedWells: [],
153
- }, metadata)
154
- }
155
-
156
- export function createSampleSheetTemplate(
157
- options: CreateSampleSheetTemplateOptions
158
- ): SampleSheetTemplate {
159
- const data: SampleSheetTemplateData = {
160
- samples: options.samples.map(normalizeSampleRecord),
161
- columns: options.columns ?? [
162
- { id: 'sampleId', label: 'Sample ID', type: 'string', required: true, groupable: false },
163
- { id: 'name', label: 'Name', type: 'string', required: false, groupable: false },
164
- ],
165
- groups: options.groups ?? [],
166
- }
167
- validateSampleSheetData(data)
168
- return createTemplateEnvelope('sample-sheet', data, options.metadata ?? {})
169
- }
170
-
171
- export function createSamplePrepTemplate(
172
- options: CreateSamplePrepTemplateOptions
173
- ): SamplePrepTemplate {
174
- const prepType = options.prepType ?? 'extraction'
175
- const volumeUnit = options.volumeUnit ?? 'uL'
176
- const steps: SamplePrepStep[] = []
177
- let order = 1
178
-
179
- for (const [index, sample] of options.samples.entries()) {
180
- if (typeof sample !== 'string') {
181
- const step = normalizeSamplePrepStep(sample, prepType, volumeUnit, order, index)
182
- steps.push(step)
183
- order = Math.max(order, step.order + 1)
184
- continue
185
- }
186
- const sourceSampleId = idFromName(sample, `sample-${index + 1}`)
187
- steps.push({
188
- id: `${sourceSampleId}-prep`,
189
- order,
190
- type: prepType,
191
- name: `Prepare ${sample}`,
192
- sourceSampleId,
193
- destinationSampleId: `${sourceSampleId}-prepared`,
194
- inputVolume: options.inputVolume,
195
- outputVolume: options.outputVolume,
196
- volumeUnit,
197
- status: 'planned',
198
- metadata: {},
199
- })
200
- order += 1
201
- }
202
-
203
- const data: SamplePrepTemplateData = {
204
- protocolName: options.protocolName ?? 'Sample preparation',
205
- steps,
206
- metadata: options.metadata ?? {},
207
- }
208
- validateSamplePrepData(data)
209
- return createTemplateEnvelope('sample-prep', data, options.metadata ?? {})
210
- }
211
-
212
- export function createDoseResponseTemplate(
213
- options: CreateDoseResponseTemplateOptions
214
- ): DoseResponseTemplate {
215
- const compounds = Array.isArray(options.compounds)
216
- ? options.compounds.map(normalizeCompound)
217
- : Object.entries(options.compounds).map(([name, concentrations], index) => normalizeCompound({
218
- id: compoundIdFromName(name, index),
219
- name,
220
- concentrations,
221
- metadata: {},
222
- }))
223
-
224
- const layout = options.layout
225
- ? isEnvelope<PlateMapTemplateData>(options.layout) ? options.layout.data : options.layout
226
- : undefined
227
-
228
- const data: DoseResponseTemplateData = {
229
- compounds,
230
- unit: options.unit,
231
- replicates: options.replicates ?? 1,
232
- controls: (options.controls ?? []).map(normalizeControl),
233
- layout,
234
- metadata: options.metadata ?? {},
235
- }
236
- validateDoseResponseData(data)
237
- return createTemplateEnvelope('dose-response', data, options.metadata ?? {})
238
- }
239
-
240
- export function createCalibrationCurveTemplate(
241
- options: CreateCalibrationCurveTemplateOptions
242
- ): CalibrationCurveTemplate {
243
- const unit = options.unit ?? 'uM'
244
- const points: CalibrationPoint[] = []
245
- let order = 1
246
-
247
- if (options.includeBlank !== false) {
248
- points.push({
249
- id: 'blank',
250
- order,
251
- role: 'blank',
252
- level: 'Blank',
253
- concentration: 0,
254
- unit,
255
- replicate: 1,
256
- include: true,
257
- status: 'planned',
258
- metadata: {},
259
- })
260
- order += 1
261
- }
262
-
263
- const standardValues: number[] = []
264
- for (const [index, concentration] of options.concentrations.entries()) {
265
- if (typeof concentration !== 'number') {
266
- const point = normalizeCalibrationPoint(concentration, unit, order, index)
267
- points.push(point)
268
- if (point.role === 'standard') standardValues.push(point.concentration)
269
- order = Math.max(order, point.order + 1)
270
- continue
271
- }
272
- standardValues.push(concentration)
273
- points.push({
274
- id: `std-${index + 1}`,
275
- order,
276
- role: 'standard',
277
- level: `Standard ${index + 1}`,
278
- concentration,
279
- unit,
280
- replicate: 1,
281
- include: true,
282
- status: 'planned',
283
- metadata: {},
284
- })
285
- order += 1
286
- }
287
-
288
- if (options.includeQc !== false) {
289
- const positiveValues = [...standardValues].filter(value => value > 0).sort((a, b) => a - b)
290
- const qcValues = positiveValues.length === 0
291
- ? []
292
- : positiveValues[0] === positiveValues[positiveValues.length - 1]
293
- ? [positiveValues[0]]
294
- : [positiveValues[0], positiveValues[positiveValues.length - 1]]
295
- for (const [index, concentration] of qcValues.entries()) {
296
- const level = index === 0 ? 'QC low' : 'QC high'
297
- points.push({
298
- id: idFromName(level, `qc-${index + 1}`),
299
- order,
300
- role: 'qc',
301
- level,
302
- concentration,
303
- unit,
304
- replicate: 1,
305
- include: true,
306
- status: 'planned',
307
- metadata: {},
308
- })
309
- order += 1
310
- }
311
- }
312
-
313
- const data: CalibrationCurveTemplateData = {
314
- analyte: options.analyte ?? 'Analyte',
315
- model: options.model ?? 'linear',
316
- xUnit: unit,
317
- responseUnit: options.responseUnit ?? 'response',
318
- points,
319
- acceptance: normalizeCalibrationAcceptance(options.acceptance, options.includeBlank !== false),
320
- metadata: options.metadata ?? {},
321
- }
322
- validateCalibrationCurveData(data)
323
- return createTemplateEnvelope('calibration-curve', data, options.metadata ?? {})
324
- }
325
-
326
- export function createTimeCourseTemplate(
327
- options: CreateTimeCourseTemplateOptions
328
- ): TimeCourseTemplate {
329
- const timepoints = options.timepoints.map((timepoint, index): TimePoint => {
330
- if (typeof timepoint !== 'number') return normalizeTimePoint(timepoint)
331
- return {
332
- id: timepointId(timepoint, options.unit, index),
333
- label: `${timepoint} ${options.unit}`,
334
- value: timepoint,
335
- unit: options.unit,
336
- metadata: {},
337
- }
338
- }).sort((a, b) => a.value - b.value)
339
-
340
- const conditions = options.conditions.map((condition, index): TimeCourseCondition => {
341
- if (typeof condition !== 'string') return normalizeCondition(condition)
342
- return {
343
- id: idFromName(condition, `condition-${index + 1}`),
344
- name: condition,
345
- color: DEFAULT_SAMPLE_COLORS[index % DEFAULT_SAMPLE_COLORS.length],
346
- metadata: {},
347
- }
348
- })
349
-
350
- const samples = conditions.flatMap(condition =>
351
- timepoints.flatMap(timepoint =>
352
- Array.from({ length: options.replicates ?? 1 }, (_, index) => ({
353
- sampleId: `${condition.id}-${timepoint.id}-r${index + 1}`,
354
- conditionId: condition.id,
355
- timepointId: timepoint.id,
356
- replicate: index + 1,
357
- metadata: {},
358
- }))
359
- )
360
- )
361
-
362
- const data: TimeCourseTemplateData = {
363
- timepoints,
364
- conditions,
365
- samples,
366
- metadata: options.metadata ?? {},
367
- }
368
- validateTimeCourseData(data)
369
- return createTemplateEnvelope('time-course', data, options.metadata ?? {})
370
- }
371
-
372
- export function createProtocolStepsTemplate(
373
- options: CreateProtocolStepsTemplateOptions
374
- ): ProtocolStepsTemplate {
375
- const steps = options.steps.map((step, index): ProtocolStepRecord => {
376
- if (typeof step === 'string') {
377
- return {
378
- id: idFromName(step, `step-${index + 1}`),
379
- type: 'custom',
380
- name: step,
381
- status: 'pending',
382
- parameters: {},
383
- order: index,
384
- metadata: {},
385
- }
386
- }
387
- return normalizeProtocolStep(step, index)
388
- }).sort((a, b) => a.order - b.order)
389
-
390
- const data: ProtocolStepsTemplateData = {
391
- steps,
392
- metadata: options.metadata ?? {},
393
- }
394
- validateProtocolStepsData(data)
395
- return createTemplateEnvelope('protocol-steps', data, options.metadata ?? {})
396
- }
397
-
398
- export function createAssayMatrixTemplate(
399
- options: CreateAssayMatrixTemplateOptions
400
- ): AssayMatrixTemplate {
401
- const samples = options.samples.map((sample, index): AssaySample => {
402
- if (typeof sample !== 'string') return normalizeAssaySample(sample)
403
- return {
404
- sampleId: idFromName(sample, `sample-${index + 1}`),
405
- name: sample,
406
- metadata: {},
407
- }
408
- })
409
- const features = options.features.map((feature, index): AssayFeature => {
410
- if (typeof feature !== 'string') return normalizeAssayFeature(feature)
411
- return {
412
- id: idFromName(feature, `feature-${index + 1}`),
413
- name: feature,
414
- type: 'readout',
415
- metadata: {},
416
- }
417
- })
418
- const sampleLookup = createAssaySampleLookup(samples)
419
- const featureLookup = createAssayFeatureLookup(features)
420
- const measurements = Object.entries(options.values ?? {}).flatMap(([sampleId, featureValues]) =>
421
- Object.entries(featureValues).map(([featureId, value]) => ({
422
- sampleId: sampleLookup.get(sampleId) ?? sampleId,
423
- featureId: featureLookup.get(featureId) ?? featureId,
424
- value,
425
- metadata: {},
426
- }))
427
- )
428
-
429
- const data: AssayMatrixTemplateData = {
430
- samples,
431
- features,
432
- measurements,
433
- metadata: options.metadata ?? {},
434
- }
435
- validateAssayMatrixData(data)
436
- return createTemplateEnvelope('assay-matrix', data, options.metadata ?? {})
437
- }
438
-
439
- export function createReagentListTemplate(
440
- options: CreateReagentListTemplateOptions
441
- ): ReagentListTemplate {
442
- const reagents = options.reagents.map((reagent, index): ReagentRecord => {
443
- if (typeof reagent !== 'string') return normalizeReagent(reagent)
444
- return {
445
- id: idFromName(reagent, `reagent-${index + 1}`),
446
- name: reagent,
447
- kind: 'reagent',
448
- metadata: {},
449
- }
450
- })
451
- const data: ReagentListTemplateData = {
452
- reagents,
453
- metadata: options.metadata ?? {},
454
- }
455
- validateReagentListData(data)
456
- return createTemplateEnvelope('reagent-list', data, options.metadata ?? {})
457
- }
458
-
459
- export function createFlowCytometryPanelTemplate(
460
- options: CreateFlowCytometryPanelTemplateOptions
461
- ): FlowCytometryPanelTemplate {
462
- const markers = options.markers.map((marker, index): FlowPanelMarker => {
463
- if (typeof marker !== 'string') return normalizeFlowMarker(marker, index)
464
- return {
465
- id: idFromName(marker, `marker-${index + 1}`),
466
- marker,
467
- fluorophore: 'unassigned',
468
- purpose: 'phenotype',
469
- compensationRequired: true,
470
- metadata: {},
471
- }
472
- })
473
- const controls = options.controls === undefined
474
- ? options.includeDefaultControls === false
475
- ? []
476
- : defaultFlowControls(markers)
477
- : options.controls.map((control, index): FlowPanelControl => {
478
- if (typeof control !== 'string') return normalizeFlowControl(control, index)
479
- return {
480
- id: idFromName(control, `control-${index + 1}`),
481
- name: control,
482
- kind: 'other',
483
- required: true,
484
- metadata: {},
485
- }
486
- })
487
- const data: FlowCytometryPanelTemplateData = {
488
- markers,
489
- controls,
490
- instrument: options.instrument,
491
- metadata: options.metadata ?? {},
492
- }
493
- validateFlowCytometryPanelData(data)
494
- return createTemplateEnvelope('flow-cytometry-panel', data, options.metadata ?? {})
495
- }
496
-
497
- export function createInstrumentRunTemplate(
498
- options: CreateInstrumentRunTemplateOptions
499
- ): InstrumentRunTemplate {
500
- const method = typeof options.method === 'string' || options.method === undefined
501
- ? {
502
- id: idFromName(options.method ?? 'Default method', 'method-1'),
503
- name: options.method ?? 'Default method',
504
- instrument: options.instrument,
505
- metadata: {},
506
- }
507
- : normalizeInstrumentMethod(options.method, options.instrument)
508
- const items: InstrumentRunItem[] = []
509
- let order = 1
510
-
511
- if (options.includeBlanks !== false) {
512
- items.push({
513
- id: 'blank-start',
514
- order,
515
- kind: 'blank',
516
- name: 'Blank start',
517
- methodId: method.id,
518
- status: 'planned',
519
- metadata: {},
520
- })
521
- order += 1
522
- }
523
-
524
- if (options.includeQc !== false) {
525
- items.push({
526
- id: 'qc-start',
527
- order,
528
- kind: 'qc',
529
- name: 'QC start',
530
- methodId: method.id,
531
- status: 'planned',
532
- metadata: {},
533
- })
534
- order += 1
535
- }
536
-
537
- for (const [index, item] of options.items.entries()) {
538
- if (typeof item !== 'string') {
539
- const normalized = normalizeInstrumentRunItem(item, method.id, order, index)
540
- items.push(normalized)
541
- order = Math.max(order, normalized.order + 1)
542
- continue
543
- }
544
- const sampleId = idFromName(item, `sample-${index + 1}`)
545
- items.push({
546
- id: `${sampleId}-run`,
547
- order,
548
- kind: 'sample',
549
- sampleId,
550
- name: item,
551
- methodId: method.id,
552
- status: 'planned',
553
- metadata: {},
554
- })
555
- order += 1
556
- }
557
-
558
- if (options.includeQc !== false) {
559
- items.push({
560
- id: 'qc-end',
561
- order,
562
- kind: 'qc',
563
- name: 'QC end',
564
- methodId: method.id,
565
- status: 'planned',
566
- metadata: {},
567
- })
568
- }
569
-
570
- const data: InstrumentRunTemplateData = {
571
- runId: 'run-1',
572
- instrument: options.instrument,
573
- methods: [method],
574
- items,
575
- metadata: options.metadata ?? {},
576
- }
577
- validateInstrumentRunData(data)
578
- return createTemplateEnvelope('instrument-run', data, options.metadata ?? {})
579
- }
580
-
581
- export function createQpcrPlateTemplate(
582
- options: CreateQpcrPlateTemplateOptions
583
- ): QpcrPlateTemplate {
584
- const format = options.format ?? 96
585
- const replicates = options.replicates ?? 3
586
- if (replicates < 1) {
587
- throw new Error('qPCR replicates must be at least 1.')
588
- }
589
-
590
- const samples = options.samples.map((sample, index): QpcrSample => {
591
- if (typeof sample !== 'string') return normalizeQpcrSample(sample)
592
- return {
593
- sampleId: idFromName(sample, `sample-${index + 1}`),
594
- name: sample,
595
- metadata: {},
596
- }
597
- })
598
- const targets = options.targets.map((target, index): QpcrTarget => {
599
- if (typeof target !== 'string') return normalizeQpcrTarget(target)
600
- return {
601
- id: idFromName(target, `target-${index + 1}`),
602
- name: target,
603
- metadata: {},
604
- }
605
- })
606
- const wells = wellIdsForFormat(format)
607
- let wellIndex = 0
608
- const reactions: QpcrReaction[] = []
609
-
610
- for (const sample of samples) {
611
- for (const target of targets) {
612
- for (let replicate = 1; replicate <= replicates; replicate += 1) {
613
- const wellId = wells[wellIndex]
614
- if (!wellId) throw new Error(`qPCR plate format ${format} does not have enough wells.`)
615
- reactions.push({
616
- id: `${sample.sampleId}-${target.id}-r${replicate}`,
617
- wellId,
618
- sampleId: sample.sampleId,
619
- targetId: target.id,
620
- replicate,
621
- controlKind: 'sample',
622
- flags: [],
623
- metadata: {},
624
- })
625
- wellIndex += 1
626
- }
627
- }
628
- }
629
-
630
- if (options.includeNoTemplateControls !== false) {
631
- for (const target of targets) {
632
- const wellId = wells[wellIndex]
633
- if (!wellId) throw new Error(`qPCR plate format ${format} does not have enough wells.`)
634
- reactions.push({
635
- id: `ntc-${target.id}`,
636
- wellId,
637
- targetId: target.id,
638
- replicate: 1,
639
- controlKind: 'no-template',
640
- flags: [],
641
- metadata: {},
642
- })
643
- wellIndex += 1
644
- }
645
- }
646
-
647
- const data: QpcrPlateTemplateData = {
648
- plateId: 'qpcr-plate-1',
649
- format,
650
- samples,
651
- targets,
652
- reactions,
653
- instrument: options.instrument,
654
- chemistry: options.chemistry ?? 'sybr',
655
- metadata: options.metadata ?? {},
656
- }
657
- validateQpcrPlateData(data)
658
- return createTemplateEnvelope('qpcr-plate', data, options.metadata ?? {})
659
- }
660
-
661
- export function createDefaultBioTemplate(templateId: TemplateId | string): BioTemplateEnvelope<unknown> {
662
- const template = getBioTemplateInfo(templateId)
663
- if (!template) {
664
- throw new Error(`Unknown bio template '${templateId}'.`)
665
- }
666
-
667
- switch (template.template_id) {
668
- case 'plate-map':
669
- return createPlateMapTemplate({
670
- name: 'Plate 1',
671
- samples: ['Control', 'Treatment'],
672
- })
673
- case 'sample-sheet':
674
- return createSampleSheetTemplate({
675
- samples: [
676
- { sampleId: 'S001', name: 'Control 1', group: 'Control' },
677
- { sampleId: 'S002', name: 'Treatment 1', group: 'Treatment' },
678
- ],
679
- })
680
- case 'sample-prep':
681
- return createSamplePrepTemplate({
682
- samples: ['S001', 'S002'],
683
- prepType: 'extraction',
684
- outputVolume: 50,
685
- })
686
- case 'dose-response':
687
- return createDoseResponseTemplate({
688
- compounds: {
689
- 'Compound A': [10, 1, 0.1],
690
- },
691
- unit: 'uM',
692
- replicates: 3,
693
- })
694
- case 'calibration-curve':
695
- return createCalibrationCurveTemplate({
696
- concentrations: [0.1, 1, 10, 100],
697
- analyte: 'Analyte',
698
- unit: 'uM',
699
- })
700
- case 'time-course':
701
- return createTimeCourseTemplate({
702
- timepoints: [0, 6, 24],
703
- unit: 'hour',
704
- conditions: ['Control', 'Treatment'],
705
- replicates: 3,
706
- })
707
- case 'protocol-steps':
708
- return createProtocolStepsTemplate({
709
- steps: [
710
- {
711
- id: 'seed-cells',
712
- type: 'addition',
713
- name: 'Seed cells',
714
- description: 'Add cells to wells or culture vessels.',
715
- duration: 15,
716
- },
717
- {
718
- id: 'incubate-overnight',
719
- type: 'incubation',
720
- name: 'Incubate overnight',
721
- duration: 960,
722
- parameters: { temperature: '37 C', co2: '5%' },
723
- },
724
- {
725
- id: 'measure-readout',
726
- type: 'measurement',
727
- name: 'Measure readout',
728
- duration: 45,
729
- },
730
- ],
731
- })
732
- case 'assay-matrix':
733
- return createAssayMatrixTemplate({
734
- samples: ['S001', 'S002'],
735
- features: ['Lactate', 'Glucose'],
736
- values: {
737
- s001: { lactate: 1.2, glucose: 5.4 },
738
- s002: { lactate: 2.1, glucose: 4.8 },
739
- },
740
- })
741
- case 'reagent-list':
742
- return createReagentListTemplate({
743
- reagents: ['DMSO', 'Compound A', 'Anti-HA antibody'],
744
- })
745
- case 'flow-cytometry-panel':
746
- return createFlowCytometryPanelTemplate({
747
- markers: ['CD3', 'CD4', 'CD8'],
748
- instrument: 'BD LSRFortessa',
749
- })
750
- case 'instrument-run':
751
- return createInstrumentRunTemplate({
752
- items: ['S001', 'S002'],
753
- instrument: 'LC-MS',
754
- method: 'Default method',
755
- })
756
- case 'qpcr-plate':
757
- return createQpcrPlateTemplate({
758
- samples: ['Control', 'Treatment'],
759
- targets: ['ACTB', 'GAPDH'],
760
- replicates: 3,
761
- instrument: 'QuantStudio',
762
- })
763
- default:
764
- throw new Error(`Unknown bio template '${templateId}'.`)
765
- }
766
- }
767
-
768
- export function createBioTemplatePackCollection(
769
- name: TemplatePackId | string,
770
- options: CreateBioTemplatePackCollectionOptions = {},
771
- ): TemplateCollectionEnvelope {
772
- const pack = getBioTemplatePackInfo(name)
773
- if (!pack) {
774
- throw new Error(`Unknown template pack '${name}'.`)
775
- }
776
-
777
- return createTemplateCollection(
778
- pack.templates.map(templateId => createDefaultBioTemplate(templateId)),
779
- { pack: pack.name, ...(options.metadata ?? {}) },
780
- )
781
- }
782
-
783
- export function createWellPlateScreenCollection(
784
- options: CreateWellPlateScreenCollectionOptions = {}
785
- ): TemplateCollectionEnvelope {
786
- const sampleRecords = presetSampleRecords(options.samples ?? ['Control', 'Treatment'])
787
- const plate = createPlateMapTemplate({
788
- name: options.plateName ?? 'Screen plate',
789
- format: options.plateFormat ?? 96,
790
- samples: sampleRecords.map((sample, index) => ({
791
- id: sample.sampleId,
792
- name: sample.name ?? sample.sampleId,
793
- color: DEFAULT_SAMPLE_COLORS[index % DEFAULT_SAMPLE_COLORS.length],
794
- })),
795
- })
796
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
797
- const doseResponse = createDoseResponseTemplate({
798
- compounds: options.compounds ?? { 'Compound A': [10, 1, 0.1] },
799
- unit: options.unit ?? 'uM',
800
- replicates: options.replicates ?? 3,
801
- layout: plate,
802
- })
803
- return createTemplateCollection(
804
- [plate, sampleSheet, doseResponse],
805
- { preset: 'wellplate-screen', ...(options.metadata ?? {}) }
806
- )
807
- }
808
-
809
- export function createQpcrExpressionCollection(
810
- options: CreateQpcrExpressionCollectionOptions = {}
811
- ): TemplateCollectionEnvelope {
812
- const sampleRecords = presetSampleRecords(options.samples ?? ['Control', 'Treatment'])
813
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
814
- const qpcr = createQpcrPlateTemplate({
815
- samples: sampleRecords.map(sample => ({
816
- sampleId: sample.sampleId,
817
- name: sample.name,
818
- group: sample.group,
819
- metadata: sample.metadata ?? {},
820
- })),
821
- targets: options.targets ?? ['ACTB', 'GAPDH'],
822
- chemistry: options.chemistry,
823
- format: options.format,
824
- includeNoTemplateControls: options.includeNoTemplateControls,
825
- replicates: options.replicates ?? 3,
826
- instrument: options.instrument,
827
- })
828
- return createTemplateCollection(
829
- [sampleSheet, qpcr],
830
- { preset: 'qpcr-expression', ...(options.metadata ?? {}) }
831
- )
832
- }
833
-
834
- export function createLcmsBatchCollection(
835
- options: CreateLcmsBatchCollectionOptions = {}
836
- ): TemplateCollectionEnvelope {
837
- const sampleRecords = presetSampleRecords(options.samples ?? ['S001', 'S002'])
838
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
839
- const instrumentRun = createInstrumentRunTemplate({
840
- items: sampleRecords.map(sample => ({
841
- sampleId: sample.sampleId,
842
- name: sample.name ?? sample.sampleId,
843
- })),
844
- method: options.method ?? 'Default method',
845
- instrument: options.instrument ?? 'LC-MS',
846
- includeQc: options.includeQc,
847
- })
848
- const assayMatrix = createAssayMatrixTemplate({
849
- samples: sampleRecords.map(sample => ({
850
- sampleId: sample.sampleId,
851
- name: sample.name,
852
- group: sample.group,
853
- metadata: sample.metadata ?? {},
854
- })),
855
- features: options.features ?? ['Glucose', 'Lactate'],
856
- })
857
- return createTemplateCollection(
858
- [sampleSheet, instrumentRun, assayMatrix],
859
- { preset: 'lcms-batch', ...(options.metadata ?? {}) }
860
- )
861
- }
862
-
863
- export function createTargetedMetabolomicsCollection(
864
- options: CreateTargetedMetabolomicsCollectionOptions = {}
865
- ): TemplateCollectionEnvelope {
866
- const sampleRecords = presetSampleRecords(options.samples ?? ['S001', 'S002'])
867
- const responseUnit = options.responseUnit ?? 'peak area ratio'
868
- const concentrationUnit = options.concentrationUnit ?? 'uM'
869
- const metaboliteFeatures = normalizeMetaboliteFeatures(
870
- options.metabolites ?? ['Glucose', 'Lactate', 'Pyruvate'],
871
- responseUnit,
872
- )
873
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
874
- const samplePrep = createSamplePrepTemplate({
875
- samples: sampleRecords.map(sample => sample.sampleId),
876
- prepType: options.prepType ?? 'extraction',
877
- protocolName: 'Metabolomics sample preparation',
878
- outputVolume: options.outputVolume ?? 50,
879
- metadata: { assay: 'targeted-metabolomics' },
880
- })
881
- const reagents = createReagentListTemplate({
882
- reagents: normalizeInternalStandardReagents(
883
- options.internalStandards ?? ['Stable isotope internal standard mix'],
884
- ),
885
- metadata: { assay: 'targeted-metabolomics' },
886
- })
887
- const calibrationCurve = createCalibrationCurveTemplate({
888
- concentrations: options.standardConcentrations ?? [0.1, 1, 10, 100],
889
- analyte: options.calibrationAnalyte ?? firstFeatureName(metaboliteFeatures, 'Metabolite panel'),
890
- unit: concentrationUnit,
891
- responseUnit,
892
- model: 'linear-weighted-1-x',
893
- includeQc: options.includeQc,
894
- metadata: { assay: 'targeted-metabolomics' },
895
- })
896
- const instrumentRun = createInstrumentRunTemplate({
897
- items: targetedMetabolomicsRunItems(
898
- sampleRecords,
899
- calibrationCurve.data.points,
900
- options.includeQc !== false,
901
- ),
902
- method: options.method ?? 'Targeted metabolomics',
903
- instrument: options.instrument ?? 'LC-MS',
904
- includeBlanks: false,
905
- includeQc: false,
906
- metadata: { assay: 'targeted-metabolomics' },
907
- })
908
- const assayMatrix = createAssayMatrixTemplate({
909
- samples: sampleRecords.map(sample => ({
910
- sampleId: sample.sampleId,
911
- name: sample.name,
912
- group: sample.group,
913
- metadata: sample.metadata ?? {},
914
- })),
915
- features: metaboliteFeatures,
916
- metadata: { assay: 'targeted-metabolomics', responseUnit },
917
- })
918
- return createTemplateCollection(
919
- [sampleSheet, samplePrep, reagents, instrumentRun, calibrationCurve, assayMatrix],
920
- { preset: 'targeted-metabolomics', ...(options.metadata ?? {}) }
921
- )
922
- }
923
-
924
- export function createElisaAssayCollection(
925
- options: CreateElisaAssayCollectionOptions = {}
926
- ): TemplateCollectionEnvelope {
927
- const sampleRecords = presetSampleRecords(options.samples ?? ['Control', 'Treatment'])
928
- const analyte = options.analyte ?? 'Analyte'
929
- const responseUnit = options.responseUnit ?? 'OD450'
930
- const plate = createPlateMapTemplate({
931
- name: options.plateName ?? 'ELISA plate',
932
- format: options.plateFormat ?? 96,
933
- samples: [
934
- { id: 'blank', name: 'Blank' },
935
- { id: 'standard', name: 'Standards' },
936
- { id: 'qc', name: 'QC' },
937
- ...sampleRecords.map((sample, index) => ({
938
- id: sample.sampleId,
939
- name: sample.name ?? sample.sampleId,
940
- color: DEFAULT_SAMPLE_COLORS[index % DEFAULT_SAMPLE_COLORS.length],
941
- })),
942
- ],
943
- })
944
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
945
- const calibrationCurve = createCalibrationCurveTemplate({
946
- concentrations: options.standardConcentrations ?? [1000, 100, 10, 1],
947
- analyte,
948
- unit: options.unit ?? 'pg/mL',
949
- responseUnit,
950
- model: 'four-parameter-logistic',
951
- })
952
- const assayMatrix = createAssayMatrixTemplate({
953
- samples: sampleRecords.map(sample => ({
954
- sampleId: sample.sampleId,
955
- name: sample.name,
956
- group: sample.group,
957
- metadata: sample.metadata ?? {},
958
- })),
959
- features: [{
960
- id: idFromName(analyte, 'analyte'),
961
- name: analyte,
962
- type: 'protein',
963
- unit: responseUnit,
964
- metadata: {},
965
- }],
966
- metadata: { analyte, responseUnit },
967
- })
968
- return createTemplateCollection(
969
- [plate, sampleSheet, calibrationCurve, assayMatrix],
970
- { preset: 'elisa-assay', ...(options.metadata ?? {}) }
971
- )
972
- }
973
-
974
- export function createFlowCytometryAssayCollection(
975
- options: CreateFlowCytometryAssayCollectionOptions = {}
976
- ): TemplateCollectionEnvelope {
977
- const sampleRecords = presetSampleRecords(options.samples ?? ['Control', 'Treatment'])
978
- const markers = options.markers ?? ['CD3', 'CD4', 'CD8']
979
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
980
- const flowPanel = createFlowCytometryPanelTemplate({
981
- markers,
982
- instrument: options.instrument ?? 'Flow cytometer',
983
- includeDefaultControls: options.includeDefaultControls,
984
- })
985
- const assayMatrix = createAssayMatrixTemplate({
986
- samples: sampleRecords.map(sample => ({
987
- sampleId: sample.sampleId,
988
- name: sample.name,
989
- group: sample.group,
990
- metadata: sample.metadata ?? {},
991
- })),
992
- features: options.features ?? flowPanel.data.markers.map(marker => ({
993
- id: marker.id,
994
- name: `${marker.marker}+ cells`,
995
- type: 'cell',
996
- unit: '%',
997
- metadata: { marker: marker.marker, fluorophore: marker.fluorophore },
998
- })),
999
- })
1000
- return createTemplateCollection(
1001
- [sampleSheet, flowPanel, assayMatrix],
1002
- { preset: 'flow-cytometry-assay', ...(options.metadata ?? {}) }
1003
- )
1004
- }
1005
-
1006
- export function createWesternBlotAssayCollection(
1007
- options: CreateWesternBlotAssayCollectionOptions = {}
1008
- ): TemplateCollectionEnvelope {
1009
- const sampleRecords = presetSampleRecords(options.samples ?? ['Control', 'Treatment'])
1010
- const loadingControl = options.loadingControl ?? 'ACTB'
1011
- const targetNames = (options.targets ?? ['Target protein']).map(target =>
1012
- typeof target === 'string' ? target : target.name ?? target.id
1013
- )
1014
- const featureNames = loadingControl && !targetNames.includes(loadingControl)
1015
- ? [...targetNames, loadingControl]
1016
- : targetNames
1017
- const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
1018
- const reagentList = createReagentListTemplate({
1019
- reagents: [
1020
- { id: 'primary-antibody', name: 'Primary antibody', kind: 'antibody', storageCondition: '4C' },
1021
- { id: 'secondary-antibody', name: 'Secondary antibody', kind: 'antibody', storageCondition: '4C' },
1022
- { id: 'lysis-buffer', name: 'Lysis buffer', kind: 'buffer', storageCondition: '4C' },
1023
- { id: 'blocking-buffer', name: 'Blocking buffer', kind: 'buffer', storageCondition: '4C' },
1024
- { id: 'wash-buffer', name: 'TBST wash buffer', kind: 'buffer', storageCondition: 'RT' },
1025
- { id: 'substrate', name: 'Detection substrate', kind: 'reagent', storageCondition: '4C' },
1026
- ],
1027
- metadata: { assay: 'western-blot' },
1028
- })
1029
- const protocol = createProtocolStepsTemplate({
1030
- steps: [
1031
- { id: 'lyse-samples', type: 'addition', name: 'Lyse samples', duration: 30 },
1032
- { id: 'run-gel', type: 'custom', name: 'Run SDS-PAGE', duration: 90 },
1033
- { id: 'transfer', type: 'transfer', name: 'Transfer to membrane', duration: 60 },
1034
- { id: 'block', type: 'incubation', name: 'Block membrane', duration: 60 },
1035
- { id: 'primary-antibody', type: 'incubation', name: 'Primary antibody incubation', duration: 960 },
1036
- { id: 'secondary-antibody', type: 'incubation', name: 'Secondary antibody incubation', duration: 60 },
1037
- { id: 'detect', type: 'measurement', name: 'Detect and quantify bands', duration: 30 },
1038
- ],
1039
- metadata: { assay: 'western-blot' },
1040
- })
1041
- const assayMatrix = createAssayMatrixTemplate({
1042
- samples: sampleRecords.map(sample => ({
1043
- sampleId: sample.sampleId,
1044
- name: sample.name,
1045
- group: sample.group,
1046
- metadata: sample.metadata ?? {},
1047
- })),
1048
- features: featureNames.map((feature, index) => ({
1049
- id: idFromName(feature, `feature-${index + 1}`),
1050
- name: feature,
1051
- type: 'protein',
1052
- unit: 'relative intensity',
1053
- metadata: { loadingControl: feature === loadingControl },
1054
- })),
1055
- metadata: { assay: 'western-blot', loadingControl },
1056
- })
1057
- return createTemplateCollection(
1058
- [sampleSheet, reagentList, protocol, assayMatrix],
1059
- { preset: 'western-blot-assay', ...(options.metadata ?? {}) }
1060
- )
1061
- }
1062
-
1063
- export function createBioTemplatePresetCollection(
1064
- name: 'wellplate-screen',
1065
- options?: CreateWellPlateScreenCollectionOptions
1066
- ): TemplateCollectionEnvelope
1067
- export function createBioTemplatePresetCollection(
1068
- name: 'qpcr-expression',
1069
- options?: CreateQpcrExpressionCollectionOptions
1070
- ): TemplateCollectionEnvelope
1071
- export function createBioTemplatePresetCollection(
1072
- name: 'lcms-batch',
1073
- options?: CreateLcmsBatchCollectionOptions
1074
- ): TemplateCollectionEnvelope
1075
- export function createBioTemplatePresetCollection(
1076
- name: 'targeted-metabolomics',
1077
- options?: CreateTargetedMetabolomicsCollectionOptions
1078
- ): TemplateCollectionEnvelope
1079
- export function createBioTemplatePresetCollection(
1080
- name: 'elisa-assay',
1081
- options?: CreateElisaAssayCollectionOptions
1082
- ): TemplateCollectionEnvelope
1083
- export function createBioTemplatePresetCollection(
1084
- name: 'flow-cytometry-assay',
1085
- options?: CreateFlowCytometryAssayCollectionOptions
1086
- ): TemplateCollectionEnvelope
1087
- export function createBioTemplatePresetCollection(
1088
- name: 'western-blot-assay',
1089
- options?: CreateWesternBlotAssayCollectionOptions
1090
- ): TemplateCollectionEnvelope
1091
- export function createBioTemplatePresetCollection(
1092
- name: string,
1093
- options?:
1094
- | CreateWellPlateScreenCollectionOptions
1095
- | CreateQpcrExpressionCollectionOptions
1096
- | CreateLcmsBatchCollectionOptions
1097
- | CreateTargetedMetabolomicsCollectionOptions
1098
- | CreateElisaAssayCollectionOptions
1099
- | CreateFlowCytometryAssayCollectionOptions
1100
- | CreateWesternBlotAssayCollectionOptions
1101
- ): TemplateCollectionEnvelope
1102
- export function createBioTemplatePresetCollection(
1103
- name: TemplatePresetId | string,
1104
- options:
1105
- | CreateWellPlateScreenCollectionOptions
1106
- | CreateQpcrExpressionCollectionOptions
1107
- | CreateLcmsBatchCollectionOptions
1108
- | CreateTargetedMetabolomicsCollectionOptions
1109
- | CreateElisaAssayCollectionOptions
1110
- | CreateFlowCytometryAssayCollectionOptions
1111
- | CreateWesternBlotAssayCollectionOptions = {}
1112
- ): TemplateCollectionEnvelope {
1113
- const preset = getBioTemplatePresetInfo(name)
1114
- if (!preset) {
1115
- throw new Error(`Unknown template preset '${name}'.`)
1116
- }
1117
- if (preset.name === 'wellplate-screen') {
1118
- return createWellPlateScreenCollection(options as CreateWellPlateScreenCollectionOptions)
1119
- }
1120
- if (preset.name === 'qpcr-expression') {
1121
- return createQpcrExpressionCollection(options as CreateQpcrExpressionCollectionOptions)
1122
- }
1123
- if (preset.name === 'lcms-batch') {
1124
- return createLcmsBatchCollection(options as CreateLcmsBatchCollectionOptions)
1125
- }
1126
- if (preset.name === 'targeted-metabolomics') {
1127
- return createTargetedMetabolomicsCollection(options as CreateTargetedMetabolomicsCollectionOptions)
1128
- }
1129
- if (preset.name === 'elisa-assay') {
1130
- return createElisaAssayCollection(options as CreateElisaAssayCollectionOptions)
1131
- }
1132
- if (preset.name === 'flow-cytometry-assay') {
1133
- return createFlowCytometryAssayCollection(options as CreateFlowCytometryAssayCollectionOptions)
1134
- }
1135
- if (preset.name === 'western-blot-assay') {
1136
- return createWesternBlotAssayCollection(options as CreateWesternBlotAssayCollectionOptions)
1137
- }
1138
- throw new Error(`Unknown template preset '${name}'.`)
1139
- }
1140
-
1141
- export type BioTemplateControlValues = Record<string, unknown>
1142
-
1143
- export function bioTemplatePresetControlValuesToOptions(
1144
- name: 'wellplate-screen',
1145
- values: BioTemplateControlValues
1146
- ): CreateWellPlateScreenCollectionOptions
1147
- export function bioTemplatePresetControlValuesToOptions(
1148
- name: 'qpcr-expression',
1149
- values: BioTemplateControlValues
1150
- ): CreateQpcrExpressionCollectionOptions
1151
- export function bioTemplatePresetControlValuesToOptions(
1152
- name: 'lcms-batch',
1153
- values: BioTemplateControlValues
1154
- ): CreateLcmsBatchCollectionOptions
1155
- export function bioTemplatePresetControlValuesToOptions(
1156
- name: 'targeted-metabolomics',
1157
- values: BioTemplateControlValues
1158
- ): CreateTargetedMetabolomicsCollectionOptions
1159
- export function bioTemplatePresetControlValuesToOptions(
1160
- name: 'elisa-assay',
1161
- values: BioTemplateControlValues
1162
- ): CreateElisaAssayCollectionOptions
1163
- export function bioTemplatePresetControlValuesToOptions(
1164
- name: 'flow-cytometry-assay',
1165
- values: BioTemplateControlValues
1166
- ): CreateFlowCytometryAssayCollectionOptions
1167
- export function bioTemplatePresetControlValuesToOptions(
1168
- name: 'western-blot-assay',
1169
- values: BioTemplateControlValues
1170
- ): CreateWesternBlotAssayCollectionOptions
1171
- export function bioTemplatePresetControlValuesToOptions(
1172
- name: TemplatePresetId | string,
1173
- values: BioTemplateControlValues
1174
- ):
1175
- | CreateWellPlateScreenCollectionOptions
1176
- | CreateQpcrExpressionCollectionOptions
1177
- | CreateLcmsBatchCollectionOptions
1178
- | CreateTargetedMetabolomicsCollectionOptions
1179
- | CreateElisaAssayCollectionOptions
1180
- | CreateFlowCytometryAssayCollectionOptions
1181
- | CreateWesternBlotAssayCollectionOptions
1182
- export function bioTemplatePresetControlValuesToOptions(
1183
- name: TemplatePresetId | string,
1184
- values: BioTemplateControlValues
1185
- ):
1186
- | CreateWellPlateScreenCollectionOptions
1187
- | CreateQpcrExpressionCollectionOptions
1188
- | CreateLcmsBatchCollectionOptions
1189
- | CreateTargetedMetabolomicsCollectionOptions
1190
- | CreateElisaAssayCollectionOptions
1191
- | CreateFlowCytometryAssayCollectionOptions
1192
- | CreateWesternBlotAssayCollectionOptions {
1193
- const preset = getBioTemplatePresetInfo(name)
1194
- if (!preset) {
1195
- throw new Error(`Unknown template preset '${name}'.`)
1196
- }
1197
-
1198
- if (preset.name === 'wellplate-screen') {
1199
- return {
1200
- samples: readStringList(values.sampleNames, ['Control', 'Treatment']),
1201
- plateName: readString(values.plateName, 'Screen plate'),
1202
- plateFormat: readPlateFormat(values.plateFormat, 96),
1203
- unit: readString(values.unit, 'uM'),
1204
- replicates: readInteger(values.replicates, 3),
1205
- }
1206
- }
1207
-
1208
- if (preset.name === 'qpcr-expression') {
1209
- return {
1210
- samples: readStringList(values.sampleNames, ['Control', 'Treatment']),
1211
- targets: readStringList(values.targets, ['ACTB', 'GAPDH']),
1212
- chemistry: readString(values.chemistry, 'sybr') as CreateQpcrPlateTemplateOptions['chemistry'],
1213
- format: readPlateFormat(values.plateFormat, 96),
1214
- includeNoTemplateControls: readBoolean(values.includeNoTemplateControls, true),
1215
- replicates: readInteger(values.replicates, 3),
1216
- instrument: readOptionalString(values.instrument),
1217
- }
1218
- }
1219
-
1220
- if (preset.name === 'lcms-batch') {
1221
- return {
1222
- samples: readStringList(values.sampleNames, ['S001', 'S002']),
1223
- features: readStringList(values.featureNames, ['Glucose', 'Lactate']),
1224
- instrument: readString(values.instrument, 'LC-MS'),
1225
- method: readString(values.method, 'Default method'),
1226
- includeQc: readBoolean(values.includeQc, true),
1227
- }
1228
- }
1229
-
1230
- if (preset.name === 'targeted-metabolomics') {
1231
- return {
1232
- samples: readStringList(values.sampleNames, ['S001', 'S002']),
1233
- metabolites: readStringList(values.metaboliteNames, ['Glucose', 'Lactate', 'Pyruvate']),
1234
- internalStandards: readStringList(
1235
- values.internalStandards,
1236
- ['Stable isotope internal standard mix'],
1237
- ),
1238
- standardConcentrations: readNumberList(values.standardConcentrations, [0.1, 1, 10, 100]),
1239
- concentrationUnit: readString(values.concentrationUnit, 'uM'),
1240
- responseUnit: readString(values.responseUnit, 'peak area ratio'),
1241
- calibrationAnalyte: readOptionalString(values.calibrationAnalyte),
1242
- instrument: readString(values.instrument, 'LC-MS'),
1243
- method: readString(values.method, 'Targeted metabolomics'),
1244
- prepType: readString(values.prepType, 'extraction') as CreateSamplePrepTemplateOptions['prepType'],
1245
- outputVolume: readNumber(values.outputVolume, 50),
1246
- includeQc: readBoolean(values.includeQc, true),
1247
- }
1248
- }
1249
-
1250
- if (preset.name === 'elisa-assay') {
1251
- return {
1252
- samples: readStringList(values.sampleNames, ['Control', 'Treatment']),
1253
- analyte: readString(values.analyte, 'Analyte'),
1254
- standardConcentrations: readNumberList(values.standardConcentrations, [1000, 100, 10, 1]),
1255
- unit: readString(values.unit, 'pg/mL'),
1256
- responseUnit: readString(values.responseUnit, 'OD450'),
1257
- plateName: readString(values.plateName, 'ELISA plate'),
1258
- plateFormat: readPlateFormat(values.plateFormat, 96),
1259
- }
1260
- }
1261
-
1262
- if (preset.name === 'flow-cytometry-assay') {
1263
- return {
1264
- samples: readStringList(values.sampleNames, ['Control', 'Treatment']),
1265
- markers: readStringList(values.markers, ['CD3', 'CD4', 'CD8']),
1266
- features: readStringList(values.featureNames, ['CD3+ cells', 'CD4+ cells', 'CD8+ cells']),
1267
- instrument: readString(values.instrument, 'Flow cytometer'),
1268
- includeDefaultControls: readBoolean(values.includeDefaultControls, true),
1269
- }
1270
- }
1271
-
1272
- if (preset.name === 'western-blot-assay') {
1273
- return {
1274
- samples: readStringList(values.sampleNames, ['Control', 'Treatment']),
1275
- targets: readStringList(values.targets, ['Target protein']),
1276
- loadingControl: readString(values.loadingControl, 'ACTB'),
1277
- }
1278
- }
1279
-
1280
- throw new Error(`Unknown template preset '${name}'.`)
1281
- }
1282
-
1283
- export function createBioTemplatePresetCollectionFromControls(
1284
- name: 'wellplate-screen',
1285
- values: BioTemplateControlValues
1286
- ): TemplateCollectionEnvelope
1287
- export function createBioTemplatePresetCollectionFromControls(
1288
- name: 'qpcr-expression',
1289
- values: BioTemplateControlValues
1290
- ): TemplateCollectionEnvelope
1291
- export function createBioTemplatePresetCollectionFromControls(
1292
- name: 'lcms-batch',
1293
- values: BioTemplateControlValues
1294
- ): TemplateCollectionEnvelope
1295
- export function createBioTemplatePresetCollectionFromControls(
1296
- name: 'targeted-metabolomics',
1297
- values: BioTemplateControlValues
1298
- ): TemplateCollectionEnvelope
1299
- export function createBioTemplatePresetCollectionFromControls(
1300
- name: 'elisa-assay',
1301
- values: BioTemplateControlValues
1302
- ): TemplateCollectionEnvelope
1303
- export function createBioTemplatePresetCollectionFromControls(
1304
- name: 'flow-cytometry-assay',
1305
- values: BioTemplateControlValues
1306
- ): TemplateCollectionEnvelope
1307
- export function createBioTemplatePresetCollectionFromControls(
1308
- name: 'western-blot-assay',
1309
- values: BioTemplateControlValues
1310
- ): TemplateCollectionEnvelope
1311
- export function createBioTemplatePresetCollectionFromControls(
1312
- name: TemplatePresetId | string,
1313
- values: BioTemplateControlValues
1314
- ): TemplateCollectionEnvelope
1315
- export function createBioTemplatePresetCollectionFromControls(
1316
- name: TemplatePresetId | string,
1317
- values: BioTemplateControlValues
1318
- ): TemplateCollectionEnvelope {
1319
- const options = bioTemplatePresetControlValuesToOptions(name, values)
1320
- return createBioTemplatePresetCollection(name, options)
1321
- }
1322
-
1323
- export function createTemplateCollection(
1324
- templates: Array<BioTemplateEnvelope<unknown>>,
1325
- metadata?: Record<string, unknown>
1326
- ): TemplateCollectionEnvelope {
1327
- const collection: TemplateCollection = {}
1328
- for (const template of templates) {
1329
- assertGenericTemplateEnvelope(template)
1330
- const templateId = template.template_id
1331
- if (collection[templateId]) {
1332
- throw new Error(`Duplicate template_id '${templateId}' in template collection.`)
1333
- }
1334
- collection[templateId] = template
1335
- }
1336
-
1337
- return metadata === undefined
1338
- ? { [TEMPLATE_COLLECTION_KEY]: collection }
1339
- : { [TEMPLATE_COLLECTION_KEY]: collection, metadata }
1340
- }
1341
-
1342
- export function extractTemplateCollection(value: unknown): TemplateCollection {
1343
- if (isEnvelope<unknown>(value)) {
1344
- assertGenericTemplateEnvelope(value)
1345
- return { [value.template_id]: value }
1346
- }
1347
- if (!isRecord(value)) {
1348
- throw new Error('Template collection payload must be an object.')
1349
- }
1350
-
1351
- const rawCollection = value[TEMPLATE_COLLECTION_KEY]
1352
- if (rawCollection === undefined) {
1353
- return {}
1354
- }
1355
- if (!isRecord(rawCollection)) {
1356
- throw new Error('Template collection must be an object.')
1357
- }
1358
-
1359
- const collection: TemplateCollection = {}
1360
- for (const [templateId, template] of Object.entries(rawCollection)) {
1361
- assertGenericTemplateEnvelope(template)
1362
- if (template.template_id !== templateId) {
1363
- throw new Error(
1364
- `Template collection key '${templateId}' does not match envelope template_id '${template.template_id}'.`
1365
- )
1366
- }
1367
- collection[templateId] = template
1368
- }
1369
- return collection
1370
- }
1371
-
1372
- export function ensureTemplateFromCollection<TTemplate extends BioTemplateEnvelope<unknown>>(
1373
- value: unknown,
1374
- templateId: TemplateId | string
1375
- ): TTemplate {
1376
- const collection = extractTemplateCollection(value)
1377
- const template = collection[templateId]
1378
- if (!template) {
1379
- throw new Error(`Template '${templateId}' was not found in template collection.`)
1380
- }
1381
- assertTemplateEnvelope(template, templateId)
1382
- return template as TTemplate
1383
- }
1384
-
1385
- export function assertTemplateEnvelope<TData>(
1386
- value: unknown,
1387
- templateId: TemplateId | string
1388
- ): asserts value is BioTemplateEnvelope<TData> {
1389
- assertGenericTemplateEnvelope(value)
1390
- if (value.template_id !== templateId) {
1391
- throw new Error(`Expected template_id '${templateId}', got '${String(value.template_id)}'.`)
1392
- }
1393
- }
1394
-
1395
- export function ensureTemplateEnvelope<TTemplate extends BioTemplateEnvelope<unknown>>(
1396
- value: unknown,
1397
- templateId: TemplateId | string
1398
- ): TTemplate {
1399
- assertTemplateEnvelope(value, templateId)
1400
- return value as TTemplate
1401
- }
1402
-
1403
- export function getTemplateData<TData>(
1404
- template: BioTemplateEnvelope<TData> | TData,
1405
- templateId?: TemplateId | string
1406
- ): TData {
1407
- if (isEnvelope<TData>(template)) {
1408
- if (templateId) assertTemplateEnvelope<TData>(template, templateId)
1409
- return template.data
1410
- }
1411
- return template
1412
- }
1413
-
1414
- export function validatePlateMapData(data: PlateMapTemplateData): void {
1415
- if (!Array.isArray(data.plates) || data.plates.length === 0) {
1416
- throw new Error('Plate-map template requires at least one plate.')
1417
- }
1418
- const sampleIds = new Set(data.samples.map(sample => sample.id))
1419
- if (sampleIds.size !== data.samples.length) {
1420
- throw new Error('Plate-map sample ids must be unique.')
1421
- }
1422
- for (const plate of data.plates) {
1423
- for (const [wellId, well] of Object.entries(plate.wells)) {
1424
- if (well.id !== wellId) {
1425
- throw new Error(`Well key '${wellId}' does not match well id '${well.id}'.`)
1426
- }
1427
- if (well.sampleType && !sampleIds.has(well.sampleType)) {
1428
- throw new Error(`Well '${well.id}' references unknown sample '${well.sampleType}'.`)
1429
- }
1430
- }
1431
- }
1432
- }
1433
-
1434
- export function validateSampleSheetData(data: SampleSheetTemplateData): void {
1435
- if (!Array.isArray(data.samples) || data.samples.length === 0) {
1436
- throw new Error('Sample-sheet template requires at least one sample.')
1437
- }
1438
- const sampleIds = new Set(data.samples.map(sample => sample.sampleId))
1439
- if (sampleIds.size !== data.samples.length) {
1440
- throw new Error('Sample-sheet sample ids must be unique.')
1441
- }
1442
- for (const group of data.groups) {
1443
- for (const sampleId of group.samples) {
1444
- if (!sampleIds.has(sampleId)) {
1445
- throw new Error(`Group '${group.name}' references unknown sample '${sampleId}'.`)
1446
- }
1447
- }
1448
- }
1449
- }
1450
-
1451
- export function validateSamplePrepData(data: SamplePrepTemplateData): void {
1452
- if (!data.protocolName) {
1453
- throw new Error('Sample-prep template requires protocolName.')
1454
- }
1455
- if (!Array.isArray(data.steps) || data.steps.length === 0) {
1456
- throw new Error('Sample-prep template requires at least one step.')
1457
- }
1458
- const stepIds = new Set(data.steps.map(step => step.id))
1459
- if (stepIds.size !== data.steps.length) {
1460
- throw new Error('Sample-prep step ids must be unique.')
1461
- }
1462
- const orders = new Set(data.steps.map(step => step.order))
1463
- if (orders.size !== data.steps.length) {
1464
- throw new Error('Sample-prep step orders must be unique.')
1465
- }
1466
- for (const step of data.steps) {
1467
- if (!step.id || !step.name) {
1468
- throw new Error('Sample-prep steps require id and name.')
1469
- }
1470
- if (step.order < 0) {
1471
- throw new Error(`Sample-prep step '${step.id}' has a negative order.`)
1472
- }
1473
- if (!step.volumeUnit) {
1474
- throw new Error(`Sample-prep step '${step.id}' requires volumeUnit.`)
1475
- }
1476
- if (step.inputVolume !== undefined && step.inputVolume < 0) {
1477
- throw new Error(`Sample-prep step '${step.id}' has a negative input volume.`)
1478
- }
1479
- if (step.outputVolume !== undefined && step.outputVolume < 0) {
1480
- throw new Error(`Sample-prep step '${step.id}' has a negative output volume.`)
1481
- }
1482
- if (step.durationMin !== undefined && step.durationMin < 0) {
1483
- throw new Error(`Sample-prep step '${step.id}' has a negative duration.`)
1484
- }
1485
- if (!step.sourceSampleId && !step.destinationSampleId && !step.reagentId) {
1486
- throw new Error(`Sample-prep step '${step.id}' requires a source, destination, or reagent.`)
1487
- }
1488
- }
1489
- }
1490
-
1491
- export function validateDoseResponseData(data: DoseResponseTemplateData): void {
1492
- if (!Array.isArray(data.compounds) || data.compounds.length === 0) {
1493
- throw new Error('Dose-response template requires at least one compound.')
1494
- }
1495
- if (!data.unit) {
1496
- throw new Error('Dose-response template requires a unit.')
1497
- }
1498
- if (data.replicates < 1) {
1499
- throw new Error('Dose-response replicates must be at least 1.')
1500
- }
1501
- for (const compound of data.compounds) {
1502
- if (compound.concentrations.length === 0) {
1503
- throw new Error(`Compound '${compound.name}' requires at least one concentration.`)
1504
- }
1505
- if (compound.concentrations.some(value => value < 0)) {
1506
- throw new Error(`Compound '${compound.name}' has a negative concentration.`)
1507
- }
1508
- }
1509
- }
1510
-
1511
- export function validateCalibrationCurveData(data: CalibrationCurveTemplateData): void {
1512
- if (!data.analyte) {
1513
- throw new Error('Calibration-curve template requires an analyte.')
1514
- }
1515
- if (!data.xUnit) {
1516
- throw new Error('Calibration-curve template requires xUnit.')
1517
- }
1518
- if (!data.responseUnit) {
1519
- throw new Error('Calibration-curve template requires responseUnit.')
1520
- }
1521
- if (!Array.isArray(data.points) || data.points.length === 0) {
1522
- throw new Error('Calibration-curve template requires at least one point.')
1523
- }
1524
- if (data.acceptance.minRSquared < 0 || data.acceptance.minRSquared > 1) {
1525
- throw new Error('Calibration-curve minRSquared must be between 0 and 1.')
1526
- }
1527
- if (data.acceptance.standardTolerancePercent < 0 || data.acceptance.qcTolerancePercent < 0) {
1528
- throw new Error('Calibration-curve tolerances cannot be negative.')
1529
- }
1530
-
1531
- const pointIds = new Set(data.points.map(point => point.id))
1532
- if (pointIds.size !== data.points.length) {
1533
- throw new Error('Calibration-curve point ids must be unique.')
1534
- }
1535
- const orders = new Set(data.points.map(point => point.order))
1536
- if (orders.size !== data.points.length) {
1537
- throw new Error('Calibration-curve point orders must be unique.')
1538
- }
1539
-
1540
- let standards = 0
1541
- for (const point of data.points) {
1542
- if (!point.id || !point.level) {
1543
- throw new Error('Calibration-curve points require id and level.')
1544
- }
1545
- if (!point.unit) {
1546
- throw new Error(`Calibration-curve point '${point.id}' requires unit.`)
1547
- }
1548
- if (point.order < 0) {
1549
- throw new Error(`Calibration-curve point '${point.id}' has a negative order.`)
1550
- }
1551
- if (point.concentration < 0) {
1552
- throw new Error(`Calibration-curve point '${point.id}' has a negative concentration.`)
1553
- }
1554
- if ((point.role === 'standard' || point.role === 'qc') && point.concentration <= 0) {
1555
- throw new Error(`Calibration-curve point '${point.id}' requires positive concentration.`)
1556
- }
1557
- if (point.replicate < 1) {
1558
- throw new Error(`Calibration-curve point '${point.id}' replicate must be at least 1.`)
1559
- }
1560
- if (point.role === 'standard' && point.include) {
1561
- standards += 1
1562
- }
1563
- }
1564
-
1565
- if (standards < 2) {
1566
- throw new Error('Calibration-curve template requires at least two included standards.')
1567
- }
1568
- if (data.acceptance.requireBlank && !data.points.some(point => point.role === 'blank')) {
1569
- throw new Error('Calibration-curve template requires a blank point when requireBlank is true.')
1570
- }
1571
- }
1572
-
1573
- export function validateTimeCourseData(data: TimeCourseTemplateData): void {
1574
- if (!Array.isArray(data.timepoints) || data.timepoints.length === 0) {
1575
- throw new Error('Time-course template requires at least one time point.')
1576
- }
1577
- if (!Array.isArray(data.conditions) || data.conditions.length === 0) {
1578
- throw new Error('Time-course template requires at least one condition.')
1579
- }
1580
- const timepointIds = new Set(data.timepoints.map(timepoint => timepoint.id))
1581
- if (timepointIds.size !== data.timepoints.length) {
1582
- throw new Error('Time-course time point ids must be unique.')
1583
- }
1584
- const conditionIds = new Set(data.conditions.map(condition => condition.id))
1585
- if (conditionIds.size !== data.conditions.length) {
1586
- throw new Error('Time-course condition ids must be unique.')
1587
- }
1588
- const sampleIds = new Set(data.samples.map(sample => sample.sampleId))
1589
- if (sampleIds.size !== data.samples.length) {
1590
- throw new Error('Time-course sample ids must be unique.')
1591
- }
1592
- for (const sample of data.samples) {
1593
- if (!timepointIds.has(sample.timepointId)) {
1594
- throw new Error(`Sample '${sample.sampleId}' references unknown time point.`)
1595
- }
1596
- if (!conditionIds.has(sample.conditionId)) {
1597
- throw new Error(`Sample '${sample.sampleId}' references unknown condition.`)
1598
- }
1599
- }
1600
- }
1601
-
1602
- export function validateProtocolStepsData(data: ProtocolStepsTemplateData): void {
1603
- if (!Array.isArray(data.steps) || data.steps.length === 0) {
1604
- throw new Error('Protocol-steps template requires at least one step.')
1605
- }
1606
- const stepIds = new Set(data.steps.map(step => step.id))
1607
- if (stepIds.size !== data.steps.length) {
1608
- throw new Error('Protocol-steps ids must be unique.')
1609
- }
1610
- const orders = new Set(data.steps.map(step => step.order))
1611
- if (orders.size !== data.steps.length) {
1612
- throw new Error('Protocol-steps orders must be unique.')
1613
- }
1614
- for (const step of data.steps) {
1615
- if (!step.id) {
1616
- throw new Error('Protocol step id is required.')
1617
- }
1618
- if (!step.name) {
1619
- throw new Error(`Protocol step '${step.id}' requires a name.`)
1620
- }
1621
- if (step.duration !== undefined && step.duration < 0) {
1622
- throw new Error(`Protocol step '${step.id}' has a negative duration.`)
1623
- }
1624
- if (step.order < 0) {
1625
- throw new Error(`Protocol step '${step.id}' has a negative order.`)
1626
- }
1627
- }
1628
- }
1629
-
1630
- export function validateAssayMatrixData(data: AssayMatrixTemplateData): void {
1631
- if (!Array.isArray(data.samples) || data.samples.length === 0) {
1632
- throw new Error('Assay-matrix template requires at least one sample.')
1633
- }
1634
- if (!Array.isArray(data.features) || data.features.length === 0) {
1635
- throw new Error('Assay-matrix template requires at least one feature.')
1636
- }
1637
- const sampleIds = new Set(data.samples.map(sample => sample.sampleId))
1638
- if (sampleIds.size !== data.samples.length) {
1639
- throw new Error('Assay-matrix sample ids must be unique.')
1640
- }
1641
- const featureIds = new Set(data.features.map(feature => feature.id))
1642
- if (featureIds.size !== data.features.length) {
1643
- throw new Error('Assay-matrix feature ids must be unique.')
1644
- }
1645
- const measurementIds = new Set<string>()
1646
- for (const measurement of data.measurements) {
1647
- const key = `${measurement.sampleId}:${measurement.featureId}`
1648
- if (measurementIds.has(key)) {
1649
- throw new Error(`Duplicate assay measurement for '${key}'.`)
1650
- }
1651
- measurementIds.add(key)
1652
- if (!sampleIds.has(measurement.sampleId)) {
1653
- throw new Error(`Measurement references unknown sample '${measurement.sampleId}'.`)
1654
- }
1655
- if (!featureIds.has(measurement.featureId)) {
1656
- throw new Error(`Measurement references unknown feature '${measurement.featureId}'.`)
1657
- }
1658
- }
1659
- }
1660
-
1661
- export function validateReagentListData(data: ReagentListTemplateData): void {
1662
- if (!Array.isArray(data.reagents) || data.reagents.length === 0) {
1663
- throw new Error('Reagent-list template requires at least one reagent.')
1664
- }
1665
- const reagentIds = new Set(data.reagents.map(reagent => reagent.id))
1666
- if (reagentIds.size !== data.reagents.length) {
1667
- throw new Error('Reagent-list reagent ids must be unique.')
1668
- }
1669
- for (const reagent of data.reagents) {
1670
- if (!reagent.id) {
1671
- throw new Error('Reagent-list reagent id is required.')
1672
- }
1673
- if (!reagent.name) {
1674
- throw new Error(`Reagent '${reagent.id}' requires a name.`)
1675
- }
1676
- if (reagent.stockLevel !== undefined && reagent.stockLevel < 0) {
1677
- throw new Error(`Reagent '${reagent.id}' has a negative stock level.`)
1678
- }
1679
- }
1680
- }
1681
-
1682
- export function validateFlowCytometryPanelData(data: FlowCytometryPanelTemplateData): void {
1683
- if (!Array.isArray(data.markers) || data.markers.length === 0) {
1684
- throw new Error('Flow-cytometry panel template requires at least one marker.')
1685
- }
1686
- const markerIds = new Set(data.markers.map(marker => marker.id))
1687
- if (markerIds.size !== data.markers.length) {
1688
- throw new Error('Flow-cytometry panel marker ids must be unique.')
1689
- }
1690
- const controlIds = new Set(data.controls.map(control => control.id))
1691
- if (controlIds.size !== data.controls.length) {
1692
- throw new Error('Flow-cytometry panel control ids must be unique.')
1693
- }
1694
- for (const marker of data.markers) {
1695
- if (!marker.id) {
1696
- throw new Error('Flow-cytometry panel marker id is required.')
1697
- }
1698
- if (!marker.marker) {
1699
- throw new Error(`Flow-cytometry panel marker '${marker.id}' requires a marker name.`)
1700
- }
1701
- }
1702
- for (const control of data.controls) {
1703
- if (!control.id) {
1704
- throw new Error('Flow-cytometry panel control id is required.')
1705
- }
1706
- if (!control.name) {
1707
- throw new Error(`Flow-cytometry panel control '${control.id}' requires a name.`)
1708
- }
1709
- if (control.markerId && !markerIds.has(control.markerId)) {
1710
- throw new Error(`Flow-cytometry panel control '${control.id}' references unknown marker '${control.markerId}'.`)
1711
- }
1712
- }
1713
- }
1714
-
1715
- export function validateInstrumentRunData(data: InstrumentRunTemplateData): void {
1716
- if (!Array.isArray(data.methods) || data.methods.length === 0) {
1717
- throw new Error('Instrument-run template requires at least one method.')
1718
- }
1719
- if (!Array.isArray(data.items) || data.items.length === 0) {
1720
- throw new Error('Instrument-run template requires at least one run item.')
1721
- }
1722
- const methodIds = new Set(data.methods.map(method => method.id))
1723
- if (methodIds.size !== data.methods.length) {
1724
- throw new Error('Instrument-run method ids must be unique.')
1725
- }
1726
- for (const method of data.methods) {
1727
- if (!method.id || !method.name) {
1728
- throw new Error('Instrument-run methods require id and name.')
1729
- }
1730
- }
1731
- const itemIds = new Set(data.items.map(item => item.id))
1732
- if (itemIds.size !== data.items.length) {
1733
- throw new Error('Instrument-run item ids must be unique.')
1734
- }
1735
- const orders = new Set(data.items.map(item => item.order))
1736
- if (orders.size !== data.items.length) {
1737
- throw new Error('Instrument-run item orders must be unique.')
1738
- }
1739
- for (const item of data.items) {
1740
- if (!item.id) {
1741
- throw new Error('Instrument-run items require id.')
1742
- }
1743
- if (!methodIds.has(item.methodId)) {
1744
- throw new Error(`Instrument-run item '${item.id}' references unknown method '${item.methodId}'.`)
1745
- }
1746
- if (item.kind === 'sample' && !item.sampleId) {
1747
- throw new Error(`Instrument-run sample item '${item.id}' requires sampleId.`)
1748
- }
1749
- if (item.order < 0) {
1750
- throw new Error(`Instrument-run item '${item.id}' has a negative order.`)
1751
- }
1752
- if (item.injectionVolume !== undefined && item.injectionVolume < 0) {
1753
- throw new Error(`Instrument-run item '${item.id}' has a negative injection volume.`)
1754
- }
1755
- if (item.expectedDurationMin !== undefined && item.expectedDurationMin < 0) {
1756
- throw new Error(`Instrument-run item '${item.id}' has a negative expected duration.`)
1757
- }
1758
- }
1759
- }
1760
-
1761
- export function validateQpcrPlateData(data: QpcrPlateTemplateData): void {
1762
- if (!Array.isArray(data.samples) || data.samples.length === 0) {
1763
- throw new Error('qPCR plate template requires at least one sample.')
1764
- }
1765
- if (!Array.isArray(data.targets) || data.targets.length === 0) {
1766
- throw new Error('qPCR plate template requires at least one target.')
1767
- }
1768
- if (!Array.isArray(data.reactions) || data.reactions.length === 0) {
1769
- throw new Error('qPCR plate template requires at least one reaction.')
1770
- }
1771
- const sampleIds = new Set(data.samples.map(sample => sample.sampleId))
1772
- if (sampleIds.size !== data.samples.length) {
1773
- throw new Error('qPCR plate sample ids must be unique.')
1774
- }
1775
- const targetIds = new Set(data.targets.map(target => target.id))
1776
- if (targetIds.size !== data.targets.length) {
1777
- throw new Error('qPCR plate target ids must be unique.')
1778
- }
1779
- const reactionIds = new Set(data.reactions.map(reaction => reaction.id))
1780
- if (reactionIds.size !== data.reactions.length) {
1781
- throw new Error('qPCR plate reaction ids must be unique.')
1782
- }
1783
- const wellIds = new Set(data.reactions.map(reaction => reaction.wellId))
1784
- if (wellIds.size !== data.reactions.length) {
1785
- throw new Error('qPCR plate reaction wells must be unique.')
1786
- }
1787
- for (const reaction of data.reactions) {
1788
- assertWellInFormat(reaction.wellId, data.format)
1789
- if (!reaction.id) {
1790
- throw new Error('qPCR reaction id is required.')
1791
- }
1792
- if (!reaction.targetId || !targetIds.has(reaction.targetId)) {
1793
- throw new Error(`qPCR reaction '${reaction.id}' references unknown target '${reaction.targetId}'.`)
1794
- }
1795
- if (reaction.sampleId && !sampleIds.has(reaction.sampleId)) {
1796
- throw new Error(`qPCR reaction '${reaction.id}' references unknown sample '${reaction.sampleId}'.`)
1797
- }
1798
- if (reaction.controlKind === 'sample' && !reaction.sampleId) {
1799
- throw new Error(`qPCR sample reaction '${reaction.id}' requires sampleId.`)
1800
- }
1801
- if (reaction.replicate < 1) {
1802
- throw new Error(`qPCR reaction '${reaction.id}' replicate must be at least 1.`)
1803
- }
1804
- if (reaction.cq !== undefined && reaction.cq !== null && reaction.cq < 0) {
1805
- throw new Error(`qPCR reaction '${reaction.id}' has a negative Cq.`)
1806
- }
1807
- if (reaction.quantity !== undefined && reaction.quantity !== null && reaction.quantity < 0) {
1808
- throw new Error(`qPCR reaction '${reaction.id}' has a negative quantity.`)
1809
- }
1810
- }
1811
- }
1812
-
1813
- function normalizeCompound(compound: CompoundDoseSeries): CompoundDoseSeries {
1814
- return {
1815
- ...compound,
1816
- concentrations: [...compound.concentrations].sort((a, b) => b - a),
1817
- metadata: compound.metadata ?? {},
1818
- }
1819
- }
1820
-
1821
- function normalizeCalibrationAcceptance(
1822
- acceptance?: Partial<CalibrationAcceptance>,
1823
- requireBlank = true
1824
- ): CalibrationAcceptance {
1825
- return {
1826
- minRSquared: acceptance?.minRSquared ?? 0.99,
1827
- standardTolerancePercent: acceptance?.standardTolerancePercent ?? 15,
1828
- qcTolerancePercent: acceptance?.qcTolerancePercent ?? 20,
1829
- requireBlank: acceptance?.requireBlank ?? requireBlank,
1830
- metadata: acceptance?.metadata ?? {},
1831
- }
1832
- }
1833
-
1834
- function normalizeCalibrationPoint(
1835
- point: CalibrationPointInput,
1836
- unit: string,
1837
- order: number,
1838
- index: number
1839
- ): CalibrationPoint {
1840
- const role = point.role ?? 'standard'
1841
- const level = point.level || `${role.charAt(0).toUpperCase()}${role.slice(1)} ${index + 1}`
1842
- return {
1843
- id: point.id || idFromName(level, `${role}-${index + 1}`),
1844
- order: point.order ?? order,
1845
- role,
1846
- level,
1847
- concentration: point.concentration ?? 0,
1848
- unit: point.unit || unit,
1849
- expectedResponse: point.expectedResponse,
1850
- response: point.response,
1851
- replicate: point.replicate ?? 1,
1852
- include: point.include ?? true,
1853
- status: point.status ?? 'planned',
1854
- metadata: point.metadata ?? {},
1855
- }
1856
- }
1857
-
1858
- function normalizeSampleRecord(sample: SampleRecord): SampleRecord {
1859
- return {
1860
- ...sample,
1861
- metadata: sample.metadata ?? {},
1862
- }
1863
- }
1864
-
1865
- function presetSampleRecords(samples: Array<string | SampleRecord>): SampleRecord[] {
1866
- return samples.map((sample, index) => {
1867
- if (typeof sample !== 'string') return normalizeSampleRecord(sample)
1868
- return {
1869
- sampleId: sampleIdFromName(sample, index),
1870
- name: sample,
1871
- metadata: {},
1872
- }
1873
- })
1874
- }
1875
-
1876
- function normalizeSamplePrepStep(
1877
- step: SamplePrepStepInput,
1878
- prepType: SamplePrepStep['type'],
1879
- volumeUnit: string,
1880
- order: number,
1881
- index: number
1882
- ): SamplePrepStep {
1883
- const type = step.type ?? prepType
1884
- const sourceSampleId = step.sourceSampleId
1885
- const name = step.name || `Prepare ${sourceSampleId ?? index + 1}`
1886
- return {
1887
- id: step.id || idFromName(name, `${type}-${index + 1}`),
1888
- order: step.order ?? order,
1889
- type,
1890
- name,
1891
- sourceSampleId,
1892
- sourcePlateId: step.sourcePlateId,
1893
- sourceWellId: step.sourceWellId,
1894
- destinationSampleId: step.destinationSampleId,
1895
- destinationPlateId: step.destinationPlateId,
1896
- destinationWellId: step.destinationWellId,
1897
- reagentId: step.reagentId,
1898
- inputVolume: step.inputVolume,
1899
- outputVolume: step.outputVolume,
1900
- volumeUnit: step.volumeUnit || volumeUnit,
1901
- durationMin: step.durationMin,
1902
- status: step.status ?? 'planned',
1903
- metadata: step.metadata ?? {},
1904
- }
1905
- }
1906
-
1907
- function normalizeControl(control: ControlDefinition): ControlDefinition {
1908
- return {
1909
- ...control,
1910
- metadata: control.metadata ?? {},
1911
- }
1912
- }
1913
-
1914
- function normalizeTimePoint(timepoint: TimePoint): TimePoint {
1915
- return {
1916
- ...timepoint,
1917
- metadata: timepoint.metadata ?? {},
1918
- }
1919
- }
1920
-
1921
- function normalizeCondition(condition: TimeCourseCondition): TimeCourseCondition {
1922
- return {
1923
- ...condition,
1924
- metadata: condition.metadata ?? {},
1925
- }
1926
- }
1927
-
1928
- function normalizeProtocolStep(
1929
- step: Partial<ProtocolStepRecord> & { name: string },
1930
- index: number
1931
- ): ProtocolStepRecord {
1932
- return {
1933
- id: step.id ?? idFromName(step.name, `step-${index + 1}`),
1934
- type: step.type ?? 'custom',
1935
- name: step.name,
1936
- description: step.description,
1937
- duration: step.duration,
1938
- status: step.status ?? 'pending',
1939
- parameters: step.parameters ?? {},
1940
- order: step.order ?? index,
1941
- metadata: step.metadata ?? {},
1942
- }
1943
- }
1944
-
1945
- function normalizeAssaySample(sample: AssaySample): AssaySample {
1946
- return {
1947
- ...sample,
1948
- metadata: sample.metadata ?? {},
1949
- }
1950
- }
1951
-
1952
- function normalizeAssayFeature(feature: AssayFeature): AssayFeature {
1953
- return {
1954
- ...feature,
1955
- metadata: feature.metadata ?? {},
1956
- }
1957
- }
1958
-
1959
- function normalizeReagent(reagent: ReagentTemplateInput): ReagentRecord {
1960
- return {
1961
- ...reagent,
1962
- expiryDate: normalizeDateString(reagent.expiryDate),
1963
- kind: reagent.kind ?? 'reagent',
1964
- metadata: reagent.metadata ?? {},
1965
- }
1966
- }
1967
-
1968
- function normalizeMetaboliteFeatures(
1969
- metabolites: Array<string | AssayFeature>,
1970
- responseUnit: string,
1971
- ): AssayFeature[] {
1972
- return metabolites.map((metabolite, index) => {
1973
- if (typeof metabolite === 'string') {
1974
- return {
1975
- id: idFromName(metabolite, `metabolite-${index + 1}`),
1976
- name: metabolite,
1977
- type: 'metabolite',
1978
- unit: responseUnit,
1979
- metadata: { quantitation: 'targeted' },
1980
- }
1981
- }
1982
-
1983
- const feature = normalizeAssayFeature(metabolite)
1984
- return {
1985
- ...feature,
1986
- type: feature.type ?? 'metabolite',
1987
- unit: feature.unit ?? responseUnit,
1988
- metadata: {
1989
- quantitation: 'targeted',
1990
- ...feature.metadata,
1991
- },
1992
- }
1993
- })
1994
- }
1995
-
1996
- function normalizeInternalStandardReagents(
1997
- internalStandards: Array<string | ReagentTemplateInput>,
1998
- ): ReagentTemplateInput[] {
1999
- return internalStandards.map((standard, index) => {
2000
- if (typeof standard === 'string') {
2001
- return {
2002
- id: idFromName(standard, `internal-standard-${index + 1}`),
2003
- name: standard,
2004
- kind: 'compound',
2005
- metadata: { role: 'internal-standard' },
2006
- }
2007
- }
2008
-
2009
- const reagent = normalizeReagent(standard)
2010
- return {
2011
- ...reagent,
2012
- kind: standard.kind ?? 'compound',
2013
- metadata: {
2014
- role: 'internal-standard',
2015
- ...reagent.metadata,
2016
- },
2017
- }
2018
- })
2019
- }
2020
-
2021
- function targetedMetabolomicsRunItems(
2022
- samples: SampleRecord[],
2023
- calibrationPoints: CalibrationPoint[],
2024
- includeQc: boolean,
2025
- ): InstrumentRunItemInput[] {
2026
- const calibrationItems = calibrationPoints
2027
- .filter(point => point.role !== 'qc' || includeQc)
2028
- .filter(point => point.role === 'blank' || point.role === 'standard' || point.role === 'qc')
2029
- .map((point): InstrumentRunItemInput => ({
2030
- id: `${point.id}-run`,
2031
- kind: calibrationRunItemKind(point.role),
2032
- name: point.level,
2033
- metadata: {
2034
- concentration: point.concentration,
2035
- unit: point.unit,
2036
- role: point.role,
2037
- },
2038
- }))
2039
-
2040
- const sampleItems = samples.map((sample): InstrumentRunItemInput => ({
2041
- sampleId: sample.sampleId,
2042
- name: sample.name ?? sample.sampleId,
2043
- kind: 'sample',
2044
- }))
2045
-
2046
- return [...calibrationItems, ...sampleItems]
2047
- }
2048
-
2049
- function calibrationRunItemKind(role: CalibrationPoint['role']): 'blank' | 'standard' | 'qc' {
2050
- if (role === 'blank' || role === 'standard' || role === 'qc') return role
2051
- return 'standard'
2052
- }
2053
-
2054
- function firstFeatureName(features: AssayFeature[], fallback: string): string {
2055
- return features.find(feature => feature.name)?.name ?? fallback
2056
- }
2057
-
2058
- function normalizeDateString(value: Date | string | null | undefined): string | undefined {
2059
- if (value === undefined || value === null) return undefined
2060
- return value instanceof Date ? value.toISOString() : value
2061
- }
2062
-
2063
- function normalizeFlowMarker(
2064
- marker: Partial<FlowPanelMarker> & { marker: string },
2065
- index: number
2066
- ): FlowPanelMarker {
2067
- return {
2068
- id: marker.id ?? idFromName(marker.marker, `marker-${index + 1}`),
2069
- marker: marker.marker,
2070
- fluorophore: marker.fluorophore ?? 'unassigned',
2071
- detector: marker.detector,
2072
- clone: marker.clone,
2073
- reagentId: marker.reagentId,
2074
- purpose: marker.purpose ?? 'phenotype',
2075
- compensationRequired: marker.compensationRequired ?? true,
2076
- metadata: marker.metadata ?? {},
2077
- }
2078
- }
2079
-
2080
- function normalizeFlowControl(
2081
- control: Partial<FlowPanelControl> & { name: string },
2082
- index: number
2083
- ): FlowPanelControl {
2084
- return {
2085
- id: control.id ?? idFromName(control.name, `control-${index + 1}`),
2086
- name: control.name,
2087
- kind: control.kind ?? 'other',
2088
- markerId: control.markerId,
2089
- required: control.required ?? true,
2090
- metadata: control.metadata ?? {},
2091
- }
2092
- }
2093
-
2094
- function normalizeInstrumentMethod(
2095
- method: Partial<InstrumentMethod> & { name?: string },
2096
- fallbackInstrument?: string
2097
- ): InstrumentMethod {
2098
- const name = method.name || method.id || 'Default method'
2099
- return {
2100
- id: method.id || idFromName(name, 'method-1'),
2101
- name,
2102
- instrument: method.instrument ?? fallbackInstrument,
2103
- acquisitionMode: method.acquisitionMode,
2104
- metadata: method.metadata ?? {},
2105
- }
2106
- }
2107
-
2108
- function normalizeInstrumentRunItem(
2109
- item: Partial<InstrumentRunItem> & { name?: string; sampleId?: string },
2110
- defaultMethodId: string,
2111
- fallbackOrder: number,
2112
- index: number
2113
- ): InstrumentRunItem {
2114
- const kind = item.kind ?? 'sample'
2115
- const sampleId = item.sampleId || (kind === 'sample'
2116
- ? idFromName(item.name ?? `Sample ${index + 1}`, `sample-${index + 1}`)
2117
- : undefined)
2118
- const label = item.name ?? sampleId ?? kind
2119
- return {
2120
- id: item.id || idFromName(label, `${kind}-${index + 1}`),
2121
- order: item.order ?? fallbackOrder,
2122
- kind,
2123
- sampleId,
2124
- name: item.name,
2125
- methodId: item.methodId || defaultMethodId,
2126
- vial: item.vial,
2127
- plateId: item.plateId,
2128
- wellId: item.wellId,
2129
- injectionVolume: item.injectionVolume,
2130
- expectedDurationMin: item.expectedDurationMin,
2131
- status: item.status ?? 'planned',
2132
- metadata: item.metadata ?? {},
2133
- }
2134
- }
2135
-
2136
- function normalizeQpcrSample(sample: QpcrSample): QpcrSample {
2137
- return {
2138
- ...sample,
2139
- metadata: sample.metadata ?? {},
2140
- }
2141
- }
2142
-
2143
- function normalizeQpcrTarget(target: QpcrTarget): QpcrTarget {
2144
- return {
2145
- ...target,
2146
- metadata: target.metadata ?? {},
2147
- }
2148
- }
2149
-
2150
- function defaultFlowControls(markers: FlowPanelMarker[]): FlowPanelControl[] {
2151
- return [
2152
- {
2153
- id: 'unstained',
2154
- name: 'Unstained',
2155
- kind: 'unstained',
2156
- required: true,
2157
- metadata: {},
2158
- },
2159
- ...markers
2160
- .filter(marker => marker.compensationRequired)
2161
- .map(marker => ({
2162
- id: `${marker.id}-single-stain`,
2163
- name: `${marker.marker} single stain`,
2164
- kind: 'single-stain' as const,
2165
- markerId: marker.id,
2166
- required: true,
2167
- metadata: {},
2168
- })),
2169
- ]
2170
- }
2171
-
2172
- function createAssaySampleLookup(samples: AssaySample[]): Map<string, string> {
2173
- const lookup = new Map<string, string>()
2174
- for (const sample of samples) {
2175
- addLookupAlias(lookup, sample.sampleId, sample.sampleId)
2176
- if (sample.name) {
2177
- addLookupAlias(lookup, sample.name, sample.sampleId)
2178
- addLookupAlias(lookup, idFromName(sample.name, sample.sampleId), sample.sampleId)
2179
- }
2180
- }
2181
- return lookup
2182
- }
2183
-
2184
- function createAssayFeatureLookup(features: AssayFeature[]): Map<string, string> {
2185
- const lookup = new Map<string, string>()
2186
- for (const feature of features) {
2187
- addLookupAlias(lookup, feature.id, feature.id)
2188
- addLookupAlias(lookup, feature.name, feature.id)
2189
- addLookupAlias(lookup, idFromName(feature.name, feature.id), feature.id)
2190
- }
2191
- return lookup
2192
- }
2193
-
2194
- function addLookupAlias(lookup: Map<string, string>, alias: string, value: string): void {
2195
- if (!lookup.has(alias)) {
2196
- lookup.set(alias, value)
2197
- }
2198
- }
2199
-
2200
- function wellIdsForFormat(format: number): string[] {
2201
- const dimensions = PLATE_DIMENSIONS[format]
2202
- if (!dimensions) {
2203
- throw new Error(`Unsupported plate format '${format}'.`)
2204
- }
2205
- const [rows, cols] = dimensions
2206
- return Array.from({ length: rows }, (_, row) =>
2207
- Array.from({ length: cols }, (_col, col) => `${rowLabel(row)}${col + 1}`)
2208
- ).flat()
2209
- }
2210
-
2211
- function assertWellInFormat(wellId: string, format: number): void {
2212
- const dimensions = PLATE_DIMENSIONS[format]
2213
- if (!dimensions) {
2214
- throw new Error(`Unsupported plate format '${format}'.`)
2215
- }
2216
- const match = /^([A-Z]+)([1-9][0-9]*)$/.exec(wellId.toUpperCase())
2217
- if (!match) {
2218
- throw new Error(`Invalid well id '${wellId}'.`)
2219
- }
2220
- const row = rowIndex(match[1])
2221
- const col = Number(match[2]) - 1
2222
- const [rows, cols] = dimensions
2223
- if (row < 0 || col < 0 || row >= rows || col >= cols) {
2224
- throw new Error(`Well '${wellId}' is outside a ${format}-well plate.`)
2225
- }
2226
- }
2227
-
2228
- function rowLabel(index: number): string {
2229
- let label = ''
2230
- let current = index
2231
- while (true) {
2232
- label = String.fromCharCode(65 + (current % 26)) + label
2233
- current = Math.floor(current / 26) - 1
2234
- if (current < 0) return label
2235
- }
2236
- }
2237
-
2238
- function rowIndex(label: string): number {
2239
- return [...label].reduce((value, char) => value * 26 + char.charCodeAt(0) - 64, 0) - 1
2240
- }
2241
-
2242
- function assertGenericTemplateEnvelope(value: unknown): asserts value is BioTemplateEnvelope<unknown> {
2243
- if (!isRecord(value)) {
2244
- throw new Error('Template envelope must be an object.')
2245
- }
2246
- if (typeof value.template_id !== 'string' || value.template_id.length === 0) {
2247
- throw new Error('Template envelope requires a template_id string.')
2248
- }
2249
- if (typeof value.template_version !== 'string' || value.template_version.length === 0) {
2250
- throw new Error('Template envelope requires a template_version string.')
2251
- }
2252
- if (!isRecord(value.data)) {
2253
- throw new Error('Template envelope data must be an object.')
2254
- }
2255
- if ('metadata' in value && !isRecord(value.metadata)) {
2256
- throw new Error('Template envelope metadata must be an object.')
2257
- }
2258
- }
2259
-
2260
- function isEnvelope<TData>(value: unknown): value is BioTemplateEnvelope<TData> {
2261
- return isRecord(value) && 'template_id' in value && 'data' in value
2262
- }
2263
-
2264
- function isRecord(value: unknown): value is Record<string, unknown> {
2265
- return typeof value === 'object' && value !== null && !Array.isArray(value)
2266
- }
2267
-
2268
- function readStringList(value: unknown, fallback: string[]): string[] {
2269
- if (Array.isArray(value)) {
2270
- const items = value.map(item => String(item).trim()).filter(Boolean)
2271
- return items.length ? items : fallback
2272
- }
2273
- if (typeof value === 'string') {
2274
- const items = value.split(',').map(item => item.trim()).filter(Boolean)
2275
- return items.length ? items : fallback
2276
- }
2277
- return fallback
2278
- }
2279
-
2280
- function readNumberList(value: unknown, fallback: number[]): number[] {
2281
- const rawItems = Array.isArray(value)
2282
- ? value
2283
- : typeof value === 'string'
2284
- ? value.split(',')
2285
- : []
2286
- const items = rawItems
2287
- .map(item => Number(String(item).trim()))
2288
- .filter(item => Number.isFinite(item))
2289
- return items.length ? items : fallback
2290
- }
2291
-
2292
- function readString(value: unknown, fallback: string): string {
2293
- return typeof value === 'string' && value.trim() ? value.trim() : fallback
2294
- }
2295
-
2296
- function readOptionalString(value: unknown): string | undefined {
2297
- return typeof value === 'string' && value.trim() ? value.trim() : undefined
2298
- }
2299
-
2300
- function readInteger(value: unknown, fallback: number): number {
2301
- const parsed = typeof value === 'number'
2302
- ? value
2303
- : typeof value === 'string'
2304
- ? Number(value)
2305
- : Number.NaN
2306
- return Number.isFinite(parsed) ? Math.max(1, Math.round(parsed)) : fallback
2307
- }
2308
-
2309
- function readNumber(value: unknown, fallback: number): number {
2310
- const parsed = typeof value === 'number'
2311
- ? value
2312
- : typeof value === 'string'
2313
- ? Number(value)
2314
- : Number.NaN
2315
- return Number.isFinite(parsed) ? parsed : fallback
2316
- }
2317
-
2318
- function readBoolean(value: unknown, fallback: boolean): boolean {
2319
- return typeof value === 'boolean' ? value : fallback
2320
- }
2321
-
2322
- function readPlateFormat(value: unknown, fallback: WellPlateFormat): WellPlateFormat {
2323
- const parsed = typeof value === 'number'
2324
- ? value
2325
- : typeof value === 'string'
2326
- ? Number(value)
2327
- : Number.NaN
2328
- return parsed in PLATE_DIMENSIONS ? (parsed as WellPlateFormat) : fallback
2329
- }
2330
-
2331
- function sampleIdFromName(name: string, index: number): string {
2332
- return idFromName(name, `sample-${index + 1}`)
2333
- }
2334
-
2335
- function compoundIdFromName(name: string, index: number): string {
2336
- return idFromName(name, `compound-${index + 1}`)
2337
- }
2338
-
2339
- function idFromName(name: string, fallback: string): string {
2340
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
2341
- return slug || fallback
2342
- }
2343
-
2344
- function timepointId(value: number, unit: string, index: number): string {
2345
- const raw = Number.isInteger(value)
2346
- ? String(value)
2347
- : String(value).replace(/0+$/, '').replace(/\.$/, '')
2348
- const token = raw.replace('.', 'p')
2349
- return token ? `t${token}-${unit}` : `timepoint-${index + 1}`
2350
- }
1
+ export {
2
+ bioTemplatePresetControlValuesToOptions,
3
+ } from './builderPresetControls'
4
+
5
+ export type {
6
+ BioTemplateControlValues,
7
+ } from './builderPresetControls'
8
+
9
+ export {
10
+ createPlateMapTemplate,
11
+ } from './plateMapBuilder'
12
+
13
+ export {
14
+ createSampleSheetTemplate,
15
+ } from './sampleSheetBuilder'
16
+
17
+ export {
18
+ createSamplePrepTemplate,
19
+ } from './samplePrepBuilder'
20
+
21
+ export {
22
+ createDoseResponseTemplate,
23
+ } from './doseResponseBuilder'
24
+
25
+ export {
26
+ createCalibrationCurveTemplate,
27
+ } from './calibrationCurveBuilder'
28
+
29
+ export {
30
+ createTimeCourseTemplate,
31
+ } from './timeCourseBuilder'
32
+
33
+ export {
34
+ createProtocolStepsTemplate,
35
+ } from './protocolStepsBuilder'
36
+
37
+ export {
38
+ createAssayMatrixTemplate,
39
+ } from './assayMatrixBuilder'
40
+
41
+ export {
42
+ createReagentListTemplate,
43
+ } from './reagentListBuilder'
44
+
45
+ export {
46
+ createFlowCytometryPanelTemplate,
47
+ } from './flowCytometryPanelBuilder'
48
+
49
+ export {
50
+ createInstrumentRunTemplate,
51
+ } from './instrumentRunBuilder'
52
+
53
+ export {
54
+ createQpcrPlateTemplate,
55
+ } from './qpcrPlateBuilder'
56
+
57
+ export {
58
+ createQpcrExpressionCollection,
59
+ } from './qpcrExpressionCollectionBuilder'
60
+
61
+ export {
62
+ createLcmsBatchCollection,
63
+ } from './lcmsBatchCollectionBuilder'
64
+
65
+ export {
66
+ createTargetedMetabolomicsCollection,
67
+ } from './targetedMetabolomicsCollectionBuilder'
68
+
69
+ export {
70
+ createElisaAssayCollection,
71
+ } from './elisaAssayCollectionBuilder'
72
+
73
+ export {
74
+ createFlowCytometryAssayCollection,
75
+ } from './flowCytometryAssayCollectionBuilder'
76
+
77
+ export {
78
+ createWesternBlotAssayCollection,
79
+ } from './westernBlotAssayCollectionBuilder'
80
+
81
+ export {
82
+ createDefaultBioTemplate,
83
+ } from './defaultBioTemplateBuilder'
84
+
85
+ export {
86
+ createBioTemplatePackCollection,
87
+ } from './templatePackCollectionBuilder'
88
+
89
+ export {
90
+ createWellPlateScreenCollection,
91
+ } from './wellPlateScreenCollectionBuilder'
92
+
93
+ export {
94
+ createBioTemplatePresetCollection,
95
+ createBioTemplatePresetCollectionFromControls,
96
+ } from './templatePresetCollectionBuilder'
97
+
98
+ export {
99
+ assertTemplateEnvelope,
100
+ createTemplateCollection,
101
+ createTemplateEnvelope,
102
+ ensureTemplateEnvelope,
103
+ ensureTemplateFromCollection,
104
+ extractTemplateCollection,
105
+ getTemplateData,
106
+ TEMPLATE_COLLECTION_KEY,
107
+ } from './templateEnvelopes'
108
+
109
+ export {
110
+ validateAssayMatrixData,
111
+ validateCalibrationCurveData,
112
+ validateDoseResponseData,
113
+ validateFlowCytometryPanelData,
114
+ validateInstrumentRunData,
115
+ validatePlateMapData,
116
+ validateProtocolStepsData,
117
+ validateQpcrPlateData,
118
+ validateReagentListData,
119
+ validateSamplePrepData,
120
+ validateSampleSheetData,
121
+ validateTimeCourseData,
122
+ } from './templateValidators'