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.Calibratechannel
1.2 Requirements Covered
| REQ ID | Title | Category |
|---|---|---|
| REQ-CALIB-001 | Calibration tab in Config Mode navigation | Navigation |
| REQ-CALIB-002 | Target selector dropdown for calibration | Target Selection |
| REQ-CALIB-003 | Targets Summary collapsible widget | Status Display |
| REQ-CALIB-004 | Select runs for chart generation | Run Selection |
| REQ-CALIB-005 | Select runs for calibration/recalibration | Run Selection |
| REQ-CALIB-006 | Calibration chart (2D/3D scatter plot) | Visualization |
| REQ-CALIB-007 | Draggable DF lines for positivity range | DF Boundaries |
| REQ-CALIB-008 | Exclude observations from calibration | Data Filtering |
| REQ-CALIB-009 | Chart settings panel | UI Configuration |
| REQ-CALIB-010 | Outdated calibration warning banner | Notifications |
| REQ-CALIB-011 | Calibrations table listing records | Record Management |
| REQ-CALIB-012 | Calibration status badges | Status Display |
| REQ-CALIB-013 | Activate calibration for target | Lifecycle |
| REQ-CALIB-014 | Cancel in-progress calibration | Lifecycle |
| REQ-CALIB-015 | Delete non-active calibration records | Record Management |
| REQ-CALIB-016 | Click calibration row to load chart data | Navigation |
| REQ-CALIB-017 | Real-time WebSocket calibration updates | Real-time |
| REQ-CALIB-018 | Failed calibration notification | Error Handling |
| REQ-CALIB-019 | Recalibration from historical observations | Lifecycle |
| REQ-CALIB-020 | Help Data toggle for calibration page | UI |
| REQ-CALIB-021 | Run Calibration Chart tab in Run View | Run View |
| REQ-CALIB-022 | Target selector for run calibration chart | Run View |
| REQ-CALIB-023 | Fetch and display calibration data for run | Run View |
| REQ-CALIB-024 | Highlight current run on calibration chart | Run View |
| REQ-CALIB-025 | Run calibration chart (2D/3D scatter) | Run View |
| REQ-CALIB-026 | Chart settings panel (run view) | Run View |
| REQ-CALIB-027 | Draggable DF lines in run view chart | Run View |
| REQ-CALIB-028 | Pin/unpin widget panel | Run View |
| REQ-CALIB-029 | Dark mode support for chart | Run 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
| Direction | Domain/Component | Purpose |
|---|---|---|
| Provides to | RUNRPT | Calibration chart widget in Run View |
| CONFIGMODE | Calibration tab in Config Mode | |
| Consumes | KITCFG | Mix, target, observation entity data |
| ANALYTICS | Run observation data for calibration | |
| AWS Lambda (OOS API) | Calibration computation | |
| AWS S3 | Calibration file storage (BSON) | |
| WebSocket/Pusher | Real-time status updates |
2. Component Architecture
2.1 Component Diagram
2.2 Component Responsibilities
| Component | Type | Responsibility | REQ Trace |
|---|---|---|---|
AssayCalibrateableRunsController | Controller | List runs eligible for calibration; start calibration/recalibration | REQ-CALIB-004, 005, 019 |
AssayCalibrationProcessesController | Controller | List, cancel, activate, and delete calibration records | REQ-CALIB-011, 013, 014, 015 |
AssayCalibratedRunsController | Controller | List runs included in the active calibration | REQ-CALIB-004 |
AssayCalibrationDfBoundsController | Controller | Persist min/max DF boundary values | REQ-CALIB-007 |
AssayCalibrationObservationController | Controller | Exclude observation from calibration | REQ-CALIB-008 |
AssayCalibrationObservationsController | Controller | Fetch observations for a calibration record | REQ-CALIB-006, 008 |
RunCalibratedDataController | Controller | Fetch calibration data for run view | REQ-CALIB-023 |
TargetCalibrationStatusesController | Controller | List target calibration statuses | REQ-CALIB-012 |
MixCalibrationStatusesController | Controller | List mix calibration statuses | REQ-CALIB-012 |
StartAssayCalibrationAction | Action | Extract observations, invoke OOS API via Calibrator | REQ-CALIB-005 |
GetAssayCalibrationResultAction | Action | Poll for result, store BSON, update calibration | REQ-CALIB-005 |
SaveCalibrationFileAction | Action | Activate calibration, upload BSON to S3 | REQ-CALIB-013 |
StartAssayRecalibrationAction | Action | Recalibrate from historical observations | REQ-CALIB-019 |
AssayCalibration | Model | Calibration state and boundary data | REQ-CALIB-011-016 |
AssayCalibrationObservation | Model | Junction table linking calibration to observations | REQ-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
| Entity | Table | Purpose | Key Relationships |
|---|---|---|---|
AssayCalibration | assay_calibrations | Calibration records with state and boundaries | -> mix, target, site |
3.2 AssayCalibration Schema
| Column | Type | Purpose |
|---|---|---|
id | uuid | Primary key |
mix_id | uuid (FK) | Associated mix |
target_id | uuid (FK) | Associated target |
site_id | uuid (FK) | Multi-site isolation |
status | enum | CALIBRATING, CALIBRATED, FAILED, CANCELLED |
min_df | decimal | Minimum dilution factor boundary |
max_df | decimal | Maximum dilution factor boundary |
is_invert_sigmoid | boolean | Sigmoid inversion flag |
error_message | text | Error details on failure |
created_at | timestamp | Creation time |
updated_at | timestamp | Last modification |
3.3 Observation Extension
The calibration domain extends the observations table with:
| Column | Type | Purpose |
|---|---|---|
is_exclude_from_calibration | boolean | Marks observation excluded from calibration |
df | decimal | Dilution 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
| Endpoint | Method | Controller | Purpose | REQ Trace |
|---|---|---|---|---|
/api/calibrateable-runs | GET | AssayCalibrateableRunsController@index | List runs eligible for calibration | REQ-CALIB-004, 005 |
/api/calibrateable-runs | POST | AssayCalibrateableRunsController@store | Start new calibration | REQ-CALIB-005 |
/api/recalibrate-runs | POST | AssayCalibrateableRunsController@recalibrate | Recalibrate from observations | REQ-CALIB-019 |
/api/calibrated-runs | GET | AssayCalibratedRunsController@index | List runs in active calibration | REQ-CALIB-004 |
/api/run-calibration-data | GET | RunCalibratedDataController@index | Fetch calibration data for run view | REQ-CALIB-023 |
/api/assay-calibration-processes | GET | AssayCalibrationProcessesController@index | List in-progress calibration processes | REQ-CALIB-011 |
/api/assay-calibration-processes/{id} | DELETE | AssayCalibrationProcessesController@destroy | Cancel in-progress calibration | REQ-CALIB-014 |
/api/assay-calibration-remove | GET | AssayCalibrationProcessesController@remove | Delete calibration record | REQ-CALIB-015 |
/api/activate-calibration | POST | AssayCalibrationProcessesController@active | Activate calibration for target | REQ-CALIB-013 |
/api/assay-calibrations/{id}/df-bounds | PUT | AssayCalibrationDfBoundsController@update | Persist min/max DF boundaries | REQ-CALIB-007 |
/api/assay-calibrations/{id}/observations | GET | AssayCalibrationObservationsController@show | Fetch observations for a calibration | REQ-CALIB-006 |
/api/exclude-from-calibration | PUT | AssayCalibrationObservationController@exclude | Exclude observation from calibration | REQ-CALIB-008 |
/api/target-calibration-statuses | GET | TargetCalibrationStatusesController@index | List target calibration statuses | REQ-CALIB-012 |
/api/mix-calibration-statuses | GET | MixCalibrationStatusesController@index | List mix calibration statuses | REQ-CALIB-012 |
/api/calibration-target/{target_id} | GET | TargetsController@show | Fetch target details for calibration | REQ-CALIB-002 |
/api/runs/{run_id}/calibrations/targets | GET | CalibratedTargetsController@index | List calibrated targets for run view | REQ-CALIB-022 |
4.2 APIs Consumed
| Service | Purpose | REQ Trace |
|---|---|---|
| AWS OOS API (Lambda) | Calibration computation | REQ-CALIB-005 |
| AWS S3 | Calibration file storage | REQ-CALIB-013 |
4.3 Events Published
All calibration events broadcast on the Run.Calibrate channel (not site-scoped):
| Event | Payload | Purpose |
|---|---|---|
AssayCalibrationStarted | processId | Notify calibration initiated |
AssayCalibrationCompleted | processId | Notify calibration succeeded |
AssayCalibrationUpdated | processId | Notify calibration state change (including failure) |
AssayCalibrationCanceled | processId | Notify 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-boundspersists 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
| Condition | Detection | Response | User Impact |
|---|---|---|---|
| AWS Lambda timeout | HTTP timeout exception | Set status = FAILED, store error | Failed notification, can retry |
| AWS Lambda error response | Non-200 status | Set status = FAILED, store error | Failed notification with error detail |
| Invalid AWS env variables | Missing AWS_* keys | Startup validation warning | Calibration unavailable |
| S3 upload failure | S3 client exception | Activation fails, calibration stays CALIBRATED | Can retry activation |
| No observations for runs | Empty result set | Validation error before Lambda call | User prompted to select different runs |
| Concurrent calibration conflict | DB constraint | 409 Conflict | Only one in-progress calibration per target |
7. Configuration
| Setting | Location | Default | Effect | REQ Trace |
|---|---|---|---|---|
AWS_ACCESS_KEY_ID | .env | -- | AWS authentication | REQ-CALIB-005 |
AWS_SECRET_ACCESS_KEY | .env | -- | AWS authentication | REQ-CALIB-005 |
AWS_DEFAULT_REGION | .env | -- | AWS region | REQ-CALIB-005 |
AWS_BUCKET | .env | -- | S3 bucket for calibration files | REQ-CALIB-013 |
AWS_OOS_API_URL | .env | -- | Lambda function endpoint | REQ-CALIB-005 |
8. Implementation Mapping
8.1 Code Locations
| Component | Type | Path |
|---|---|---|
| AssayCalibrateableRunsController | Controller | app/Http/Controllers/AssayCalibrations/AssayCalibrateableRunsController.php |
| AssayCalibrationProcessesController | Controller | app/Http/Controllers/AssayCalibrations/AssayCalibrationProcessesController.php |
| AssayCalibratedRunsController | Controller | app/Http/Controllers/AssayCalibrations/AssayCalibratedRunsController.php |
| AssayCalibrationDfBoundsController | Controller | app/Http/Controllers/AssayCalibrations/AssayCalibrationDfBoundsController.php |
| AssayCalibrationObservationController | Controller | app/Http/Controllers/AssayCalibrations/AssayCalibrationObservationController.php |
| AssayCalibrationObservationsController | Controller | app/Http/Controllers/AssayCalibrations/AssayCalibrationObservationsController.php |
| RunCalibratedDataController | Controller | app/Http/Controllers/AssayCalibrations/RunCalibratedDataController.php |
| TargetCalibrationStatusesController | Controller | app/Http/Controllers/AssayCalibrations/TargetCalibrationStatusesController.php |
| MixCalibrationStatusesController | Controller | app/Http/Controllers/AssayCalibrations/MixCalibrationStatusesController.php |
| TargetsController (calibration) | Controller | app/Http/Controllers/AssayCalibrations/TargetsController.php |
| CalibratedTargetsController | Controller | app/Http/Controllers/Runs/Calibration/CalibratedTargetsController.php |
| SaveCalibrationDataController | Controller | app/Http/Controllers/SaveCalibrationDataController.php |
| TargetCalibrationFilesController | Controller | app/Http/Controllers/TargetCalibrationFilesController.php |
| StartAssayCalibrationAction | Action | app/Actions/AssayCalibration/StartAssayCalibrationAction.php |
| GetAssayCalibrationResultAction | Action | app/Actions/AssayCalibration/GetAssayCalibrationResultAction.php |
| GetTargetCalibrationStatusesAction | Action | app/Actions/AssayCalibration/GetTargetCalibrationStatusesAction.php |
| GetMixCalibrationStatusesAction | Action | app/Actions/AssayCalibration/GetMixCalibrationStatusesAction.php |
| StartAssayRecalibrationAction | Action | app/Actions/AssayCalibration/Support/StartAssayRecalibrationAction.php |
| SaveCalibrationFileAction | Action | app/Actions/AssayCalibration/Support/SaveCalibrationFileAction.php |
| AssayCalibrationRepository | Support | app/Actions/AssayCalibration/Support/AssayCalibrationRepository.php |
| AssayCalibrationDataFactory | Support | app/Actions/AssayCalibration/Support/AssayCalibrationDataFactory.php |
| TargetCalibrationStatusFinder | Support | app/Actions/AssayCalibration/Support/TargetCalibrationStatusFinder.php |
| Calibrator | Service | app/Calibrator/Calibrator.php |
| AssayCalibrationProcesses | Support | app/Support/AssayCalibrationProcesses.php |
| StoreCalibratedBson | Support | app/Support/StoreCalibratedBson.php |
| AssayCalibration | Model | app/AssayCalibration.php |
| AssayCalibrationObservation | Model | app/AssayCalibrationObservation.php |
| StartAssayCalibrationJob | Job | app/Jobs/StartAssayCalibrationJob.php |
| AssayCalibrationGetResultJob | Job | app/Jobs/AssayCalibrationGetResultJob.php |
| StartAssayRecalibrationJob | Job | app/Jobs/StartAssayRecalibrationJob.php |
| AssayCalibrationStarted | Event | app/Events/AssayCalibrationStarted.php |
| AssayCalibrationCompleted | Event | app/Events/AssayCalibrationCompleted.php |
| AssayCalibrationUpdated | Event | app/Events/AssayCalibrationUpdated.php |
| AssayCalibrationCanceled | Event | app/Events/AssayCalibrationCanceled.php |
| AssayCalibrationFailed | Notification | app/Notifications/AssayCalibrationFailed.php |
| AssayCalibration (Vue) | Vue Component | resources/frontend/src/components/assay-configurations/AssayCalibration.vue |
| CalibrationChart | Vue Component | resources/frontend/src/components/calibrations/Chart/CalibrationChart.vue |
| CalibrationChartSettings | Vue Component | resources/frontend/src/components/calibrations/Chart/Settings/CalibrationChartSettings.vue |
| CalibrationTargetsCalibrationsTable | Vue Component | resources/frontend/src/components/calibrations/tabs/target-calibrations/CalibrationTargetsCalibrationsTable.vue |
| CalibrationTargetsTable | Vue Component | resources/frontend/src/components/calibrations/tabs/target-calibrations/CalibrationTargetsTable.vue |
| OngoingCalibrationTable | Vue Component | resources/frontend/src/components/calibrations/processes/OngoingCalibrationTable.vue |
| RunCalibrationChartWidget | Vue Component | resources/frontend/src/components/runs/summary/RunCalibrationChartWidget.vue |
| Plotly2D | Vue Component | resources/frontend/src/components/calibrations/Chart/Plotly2D.vue |
| Plotly3D | Vue Component | resources/frontend/src/components/calibrations/Chart/Plotly3D.vue |
| DraggablePlotLine | JS Module | resources/frontend/src/components/calibrations/support/DraggablePlotLine.js |
8.2 Requirement Traceability
| REQ ID | Design Section | Primary Code |
|---|---|---|
| REQ-CALIB-001 | section 1.4 | Tab in Config Mode (ConfigModeTabHandler.js) |
| REQ-CALIB-002-003 | section 5.4 | TargetsController, CalibrationTargetsTable |
| REQ-CALIB-004-005 | section 5.1 | AssayCalibrateableRunsController, StartAssayCalibrationAction, StartAssayCalibrationJob |
| REQ-CALIB-006 | section 5.3 | CalibrationChart (Plotly.js), AssayCalibrationObservationsController |
| REQ-CALIB-007 | section 5.3 | Draggable DF lines (DraggablePlotLine.js), AssayCalibrationDfBoundsController |
| REQ-CALIB-008 | section 5.2 | AssayCalibrationObservationController |
| REQ-CALIB-009 | section 5.3 | CalibrationChartSettings |
| REQ-CALIB-010-012 | section 3.4 | TargetCalibrationStatusesController, MixCalibrationStatusesController |
| REQ-CALIB-013 | section 5.1 | AssayCalibrationProcessesController::active, SaveCalibrationFileAction |
| REQ-CALIB-014 | section 3.4 | AssayCalibrationProcessesController::destroy, AssayCalibrationCanceled |
| REQ-CALIB-015-016 | section 4.1 | AssayCalibrationProcessesController::remove, CalibrationTargetsCalibrationsTable |
| REQ-CALIB-017 | section 4.3 | WebSocket events on Run.Calibrate channel |
| REQ-CALIB-018 | section 6 | AssayCalibrationFailed notification, AssayCalibrationUpdated event |
| REQ-CALIB-019 | section 5.2 | StartAssayRecalibrationAction, StartAssayRecalibrationJob |
| REQ-CALIB-020 | -- | Feature-flag gated help widget |
| REQ-CALIB-021-029 | section 5.4 | RunCalibrationChartWidget, CalibratedTargetsController, Run View integration |
9. Design Decisions
| Decision | Rationale | Alternatives Considered |
|---|---|---|
| AWS Lambda for computation | Calibration math is compute-intensive; Lambda provides elastic scaling and isolation from the web server | In-process PHP (rejected: memory/CPU constraints, blocking), dedicated microservice (rejected: operational overhead) |
| S3 for calibration storage | Calibration BSON files need durable, accessible storage; S3 integrates with Lambda | Database BLOB (rejected: performance, size limits), local filesystem (rejected: multi-server) |
| Plotly.js for charting | Interactive 2D/3D support, draggable annotations, dark mode, scatter plots with 1000+ points | Chart.js (rejected: no 3D), D3 (rejected: too low-level), ECharts (rejected: less 3D scatter support) |
| WebSocket for real-time updates | Calibration is a long-running operation; users need live status without polling | Polling (rejected: wasteful, delayed), SSE (rejected: limited browser support for private channels) |
| Single active calibration per target | Analysis pipeline needs a single definitive DF range per target | Multiple active (rejected: ambiguous analysis parameters) |
| Observation exclusion as soft flag | Preserves original data; exclusion can be reversed; recalibration respects history | Hard delete (rejected: data loss, no undo) |
10. Performance Considerations
| Scenario | Concern | Mitigation |
|---|---|---|
| Large calibration (1000+ observations) | Lambda timeout | Lambda concurrency limits, chunked observation batches |
| Chart rendering with many data points | Browser memory, render lag | Plotly.js WebGL renderer for large datasets |
| S3 upload on activation | Network latency | Async upload with retry |
| Multiple users viewing same calibration | WebSocket fan-out | Site-scoped channels reduce broadcast scope |
11. Related Documents
| Document | Relevant Sections |
|---|---|
| SRS: calibration.md | Requirements source |
| SDS: KITCFG Domain | Mix/target entities, calibration settings |
| SDS: CONFIGMODE Domain | Config Mode tab host |
| SDS: ANALYTICS Domain | Run observation data |
| SDS: RUNRPT Domain | Run View widget integration |
| SDS: Architecture | AWS integration patterns |
| SDS: Cross-Cutting | Queue, WebSocket patterns |