This commit is contained in:
brusnitsyn
2026-02-20 17:28:16 +09:00
parent 94e374c32b
commit 52a80ccd3b
41 changed files with 2555 additions and 206 deletions

View File

@@ -12,6 +12,16 @@ use Carbon\Carbon;
class StatisticsService
{
/**
* Метрики, которые нужно суммировать (целые числа)
*/
protected array $summableMetrics = [4, 12, 11, 10, 13, 7, 9, 8, 17, 14, 16];
/**
* Метрики, которые нужно усреднять (числа с плавающей точкой)
*/
protected array $averageMetrics = [18];
protected array $metricMapping = [
4 => 'plan', // Плановые поступления
12 => 'emergency', // Экстренные поступления
@@ -21,13 +31,24 @@ class StatisticsService
7 => 'outcome', // Выбыло
9 => 'deceased', // Умерло
8 => 'current', // Состоит
17 => 'count_staff', // Кол-во мед. персонала
14 => 'count_observable', // Кол-во пациентов на контроле
16 => 'count_unwanted', // Кол-во нежелательных событий
18 => 'average_bed_days' // Средний койко-день
];
public function __construct(
protected BedDayService $bedDayService
) { }
/**
* Получить статистические данные с оптимизацией
*/
public function getStatisticsData(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
{
// Очищаем кэш памяти перед началом
$this->bedDayService->clearMemoryCache();
// Определяем порог для использования оптимизированного метода
$daysDiff = Carbon::parse($startDate)->diffInDays(Carbon::parse($endDate));
@@ -45,6 +66,63 @@ class StatisticsService
return $this->getDetailedStatistics($user, $startDate, $endDate, $isRangeOneDay);
}
/**
* Получить средние койко-дни за период из сохраненных метрик отчетов
*/
protected function getAverageBedDaysFromReports(array $departmentIds, string $startDate, string $endDate): array
{
if (empty($departmentIds)) {
return [];
}
try {
$results = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $departmentIds)
->where('mr.rf_metrika_item_id', 18)
->whereBetween('r.created_at', [$startDate, $endDate])
->select(
'r.rf_department_id',
DB::raw('AVG(CAST(mr.value AS DECIMAL)) as avg_value')
)
->groupBy('r.rf_department_id')
->get()
->keyBy('rf_department_id');
$averages = [];
foreach ($departmentIds as $departmentId) {
if (isset($results[$departmentId]) && $results[$departmentId]->avg_value !== null) {
$averages[$departmentId] = round((float)$results[$departmentId]->avg_value, 1);
} else {
$averages[$departmentId] = 0;
}
}
return $averages;
} catch (\Exception $e) {
\Log::error("Error in getAverageBedDaysFromReports: " . $e->getMessage());
return array_fill_keys($departmentIds, 0);
}
}
/**
* Получить общий средний койко-день
*/
protected function getOverallAverageBedDays(array $averages): float
{
$total = 0;
$count = 0;
foreach ($averages as $avg) {
if ($avg > 0) {
$total += $avg;
$count++;
}
}
return $count > 0 ? round($total / $count, 1) : 0;
}
/**
* Агрегированный метод для очень больших диапазонов (больше 30 дней)
*/
@@ -60,7 +138,7 @@ class StatisticsService
// Загружаем все отделения
$departments = Department::select('department_id', 'rf_department_type', 'name_short')
->with(['departmentType'])
->orderBy('name_short')
->orderBy('rf_department_type')
->get()
->keyBy('department_id');
@@ -80,12 +158,24 @@ class StatisticsService
$isRangeOneDay ? $dateReport : $dateReport[1]
);
// Получаем средние койко-дни из метрик отчетов
$averageBedDays = $this->getAverageBedDaysFromReports(
$departments->pluck('department_id')->toArray(),
$startDate,
$endDate
);
// Общий средний койко-день
$overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays);
return $this->processAggregatedData(
$departments,
$defaultMetrics,
$aggregatedData,
$lastReportsData,
$isRangeOneDay
$isRangeOneDay,
$averageBedDays,
$overallAverageBedDays
);
}
@@ -94,25 +184,52 @@ class StatisticsService
*/
private function getAggregatedReportData(array $departmentIds, $dateReport, bool $isRangeOneDay): Collection
{
$query = DB::table('reports as r')
// Для суммируемых метрик - SUM
$summableQuery = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->select(
'r.rf_department_id',
'mr.rf_metrika_item_id',
DB::raw('SUM(CAST(mr.value AS INTEGER)) as total')
DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')
)
->whereIn('r.rf_department_id', $departmentIds);
->whereIn('r.rf_department_id', $departmentIds)
->whereIn('mr.rf_metrika_item_id', $this->summableMetrics);
if ($isRangeOneDay) {
$query->whereDate('r.created_at', $dateReport);
$summableQuery->whereDate('r.created_at', $dateReport);
} else {
$query->whereBetween('r.created_at', $dateReport);
$summableQuery->whereBetween('r.created_at', $dateReport);
}
return $query->whereIn('mr.rf_metrika_item_id', array_keys($this->metricMapping))
$summableResults = $summableQuery
->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id')
->get()
->groupBy('rf_department_id');
->get();
// Для усредняемых метрик - AVG (например, средний койко-день)
$averageQuery = DB::table('reports as r')
->join('metrika_results as mr', 'r.report_id', '=', 'mr.rf_report_id')
->select(
'r.rf_department_id',
'mr.rf_metrika_item_id',
DB::raw('AVG(CAST(mr.value AS DECIMAL)) as total')
)
->whereIn('r.rf_department_id', $departmentIds)
->whereIn('mr.rf_metrika_item_id', $this->averageMetrics);
if ($isRangeOneDay) {
$averageQuery->whereDate('r.created_at', $dateReport);
} else {
$averageQuery->whereBetween('r.created_at', $dateReport);
}
$averageResults = $averageQuery
->groupBy('r.rf_department_id', 'mr.rf_metrika_item_id')
->get();
// Объединяем результаты
$allResults = $summableResults->concat($averageResults);
return $allResults->groupBy('rf_department_id');
}
/**
@@ -145,7 +262,9 @@ class StatisticsService
Collection $defaultMetrics,
Collection $aggregatedData,
Collection $lastReportsData,
bool $isRangeOneDay
bool $isRangeOneDay,
array $averageBedDays,
float $overallAverageBedDays
): array {
$groupedData = [];
$totalsByType = [];
@@ -182,14 +301,21 @@ class StatisticsService
$allCount = $counters['plan'] + $counters['emergency'];
$percentLoadedBeds = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
// Получаем средний койко-день для отделения
$departmentAverageBedDays = $averageBedDays[$departmentId] ?? 0;
// Формируем данные
$departmentData = $this->createDepartmentData(
$department->name_short,
$departmentId,
$bedsCount,
$allCount,
$counters,
$percentLoadedBeds,
$departmentType
$departmentType,
null,
$departmentAverageBedDays,
$overallAverageBedDays
);
$groupedData[$departmentType][] = $departmentData;
@@ -214,7 +340,7 @@ class StatisticsService
// Загружаем все отделения
$departments = Department::select('department_id', 'rf_department_type', 'name_short')
->with(['departmentType'])
->orderBy('name_short')
->orderBy('rf_department_type')
->get()
->keyBy('department_id');
@@ -228,13 +354,25 @@ class StatisticsService
$reportIds = $reports->flatMap(fn($items) => $items->pluck('report_id'))->toArray();
$reportMetrics = $this->getReportMetricsBatch($reportIds);
// Получаем средние койко-дни из метрик отчетов
$averageBedDays = $this->getAverageBedDaysFromReports(
$departments->pluck('department_id')->toArray(),
$startDate,
$endDate
);
// Общий средний койко-день
$overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays);
return $this->processOptimizedData(
$departments,
$defaultMetrics,
$reports,
$reportMetrics,
$dateReport,
$isRangeOneDay
$isRangeOneDay,
$averageBedDays,
$overallAverageBedDays
);
}
@@ -279,12 +417,26 @@ class StatisticsService
return collect();
}
return DB::table('metrika_results')
// Получаем все метрики, но для 18 не преобразуем в integer
$results = DB::table('metrika_results')
->whereIn('rf_report_id', $reportIds)
->whereIn('rf_metrika_item_id', array_keys($this->metricMapping))
->select('rf_report_id', 'rf_metrika_item_id', 'value')
->get()
->groupBy('rf_report_id');
// Преобразуем значения в зависимости от типа метрики
foreach ($results as $reportId => $metrics) {
foreach ($metrics as $metric) {
if (in_array($metric->rf_metrika_item_id, $this->summableMetrics)) {
$metric->value = (int)$metric->value;
} else {
$metric->value = (float)$metric->value;
}
}
}
return $results;
}
/**
@@ -296,7 +448,9 @@ class StatisticsService
Collection $reports,
Collection $reportMetrics,
$dateReport,
bool $isRangeOneDay
bool $isRangeOneDay,
array $averageBedDays,
float $overallAverageBedDays
): array {
$groupedData = [];
$totalsByType = [];
@@ -362,14 +516,21 @@ class StatisticsService
$allCount = $counters['plan'] + $counters['emergency'];
$percentLoadedBeds = $bedsCount > 0 ? round($counters['current'] * 100 / $bedsCount) : 0;
// Получаем средний койко-день для отделения
$departmentAverageBedDays = $averageBedDays[$departmentId] ?? 0;
// Формируем данные отделения
$departmentData = $this->createDepartmentData(
$department->name_short,
$departmentId,
$bedsCount,
$allCount,
$counters,
$percentLoadedBeds,
$departmentType
$departmentType,
null,
$departmentAverageBedDays,
$overallAverageBedDays
);
$groupedData[$departmentType][] = $departmentData;
@@ -403,9 +564,19 @@ class StatisticsService
}
$query->with('metrikaResults');
}])
->orderBy('name_short')
->orderBy('rf_department_type')
->get();
// Получаем средние койко-дни из метрик отчетов
$averageBedDays = $this->getAverageBedDaysFromReports(
$departments->pluck('department_id')->toArray(),
$startDate,
$endDate
);
// Общий средний койко-день
$overallAverageBedDays = $this->getOverallAverageBedDays($averageBedDays);
foreach ($departments as $department) {
$departmentType = $department->departmentType->name_full;
@@ -458,14 +629,24 @@ class StatisticsService
$allCount = $counters['plan'] + $counters['emergency'];
$percentLoadedBeds = $bedsCount > 0 ? round($counters['current'] * 100 / $bedsCount) : 0;
if ($isRangeOneDay)
$isReportToday = !empty($lastReport);
else $isReportToday = null;
$departmentAverageBedDays = $averageBedDays[$department->department_id] ?? 0;
// Формируем данные отделения
$departmentData = $this->createDepartmentData(
$department->name_short,
$department->department_id,
$bedsCount,
$allCount,
$counters,
$percentLoadedBeds,
$departmentType
$departmentType,
$isReportToday,
$departmentAverageBedDays,
$overallAverageBedDays
);
$groupedData[$departmentType][] = $departmentData;
@@ -480,14 +661,19 @@ class StatisticsService
*/
private function createDepartmentData(
string $name,
int $departmentId,
int $beds,
int $allCount,
array $counters,
int $percentLoadedBeds,
string $type
string $type,
?bool $isReportToday = null,
float $departmentAverageBedDays = 0,
float $overallAverageBedDays = 0
): array {
return [
'department' => $name,
'department_id' => $departmentId,
'beds' => $beds,
'recipients' => [
'all' => $allCount,
@@ -503,8 +689,14 @@ class StatisticsService
'emergency' => $counters['emergency_surgical']
],
'deceased' => $counters['deceased'],
'countStaff' => $counters['count_staff'],
'countObservable' => $counters['count_observable'],
'countUnwanted' => $counters['count_unwanted'],
'averageBedDays' => $departmentAverageBedDays,
'overallAverageBedDays' => $overallAverageBedDays,
'type' => $type,
'isDepartment' => true
'isDepartment' => true,
'isReportToday' => $isReportToday,
];
}
@@ -527,6 +719,11 @@ class StatisticsService
'deceased_sum' => 0,
'percentLoadedBeds_total' => 0,
'percentLoadedBeds_count' => 0,
'staff_sum' => 0,
'observable_sum' => 0,
'unwanted_sum' => 0,
'average_bed_days_total' => 0,
'average_bed_days_count' => 0,
];
}
@@ -548,6 +745,11 @@ class StatisticsService
$totals['deceased_sum'] += $departmentData['deceased'];
$totals['percentLoadedBeds_total'] += $departmentData['percentLoadedBeds'];
$totals['percentLoadedBeds_count']++;
$totals['staff_sum'] += $departmentData['countStaff'];
$totals['observable_sum'] += $departmentData['countObservable'];
$totals['unwanted_sum'] += $departmentData['countUnwanted'];
$totals['average_bed_days_total'] += $departmentData['averageBedDays'];
$totals['average_bed_days_count']++;
}
/**
@@ -563,7 +765,7 @@ class StatisticsService
$finalData[] = [
'isGroupHeader' => true,
'groupName' => $type,
'colspan' => 12,
'colspan' => 14,
'type' => $type
];
@@ -592,7 +794,11 @@ class StatisticsService
// ? round($grandTotals['percentLoadedBeds_total'] / $grandTotals['percentLoadedBeds_count'])
// : 0;
//
// $finalData[] = $this->createTotalRow('all', $grandTotals, $avgPercent, true);
// $grandAvgBedDays = $grandTotals['averageBedDays_count'] > 0
// ? round($grandTotals['averageBedDays_total'] / $grandTotals['averageBedDays_count'], 1)
// : $overallAverageBedDays;
//
// $finalData[] = $this->createTotalRow('all', $grandTotals, $avgPercent, true, $grandAvgBedDays);
// }
return [
@@ -605,7 +811,7 @@ class StatisticsService
/**
* Создать строку итогов
*/
private function createTotalRow(string $type, array $total, int $avgPercent, bool $isGrandTotal): array
private function createTotalRow(string $type, array $total, int $avgPercent, bool $isGrandTotal, float $avgBedDays = 0): array
{
$row = [
'isTotalRow' => !$isGrandTotal,
@@ -628,8 +834,12 @@ class StatisticsService
'emergency' => $total['emergency_surgical_sum']
],
'deceased' => $total['deceased_sum'],
'averageBedDays' => '—',
'type' => $type,
'departments_count' => $total['departments_count'],
'countStaff' => $total['staff_sum'],
'countObservable' => $total['observable_sum'],
'countUnwanted' => $total['unwanted_sum'],
'isBold' => true
];