$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 */ 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, ]; } }