$sectionStateCounts * @param array $requiredSectionStateCounts * @param list $publishBlockers * @return array{ * review_status: string, * review_completeness_state: string, * evidence_completeness_state: string, * has_ready_export: bool, * section_state_counts: array, * required_section_count: int, * required_section_state_counts: array, * required_section_limited_count: int, * contains_pii: bool, * protected_values_hidden: bool, * disclosure_present: bool, * customer_safe_state: string, * readiness_state: string, * primary_reason: string, * primary_action: string, * limitations: list, * section_summary: array{ * required_total: int, * required_complete: int, * required_limited: int, * partial: int, * missing: int, * stale: int * } * } */ public static function derive( string $reviewStatus, string $reviewCompletenessState, string $evidenceCompletenessState, array $sectionStateCounts, int $requiredSectionCount, array $requiredSectionStateCounts, array $publishBlockers, bool $hasReadyExport, bool $includePii, bool $protectedValuesHidden = true, bool $disclosurePresent = true, ): array { $sectionStateCounts = self::normalizeCounts($sectionStateCounts); $requiredSectionStateCounts = self::normalizeCounts($requiredSectionStateCounts); $requiredLimitedCount = max( 0, (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Partial->value] ?? 0) + (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Missing->value] ?? 0) + (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Stale->value] ?? 0) ); $limitations = []; if (! $hasReadyExport) { $limitations[] = ['code' => 'export_not_ready']; } if ($evidenceCompletenessState !== EnvironmentReviewCompletenessState::Complete->value) { $limitations[] = ['code' => match ($evidenceCompletenessState) { EnvironmentReviewCompletenessState::Missing->value => 'evidence_basis_missing', EnvironmentReviewCompletenessState::Stale->value => 'evidence_basis_stale', default => 'evidence_basis_incomplete', }]; } if ($requiredLimitedCount > 0) { $limitations[] = ['code' => 'required_sections_incomplete']; } if ($publishBlockers !== []) { $limitations[] = ['code' => 'publish_blockers_present']; } if ($includePii) { $limitations[] = ['code' => 'contains_pii']; } if (! $disclosurePresent) { $limitations[] = ['code' => 'disclosure_missing']; } $readinessState = match (true) { ! $hasReadyExport => self::STATE_EXPORT_NOT_READY, self::hasMaterialLimitations($limitations) => self::STATE_PUBLISHED_WITH_LIMITATIONS, $includePii => self::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE, default => self::STATE_CUSTOMER_SAFE_READY, }; $customerSafeState = match ($readinessState) { self::STATE_CUSTOMER_SAFE_READY => 'customer_safe_ready', self::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => 'internal_only', self::STATE_EXPORT_NOT_READY => 'not_ready', default => 'requires_review', }; $primaryReason = $limitations[0]['code'] ?? 'customer_safe_ready'; $primaryAction = match ($readinessState) { self::STATE_CUSTOMER_SAFE_READY => 'download_customer_safe_review_pack', self::STATE_INTERNAL_REVIEW_PACKAGE_AVAILABLE => 'review_package_contents', self::STATE_EXPORT_NOT_READY => 'open_evidence_basis', default => 'review_output_limitations', }; $requiredComplete = max(0, min( $requiredSectionCount, (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Complete->value] ?? 0) )); return [ 'review_status' => $reviewStatus, 'review_completeness_state' => $reviewCompletenessState, 'evidence_completeness_state' => $evidenceCompletenessState, 'has_ready_export' => $hasReadyExport, 'section_state_counts' => $sectionStateCounts, 'required_section_count' => $requiredSectionCount, 'required_section_state_counts' => $requiredSectionStateCounts, 'required_section_limited_count' => $requiredLimitedCount, 'contains_pii' => $includePii, 'protected_values_hidden' => $protectedValuesHidden, 'disclosure_present' => $disclosurePresent, 'customer_safe_state' => $customerSafeState, 'readiness_state' => $readinessState, 'primary_reason' => $primaryReason, 'primary_action' => $primaryAction, 'limitations' => $limitations, 'section_summary' => [ 'required_total' => $requiredSectionCount, 'required_complete' => $requiredComplete, 'required_limited' => $requiredLimitedCount, 'partial' => (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Partial->value] ?? 0), 'missing' => (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Missing->value] ?? 0), 'stale' => (int) ($requiredSectionStateCounts[EnvironmentReviewCompletenessState::Stale->value] ?? 0), ], ]; } /** * @param array $counts * @return array */ private static function normalizeCounts(array $counts): array { return collect($counts) ->mapWithKeys(static fn (mixed $count, string|int $key): array => [(string) $key => max(0, (int) $count)]) ->all(); } /** * @param list $limitations */ private static function hasMaterialLimitations(array $limitations): bool { return collect($limitations) ->pluck('code') ->contains(static fn (string $code): bool => $code !== 'contains_pii'); } }