Skip to main content
Version: Next

Calibration Design

Document Type: Domain Design (Tier 2) Domain: CALIBRATION Domain Character: Stateful SRS Reference: calibration.md Status: Draft Last Updated: 2026-03-06 Version: v3.1.0


1. Overview

1.1 Purpose

The Calibration domain manages the assay calibration lifecycle for PCR analysis targets. It provides the infrastructure for calibration computation (via AWS Lambda), result storage (S3/BSON), interactive chart visualization (Plotly.js), and real-time status updates (WebSocket). Calibration determines the Dilution Factor (DF) boundaries used in quantitative analysis.

This domain is characterized by:

  • Stateful lifecycle -- calibrations move through CALIBRATING, CALIBRATED, FAILED, CANCELLED states
  • External integration -- AWS Lambda for computation, S3 for file storage
  • Interactive visualization -- Plotly.js 2D/3D scatter plots with draggable DF boundaries
  • Real-time updates -- WebSocket events on the Run.Calibrate channel

1.2 Requirements Covered

REQ IDTitleCategory
REQ-CALIB-001Calibration tab in Config Mode navigationNavigation
REQ-CALIB-002Target selector dropdown for calibrationTarget Selection
REQ-CALIB-003Targets Summary collapsible widgetStatus Display
REQ-CALIB-004Select runs for chart generationRun Selection
REQ-CALIB-005Select runs for calibration/recalibrationRun Selection
REQ-CALIB-006Calibration chart (2D/3D scatter plot)Visualization
REQ-CALIB-007Draggable DF lines for positivity rangeDF Boundaries
REQ-CALIB-008Exclude observations from calibrationData Filtering
REQ-CALIB-009Chart settings panelUI Configuration
REQ-CALIB-010Outdated calibration warning bannerNotifications
REQ-CALIB-011Calibrations table listing recordsRecord Management
REQ-CALIB-012Calibration status badgesStatus Display
REQ-CALIB-013Activate calibration for targetLifecycle
REQ-CALIB-014Cancel in-progress calibrationLifecycle
REQ-CALIB-015Delete non-active calibration recordsRecord Management
REQ-CALIB-016Click calibration row to load chart dataNavigation
REQ-CALIB-017Real-time WebSocket calibration updatesReal-time
REQ-CALIB-018Failed calibration notificationError Handling
REQ-CALIB-019Recalibration from historical observationsLifecycle
REQ-CALIB-020Help Data toggle for calibration pageUI
REQ-CALIB-021Run Calibration Chart tab in Run ViewRun View
REQ-CALIB-022Target selector for run calibration chartRun View
REQ-CALIB-023Fetch and display calibration data for runRun View
REQ-CALIB-024Highlight current run on calibration chartRun View
REQ-CALIB-025Run calibration chart (2D/3D scatter)Run View
REQ-CALIB-026Chart settings panel (run view)Run View
REQ-CALIB-027Draggable DF lines in run view chartRun View
REQ-CALIB-028Pin/unpin widget panelRun View
REQ-CALIB-029Dark mode support for chartRun View

1.3 Constraints

Tier 2 Constraint: This document describes ownership, patterns, and design rationale. It does not duplicate calibration algorithm details owned by the AWS Lambda service.

1.4 Dependencies

DirectionDomain/ComponentPurpose
Provides toRUNRPTCalibration chart widget in Run View
CONFIGMODECalibration tab in Config Mode
ConsumesKITCFGMix, target, observation entity data
ANALYTICSRun observation data for calibration
AWS Lambda (OOS API)Calibration computation
AWS S3Calibration file storage (BSON)
WebSocket/PusherReal-time status updates

2. Component Architecture

2.1 Component Diagram

2.2 Component Responsibilities

