refactor(valueset): simplify API response by removing pagination

- Remove pagination from ValueSetController::index() and ValueSetModel::getValueSets()
     - Delete duplicate AGENTS.md documentation (consolidated into CLAUDE.md)
     - Update .gitignore to exclude .claude folder
     - Add CLAUDE.md with comprehensive agent instructions for Valueset queries
     - Document new Lookups static library in README.md
This commit is contained in:
mahdahar 2026-01-09 16:58:43 +07:00
parent d4029dce38
commit f11bde4d30
9 changed files with 1177 additions and 108 deletions

12
.factory/config.json Normal file
View File

@ -0,0 +1,12 @@
{
"custom_models": [
{
"model_display_name": "MiniMax-M2.1",
"model": "MiniMax-M2.1",
"base_url": "https://api.minimax.io/anthropic",
"api_key": "sk-cp-eMsvq_OqP6UiCBirrr3W6gZlG6-NXnIQeneGNpAJ8aWxywzNq5I9mibfQFBBy84C2Mm7jCqMtjKmbpnx6h02nz_D7xG6ETmBY4K6Nog454cYs_ZkYgMyG_g",
"provider": "anthropic",
"max_tokens": 64000
}
]
}

5
.gitignore vendored
View File

@ -125,3 +125,8 @@ _modules/*
/results/
/phpunit*.xml
/public/.htaccess
#-------------------------
# Claude
#-------------------------
.claude

View File

@ -1,86 +0,0 @@
# Valueset Data Scanning - Agent Instructions
## Quick Reference
**Database Tables:**
- [`valuesetdef`](app/Models/ValueSet/ValueSetDefModel.php:8) - Category definitions (VSetID, VSName, VSDesc)
- [`valueset`](app/Models/ValueSet/ValueSetModel.php:8) - Actual values (VID, VSetID, VValue, VDesc, VOrder)
## Agent Workflow for Valueset Queries
### Step 1: Identify the Request Type
**Category Search:**
```
User: "Show me all values in [CATEGORY_NAME]"
Agent: Search valuesetdef by VSName → Get VSetID → Query values by VSetID
```
**Reference Search:**
```
User: "Show me values for [TABLE.COLUMN]"
Agent: Search valuesetdef by VSDesc → Get VSetID → Query values by VSetID
```
**ID Search:**
```
User: "Show me values for VSetDefID [ID]"
Agent: Directly query values by VSetID
```
**Filtered Search:**
```
User: "Find values containing [TERM] in [CATEGORY]"
Agent: Search valuesetdef by VSName → Get VSetID → Query values with LIKE filter
```
### Step 2: Execute Query
**Model Methods:**
- [`ValueSetDefModel::getValueSetDefs($param)`](app/Models/ValueSet/ValueSetDefModel.php:18) - Search categories
- [`ValueSetModel::getValueSetByValueSetDef($VSetID)`](app/Models/ValueSet/ValueSetModel.php:52) - Get values by category
- [`ValueSetModel::getValueSets($param, $page, $limit, $VSetID)`](app/Models/ValueSet/ValueSetModel.php:18) - Get values with filters
### Step 3: Return Results
**Response Format:**
```json
{
"VSetID": 27,
"VSName": "Test Type",
"values": [
{ "VID": 1, "VValue": "TEST", "VDesc": "Test", "VOrder": 1 },
{ "VID": 2, "VValue": "PARAM", "VDesc": "Parameter", "VOrder": 2 }
]
}
```
## Common Valuesets
| VSetDefID | VSName | VSDesc | Search Keywords |
|-----------|--------|--------|-----------------|
| 3 | Gender | - | gender, sex |
| 27 | Test Type | `testdefsite.TestType` | test, type, testdefsite |
| 15 | Specimen Type | - | specimen, type, blood, urine |
| 31 | Range Types | `refnum.RangeType` | range, refnum |
| 46 | Num Ref Type | `refnum.NumRefType` | numeric, reference |
## Example Agent Conversations
**User:** "Show me Gender values"
**Agent:**
1. `getValueSetDefs("Gender")` → VSetID = 3
2. `getValueSetByValueSetDef(3)` → Returns values
3. Output: Female, Male, Unknown
**User:** "What values for testdefsite.TestType?"
**Agent:**
1. `getValueSetDefs("testdefsite.TestType")` → VSetID = 27
2. `getValueSetByValueSetDef(27)` → Returns values
3. Output: TEST, PARAM, CALC, GROUP, TITLE
**User:** "Find values with 'STAT' in Priority"
**Agent:**
1. `getValueSetDefs("Priority")` → VSetID = 1
2. `getValueSets("STAT", null, 50, 1)` → Returns matching values
3. Output: STAT, STAT2 (if exists)

174
CLAUDE.md Normal file
View File

@ -0,0 +1,174 @@
# CLQMS Backend - Claude Code Instructions
**Project:** Clinical Laboratory Quality Management System (CLQMS) Backend
**Framework:** CodeIgniter 4 (PHP)
**Platform:** Windows - Use PowerShell or CMD for terminal commands
**Frontend:** Alpine.js (views/v2 directory contains Alpine.js components)
### Views/V2 Structure
```
app/Views/v2/
├── layout/
│ └── main_layout.php # Main layout with sidebar, navbar, Alpine.js layout() component
├── auth/
│ └── login.php # Login page
├── dashboard/
│ └── dashboard_index.php # Dashboard view
├── patients/
│ ├── patients_index.php # Patient list with x-data="patients()" component
│ └── dialog_form.php # Patient form dialog
├── requests/
│ └── requests_index.php # Lab requests
├── settings/
│ └── settings_index.php # Settings page
└── master/
├── organization/ # Organization management (accounts, sites, disciplines, departments, workstations)
├── specimen/ # Specimen management (containers, preparations)
├── tests/ # Lab tests (tests_index, param_dialog, grp_dialog, calc_dialog)
└── valuesets/ # Value sets management
```
### Alpine.js Patterns
- **Global layout:** `layout()` function in `main_layout.php` handles sidebar state, theme toggle, and navigation
- **Page components:** Each page uses `x-data="componentName()"` (e.g., `x-data="patients()"`)
- **API calls:** Use `fetch()` with `BASEURL` global variable and `credentials: 'include'`
- **Dialogs:** Modals use `x-show` with `@click.self` backdrop click to close
- **TailwindCSS 4:** Loaded via CDN with custom CSS variables for theming
## Quick Reference
**Static Library:**
- [`Lookups`](app/Libraries/Lookups.php) - Static lookup constants (no database queries)
**Usage:**
```php
use App\Libraries\Lookups;
// Get formatted lookup [{value: 'KEY', label: 'Label'}, ...]
Lookups::get('gender');
// Get raw associative array ['KEY' => 'Label', ...]
Lookups::getRaw('gender');
// Get all lookups for frontend
Lookups::getAll();
```
## Agent Workflow for Valueset Queries
Use `Lookups` class for all lookup queries - no database queries needed.
### Step 1: Identify the Lookup Constant
**By Category Name:** Match VSName to constant name (e.g., "Gender" → `Lookups::GENDER`)
**By Reference:** Match VSDesc to constant name (e.g., `testdefsite.TestType``Lookups::TEST_TYPE`)
**By VSetDefID:** Map VSetDefID to constant (see Common Lookups table below)
### Step 2: Retrieve Values
```php
// Formatted for frontend dropdowns
Lookups::get('gender'); // [{value: '1', label: 'Female'}, ...]
// Raw key-value pairs
Lookups::getRaw('gender'); // ['1' => 'Female', '2' => 'Male', ...]
```
### Step 3: Return Results
**Response Format (formatted):**
```json
[
{ "value": "1", "label": "Female" },
{ "value": "2", "label": "Male" },
{ "value": "3", "label": "Unknown" }
]
```
## Common Lookups
| VSetDefID | Constant | Search Keywords |
|-----------|----------|-----------------|
| 1 | `WS_TYPE` | workstation, type |
| 2 | `ENABLE_DISABLE` | enable, disable |
| 3 | `GENDER` | gender, sex |
| 4 | `MARITAL_STATUS` | marital, status |
| 5 | `DEATH_INDICATOR` | death, indicator |
| 6 | `IDENTIFIER_TYPE` | identifier, type, KTP, passport |
| 7 | `OPERATION` | operation, CRUD |
| 8 | `DID_TYPE` | device, ID, AAID, IDFA |
| 9 | `REQUESTED_ENTITY` | requested, entity, patient, insurance |
| 10 | `ORDER_PRIORITY` | priority, order, stat, ASAP |
| 11 | `ORDER_STATUS` | status, order |
| 12 | `LOCATION_TYPE` | location, type |
| 13 | `ADDITIVE` | additive, heparin, EDTA |
| 14 | `CONTAINER_CLASS` | container, class |
| 15 | `SPECIMEN_TYPE` | specimen, type, blood, urine |
| 16 | `UNIT` | unit |
| 17 | `GENERATE_BY` | generate, by |
| 18 | `SPECIMEN_ACTIVITY` | specimen, activity |
| 19 | `ACTIVITY_RESULT` | activity, result |
| 20 | `SPECIMEN_STATUS` | specimen, status |
| 21 | `SPECIMEN_CONDITION` | specimen, condition |
| 22 | `SPECIMEN_ROLE` | specimen, role |
| 23 | `COLLECTION_METHOD` | collection, method |
| 24 | `BODY_SITE` | body, site |
| 25 | `CONTAINER_SIZE` | container, size |
| 26 | `FASTING_STATUS` | fasting, status |
| 27 | `TEST_TYPE` | test, type, testdefsite |
| 28 | `RESULT_UNIT` | result, unit |
| 29 | `FORMULA_LANGUAGE` | formula, language |
| 30 | `RACE` | race, ethnicity |
| 31 | `RELIGION` | religion |
| 32 | `ETHNIC` | ethnic |
| 33 | `COUNTRY` | country (loaded from external file) |
| 34 | `CONTAINER_CAP_COLOR` | container, cap, color |
| 35 | `TEST_ACTIVITY` | test, activity |
| 36 | `ADT_EVENT` | ADT, event |
| 37 | `SITE_TYPE` | site, type |
| 38 | `SITE_CLASS` | site, class |
| 39 | `ENTITY_TYPE` | entity, type |
| 40 | `AREA_CLASS` | area, class |
| 41 | `MATH_SIGN` | math, sign |
| 42 | `V_CATEGORY` | category |
| 43 | `RESULT_TYPE` | result, type |
| 44 | `REFERENCE_TYPE` | reference, type |
| 45 | `RANGE_TYPE` | range, type |
| 46 | `NUMERIC_REF_TYPE` | numeric, reference |
| 47 | `TEXT_REF_TYPE` | text, reference |
**Convenience Aliases:**
- `Lookups::PRIORITY` → alias for `ORDER_PRIORITY`
- `Lookups::TEST_STATUS` → Test status values
- `Lookups::REQUEST_STATUS` → alias for `SPECIMEN_STATUS`
- `Lookups::RESULT_STATUS` → Result status values
## Example Agent Conversations
**User:** "Show me Gender values"
**Agent:**
1. `Lookups::get('gender')` → Returns formatted array
2. Output: Female, Male, Unknown
**User:** "What values for testdefsite.TestType?"
**Agent:**
1. `Lookups::get('test_type')` → Returns formatted array
2. Output: TEST, PARAM, CALC, GROUP, TITLE
**User:** "Find specimen status options"
**Agent:**
1. `Lookups::get('specimen_status')` → Returns formatted array
2. Output: To be collected, Collected, In-transport, Arrived, etc.
---
## Commanding Officer Persona Mode
When the user addresses you as their commanding officer or in a starship context, respond accordingly:
- Address the officer respectfully ("Commander", "Captain", "Sir/Ma'am")
- Use military/space command terminology ("affirmative", "reporting", "orders", "status")
- Frame technical responses in mission-ops format ("Systems operational", "Data retrieved", "Report ready")
- Keep responses crisp and professional, befitting ship command
- Example: "Commander, the valueset data you requested is ready for review."
- Start something with basmalah and end with hamdalah

View File

@ -66,6 +66,77 @@ When working on UI components or dropdowns, **always check for existing ValueSet
---
## 📚 Static Lookups Library (`app/Libraries/Lookups.php`)
For frequently used lookup values, CLQMS provides a **static library** with all values hardcoded. This eliminates database queries for common dropdowns and improves performance.
### Available Lookups
| Constant | VSetID | Description |
|----------|--------|-------------|
| `WS_TYPE` | 1 | Workstation type (Primary, Secondary) |
| `ENABLE_DISABLE` | 2 | Enable/disable flags |
| `GENDER` | 3 | Gender (Female, Male, Unknown) |
| `MARITAL_STATUS` | 4 | Marital status (A/D/M/S/W/B/U/O) |
| `DEATH_INDICATOR` | 5 | Death indicator (Y/N) |
| `IDENTIFIER_TYPE` | 6 | ID types (KTP, Passport, SSN, SIM) |
| `OPERATION` | 7 | CRUD operations (Create, Read, Update, Delete) |
| `ORDER_PRIORITY` | 10 | Order priority (S=Stat, A=ASAP, R=Routine, P=Preop, etc.) |
| `ORDER_STATUS` | 11 | Order status (A/CA/CM/DC/ER/HD/IP/RP/SC/CL/AC/DL) |
| `SPECIMEN_TYPE` | 15 | Specimen types (BLD, SER, PLAS, UR, CSF, etc.) |
| `SPECIMEN_STATUS` | 20 | Specimen status (STC, SCtd, SArrv, SRcvd, SAna, etc.) |
| `SPECIMEN_CONDITION` | 21 | Specimen condition (HEM, ITC, LIP, etc.) |
| `TEST_TYPE` | 27 | Test types (TEST, PARAM, CALC, GROUP, TITLE) |
| `RESULT_UNIT` | 28 | Result units (g/dL, mg/dL, x10^6/mL, etc.) |
| `RACE` | 30 | Ethnicity/race (Jawa, Sunda, Batak, etc.) |
| `RELIGION` | 31 | Religion (Islam, Kristen, Katolik, Hindu, Budha, etc.) |
| `SITE_TYPE` | 37 | Site type (GH, PH, GHL, PHL, GL, PL) |
| `SITE_CLASS` | 38 | Site class (A/B/C/D/Utm/Ptm) |
| `RESULT_TYPE` | 43 | Result type (NMRIC, RANGE, TEXT, VSET) |
| `RANGE_TYPE` | 45 | Range type (REF, CRTC, VAL, RERUN) |
| `HIV_RESULT` | 1001 | HIV results (NEG, POS, GZ) |
**Plus 25+ more categories** - see [`app/Libraries/Lookups.php`](app/Libraries/Lookups.php) for complete list.
### Usage
```php
use App\Libraries\Lookups;
// Get all lookups formatted for frontend
$allLookups = Lookups::getAll();
// Get single lookup (returns [{value: 'X', label: 'Y'}, ...])
$gender = Lookups::get('GENDER');
// Get raw associative array
$priorities = Lookups::getRaw('ORDER_PRIORITY');
// Returns: ['S' => 'Stat', 'A' => 'ASAP', 'R' => 'Routine', ...]
// Get label by key
$label = Lookups::getLabel('GENDER', '1'); // Returns 'Female'
```
### Frontend Usage (Alpine.js)
```php
// In your PHP view file
<script>
const LOOKUPS = <?= json_encode(\App\Libraries\Lookups::getAll()) ?>;
console.log(LOOKUPS.gender);
// Output: [{"value":"1","label":"Female"},{"value":"2","label":"Male"},{"value":"3","label":"Unknown"}]
</script>
```
### When to Use
| Approach | Use Case |
|----------|----------|
| **Static Lookups** | Frequently used values that rarely change (gender, status, types) |
| **API `/api/valueset*`** | Dynamic values that may be modified by admins at runtime |
---
## 📋 Master Data Management
CLQMS provides comprehensive master data management for laboratory operations. All master data is accessible via the V2 UI at `/v2/master/*` endpoints.

View File

@ -24,21 +24,13 @@ class ValueSetController extends BaseController {
public function index() {
$param = $this->request->getVar('param');
$VSetID = $this->request->getVar('VSetID');
$page = $this->request->getVar('page') ?? 1;
$limit = $this->request->getVar('limit') ?? 20;
$result = $this->model->getValueSets($param, $page, $limit, $VSetID);
$data = $this->model->getValueSets($param, $VSetID);
return $this->respond([
'status' => 'success',
'message'=> "Data fetched successfully",
'data' => $result['data'],
'pagination' => [
'currentPage' => (int)$page,
'totalPages' => $result['pager']->getPageCount(),
'totalItems' => $result['pager']->getTotal(),
'limit' => (int)$limit
]
'data' => $data
], 200);
}

View File

@ -0,0 +1,259 @@
<?php
/**
* Country Lookup Data (ISO 3166-1 alpha-2 codes)
* This file is included by Lookups.php
*
* Total: 249 countries
*/
return [
'AFG' => 'Afghanistan',
'ALA' => 'Åland Islands',
'ALB' => 'Albania',
'DZA' => 'Algeria',
'ASM' => 'American Samoa',
'AND' => 'Andorra',
'AGO' => 'Angola',
'AIA' => 'Anguilla',
'ATA' => 'Antarctica',
'ATG' => 'Antigua and Barbuda',
'ARG' => 'Argentina',
'ARM' => 'Armenia',
'ABW' => 'Aruba',
'AUS' => 'Australia',
'AUT' => 'Austria',
'AZE' => 'Azerbaijan',
'BHS' => 'Bahamas',
'BHR' => 'Bahrain',
'BGD' => 'Bangladesh',
'BRB' => 'Barbados',
'BLR' => 'Belarus',
'BEL' => 'Belgium',
'BLZ' => 'Belize',
'BEN' => 'Benin',
'BMU' => 'Bermuda',
'BTN' => 'Bhutan',
'BOL' => 'Bolivia, Plurinational State of',
'BES' => 'Bonaire, Sint Eustatius and Saba',
'BIH' => 'Bosnia and Herzegovina',
'BWA' => 'Botswana',
'BVT' => 'Bouvet Island',
'BRA' => 'Brazil',
'IOT' => 'British Indian Ocean Territory',
'BRN' => 'Brunei Darussalam',
'BGR' => 'Bulgaria',
'BFA' => 'Burkina Faso',
'BDI' => 'Burundi',
'CPV' => 'Cabo Verde',
'KHM' => 'Cambodia',
'CMR' => 'Cameroon',
'CAN' => 'Canada',
'CYM' => 'Cayman Islands',
'CAF' => 'Central African Republic',
'TCD' => 'Chad',
'CHL' => 'Chile',
'CHN' => 'China',
'CXR' => 'Christmas Island',
'CCK' => 'Cocos (Keeling) Islands',
'COL' => 'Colombia',
'COM' => 'Comoros',
'COG' => 'Congo',
'COD' => 'Congo, Democratic Republic of the',
'COK' => 'Cook Islands',
'CRI' => 'Costa Rica',
'CIV' => "Côte d'Ivoire",
'HRV' => 'Croatia',
'CUB' => 'Cuba',
'CUW' => 'Curaçao',
'CYP' => 'Cyprus',
'CZE' => 'Czechia',
'DNK' => 'Denmark',
'DJI' => 'Djibouti',
'DMA' => 'Dominica',
'DOM' => 'Dominican Republic',
'ECU' => 'Ecuador',
'EGY' => 'Egypt',
'SLV' => 'El Salvador',
'GNQ' => 'Equatorial Guinea',
'ERI' => 'Eritrea',
'EST' => 'Estonia',
'SWZ' => 'Eswatini',
'ETH' => 'Ethiopia',
'FLK' => 'Falkland Islands (Malvinas)',
'FRO' => 'Faroe Islands',
'FJI' => 'Fiji',
'FIN' => 'Finland',
'FRA' => 'France',
'GUF' => 'French Guiana',
'PYF' => 'French Polynesia',
'ATF' => 'French Southern Territories',
'GAB' => 'Gabon',
'GMB' => 'Gambia',
'GEO' => 'Georgia',
'DEU' => 'Germany',
'GHA' => 'Ghana',
'GIB' => 'Gibraltar',
'GRC' => 'Greece',
'GRL' => 'Greenland',
'GRD' => 'Grenada',
'GLP' => 'Guadeloupe',
'GUM' => 'Guam',
'GTM' => 'Guatemala',
'GGY' => 'Guernsey',
'GIN' => 'Guinea',
'GNB' => 'Guinea-Bissau',
'GUY' => 'Guyana',
'HTI' => 'Haiti',
'HMD' => 'Heard Island and McDonald Islands',
'VAT' => 'Holy See',
'HND' => 'Honduras',
'HKG' => 'Hong Kong',
'HUN' => 'Hungary',
'ISL' => 'Iceland',
'IND' => 'India',
'IDN' => 'Indonesia',
'IRN' => 'Iran, Islamic Republic of',
'IRQ' => 'Iraq',
'IRL' => 'Ireland',
'IMN' => 'Isle of Man',
'ISR' => 'Israel',
'ITA' => 'Italy',
'JAM' => 'Jamaica',
'JPN' => 'Japan',
'JEY' => 'Jersey',
'JOR' => 'Jordan',
'KAZ' => 'Kazakhstan',
'KEN' => 'Kenya',
'KIR' => 'Kiribati',
'PRK' => 'Korea, Democratic People\'s Republic of',
'KOR' => 'Korea, Republic of',
'KWT' => 'Kuwait',
'KGZ' => 'Kyrgyzstan',
'LAO' => 'Lao People\'s Democratic Republic',
'LVA' => 'Latvia',
'LBN' => 'Lebanon',
'LSO' => 'Lesotho',
'LBR' => 'Liberia',
'LBY' => 'Libya',
'LIE' => 'Liechtenstein',
'LTU' => 'Lithuania',
'LUX' => 'Luxembourg',
'MAC' => 'Macao',
'MDG' => 'Madagascar',
'MWI' => 'Malawi',
'MYS' => 'Malaysia',
'MDV' => 'Maldives',
'MLI' => 'Mali',
'MLT' => 'Malta',
'MHL' => 'Marshall Islands',
'MTQ' => 'Martinique',
'MRT' => 'Mauritania',
'MUS' => 'Mauritius',
'MYT' => 'Mayotte',
'MEX' => 'Mexico',
'FSM' => 'Micronesia, Federated States of',
'MDA' => 'Moldova, Republic of',
'MCO' => 'Monaco',
'MNG' => 'Mongolia',
'MNE' => 'Montenegro',
'MSR' => 'Montserrat',
'MAR' => 'Morocco',
'MOZ' => 'Mozambique',
'MMR' => 'Myanmar',
'NAM' => 'Namibia',
'NRU' => 'Nauru',
'NPL' => 'Nepal',
'NLD' => 'Netherlands, Kingdom of the',
'NCL' => 'New Caledonia',
'NZL' => 'New Zealand',
'NIC' => 'Nicaragua',
'NER' => 'Niger',
'NGA' => 'Nigeria',
'NIU' => 'Niue',
'NFK' => 'Norfolk Island',
'MKD' => 'North Macedonia',
'MNP' => 'Northern Mariana Islands',
'NOR' => 'Norway',
'OMN' => 'Oman',
'PAK' => 'Pakistan',
'PLW' => 'Palau',
'PSE' => 'Palestine, State of',
'PAN' => 'Panama',
'PNG' => 'Papua New Guinea',
'PRY' => 'Paraguay',
'PER' => 'Peru',
'PHL' => 'Philippines',
'PCN' => 'Pitcairn',
'POL' => 'Poland',
'PRT' => 'Portugal',
'PRI' => 'Puerto Rico',
'QAT' => 'Qatar',
'REU' => 'Réunion',
'ROU' => 'Romania',
'RUS' => 'Russian Federation',
'RWA' => 'Rwanda',
'BLM' => 'Saint Barthélemy',
'SHN' => 'Saint Helena, Ascension and Tristan da Cunha',
'KNA' => 'Saint Kitts and Nevis',
'LCA' => 'Saint Lucia',
'MAF' => 'Saint Martin (French part)',
'SPM' => 'Saint Pierre and Miquelon',
'VCT' => 'Saint Vincent and the Grenadines',
'WSM' => 'Samoa',
'SMR' => 'San Marino',
'STP' => 'Sao Tome and Principe',
'SAU' => 'Saudi Arabia',
'SEN' => 'Senegal',
'SRB' => 'Serbia',
'SYC' => 'Seychelles',
'SLE' => 'Sierra Leone',
'SGP' => 'Singapore',
'SXM' => 'Sint Maarten (Dutch part)',
'SVK' => 'Slovakia',
'SVN' => 'Slovenia',
'SLB' => 'Solomon Islands',
'SOM' => 'Somalia',
'ZAF' => 'South Africa',
'SGS' => 'South Georgia and the South Sandwich Islands',
'SSD' => 'South Sudan',
'ESP' => 'Spain',
'LKA' => 'Sri Lanka',
'SDN' => 'Sudan',
'SUR' => 'Suriname',
'SJM' => 'Svalbard and Jan Mayen',
'SWE' => 'Sweden',
'CHE' => 'Switzerland',
'SYR' => 'Syrian Arab Republic',
'TWN' => 'Taiwan, Province of China',
'TJK' => 'Tajikistan',
'TZA' => 'Tanzania, United Republic of',
'THA' => 'Thailand',
'TLS' => 'Timor-Leste',
'TGO' => 'Togo',
'TKL' => 'Tokelau',
'TON' => 'Tonga',
'TTO' => 'Trinidad and Tobago',
'TUN' => 'Tunisia',
'TUR' => 'Türkiye',
'TKM' => 'Turkmenistan',
'TCA' => 'Turks and Caicos Islands',
'TUV' => 'Tuvalu',
'UGA' => 'Uganda',
'UKR' => 'Ukraine',
'ARE' => 'United Arab Emirates',
'GBR' => 'United Kingdom of Great Britain and Northern Ireland',
'USA' => 'United States of America',
'UMI' => 'United States Minor Outlying Islands',
'URY' => 'Uruguay',
'UZB' => 'Uzbekistan',
'VUT' => 'Vanuatu',
'VEN' => 'Venezuela, Bolivarian Republic of',
'VNM' => 'Viet Nam',
'VGB' => 'Virgin Islands (British)',
'VIR' => 'Virgin Islands (U.S.)',
'WLF' => 'Wallis and Futuna',
'ESH' => 'Western Sahara',
'YEM' => 'Yemen',
'ZMB' => 'Zambia',
'ZWE' => 'Zimbabwe'
];

