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 |
(uses scheme config) |
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 --> Finalized: Generate reports
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¶
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