ComponentTypeResponsibilityREQ Trace
AssayCalibrateableRunsControllerControllerList runs eligible for calibration; start calibration/recalibrationREQ-CALIB-004, 005, 019
AssayCalibrationProcessesControllerControllerList, cancel, activate, and delete calibration recordsREQ-CALIB-011, 013, 014, 015
AssayCalibratedRunsControllerControllerList runs included in the active calibrationREQ-CALIB-004
AssayCalibrationDfBoundsControllerControllerPersist min/max DF boundary valuesREQ-CALIB-007
AssayCalibrationObservationControllerControllerExclude observation from calibrationREQ-CALIB-008
AssayCalibrationObservationsControllerControllerFetch observations for a calibration recordREQ-CALIB-006, 008
RunCalibratedDataControllerControllerFetch calibration data for run viewREQ-CALIB-023
TargetCalibrationStatusesControllerControllerList target calibration statusesREQ-CALIB-012
MixCalibrationStatusesControllerControllerList mix calibration statusesREQ-CALIB-012
StartAssayCalibrationActionActionExtract observations, invoke OOS API via CalibratorREQ-CALIB-005
GetAssayCalibrationResultActionActionPoll for result, store BSON, update calibrationREQ-CALIB-005
SaveCalibrationFileActionActionActivate calibration, upload BSON to S3REQ-CALIB-013
StartAssayRecalibrationActionActionRecalibrate from historical observationsREQ-CALIB-019
AssayCalibrationModelCalibration state and boundary dataREQ-CALIB-011-016
AssayCalibrationObservationModelJunction table linking calibration to observationsREQ-CALIB-006, 008

2.3 Architectural Patterns

Pattern: OOS API Integration via Calibrator

Calibration computation is offloaded to the OOS API (a dedicated analysis server, accessed via the Calibrator class which wraps FullAnalysis). The system sends a PythonDOM containing observation data and receives computed calibration parameters:

StartAssayCalibrationAction
1. Build PythonDOM from selected runs' observations (via AssayCalibrationDataFactory)
2. Call Calibrator::start() which POSTs to OOS API /analyser/create
3. Store AssayCalibration record (CALIBRATING) via AssayCalibrationRepository
4. Broadcast AssayCalibrationStarted event
5. Dispatch AssayCalibrationGetResultJob to poll for results

GetAssayCalibrationResultAction (called by AssayCalibrationGetResultJob)
1. Poll Calibrator::getResult() (which calls /analyser/wait then /analyser/result)
2. Store calibration BSON file via StoreCalibratedBson
3. Update AssayCalibration record (CALIBRATED, min_df, max_df)
4. Update AssayCalibrationObservation records with computed values (F, DF, RFU, CT)
5. Broadcast AssayCalibrationCompleted event

Pattern: S3 File Storage

Calibration results are stored as BSON files on S3. Activation uploads the current calibration to S3 for downstream consumption by the analysis pipeline:

SaveCalibrationFileAction (called by AssayCalibrationProcessesController::active)
1. Validate calibration status is CALIBRATED
2. Deactivate all other calibrations for same target
3. Mark this calibration as active
4. Upload BSON to S3 via StoreCalibratedBson::storeToS3Bucket()
5. Update target with calibration_file_path, min_df, max_df
6. Dispatch RuleMapper to update auto-mapped rules

3. Data Design

3.1 Entity Ownership

EntityTablePurposeKey Relationships
AssayCalibrationassay_calibrationsCalibration records with state and boundaries-> mix, target, site

3.2 AssayCalibration Schema

ColumnTypePurpose
iduuidPrimary key
mix_iduuid (FK)Associated mix
target_iduuid (FK)Associated target
site_iduuid (FK)Multi-site isolation
statusenumCALIBRATING, CALIBRATED, FAILED, CANCELLED
min_dfdecimalMinimum dilution factor boundary
max_dfdecimalMaximum dilution factor boundary
is_invert_sigmoidbooleanSigmoid inversion flag
error_messagetextError details on failure
created_attimestampCreation time
updated_attimestampLast modification

3.3 Observation Extension

The calibration domain extends the observations table with:

