Обновлен стартовый экран
Переписаны запросы для статистики, отчетов Добавлена интеграция отчета сестры
This commit is contained in:
@@ -2,9 +2,13 @@
|
||||
namespace App\Services\Classification;
|
||||
|
||||
use App\Models\MedicalHistory;
|
||||
use App\Models\ObservableMedicalHistory;
|
||||
use App\Models\ReportDutyPatient;
|
||||
use App\Models\ReportNursePatient;
|
||||
use App\Models\UnifiedMedicalHistory;
|
||||
use App\Services\DateRange;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class PatientStatusClassifier
|
||||
{
|
||||
@@ -32,53 +36,317 @@ class PatientStatusClassifier
|
||||
/**
|
||||
* Определяет поступил ли пациент в диапазоне.
|
||||
*/
|
||||
public static function classifyAdmitted(Carbon|string|null $ingoingDate): bool
|
||||
public static function classifyAdmitted(Carbon|string|null $ingoingDate, DateRange $dateRange): bool
|
||||
{
|
||||
if (is_null($ingoingDate)) {return false;}
|
||||
if (is_null($ingoingDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ingoingLocal = Carbon::parse($ingoingDate);
|
||||
$now = Carbon::now();
|
||||
|
||||
// Окно смены: вчера 09:00 → сегодня 09:00
|
||||
$shiftStart = $now->copy()->subDay()->setTime(9, 0);
|
||||
$shiftEnd = $now->copy()->setTime(9, 0);
|
||||
// Начало диапазона (с 09:00 первого дня)
|
||||
$rangeStart = $dateRange->start()->copy()->setTime(9, 0, 0);
|
||||
|
||||
return $ingoingLocal->between($shiftStart, $shiftEnd);
|
||||
// Конец диапазона (до 09:00 последнего дня)
|
||||
$rangeEnd = $dateRange->end()->copy()->setTime(9, 0, 0);
|
||||
|
||||
return $ingoingLocal->between($rangeStart, $rangeEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection|null $reanimations
|
||||
* @param DateRange $dateRange
|
||||
* @return bool
|
||||
*/
|
||||
public static function classifyReanimation(Collection|null $reanimations, DateRange $dateRange): bool
|
||||
{
|
||||
if ($reanimations === null || $reanimations->isEmpty()) return false;
|
||||
if ($reanimations
|
||||
->where('out_date', '<', $dateRange->endSql())
|
||||
->where('out_date', '>=', $dateRange->startSql())
|
||||
->isNotEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function classifyObservable(?ObservableMedicalHistory $observable, DateRange $dateRange): bool
|
||||
{
|
||||
if (empty($observable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start = $dateRange->start();
|
||||
$end = $dateRange->end();
|
||||
|
||||
// Наблюдение началось после окончания диапазона
|
||||
if ($observable->observable_in > $end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Наблюдение закончилось до начала диапазона
|
||||
if ($observable->observable_out !== null && $observable->observable_out <= $start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function classifyPeriodFlags(
|
||||
UnifiedMedicalHistory|MedicalHistory|ReportDutyPatient|ReportNursePatient $history,
|
||||
DateRange $dateRange
|
||||
): array {
|
||||
// Получаем все миграции
|
||||
$migrations = self::historyMigrations($history);
|
||||
|
||||
if ($migrations->isEmpty()) {
|
||||
return self::emptyFlags($history);
|
||||
}
|
||||
|
||||
// Приводим даты к Carbon
|
||||
$deathDate = self::toCarbon($history->death_date ?? null);
|
||||
|
||||
// Получаем границы периода
|
||||
$periodStart = $dateRange->start();
|
||||
$periodEnd = $dateRange->end();
|
||||
|
||||
// ========== 1. СМЕРТЬ ==========
|
||||
// if ($deathDate && $deathDate->lte($periodEnd)) {
|
||||
// return [
|
||||
// 'recipient' => false,
|
||||
// 'discharged' => false,
|
||||
// 'deceased' => true,
|
||||
// 'transferred' => false,
|
||||
// 'outcome' => true,
|
||||
// 'current_at_end' => false,
|
||||
// 'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
// 'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
// ];
|
||||
// }
|
||||
|
||||
// ========== 2. АНАЛИЗ МИГРАЦИЙ ==========
|
||||
$hasRecipientInPeriod = false;
|
||||
$hasDeathInPeriod = false;
|
||||
$hasTransferInPeriod = false;
|
||||
$hasDischargeInPeriod = false;
|
||||
$hasActiveMigrationAtEnd = false;
|
||||
$transferred = [];
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$ingoingDate = self::toCarbon($migration->ingoing_date ?? null);
|
||||
$outDate = self::toCarbon($migration->out_date ?? null);
|
||||
$visitResultId = (int) ($migration->visit_result_id ?? 0);
|
||||
$statCureResultId = (int) ($migration->stat_cure_result_id ?? 0);
|
||||
|
||||
// Поступление в периоде
|
||||
if ($ingoingDate && self::dateInPeriod($ingoingDate, $dateRange)) {
|
||||
$hasRecipientInPeriod = true;
|
||||
}
|
||||
|
||||
// Проверка на активную миграцию в конце периода
|
||||
if ($ingoingDate && $ingoingDate <= $periodEnd) {
|
||||
if (!$outDate || $outDate > $periodEnd) {
|
||||
$hasActiveMigrationAtEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Выбытие в периоде (есть out_date в периоде)
|
||||
if ($outDate && self::dateInPeriod($outDate, $dateRange)) {
|
||||
// Смерть по исходу лечения (5, 15)
|
||||
if (in_array($visitResultId, [5, 15], true)) {
|
||||
$hasDeathInPeriod = true;
|
||||
}
|
||||
// Перевод (коды 4, 14)
|
||||
elseif (in_array($visitResultId, [4, 14], true)) {
|
||||
$hasTransferInPeriod = true;
|
||||
}
|
||||
// Выписка
|
||||
else {
|
||||
$hasDischargeInPeriod = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 3. ЕСЛИ НЕТ ИСХОДА ПО МИГРАЦИЯМ, ПРОВЕРЯЕМ EXTRACT_DATE ==========
|
||||
// if (!$hasDeathInPeriod && !$hasTransferInPeriod && !$hasDischargeInPeriod) {
|
||||
// $extractDate = self::toCarbon($history->extract_date ?? null);
|
||||
// $visitResultId = (int) ($history->visit_result_id ?? 0);
|
||||
//
|
||||
// if ($extractDate && $extractDate->lte($periodEnd)) {
|
||||
// // Смерть
|
||||
// if ($deathDate && $deathDate->lte($periodEnd)) {
|
||||
// $hasDeathInPeriod = true;
|
||||
// }
|
||||
// // Перевод
|
||||
// elseif (in_array($visitResultId, [4, 14], true)) {
|
||||
// $hasTransferInPeriod = true;
|
||||
// }
|
||||
// // Выписка
|
||||
// else {
|
||||
// $hasDischargeInPeriod = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// ========== 4. ФОРМИРОВАНИЕ РЕЗУЛЬТАТА ==========
|
||||
|
||||
// Смерть
|
||||
if ($hasDeathInPeriod) {
|
||||
return [
|
||||
'recipient' => $hasRecipientInPeriod,
|
||||
'discharged' => false,
|
||||
'deceased' => true,
|
||||
'transferred' => false,
|
||||
'outcome' => false,
|
||||
'current_at_end' => false,
|
||||
'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
];
|
||||
}
|
||||
|
||||
// Перевод
|
||||
if ($hasTransferInPeriod) {
|
||||
return [
|
||||
'recipient' => $hasRecipientInPeriod,
|
||||
'discharged' => false,
|
||||
'deceased' => false,
|
||||
'transferred' => true,
|
||||
'outcome' => false,
|
||||
'current_at_end' => false,
|
||||
'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
];
|
||||
}
|
||||
|
||||
// Выписка
|
||||
if ($hasDischargeInPeriod) {
|
||||
return [
|
||||
'recipient' => $hasRecipientInPeriod,
|
||||
'discharged' => true,
|
||||
'deceased' => false,
|
||||
'transferred' => false,
|
||||
'outcome' => true,
|
||||
'current_at_end' => false,
|
||||
'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
];
|
||||
}
|
||||
|
||||
// В отделении на конец периода
|
||||
if ($hasActiveMigrationAtEnd) {
|
||||
return [
|
||||
'recipient' => $hasRecipientInPeriod,
|
||||
'discharged' => false,
|
||||
'deceased' => false,
|
||||
'transferred' => false,
|
||||
'outcome' => false,
|
||||
'current_at_end' => true,
|
||||
'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
];
|
||||
}
|
||||
|
||||
// По умолчанию - поступивший, но не в отделении (например, выписан до периода)
|
||||
return [
|
||||
'recipient' => $hasRecipientInPeriod,
|
||||
'discharged' => false,
|
||||
'deceased' => false,
|
||||
'transferred' => false,
|
||||
'outcome' => false,
|
||||
'current_at_end' => false,
|
||||
'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Пустые флаги для пациента без миграций
|
||||
*/
|
||||
private static function emptyFlags($history): array
|
||||
{
|
||||
return [
|
||||
'recipient' => false,
|
||||
'discharged' => false,
|
||||
'deceased' => false,
|
||||
'transferred' => false,
|
||||
'outcome' => false,
|
||||
'current_at_end' => false,
|
||||
'planned' => (int) ($history->urgency_id ?? 0) === 2,
|
||||
'urgent' => (int) ($history->urgency_id ?? 0) === 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Определяет статус пациента на основе "сырых" полей из БД.
|
||||
* Логика изолирована и может быть легко протестирована.
|
||||
*/
|
||||
public static function classify(UnifiedMedicalHistory|MedicalHistory $history, DateRange $dateRange): string
|
||||
public static function classify(UnifiedMedicalHistory|MedicalHistory|ReportDutyPatient|ReportNursePatient $history, DateRange $dateRange): string
|
||||
{
|
||||
// 1. Смерть — приоритет №1 (не зависит от дат)
|
||||
if (!empty($history->death_date)) {
|
||||
$flags = self::classifyPeriodFlags($history, $dateRange);
|
||||
|
||||
if ($flags['deceased']) {
|
||||
return self::STATUS_DECEASED;
|
||||
}
|
||||
|
||||
// 2. Если есть дата выбытия
|
||||
if (!empty($history->extract_date)) {
|
||||
// Переведён (коды 3, 4)
|
||||
if (in_array($history->visit_result_id, [3, 4], true)) {
|
||||
return self::STATUS_TRANSFERRED;
|
||||
}
|
||||
// Выписан домой/иное (исключаем коды 3-6)
|
||||
if (in_array($history->visit_result_id, [1], true)) {
|
||||
return self::STATUS_DISCHARGED;
|
||||
}
|
||||
if ($flags['transferred']) {
|
||||
return self::STATUS_TRANSFERRED;
|
||||
}
|
||||
|
||||
// 3. Поступившие
|
||||
if ($history->latestMigration?->getAdmittedInCurrentAttribute()) {
|
||||
if ($flags['discharged']) {
|
||||
return self::STATUS_DISCHARGED;
|
||||
}
|
||||
if ($flags['recipient']) {
|
||||
return self::STATUS_RECIPIENT;
|
||||
}
|
||||
|
||||
// 4. В отделении
|
||||
if (empty($history->latestMigration?->out_date) && $history->latestMigration?->ingoing_date < $dateRange->startDate) {
|
||||
if ($flags['current_at_end']) {
|
||||
return self::STATUS_IN_DEPARTMENT;
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
private static function historyMigrations(
|
||||
UnifiedMedicalHistory|MedicalHistory|ReportDutyPatient|ReportNursePatient $history
|
||||
): Collection {
|
||||
if (method_exists($history, 'relationLoaded') && $history->relationLoaded('migrations')) {
|
||||
return $history->migrations ?? collect();
|
||||
}
|
||||
|
||||
return collect([$history->latestMigration])->filter();
|
||||
}
|
||||
|
||||
private static function isCurrentAtPeriodEnd(
|
||||
UnifiedMedicalHistory|MedicalHistory|ReportDutyPatient|ReportNursePatient $history,
|
||||
Collection $migrations,
|
||||
DateRange $dateRange
|
||||
): bool {
|
||||
if (self::toCarbon($history->death_date ?? null)?->lte($dateRange->endDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::toCarbon($history->extract_date ?? null)?->lte($dateRange->endDate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $migrations->contains(function ($migration) use ($dateRange) {
|
||||
$ingoingDate = self::toCarbon($migration->ingoing_date ?? null);
|
||||
$outDate = self::toCarbon($migration->out_date ?? null);
|
||||
|
||||
return $ingoingDate
|
||||
&& $ingoingDate->lte($dateRange->endDate)
|
||||
&& (! $outDate || $outDate->gt($dateRange->endDate));
|
||||
});
|
||||
}
|
||||
|
||||
public static function dateInPeriod(Carbon|string|null $date, DateRange $dateRange): bool
|
||||
{
|
||||
$date = self::toCarbon($date);
|
||||
|
||||
return $date
|
||||
&& $date->gt($dateRange->startDate)
|
||||
&& $date->lte($dateRange->endDate);
|
||||
}
|
||||
|
||||
private static function toCarbon(Carbon|string|null $date): ?Carbon
|
||||
{
|
||||
return $date ? Carbon::parse($date) : null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user