250 lines
7.5 KiB
Markdown
250 lines
7.5 KiB
Markdown
|
|
# CLQMS MVP Sprint Plan
|
||
|
|
|
||
|
|
> **Scope**: Result Entry + Validation + Reporting (2-week sprint)
|
||
|
|
> **Removed**: Edge API, Worklists, Calculated Tests, QC, Instrument Integration
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Current State
|
||
|
|
|
||
|
|
### What's Working
|
||
|
|
- Patient CRUD + Identifiers + Addresses
|
||
|
|
- Patient Visit + ADT
|
||
|
|
- Lab Orders (create with specimen generation)
|
||
|
|
- Test Definitions + Reference Ranges (refnum)
|
||
|
|
- Authentication (JWT)
|
||
|
|
- Master Data (ValueSets, Locations, etc.)
|
||
|
|
|
||
|
|
### Critical Gap
|
||
|
|
- `ResultController` only returns JWT payload (no CRUD)
|
||
|
|
- `PatResultModel` has no validation logic
|
||
|
|
- No PDF generation capability
|
||
|
|
- Only 1 working route: `GET /api/result` (returns auth check only)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 1: Result CRUD + Validation (Week 1)
|
||
|
|
|
||
|
|
### Day 1-2: PatResultModel Enhancement
|
||
|
|
|
||
|
|
**Location**: `app/Models/PatResultModel.php`
|
||
|
|
|
||
|
|
Add methods:
|
||
|
|
- `validateAndFlag($resultID, $value)` - Compare result against refnum ranges
|
||
|
|
- Check patient age, sex from order
|
||
|
|
- Match refnum criteria
|
||
|
|
- Return 'L', 'H', or null
|
||
|
|
- `getByOrder($orderID)` - Fetch all results for an order with test names
|
||
|
|
- `getByPatient($internalPID)` - Get cumulative patient results
|
||
|
|
- `updateWithValidation($resultID, $data)` - Update + auto-validate
|
||
|
|
|
||
|
|
### Day 3-4: ResultController
|
||
|
|
|
||
|
|
**Location**: `app/Controllers/ResultController.php`
|
||
|
|
|
||
|
|
Replace placeholder with full CRUD:
|
||
|
|
|
||
|
|
| Method | Endpoint | Description |
|
||
|
|
|--------|----------|-------------|
|
||
|
|
| `index()` | `GET /api/results` | List results (filter by order/patient) |
|
||
|
|
| `show($id)` | `GET /api/results/{id}` | Get single result |
|
||
|
|
| `update($id)` | `PATCH /api/results/{id}` | Update result + auto-validate |
|
||
|
|
| `delete($id)` | `DELETE /api/results/{id}` | Soft delete result |
|
||
|
|
|
||
|
|
**Features**:
|
||
|
|
- Filter by `order_id` or `patient_id` query param
|
||
|
|
- Include test name from `testdefsite`
|
||
|
|
- Auto-calculate flags on update
|
||
|
|
- Return standardized ResponseTrait format
|
||
|
|
|
||
|
|
### Day 5: Routes & Testing
|
||
|
|
|
||
|
|
**Location**: `app/Config/Routes.php`
|
||
|
|
|
||
|
|
Replace line 18:
|
||
|
|
```php
|
||
|
|
// OLD
|
||
|
|
$routes->get('result', 'ResultController::index');
|
||
|
|
|
||
|
|
// NEW
|
||
|
|
$routes->group('results', function ($routes) {
|
||
|
|
$routes->get('/', 'ResultController::index');
|
||
|
|
$routes->get('(:num)', 'ResultController::show/$1');
|
||
|
|
$routes->patch('(:num)', 'ResultController::update/$1');
|
||
|
|
$routes->delete('(:num)', 'ResultController::delete/$1');
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
**Testing**: Manual API testing with Postman/Insomnia
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 2: HTML Report Viewing (Week 2)
|
||
|
|
|
||
|
|
### Day 1-2: Report View & Controller
|
||
|
|
|
||
|
|
**Create Report View**: `app/Views/reports/lab_report.php`
|
||
|
|
Template sections:
|
||
|
|
- Patient header (Name, DOB, ID)
|
||
|
|
- Order info (OrderID, Date, Doctor)
|
||
|
|
- Results table (Test, Result, Units, Reference, Flag)
|
||
|
|
- Footer (Lab info, signature)
|
||
|
|
- Print-friendly CSS with `@media print` support
|
||
|
|
|
||
|
|
**Create ReportController**: `app/Controllers/ReportController.php`
|
||
|
|
|
||
|
|
| Method | Endpoint | Description |
|
||
|
|
|--------|----------|-------------|
|
||
|
|
| `view($orderID)` | `GET /api/reports/{orderID}` | Generate HTML lab report |
|
||
|
|
|
||
|
|
**Logic**:
|
||
|
|
- Fetch order with patient details
|
||
|
|
- Get all results for order (with flags)
|
||
|
|
- Include test definitions (name, units, ref range)
|
||
|
|
- Render HTML view using CodeIgniter's view() function
|
||
|
|
- Users can print/save as PDF via browser
|
||
|
|
|
||
|
|
### Day 3-4: Routes & Polish
|
||
|
|
|
||
|
|
**Routes** (add to Routes.php):
|
||
|
|
```php
|
||
|
|
$routes->get('reports/(:num)', 'ReportController::view/$1');
|
||
|
|
```
|
||
|
|
|
||
|
|
**HTML Styling**:
|
||
|
|
- Professional lab report format
|
||
|
|
- Show abnormal flags (L/H) highlighted
|
||
|
|
- Include reference ranges
|
||
|
|
- Signature line for pathologist
|
||
|
|
- Responsive design with print button
|
||
|
|
|
||
|
|
**Testing**:
|
||
|
|
- Generate report with actual data
|
||
|
|
- Verify formatting
|
||
|
|
- Test print functionality
|
||
|
|
- Test edge cases (no results, all normal, mix of flags)
|
||
|
|
|
||
|
|
**Note**: PDF generation deferred to post-MVP. Users can use browser's "Print to PDF" feature for now.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Database Schema Reference
|
||
|
|
|
||
|
|
### patres (Results)
|
||
|
|
| Column | Type | Notes |
|
||
|
|
|--------|------|-------|
|
||
|
|
| ResultID | INT | PK |
|
||
|
|
| OrderID | INT | FK to ordertest |
|
||
|
|
| TestSiteID | INT | FK to testdefsite |
|
||
|
|
| Result | VARCHAR(255) | The actual value |
|
||
|
|
| ResultDateTime | DATETIME | When entered |
|
||
|
|
| RefNumID | INT | Applied reference range |
|
||
|
|
|
||
|
|
### refnum (Reference Ranges)
|
||
|
|
| Column | Type | Notes |
|
||
|
|
|--------|------|-------|
|
||
|
|
| RefNumID | INT | PK |
|
||
|
|
| TestSiteID | INT | Which test |
|
||
|
|
| Sex | VARCHAR | M/F/ALL |
|
||
|
|
| AgeStart/AgeEnd | INT | Age criteria |
|
||
|
|
| Low/High | DECIMAL | Range values |
|
||
|
|
| LowSign/HighSign | VARCHAR | <=, <, etc |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Endpoints Summary
|
||
|
|
|
||
|
|
| Endpoint | Method | Auth | Description |
|
||
|
|
|----------|--------|------|-------------|
|
||
|
|
| `/api/results` | GET | Yes | List results (filter: ?order_id= or ?patient_id=) |
|
||
|
|
| `/api/results/{id}` | GET | Yes | Get single result |
|
||
|
|
| `/api/results/{id}` | PATCH | Yes | Update result + auto-flag |
|
||
|
|
| `/api/results/{id}` | DELETE | Yes | Soft delete result |
|
||
|
|
| `/api/reports/{orderID}` | GET | Yes | Generate PDF report |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Flag Logic (Reference Range Validation)
|
||
|
|
|
||
|
|
```php
|
||
|
|
// Pseudo-code for validation
|
||
|
|
function validateResult($resultValue, $refNumID) {
|
||
|
|
$ref = getRefNum($refNumID);
|
||
|
|
$patient = getPatientFromOrder($orderID);
|
||
|
|
|
||
|
|
// Match criteria (sex, age)
|
||
|
|
if (!matchesCriteria($ref, $patient)) {
|
||
|
|
return null; // No flag if criteria don't match
|
||
|
|
}
|
||
|
|
|
||
|
|
$value = floatval($resultValue);
|
||
|
|
$low = floatval($ref['Low']);
|
||
|
|
$high = floatval($ref['High']);
|
||
|
|
|
||
|
|
// Check low
|
||
|
|
if ($ref['LowSign'] === '<=' && $value <= $low) return 'L';
|
||
|
|
if ($ref['LowSign'] === '<' && $value < $low) return 'L';
|
||
|
|
|
||
|
|
// Check high
|
||
|
|
if ($ref['HighSign'] === '>=' && $value >= $high) return 'H';
|
||
|
|
if ($ref['HighSign'] === '>' && $value > $high) return 'H';
|
||
|
|
|
||
|
|
return null; // Normal
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Out of Scope (Post-MVP)
|
||
|
|
|
||
|
|
- **Edge API**: Instrument integration (`app/Controllers/EdgeController.php`)
|
||
|
|
- **Worklist Generation**: Technician worklists
|
||
|
|
- **Calculated Tests**: Formula execution (CALC type)
|
||
|
|
- **Quality Control**: QC samples, Levy-Jennings charts
|
||
|
|
- **Calculated Test Execution**: Deferred to later
|
||
|
|
- **Delta Checking**: Result trending
|
||
|
|
- **Critical Result Alerts**: Notification system
|
||
|
|
- **Audit Trail**: Complete audit logging
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Success Criteria
|
||
|
|
|
||
|
|
- [ ] Can enter result values via API
|
||
|
|
- [ ] Results auto-validate against refnum ranges
|
||
|
|
- [ ] Abnormal results show L/H flags
|
||
|
|
- [ ] Can view all results for an order
|
||
|
|
- [ ] Can generate PDF lab report
|
||
|
|
- [ ] Report shows patient, order, results with flags
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Files to Create/Modify
|
||
|
|
|
||
|
|
### Create
|
||
|
|
1. `app/Controllers/ReportController.php` - HTML report controller
|
||
|
|
2. `app/Views/reports/lab_report.php` - HTML report template with dynamic flag calculation
|
||
|
|
3. `app/Database/Migrations/2026-03-04-073950_RemoveFlagColumnFromPatRes.php` - Remove Flag column
|
||
|
|
4. `public/paths/reports.yaml` - OpenAPI documentation for reports endpoint
|
||
|
|
|
||
|
|
### Modify
|
||
|
|
1. `app/Models/PatResultModel.php` - Add validation methods (validateAndFlag, getByOrder, getByPatient, updateWithValidation, getWithRelations, softDelete)
|
||
|
|
2. `app/Controllers/ResultController.php` - Full CRUD (index, show, update, delete)
|
||
|
|
3. `app/Config/Routes.php` - New routes for results and reports
|
||
|
|
4. `public/paths/results.yaml` - Updated OpenAPI documentation
|
||
|
|
5. `public/api-docs.yaml` - Added Reports tag
|
||
|
|
6. Regenerated `public/api-docs.bundled.yaml`
|
||
|
|
|
||
|
|
**Note**: Flag is calculated dynamically at runtime, not stored in database. This allows for:
|
||
|
|
- Real-time validation against current reference ranges
|
||
|
|
- Support for reference range updates without re-processing historical results
|
||
|
|
- Reduced storage requirements
|
||
|
|
|
||
|
|
**API Documentation**: Remember to run `node public/bundle-api-docs.js` after updating YAML files to regenerate the bundled documentation.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Last Updated: 2026-03-04*
|
||
|
|
*Sprint Duration: 2 weeks*
|
||
|
|
*Team Size: 1 developer*
|