ColumnTypePurpose
is_exclude_from_calibrationbooleanMarks observation excluded from calibration
dfdecimalDilution factor value from extended data

3.4 State Management

State Rules:

  • Only one calibration can be ACTIVE per target at a time
  • Activating a new calibration deactivates the previous one
  • CALIBRATING records cannot be deleted (must cancel first)
  • ACTIVE records cannot be deleted (must deactivate first)

4. Interface Design

4.1 APIs Provided

EndpointMethodControllerPurposeREQ Trace
/api/calibrateable-runsGETAssayCalibrateableRunsController@indexList runs eligible for calibrationREQ-CALIB-004, 005
/api/calibrateable-runsPOSTAssayCalibrateableRunsController@storeStart new calibrationREQ-CALIB-005
/api/recalibrate-runsPOSTAssayCalibrateableRunsController@recalibrateRecalibrate from observationsREQ-CALIB-019
/api/calibrated-runsGETAssayCalibratedRunsController@indexList runs in active calibrationREQ-CALIB-004
/api/run-calibration-dataGETRunCalibratedDataController@indexFetch calibration data for run viewREQ-CALIB-023
/api/assay-calibration-processesGETAssayCalibrationProcessesController@indexList in-progress calibration processesREQ-CALIB-011
/api/assay-calibration-processes/{id}DELETEAssayCalibrationProcessesController@destroyCancel in-progress calibrationREQ-CALIB-014
/api/assay-calibration-removeGETAssayCalibrationProcessesController@removeDelete calibration recordREQ-CALIB-015
/api/activate-calibrationPOSTAssayCalibrationProcessesController@activeActivate calibration for targetREQ-CALIB-013
/api/assay-calibrations/{id}/df-boundsPUTAssayCalibrationDfBoundsController@updatePersist min/max DF boundariesREQ-CALIB-007
/api/assay-calibrations/{id}/observationsGETAssayCalibrationObservationsController@showFetch observations for a calibrationREQ-CALIB-006
/api/exclude-from-calibrationPUTAssayCalibrationObservationController@excludeExclude observation from calibrationREQ-CALIB-008
/api/target-calibration-statusesGETTargetCalibrationStatusesController@indexList target calibration statusesREQ-CALIB-012
/api/mix-calibration-statusesGETMixCalibrationStatusesController@indexList mix calibration statusesREQ-CALIB-012
/api/calibration-target/{target_id}GETTargetsController@showFetch target details for calibrationREQ-CALIB-002
/api/runs/{run_id}/calibrations/targetsGETCalibratedTargetsController@indexList calibrated targets for run viewREQ-CALIB-022

4.2 APIs Consumed

ServicePurposeREQ Trace
AWS OOS API (Lambda)Calibration computationREQ-CALIB-005
AWS S3Calibration file storageREQ-CALIB-013

4.3 Events Published

All calibration events broadcast on the Run.Calibrate channel (not site-scoped):

EventPayloadPurpose
AssayCalibrationStartedprocessIdNotify calibration initiated
AssayCalibrationCompletedprocessIdNotify calibration succeeded
AssayCalibrationUpdatedprocessIdNotify calibration state change (including failure)
AssayCalibrationCanceledprocessIdNotify cancellation (American spelling, single L)

Note: There is no dedicated AssayCalibrationFailed event. Failures are broadcast via AssayCalibrationUpdated, and the user is also notified via the AssayCalibrationFailed notification (in-app notification, not a broadcast event). There is no AssayCalibrationActivated event; activation is handled synchronously in SaveCalibrationFileAction.


5. Behavioral Design

5.1 Calibration Execution Flow

5.2 Observation Exclusion

Algorithm: Exclude Observation from Calibration

Handler: AssayCalibrationObservationController::exclude()

Inputs:
- observation_id: Target observation (from request)

Outputs:
- void (observation marked excluded)

Steps:
1. Find observation by ID
2. Set observation.is_exclude_from_calibration = true

