144 lines
5.4 KiB
PHP
144 lines
5.4 KiB
PHP
<?php
|
||
|
||
namespace App\Application\Reports;
|
||
|
||
use App\Application\Reports\DTO\GenerateReportInput;
|
||
use App\Application\Reports\DTO\GenerateReportResult;
|
||
use App\Application\Reports\DTO\ReportComparisonResult;
|
||
use App\Domain\Reports\Contracts\AuditLogger;
|
||
use App\Domain\Reports\Contracts\MetricCalculator;
|
||
use App\Domain\Reports\Contracts\PatientSource;
|
||
use App\Domain\Reports\Contracts\ReportRepository;
|
||
use App\Domain\Reports\Models\PatientCollection;
|
||
use App\Domain\Reports\ValueObjects\MetrikaConfig;
|
||
use DateTimeImmutable;
|
||
|
||
final readonly class GenerateReportUseCase
|
||
{
|
||
/**
|
||
* @param iterable<MetricCalculator> $calculators
|
||
*/
|
||
public function __construct(
|
||
private ReportRepository $reportRepository,
|
||
private AuditLogger $auditLogger,
|
||
private CompareLegacyAndNewReportUseCase $comparator,
|
||
private ?PatientSource $patientSource = null,
|
||
private iterable $calculators = [],
|
||
private bool $compareBeforeCutover = true,
|
||
) {}
|
||
|
||
public function handle(GenerateReportInput $input): GenerateReportResult
|
||
{
|
||
$resolvedInput = $this->resolveInput($input);
|
||
$saved = $this->reportRepository->save($resolvedInput->toSnapshot());
|
||
|
||
$comparison = null;
|
||
|
||
if ($this->compareBeforeCutover) {
|
||
try {
|
||
$comparison = $this->comparator->handle(
|
||
$resolvedInput->withPersistedReportId($saved->reportId)
|
||
);
|
||
} catch (\Throwable $exception) {
|
||
$comparison = new ReportComparisonResult(
|
||
reportType: $resolvedInput->reportType,
|
||
path: 'new',
|
||
status: 'failed',
|
||
diff: ['error' => $exception->getMessage()],
|
||
departmentId: $resolvedInput->departmentId,
|
||
userId: $resolvedInput->userId,
|
||
periodStart: $resolvedInput->periodStart->format('Y-m-d H:i:s'),
|
||
periodEnd: $resolvedInput->periodEnd->format('Y-m-d H:i:s'),
|
||
reportId: $saved->reportId,
|
||
durationMs: 0.0,
|
||
);
|
||
}
|
||
|
||
$this->auditLogger->logComparison($comparison);
|
||
}
|
||
|
||
return new GenerateReportResult(
|
||
reportId: $saved->reportId,
|
||
path: 'new',
|
||
usedNewArchitecture: true,
|
||
comparison: $comparison,
|
||
);
|
||
}
|
||
|
||
private function resolveInput(GenerateReportInput $input): GenerateReportInput
|
||
{
|
||
if ($input->rawPayload !== null) {
|
||
return $input;
|
||
}
|
||
|
||
if ($this->patientSource === null) {
|
||
return $input;
|
||
}
|
||
|
||
$patients = $this->patientSource->load($input->toContext());
|
||
$payload = $patients->metadata('payload', []);
|
||
|
||
if (! is_array($payload) || $payload === []) {
|
||
$payload = $this->buildPayloadFromCalculators($input, $patients);
|
||
}
|
||
|
||
return new GenerateReportInput(
|
||
departmentId: $input->departmentId,
|
||
userId: $input->userId,
|
||
actorUserId: $input->actorUserId,
|
||
periodStart: $input->periodStart,
|
||
periodEnd: $input->periodEnd,
|
||
metrics: (array) ($payload['metrics'] ?? $input->metrics),
|
||
observationPatients: (array) ($payload['observationPatients'] ?? $input->observationPatients),
|
||
unwantedEvents: (array) ($payload['unwantedEvents'] ?? $input->unwantedEvents),
|
||
reportId: isset($payload['reportId']) ? (int) $payload['reportId'] : $input->reportId,
|
||
status: (string) ($payload['status'] ?? $input->status),
|
||
reportType: $input->reportType,
|
||
autoFill: $input->autoFill,
|
||
rawPayload: $payload,
|
||
persistedReportId: $input->persistedReportId,
|
||
createdAt: isset($payload['created_at'])
|
||
? new DateTimeImmutable((string) $payload['created_at'])
|
||
: $input->createdAt,
|
||
sentAt: isset($payload['sent_at'])
|
||
? new DateTimeImmutable((string) $payload['sent_at'])
|
||
: $input->sentAt,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Переходный fallback для будущих сценариев с calculator-based flow.
|
||
*
|
||
* @return array<string, mixed>
|
||
*/
|
||
private function buildPayloadFromCalculators(GenerateReportInput $input, PatientCollection $patients): array
|
||
{
|
||
$metrics = $input->metrics;
|
||
|
||
foreach ($this->calculators as $calculator) {
|
||
$metrics = [
|
||
...$metrics,
|
||
...$calculator->calculate($input->toContext(), $patients)->normalized(),
|
||
];
|
||
}
|
||
|
||
return [
|
||
'departmentId' => $input->departmentId,
|
||
'userId' => $input->userId,
|
||
'actorUserId' => $input->actorUserId,
|
||
'autoFill' => $input->autoFill,
|
||
'dates' => [
|
||
$input->periodStart->getTimestamp(),
|
||
$input->periodEnd->getTimestamp(),
|
||
],
|
||
'status' => $input->status,
|
||
'created_at' => $input->createdAt?->format('Y-m-d H:i:s') ?? $input->periodEnd->format('Y-m-d H:i:s'),
|
||
'sent_at' => $input->sentAt?->format('Y-m-d H:i:s') ?? $input->periodEnd->format('Y-m-d H:i:s'),
|
||
'metrics' => MetrikaConfig::toPayloadMetrics($metrics),
|
||
'observationPatients' => $input->observationPatients,
|
||
'unwantedEvents' => $input->unwantedEvents,
|
||
'reportId' => $input->reportId,
|
||
];
|
||
}
|
||
}
|