Scheme Architecture Guide¶
This document explains how test schemes (DTS, VL, EID, TB, Recency, COVID-19, DBS, Custom Tests) are organized in the ePT project. Use this as a reference when maintaining existing schemes or adding new ones.
Table of Contents¶
- Overview
- Directory Structure
- Scheme Components
- Data Flow
- Adding a New Scheme
- Scheme-Specific Details
Overview¶
Each scheme in ePT follows a consistent MVC architecture:
graph TB
subgraph "Participant Flow"
P[Participant] --> PC["{Scheme}Controller"]
PC --> PV["views/scripts/{scheme}/response.phtml"]
PC --> SS["Shipments Service"]
end
subgraph "Admin Flow"
A[Admin] --> SC["ShipmentController"]
SC --> AP["partials/schemes/{scheme}.phtml"]
SC --> ES["Evaluation Service"]
end
subgraph "Data Layer"
SS --> DT["DbTable/Response{Scheme}"]
ES --> M["Model/{Scheme}.php"]
DT --> DB[(Database)]
M --> DB
end
subgraph "Database Tables"
DB --> RR["response_result_{scheme}"]
DB --> REF["reference_result_{scheme}"]
DB --> SPM["shipment_participant_map"]
end
| Component | Purpose | Location |
|---|---|---|
| Model | Evaluation logic, scoring, reports | application/models/{Scheme}.php |
| Controller | Request handling, response entry | application/controllers/{Scheme}Controller.php |
| Views | Participant response forms | application/views/scripts/{scheme}/ |
| DbTable | Database persistence | application/models/DbTable/Response{Scheme}.php |
| Admin Partial | Shipment add/edit forms | application/modules/admin/views/scripts/shipment/partials/schemes/{scheme}.phtml |
Directory Structure¶
application/
├── models/
│ ├── Dts.php # DTS evaluation & reports
│ ├── Vl.php # VL evaluation & reports
│ ├── Tb.php # TB evaluation & reports
│ ├── Eid.php # EID evaluation & reports
│ ├── Recency.php # Recency evaluation & reports
│ ├── Covid19.php # COVID-19 evaluation & reports
│ ├── CustomTest.php # Custom Tests evaluation
│ └── DbTable/
│ ├── ResponseDts.php # response_result_dts table
│ ├── ResponseVl.php # response_result_vl table
│ ├── ResponseTb.php # response_result_tb table
│ ├── ResponseEid.php # response_result_eid table
│ ├── ResponseRecency.php # response_result_recency table
│ ├── ResponseCovid19.php # response_result_covid19 table
│ ├── ResponseDbs.php # response_result_dbs table
│ ├── ResponseGenericTest.php # response_result_generic_test table (Custom Tests)
│ ├── Shipments.php # shipment table
│ └── ShipmentParticipantMap.php # shipment_participant_map table
│
├── controllers/
│ ├── DtsController.php # DTS response entry
│ ├── VlController.php # VL response entry
│ ├── TbController.php # TB response entry
│ ├── EidController.php # EID response entry
│ ├── RecencyController.php # Recency response entry
│ ├── Covid19Controller.php # COVID-19 response entry
│ ├── DbsController.php # DBS response entry
│ └── CustomTestController.php # Custom Tests response entry
│
├── views/scripts/
│ ├── dts/response.phtml # DTS response form
│ ├── vl/response.phtml # VL response form
│ ├── tb/response.phtml # TB response form
│ ├── eid/response.phtml # EID response form
│ ├── recency/response.phtml # Recency response form
│ ├── covid19/response.phtml # COVID-19 response form
│ ├── dbs/response.phtml # DBS response form
│ └── custom-test/response.phtml # Custom Tests response form
│
├── services/
│ ├── Shipments.php # updateDtsResults(), updateVlResults(), etc.
│ ├── Schemes.php # getPossibleResults(), getSchemeControls(), etc.
│ ├── Evaluation.php # Evaluation workflow management
│ └── Reports.php # Report generation
│
└── modules/admin/views/scripts/shipment/
├── edit.phtml # Slim wrapper (includes partials)
├── get-sample-form.ajax.phtml # AJAX loader for ADD flow
└── partials/
├── common/
│ ├── form-header.phtml # Shared header fields
│ ├── form-footer.phtml # Submit buttons
│ └── form-scripts.phtml # Shared JavaScript
└── schemes/
├── dts.phtml # DTS shipment form
├── vl.phtml # VL shipment form
├── tb.phtml # TB shipment form
├── eid.phtml # EID shipment form
├── recency.phtml # Recency shipment form
├── covid19.phtml # COVID-19 shipment form
├── dbs.phtml # DBS shipment form
└── generic.phtml # Custom Tests shipment form
Scheme Components¶
1. Model (application/models/{Scheme}.php)¶
Each scheme model contains the evaluate() method - the core evaluation logic:
public function evaluate($shipmentResult, $shipmentId, $reEvaluate = false)
{
// 1. Fetch reference results
// 2. Compare participant responses vs reference
// 3. Calculate scores (response + documentation)
// 4. Determine pass/fail
// 5. Update shipment_participant_map with results
// 6. Return evaluation summary
}
Key Methods in Models:
| Method | Purpose |
|---|---|
evaluate() |
Core scoring logic |
generate{Scheme}ExcelReport() |
Excel report generation |
getDataForIndividualPDFBatch() |
PDF report data |
get{Scheme}Samples() |
Retrieve sample data |
Model Sizes (complexity indicator): - TB.php: 133K (most complex - molecular/microscopy) - VL.php: 114K (Z-score calculations) - DTS.php: 111K (multiple algorithms) - Covid19.php: 79K - CustomTest.php: 79K - Recency.php: 63K - Eid.php: 21K (simplest)
2. Controller (application/controllers/{Scheme}Controller.php)¶
Handles participant response entry:
public function responseAction()
{
if ($this->getRequest()->isPost()) {
// Save results via service layer
$this->shipmentService->update{Scheme}Results($data);
// Redirect appropriately
} else {
// Load form data
$this->view->samples = $this->schemeService->get{Scheme}Samples();
$this->view->possibleResults = $this->schemeService->getPossibleResults();
// Render response.phtml
}
}
Common Controller Actions:
- responseAction() - Main response entry (GET: show form, POST: save)
- downloadAction() - Download reference data/reports
- deleteAction() - Delete response (if applicable)
3. Response View (application/views/scripts/{scheme}/response.phtml)¶
Participant-facing form for entering test results. Common elements:
- Laboratory/Participant information
- Receipt and testing dates
- Assay/kit selection
- Results table (scheme-specific columns)
- QC section (conditional)
- Supervisor review
- File upload (some schemes)
- Manual override (admin only)
4. DbTable (application/models/DbTable/Response{Scheme}.php)¶
Database persistence layer:
class Application_Model_DbTable_ResponseDts extends Zend_Db_Table_Abstract
{
protected $_name = 'response_result_dts';
protected $_primary = array('shipment_map_id', 'sample_id');
public function updateResults($params)
{
// Insert or update response records
}
public function updateResultsByAPI($params, $dm)
{
// API-based result submission
}
}
5. Admin Partial (partials/schemes/{scheme}.phtml)¶
Unified shipment add/edit form that works for both flows:
<?php
// Mode detection
$isEditMode = isset($this->shipmentData) && !empty($this->shipmentData);
// Normalize data
$samples = $isEditMode
? $this->shipmentData['reference']
: $this->schemeControls;
$possibleResults = $isEditMode
? $this->shipmentData['possibleResults']
: $this->schemePossibleResults;
?>
<!-- Form HTML works for both ADD and EDIT -->
Data Flow¶
Participant Response Flow¶
sequenceDiagram
participant P as Participant
participant D as Dashboard
participant C as {Scheme}Controller
participant S as Shipments Service
participant DB as Database
P->>D: View available shipments
D->>P: Show scheme list
P->>C: Click "Enter Results" (GET)
C->>C: Load samples, testkits, possible results
C->>P: Render response.phtml form
P->>C: Submit results (POST)
C->>S: update{Scheme}Results($data)
S->>DB: Insert/Update response_result_{scheme}
S->>DB: Update shipment_participant_map status
DB-->>C: Success
C->>P: Redirect to dashboard
Evaluation Flow¶
sequenceDiagram
participant A as Admin
participant EC as EvaluateController
participant ES as Evaluation Service
participant M as {Scheme} Model
participant DB as Database
A->>EC: Trigger evaluation
EC->>ES: getShipmentToEvaluate()
ES->>DB: Load participant responses
DB-->>ES: Response data
ES->>M: evaluate($shipmentResult, $shipmentId)
M->>DB: Fetch reference_result_{scheme}
DB-->>M: Reference data
M->>M: Compare responses vs reference
M->>M: Calculate scores
M->>M: Determine pass/fail
M->>DB: Update shipment_participant_map
Note over DB: shipment_score, documentation_score,<br/>final_result, failure_reason
M-->>EC: Evaluation complete
EC->>A: Show results
Shipment Creation Flow (Admin)¶
sequenceDiagram
participant A as Admin
participant I as index.phtml
participant SC as ShipmentController
participant AJAX as get-sample-form.ajax
participant P as partials/schemes/{scheme}
participant DB as Database
A->>I: Select scheme from dropdown
I->>SC: getSampleFormAction() [AJAX]
SC->>AJAX: Load form partial
AJAX->>P: Include scheme partial
P-->>A: Render form fields
A->>A: Fill in sample details
A->>SC: Submit (addAction/editAction)
SC->>DB: Save to shipment table
SC->>DB: Save to reference_result_{scheme}
DB-->>SC: Success
SC->>A: Redirect to shipment list
Database Tables¶
Core Tables¶
| Table | Purpose |
|---|---|
shipment |
Shipment metadata, status, configuration |
shipment_participant_map |
Links shipments to participants, stores scores |
scheme_list |
Scheme definitions and configuration |
Per-Scheme Tables¶
| Scheme | Response Table | Reference Table |
|---|---|---|
| DTS | response_result_dts |
reference_result_dts |
| VL | response_result_vl |
reference_result_vl |
| TB | response_result_tb |
reference_result_tb |
| EID | response_result_eid |
reference_result_eid |
| Recency | response_result_recency |
reference_result_recency |
| COVID-19 | response_result_covid19 |
reference_result_covid19 |
| DBS | response_result_dbs |
reference_result_dbs |
| Custom Tests | response_result_generic_test |
reference_result_generic_test |
Table Relationships¶
erDiagram
shipment ||--o{ shipment_participant_map : "has participants"
shipment ||--o{ reference_result : "has reference samples"
shipment_participant_map ||--o{ response_result : "has responses"
participant ||--o{ shipment_participant_map : "enrolled in"
shipment {
int shipment_id PK
string shipment_code
string scheme_type
int distribution_id
string status
json shipment_attributes
}
shipment_participant_map {
int map_id PK
int shipment_id FK
int participant_id FK
string response_status
decimal shipment_score
decimal documentation_score
int final_result
json failure_reason
}
reference_result {
int shipment_id PK
string sample_id PK
string reference_result
int sample_score
}
response_result {
int shipment_map_id PK
string sample_id PK
string reported_result
timestamp created_on
}
Adding a New Scheme¶
Step 1: Create Model¶
Create application/models/NewScheme.php:
class Application_Model_NewScheme
{
public function evaluate($shipmentResult, $shipmentId, $reEvaluate = false)
{
// Implement evaluation logic
}
public function generateNewSchemeExcelReport($shipmentId)
{
// Implement report generation
}
}
Step 2: Create DbTable¶
Create application/models/DbTable/ResponseNewScheme.php:
class Application_Model_DbTable_ResponseNewScheme extends Zend_Db_Table_Abstract
{
protected $_name = 'response_result_newscheme';
protected $_primary = array('shipment_map_id', 'sample_id');
public function updateResults($params) { /* ... */ }
}
Step 3: Create Controller¶
Create application/controllers/NewschemeController.php:
class NewschemeController extends Zend_Controller_Action
{
public function responseAction()
{
// Handle GET and POST
}
}
Step 4: Create Response View¶
Create application/views/scripts/newscheme/response.phtml
Step 5: Create Admin Partial¶
Create application/modules/admin/views/scripts/shipment/partials/schemes/newscheme.phtml
Step 6: Update Routers¶
Add to get-sample-form.ajax.phtml:
} elseif ($this->scheme == 'newscheme') {
include('partials/schemes/newscheme.phtml');
}
Add to edit.phtml:
} elseif ($shipmentData['shipment']['scheme_type'] == 'newscheme') {
include dirname(__FILE__) . '/partials/schemes/newscheme.phtml';
}
Step 7: Update Service Layer¶
Add to Shipments.php:
public function updateNewSchemeResults($data)
{
// Implement result saving logic
}
Step 8: Create Database Tables¶
CREATE TABLE reference_result_newscheme (
shipment_id INT,
sample_id VARCHAR(255),
-- scheme-specific fields
PRIMARY KEY (shipment_id, sample_id)
);
CREATE TABLE response_result_newscheme (
shipment_map_id INT,
sample_id VARCHAR(255),
-- scheme-specific fields
PRIMARY KEY (shipment_map_id, sample_id)
);
Scheme-Specific Details¶
DTS (Dried Tube Specimen)¶
- Complexity: High (multiple algorithms, RTRI support, syphilis testing)
- Key Features:
- 2 or 3 test panel configuration
- Multiple testing algorithms (Serial, Parallel, etc.)
- EIA/WB/Geenius modals for detailed results
- Optional syphilis and RTRI testing
- Algorithm-based scoring
VL (Viral Load)¶
- Complexity: High (quantitative analysis)
- Key Features:
- Z-score based evaluation
- Assay-specific range calculations
- TND (Target Not Detected) handling
- Log10 conversion support
- File upload for raw data
TB (Tuberculosis)¶
- Complexity: High (multiple assay types)
- Key Features:
- Molecular vs Microscopy test types
- GeneXpert MTB/RIF and Ultra support
- Rif resistance detection
- Probe values (A, B, C, D, E)
- Instrument tracking
- Draft save capability
EID (Early Infant Diagnosis)¶
- Complexity: Low (simplest scheme)
- Key Features:
- Binary result matching
- CT/OD value capture
- Extraction assay selection
Recency¶
- Complexity: Medium
- Key Features:
- RTRI algorithm support
- Control/Verification/Longterm line interpretation
- Recency classification (Negative/Recent/Long-term)
COVID-19¶
- Complexity: Medium
- Key Features:
- 1-3 test platform support
- PCR reagent tracking
- Gene type identification
- Platform validation
DBS (Dried Blood Spot)¶
- Complexity: Medium-High
- Key Features:
- Multiple EIA tests (up to 3)
- Western Blot with 9 protein bands
- OD and cutoff values
Custom Tests¶
- Complexity: Configurable
- Key Features:
- Supports qualitative and quantitative tests
- Configurable pass/fail thresholds
- Dynamic field configuration
- Custom test kit management
Configuration¶
Scheme configuration is managed via Pt_Commons_SchemeConfig::get('{scheme}'):
$config = Pt_Commons_SchemeConfig::get('dts');
// Returns: allowedAlgorithms, dtsSchemeType, rtriEnabled, etc.
Configuration sources:
1. Database: scheme_list.user_test_config (JSON)
2. System config files
3. Model defaults
Shipment Lifecycle¶
stateDiagram-v2
[*] --> Draft: Create shipment
Draft --> Ready: Add samples & reference data
Ready --> Shipped: Ship to participants
Shipped --> ResponseCollection: Participants enter results
ResponseCollection --> Evaluation: Admin triggers evaluation
Evaluation --> Evaluated: Scores calculated
Evaluated --> ReportsGenerated: Generate reports
ReportsGenerated --> Finalized: Review and finalize
Finalized --> [*]
note right of ResponseCollection
Participants submit via
{scheme}/response.phtml
end note
note right of Evaluation
Model::evaluate() compares
responses vs reference
end note
Scoring Formula¶
The lifecycle above describes workflow stages, not literal values for every database status. Deadline processing can also queue evaluation. Generation and finalization are separate actions.
Most schemes follow this pattern:
flowchart LR
subgraph Input
CR[Correct Responses]
MP[Max Possible]
DI[Documentation Items]
end
subgraph Calculation
RS["Response Score<br/>(CR/MP) × (100-Doc%)"]
DS["Documentation Score<br/>Sum of item scores"]
end
subgraph Output
FS[Final Score]
PF{Pass/Fail}
end
CR --> RS
MP --> RS
DI --> DS
RS --> FS
DS --> FS
FS --> PF
PF -->|">= Threshold"| Pass
PF -->|"< Threshold"| Fail
Formula:
Response Score = (Correct Responses / Max Possible) × (100 - Documentation%)
Documentation Score = Sum of documentation item scores
Final Score = Response Score + Documentation Score
Pass = Final Score >= Passing Percentage (configurable per scheme)
Report Generation & Excel Export¶
Report Architecture¶
flowchart TB
subgraph "User Interface"
A[Admin] --> RC[Report Controllers]
RC --> RV[Report Views]
end
subgraph "Service Layer"
RC --> RS[Reports Service]
RS --> ES[Evaluation Service]
end
subgraph "Model Layer"
RS --> DM["{Scheme} Model"]
DM --> GER["generate{Scheme}ExcelReport()"]
DM --> GDP["getDataFor{Type}PDF()"]
end
subgraph "Output"
GER --> XLSX[Excel .xlsx]
GDP --> PDF[PDF Reports]
XLSX --> ZIP[ZIP Archive]
PDF --> ZIP
end
Excel Report Methods by Scheme¶
Each scheme model has a dedicated Excel report generation method:
| Scheme | Model Method | Output |
|---|---|---|
| DTS | generateDtsRapidHivExcelReport($shipmentId) |
Multi-sheet XLSX with test results, scores |
| VL | generateDtsViralLoadExcelReport($shipmentId) |
Participant details, assay results, Z-scores |
| TB | generateTbExcelReport($shipmentId) |
Participant list, MTB/RIF results |
| EID | generateDbsEidExcelReport($shipmentId) |
EID PT results, assay info |
| Recency | generateRecencyExcelReport($shipmentId) |
Lab info, recency PT results |
| COVID-19 | generateCovid19ExcelReport($shipmentId) |
Instructions, test types, responses |
| Custom Tests | generateGenericTestExcelReport($shipmentId, $schemeType) |
Participant list, test responses |
PDF Generation Methods¶
Some schemes support individual and summary PDF reports:
| Scheme | Individual PDF | Summary PDF |
|---|---|---|
| TB | getDataForIndividualPDFBatch($mapIds) |
getDataForSummaryPDF($shipmentId) |
| VL | getDataForIndividualPDFBatch($shipmentId, $mapIds, $attributes) |
- |
| Custom Tests | - | getDataForSummaryPDF($shipmentId) |
Report Controllers¶
Located in application/modules/reports/controllers/:
| Controller | Key Actions | Purpose |
|---|---|---|
ShipmentsController |
indexAction, shipmentsExportAction |
Shipment overview reports |
ShipmentResponseController |
indexAction, shipmentsExportPdfAction |
Response reports |
FinalizeController |
shipmentsAction, sendReportMailAction |
Finalized reports |
ParticipantPerformanceController |
indexAction, exportAction |
Performance analytics |
ParticipantTrendsController |
indexAction |
Trend analysis |
CorrectiveActionsController |
indexAction |
Corrective action reports |
TbResultsController |
indexAction |
TB-specific results |
TbAllSitesResultsController |
indexAction |
Multi-site TB reports |
Report Service (application/services/Reports.php)¶
Central orchestration for report generation:
// Route to scheme-specific Excel generation
public function getShipmentParticipant($shipmentId, $schemeType = null)
{
switch ($schemeType) {
case 'dts': return $this->dtsModel->generateDtsRapidHivExcelReport($shipmentId);
case 'vl': return $this->vlModel->generateDtsViralLoadExcelReport($shipmentId);
case 'tb': return $this->tbModel->generateTbExcelReport($shipmentId);
// ... etc
}
}
// Generate overview report
public function exportShipmentsReport($params)
{
// Creates "Shipment Response Overview" Excel
// Columns: scheme, shipment code, sample label, responses, pass rates
}
Report Flow¶
sequenceDiagram
participant A as Admin
participant RC as ReportController
participant RS as Reports Service
participant M as {Scheme} Model
participant DB as Database
participant F as File System
A->>RC: Request report (shipmentsExportAction)
RC->>RS: getShipmentParticipant($shipmentId, $scheme)
RS->>M: generate{Scheme}ExcelReport($shipmentId)
M->>DB: Fetch participant data
M->>DB: Fetch response data
M->>DB: Fetch reference data
DB-->>M: Data
M->>M: Build Excel with PhpSpreadsheet
M->>F: Save .xlsx to temp
F-->>RC: File path
RC->>A: Download file
File Output Locations¶
| Type | Path | Example |
|---|---|---|
| Temp exports | TEMP_UPLOAD_PATH/ |
shipment-response-1706284800.xlsx |
| Summary PDFs | /downloads/reports/{code}/ |
DTS-2024-01-summary.pdf |
| Individual PDFs | /downloads/reports/{code}/ |
DTS-2024-01-{participant}.pdf |
| ZIP archives | /downloads/reports/ |
DTS-2024-01.zip |
| TB Forms | TEMP_UPLOAD_PATH/ |
DTS-2024-01-TB-FORMS.zip |
Excel Report Structure (Example: DTS)¶
DTS-Shipment-Report.xlsx
├── Sheet 1: Instructions
│ └── Tab descriptions, color coding legend
├── Sheet 2: Participant List
│ └── Lab ID, Name, Country, Region, Site Type
├── Sheet 3: PT Results
│ └── Sample, Test 1/2/3 Results, Final Result, Score
├── Sheet 4: Panel Score
│ └── Per-sample scoring breakdown
├── Sheet 5: Documentation Score
│ └── Receipt date, rehydration, testing date scores
└── Sheet 6: Total Score
└── Response + Documentation = Final Score, Pass/Fail
Supported Export Formats¶
| Format | Library | Use Case |
|---|---|---|
| Excel (.xlsx) | PhpOffice\PhpSpreadsheet | Detailed data export, analysis |
| TCPDF/Custom | Individual certificates, summaries | |
| ZIP | ZipArchive | Bulk downloads, all reports bundled |
Adding Reports for New Scheme¶
-
Add Excel method to Model:
public function generateNewSchemeExcelReport($shipmentId) { $excel = new Spreadsheet(); // Build sheets... $writer = IOFactory::createWriter($excel, 'Xlsx'); $filename = 'newscheme-' . $shipmentId . '.xlsx'; $writer->save(TEMP_UPLOAD_PATH . '/' . $filename); return $filename; } -
Add to Reports Service:
case 'newscheme': return $this->newSchemeModel->generateNewSchemeExcelReport($shipmentId); -
Add PDF support (optional):
public function getDataForIndividualPDFBatch($mapIds) { // Fetch data for PDF generation }
Related Documentation¶
- Admin Shipment Forms
- Database migrations in
/database/migrations/ - API documentation for external integrations