Notes:
- Exclusion is a simple flag update on the observations table
- No automatic recalculation occurs on exclusion -- the user must
trigger recalibration separately via POST /api/recalibrate-runs
- Excluded observations are filtered during calibration data extraction
(StartAssayCalibrationAction queries where is_exclude_from_calibration = false)
- Recalibration from historical data respects exclusions (REQ-CALIB-019)

5.3 Plotly.js Chart Architecture

The calibration chart renders as a 2D or 3D Plotly.js scatter plot:

2D Mode: X = CT value, Y = DF value. Observations plotted as scatter points. Min/max DF shown as horizontal draggable lines.

3D Mode: X = CT value, Y = DF value, Z = additional dimension (configurable). Calibration surface rendered.

Draggable DF Lines (REQ-CALIB-007):

  • Min DF line (lower boundary) and Max DF line (upper boundary) are rendered as Plotly shapes
  • User drags lines to adjust positivity range
  • On drag end, PUT /api/assay-calibrations/{id}/df-bounds persists new boundaries
  • Chart re-renders with updated DF range

Current Run Highlighting (REQ-CALIB-024):

  • In Run View mode, observations from the current run are rendered with a distinct marker (larger, different color)
  • Other calibration observations rendered as standard markers

5.4 Run View Widget

The RunCalibrationChartWidget appears as a tab in the Run View widget panel (REQ-CALIB-021), rendered via RunViewWidgetList.vue. Target selection for the run view is provided by CalibratedTargetsController (/api/runs/{run_id}/calibrations/targets). Run-specific calibration data is fetched via RunCalibratedDataController (/api/run-calibration-data). The chart reuses the same Plotly.js chart component (CalibrationChart) as the Config Mode calibration page but with:

  • A target selector scoped to targets with existing calibrations for the current run
  • Current run observations highlighted
  • Read-only DF lines (adjustments happen in Config Mode)

6. Error Handling

ConditionDetectionResponseUser Impact
AWS Lambda timeoutHTTP timeout exceptionSet status = FAILED, store errorFailed notification, can retry
AWS Lambda error responseNon-200 statusSet status = FAILED, store errorFailed notification with error detail
Invalid AWS env variablesMissing AWS_* keysStartup validation warningCalibration unavailable
S3 upload failureS3 client exceptionActivation fails, calibration stays CALIBRATEDCan retry activation
No observations for runsEmpty result setValidation error before Lambda callUser prompted to select different runs
Concurrent calibration conflictDB constraint409 ConflictOnly one in-progress calibration per target

7. Configuration

SettingLocationDefaultEffectREQ Trace
AWS_ACCESS_KEY_ID.env--AWS authenticationREQ-CALIB-005
AWS_SECRET_ACCESS_KEY.env--AWS authenticationREQ-CALIB-005
AWS_DEFAULT_REGION.env--AWS regionREQ-CALIB-005
AWS_BUCKET.env--S3 bucket for calibration filesREQ-CALIB-013
AWS_OOS_API_URL.env--Lambda function endpointREQ-CALIB-005

8. Implementation Mapping

8.1 Code Locations