649
app/Libraries/Lookups.php Normal file
View File

@ -0,0 +1,649 @@
<?php
namespace App\Libraries;
/**
* Static Lookup Values
* All predefined lookup values stored as constants for easy access.
* No database queries - all data is hardcoded.
* Based on valuesetdef and valueset tables.
*/
class Lookups {
// VSetID 1: Workstation Type
const WS_TYPE = [
'0' => 'Primary',
'1' => 'Secondary'
];
// VSetID 2: Enable/Disable
const ENABLE_DISABLE = [
'0' => 'Disabled',
'1' => 'Enabled'
];
// VSetID 3: Gender
const GENDER = [
'1' => 'Female',
'2' => 'Male',
'3' => 'Unknown'
];
// VSetID 4: Marital Status
const MARITAL_STATUS = [
'A' => 'Separated',
'D' => 'Divorced',
'M' => 'Married',
'S' => 'Single',
'W' => 'Widowed',
'B' => 'Unmarried',
'U' => 'Unknown',
'O' => 'Other'
];
// VSetID 5: Death Indicator
const DEATH_INDICATOR = [
'Y' => 'Death',
'N' => 'Life'
];
// VSetID 6: Identifier Type
const IDENTIFIER_TYPE = [
'KTP' => 'Kartu Tanda Penduduk',
'PASS' => 'Passport',
'SSN' => 'Social Security Number',
'SIM' => 'Surat Izin Mengemudi',
'KTAS' => 'Kartu Izin Tinggal Terbatas'
];
// VSetID 7: Operation (CRUD)
const OPERATION = [
'Create' => 'Create record',
'Read' => 'Read record/field',
'Update' => 'Update record/field',
'Delete' => 'Delete record/field'
];
// VSetID 8: DID Type
const DID_TYPE = [
'WDID' => 'Windows Device ID',
'AAID' => 'Android AAID',
'IDFA' => 'iOS IDFA'
];
// VSetID 9: Requested Entity
const REQUESTED_ENTITY = [
'PAT' => 'Patient',
'ISN' => 'Insurance',
'ACC' => 'Account',
'DOC' => 'Doctor'
];
// VSetID 10: Order Priority
const ORDER_PRIORITY = [
'S' => 'Stat',
'A' => 'ASAP',
'R' => 'Routine',
'P' => 'Preop',
'C' => 'Callback',
'T' => 'Timing critical',
'PRN' => 'As needed'
];
// VSetID 11: Order Status
const ORDER_STATUS = [
'A' => 'Some, not all results available',
'CA' => 'Order is cancelled',
'CM' => 'Order is completed',
'DC' => 'Order was discontinued',
'ER' => 'Error, order not found',
'HD' => 'Order on hold',
'IP' => 'In process, unspecified',
'RP' => 'Order has been replaced',
'SC' => 'In process, scheduled',
'CL' => 'Closed',
'AC' => 'Archived',
'DL' => 'Deleted'
];
// VSetID 12: Location Type
const LOCATION_TYPE = [
'FCLT' => 'Facility',
'BLDG' => 'Building',
'FLOR' => 'Floor',
'POC' => 'Point of Care',
'ROOM' => 'Room',
'BED' => 'Bed',
'MOBL' => 'Mobile',
'REMT' => 'Remote'
];
// VSetID 13: Additive
const ADDITIVE = [
'Hep' => 'Heparin ammonium',
'Apro' => 'Aprotinin',
'HepCa' => 'Heparin calcium',
'H3BO3' => 'Boric acid',
'CaOxa' => 'Calcium oxalate',
'EDTA' => 'EDTA',
'Ede' => 'Edetate',
'HCl' => 'Hydrochloric acid',
'Hrdn' => 'Hirudin',
'EdeK' => 'Edetate dipotassium',
'EdeTri' => 'Tripotassium edetate',
'LiHep' => 'Heparin lithium',
'EdeNa' => 'Edetate disodium',
'NaCtrt' => 'Sodium citrate',
'NaHep' => 'Heparin sodium',
'NaF' => 'Sodium fluoride',
'Borax' => 'Sodium tetraborate',
'Mntl' => 'Mannitol',
'NaFrm' => 'Sodium formate'
];
// VSetID 14: Container Class
const CONTAINER_CLASS = [
'Pri' => 'Primary',
'Sec' => 'Secondary',
'Ter' => 'Tertiary'
];
// VSetID 15: Specimen Type
const SPECIMEN_TYPE = [
'BLD' => 'Whole blood',
'BLDA' => 'Blood arterial',
'BLDCO' => 'Cord blood',
'FBLOOD' => 'Blood, Fetal',
'CSF' => 'Cerebral spinal fluid',
'WB' => 'Blood, Whole',
'BBL' => 'Blood bag',
'SER' => 'Serum',
'PLAS' => 'Plasma',
'PLB' => 'Plasma bag',
'MUCOS' => 'Mucosa',
'MUCUS' => 'Mucus',
'UR' => 'Urine',
'RANDU' => 'Urine, Random',
'URINM' => 'Urine, Midstream'
];
// VSetID 16: Unit
const UNIT = [
'L' => 'Liter',
'mL' => 'Mili Liter',
'Pcs' => 'Pieces'
];
// VSetID 17: Generate By
const GENERATE_BY = [
'order' => 'Generate by order',
'user' => 'Generate by user'
];
// VSetID 18: Specimen Activity
const SPECIMEN_ACTIVITY = [
'SColl' => 'Collection',
'STran' => 'Transport',
'SRec' => 'Reception',
'SPrep' => 'Preparation',
'SAlqt' => 'Aliquot',
'SDisp' => 'Dispatching',
'SDest' => 'Destruction'
];
// VSetID 19: Activity Result
const ACTIVITY_RESULT = [
'0' => 'Failed',
'1' => 'Success with note',
'2' => 'Success'
];
// VSetID 20: Specimen Status
const SPECIMEN_STATUS = [
'STC' => 'To be collected',
'SCFld' => 'Collection failed',
'SCtd' => 'Collected',
'STran' => 'In-transport',
'STFld' => 'Transport failed',
'SArrv' => 'Arrived',
'SRejc' => 'Rejected',
'SRcvd' => 'Received',
'SPAna' => 'Pre-analytical',
'SPAF' => 'Pre-analytical failed',
'STA' => 'To be analyze',
'SAFld' => 'Analytical failed',
'SAna' => 'Analytical',
'STS' => 'To be stored',
'SSFld' => 'Store failed',
'SStrd' => 'Stored',
'SExp' => 'Expired',
'STD' => 'To be destroyed',
'SDFld' => 'Failed to destroy',
'SDstd' => 'Destroyed'
];
// VSetID 21: Specimen Condition
const SPECIMEN_CONDITION = [
'HEM' => 'Hemolyzed',
'ITC' => 'Icteric',
'LIP' => 'Lipemic',
'CFU' => 'Centrifuged',
'ROOM' => 'Room temperature',
'COOL' => 'Cool',
'FROZ' => 'Frozen',
'CLOT' => 'Clotted',
'AUT' => 'Autolyzed',
'CON' => 'Contaminated',
'LIVE' => 'Live'
];
// VSetID 22: Specimen Role
const SPECIMEN_ROLE = [
'P' => 'Patient',
'B' => 'Blind Sample',
'Q' => 'Control specimen',
'E' => 'Electronic QC',
'F' => 'Filler Organization Proficiency',
'O' => 'Operator Proficiency',
'C' => 'Calibrator',
'R' => 'Replicate',
'V' => 'Verifying Calibrator'
];
// VSetID 23: Collection Method
const COLLECTION_METHOD = [
'pcntr' => 'Puncture',
'fprk' => 'Finger-prick sampling',
'ucct' => 'Urine specimen collection, clean catch',
'utcl' => 'Timed urine collection',
'ucth' => 'Urine specimen collection, catheterized',
'scgh' => 'Collection of coughed sputum',
'bpsy' => 'Biopsy',
'aspn' => 'Aspiration',
'excs' => 'Excision',
'scrp' => 'Scraping'
];
// VSetID 24: Body Site
const BODY_SITE = [
'LA' => 'Left Arm',
'RA' => 'Right Arm',
'LF' => 'Left Foot',
'RF' => 'Right Foot'
];
// VSetID 25: Container Size
const CONTAINER_SIZE = [
'5ml' => '5 mL',
'7ml' => '7 mL',
'10ml' => '10 mL',
'1l' => '1 L'
];
// VSetID 26: Fasting Status
const FASTING_STATUS = [
'F' => 'Fasting',
'NF' => 'Not Fasting',
'NG' => 'Not Given'
];
// VSetID 27: Test Type
const TEST_TYPE = [
'TEST' => 'Test',
'PARAM' => 'Parameter',
'CALC' => 'Calculated Test',
'GROUP' => 'Group Test',
'TITLE' => 'Title'
];
// VSetID 28: Result Unit
const RESULT_UNIT = [
'g/dL' => 'g/dL',
'g/L' => 'g/L',
'mg/dL' => 'mg/dL',
'mg/L' => 'mg/L',
'L/L' => 'L/L',
'x106/mL' => 'x106/mL',
'x1012/L' => 'x1012/L',
'fL' => 'fL',
'pg' => 'pg',
'x109/L' => 'x109/L'
];
// VSetID 29: Formula Language
const FORMULA_LANGUAGE = [
'Phyton' => 'Phyton',
'CQL' => 'Clinical Quality Language',
'FHIRP' => 'FHIRPath',
'SQL' => 'SQL'
];
// VSetID 30: Race (Ethnicity)
const RACE = [
'JAWA' => 'Jawa',
'SUNDA' => 'Sunda',
'BATAK' => 'Batak',
'SULOR' => 'Suku asal Sulawesi lainnya',
'MDRA' => 'Madura',
'BTWI' => 'Betawi',
'MNG' => 'Minangkabau',
'BUGIS' => 'Bugis',
'MLYU' => 'Melayu',
'SUMSL' => 'Suku asal Sumatera Selatan',
'BTNOR' => 'Suku asal Banten',
'NTTOR' => 'Suku asal Nusa Tenggara Timur',
'BNJAR' => 'Banjar',
'ACEH' => 'Aceh',
'BALI' => 'Bali',
'SASAK' => 'Sasak',
'DAYAK' => 'Dayak',
'TNGHA' => 'Tionghoa',
'PPAOR' => 'Suku asal Papua',
'MKSSR' => 'Makassar',
'SUMOR' => 'Suku asal Sumatera lainnya',
'MLKOR' => 'Suku asal Maluku',
'KLMOR' => 'Suku asal Kalimantan lainnya',
'CRBON' => 'Cirebon',
'JBIOR' => 'Suku asal Jambi',
'LPGOR' => 'Suku Lampung',
'NTBOR' => 'Suku asal Nusa Tenggara Barat lainnya',
'GRTLO' => 'Gorontalo',
'MNHSA' => 'Minahasa',
'NIAS' => 'Nias',
'FORGN' => 'Asing/luar negeri'
];
// VSetID 31: Religion
const RELIGION = [
'ISLAM' => 'Islam',
'KRSTN' => 'Kristen',
'KTLIK' => 'Katolik',
'HINDU' => 'Hindu',
'BUDHA' => 'Budha',
'KHCU' => 'Khong Hu Cu',
'OTHER' => 'Lainnya'
];
// VSetID 32: Ethnic
const ETHNIC = [
'PPMLN' => 'Papua Melanezoid',
'NGRID' => 'Negroid',
'WDOID' => 'Weddoid',
'MMPM' => 'Melayu Mongoloid_Proto Melayu',
'MMDM' => 'Melayu Mongoloid_Deutro Melayu',
'TNGHA' => 'Tionghoa',
'INDIA' => 'India',
'ARAB' => 'Arab'
];
// VSetID 33: Country (ISO 2-letter codes - ISO 3166-1 alpha-2)
const COUNTRY = null; // Loaded from external file
/**
* Get COUNTRY data from external file (lazy load)
*/
private static function loadCountryData(): array {
$file = APPPATH . 'Libraries/Data/Countries.php';
if (is_file($file)) {
return require $file;
}
return [];
}
/**
* Get formatted country list
*/
public static function getCountry(): array {
return self::format(self::loadCountryData());
}
// VSetID 34: Container Cap Color
const CONTAINER_CAP_COLOR = [
'PRPL' => 'Purple',
'RED' => 'Red',
'YLLW' => 'Yellow',
'GRN' => 'Green',
'PINK' => 'Pink',
'LBLU' => 'Light Blue',
'RBLU' => 'Royal Blue',
'GRAY' => 'Gray'
];
// VSetID 35: Test Activity
const TEST_ACTIVITY = [
'ORD' => 'Order',
'ANA' => 'Analyse',
'VER' => 'Result Verification/Technical Validation',
'REV' => 'Clinical Review/Clinical Validation',
'REP' => 'Reporting'
];
// VSetID 36: ADT Event
const ADT_EVENT = [
'A01' => 'Admit',
'A02' => 'Transfer',
'A03' => 'Discharge',
'A04' => 'Register',
'A08' => 'Update patient information',
'A11' => 'Cancel admit',
'A12' => 'Cancel transfer',
'A13' => 'Cancel discharge',
'A23' => 'Delete patient record',
'A24' => 'Link patient information',
'A37' => 'Unlink patient information',
'A54' => 'Change attending doctor',
'A61' => 'Change consulting doctor'
];
// VSetID 37: Site Type
const SITE_TYPE = [
'GH' => 'Government Hospital',
'PH' => 'Private Hospital',
'GHL' => 'Government Hospital Lab',
'PHL' => 'Private Hospital Lab',
'GL' => 'Government Lab',
'PL' => 'Private Lab'
];
// VSetID 38: Site Class
const SITE_CLASS = [
'A' => 'Kelas A',
'B' => 'Kelas B',
'C' => 'Kelas C',
'D' => 'Kelas D',
'Utm' => 'Utama',
'Ptm' => 'Pratama'
];
// VSetID 39: Entity Type
const ENTITY_TYPE = [
'HIS' => 'HIS',
'SITE' => 'Site',
'WST' => 'Workstation',
'INST' => 'Equipment/Instrument'
];
// VSetID 40: Area Class
const AREA_CLASS = [
'PROP' => 'Propinsi',
'KAB' => 'Kabupaten',
'KOTA' => 'Kota'
];
// VSetID 41: Math Sign
const MATH_SIGN = [
'=' => 'Equal',
'<' => 'Less than',
'>' => 'Greater than',
'<=' => 'Less than or equal to',
'>=' => 'Greater than or equal to'
];
// VSetID 42: VCategory
const V_CATEGORY = [
'0' => 'System',
'1' => 'User-defined'
];
// VSetID 43: Result Type
const RESULT_TYPE = [
'NMRIC' => 'Numeric',
'RANGE' => 'Range',
'TEXT' => 'Text',
'VSET' => 'Value set'
];
// VSetID 44: Reference Type
const REFERENCE_TYPE = [
'NMRC' => 'Numeric',
'TEXT' => 'Text'
];
// VSetID 45: Range Type
const RANGE_TYPE = [
'REF' => 'Reference Range',
'CRTC' => 'Critical Range',
'VAL' => 'Validation Range',
'RERUN' => 'Rerun Range'
];
// VSetID 46: Numeric Reference Type
const NUMERIC_REF_TYPE = [
'RANGE' => 'Range',
'THOLD' => 'Threshold'
];
// VSetID 47: Text Reference Type
const TEXT_REF_TYPE = [
'VSET' => 'Value Set',
'TEXT' => 'Text'
];
// Convenience constants (aliases for common use cases)
const PRIORITY = self::ORDER_PRIORITY;
const TEST_STATUS = [
'PENDING' => 'Waiting for Results',
'IN_PROCESS' => 'Analyzing',
'VERIFIED' => 'Verified & Signed',
'REJECTED' => 'Sample Rejected'
];
const REQUEST_STATUS = self::SPECIMEN_STATUS;
const RESULT_STATUS = [
'PRELIMINARY' => 'Preliminary',
'FINAL' => 'Final',
'CORRECTED' => 'Corrected',
'CANCELLED' => 'Cancelled'
];
/**
* Get all lookups formatted for frontend
* @return array
*/
public static function getAll(): array {
return [
'ws_type' => self::format(self::WS_TYPE),
'enable_disable' => self::format(self::ENABLE_DISABLE),
'gender' => self::format(self::GENDER),
'marital_status' => self::format(self::MARITAL_STATUS),
'death_indicator' => self::format(self::DEATH_INDICATOR),
'identifier_type' => self::format(self::IDENTIFIER_TYPE),
'operation' => self::format(self::OPERATION),
'did_type' => self::format(self::DID_TYPE),
'requested_entity' => self::format(self::REQUESTED_ENTITY),
'order_priority' => self::format(self::ORDER_PRIORITY),
'order_status' => self::format(self::ORDER_STATUS),
'location_type' => self::format(self::LOCATION_TYPE),
'additive' => self::format(self::ADDITIVE),
'container_class' => self::format(self::CONTAINER_CLASS),
'specimen_type' => self::format(self::SPECIMEN_TYPE),
'unit' => self::format(self::UNIT),
'generate_by' => self::format(self::GENERATE_BY),
'specimen_activity' => self::format(self::SPECIMEN_ACTIVITY),
'activity_result' => self::format(self::ACTIVITY_RESULT),
'specimen_status' => self::format(self::SPECIMEN_STATUS),
'specimen_condition' => self::format(self::SPECIMEN_CONDITION),
'specimen_role' => self::format(self::SPECIMEN_ROLE),
'collection_method' => self::format(self::COLLECTION_METHOD),
'body_site' => self::format(self::BODY_SITE),
'container_size' => self::format(self::CONTAINER_SIZE),
'fasting_status' => self::format(self::FASTING_STATUS),
'test_type' => self::format(self::TEST_TYPE),
'result_unit' => self::format(self::RESULT_UNIT),
'formula_language' => self::format(self::FORMULA_LANGUAGE),
'race' => self::format(self::RACE),
'religion' => self::format(self::RELIGION),
'ethnic' => self::format(self::ETHNIC),
'country' => self::getCountry(),
'container_cap_color' => self::format(self::CONTAINER_CAP_COLOR),
'test_activity' => self::format(self::TEST_ACTIVITY),
'adt_event' => self::format(self::ADT_EVENT),
'site_type' => self::format(self::SITE_TYPE),
'site_class' => self::format(self::SITE_CLASS),
'entity_type' => self::format(self::ENTITY_TYPE),
'area_class' => self::format(self::AREA_CLASS),
'math_sign' => self::format(self::MATH_SIGN),
'v_category' => self::format(self::V_CATEGORY),
'result_type' => self::format(self::RESULT_TYPE),
'reference_type' => self::format(self::REFERENCE_TYPE),
'range_type' => self::format(self::RANGE_TYPE),
'numeric_ref_type' => self::format(self::NUMERIC_REF_TYPE),
'text_ref_type' => self::format(self::TEXT_REF_TYPE)
];
}
/**
* Format associative array as [{value: 'KEY', label: 'Label'}, ...]
*/
private static function format(array $array): array {
$result = [];
foreach ($array as $key => $label) {
$result[] = ['value' => (string) $key, 'label' => (string) $label];
}
return $result;
}
/**
* Get single lookup by constant name (case-insensitive)
* @param string $name Constant name (e.g., 'gender', 'PRIORITY')
* @return array|null
*/
public static function get(string $name): ?array {
$const = strtoupper($name);
// Special case for COUNTRY (loaded from external file)
if ($const === 'COUNTRY') {
return self::getCountry();
}
if (defined("self::$const")) {
return self::format(constant("self::$const"));
}
return null;
}
/**
* Get raw constant array (not formatted)
* @param string $name
* @return array|null
*/
public static function getRaw(string $name): ?array {
$const = strtoupper($name);
// Special case for COUNTRY (loaded from external file)
if ($const === 'COUNTRY') {
return self::loadCountryData();
}
if (defined("self::$const")) {
return constant("self::$const");
}
return null;
}
/**
* Get label by key from a lookup
* @param string $lookup Lookup name
* @param string $key Key to find
* @return string|null
*/
public static function getLabel(string $lookup, string $key): ?string {
$raw = self::getRaw($lookup);
return $raw[$key] ?? null;
}
}

View File

@ -15,7 +15,7 @@ class ValueSetModel extends BaseModel {
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
public function getValueSets($param = null, $page = null, $limit = 50, $VSetID = null) {
public function getValueSets($param = null, $VSetID = null) {
$this->select("valueset.*, valuesetdef.VSName as VCategoryName")
->join('valuesetdef', 'valueset.VSetID = valuesetdef.VSetID', 'LEFT');
@ -31,13 +31,6 @@ class ValueSetModel extends BaseModel {
->groupEnd();
}
if ($page !== null) {
return [
'data' => $this->paginate($limit, 'default', $page),
'pager' => $this->pager
];
}
return $this->findAll();
}