logSnapshotMemory('snapshots:start', [ 'report_id' => $report->report_id, 'department_id' => $report->rf_department_id, 'fillable_auto' => (bool) $fillableAuto, ]); $department = Department::query()->where('department_id', $report->rf_department_id)->first() ?? $user->department; $branchId = $department ? $this->getBranchId($department->rf_mis_department_id) : null; if (! $department || ! $branchId) { return; } MedicalHistorySnapshot::query() ->where('rf_report_id', $report->report_id) ->delete(); $this->logSnapshotMemory('snapshots:after_delete_old', [ 'report_id' => $report->report_id, ]); [$startDate, $endDate] = $this->parseDates($dates); $dateRange = $this->dateRangeService->getNormalizedDateRange($user, $startDate, $endDate); $metrics = []; $this->logSnapshotMemory('snapshots:before_plan_load', ['report_id' => $report->report_id]); $planPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'plan', $dateRange, $branchId, false, ! $fillableAuto, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_plan_load', [ 'report_id' => $report->report_id, 'count' => $planPatients->count(), ]); $this->createSnapshotsForType($report, 'plan', $planPatients); $this->logSnapshotMemory('snapshots:after_plan_save', ['report_id' => $report->report_id]); $metrics[4] = $planPatients->count(); $this->logSnapshotMemory('snapshots:before_emergency_load', ['report_id' => $report->report_id]); $emergencyPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'emergency', $dateRange, $branchId, false, ! $fillableAuto, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_emergency_load', [ 'report_id' => $report->report_id, 'count' => $emergencyPatients->count(), ]); $this->createSnapshotsForType($report, 'emergency', $emergencyPatients); $this->logSnapshotMemory('snapshots:after_emergency_save', ['report_id' => $report->report_id]); $metrics[12] = $emergencyPatients->count(); $this->logSnapshotMemory('snapshots:before_discharged_load', ['report_id' => $report->report_id]); $dischargedPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'outcome-discharged', $dateRange, $branchId, false, null, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_discharged_load', [ 'report_id' => $report->report_id, 'count' => $dischargedPatients->count(), ]); $this->createSnapshotsForType($report, 'discharged', $dischargedPatients); $this->logSnapshotMemory('snapshots:after_discharged_save', ['report_id' => $report->report_id]); $metrics[15] = $dischargedPatients->count(); $this->logSnapshotMemory('snapshots:before_transferred_load', ['report_id' => $report->report_id]); $transferredPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'outcome-transferred', $dateRange, $branchId, false, null, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_transferred_load', [ 'report_id' => $report->report_id, 'count' => $transferredPatients->count(), ]); $this->createSnapshotsForType($report, 'transferred', $transferredPatients); $this->logSnapshotMemory('snapshots:after_transferred_save', ['report_id' => $report->report_id]); $metrics[13] = $transferredPatients->count(); $this->logSnapshotMemory('snapshots:before_deceased_load', ['report_id' => $report->report_id]); $deceasedPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'outcome-deceased', $dateRange, $branchId, false, null, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_deceased_load', [ 'report_id' => $report->report_id, 'count' => $deceasedPatients->count(), ]); $this->createSnapshotsForType($report, 'deceased', $deceasedPatients); $this->logSnapshotMemory('snapshots:after_deceased_save', ['report_id' => $report->report_id]); $this->logSnapshotMemory('snapshots:before_recipient_load', ['report_id' => $report->report_id]); $recipientPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'recipient', $dateRange, $branchId, false, null, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_recipient_load', [ 'report_id' => $report->report_id, 'count' => $recipientPatients->count(), ]); $this->createSnapshotsForType($report, 'recipient', $recipientPatients); $this->logSnapshotMemory('snapshots:after_recipient_save', ['report_id' => $report->report_id]); $this->logSnapshotMemory('snapshots:before_current_load', ['report_id' => $report->report_id]); $currentPatients = $this->unifiedPatientService->getLivePatientsByStatus( $department, $user, 'current', $dateRange, $branchId, false, null, $fillableAuto, true ); $this->logSnapshotMemory('snapshots:after_current_load', [ 'report_id' => $report->report_id, 'count' => $currentPatients->count(), ]); $this->createSnapshotsForType($report, 'current', $currentPatients); $this->logSnapshotMemory('snapshots:after_current_save', ['report_id' => $report->report_id]); $planSurgeryCount = $this->patientService->getSurgicalPatients( 'plan', $branchId, $dateRange, true ); $emergencySurgeryCount = $this->patientService->getSurgicalPatients( 'emergency', $branchId, $dateRange, true ); $this->saveMetrics($report, $metrics); $this->logSnapshotMemory('snapshots:after_save_metrics', ['report_id' => $report->report_id]); } private function logSnapshotMemory(string $stage, array $context = []): void { \Log::info('report.snapshot.memory', [ 'stage' => $stage, 'memory_mb' => round(memory_get_usage(true) / 1024 / 1024, 2), 'peak_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2), ...$context, ]); } /** * Сохранить метрики в базу */ private function saveMetrics(Report $report, array $metrics): void { foreach ($metrics as $metrikaItemId => $value) { MetrikaResult::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'rf_metrika_item_id' => $metrikaItemId, ], [ 'value' => $value, ] ); } } /** * Получить статистику из снапшотов */ public function getStatisticsFromSnapshots(array $reportIds): array { return [ 'plan' => $this->getCountFromSnapshots('plan', $reportIds), 'emergency' => $this->getCountFromSnapshots('emergency', $reportIds), 'outcome' => $this->getCountFromSnapshots('outcome', $reportIds), 'deceased' => $this->getCountFromSnapshots('deceased', $reportIds), 'discharged' => $this->getCountFromSnapshots('discharged', $reportIds), 'transferred' => $this->getCountFromSnapshots('transferred', $reportIds), 'recipient' => $this->getCountFromSnapshots('recipient', $reportIds), ]; } /** * Получить пациентов из снапшотов по типу */ public function getPatientsFromSnapshots( string $type, array $reportIds, ?int $branchId = null, bool $onlyIds = false, bool $markRecipients = false, ?array $recipientReportIds = null ): Collection { $snapshots = MedicalHistorySnapshot::query() ->whereIn('rf_report_id', $reportIds) ->where('patient_type', $type) ->get() ->unique(fn (MedicalHistorySnapshot $snapshot) => $snapshot->patient_uid ?: ($snapshot->rf_medicalhistory_id ? "mis:{$snapshot->rf_medicalhistory_id}" : "snapshot:{$snapshot->medical_history_snapshot_id}")) ->values(); if ($snapshots->isEmpty()) { return collect(); } $recipientIds = []; if ($markRecipients) { $recipientReportIds ??= $reportIds; $recipientIds = MedicalHistorySnapshot::query() ->whereIn('rf_report_id', $recipientReportIds) ->where('patient_type', 'recipient') ->get() ->map(fn (MedicalHistorySnapshot $snapshot) => UnifiedPatientData::fromSnapshot($snapshot)->id) ->unique() ->values() ->all(); } $operationsByHistoryId = $this->getOperationsByMedicalHistoryId($snapshots); $operationsByDepartmentPatientId = $this->getOperationsByDepartmentPatientId($snapshots); $patients = $snapshots->map(function (MedicalHistorySnapshot $snapshot) use ($recipientIds, $operationsByHistoryId, $operationsByDepartmentPatientId) { $patientId = $snapshot->rf_department_patient_id ? "manual:{$snapshot->rf_department_patient_id}" : ($snapshot->patient_uid ?: "mis:{$snapshot->rf_medicalhistory_id}"); $misOperations = $snapshot->rf_medicalhistory_id ? ($operationsByHistoryId[$snapshot->rf_medicalhistory_id] ?? []) : []; $manualOperations = $snapshot->rf_department_patient_id ? ($operationsByDepartmentPatientId[$snapshot->rf_department_patient_id] ?? []) : []; $operations = collect($misOperations) ->merge($manualOperations) ->unique(fn (array $operation) => ($operation['code'] ?? '').'|'.($operation['name'] ?? '')) ->values() ->all(); return UnifiedPatientData::fromSnapshot( $snapshot, in_array($patientId, $recipientIds, true), $operations ); })->sortByDesc(fn (UnifiedPatientData $patient) => $patient->admittedAt ?? '')->values(); if ($onlyIds) { return $patients->pluck('id'); } return $patients; } public function getPatientsFromOneDayCurrentSnapshots( string $type, array $reportIds, bool $onlyIds = false, ?array $recipientReportIds = null ): Collection { $snapshots = MedicalHistorySnapshot::query() ->whereIn('rf_report_id', $reportIds) ->where('patient_type', 'current') ->get(); if ($snapshots->isEmpty()) { return $this->getPatientsFromSnapshots( $type, $reportIds, null, $onlyIds, true, $recipientReportIds ); } $recipientReportIds ??= $reportIds; $recipientIds = MedicalHistorySnapshot::query() ->whereIn('rf_report_id', $recipientReportIds) ->where('patient_type', 'recipient') ->get() ->map(fn (MedicalHistorySnapshot $snapshot) => UnifiedPatientData::fromSnapshot($snapshot)->id) ->unique() ->values() ->all(); $operationsByHistoryId = $this->getOperationsByMedicalHistoryId($snapshots); $operationsByDepartmentPatientId = $this->getOperationsByDepartmentPatientId($snapshots); $patients = $snapshots ->filter(fn (MedicalHistorySnapshot $snapshot) => $snapshot->patient_kind === $type) ->unique(fn (MedicalHistorySnapshot $snapshot) => $snapshot->patient_uid ?: ($snapshot->rf_medicalhistory_id ? "mis:{$snapshot->rf_medicalhistory_id}" : "snapshot:{$snapshot->medical_history_snapshot_id}")) ->map(function (MedicalHistorySnapshot $snapshot) use ($recipientIds, $operationsByHistoryId, $operationsByDepartmentPatientId) { $misOperations = $snapshot->rf_medicalhistory_id ? ($operationsByHistoryId[$snapshot->rf_medicalhistory_id] ?? []) : []; $manualOperations = $snapshot->rf_department_patient_id ? ($operationsByDepartmentPatientId[$snapshot->rf_department_patient_id] ?? []) : []; $operations = collect($misOperations) ->merge($manualOperations) ->unique(fn (array $operation) => ($operation['code'] ?? '').'|'.($operation['name'] ?? '')) ->values() ->all(); $patient = UnifiedPatientData::fromSnapshot( $snapshot, false, $operations ); $patient->isRecipientToday = in_array($patient->id, $recipientIds, true); return $patient; }) ->sortByDesc(fn (UnifiedPatientData $patient) => $patient->admittedAt ?? '') ->values(); if ($onlyIds) { return $patients->pluck('id'); } return $patients; } /** * Получить количество пациентов из снапшотов */ private function getCountFromSnapshots(string $type, array $reportIds): int { $query = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds); if ($type === 'outcome') { $query->whereIn('patient_type', ['discharged', 'deceased']); } else { $query->where('patient_type', $type); } return $query->get() ->map(fn (MedicalHistorySnapshot $snapshot) => $snapshot->patient_uid ?: ($snapshot->rf_medicalhistory_id ? "mis:{$snapshot->rf_medicalhistory_id}" : "snapshot:{$snapshot->medical_history_snapshot_id}")) ->unique() ->count(); } /** * Создать снапшоты для определенного типа пациентов */ private function createSnapshotsForType(Report $report, string $type, Collection $patients): void { foreach ($patients as $patient) { if (! $patient instanceof UnifiedPatientData) { continue; } MedicalHistorySnapshot::updateOrCreate( [ 'rf_report_id' => $report->report_id, 'patient_uid' => $patient->patientUid, 'patient_type' => $type, ], [ 'rf_report_id' => $report->report_id, 'patient_type' => $type, ...$patient->toSnapshotPayload($type), ] ); } } /** * Получить ID отделения */ private function getBranchId(int $misDepartmentId): ?int { return MisStationarBranch::where('rf_DepartmentID', $misDepartmentId) ->value('StationarBranchID'); } /** * Разобрать даты */ private function parseDates(array $dates): array { return [ Carbon::createFromTimestampMs($dates[0])->setTimezone('Asia/Yakutsk'), Carbon::createFromTimestampMs($dates[1])->setTimezone('Asia/Yakutsk'), ]; } private function getOperationsByMedicalHistoryId(Collection $snapshots): array { $historyIds = $snapshots->pluck('rf_medicalhistory_id')->filter()->unique()->values(); if ($historyIds->isEmpty()) { return []; } return MisMedicalHistory::query() ->whereIn('MedicalHistoryID', $historyIds) ->with(['surgicalOperations.serviceMedical']) ->get() ->mapWithKeys(function (MisMedicalHistory $history) { return [ $history->MedicalHistoryID => $history->surgicalOperations->map(fn ($operation) => [ 'code' => $operation->serviceMedical?->ServiceMedicalCode, 'name' => $operation->serviceMedical?->ServiceMedicalName, ])->values()->all(), ]; }) ->all(); } private function getOperationsByDepartmentPatientId(Collection $snapshots): array { $departmentPatientIds = $snapshots->pluck('rf_department_patient_id')->filter()->unique()->values(); if ($departmentPatientIds->isEmpty()) { return []; } return DepartmentPatientOperation::query() ->whereIn('rf_department_patient_id', $departmentPatientIds) ->with('serviceMedical') ->get() ->groupBy('rf_department_patient_id') ->map(fn (Collection $operations) => $operations->map(fn (DepartmentPatientOperation $operation) => [ 'code' => $operation->serviceMedical?->ServiceMedicalCode ?? $operation->service_code, 'name' => $operation->serviceMedical?->ServiceMedicalName ?? $operation->service_name, ])->values()->all()) ->all(); } }