TenantAtlas/app/Services/TenantReviews/TenantReviewComposer.php
2026-03-21 23:02:02 +01:00

72 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\TenantReviews;
use App\Models\EvidenceSnapshot;
use App\Models\TenantReview;
use App\Support\TenantReviewStatus;
final class TenantReviewComposer
{
public function __construct(
private readonly TenantReviewFingerprint $fingerprint,
private readonly TenantReviewSectionFactory $sectionFactory,
private readonly TenantReviewReadinessGate $readinessGate,
) {}
/**
* @return array{
* fingerprint: string,
* completeness_state: string,
* status: string,
* summary: array<string, mixed>,
* sections: list<array<string, mixed>>
* }
*/
public function compose(EvidenceSnapshot $snapshot, ?TenantReview $review = null): array
{
$tenant = $snapshot->tenant;
if ($tenant === null) {
throw new \RuntimeException('Evidence snapshot tenant is required for review composition.');
}
$sections = $this->sectionFactory->make($snapshot);
$blockers = $this->readinessGate->blockersForSections($sections);
$sectionStateCounts = $this->readinessGate->sectionStateCounts($sections);
$completeness = $this->readinessGate->completenessForSections($sections);
$status = $this->readinessGate->statusForSections($sections);
if ($review instanceof TenantReview && $review->isPublished()) {
$status = TenantReviewStatus::Published;
}
return [
'fingerprint' => $this->fingerprint->forSnapshot($tenant, $snapshot),
'completeness_state' => $completeness->value,
'status' => $status->value,
'summary' => [
'evidence_basis' => [
'snapshot_id' => (int) $snapshot->getKey(),
'snapshot_fingerprint' => (string) $snapshot->fingerprint,
'snapshot_completeness_state' => (string) $snapshot->completeness_state,
'snapshot_generated_at' => $snapshot->generated_at?->toIso8601String(),
],
'section_count' => count($sections),
'section_state_counts' => $sectionStateCounts,
'publish_blockers' => $blockers,
'has_ready_export' => false,
'finding_count' => (int) data_get($sections, '0.summary_payload.finding_count', 0),
'report_count' => 2,
'operation_count' => (int) data_get($sections, '5.summary_payload.operation_count', 0),
'highlights' => data_get($sections, '0.render_payload.highlights', []),
'recommended_next_actions' => data_get($sections, '0.render_payload.next_actions', []),
'last_composed_at' => now()->toIso8601String(),
],
'sections' => $sections,
];
}
}