ComponentTypePath
AssayCalibrateableRunsControllerControllerapp/Http/Controllers/AssayCalibrations/AssayCalibrateableRunsController.php
AssayCalibrationProcessesControllerControllerapp/Http/Controllers/AssayCalibrations/AssayCalibrationProcessesController.php
AssayCalibratedRunsControllerControllerapp/Http/Controllers/AssayCalibrations/AssayCalibratedRunsController.php
AssayCalibrationDfBoundsControllerControllerapp/Http/Controllers/AssayCalibrations/AssayCalibrationDfBoundsController.php
AssayCalibrationObservationControllerControllerapp/Http/Controllers/AssayCalibrations/AssayCalibrationObservationController.php
AssayCalibrationObservationsControllerControllerapp/Http/Controllers/AssayCalibrations/AssayCalibrationObservationsController.php
RunCalibratedDataControllerControllerapp/Http/Controllers/AssayCalibrations/RunCalibratedDataController.php
TargetCalibrationStatusesControllerControllerapp/Http/Controllers/AssayCalibrations/TargetCalibrationStatusesController.php
MixCalibrationStatusesControllerControllerapp/Http/Controllers/AssayCalibrations/MixCalibrationStatusesController.php
TargetsController (calibration)Controllerapp/Http/Controllers/AssayCalibrations/TargetsController.php
CalibratedTargetsControllerControllerapp/Http/Controllers/Runs/Calibration/CalibratedTargetsController.php
SaveCalibrationDataControllerControllerapp/Http/Controllers/SaveCalibrationDataController.php
TargetCalibrationFilesControllerControllerapp/Http/Controllers/TargetCalibrationFilesController.php
StartAssayCalibrationActionActionapp/Actions/AssayCalibration/StartAssayCalibrationAction.php
GetAssayCalibrationResultActionActionapp/Actions/AssayCalibration/GetAssayCalibrationResultAction.php
GetTargetCalibrationStatusesActionActionapp/Actions/AssayCalibration/GetTargetCalibrationStatusesAction.php
GetMixCalibrationStatusesActionActionapp/Actions/AssayCalibration/GetMixCalibrationStatusesAction.php
StartAssayRecalibrationActionActionapp/Actions/AssayCalibration/Support/StartAssayRecalibrationAction.php
SaveCalibrationFileActionActionapp/Actions/AssayCalibration/Support/SaveCalibrationFileAction.php
AssayCalibrationRepositorySupportapp/Actions/AssayCalibration/Support/AssayCalibrationRepository.php
AssayCalibrationDataFactorySupportapp/Actions/AssayCalibration/Support/AssayCalibrationDataFactory.php
TargetCalibrationStatusFinderSupportapp/Actions/AssayCalibration/Support/TargetCalibrationStatusFinder.php
CalibratorServiceapp/Calibrator/Calibrator.php
AssayCalibrationProcessesSupportapp/Support/AssayCalibrationProcesses.php
StoreCalibratedBsonSupportapp/Support/StoreCalibratedBson.php
AssayCalibrationModelapp/AssayCalibration.php
AssayCalibrationObservationModelapp/AssayCalibrationObservation.php
StartAssayCalibrationJobJobapp/Jobs/StartAssayCalibrationJob.php
AssayCalibrationGetResultJobJobapp/Jobs/AssayCalibrationGetResultJob.php
StartAssayRecalibrationJobJobapp/Jobs/StartAssayRecalibrationJob.php
AssayCalibrationStartedEventapp/Events/AssayCalibrationStarted.php
AssayCalibrationCompletedEventapp/Events/AssayCalibrationCompleted.php
AssayCalibrationUpdatedEventapp/Events/AssayCalibrationUpdated.php
AssayCalibrationCanceledEventapp/Events/AssayCalibrationCanceled.php
AssayCalibrationFailedNotificationapp/Notifications/AssayCalibrationFailed.php
AssayCalibration (Vue)Vue Componentresources/frontend/src/components/assay-configurations/AssayCalibration.vue
CalibrationChartVue Componentresources/frontend/src/components/calibrations/Chart/CalibrationChart.vue
CalibrationChartSettingsVue Componentresources/frontend/src/components/calibrations/Chart/Settings/CalibrationChartSettings.vue
CalibrationTargetsCalibrationsTableVue Componentresources/frontend/src/components/calibrations/tabs/target-calibrations/CalibrationTargetsCalibrationsTable.vue
CalibrationTargetsTableVue Componentresources/frontend/src/components/calibrations/tabs/target-calibrations/CalibrationTargetsTable.vue
OngoingCalibrationTableVue Componentresources/frontend/src/components/calibrations/processes/OngoingCalibrationTable.vue
RunCalibrationChartWidgetVue Componentresources/frontend/src/components/runs/summary/RunCalibrationChartWidget.vue
Plotly2DVue Componentresources/frontend/src/components/calibrations/Chart/Plotly2D.vue
Plotly3DVue Componentresources/frontend/src/components/calibrations/Chart/Plotly3D.vue
DraggablePlotLineJS Moduleresources/frontend/src/components/calibrations/support/DraggablePlotLine.js

