Reports Domain Design
Document Type: Domain Design (Tier 2) Domain: REPORTS Domain Character: Thin orchestration SRS Reference: reports.md, print.md Status: Draft Last Updated: 2026-01-25
1. Overview
1.1 Purpose
The Reports domain provides analytical and statistical reporting capabilities for PCR run data. It encompasses three report types:
- Levey Jennings (LJ) Reports - Quality control charts showing Westgard-tracked control performance over time
- Trends Reports - Outcome trend analysis with configurable aggregation and alerting
- Outcomes Reports - Detailed outcome data with drill-down capabilities
This is a thin orchestration domain because it primarily coordinates data retrieval from other domains (RUNRPT, KITCFG, ERRORCODES) and delegates visualization to the frontend. Backend logic focuses on data aggregation, filtering, and export generation.
1.2 Requirements Covered
| REQ ID | Title | Priority |
|---|---|---|
| REQ-REPORTS-001 | Generate Levey Jennings Report | Must |
| REQ-REPORTS-002 | Create Levey Jennings Dataset | Must |
| REQ-REPORTS-003 | Navigate from Levey Jennings Report | Must |
| REQ-REPORTS-004 | Export Levey Jennings Report | Must |
| REQ-REPORTS-005 | Manage Westgard Ranges | Must |
| REQ-REPORTS-006 | Bulk Edit Westgard Limits | Should |
| REQ-REPORTS-007 | Configure Westgard Display Options | Must |
| REQ-REPORTS-008 | Generate Trends Report | Must |
| REQ-REPORTS-009 | Create Trends Dataset | Must |
| REQ-REPORTS-010 | Navigate from Trends Report | Must |
| REQ-REPORTS-011 | Export and Print Trends Report | Must |
| REQ-REPORTS-012 | Aggregate Trends Data | Must |
| REQ-REPORTS-013 | Manage Trends Alerts | Must |
| REQ-REPORTS-014 | Trigger and Deliver Alert Notifications | Must |
| REQ-REPORTS-015 | Handle Special Trends Data | Should |
| REQ-REPORTS-016 | Generate Outcomes Report | Must |
| REQ-REPORTS-017 | Create Outcomes Dataset | Must |
| REQ-REPORTS-018 | Navigate from Outcomes Report | Must |
| REQ-REPORTS-019 | Export and Print Outcomes Report | Must |
| REQ-REPORTS-020 | Configure Combined Outcomes | Should |
| REQ-REPORTS-021 | Multi-Site Report Access | Must |
| REQ-REPORTS-022 | Exclude Archived Runs from Reports | Must |
| REQ-REPORTS-023 | Display Crossover Wells in Reports | Must |
| REQ-REPORTS-024 | Role-Based Report Access | Must |
| REQ-REPORTS-025 | Report Performance | Must |
| REQ-PRINT-001 | Generate Standardized PDF File Names | Must |
| REQ-PRINT-002 | Display Standardized Report Header | Must |
| REQ-PRINT-003 | Include Complete Report Data | Must |
| REQ-PRINT-004 | Display Standardized Report Footer | Must |
| REQ-PRINT-005 | Include Report Metadata Section | Must |
1.3 Constraints
Tier 2 Constraint: This document describes ownership, patterns, and design rationale. It links to reference docs for full schemas and API specifications.
1.4 Dependencies
| Direction | Domain/Component | Purpose |
|---|---|---|
| Consumes | Well Model | LJ chart data points, outcomes data |
| WestgardLimit Model | QC range definitions, mean/SD values | |
| DailyOutcomes Table | Pre-aggregated trends data | |
| Run Model | Run metadata, filtering | |
| Mix/Target Models | Report filtering criteria | |
| Thermocycler Model | Instrument-based filtering/grouping | |
| TrendsReportAlert Model | Alert configuration and evaluation | |
| Provides to | Frontend | Report data for visualization |
| Export Consumers | CSV/Excel/PDF exports | |
| NOTIF Domain | Alert notifications |
2. Component Architecture
2.1 Component Diagram
2.2 Component Responsibilities
| Component | Layer | Responsibility | REQ Trace |
|---|---|---|---|
LJReportController | Controller | Generate LJ report data with wells and Westgard limits | REQ-REPORTS-001, 002, 003, 005, 007, 023 |
LJReportDownloadController | Controller | Export LJ report to Excel | REQ-REPORTS-004 |
LJReportLotsController | Controller | Manage Westgard range (lot) data | REQ-REPORTS-002, 005 |
LjReportData | Service | Aggregate well and Westgard data for LJ charts | REQ-REPORTS-001 |
LjReportExport | Export | Format LJ data for Excel export | REQ-REPORTS-004 |
WestgardLimitsController | Controller | CRUD for Westgard statistical ranges | REQ-REPORTS-005, 006 |
TrendsReportController | Controller | Generate trends report from daily outcomes | REQ-REPORTS-008, 009, 012, 015 |
TrendsReportExportController | Controller | Export trends report | REQ-REPORTS-011 |
TrendsReportPrintController | Controller | Generate printable trends report | REQ-REPORTS-011, REQ-PRINT-* |
TrendsReportAlertsController | Controller | Manage trend alert configurations | REQ-REPORTS-013 |
TrendsReportAlert | Model | Alert configuration with thresholds and schedules | REQ-REPORTS-013, 014 |
OutcomesReportController | Controller | Generate outcomes report with pagination | REQ-REPORTS-016, 017, 018 |
OutcomesReportExportController | Controller | Export outcomes report | REQ-REPORTS-019 |
OutcomesReportPrintController | Controller | Generate printable outcomes report | REQ-REPORTS-019, REQ-PRINT-* |
OutcomesReportFilters | DTO | Encapsulate outcomes filter criteria | REQ-REPORTS-017 |
3. Data Design
3.1 Entities
This domain does not own persistent entities. It consumes data from other domains and one pre-aggregated table.
| Entity | Owner | Usage in REPORTS |
|---|---|---|
wells | RUNRPT | Source data for LJ and Outcomes reports |
westgard_limits | KITCFG | QC range definitions with mean/SD/CV |
daily_outcomes | REPORTS (aggregated) | Pre-computed outcome counts for Trends |
trends_report_alerts | REPORTS | Alert configurations |
runs | RUNRPT | Run metadata, archive status |
mixes | KITCFG | Filter criteria |
thermocyclers | KITCFG | Filter/grouping criteria |
3.2 Data Structures
LJ Report Response
interface LJReportResponse {
wells: LJReportWell[]; // Non-crossover wells
crossover_wells: LJReportWell[]; // Crossover wells (separate display)
westgard_limits: WestgardLimit[]; // Statistical ranges
}
interface LJReportWell {
id: string;
extraction_date: Date;
ct_value: number;
quant_value: number;
run_name: string;
mix_name: string;
target_name: string;
control_type: string;
is_crossover: boolean;
error_code?: string;
}
interface WestgardLimit {
id: string;
lot_name: string;
mean: number;
sd: number;
cv: number;
quant_or_ct: 'CT' | 'Quantity';
created_at: Date;
event: 'NEWWG' | 'UPDATE' | 'RESOLUTION';
}
Trends Report Response
interface TrendsReportRow {
site_name: string;
date: string; // Aggregated by interval
mix_name: string; // Or "All selected mixes"
thermocycler_serial_number: string; // Or "All selected thermocyclers"
outcome: string; // Or "All selected outcomes"
well_count: number;
percentage: number;
}
Trends Alert Configuration
interface TrendsReportAlert {
id: string;
is_patient: boolean;
threshold_type: 'COUNT' | 'PERCENTAGE';
trigger: 'ABOVE' | 'BELOW';
threshold_value: number;
interval: 'DAY' | 'WEEK' | 'MONTH';
notifiers: 'EMAIL' | 'IN_APP' | 'BOTH';
thermocycler_ids: string[];
mix_ids: string[];
outcomes: OutcomeFilter[];
enabled: boolean;
site_id: string;
schedule?: AlertSchedule;
}
3.3 State Transitions
This domain is stateless for report generation. Alert state is managed through enable/disable operations.
4. Interface Design
4.1 APIs Provided
| Endpoint | Method | Purpose | Controller |
|---|---|---|---|
/api/lj-report | GET | Generate LJ report data | LJReportController |
/api/lj-report/download | GET | Export LJ report to Excel | LJReportDownloadController |
/api/lj-report/lots | GET | Get available Westgard lots | LJReportLotsController |
/api/westgard-limits | GET/POST/PUT/DELETE | Manage Westgard ranges | WestgardLimitsController |
/api/trends-report | GET | Generate trends report data | TrendsReportController |
/api/trends-report/export | GET | Export trends report | TrendsReportExportController |
/api/trends-report/print/{id} | GET | Get cached print data | TrendsReportPrintController |
/api/trends-report-alerts | GET/POST/PUT/DELETE | Manage alerts | TrendsReportAlertsController |
/api/outcomes-report | GET | Generate outcomes report | OutcomesReportController |
/api/outcomes-report/export | GET | Export outcomes report | OutcomesReportExportController |
/api/outcomes-report/print/{id} | GET | Get cached print data | OutcomesReportPrintController |
4.2 APIs Consumed
| Endpoint/Source | Purpose |
|---|---|
| Well Model queries | Source data for LJ and Outcomes |
| DailyOutcomes table | Pre-aggregated data for Trends |
| Site/Mix/Thermocycler Models | Filter option population |
5. Behavioral Design
5.1 LJ Report Data Aggregation
Algorithm: Generate Levey Jennings Report Data
Inputs:
- well_filters: WellFilters - Date range, error status filters
- run_filters: RunFilters - Run-level filters
- target_ids: array - Selected targets
- role_to_mix_mapping_ids: array - Control role mappings
- extraction_instrument_ids: array - Selected instruments
- lot_ids: array - Selected Westgard lots (optional)
- run_id: string - Focus on specific run (optional)
Outputs:
- wells: Collection - Control wells with observations
- crossover_wells: Collection - Crossover wells (separate)
- westgard_limits: Collection - Applicable Westgard ranges
Steps:
1. If run_id provided:
a. Load wells from specified run
b. Load 10 previous wells by extraction_date
c. Load 10 next wells by extraction_date
d. Merge into result set
2. Else:
a. Load all matching wells (limit 999)
3. Apply archive exclusion (whereNotBelongsToArchivedRun)
4. Load Westgard limits matching filters
5. Separate wells into crossover and non-crossover
6. Return combined dataset
Notes:
- Archived runs always excluded (REQ-REPORTS-022)
- Wells split by is_crossover flag (REQ-REPORTS-023)
- Westgard limits include soft-deleted for historical accuracy
5.2 Trends Report Aggregation
Algorithm: Generate Trends Report
Inputs:
- is_patient: boolean - Patient vs control samples
- sample_names: array - Control role aliases with crossover flag
- thermocycler_ids: array - Instrument filter
- mix_ids: array - Mix filter
- site_ids: array - Site filter
- outcome_ids: array - Outcome filter
- date_range: [start, end] - Date filter
- interval: 'Day' | 'Week' | 'Month' - Aggregation interval
- aggregated_filters: { mixes, outcomes, thermocyclers } - Aggregation flags
Outputs:
- rows: Collection - Aggregated outcome data
Steps:
1. Query daily_outcomes table
2. Apply sample type filter:
a. If is_patient: filter role_alias = 'Patient'
b. Else: filter by sample_names with is_crossover matching
3. Apply entity filters (thermocycler, site, outcome, mix)
4. Apply date range filter
5. Group by:
a. site_id (always)
b. interval (Day/Week/Month with SQL date functions)
c. thermocycler_id (unless aggregated)
d. mix_id (unless aggregated)
e. outcome_id + is_crossover (unless aggregated)
6. Calculate:
a. well_count: SUM(count)
b. percentage: well_count / partition total * 100
7. Return ordered by date
Notes:
- Uses pre-computed daily_outcomes table for performance
- Interval grouping uses SQL date functions (DAYOFWEEK, LAST_DAY)
- Percentage calculated using window function (OVER PARTITION BY)
5.2.1 Timezone Handling
The daily_outcomes cache normalizes dates to lab timezone:
| Well Type | Date Source | Handling |
|---|---|---|
| Extraction wells | Well extraction_date | Convert to lab timezone, store date-only |
| Non-extraction controls | Run created_at | Inherit run date |
This ensures all wells in a run are grouped by the same date regardless of original timestamp precision.
5.2.2 Cache Update Strategy
| Operation | Component | Trigger |
|---|---|---|
| Initial Population | PopulateDailyOutcomesTableAction | Migration or manual rebuild |
| Incremental Update | UpdateDailyOutcomesTableAction | Run file processed |
| Background Job | UpdateDailyOutcomeTableJob | Dispatched after each run upload |
| Full Regeneration | Migration | Data correction required |
5.2.3 Performance Characteristics
| Metric | Before Cache | With Cache |
|---|---|---|
| Query Time | Seconds to minutes | Milliseconds |
| Storage | N/A | Additional pre-computed rows |
| Synchronization | N/A | Automatic via job dispatch |
Special Handling:
is_crossoverflag preserved in aggregation for QC filtering- Soft-deleted records preserved for historical accuracy
5.3 Print Report Generation
Algorithm: Generate Print Report Data
Inputs:
- uniq_identifier: string - Cache key for request data
- report_type: 'trends' | 'outcomes' - Report type
Outputs:
- data: array - Complete report data for printing
Steps:
1. Retrieve cached request data by identifier
2. If processed data exists in cache:
a. Return cached data directly
3. Else:
a. Rebuild query using cached filter parameters
b. Execute full query (no pagination)
c. Return complete dataset
Notes:
- Print controllers return ALL data regardless of pagination (REQ-PRINT-003)
- Frontend handles header/footer formatting (REQ-PRINT-002, 004)
- File naming handled by frontend (REQ-PRINT-001)
6. Error Handling
| Condition | Detection | Response | User Impact |
|---|---|---|---|
| Empty dataset | Zero results | Return empty array | "No data matches filters" message |
| Invalid Westgard SD | SD <= 0 | Validation error | Cannot save range |
| Invalid Westgard mean | Mean < 0 | Validation error | Cannot save range |
| Duplicate range name | Uniqueness check | 422 response | "Range name must be unique" |
| Alert threshold invalid | Value validation | Validation error | Cannot save alert |
| Cache miss (print) | No cached data | Rebuild query | Slightly slower response |
7. Configuration
| Setting | Location | Default | Effect |
|---|---|---|---|
default_lj_unit | ClientConfiguration | CT | Default Y-axis unit |
lj_show_mean | ClientConfiguration | true | Mean line visibility |
lj_show_sd_lines | ClientConfiguration | true | SD line visibility |
lj_show_bands | ClientConfiguration | true | Color band visibility |
trends_default_interval | ClientConfiguration | Week | Default aggregation interval |
alert_email_enabled | Environment | true | Email notification toggle |
progressive_loading_threshold | Environment | 1000 | Pagination threshold |
See Configuration Reference for details.
8. Implementation Mapping
8.1 Code Locations
| Component | Type | Path |
|---|---|---|
| LJReportController | Controller | code/app/Http/Controllers/LJReportController.php |
| LJReportDownloadController | Controller | code/app/Http/Controllers/LJReportDownloadController.php |
| LJReportLotsController | Controller | code/app/Http/Controllers/LJReportLotsController.php |
| LjReportData | Service | code/app/LjReportData.php |
| LjReportExport | Export | code/app/Exports/LjReportExport.php |
| WestgardLimit | Model | code/app/WestgardLimit.php |
| WestgardLimitsController | Controller | code/app/Http/Controllers/WestgardLimitsController.php |
| TrendsReportController | Controller | code/app/Http/Controllers/TrendsReport/TrendsReportController.php |
| TrendsReportExportController | Controller | code/app/Http/Controllers/TrendsReportExportController.php |
| TrendsReportPrintController | Controller | code/app/Http/Controllers/TrendsReportPrintController.php |
| TrendsReportAlertsController | Controller | code/app/Http/Controllers/TrendsReportAlertsController.php |
| TrendsReportAlert | Model | code/app/TrendsReportAlert.php |
| TrendsReportExport | Export | code/app/Exports/TrendsReportExport.php |
| OutcomesReportController | Controller | code/app/Http/Controllers/OutcomesReportController.php |
| OutcomesReportExportController | Controller | code/app/Http/Controllers/OutcomesReportExportController.php |
| OutcomesReportPrintController | Controller | code/app/Http/Controllers/OutcomesReportPrintController.php |
| OutcomesReportFilters | DTO | code/app/Actions/OutcomesReport/OutcomesReportFilters.php |
8.2 Requirement Traceability
| REQ ID | Design Section | Code Location |
|---|---|---|
| REQ-REPORTS-001 | §5.1 LJ Aggregation | LJReportController.php, LjReportData.php |
| REQ-REPORTS-002 | §5.1 LJ Aggregation | LJReportLotsController.php, LjReportData.php |
| REQ-REPORTS-003 | §4.1 APIs | LJReportController.php |
| REQ-REPORTS-004 | §2.2 Components | LJReportDownloadController.php, LjReportExport.php |
| REQ-REPORTS-005 | §2.2 Components | WestgardLimitsController.php, WestgardLimit.php |
| REQ-REPORTS-006 | §2.2 Components | WestgardLimitsController.php |
| REQ-REPORTS-007 | §7 Configuration | LJReportController.php, ClientConfiguration.php |
| REQ-REPORTS-008 | §5.2 Trends Aggregation | TrendsReportController.php |
| REQ-REPORTS-009 | §5.2 Trends Aggregation | TrendsReportController.php |
| REQ-REPORTS-010 | §4.1 APIs | TrendsReportController.php |
| REQ-REPORTS-011 | §5.3 Print Generation | TrendsReportExportController.php, TrendsReportPrintController.php |
| REQ-REPORTS-012 | §5.2 Trends Aggregation | TrendsReportController.php (aggregated_filters) |
| REQ-REPORTS-013 | §3.2 Alert Config | TrendsReportAlertsController.php, TrendsReportAlert.php |
| REQ-REPORTS-014 | §3.3 Alert State | NotifyTrendAlertsAction.php, TrendsReportAlertsScheduleCheck.php |
| REQ-REPORTS-015 | §5.2 Trends Aggregation | TrendsReportController.php (outcomeMessage) |
| REQ-REPORTS-016 | §2.2 Components | OutcomesReportController.php |
| REQ-REPORTS-017 | §2.2 Components | OutcomesReportFilters.php |
| REQ-REPORTS-018 | §4.1 APIs | OutcomesReportController.php |
| REQ-REPORTS-019 | §5.3 Print Generation | OutcomesReportExportController.php, OutcomesReportPrintController.php |
| REQ-REPORTS-020 | §2.2 Components | Combined outcome configuration (KITCFG) |
| REQ-REPORTS-021 | §5.1, §5.2 Aggregation | Site filtering in all controllers |
| REQ-REPORTS-022 | §5.1 LJ Aggregation | whereNotBelongsToArchivedRun() |
| REQ-REPORTS-023 | §5.1 LJ Aggregation | LjReportData.php (is_crossover split) |
| REQ-REPORTS-024 | §2.2 Components | Middleware role checks |
| REQ-REPORTS-025 | §7 Configuration | Pagination in controllers |
| REQ-PRINT-001 | §5.3 Print Generation | Frontend (file naming) |
| REQ-PRINT-002 | §5.3 Print Generation | Frontend (header formatting) |
| REQ-PRINT-003 | §5.3 Print Generation | Print controllers (no pagination) |
| REQ-PRINT-004 | §5.3 Print Generation | Frontend (footer formatting) |
| REQ-PRINT-005 | §5.3 Print Generation | Frontend (metadata section) |
9. Design Decisions
| Decision | Rationale | Alternatives Considered |
|---|---|---|
| Pre-aggregated daily_outcomes table | Performance for trends over large datasets | Real-time aggregation (rejected: too slow) |
| Separate crossover wells in LJ response | Business requirement for distinct display | Single wells array (rejected: loses visibility) |
| Frontend-driven print formatting | Flexibility, separation of concerns | Server-side PDF generation (rejected: less flexible) |
| Cache-based print data | Support large reports without timeout | Direct query (rejected: timeouts on large data) |
| SQL window functions for percentage | Database-efficient calculation | PHP post-processing (rejected: slower) |
| Soft-delete preservation for Westgard | Historical accuracy in reports | Hard delete (rejected: loses history) |
10. Related Documents
| Document | Relevant Sections |
|---|---|
| SRS: reports.md | Requirements source |
| SRS: print.md | Print requirements source |
| SDS: Architecture | System architecture context |
| SDS: RUNRPT Domain | Well/Run data source |
| SDS: KITCFG Domain | Westgard configuration |
| SDS: Database Reference | Schema details |