fix(abdm): unify medication request FHIR representation across OPD and discharge (generic salt subtitle, SNOMED food timing, and natural dosage instructions)
This commit is contained in:
parent
a454bd178c
commit
d098221676
|
|
@ -280,7 +280,7 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
|
||||
$medCoding = [];
|
||||
if ($isSaltDistinct) {
|
||||
// Primary coding represents the generic substance / salt so PHR displays it in the subtitle line instead of "NA"
|
||||
// Primary coding represents the generic substance / salt
|
||||
$medCoding[] = [
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => $saltCode,
|
||||
|
|
@ -344,6 +344,23 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
],
|
||||
];
|
||||
|
||||
// Reason / Indication: Map salt composition if available (matches OPD Consult pattern so PHR displays salt instead of "NA")
|
||||
$saltReasonText = $salt;
|
||||
if ($saltReasonText === '' || ! $this->isMeaningfulValue($saltReasonText)) {
|
||||
$saltReasonText = $this->tryResolveGenericSalt($name, $medId);
|
||||
}
|
||||
if ($saltReasonText === '' || ! $this->isMeaningfulValue($saltReasonText)) {
|
||||
$firstCond = $diagnosesList[0]['name'] ?? ($diagnosesList[0]['text'] ?? ($source['problem'] ?? ''));
|
||||
$saltReasonText = is_string($firstCond) && trim($firstCond) !== '' ? trim($firstCond) : '';
|
||||
}
|
||||
if ($saltReasonText !== '') {
|
||||
$medResource['reasonCode'] = [
|
||||
[
|
||||
'text' => $saltReasonText,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if (is_array($practitioner)) {
|
||||
$medResource['requester'] = [
|
||||
'reference' => 'urn:uuid:' . (string) $practitioner['id'],
|
||||
|
|
@ -1800,18 +1817,22 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
|
||||
// Dose unit detection
|
||||
$doseUnit = 'Tablet';
|
||||
$doseValue = 1;
|
||||
$doseValue = '1';
|
||||
|
||||
if (preg_match('/\b(SACHET|POWDER)\b/', $raw)) {
|
||||
$doseUnit = 'Sachet';
|
||||
} elseif (preg_match('/\b(SYRUP|SUSPENSION|ML)\b/', $raw)) {
|
||||
$doseUnit = 'mL';
|
||||
$doseValue = 5;
|
||||
$doseValue = '5';
|
||||
} elseif (preg_match('/\b(CAPSULE|CAP)\b/', $raw)) {
|
||||
$doseUnit = 'Capsule';
|
||||
} elseif (preg_match('/\b(DROPS?)\b/', $raw)) {
|
||||
$doseUnit = 'drops';
|
||||
$doseValue = 2;
|
||||
$doseValue = '2';
|
||||
}
|
||||
|
||||
if (preg_match('/([0-9]+(?:\/[0-9]+|\.[0-9]+)?)\s*(?:Tab|Cap|Tablet|Capsule|Sachet|ml|drops)/i', $dosageText, $vm)) {
|
||||
$doseValue = $vm[1];
|
||||
}
|
||||
|
||||
// Duration detection
|
||||
|
|
|
|||
|
|
@ -170,6 +170,15 @@ class OpdFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\AbstractModul
|
|||
'dosageInstruction' => $dosageInstruction,
|
||||
];
|
||||
|
||||
$salt = trim((string) ($med['salt'] ?? $med['generic'] ?? $med['generic_name'] ?? ''));
|
||||
if ($salt !== '') {
|
||||
$medResource['reasonCode'] = [
|
||||
[
|
||||
'text' => $salt,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$builder->addMedicationRequest($medResource);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,15 @@ class PrescriptionFhirGenerator extends AbstractModuleFhirGenerator
|
|||
'dosageInstruction' => $dosageInstruction,
|
||||
];
|
||||
|
||||
$salt = trim((string) ($med['salt'] ?? $med['generic'] ?? $med['generic_name'] ?? ''));
|
||||
if ($salt !== '') {
|
||||
$medResource['reasonCode'] = [
|
||||
[
|
||||
'text' => $salt,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($encounterRef !== null) {
|
||||
$medResource['encounter'] = ['reference' => $encounterRef];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -400,58 +400,157 @@ class FhirR4Builder
|
|||
]];
|
||||
}
|
||||
|
||||
$dosageInstruction = ['text' => (string) ($medication['dosage'] ?? '')];
|
||||
$routeText = trim((string) ($medication['route_text'] ?? ''));
|
||||
$routeCode = trim((string) ($medication['route_code'] ?? ''));
|
||||
if (! $this->isMeaningfulValue($routeText)) {
|
||||
$medTypeUpper = strtoupper($medType);
|
||||
if (in_array($medTypeUpper, ['INJ', 'INJECTION', 'IV', 'IM'], true)) {
|
||||
$routeText = 'Injection';
|
||||
$routeCode = '47625008';
|
||||
} elseif (in_array($medTypeUpper, ['CREAM', 'OINT', 'OINTMENT', 'GEL', 'LOTION'], true)) {
|
||||
$routeText = 'Topical';
|
||||
$routeCode = '6064005';
|
||||
} else {
|
||||
$routeText = 'Oral';
|
||||
$routeCode = '260548002';
|
||||
}
|
||||
} elseif ($routeCode === '') {
|
||||
$routeCode = $this->resolveRouteSnomedCode($routeText);
|
||||
$rawDosageText = trim((string) ($medication['dosage'] ?? ''));
|
||||
$freqText = trim((string) ($medication['frequency_text'] ?? ''));
|
||||
$timingResult = $this->buildFhirTimingStructure($freqText, $rawDosageText);
|
||||
|
||||
$combinedDosageContext = strtoupper($rawDosageText . ' ' . $freqText . ' ' . $medType . ' ' . $drugName);
|
||||
|
||||
// 1. Food relation / Additional Instruction (Point 2: standard SNOMED 307166007 / 307165006)
|
||||
$addInstruct = null;
|
||||
$foodSuffix = '';
|
||||
if (preg_match('/\b(EMPTY STOMACH|BEFORE FOOD|BEFORE MEALS?|BF|AC)\b/', $combinedDosageContext)) {
|
||||
$addInstruct = [[
|
||||
'coding' => [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => '307165006',
|
||||
'display' => 'Before meals',
|
||||
]],
|
||||
'text' => 'Before food / Empty stomach',
|
||||
]];
|
||||
$foodSuffix = 'before food / empty stomach';
|
||||
} elseif (preg_match('/\b(AFTER FOOD|AFTER MEALS?|AF|PC)\b/', $combinedDosageContext)) {
|
||||
$addInstruct = [[
|
||||
'coding' => [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => '307166007',
|
||||
'display' => 'After meals',
|
||||
]],
|
||||
'text' => 'After food',
|
||||
]];
|
||||
$foodSuffix = 'after food';
|
||||
} elseif (preg_match('/\b(WITH FOOD|WITH MEALS?|WF)\b/', $combinedDosageContext)) {
|
||||
$addInstruct = [[
|
||||
'coding' => [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => '311504000',
|
||||
'display' => 'With or after food',
|
||||
]],
|
||||
'text' => 'With food',
|
||||
]];
|
||||
$foodSuffix = 'with food';
|
||||
}
|
||||
|
||||
$routeEntry = ['text' => $routeText];
|
||||
if ($this->isMeaningfulValue($routeCode) && $routeCode !== '371530004') {
|
||||
$routeEntry['coding'] = [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => $routeCode,
|
||||
'display' => $routeText,
|
||||
]];
|
||||
} else {
|
||||
$routeEntry['coding'] = [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => '260548002',
|
||||
'display' => $routeText,
|
||||
]];
|
||||
// 2. Timing display & frequency
|
||||
$timingDisplay = 'Once daily';
|
||||
if (! empty($timingResult['timing']['code']['coding'][0]['display'])) {
|
||||
$timingDisplay = (string) $timingResult['timing']['code']['coding'][0]['display'];
|
||||
} elseif (preg_match('/\b(TDS|TID|THRICE|3 TIMES)\b/', $combinedDosageContext)) {
|
||||
$timingDisplay = 'Three times daily';
|
||||
} elseif (preg_match('/\b(QID|4 TIMES)\b/', $combinedDosageContext)) {
|
||||
$timingDisplay = 'Four times daily';
|
||||
} elseif (preg_match('/\b(BD|BID|TWICE|2 TIMES)\b/', $combinedDosageContext)) {
|
||||
$timingDisplay = 'Twice daily';
|
||||
} elseif (preg_match('/\b(SOS|PRN|AS NEEDED)\b/', $combinedDosageContext)) {
|
||||
$timingDisplay = 'As needed';
|
||||
} elseif (preg_match('/\b(HS|BEDTIME|AT NIGHT)\b/', $combinedDosageContext)) {
|
||||
$timingDisplay = 'At bedtime';
|
||||
} elseif (preg_match('/\b(STAT|ONCE|NOW)\b/', $combinedDosageContext)) {
|
||||
$timingDisplay = 'Immediately';
|
||||
}
|
||||
$dosageInstruction['route'] = $routeEntry;
|
||||
|
||||
$methodText = trim((string) ($medication['method_text'] ?? $medType));
|
||||
$methodCode = trim((string) ($medication['method_code'] ?? ''));
|
||||
if ($this->isMeaningfulValue($methodText)) {
|
||||
$methodEntry = ['text' => $methodText];
|
||||
if ($this->isMeaningfulValue($methodCode) && $methodCode !== '371530004') {
|
||||
$methodEntry['coding'] = [[
|
||||
// 3. Route & Method detection (Standard SNOMED: Oral Route 26643006 / Swallow 421521009)
|
||||
$medTypeUpper = strtoupper($medType);
|
||||
$routeCode = '26643006';
|
||||
$routeDisplay = 'Oral Route';
|
||||
$routeText = 'Oral Route';
|
||||
$methodCode = '421521009'; // Swallow
|
||||
$methodDisplay = 'Swallow';
|
||||
|
||||
if (preg_match('/\b(INJ|IV|INTRAVENOUS|INFUSION)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['INJ', 'INJECTION', 'IV', 'INFUSION'], true)) {
|
||||
$routeCode = '47625008';
|
||||
$routeDisplay = 'Intravenous route';
|
||||
$routeText = 'Intravenous (IV)';
|
||||
$methodCode = '422145002';
|
||||
$methodDisplay = 'Inject';
|
||||
} elseif (preg_match('/\b(IM|INTRAMUSCULAR)\b/', $combinedDosageContext) || $medTypeUpper === 'IM') {
|
||||
$routeCode = '78421000';
|
||||
$routeDisplay = 'Intramuscular route';
|
||||
$routeText = 'Intramuscular (IM)';
|
||||
$methodCode = '422145002';
|
||||
$methodDisplay = 'Inject';
|
||||
} elseif (preg_match('/\b(DROPS?|EYE|EAR|OPHTHALMIC)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['DROPS', 'DROP', 'EYE DROP', 'EAR DROP'], true)) {
|
||||
$routeCode = '54485002';
|
||||
$routeDisplay = 'Ophthalmic route';
|
||||
$routeText = 'Drops';
|
||||
$methodCode = '421553004';
|
||||
$methodDisplay = 'Instill';
|
||||
} elseif (preg_match('/\b(CREAM|OINTMENT|GEL|TOPICAL|LOTION)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['CREAM', 'OINT', 'OINTMENT', 'GEL', 'LOTION'], true)) {
|
||||
$routeCode = '6064005';
|
||||
$routeDisplay = 'Topical route';
|
||||
$routeText = 'Topical';
|
||||
$methodCode = '417924000';
|
||||
$methodDisplay = 'Apply';
|
||||
}
|
||||
|
||||
// 4. Dose value & unit
|
||||
$doseUnit = 'Tablet';
|
||||
$doseValue = '1';
|
||||
if (preg_match('/\b(SACHET|POWDER)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['SACHET', 'POWDER'], true)) {
|
||||
$doseUnit = 'Sachet';
|
||||
} elseif (preg_match('/\b(SYRUP|SUSPENSION|ML)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['SYP', 'SYRUP', 'SUSP', 'SUSPENSION', 'ML'], true)) {
|
||||
$doseUnit = 'mL';
|
||||
$doseValue = '5';
|
||||
} elseif (preg_match('/\b(CAPSULE|CAP)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['CAP', 'CAPSULE'], true)) {
|
||||
$doseUnit = 'Capsule';
|
||||
} elseif (preg_match('/\b(DROPS?)\b/', $combinedDosageContext) || in_array($medTypeUpper, ['DROPS', 'DROP'], true)) {
|
||||
$doseUnit = 'drops';
|
||||
$doseValue = '2';
|
||||
}
|
||||
|
||||
if (preg_match('/([0-9]+(?:\/[0-9]+|\.[0-9]+)?)\s*(?:Tab|Cap|Tablet|Capsule|Sachet|ml|drops)/i', $rawDosageText, $vm)) {
|
||||
$doseValue = $vm[1];
|
||||
}
|
||||
|
||||
// 5. Duration
|
||||
$durationDays = 0;
|
||||
if (preg_match('/(?:FOR\s*)?(\d+)\s*DAYS?/i', $rawDosageText, $dm)) {
|
||||
$durationDays = (int) $dm[1];
|
||||
}
|
||||
|
||||
// 6. Build natural, understandable sentence (Point 3)
|
||||
$instructionText = 'Take ' . $doseValue . ' ' . $doseUnit . ' ' . $timingDisplay;
|
||||
if ($foodSuffix !== '') {
|
||||
$instructionText .= ' ' . $foodSuffix;
|
||||
}
|
||||
if ($durationDays > 0) {
|
||||
$instructionText .= ' for ' . $durationDays . ' days';
|
||||
}
|
||||
|
||||
$dosageInstruction = [
|
||||
'text' => $instructionText,
|
||||
'route' => [
|
||||
'coding' => [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => $routeCode,
|
||||
'display' => $routeDisplay,
|
||||
]],
|
||||
'text' => $routeText,
|
||||
],
|
||||
'method' => [
|
||||
'coding' => [[
|
||||
'system' => 'http://snomed.info/sct',
|
||||
'code' => $methodCode,
|
||||
'display' => $methodText,
|
||||
]];
|
||||
}
|
||||
$dosageInstruction['method'] = $methodEntry;
|
||||
'display' => $methodDisplay,
|
||||
]],
|
||||
'text' => $methodDisplay,
|
||||
],
|
||||
];
|
||||
|
||||
if ($addInstruct !== null) {
|
||||
$dosageInstruction['additionalInstruction'] = $addInstruct;
|
||||
}
|
||||
|
||||
$freqText = trim((string) ($medication['frequency_text'] ?? ''));
|
||||
$dosageText = (string) ($medication['dosage'] ?? '');
|
||||
$timingResult = $this->buildFhirTimingStructure($freqText, $dosageText);
|
||||
if ($timingResult['timing'] !== null) {
|
||||
$dosageInstruction['timing'] = $timingResult['timing'];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user