Files
onboard/app/Console/Commands/RecalculatePreoperativeMetric.php
brusnitsyn 719eb1403f * добавил исход спец контингенту
* оптимизация обновления при редактировании спец контингента
* добавил поддержку заключительных диагнозов
* изменил определение законченной операции
* добавил поддержку исхода операции
* добавил определение отмены для операции через назначение
* работа над диапазонами календарей, подсчет статистики
* добавил статусы отчетов и подкорректировал привязку спец контингента к отчету
* добавил новые сервисы для будущего кеширования
* частичное разделение логики подсчета пациентов
2026-04-22 20:35:39 +09:00

245 lines
8.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
// app/Console/Commands/RecalculatePreoperativeMetric.php
namespace App\Console\Commands;
use App\Models\Report;
use App\Models\MedicalHistorySnapshot;
use App\Models\MetrikaResult;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class RecalculatePreoperativeMetric extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'metrics:recalculate-preoperative
{--report= : ID конкретного отчета}
{--department= : ID отделения}
{--from= : Начальная дата (Y-m-d)}
{--to= : Конечная дата (Y-m-d)}
{--force : Принудительно перезаписать даже если есть значение}
{--chunk=100 : Количество отчетов за один раз}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Пересчет предоперационного койко-дня (метрика 25) для отчетов';
/**
* Execute the console command.
*/
public function handle()
{
$this->info('🚀 Начинаем пересчет предоперационного койко-дня...');
$query = Report::query();
// Фильтр по конкретному отчету
if ($reportId = $this->option('report')) {
$query->where('report_id', $reportId);
$this->info("📋 Фильтр: отчет ID {$reportId}");
}
// Фильтр по отделению
if ($departmentId = $this->option('department')) {
$query->where('rf_department_id', $departmentId);
$this->info("🏥 Фильтр: отделение ID {$departmentId}");
}
// Фильтр по дате
if ($from = $this->option('from')) {
$query->where('period_start', '>=', Carbon::parse($from, 'Asia/Yakutsk')->startOfDay()->format('Y-m-d H:i:s'));
$this->info("📅 Фильтр: с {$from}");
}
if ($to = $this->option('to')) {
$query->where('period_end', '<', Carbon::parse($to, 'Asia/Yakutsk')->addDay()->startOfDay()->format('Y-m-d H:i:s'));
$this->info("📅 Фильтр: по {$to}");
}
$force = $this->option('force');
$chunkSize = (int)$this->option('chunk');
$totalReports = $query->count();
if ($totalReports === 0) {
$this->warn('❌ Отчеты не найдены');
return 0;
}
$this->info("📊 Найдено отчетов: {$totalReports}");
if ($totalReports > 1000 && !$this->confirm("⚠️ Обработка {$totalReports} отчетов может занять время. Продолжить?")) {
$this->info('❌ Операция отменена');
return 0;
}
$bar = $this->output->createProgressBar($totalReports);
$bar->start();
$updated = 0;
$skipped = 0;
$errors = 0;
$noPatients = 0;
$query->orderBy('report_id')->chunk($chunkSize, function ($reports) use ($force, &$updated, &$skipped, &$errors, &$noPatients, $bar) {
foreach ($reports as $report) {
try {
$result = $this->processReport($report, $force);
match ($result) {
'updated' => $updated++,
'skipped' => $skipped++,
'no_patients' => $noPatients++,
default => null
};
} catch (\Exception $e) {
$errors++;
Log::error("Ошибка обработки отчета {$report->report_id}: " . $e->getMessage());
}
$bar->advance();
}
});
$bar->finish();
$this->newLine(2);
// Итоговая таблица
$this->table(
['Статус', 'Количество'],
[
['✅ Обновлено', $updated],
['⏭️ Пропущено (уже есть)', $skipped],
['👤 Нет пациентов', $noPatients],
['❌ Ошибок', $errors],
['📊 Всего', $totalReports],
]
);
// Покажем примеры обновленных отчетов
if ($updated > 0) {
$this->newLine();
$this->info('📋 Примеры обновленных отчетов:');
$samples = Report::whereHas('metrikaResults', function($q) {
$q->where('rf_metrika_item_id', 21);
})
->orderBy('report_id', 'desc')
->limit(5)
->get()
->map(function($report) {
$metric = $report->metrikaResults
->where('rf_metrika_item_id', 21)
->first();
return [
'report_id' => $report->report_id,
'department' => $report->rf_department_id,
'date' => $report->created_at->format('Y-m-d'),
'preoperative_days' => $metric ? $metric->value : 'N/A',
];
});
$this->table(['ID отчета', 'Отделение', 'Дата', 'Предоп. дни'], $samples->toArray());
}
$this->newLine();
$this->info('✅ Готово!');
return 0;
}
/**
* Обработка одного отчета
*/
private function processReport(Report $report, bool $force): string
{
// Проверяем, есть ли уже метрика
$existing = MetrikaResult::where('rf_report_id', $report->report_id)
->where('rf_metrika_item_id', 21)
->first();
if ($existing && !$force) {
return 'skipped';
}
// Получаем пациентов из снапшотов
$snapshots = MedicalHistorySnapshot::where('rf_report_id', $report->report_id)
->whereIn('patient_type', ['discharged', 'deceased'])
->pluck('rf_medicalhistory_id');
if ($snapshots->isEmpty()) {
// Сохраняем 0 если нет пациентов
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 21,
],
['value' => 0]
);
return 'no_patients';
}
// Получаем первые операции и первые поступления
$operations = DB::table('stt_surgicaloperation as so')
->join('stt_migrationpatient as mp', 'so.rf_MedicalHistoryID', '=', 'mp.rf_MedicalHistoryID')
->whereIn('so.rf_MedicalHistoryID', $snapshots)
->whereNotNull('so.Date')
->whereNotNull('mp.DateIngoing')
->select(
'so.rf_MedicalHistoryID',
DB::raw('MIN(so."Date") as first_operation'),
DB::raw('MIN(mp."DateIngoing") as first_admission')
)
->groupBy('so.rf_MedicalHistoryID')
->get();
if ($operations->isEmpty()) {
// Сохраняем 0 если нет операций
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 21,
],
['value' => 0]
);
return 'no_patients';
}
$totalDays = 0;
$count = 0;
foreach ($operations as $op) {
$days = Carbon::parse($op->first_admission)
->diffInDays(Carbon::parse($op->first_operation));
if ($days >= 0) {
$totalDays += $days;
$count++;
}
}
$avgDays = $count > 0 ? round($totalDays / $count, 1) : 0;
// Сохраняем метрику
MetrikaResult::updateOrCreate(
[
'rf_report_id' => $report->report_id,
'rf_metrika_item_id' => 21,
],
['value' => $avgDays]
);
return 'updated';
}
}