8.2 Requirement Traceability

REQ IDDesign SectionPrimary Code
REQ-CALIB-001section 1.4Tab in Config Mode (ConfigModeTabHandler.js)
REQ-CALIB-002-003section 5.4TargetsController, CalibrationTargetsTable
REQ-CALIB-004-005section 5.1AssayCalibrateableRunsController, StartAssayCalibrationAction, StartAssayCalibrationJob
REQ-CALIB-006section 5.3CalibrationChart (Plotly.js), AssayCalibrationObservationsController
REQ-CALIB-007section 5.3Draggable DF lines (DraggablePlotLine.js), AssayCalibrationDfBoundsController
REQ-CALIB-008section 5.2AssayCalibrationObservationController
REQ-CALIB-009section 5.3CalibrationChartSettings
REQ-CALIB-010-012section 3.4TargetCalibrationStatusesController, MixCalibrationStatusesController
REQ-CALIB-013section 5.1AssayCalibrationProcessesController::active, SaveCalibrationFileAction
REQ-CALIB-014section 3.4AssayCalibrationProcessesController::destroy, AssayCalibrationCanceled
REQ-CALIB-015-016section 4.1AssayCalibrationProcessesController::remove, CalibrationTargetsCalibrationsTable
REQ-CALIB-017section 4.3WebSocket events on Run.Calibrate channel
REQ-CALIB-018section 6AssayCalibrationFailed notification, AssayCalibrationUpdated event
REQ-CALIB-019section 5.2StartAssayRecalibrationAction, StartAssayRecalibrationJob
REQ-CALIB-020--Feature-flag gated help widget
REQ-CALIB-021-029section 5.4RunCalibrationChartWidget, CalibratedTargetsController, Run View integration

9. Design Decisions

DecisionRationaleAlternatives Considered
AWS Lambda for computationCalibration math is compute-intensive; Lambda provides elastic scaling and isolation from the web serverIn-process PHP (rejected: memory/CPU constraints, blocking), dedicated microservice (rejected: operational overhead)
S3 for calibration storageCalibration BSON files need durable, accessible storage; S3 integrates with LambdaDatabase BLOB (rejected: performance, size limits), local filesystem (rejected: multi-server)
Plotly.js for chartingInteractive 2D/3D support, draggable annotations, dark mode, scatter plots with 1000+ pointsChart.js (rejected: no 3D), D3 (rejected: too low-level), ECharts (rejected: less 3D scatter support)
WebSocket for real-time updatesCalibration is a long-running operation; users need live status without pollingPolling (rejected: wasteful, delayed), SSE (rejected: limited browser support for private channels)
Single active calibration per targetAnalysis pipeline needs a single definitive DF range per targetMultiple active (rejected: ambiguous analysis parameters)
Observation exclusion as soft flagPreserves original data; exclusion can be reversed; recalibration respects historyHard delete (rejected: data loss, no undo)

10. Performance Considerations

ScenarioConcernMitigation
Large calibration (1000+ observations)Lambda timeoutLambda concurrency limits, chunked observation batches
Chart rendering with many data pointsBrowser memory, render lagPlotly.js WebGL renderer for large datasets
S3 upload on activationNetwork latencyAsync upload with retry
Multiple users viewing same calibrationWebSocket fan-outSite-scoped channels reduce broadcast scope

DocumentRelevant Sections
SRS: calibration.mdRequirements source
SDS: KITCFG DomainMix/target entities, calibration settings
SDS: CONFIGMODE DomainConfig Mode tab host
SDS: ANALYTICS DomainRun observation data
SDS: RUNRPT DomainRun View widget integration
SDS: ArchitectureAWS integration patterns
SDS: Cross-CuttingQueue, WebSocket patterns