@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.
- package/dist/__tests__/components/AppTopBar.navigation.test.d.ts +1 -0
- package/dist/__tests__/components/DoseCalculatorVolumeField.test.d.ts +1 -0
- package/dist/__tests__/components/PlateMapEditorToolbarInternal.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.controls.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.navigation.test.d.ts +1 -0
- package/dist/__tests__/components/PluginWorkspaceView.shell.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStep.presentation.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStepEditor.state.test.d.ts +1 -0
- package/dist/__tests__/components/ProtocolStepParameterField.test.d.ts +1 -0
- package/dist/__tests__/components/ReagentList.presentation.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.colors.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.drag.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.groups.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelector.selection.test.d.ts +1 -0
- package/dist/__tests__/components/SampleSelectorSampleRow.test.d.ts +1 -0
- package/dist/__tests__/components/ScheduleCalendar.test.d.ts +1 -0
- package/dist/__tests__/components/SettingsModal.schema.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.colors.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.conditions.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.geometry.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.interaction.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.legend.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.rendering.test.d.ts +1 -0
- package/dist/__tests__/components/WellPlate.sampleDrop.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/classify.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/columns.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/compose.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/cooccurrence.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/fingerprint.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/integration.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/template.test.d.ts +1 -0
- package/dist/__tests__/composables/autoGroup/tokenize.test.d.ts +1 -0
- package/dist/__tests__/composables/useAutoGroupInputSources.test.d.ts +1 -0
- package/dist/__tests__/composables/useScheduleCalendarLayout.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsComponents.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsExports.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsParsing.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsTemplates.test.d.ts +1 -0
- package/dist/__tests__/docs/extractDocsTheme.test.d.ts +1 -0
- package/dist/components/AppTopBar.navigation.d.ts +11 -0
- package/dist/components/BaseButton.vue.d.ts +1 -1
- package/dist/components/BaseCheckbox.vue.d.ts +1 -1
- package/dist/components/BaseInput.vue.d.ts +2 -2
- package/dist/components/BasePill.vue.d.ts +1 -1
- package/dist/components/BaseRadioGroup.vue.d.ts +1 -1
- package/dist/components/BaseSelect.vue.d.ts +1 -1
- package/dist/components/BaseSlider.vue.d.ts +2 -2
- package/dist/components/BaseTextarea.vue.d.ts +2 -2
- package/dist/components/BaseToggle.vue.d.ts +1 -1
- package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
- package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
- package/dist/components/ColorSlider.vue.d.ts +2 -2
- package/dist/components/ConcentrationInput.vue.d.ts +2 -2
- package/dist/components/DatePicker.vue.d.ts +1 -1
- package/dist/components/DateTimePicker.vue.d.ts +2 -2
- package/dist/components/DoseCalculatorVolumeField.vue.d.ts +15 -0
- package/dist/components/DropdownButton.vue.d.ts +1 -1
- package/dist/components/FileUploader.vue.d.ts +2 -2
- package/dist/components/FormulaInput.vue.d.ts +2 -2
- package/dist/components/IconButton.vue.d.ts +1 -1
- package/dist/components/LoadingSpinner.vue.d.ts +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +2 -2
- package/dist/components/MultiSelect.vue.d.ts +1 -1
- package/dist/components/NumberInput.vue.d.ts +1 -1
- package/dist/components/PlateMapEditor.vue.d.ts +6 -6
- package/dist/components/PluginWorkspaceView.controls.d.ts +28 -0
- package/dist/components/PluginWorkspaceView.navigation.d.ts +29 -0
- package/dist/components/PluginWorkspaceView.props.d.ts +151 -0
- package/dist/components/PluginWorkspaceView.shell.d.ts +19 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +46 -195
- package/dist/components/ProgressBar.vue.d.ts +1 -1
- package/dist/components/ProtocolStep.presentation.d.ts +4 -0
- package/dist/components/ProtocolStepEditor.state.d.ts +18 -0
- package/dist/components/ProtocolStepParameterField.vue.d.ts +12 -0
- package/dist/components/ReagentList.presentation.d.ts +16 -0
- package/dist/components/ResourceCard.vue.d.ts +1 -1
- package/dist/components/SampleSelector.colors.d.ts +13 -0
- package/dist/components/SampleSelector.drag.d.ts +24 -0
- package/dist/components/SampleSelector.groups.d.ts +15 -0
- package/dist/components/SampleSelector.selection.d.ts +26 -0
- package/dist/components/SampleSelector.vue.d.ts +4 -1
- package/dist/components/SampleSelectorSampleRow.vue.d.ts +21 -0
- package/dist/components/SegmentedControl.vue.d.ts +1 -1
- package/dist/components/SequenceInput.vue.d.ts +2 -2
- package/dist/components/SequenceProgressBar.vue.d.ts +1 -1
- package/dist/components/SettingsModal.schema.d.ts +9 -0
- package/dist/components/StatusIndicator.vue.d.ts +1 -1
- package/dist/components/TagsInput.vue.d.ts +2 -2
- package/dist/components/TimePicker.vue.d.ts +2 -2
- package/dist/components/TimeRangeInput.vue.d.ts +1 -1
- package/dist/components/UnitInput.vue.d.ts +2 -2
- package/dist/components/WellPlate.colors.d.ts +9 -0
- package/dist/components/WellPlate.conditions.d.ts +26 -0
- package/dist/components/WellPlate.geometry.d.ts +23 -0
- package/dist/components/WellPlate.interaction.d.ts +71 -0
- package/dist/components/WellPlate.legend.d.ts +2 -0
- package/dist/components/WellPlate.rendering.d.ts +24 -0
- package/dist/components/WellPlate.sampleDrop.d.ts +8 -0
- package/dist/components/WellPlate.vue.d.ts +1 -1
- package/dist/components/index.js +2 -2
- package/dist/components/internal/ActionItemInternal.vue.d.ts +1 -1
- package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +28 -0
- package/dist/{components-BhK-dW99.js → components-DtHA2bgp.js} +3754 -2991
- package/dist/components-DtHA2bgp.js.map +1 -0
- package/dist/composables/autoGroup/classKey.d.ts +4 -0
- package/dist/composables/autoGroup/classify.d.ts +28 -0
- package/dist/composables/autoGroup/colors.d.ts +2 -0
- package/dist/composables/autoGroup/columns.d.ts +10 -0
- package/dist/composables/autoGroup/compose.d.ts +8 -0
- package/dist/composables/autoGroup/cooccurrence.d.ts +2 -0
- package/dist/composables/autoGroup/csv-shim.d.ts +2 -0
- package/dist/composables/autoGroup/fingerprint.d.ts +3 -0
- package/dist/composables/autoGroup/index.d.ts +16 -0
- package/dist/composables/autoGroup/replicatePreGroup.d.ts +38 -0
- package/dist/composables/autoGroup/template.d.ts +15 -0
- package/dist/composables/autoGroup/tokenize.d.ts +8 -0
- package/dist/composables/autoGroupConstants.d.ts +1 -0
- package/dist/composables/autoGroupGrouping.d.ts +3 -0
- package/dist/composables/controlComponentBindings.d.ts +7 -0
- package/dist/composables/controlSchemaAdapters.d.ts +20 -0
- package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
- package/dist/composables/controlSchemaFormFields.d.ts +3 -0
- package/dist/composables/controlSchemaLayout.d.ts +7 -0
- package/dist/composables/controlSchemaModel.d.ts +5 -0
- package/dist/composables/controlSchemaNormalize.d.ts +15 -0
- package/dist/composables/controlSchemaTypes.d.ts +305 -0
- package/dist/composables/controlSchemaUtils.d.ts +9 -0
- package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
- package/dist/composables/formBuilderSchema.d.ts +18 -0
- package/dist/composables/index.js +3 -3
- package/dist/composables/pluginEndpointBuilder.d.ts +13 -0
- package/dist/composables/protocolTemplateCatalog.d.ts +26 -0
- package/dist/composables/useAutoGroup.d.ts +61 -74
- package/dist/composables/useAutoGroupInputSources.d.ts +32 -0
- package/dist/composables/useBioTemplateControls.d.ts +1 -1
- package/dist/composables/useBioTemplatePresetWorkspace.d.ts +1 -1
- package/dist/composables/useBioTemplateWorkspace.d.ts +1 -1
- package/dist/composables/useControlSchema.d.ts +8 -346
- package/dist/composables/useControlWorkspace.d.ts +5 -0
- package/dist/composables/useForm.d.ts +2 -33
- package/dist/composables/useFormBuilder.d.ts +2 -9
- package/dist/composables/useFormValidation.d.ts +34 -0
- package/dist/composables/usePluginClient.d.ts +1 -4
- package/dist/composables/useProtocolTemplates.d.ts +2 -24
- package/dist/composables/useScheduleCalendarLayout.d.ts +49 -0
- package/dist/{composables-Bg7CFuNz.js → composables-Dlg8jenH.js} +33 -31
- package/dist/composables-Dlg8jenH.js.map +1 -0
- package/dist/index.js +4 -4
- package/dist/install.js +2 -2
- package/dist/styles.css +547 -516
- package/dist/templates/adapters.d.ts +14 -47
- package/dist/templates/assayLookups.d.ts +3 -0
- package/dist/templates/assayMatrixAdapters.d.ts +6 -0
- package/dist/templates/assayMatrixBuilder.d.ts +2 -0
- package/dist/templates/assayNormalizers.d.ts +4 -0
- package/dist/templates/builderDefaults.d.ts +1 -0
- package/dist/templates/builderIdUtils.d.ts +4 -0
- package/dist/templates/builderPresetControls.d.ts +10 -0
- package/dist/templates/builderReadUtils.d.ts +8 -0
- package/dist/templates/builders.d.ts +26 -67
- package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
- package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
- package/dist/templates/calibrationNormalizers.d.ts +5 -0
- package/dist/templates/componentBindingCatalog.d.ts +25 -0
- package/dist/templates/componentBindingHelpers.d.ts +17 -0
- package/dist/templates/componentDoseResponseProps.d.ts +3 -0
- package/dist/templates/componentGenericProps.d.ts +8 -0
- package/dist/templates/componentPlateHelpers.d.ts +5 -0
- package/dist/templates/componentPlateMapProps.d.ts +3 -0
- package/dist/templates/componentPropsFactory.d.ts +3 -0
- package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
- package/dist/templates/componentTargetResolvers.d.ts +5 -0
- package/dist/templates/componentTemplateProps.d.ts +3 -0
- package/dist/templates/controlSchemaClone.d.ts +4 -0
- package/dist/templates/controlSchemaConstants.d.ts +10 -0
- package/dist/templates/controlSchemaMerge.d.ts +3 -0
- package/dist/templates/controlSchemaTargets.d.ts +17 -0
- package/dist/templates/controlSchemaTypes.d.ts +4 -0
- package/dist/templates/controlSchemas.d.ts +4 -4
- package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
- package/dist/templates/doseResponseAdapters.d.ts +4 -0
- package/dist/templates/doseResponseBuilder.d.ts +2 -0
- package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
- package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
- package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
- package/dist/templates/flowNormalizers.d.ts +8 -0
- package/dist/templates/flowPanelAdapters.d.ts +4 -0
- package/dist/templates/index.js +1 -1
- package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
- package/dist/templates/instrumentRunAdapters.d.ts +8 -0
- package/dist/templates/instrumentRunBuilder.d.ts +2 -0
- package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
- package/dist/templates/plateGeometry.d.ts +4 -0
- package/dist/templates/plateMapAdapters.d.ts +3 -0
- package/dist/templates/plateMapBuilder.d.ts +2 -0
- package/dist/templates/presetControlSchemas.d.ts +534 -0
- package/dist/templates/protocolAdapters.d.ts +5 -0
- package/dist/templates/protocolNormalizers.d.ts +6 -0
- package/dist/templates/protocolStepsBuilder.d.ts +2 -0
- package/dist/templates/qpcrAdapters.d.ts +5 -0
- package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
- package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
- package/dist/templates/reagentAdapters.d.ts +5 -0
- package/dist/templates/reagentListBuilder.d.ts +2 -0
- package/dist/templates/runNormalizers.d.ts +10 -0
- package/dist/templates/sampleNormalizers.d.ts +4 -0
- package/dist/templates/samplePrepAdapters.d.ts +4 -0
- package/dist/templates/samplePrepBuilder.d.ts +2 -0
- package/dist/templates/sampleSheetAdapters.d.ts +5 -0
- package/dist/templates/sampleSheetBuilder.d.ts +2 -0
- package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
- package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
- package/dist/templates/templateAdapterTypes.d.ts +48 -0
- package/dist/templates/templateControlSchemas.d.ts +400 -0
- package/dist/templates/templateCreateOptions.d.ts +165 -0
- package/dist/templates/templateEnvelopes.d.ts +9 -0
- package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
- package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
- package/dist/templates/templateQpcrTypes.d.ts +42 -0
- package/dist/templates/templateValidators.d.ts +13 -0
- package/dist/templates/timeCourseAdapters.d.ts +5 -0
- package/dist/templates/timeCourseBuilder.d.ts +2 -0
- package/dist/templates/types.d.ts +5 -250
- package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
- package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
- package/dist/{templates-BorLR_7p.js → templates-DtdUvJ4c.js} +3565 -3411
- package/dist/templates-DtdUvJ4c.js.map +1 -0
- package/dist/types/auto-group.d.ts +79 -9
- package/dist/types/componentLabTypes.d.ts +161 -0
- package/dist/types/componentWorkflowTypes.d.ts +150 -0
- package/dist/types/components.d.ts +2 -311
- package/dist/{useProtocolTemplates-n6AJqSqv.js → useProtocolTemplates-Bm5vyH4_.js} +1220 -454
- package/dist/useProtocolTemplates-Bm5vyH4_.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.navigation.test.ts +70 -0
- package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +53 -0
- package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +54 -0
- package/src/__tests__/components/PluginWorkspaceView.controls.test.ts +156 -0
- package/src/__tests__/components/PluginWorkspaceView.navigation.test.ts +102 -0
- package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +41 -0
- package/src/__tests__/components/ProtocolStep.presentation.test.ts +31 -0
- package/src/__tests__/components/ProtocolStepEditor.state.test.ts +165 -0
- package/src/__tests__/components/ProtocolStepParameterField.test.ts +44 -0
- package/src/__tests__/components/ReagentList.presentation.test.ts +68 -0
- package/src/__tests__/components/SampleSelector.colors.test.ts +49 -0
- package/src/__tests__/components/SampleSelector.drag.test.ts +100 -0
- package/src/__tests__/components/SampleSelector.groups.test.ts +81 -0
- package/src/__tests__/components/SampleSelector.selection.test.ts +70 -0
- package/src/__tests__/components/SampleSelector.test.ts +32 -0
- package/src/__tests__/components/SampleSelectorSampleRow.test.ts +37 -0
- package/src/__tests__/components/ScheduleCalendar.test.ts +44 -0
- package/src/__tests__/components/SettingsModal.schema.test.ts +97 -0
- package/src/__tests__/components/WellPlate.colors.test.ts +28 -0
- package/src/__tests__/components/WellPlate.conditions.test.ts +68 -0
- package/src/__tests__/components/WellPlate.geometry.test.ts +54 -0
- package/src/__tests__/components/WellPlate.interaction.test.ts +171 -0
- package/src/__tests__/components/WellPlate.legend.test.ts +13 -0
- package/src/__tests__/components/WellPlate.rendering.test.ts +122 -0
- package/src/__tests__/components/WellPlate.sampleDrop.test.ts +70 -0
- package/src/__tests__/composables/autoGroup/classify.test.ts +107 -0
- package/src/__tests__/composables/autoGroup/columns.test.ts +135 -0
- package/src/__tests__/composables/autoGroup/compose.test.ts +227 -0
- package/src/__tests__/composables/autoGroup/cooccurrence.test.ts +91 -0
- package/src/__tests__/composables/autoGroup/fingerprint.test.ts +50 -0
- package/src/__tests__/composables/autoGroup/integration.test.ts +79 -0
- package/src/__tests__/composables/autoGroup/template.test.ts +70 -0
- package/src/__tests__/composables/autoGroup/tokenize.test.ts +33 -0
- package/src/__tests__/composables/useAutoGroup.test.ts +129 -625
- package/src/__tests__/composables/useAutoGroupInputSources.test.ts +107 -0
- package/src/__tests__/composables/useControlSchema.test.ts +23 -0
- package/src/__tests__/composables/useScheduleCalendarLayout.test.ts +89 -0
- package/src/__tests__/docs/extractDocsComponents.test.ts +142 -0
- package/src/__tests__/docs/extractDocsExports.test.ts +77 -0
- package/src/__tests__/docs/extractDocsParsing.test.ts +69 -0
- package/src/__tests__/docs/extractDocsTemplates.test.ts +54 -0
- package/src/__tests__/docs/extractDocsTheme.test.ts +89 -0
- package/src/__tests__/docs/frontendDocsCatalog.test.ts +1 -1
- package/src/__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt +187 -0
- package/src/components/AppSidebar.vue +2 -6
- package/src/components/AppTopBar.navigation.ts +62 -0
- package/src/components/AppTopBar.vue +17 -44
- package/src/components/AutoGroupModal.story.vue +50 -0
- package/src/components/AutoGroupModal.vue +441 -158
- package/src/components/ControlWorkspaceView.vue +2 -6
- package/src/components/DoseCalculator.vue +13 -73
- package/src/components/DoseCalculatorVolumeField.vue +61 -0
- package/src/components/ExperimentTimeline.vue +6 -31
- package/src/components/FormBuilder.vue +2 -7
- package/src/components/PlateMapEditor.vue +32 -106
- package/src/components/PluginWorkspaceView.controls.ts +182 -0
- package/src/components/PluginWorkspaceView.navigation.ts +106 -0
- package/src/components/PluginWorkspaceView.props.ts +174 -0
- package/src/components/PluginWorkspaceView.shell.ts +66 -0
- package/src/components/PluginWorkspaceView.vue +85 -404
- package/src/components/ProtocolStep.presentation.ts +31 -0
- package/src/components/ProtocolStepEditor.state.ts +104 -0
- package/src/components/ProtocolStepEditor.vue +48 -179
- package/src/components/ProtocolStepParameterField.vue +134 -0
- package/src/components/ReagentList.presentation.ts +105 -0
- package/src/components/ReagentList.vue +16 -79
- package/src/components/SampleSelector.colors.ts +43 -0
- package/src/components/SampleSelector.drag.ts +164 -0
- package/src/components/SampleSelector.groups.ts +109 -0
- package/src/components/SampleSelector.selection.ts +103 -0
- package/src/components/SampleSelector.vue +82 -349
- package/src/components/SampleSelectorSampleRow.vue +64 -0
- package/src/components/ScheduleCalendar.vue +44 -199
- package/src/components/SettingsModal.schema.ts +71 -0
- package/src/components/SettingsModal.vue +16 -46
- package/src/components/WellPlate.colors.ts +56 -0
- package/src/components/WellPlate.conditions.ts +100 -0
- package/src/components/WellPlate.geometry.ts +91 -0
- package/src/components/WellPlate.interaction.ts +272 -0
- package/src/components/WellPlate.legend.ts +8 -0
- package/src/components/WellPlate.rendering.ts +105 -0
- package/src/components/WellPlate.sampleDrop.ts +73 -0
- package/src/components/WellPlate.vue +102 -550
- package/src/components/internal/PlateMapEditorToolbarInternal.vue +128 -0
- package/src/composables/autoGroup/classKey.ts +5 -0
- package/src/composables/autoGroup/classify.ts +205 -0
- package/src/composables/autoGroup/colors.ts +6 -0
- package/src/composables/autoGroup/columns.ts +226 -0
- package/src/composables/autoGroup/compose.ts +156 -0
- package/src/composables/autoGroup/cooccurrence.ts +46 -0
- package/src/composables/autoGroup/csv-shim.ts +44 -0
- package/src/composables/autoGroup/fingerprint.ts +49 -0
- package/src/composables/autoGroup/index.ts +20 -0
- package/src/composables/autoGroup/replicatePreGroup.ts +90 -0
- package/src/composables/autoGroup/template.ts +126 -0
- package/src/composables/autoGroup/tokenize.ts +41 -0
- package/src/composables/autoGroup/vocab.json +67 -0
- package/src/composables/autoGroupConstants.ts +4 -0
- package/src/composables/autoGroupGrouping.ts +148 -0
- package/src/composables/controlComponentBindings.ts +80 -0
- package/src/composables/controlSchemaAdapters.ts +196 -0
- package/src/composables/controlSchemaDoseDesign.ts +215 -0
- package/src/composables/controlSchemaFormFields.ts +61 -0
- package/src/composables/controlSchemaLayout.ts +59 -0
- package/src/composables/controlSchemaModel.ts +163 -0
- package/src/composables/controlSchemaNormalize.ts +101 -0
- package/src/composables/controlSchemaTypes.ts +364 -0
- package/src/composables/controlSchemaUtils.ts +36 -0
- package/src/composables/controlWorkspaceOptions.ts +21 -0
- package/src/composables/formBuilderSchema.ts +153 -0
- package/src/composables/pluginEndpointBuilder.ts +203 -0
- package/src/composables/protocolTemplateCatalog.ts +325 -0
- package/src/composables/useAutoGroup.ts +395 -549
- package/src/composables/useAutoGroupInputSources.ts +147 -0
- package/src/composables/useBioTemplateControls.ts +1 -1
- package/src/composables/useBioTemplatePresetWorkspace.ts +1 -1
- package/src/composables/useBioTemplateWorkspace.ts +1 -1
- package/src/composables/useControlSchema.ts +64 -1312
- package/src/composables/useControlWorkspace.ts +201 -0
- package/src/composables/useForm.ts +5 -187
- package/src/composables/useFormBuilder.ts +11 -153
- package/src/composables/useFormValidation.ts +154 -0
- package/src/composables/usePluginClient.ts +10 -193
- package/src/composables/useProtocolTemplates.ts +10 -328
- package/src/composables/useScheduleCalendarLayout.ts +287 -0
- package/src/styles/components/auto-group-modal.css +248 -310
- package/src/templates/adapters.ts +89 -930
- package/src/templates/assayLookups.ts +33 -0
- package/src/templates/assayMatrixAdapters.ts +78 -0
- package/src/templates/assayMatrixBuilder.ts +59 -0
- package/src/templates/assayNormalizers.ts +34 -0
- package/src/templates/builderDefaults.ts +11 -0
- package/src/templates/builderIdUtils.ts +20 -0
- package/src/templates/builderPresetControls.ts +165 -0
- package/src/templates/builderReadUtils.ts +57 -0
- package/src/templates/builders.ts +122 -2350
- package/src/templates/calibrationCurveAdapters.ts +59 -0
- package/src/templates/calibrationCurveBuilder.ts +99 -0
- package/src/templates/calibrationNormalizers.ts +60 -0
- package/src/templates/componentBindingCatalog.ts +90 -0
- package/src/templates/componentBindingHelpers.ts +93 -0
- package/src/templates/componentBindings.ts +12 -461
- package/src/templates/componentDoseResponseProps.ts +42 -0
- package/src/templates/componentGenericProps.ts +77 -0
- package/src/templates/componentPlateHelpers.ts +29 -0
- package/src/templates/componentPlateMapProps.ts +32 -0
- package/src/templates/componentPropsFactory.ts +21 -0
- package/src/templates/componentQpcrPlateProps.ts +28 -0
- package/src/templates/componentTargetResolvers.ts +69 -0
- package/src/templates/componentTemplateProps.ts +78 -0
- package/src/templates/controlSchemaClone.ts +32 -0
- package/src/templates/controlSchemaConstants.ts +11 -0
- package/src/templates/controlSchemaMerge.ts +40 -0
- package/src/templates/controlSchemaTargets.ts +87 -0
- package/src/templates/controlSchemaTypes.ts +20 -0
- package/src/templates/controlSchemas.ts +22 -704
- package/src/templates/defaultBioTemplateBuilder.ts +124 -0
- package/src/templates/doseResponseAdapters.ts +45 -0
- package/src/templates/doseResponseBuilder.ts +44 -0
- package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
- package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
- package/src/templates/flowCytometryPanelBuilder.ts +53 -0
- package/src/templates/flowNormalizers.ts +58 -0
- package/src/templates/flowPanelAdapters.ts +58 -0
- package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
- package/src/templates/instrumentRunAdapters.ts +163 -0
- package/src/templates/instrumentRunBuilder.ts +97 -0
- package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
- package/src/templates/plateGeometry.ts +62 -0
- package/src/templates/plateMapAdapters.ts +36 -0
- package/src/templates/plateMapBuilder.ts +43 -0
- package/src/templates/presetControlSchemas.ts +258 -0
- package/src/templates/protocolAdapters.ts +69 -0
- package/src/templates/protocolNormalizers.ts +37 -0
- package/src/templates/protocolStepsBuilder.ts +36 -0
- package/src/templates/qpcrAdapters.ts +104 -0
- package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
- package/src/templates/qpcrPlateBuilder.ts +96 -0
- package/src/templates/reagentAdapters.ts +77 -0
- package/src/templates/reagentListBuilder.ts +30 -0
- package/src/templates/runNormalizers.ts +63 -0
- package/src/templates/sampleNormalizers.ts +58 -0
- package/src/templates/samplePrepAdapters.ts +63 -0
- package/src/templates/samplePrepBuilder.ts +51 -0
- package/src/templates/sampleSheetAdapters.ts +75 -0
- package/src/templates/sampleSheetBuilder.ts +23 -0
- package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
- package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
- package/src/templates/templateAdapterTypes.ts +58 -0
- package/src/templates/templateControlSchemas.ts +320 -0
- package/src/templates/templateCreateOptions.ts +208 -0
- package/src/templates/templateEnvelopes.ts +137 -0
- package/src/templates/templatePackCollectionBuilder.ts +23 -0
- package/src/templates/templatePresetCollectionBuilder.ts +139 -0
- package/src/templates/templateQpcrTypes.ts +48 -0
- package/src/templates/templateValidators.ts +414 -0
- package/src/templates/timeCourseAdapters.ts +73 -0
- package/src/templates/timeCourseBuilder.ts +64 -0
- package/src/templates/types.ts +79 -275
- package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
- package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
- package/src/types/auto-group.ts +107 -9
- package/src/types/componentLabTypes.ts +235 -0
- package/src/types/componentWorkflowTypes.ts +190 -0
- package/src/types/components.ts +74 -424
- package/dist/components-BhK-dW99.js.map +0 -1
- package/dist/composables-Bg7CFuNz.js.map +0 -1
- package/dist/templates-BorLR_7p.js.map +0 -1
- package/dist/useProtocolTemplates-n6AJqSqv.js.map +0 -1
|
@@ -1,2350 +1,122 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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'
|