942 lines
22 KiB
Markdown
942 lines
22 KiB
Markdown
|
|
# Manual Result Entry Implementation Plan
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This document outlines the implementation plan for manual laboratory result entry functionality in CLQMS. The system already creates empty `patres` records when orders are placed. This plan covers the complete workflow for entering, validating, and verifying test results.
|
||
|
|
|
||
|
|
**Current State:** Empty `patres` records exist for all ordered tests
|
||
|
|
**Target State:** Full result entry with reference range validation, abnormal flag calculation, and verification workflow
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 1: Core Result Management (Priority: HIGH)
|
||
|
|
|
||
|
|
### 1.1 Extend PatResultModel
|
||
|
|
|
||
|
|
**File:** `app/Models/PatResultModel.php`
|
||
|
|
|
||
|
|
#### New Methods to Add:
|
||
|
|
|
||
|
|
```php
|
||
|
|
/**
|
||
|
|
* Get results with filtering and pagination
|
||
|
|
*
|
||
|
|
* @param array $filters Available filters:
|
||
|
|
* - InternalPID: int - Filter by patient
|
||
|
|
* - OrderID: string - Filter by order
|
||
|
|
* - ResultStatus: string - PEN, PRE, FIN, AMD
|
||
|
|
* - TestSiteID: int - Filter by test
|
||
|
|
* - date_from: string - YYYY-MM-DD
|
||
|
|
* - date_to: string - YYYY-MM-DD
|
||
|
|
* - WorkstationID: int - Filter by workstation
|
||
|
|
* @param int $page
|
||
|
|
* @param int $perPage
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getResults(array $filters = [], int $page = 1, int $perPage = 20): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get single result with full details
|
||
|
|
* Includes: patient demographics, test info, specimen info, reference ranges
|
||
|
|
*
|
||
|
|
* @param int $resultID
|
||
|
|
* @return array|null
|
||
|
|
*/
|
||
|
|
public function getResultWithDetails(int $resultID): ?array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update result with validation
|
||
|
|
*
|
||
|
|
* @param int $resultID
|
||
|
|
* @param array $data
|
||
|
|
* - Result: string - The result value
|
||
|
|
* - Unit: string - Unit of measurement (optional)
|
||
|
|
* - AbnormalFlag: string - H, L, N, A, C (optional, auto-calculated)
|
||
|
|
* - Comment: string - Result comment (optional)
|
||
|
|
* - ResultStatus: string - Status update (optional)
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function updateResult(int $resultID, array $data): bool
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get pending results for a workstation (worklist)
|
||
|
|
*
|
||
|
|
* @param int $workstationID
|
||
|
|
* @param array $filters Additional filters
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getPendingByWorkstation(int $workstationID, array $filters = []): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all results for an order
|
||
|
|
*
|
||
|
|
* @param string $orderID
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getByOrder(string $orderID): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Verify a result
|
||
|
|
*
|
||
|
|
* @param int $resultID
|
||
|
|
* @param int $userID - ID of verifying user
|
||
|
|
* @param string|null $comment - Optional verification comment
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function verifyResult(int $resultID, int $userID, ?string $comment = null): bool
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Unverify a result (amendment)
|
||
|
|
*
|
||
|
|
* @param int $resultID
|
||
|
|
* @param int $userID - ID of user amending
|
||
|
|
* @param string $reason - Required reason for amendment
|
||
|
|
* @return bool
|
||
|
|
*/
|
||
|
|
public function unverifyResult(int $resultID, int $userID, string $reason): bool
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Fields to Add to `$allowedFields`:
|
||
|
|
|
||
|
|
```php
|
||
|
|
protected $allowedFields = [
|
||
|
|
'SiteID',
|
||
|
|
'OrderID',
|
||
|
|
'InternalSID',
|
||
|
|
'SID',
|
||
|
|
'SampleID',
|
||
|
|
'TestSiteID',
|
||
|
|
'TestSiteCode',
|
||
|
|
'AspCnt',
|
||
|
|
'Result',
|
||
|
|
'Unit', // NEW
|
||
|
|
'SampleType',
|
||
|
|
'ResultDateTime',
|
||
|
|
'WorkstationID',
|
||
|
|
'EquipmentID',
|
||
|
|
'RefNumID',
|
||
|
|
'RefTxtID',
|
||
|
|
'ResultStatus', // NEW: PEN, PRE, FIN, AMD
|
||
|
|
'Verified', // NEW: boolean
|
||
|
|
'VerifiedBy', // NEW: user ID
|
||
|
|
'VerifiedDate', // NEW: datetime
|
||
|
|
'EnteredBy', // NEW: user ID
|
||
|
|
'AbnormalFlag', // NEW: H, L, N, A, C
|
||
|
|
'Comment', // NEW
|
||
|
|
'CreateDate',
|
||
|
|
'EndDate',
|
||
|
|
'ArchiveDate',
|
||
|
|
'DelDate'
|
||
|
|
];
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 1.2 Create ResultEntryService
|
||
|
|
|
||
|
|
**File:** `app/Libraries/ResultEntryService.php`
|
||
|
|
|
||
|
|
This service handles all business logic for result entry.
|
||
|
|
|
||
|
|
```php
|
||
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Libraries;
|
||
|
|
|
||
|
|
use App\Models\PatResultModel;
|
||
|
|
use App\Models\Patient\PatientModel;
|
||
|
|
use App\Models\RefRange\RefNumModel;
|
||
|
|
use App\Models\RefRange\RefTxtModel;
|
||
|
|
use App\Models\Test\TestDefSiteModel;
|
||
|
|
|
||
|
|
class ResultEntryService
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Validate result value based on test type
|
||
|
|
*
|
||
|
|
* @param string $value
|
||
|
|
* @param int $testSiteID
|
||
|
|
* @return array ['valid' => bool, 'error' => string|null]
|
||
|
|
*/
|
||
|
|
public function validateResult(string $value, int $testSiteID): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find applicable reference range
|
||
|
|
*
|
||
|
|
* @param int $testSiteID
|
||
|
|
* @param array $patient Demographics: age (months), sex, specimenType
|
||
|
|
* @return array|null Reference range data
|
||
|
|
*/
|
||
|
|
public function getApplicableRange(int $testSiteID, array $patient): ?array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Calculate abnormal flag based on value and range
|
||
|
|
*
|
||
|
|
* @param string|float $value
|
||
|
|
* @param array $range Reference range data
|
||
|
|
* @return string H, L, N, A, or C
|
||
|
|
*/
|
||
|
|
public function calculateAbnormalFlag($value, array $range): string
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format reference range for display
|
||
|
|
*
|
||
|
|
* @param array $range
|
||
|
|
* @return string Human-readable range (e.g., "10.0 - 20.0 mg/dL")
|
||
|
|
*/
|
||
|
|
public function formatDisplayRange(array $range): string
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check delta (compare with previous result)
|
||
|
|
*
|
||
|
|
* @param int $resultID Current result being edited
|
||
|
|
* @param string|float $newValue
|
||
|
|
* @return array ['hasPrevious' => bool, 'previousValue' => string|null, 'deltaPercent' => float|null, 'significant' => bool]
|
||
|
|
*/
|
||
|
|
public function checkDelta(int $resultID, $newValue): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Process result entry
|
||
|
|
*
|
||
|
|
* @param int $resultID
|
||
|
|
* @param array $data
|
||
|
|
* @param int $userID User entering the result
|
||
|
|
* @return array ['success' => bool, 'result' => array|null, 'errors' => array]
|
||
|
|
*/
|
||
|
|
public function processEntry(int $resultID, array $data, int $userID): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update calculated tests after dependency changes
|
||
|
|
*
|
||
|
|
* @param string $orderID
|
||
|
|
* @param int $userID
|
||
|
|
* @return int Number of calculated results updated
|
||
|
|
*/
|
||
|
|
public function recalculateDependentResults(string $orderID, int $userID): int
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get worklist for workstation
|
||
|
|
*
|
||
|
|
* @param int $workstationID
|
||
|
|
* @param array $filters
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getWorklist(int $workstationID, array $filters = []): array
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 1.3 Implement ResultController
|
||
|
|
|
||
|
|
**File:** `app/Controllers/ResultController.php`
|
||
|
|
|
||
|
|
Replace the placeholder controller with full implementation:
|
||
|
|
|
||
|
|
```php
|
||
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Controllers;
|
||
|
|
|
||
|
|
use App\Traits\ResponseTrait;
|
||
|
|
use App\Libraries\ResultEntryService;
|
||
|
|
use App\Models\PatResultModel;
|
||
|
|
use CodeIgniter\Controller;
|
||
|
|
|
||
|
|
class ResultController extends Controller
|
||
|
|
{
|
||
|
|
use ResponseTrait;
|
||
|
|
|
||
|
|
protected $resultModel;
|
||
|
|
protected $entryService;
|
||
|
|
|
||
|
|
public function __construct()
|
||
|
|
{
|
||
|
|
$this->resultModel = new PatResultModel();
|
||
|
|
$this->entryService = new ResultEntryService();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET /api/results
|
||
|
|
* List results with filtering
|
||
|
|
*/
|
||
|
|
public function index()
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET /api/results/{id}
|
||
|
|
* Get single result with details
|
||
|
|
*/
|
||
|
|
public function show($id = null)
|
||
|
|
|
||
|
|
/**
|
||
|
|
* PATCH /api/results/{id}
|
||
|
|
* Update result value
|
||
|
|
*/
|
||
|
|
public function update($id = null)
|
||
|
|
|
||
|
|
/**
|
||
|
|
* POST /api/results/batch
|
||
|
|
* Batch update multiple results
|
||
|
|
*/
|
||
|
|
public function batchUpdate()
|
||
|
|
|
||
|
|
/**
|
||
|
|
* POST /api/results/{id}/verify
|
||
|
|
* Verify a result
|
||
|
|
*/
|
||
|
|
public function verify($id = null)
|
||
|
|
|
||
|
|
/**
|
||
|
|
* POST /api/results/{id}/unverify
|
||
|
|
* Unverify/amend a result
|
||
|
|
*/
|
||
|
|
public function unverify($id = null)
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET /api/results/worklist
|
||
|
|
* Get pending results for workstation
|
||
|
|
*/
|
||
|
|
public function worklist()
|
||
|
|
|
||
|
|
/**
|
||
|
|
* GET /api/results/order/{orderID}
|
||
|
|
* Get all results for an order
|
||
|
|
*/
|
||
|
|
public function byOrder($orderID = null)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 2: Database Schema (Priority: HIGH)
|
||
|
|
|
||
|
|
### 2.1 Migration for New Fields
|
||
|
|
|
||
|
|
**File:** `app/Database/Migrations/2025-03-04-000001_AddResultFields.php`
|
||
|
|
|
||
|
|
```php
|
||
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Database\Migrations;
|
||
|
|
|
||
|
|
use CodeIgniter\Database\Migration;
|
||
|
|
|
||
|
|
class AddResultFields extends Migration
|
||
|
|
{
|
||
|
|
public function up()
|
||
|
|
{
|
||
|
|
$this->forge->addColumn('patres', [
|
||
|
|
'ResultStatus' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 10,
|
||
|
|
'null' => true,
|
||
|
|
'comment' => 'PEN=Pending, PRE=Preliminary, FIN=Final, AMD=Amended',
|
||
|
|
'after' => 'RefTxtID'
|
||
|
|
],
|
||
|
|
'Verified' => [
|
||
|
|
'type' => 'TINYINT',
|
||
|
|
'constraint' => 1,
|
||
|
|
'default' => 0,
|
||
|
|
'after' => 'ResultStatus'
|
||
|
|
],
|
||
|
|
'VerifiedBy' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true,
|
||
|
|
'null' => true,
|
||
|
|
'after' => 'Verified'
|
||
|
|
],
|
||
|
|
'VerifiedDate' => [
|
||
|
|
'type' => 'DATETIME',
|
||
|
|
'null' => true,
|
||
|
|
'after' => 'VerifiedBy'
|
||
|
|
],
|
||
|
|
'EnteredBy' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true,
|
||
|
|
'null' => true,
|
||
|
|
'after' => 'VerifiedDate'
|
||
|
|
],
|
||
|
|
'AbnormalFlag' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 1,
|
||
|
|
'null' => true,
|
||
|
|
'comment' => 'H=High, L=Low, N=Normal, A=Abnormal, C=Critical',
|
||
|
|
'after' => 'EnteredBy'
|
||
|
|
],
|
||
|
|
'Comment' => [
|
||
|
|
'type' => 'TEXT',
|
||
|
|
'null' => true,
|
||
|
|
'after' => 'AbnormalFlag'
|
||
|
|
],
|
||
|
|
'Unit' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 50,
|
||
|
|
'null' => true,
|
||
|
|
'after' => 'Result'
|
||
|
|
]
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function down()
|
||
|
|
{
|
||
|
|
$this->forge->dropColumn('patres', [
|
||
|
|
'ResultStatus',
|
||
|
|
'Verified',
|
||
|
|
'VerifiedBy',
|
||
|
|
'VerifiedDate',
|
||
|
|
'EnteredBy',
|
||
|
|
'AbnormalFlag',
|
||
|
|
'Comment',
|
||
|
|
'Unit'
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2.2 Create Result History Table (Audit Trail)
|
||
|
|
|
||
|
|
**File:** `app/Database/Migrations/2025-03-04-000002_CreatePatResHistory.php`
|
||
|
|
|
||
|
|
```php
|
||
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Database\Migrations;
|
||
|
|
|
||
|
|
use CodeIgniter\Database\Migration;
|
||
|
|
|
||
|
|
class CreatePatResHistory extends Migration
|
||
|
|
{
|
||
|
|
public function up()
|
||
|
|
{
|
||
|
|
$this->forge->addField([
|
||
|
|
'HistoryID' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true,
|
||
|
|
'auto_increment' => true
|
||
|
|
],
|
||
|
|
'ResultID' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true
|
||
|
|
],
|
||
|
|
'OrderID' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true
|
||
|
|
],
|
||
|
|
'TestSiteID' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true
|
||
|
|
],
|
||
|
|
'OldResult' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 255,
|
||
|
|
'null' => true
|
||
|
|
],
|
||
|
|
'NewResult' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 255,
|
||
|
|
'null' => true
|
||
|
|
],
|
||
|
|
'OldStatus' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 10,
|
||
|
|
'null' => true
|
||
|
|
],
|
||
|
|
'NewStatus' => [
|
||
|
|
'type' => 'VARCHAR',
|
||
|
|
'constraint' => 10,
|
||
|
|
'null' => true
|
||
|
|
],
|
||
|
|
'ChangedBy' => [
|
||
|
|
'type' => 'INT',
|
||
|
|
'constraint' => 11,
|
||
|
|
'unsigned' => true
|
||
|
|
],
|
||
|
|
'ChangeReason' => [
|
||
|
|
'type' => 'TEXT',
|
||
|
|
'null' => true
|
||
|
|
],
|
||
|
|
'CreateDate' => [
|
||
|
|
'type' => 'DATETIME',
|
||
|
|
'null' => false
|
||
|
|
]
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->forge->addKey('HistoryID', true);
|
||
|
|
$this->forge->addKey('ResultID');
|
||
|
|
$this->forge->addKey('CreateDate');
|
||
|
|
$this->forge->createTable('patreshistory');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function down()
|
||
|
|
{
|
||
|
|
$this->forge->dropTable('patreshistory');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 3: API Routes (Priority: HIGH)
|
||
|
|
|
||
|
|
### 3.1 Update Routes.php
|
||
|
|
|
||
|
|
Add to `app/Config/Routes.php` within the existing `api` group:
|
||
|
|
|
||
|
|
```php
|
||
|
|
// Results
|
||
|
|
$routes->group('results', function ($routes) {
|
||
|
|
$routes->get('/', 'ResultController::index');
|
||
|
|
$routes->get('worklist', 'ResultController::worklist');
|
||
|
|
$routes->get('order/(:any)', 'ResultController::byOrder/$1');
|
||
|
|
$routes->get('(:num)', 'ResultController::show/$1');
|
||
|
|
$routes->patch('(:num)', 'ResultController::update/$1');
|
||
|
|
$routes->post('batch', 'ResultController::batchUpdate');
|
||
|
|
$routes->post('(:num)/verify', 'ResultController::verify/$1');
|
||
|
|
$routes->post('(:num)/unverify', 'ResultController::unverify/$1');
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 4: API Documentation (Priority: MEDIUM)
|
||
|
|
|
||
|
|
### 4.1 Create Results Schema
|
||
|
|
|
||
|
|
**File:** `public/components/schemas/results.yaml`
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
Result:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
ResultID:
|
||
|
|
type: integer
|
||
|
|
OrderID:
|
||
|
|
type: integer
|
||
|
|
InternalSID:
|
||
|
|
type: integer
|
||
|
|
nullable: true
|
||
|
|
TestSiteID:
|
||
|
|
type: integer
|
||
|
|
TestSiteCode:
|
||
|
|
type: string
|
||
|
|
TestSiteName:
|
||
|
|
type: string
|
||
|
|
nullable: true
|
||
|
|
SID:
|
||
|
|
type: string
|
||
|
|
SampleID:
|
||
|
|
type: string
|
||
|
|
Result:
|
||
|
|
type: string
|
||
|
|
nullable: true
|
||
|
|
Unit:
|
||
|
|
type: string
|
||
|
|
nullable: true
|
||
|
|
ResultStatus:
|
||
|
|
type: string
|
||
|
|
enum: [PEN, PRE, FIN, AMD]
|
||
|
|
nullable: true
|
||
|
|
Verified:
|
||
|
|
type: boolean
|
||
|
|
default: false
|
||
|
|
VerifiedBy:
|
||
|
|
type: integer
|
||
|
|
nullable: true
|
||
|
|
VerifiedDate:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
nullable: true
|
||
|
|
EnteredBy:
|
||
|
|
type: integer
|
||
|
|
nullable: true
|
||
|
|
AbnormalFlag:
|
||
|
|
type: string
|
||
|
|
enum: [H, L, N, A, C]
|
||
|
|
nullable: true
|
||
|
|
Comment:
|
||
|
|
type: string
|
||
|
|
nullable: true
|
||
|
|
CreateDate:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
ReferenceRange:
|
||
|
|
type: object
|
||
|
|
nullable: true
|
||
|
|
properties:
|
||
|
|
Low:
|
||
|
|
type: number
|
||
|
|
High:
|
||
|
|
type: number
|
||
|
|
Display:
|
||
|
|
type: string
|
||
|
|
Patient:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
InternalPID:
|
||
|
|
type: integer
|
||
|
|
PatientID:
|
||
|
|
type: string
|
||
|
|
NameFirst:
|
||
|
|
type: string
|
||
|
|
NameLast:
|
||
|
|
type: string
|
||
|
|
Birthdate:
|
||
|
|
type: string
|
||
|
|
format: date
|
||
|
|
Sex:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
ResultEntryRequest:
|
||
|
|
type: object
|
||
|
|
required:
|
||
|
|
- Result
|
||
|
|
properties:
|
||
|
|
Result:
|
||
|
|
type: string
|
||
|
|
description: The result value
|
||
|
|
Unit:
|
||
|
|
type: string
|
||
|
|
description: Unit override (optional)
|
||
|
|
AbnormalFlag:
|
||
|
|
type: string
|
||
|
|
enum: [H, L, N, A, C]
|
||
|
|
description: Override auto-calculated flag (optional)
|
||
|
|
Comment:
|
||
|
|
type: string
|
||
|
|
description: Result comment
|
||
|
|
ResultStatus:
|
||
|
|
type: string
|
||
|
|
enum: [PEN, PRE, FIN]
|
||
|
|
description: Set status (can't set to AMD via update)
|
||
|
|
|
||
|
|
ResultBatchRequest:
|
||
|
|
type: object
|
||
|
|
required:
|
||
|
|
- results
|
||
|
|
properties:
|
||
|
|
results:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
ResultID:
|
||
|
|
type: integer
|
||
|
|
Result:
|
||
|
|
type: string
|
||
|
|
Unit:
|
||
|
|
type: string
|
||
|
|
Comment:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
ResultVerifyRequest:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
comment:
|
||
|
|
type: string
|
||
|
|
description: Optional verification comment
|
||
|
|
|
||
|
|
ResultUnverifyRequest:
|
||
|
|
type: object
|
||
|
|
required:
|
||
|
|
- reason
|
||
|
|
properties:
|
||
|
|
reason:
|
||
|
|
type: string
|
||
|
|
description: Required reason for amendment
|
||
|
|
|
||
|
|
ResultWorklistResponse:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
status:
|
||
|
|
type: string
|
||
|
|
message:
|
||
|
|
type: string
|
||
|
|
data:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
ResultID:
|
||
|
|
type: integer
|
||
|
|
PatientName:
|
||
|
|
type: string
|
||
|
|
PatientID:
|
||
|
|
type: string
|
||
|
|
OrderID:
|
||
|
|
type: string
|
||
|
|
TestCode:
|
||
|
|
type: string
|
||
|
|
TestName:
|
||
|
|
type: string
|
||
|
|
ResultStatus:
|
||
|
|
type: string
|
||
|
|
Priority:
|
||
|
|
type: string
|
||
|
|
OrderDate:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 Create API Paths Documentation
|
||
|
|
|
||
|
|
**File:** `public/paths/results.yaml`
|
||
|
|
|
||
|
|
Document all endpoints (GET /api/results, GET /api/results/{id}, PATCH /api/results/{id}, POST /api/results/batch, POST /api/results/{id}/verify, POST /api/results/{id}/unverify, GET /api/results/worklist, GET /api/results/order/{orderID}) with:
|
||
|
|
- Summary and description
|
||
|
|
- Security (bearerAuth)
|
||
|
|
- Parameters (path, query)
|
||
|
|
- RequestBody schemas
|
||
|
|
- Response schemas
|
||
|
|
- Error responses
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 5: Reference Range Integration (Priority: MEDIUM)
|
||
|
|
|
||
|
|
### 5.1 Create RefRangeService
|
||
|
|
|
||
|
|
**File:** `app/Libraries/RefRangeService.php`
|
||
|
|
|
||
|
|
```php
|
||
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Libraries;
|
||
|
|
|
||
|
|
use App\Models\RefRange\RefNumModel;
|
||
|
|
use App\Models\RefRange\RefTxtModel;
|
||
|
|
|
||
|
|
class RefRangeService
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Get applicable reference range for a test and patient
|
||
|
|
*
|
||
|
|
* @param int $testSiteID
|
||
|
|
* @param array $patient Contains: age (in months), sex (M/F), specimenType
|
||
|
|
* @return array|null
|
||
|
|
*/
|
||
|
|
public function getApplicableRange(int $testSiteID, array $patient): ?array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Evaluate numeric result against range
|
||
|
|
*
|
||
|
|
* @param float $value
|
||
|
|
* @param array $range
|
||
|
|
* @return string H, L, N, A, C
|
||
|
|
*/
|
||
|
|
public function evaluateNumeric(float $value, array $range): string
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get text reference options
|
||
|
|
*
|
||
|
|
* @param int $refTxtID
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
public function getTextOptions(int $refTxtID): array
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Format range for display
|
||
|
|
*
|
||
|
|
* @param array $range
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
public function formatDisplay(array $range): string
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Phase 6: Testing (Priority: MEDIUM)
|
||
|
|
|
||
|
|
### 6.1 Create Feature Tests
|
||
|
|
|
||
|
|
**File:** `tests/feature/Results/ResultEntryTest.php`
|
||
|
|
|
||
|
|
Test scenarios:
|
||
|
|
- Get results list with filters
|
||
|
|
- Get single result with details
|
||
|
|
- Update result value
|
||
|
|
- Validation errors (invalid value)
|
||
|
|
- Auto-calculate abnormal flag
|
||
|
|
- Delta check notification
|
||
|
|
- Batch update
|
||
|
|
|
||
|
|
**File:** `tests/feature/Results/ResultVerifyTest.php`
|
||
|
|
|
||
|
|
Test scenarios:
|
||
|
|
- Verify result successfully
|
||
|
|
- Unverify with reason
|
||
|
|
- Attempt to modify verified result
|
||
|
|
- Permission checks
|
||
|
|
- Amendment workflow
|
||
|
|
|
||
|
|
**File:** `tests/feature/Results/ResultWorklistTest.php`
|
||
|
|
|
||
|
|
Test scenarios:
|
||
|
|
- Get worklist by workstation
|
||
|
|
- Filter by priority
|
||
|
|
- Sort by order date
|
||
|
|
- Pagination
|
||
|
|
|
||
|
|
### 6.2 Create Unit Tests
|
||
|
|
|
||
|
|
**File:** `tests/unit/Libraries/ResultEntryServiceTest.php`
|
||
|
|
|
||
|
|
Test scenarios:
|
||
|
|
- Result validation
|
||
|
|
- Reference range matching
|
||
|
|
- Abnormal flag calculation
|
||
|
|
- Delta calculation
|
||
|
|
- Calculated test formulas
|
||
|
|
|
||
|
|
**File:** `tests/unit/Libraries/RefRangeServiceTest.php`
|
||
|
|
|
||
|
|
Test scenarios:
|
||
|
|
- Age-based range selection
|
||
|
|
- Sex-based range selection
|
||
|
|
- Specimen type matching
|
||
|
|
- Boundary value evaluation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Flow Diagram
|
||
|
|
|
||
|
|
```
|
||
|
|
Order Creation
|
||
|
|
|
|
||
|
|
v
|
||
|
|
Empty patres records created
|
||
|
|
|
|
||
|
|
v
|
||
|
|
GET /api/results/worklist <-- Technician sees pending results
|
||
|
|
|
|
||
|
|
v
|
||
|
|
GET /api/results/{id} <-- Load result with patient info
|
||
|
|
|
|
||
|
|
v
|
||
|
|
PATCH /api/results/{id} <-- Enter result value
|
||
|
|
| |
|
||
|
|
| v
|
||
|
|
| ResultEntryService.validateResult()
|
||
|
|
| |
|
||
|
|
| v
|
||
|
|
| RefRangeService.getApplicableRange()
|
||
|
|
| |
|
||
|
|
| v
|
||
|
|
| Auto-calculate AbnormalFlag
|
||
|
|
| |
|
||
|
|
v v
|
||
|
|
Result updated in patres
|
||
|
|
|
|
||
|
|
v
|
||
|
|
POST /api/results/{id}/verify <-- Senior tech/pathologist
|
||
|
|
|
|
||
|
|
v
|
||
|
|
ResultStatus = FIN, Verified = true
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Status Definitions
|
||
|
|
|
||
|
|
| Status | Code | Description |
|
||
|
|
|--------|------|-------------|
|
||
|
|
| Pending | PEN | Order created, awaiting result entry |
|
||
|
|
| Preliminary | PRE | Result entered but not verified |
|
||
|
|
| Final | FIN | Result verified by authorized user |
|
||
|
|
| Amended | AMD | Previously final result modified |
|
||
|
|
|
||
|
|
## Abnormal Flag Definitions
|
||
|
|
|
||
|
|
| Flag | Meaning | Action Required |
|
||
|
|
|------|---------|-----------------|
|
||
|
|
| N | Normal | None |
|
||
|
|
| H | High | Review |
|
||
|
|
| L | Low | Review |
|
||
|
|
| A | Abnormal (text) | Review |
|
||
|
|
| C | Critical | Immediate notification |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Questions for Implementation
|
||
|
|
|
||
|
|
Before starting implementation, clarify:
|
||
|
|
|
||
|
|
1. **Who can verify results?**
|
||
|
|
- Option A: Any authenticated user
|
||
|
|
- Option B: Users with specific role (senior tech, pathologist)
|
||
|
|
- Option C: Configure per test/discipline
|
||
|
|
|
||
|
|
2. **Can calculated tests be manually edited?**
|
||
|
|
- Option A: No, always auto-computed
|
||
|
|
- Option B: Yes, allow override with reason
|
||
|
|
- Option C: Configurable per test
|
||
|
|
|
||
|
|
3. **Audit trail requirements:**
|
||
|
|
- Option A: Full history (every change)
|
||
|
|
- Option B: Only amendments (verified→unverify→verify)
|
||
|
|
- Option C: No audit trail needed
|
||
|
|
|
||
|
|
4. **Critical results handling:**
|
||
|
|
- Option A: Flag only
|
||
|
|
- Option B: Flag + notification system
|
||
|
|
- Option C: Flag + mandatory acknowledgment
|
||
|
|
|
||
|
|
5. **Batch entry priority:**
|
||
|
|
- Must-have or nice-to-have?
|
||
|
|
- Support for templates/predefined sets?
|
||
|
|
|
||
|
|
6. **Delta check sensitivity:**
|
||
|
|
- Fixed percentage threshold (e.g., 20%)?
|
||
|
|
- Test-specific thresholds?
|
||
|
|
- Configurable?
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Checklist
|
||
|
|
|
||
|
|
- [ ] Phase 1.1: Extend PatResultModel with CRUD methods
|
||
|
|
- [ ] Phase 1.2: Create ResultEntryService with business logic
|
||
|
|
- [ ] Phase 1.3: Implement ResultController methods
|
||
|
|
- [ ] Phase 2.1: Create migration for new patres fields
|
||
|
|
- [ ] Phase 2.2: Create patreshistory table
|
||
|
|
- [ ] Phase 3.1: Add routes to Routes.php
|
||
|
|
- [ ] Phase 4.1: Create results.yaml schema
|
||
|
|
- [ ] Phase 4.2: Create results.yaml paths documentation
|
||
|
|
- [ ] Phase 4.3: Run `node public/bundle-api-docs.js`
|
||
|
|
- [ ] Phase 5.1: Create RefRangeService
|
||
|
|
- [ ] Phase 6.1: Create feature tests
|
||
|
|
- [ ] Phase 6.2: Create unit tests
|
||
|
|
- [ ] Run full test suite: `./vendor/bin/phpunit`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Estimated Timeline
|
||
|
|
|
||
|
|
| Phase | Duration | Priority |
|
||
|
|
|-------|----------|----------|
|
||
|
|
| Phase 1: Core Management | 2-3 days | HIGH |
|
||
|
|
| Phase 2: Database Schema | 0.5 day | HIGH |
|
||
|
|
| Phase 3: API Routes | 0.5 day | HIGH |
|
||
|
|
| Phase 4: Documentation | 1 day | MEDIUM |
|
||
|
|
| Phase 5: Reference Ranges | 1-2 days | MEDIUM |
|
||
|
|
| Phase 6: Testing | 2-3 days | MEDIUM |
|
||
|
|
| **Total** | **7-10 days** | |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Notes
|
||
|
|
|
||
|
|
- All dates stored in UTC, convert to local time for display
|
||
|
|
- Use transactions for all multi-table operations
|
||
|
|
- Follow existing code style (camelCase methods, snake_case properties)
|
||
|
|
- Update AGENTS.md if adding new commands or patterns
|
||
|
|
- Consider performance: worklist queries should be fast (< 500ms)
|
||
|
|
|
||
|
|
*Last Updated: 2025-03-04*
|