Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 59s
Implemented the first version of the operator resolution guidance framework. Added new foundation classes (ResolutionCase, ResolutionAction) and a ReviewPackOutputResolutionAdapter. Updated the Customer Review Workspace and Environment Review Resource to use the new adapter. Added extensive test coverage for the framework and UI integrations.
578 lines
29 KiB
PHP
578 lines
29 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\ReviewPacks;
|
|
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ReviewPack;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\ReviewPackStatus;
|
|
use Illuminate\Support\Collection;
|
|
|
|
final class ReviewPackOutputResolutionGuidance
|
|
{
|
|
public const string STATE_CUSTOMER_SAFE_READY = 'customer_safe_ready';
|
|
|
|
public const string STATE_PUBLISHED_WITH_LIMITATIONS = 'published_with_limitations';
|
|
|
|
public const string STATE_PUBLICATION_BLOCKED = 'publication_blocked';
|
|
|
|
public const string STATE_INTERNAL_ONLY = 'internal_only';
|
|
|
|
public const string STATE_EXPORT_NOT_READY = 'export_not_ready';
|
|
|
|
public const string STATE_UNKNOWN = 'unknown';
|
|
|
|
/**
|
|
* @param array{download?:?string,review?:?string,evidence?:?string,operation?:?string} $urls
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function fromReview(EnvironmentReview $review, array $urls = []): array
|
|
{
|
|
return self::fromReadiness(self::readinessForReview($review), $urls);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function readinessForReview(EnvironmentReview $review): array
|
|
{
|
|
$review->loadMissing(['sections', 'evidenceSnapshot', 'currentExportReviewPack']);
|
|
|
|
$pack = $review->currentExportReviewPack;
|
|
$snapshot = $review->evidenceSnapshot;
|
|
$summary = is_array($review->summary) ? $review->summary : [];
|
|
$sections = self::outputSections($review, $pack);
|
|
$requiredSections = $sections
|
|
->filter(static fn (mixed $section): bool => (bool) $section->required)
|
|
->values();
|
|
|
|
return ReviewPackOutputReadiness::derive(
|
|
reviewStatus: (string) $review->status,
|
|
reviewCompletenessState: (string) $review->completeness_state,
|
|
evidenceCompletenessState: $snapshot instanceof EvidenceSnapshot
|
|
? (string) $snapshot->completeness_state
|
|
: EnvironmentReviewCompletenessState::Missing->value,
|
|
sectionStateCounts: self::sectionStateCounts($sections),
|
|
requiredSectionCount: $requiredSections->count(),
|
|
requiredSectionStateCounts: self::sectionStateCounts($requiredSections),
|
|
publishBlockers: is_array($summary['publish_blockers'] ?? null) ? $summary['publish_blockers'] : [],
|
|
hasReadyExport: self::hasReadyExport($pack),
|
|
includePii: (bool) (is_array($pack?->options ?? null) ? ($pack->options['include_pii'] ?? true) : true),
|
|
protectedValuesHidden: true,
|
|
disclosurePresent: true,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $readiness
|
|
* @param array{download?:?string,review?:?string,evidence?:?string,operation?:?string} $urls
|
|
* @return array{
|
|
* state:string,
|
|
* label:string,
|
|
* status_label:string,
|
|
* color:string,
|
|
* severity:string,
|
|
* boundary_state:string,
|
|
* boundary_label:string,
|
|
* boundary_color:string,
|
|
* primary_reason:string,
|
|
* impact:string,
|
|
* qualified_download_label:string,
|
|
* limitation_count:int,
|
|
* limitation_summary:?string,
|
|
* action_help:?string,
|
|
* primary_action:array{key:string,label:string,url:?string,kind:string,icon:string}|null,
|
|
* secondary_actions:list<array{key:string,label:string,url:?string,kind:string,icon:string}>,
|
|
* limitations:list<array{key:string,label:string,severity:string,reason:string,action:?array{key:string,label:string,url:?string,kind:string,icon:string},details:list<string>}>,
|
|
* technical_details:array<string, string>
|
|
* }
|
|
*/
|
|
public static function fromReadiness(array $readiness, array $urls = []): array
|
|
{
|
|
$limitations = self::limitations($readiness, $urls);
|
|
$state = self::state($readiness, $limitations);
|
|
$primaryLimitation = $limitations[0] ?? null;
|
|
$primaryAction = self::primaryAction(
|
|
state: $state,
|
|
primaryLimitationKey: is_array($primaryLimitation) ? (string) $primaryLimitation['key'] : null,
|
|
urls: $urls,
|
|
);
|
|
$secondaryActions = self::secondaryActions($state, $primaryAction, $urls);
|
|
$boundaryState = self::boundaryState($state, $readiness);
|
|
|
|
return [
|
|
'state' => $state,
|
|
'label' => self::label($state),
|
|
'status_label' => self::statusLabel($state),
|
|
'color' => self::color($state),
|
|
'severity' => self::severity($state),
|
|
'boundary_state' => $boundaryState,
|
|
'boundary_label' => self::boundaryLabel($boundaryState),
|
|
'boundary_color' => self::boundaryColor($boundaryState),
|
|
'primary_reason' => is_array($primaryLimitation)
|
|
? self::shortReason((string) $primaryLimitation['key'])
|
|
: self::defaultPrimaryReason($state),
|
|
'impact' => self::impact($state),
|
|
'qualified_download_label' => self::qualifiedDownloadLabel($state),
|
|
'limitation_count' => count($limitations),
|
|
'limitation_summary' => $limitations === []
|
|
? null
|
|
: trans_choice('localization.review.output_limitations_summary', count($limitations), ['count' => count($limitations)]),
|
|
'action_help' => self::actionHelp($state),
|
|
'primary_action' => $primaryAction,
|
|
'secondary_actions' => $secondaryActions,
|
|
'limitations' => $limitations,
|
|
'technical_details' => self::technicalDetails($readiness),
|
|
];
|
|
}
|
|
|
|
private static function hasReadyExport(?ReviewPack $pack): bool
|
|
{
|
|
if (! $pack instanceof ReviewPack) {
|
|
return false;
|
|
}
|
|
|
|
if ($pack->status !== ReviewPackStatus::Ready->value) {
|
|
return false;
|
|
}
|
|
|
|
if ($pack->expires_at !== null && $pack->expires_at->isPast()) {
|
|
return false;
|
|
}
|
|
|
|
return filled($pack->file_path) && filled($pack->file_disk);
|
|
}
|
|
|
|
/**
|
|
* @return Collection<int, mixed>
|
|
*/
|
|
private static function outputSections(EnvironmentReview $review, ?ReviewPack $pack): Collection
|
|
{
|
|
return $review->sections
|
|
->filter(static fn (mixed $section): bool => self::includesOperations($pack) || $section->section_key !== 'operations_health')
|
|
->values();
|
|
}
|
|
|
|
private static function includesOperations(?ReviewPack $pack): bool
|
|
{
|
|
return (bool) (is_array($pack?->options ?? null) ? ($pack->options['include_operations'] ?? true) : true);
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, mixed> $sections
|
|
* @return array<string, int>
|
|
*/
|
|
private static function sectionStateCounts(Collection $sections): array
|
|
{
|
|
return $sections
|
|
->countBy(static fn (mixed $section): string => (string) $section->completeness_state)
|
|
->map(static fn (int $count): int => max(0, $count))
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param list<array{key:string,label:string,severity:string,reason:string,action:?array{key:string,label:string,url:?string,kind:string,icon:string},details:list<string>,priority:int}> $limitations
|
|
*/
|
|
private static function state(array $readiness, array $limitations): string
|
|
{
|
|
$limitationKeys = collect($limitations)->pluck('key');
|
|
|
|
return match (true) {
|
|
$limitationKeys->contains('publish_blockers_present') => self::STATE_PUBLICATION_BLOCKED,
|
|
! (bool) ($readiness['has_ready_export'] ?? false) => self::STATE_EXPORT_NOT_READY,
|
|
(bool) ($readiness['contains_pii'] ?? false) => self::STATE_INTERNAL_ONLY,
|
|
$limitations !== [] => self::STATE_PUBLISHED_WITH_LIMITATIONS,
|
|
(string) ($readiness['readiness_state'] ?? '') === ReviewPackOutputReadiness::STATE_CUSTOMER_SAFE_READY => self::STATE_CUSTOMER_SAFE_READY,
|
|
default => self::STATE_UNKNOWN,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $readiness
|
|
* @param array{download?:?string,review?:?string,evidence?:?string,operation?:?string} $urls
|
|
* @return list<array{key:string,label:string,severity:string,reason:string,action:?array{key:string,label:string,url:?string,kind:string,icon:string},details:list<string>,priority:int}>
|
|
*/
|
|
private static function limitations(array $readiness, array $urls): array
|
|
{
|
|
$sectionSummary = is_array($readiness['section_summary'] ?? null) ? $readiness['section_summary'] : [];
|
|
$sectionCounts = is_array($readiness['section_state_counts'] ?? null) ? $readiness['section_state_counts'] : [];
|
|
$limitations = collect(is_array($readiness['limitations'] ?? null) ? $readiness['limitations'] : [])
|
|
->filter(static fn (mixed $limitation): bool => is_array($limitation) && is_string($limitation['code'] ?? null))
|
|
->map(function (array $limitation) use ($readiness, $sectionSummary, $sectionCounts, $urls): ?array {
|
|
$code = (string) $limitation['code'];
|
|
|
|
return match ($code) {
|
|
'publish_blockers_present' => [
|
|
'key' => $code,
|
|
'label' => __('localization.review.publication_blocked'),
|
|
'severity' => 'danger',
|
|
'reason' => __('localization.review.publication_blocked_description'),
|
|
'action' => self::action('resolve_review_blockers', $urls['review'] ?? $urls['evidence'] ?? null),
|
|
'details' => [
|
|
__('localization.review.technical_detail_review_status_value', ['value' => (string) ($readiness['review_status'] ?? __('localization.review.unavailable'))]),
|
|
],
|
|
'priority' => 100,
|
|
],
|
|
'export_not_ready' => [
|
|
'key' => $code,
|
|
'label' => __('localization.review.export_not_ready'),
|
|
'severity' => 'warning',
|
|
'reason' => __('localization.review.export_not_ready_guidance_reason'),
|
|
'action' => self::action('review_output_limitations', $urls['review'] ?? $urls['evidence'] ?? null),
|
|
'details' => [
|
|
__('localization.review.technical_detail_ready_export_value', ['value' => __('localization.review.no')]),
|
|
],
|
|
'priority' => 90,
|
|
],
|
|
'evidence_basis_missing', 'evidence_basis_stale', 'evidence_basis_incomplete' => [
|
|
'key' => $code,
|
|
'label' => __('localization.review.evidence_basis_incomplete_guidance'),
|
|
'severity' => 'warning',
|
|
'reason' => __('localization.review.evidence_basis_incomplete_guidance_reason'),
|
|
'action' => self::action('open_evidence_basis', $urls['evidence'] ?? $urls['review'] ?? null),
|
|
'details' => [
|
|
__('localization.review.technical_detail_evidence_state_value', ['value' => (string) ($readiness['evidence_completeness_state'] ?? __('localization.review.unavailable'))]),
|
|
],
|
|
'priority' => match ($code) {
|
|
'evidence_basis_missing' => 82,
|
|
'evidence_basis_stale' => 81,
|
|
default => 80,
|
|
},
|
|
],
|
|
'required_sections_incomplete' => [
|
|
'key' => $code,
|
|
'label' => __('localization.review.required_review_sections_missing'),
|
|
'severity' => 'warning',
|
|
'reason' => trans_choice(
|
|
'localization.review.required_review_sections_missing_reason',
|
|
(int) ($sectionSummary['required_limited'] ?? 0),
|
|
['count' => (int) ($sectionSummary['required_limited'] ?? 0)],
|
|
),
|
|
'action' => self::action('review_section_limitations', $urls['review'] ?? $urls['evidence'] ?? null),
|
|
'details' => [
|
|
__('localization.review.technical_detail_section_counts_value', [
|
|
'complete' => (int) ($sectionCounts[EnvironmentReviewCompletenessState::Complete->value] ?? 0),
|
|
'partial' => (int) ($sectionCounts[EnvironmentReviewCompletenessState::Partial->value] ?? 0),
|
|
'missing' => (int) ($sectionCounts[EnvironmentReviewCompletenessState::Missing->value] ?? 0),
|
|
'stale' => (int) ($sectionCounts[EnvironmentReviewCompletenessState::Stale->value] ?? 0),
|
|
]),
|
|
],
|
|
'priority' => 70,
|
|
],
|
|
'contains_pii' => [
|
|
'key' => $code,
|
|
'label' => __('localization.review.internal_package_includes_pii'),
|
|
'severity' => 'warning',
|
|
'reason' => __('localization.review.internal_package_includes_pii_reason'),
|
|
'action' => self::action('review_pii_redaction_state', $urls['review'] ?? $urls['download'] ?? null),
|
|
'details' => [
|
|
__('localization.review.technical_detail_contains_pii_value', ['value' => __('localization.review.yes')]),
|
|
],
|
|
'priority' => 60,
|
|
],
|
|
'disclosure_missing' => [
|
|
'key' => $code,
|
|
'label' => __('localization.review.output_disclosure_missing'),
|
|
'severity' => 'warning',
|
|
'reason' => __('localization.review.output_disclosure_missing_reason'),
|
|
'action' => self::action('review_output_limitations', $urls['review'] ?? $urls['download'] ?? null),
|
|
'details' => [
|
|
__('localization.review.technical_detail_disclosure_present_value', ['value' => __('localization.review.no')]),
|
|
],
|
|
'priority' => 50,
|
|
],
|
|
default => null,
|
|
};
|
|
})
|
|
->filter()
|
|
->sortByDesc('priority')
|
|
->values()
|
|
->all();
|
|
|
|
return $limitations;
|
|
}
|
|
|
|
/**
|
|
* @param array{download?:?string,review?:?string,evidence?:?string,operation?:?string} $urls
|
|
* @return array{key:string,label:string,url:?string,kind:string,icon:string}|null
|
|
*/
|
|
private static function primaryAction(string $state, ?string $primaryLimitationKey, array $urls): ?array
|
|
{
|
|
$actionKey = match ($primaryLimitationKey) {
|
|
'publish_blockers_present' => 'resolve_review_blockers',
|
|
'evidence_basis_missing', 'evidence_basis_stale', 'evidence_basis_incomplete' => 'open_evidence_basis',
|
|
'required_sections_incomplete' => 'review_section_limitations',
|
|
'contains_pii' => 'review_pii_redaction_state',
|
|
'disclosure_missing' => 'review_output_limitations',
|
|
'export_not_ready' => 'review_output_limitations',
|
|
default => match ($state) {
|
|
self::STATE_CUSTOMER_SAFE_READY => 'download_customer_safe_review_pack',
|
|
self::STATE_INTERNAL_ONLY => 'review_pii_redaction_state',
|
|
self::STATE_EXPORT_NOT_READY => 'review_output_limitations',
|
|
self::STATE_PUBLICATION_BLOCKED => 'resolve_review_blockers',
|
|
self::STATE_PUBLISHED_WITH_LIMITATIONS => 'review_output_limitations',
|
|
default => 'review_output_limitations',
|
|
},
|
|
};
|
|
|
|
return self::action($actionKey, self::primaryActionUrl($actionKey, $urls));
|
|
}
|
|
|
|
/**
|
|
* @param array{download?:?string,review?:?string,evidence?:?string,operation?:?string} $urls
|
|
* @param array{key:string,label:string,url:?string,kind:string,icon:string}|null $primaryAction
|
|
* @return list<array{key:string,label:string,url:?string,kind:string,icon:string}>
|
|
*/
|
|
private static function secondaryActions(string $state, ?array $primaryAction, array $urls): array
|
|
{
|
|
$actions = match ($state) {
|
|
self::STATE_CUSTOMER_SAFE_READY => [
|
|
self::action('open_review', $urls['review'] ?? null),
|
|
],
|
|
self::STATE_INTERNAL_ONLY => [
|
|
self::action('download_internal_review_pack', $urls['download'] ?? null),
|
|
self::action('open_review', $urls['review'] ?? null),
|
|
],
|
|
self::STATE_EXPORT_NOT_READY => [
|
|
self::action('open_evidence_basis', $urls['evidence'] ?? null),
|
|
self::action('open_review', $urls['review'] ?? null),
|
|
],
|
|
self::STATE_PUBLICATION_BLOCKED => [
|
|
self::action('download_review_pack_with_limitations', $urls['download'] ?? null),
|
|
self::action('open_evidence_basis', $urls['evidence'] ?? null),
|
|
self::action('open_operation_proof', $urls['operation'] ?? null),
|
|
],
|
|
self::STATE_PUBLISHED_WITH_LIMITATIONS => [
|
|
self::action('download_review_pack_with_limitations', $urls['download'] ?? null),
|
|
self::action('open_review', $urls['review'] ?? null),
|
|
self::action('open_evidence_basis', $urls['evidence'] ?? null),
|
|
],
|
|
default => [
|
|
self::action('open_review', $urls['review'] ?? null),
|
|
],
|
|
};
|
|
|
|
return collect($actions)
|
|
->filter(static fn (?array $action): bool => is_array($action) && filled($action['url']))
|
|
->reject(static fn (array $action): bool => $primaryAction !== null
|
|
&& $action['label'] === $primaryAction['label']
|
|
&& $action['url'] === $primaryAction['url'])
|
|
->unique(static fn (array $action): string => $action['label'].'|'.$action['url'])
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
/**
|
|
* @param array{download?:?string,review?:?string,evidence?:?string,operation?:?string} $urls
|
|
*/
|
|
private static function primaryActionUrl(string $actionKey, array $urls): ?string
|
|
{
|
|
return match ($actionKey) {
|
|
'download_customer_safe_review_pack', 'download_internal_review_pack', 'download_review_pack_with_limitations' => $urls['download'] ?? null,
|
|
'open_evidence_basis' => $urls['evidence'] ?? $urls['review'] ?? null,
|
|
'review_section_limitations', 'resolve_review_blockers', 'review_output_limitations', 'review_pii_redaction_state', 'open_review' => $urls['review'] ?? $urls['evidence'] ?? $urls['download'] ?? null,
|
|
'open_operation_proof' => $urls['operation'] ?? null,
|
|
default => $urls['review'] ?? $urls['download'] ?? $urls['evidence'] ?? null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array{key:string,label:string,url:?string,kind:string,icon:string}|null
|
|
*/
|
|
private static function action(string $actionKey, ?string $url): ?array
|
|
{
|
|
return [
|
|
'key' => $actionKey,
|
|
'label' => match ($actionKey) {
|
|
'download_customer_safe_review_pack' => __('localization.review.download_customer_safe_review_pack'),
|
|
'download_internal_review_pack' => __('localization.review.download_internal_review_pack'),
|
|
'download_review_pack_with_limitations' => __('localization.review.download_review_pack_with_limitations'),
|
|
'open_evidence_basis' => __('localization.review.open_evidence_basis'),
|
|
'review_section_limitations' => __('localization.review.review_section_limitations'),
|
|
'review_pii_redaction_state' => __('localization.review.review_pii_redaction_state'),
|
|
'resolve_review_blockers' => __('localization.review.resolve_review_blockers'),
|
|
'open_operation_proof' => __('localization.review.open_operation_proof'),
|
|
'open_review' => __('localization.review.open_review'),
|
|
default => __('localization.review.review_output_limitations'),
|
|
},
|
|
'url' => $url,
|
|
'kind' => str_starts_with($actionKey, 'download_') ? 'download' : 'environment_link',
|
|
'icon' => str_starts_with($actionKey, 'download_')
|
|
? 'heroicon-o-arrow-down-tray'
|
|
: 'heroicon-o-arrow-top-right-on-square',
|
|
];
|
|
}
|
|
|
|
private static function label(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_PUBLICATION_BLOCKED => __('localization.review.output_not_customer_ready'),
|
|
self::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready'),
|
|
self::STATE_INTERNAL_ONLY => __('localization.review.internal_review_package_available'),
|
|
self::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready'),
|
|
self::STATE_PUBLISHED_WITH_LIMITATIONS => __('localization.review.published_with_limitations'),
|
|
default => __('localization.review.requires_review'),
|
|
};
|
|
}
|
|
|
|
private static function statusLabel(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_PUBLICATION_BLOCKED => __('localization.review.publication_blocked'),
|
|
self::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready'),
|
|
self::STATE_INTERNAL_ONLY => __('localization.review.internal_review_package_available'),
|
|
self::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready'),
|
|
self::STATE_PUBLISHED_WITH_LIMITATIONS => __('localization.review.published_with_limitations'),
|
|
default => __('localization.review.requires_review'),
|
|
};
|
|
}
|
|
|
|
private static function color(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_CUSTOMER_SAFE_READY => 'success',
|
|
self::STATE_PUBLICATION_BLOCKED => 'danger',
|
|
self::STATE_EXPORT_NOT_READY => 'gray',
|
|
default => 'warning',
|
|
};
|
|
}
|
|
|
|
private static function severity(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_CUSTOMER_SAFE_READY => 'success',
|
|
self::STATE_PUBLICATION_BLOCKED => 'danger',
|
|
self::STATE_EXPORT_NOT_READY => 'warning',
|
|
default => 'warning',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $readiness
|
|
*/
|
|
private static function boundaryState(string $state, array $readiness): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_CUSTOMER_SAFE_READY => 'customer_safe_ready',
|
|
self::STATE_INTERNAL_ONLY => 'internal_only',
|
|
self::STATE_EXPORT_NOT_READY => 'not_ready',
|
|
self::STATE_PUBLICATION_BLOCKED, self::STATE_PUBLISHED_WITH_LIMITATIONS => 'requires_review',
|
|
default => (string) ($readiness['customer_safe_state'] ?? 'requires_review'),
|
|
};
|
|
}
|
|
|
|
private static function boundaryLabel(string $state): string
|
|
{
|
|
return match ($state) {
|
|
'customer_safe_ready' => __('localization.review.customer_safe'),
|
|
'internal_only' => __('localization.review.internal_only'),
|
|
'not_ready' => __('localization.review.not_ready'),
|
|
default => __('localization.review.requires_review'),
|
|
};
|
|
}
|
|
|
|
private static function boundaryColor(string $state): string
|
|
{
|
|
return match ($state) {
|
|
'customer_safe_ready' => 'success',
|
|
'internal_only', 'requires_review' => 'warning',
|
|
default => 'gray',
|
|
};
|
|
}
|
|
|
|
private static function defaultPrimaryReason(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_PUBLICATION_BLOCKED => __('localization.review.publication_blocked_description'),
|
|
self::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready_guidance_reason'),
|
|
self::STATE_INTERNAL_ONLY => __('localization.review.internal_package_includes_pii_reason'),
|
|
self::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready_reason'),
|
|
default => __('localization.review.review_pack_with_limitations_description'),
|
|
};
|
|
}
|
|
|
|
private static function shortReason(string $limitationKey): string
|
|
{
|
|
return match ($limitationKey) {
|
|
'publish_blockers_present' => __('localization.review.publication_blocked_short_reason'),
|
|
'export_not_ready' => __('localization.review.export_not_ready_short_reason'),
|
|
'evidence_basis_missing', 'evidence_basis_stale', 'evidence_basis_incomplete' => __('localization.review.evidence_basis_incomplete_short_reason'),
|
|
'required_sections_incomplete' => __('localization.review.required_review_sections_missing_short_reason'),
|
|
'contains_pii' => __('localization.review.internal_package_includes_pii_short_reason'),
|
|
'disclosure_missing' => __('localization.review.output_disclosure_missing_short_reason'),
|
|
default => __('localization.review.review_output_limitations'),
|
|
};
|
|
}
|
|
|
|
private static function impact(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_PUBLICATION_BLOCKED => __('localization.review.publication_blocked_impact'),
|
|
self::STATE_EXPORT_NOT_READY => __('localization.review.export_not_ready_impact'),
|
|
self::STATE_INTERNAL_ONLY => __('localization.review.internal_review_package_available_impact'),
|
|
self::STATE_CUSTOMER_SAFE_READY => __('localization.review.customer_safe_review_pack_ready_impact'),
|
|
default => __('localization.review.published_with_limitations_impact'),
|
|
};
|
|
}
|
|
|
|
private static function actionHelp(string $state): ?string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_PUBLICATION_BLOCKED => __('localization.review.output_action_help_publication_blocked'),
|
|
self::STATE_PUBLISHED_WITH_LIMITATIONS => __('localization.review.output_action_help_published_with_limitations'),
|
|
self::STATE_INTERNAL_ONLY => __('localization.review.output_action_help_internal_only'),
|
|
self::STATE_EXPORT_NOT_READY => __('localization.review.output_action_help_export_not_ready'),
|
|
self::STATE_CUSTOMER_SAFE_READY => __('localization.review.output_action_help_customer_safe_ready'),
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private static function qualifiedDownloadLabel(string $state): string
|
|
{
|
|
return match ($state) {
|
|
self::STATE_CUSTOMER_SAFE_READY => __('localization.review.download_customer_safe_review_pack'),
|
|
self::STATE_INTERNAL_ONLY => __('localization.review.download_internal_review_pack'),
|
|
default => __('localization.review.download_review_pack_with_limitations'),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $readiness
|
|
* @return array<string, string>
|
|
*/
|
|
private static function technicalDetails(array $readiness): array
|
|
{
|
|
$sectionSummary = is_array($readiness['section_summary'] ?? null) ? $readiness['section_summary'] : [];
|
|
|
|
return [
|
|
__('localization.review.review_status') => (string) ($readiness['review_status'] ?? __('localization.review.unavailable')),
|
|
__('localization.review.output_readiness') => self::statusLabel(self::state($readiness, self::limitations($readiness, []))),
|
|
__('localization.review.publication_sharing_state') => self::boundaryLabel(self::boundaryState(
|
|
self::state($readiness, self::limitations($readiness, [])),
|
|
$readiness,
|
|
)),
|
|
__('localization.review.has_ready_export') => (bool) ($readiness['has_ready_export'] ?? false)
|
|
? __('localization.review.yes')
|
|
: __('localization.review.no'),
|
|
__('localization.review.evidence_basis_state') => (string) ($readiness['evidence_completeness_state'] ?? __('localization.review.unavailable')),
|
|
__('localization.review.section_completeness') => __('localization.review.technical_detail_required_sections_value', [
|
|
'complete' => (int) ($sectionSummary['required_complete'] ?? 0),
|
|
'total' => (int) ($sectionSummary['required_total'] ?? 0),
|
|
'limited' => (int) ($sectionSummary['required_limited'] ?? 0),
|
|
]),
|
|
__('localization.review.pii_state') => (bool) ($readiness['contains_pii'] ?? false)
|
|
? __('localization.review.yes')
|
|
: __('localization.review.no'),
|
|
__('localization.review.protected_values') => (bool) ($readiness['protected_values_hidden'] ?? true)
|
|
? __('localization.review.protected_values_hidden')
|
|
: __('localization.review.unavailable'),
|
|
__('localization.review.disclosure') => (bool) ($readiness['disclosure_present'] ?? false)
|
|
? __('localization.review.disclosure_present')
|
|
: __('localization.review.no'),
|
|
];
|
|
}
|
|
}
|