Files
onboard/app/Services/BedDayService.php
2026-03-25 17:37:32 +09:00

332 lines
12 KiB
PHP

<?php
// app/Services/BedDayService.php
namespace App\Services;
use App\Models\MetrikaResult;
use App\Models\Report;
use App\Models\MedicalHistorySnapshot;
use App\Models\MisMedicalHistory;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Collection;
class BedDayService
{
/**
* ID метрики для среднего койко-дня
*/
const METRIC_BED_DAYS_ID = 18;
/**
* Кэш для хранения результатов запросов
*/
protected array $cache = [];
/**
* Получить средний койко-день для отделения из снапшотов за период
*/
public function getAverageBedDaysFromSnapshots(int $departmentId, string $startDate, string $endDate, bool $isRangeOneDay): float
{
$cacheKey = "snapshots_{$departmentId}_{$startDate}_{$endDate}_" . ($isRangeOneDay ? '1day' : 'range');
if (isset($this->cache[$cacheKey])) {
return $this->cache[$cacheKey];
}
// Для одного дня берем последние 30 дней для статистической значимости
$actualStartDate = $isRangeOneDay
? Carbon::now('Asia/Yakutsk')->startOfYear()->format('Y-m-d')
: $startDate;
// Находим отчеты за период
$reports = Report::where('rf_department_id', $departmentId)
// ->whereBetween('created_at', [$actualStartDate, $endDate])
->where('sent_at', '>=', $actualStartDate)
->where('sent_at', '<=', $endDate)
->pluck('report_id');
if ($reports->isEmpty()) {
$this->cache[$cacheKey] = 0;
return 0;
}
// Получаем все снапшоты выписанных пациентов из этих отчетов
$snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reports)
->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED)
->distinct()
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
$this->cache[$cacheKey] = 0;
return 0;
}
// Рассчитываем средний койко-день
$totalDays = 0;
$validCount = 0;
foreach ($snapshots as $snapshot) {
$history = $snapshot->medicalHistory;
if ($history && $history->DateRecipient && $history->DateExtract) {
$start = Carbon::parse($history->DateRecipient);
$end = Carbon::parse($history->DateExtract);
// Проверяем, что дата выписки входит в период отчета
if ($end->between($actualStartDate, $endDate)) {
$days = $start->diffInDays($end);
$totalDays += $days;
$validCount++;
}
}
}
$avgDays = $validCount > 0 ? round($totalDays / $validCount, 1) : 0;
$this->cache[$cacheKey] = $avgDays;
return $avgDays;
}
/**
* Получить средние койко-дни для всех отделений из снапшотов
*/
public function getAverageBedDaysByDepartmentsFromSnapshots(array $departmentIds, string $startDate, string $endDate, bool $isRangeOneDay): array
{
if (empty($departmentIds)) {
return [];
}
$cacheKey = 'all_snapshots_' . md5(implode(',', $departmentIds) . $startDate . $endDate . ($isRangeOneDay ? '1day' : 'range'));
if (isset($this->cache[$cacheKey])) {
return $this->cache[$cacheKey];
}
$actualStartDate = $isRangeOneDay
? Carbon::parse($endDate)->subDays(30)->format('Y-m-d')
: $startDate;
// Находим все отчеты за период по отделениям
$reportsByDepartment = Report::whereIn('rf_department_id', $departmentIds)
// ->whereBetween('created_at', [$actualStartDate, $endDate])
->where('sent_at', '>=', $actualStartDate)
->where('sent_at', '<=', $endDate)
->select('report_id', 'rf_department_id')
->get()
->groupBy('rf_department_id');
$averages = [];
foreach ($departmentIds as $departmentId) {
$departmentReports = $reportsByDepartment->get($departmentId, collect());
if ($departmentReports->isEmpty()) {
$averages[$departmentId] = 0;
continue;
}
$reportIds = $departmentReports->pluck('report_id')->toArray();
// Получаем снапшоты для отчетов отделения
$snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reportIds)
->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED)
->distinct()
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
$averages[$departmentId] = 0;
continue;
}
$totalDays = 0;
$validCount = 0;
foreach ($snapshots as $snapshot) {
$history = $snapshot->medicalHistory;
if ($history && $history->DateRecipient && $history->DateExtract) {
$end = Carbon::parse($history->DateExtract);
if ($end->between($actualStartDate, $endDate)) {
$start = Carbon::parse($history->DateRecipient);
$days = $start->diffInDays($end);
$totalDays += $days;
$validCount++;
}
}
}
$averages[$departmentId] = $validCount > 0 ? round($totalDays / $validCount, 1) : 0;
}
$this->cache[$cacheKey] = $averages;
return $averages;
}
/**
* Получить общий средний койко-день из всех снапшотов
*/
public function getOverallAverageBedDaysFromSnapshots(array $departmentIds, string $startDate, string $endDate, bool $isRangeOneDay): float
{
$averages = $this->getAverageBedDaysByDepartmentsFromSnapshots($departmentIds, $startDate, $endDate, $isRangeOneDay);
$total = 0;
$count = 0;
foreach ($averages as $avg) {
if ($avg > 0) {
$total += $avg;
$count++;
}
}
return $count > 0 ? round($total / $count, 1) : 0;
}
/**
* Получить детальную статистику по койко-дням из снапшотов
*/
public function getDetailedStatsFromSnapshots(int $departmentId, string $startDate, string $endDate): array
{
$reports = Report::where('rf_department_id', $departmentId)
// ->whereBetween('created_at', [$startDate, $endDate])
->where('sent_at', '>', $startDate)
->where('sent_at', '<=', $endDate)
->pluck('report_id');
if ($reports->isEmpty()) {
return [
'department_id' => $departmentId,
'period' => ['start' => $startDate, 'end' => $endDate],
'total_patients' => 0,
'average_bed_days' => 0,
'distribution' => [],
'by_month' => []
];
}
$snapshots = MedicalHistorySnapshot::whereIn('rf_report_id', $reports)
->where('patient_type', MedicalHistorySnapshot::PATIENT_TYPE_DISCHARGED)
->distinct()
->with('medicalHistory')
->get();
if ($snapshots->isEmpty()) {
return [
'department_id' => $departmentId,
'period' => ['start' => $startDate, 'end' => $endDate],
'total_patients' => 0,
'average_bed_days' => 0,
'distribution' => [],
'by_month' => []
];
}
$distribution = [
'1-3' => 0,
'4-7' => 0,
'8-14' => 0,
'15-21' => 0,
'22-30' => 0,
'30+' => 0
];
$byMonth = [];
$totalDays = 0;
$validCount = 0;
foreach ($snapshots as $snapshot) {
$history = $snapshot->medicalHistory;
if ($history && $history->DateRecipient && $history->DateExtract) {
$end = Carbon::parse($history->DateExtract);
if ($end->between($startDate, $endDate)) {
$start = Carbon::parse($history->DateRecipient);
$days = $start->diffInDays($end);
$totalDays += $days;
$validCount++;
// Распределение
if ($days <= 3) $distribution['1-3']++;
elseif ($days <= 7) $distribution['4-7']++;
elseif ($days <= 14) $distribution['8-14']++;
elseif ($days <= 21) $distribution['15-21']++;
elseif ($days <= 30) $distribution['22-30']++;
else $distribution['30+']++;
// По месяцам
$month = $end->format('Y-m');
if (!isset($byMonth[$month])) {
$byMonth[$month] = ['total' => 0, 'count' => 0];
}
$byMonth[$month]['total'] += $days;
$byMonth[$month]['count']++;
}
}
}
// Рассчитываем средние по месяцам
$monthlyStats = [];
foreach ($byMonth as $month => $data) {
$monthlyStats[] = [
'month' => $month,
'avg_days' => round($data['total'] / $data['count'], 1),
'count' => $data['count']
];
}
return [
'department_id' => $departmentId,
'period' => ['start' => $startDate, 'end' => $endDate],
'total_patients' => $validCount,
'average_bed_days' => $validCount > 0 ? round($totalDays / $validCount, 1) : 0,
'distribution' => $distribution,
'by_month' => $monthlyStats
];
}
/**
* Обновить метрики для всех отчетов на основе снапшотов
*/
public function updateAllMetricsFromSnapshots(): array
{
$reports = Report::all();
$results = [];
foreach ($reports as $report) {
// Для каждого отчета считаем средний койко-день за последние 30 дней до даты отчета
$endDate = $report->created_at;
$startDate = Carbon::startOfYear();
$avg = $this->getAverageBedDaysFromSnapshots(
$report->rf_department_id,
$startDate,
$endDate,
false
);
// Сохраняем в метрики
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => self::METRIC_BED_DAYS_ID,
],
['value' => $avg]
);
$results[$report->report_id] = $avg;
}
return $results;
}
/**
* Очистить кэш памяти
*/
public function clearMemoryCache(): void
{
$this->cache = [];
}
}