Implemented deterministic Baseline Result Semantics (Spec 383), introducing CompareSubjectResult and CompareEvidenceResult. Replaced generic arrays with strict Data Transfer Objects for Baseline engine output. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #454
136 lines
4.4 KiB
PHP
136 lines
4.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Baselines\CompareSemantics;
|
|
|
|
use App\Support\OperationRunOutcome;
|
|
|
|
final class BaselineCompareRunSummaryClassifier
|
|
{
|
|
/**
|
|
* @param list<CompareSubjectOutcome|array<string, mixed>> $subjectOutcomes
|
|
* @param list<string> $uncoveredTypes
|
|
* @return array{
|
|
* run_outcome: string,
|
|
* operation_outcome: string,
|
|
* counts: array<string, array<string, int>>
|
|
* }
|
|
*/
|
|
public function summarize(
|
|
array $subjectOutcomes,
|
|
int $driftFindingsCount,
|
|
bool $warningsRecorded,
|
|
bool $resumeTokenPresent = false,
|
|
array $uncoveredTypes = [],
|
|
): array {
|
|
$counts = [
|
|
'by_reason' => [],
|
|
'by_category' => [],
|
|
'by_actionability' => [],
|
|
'by_readiness_impact' => [],
|
|
];
|
|
|
|
$verifiedSubjectCount = 0;
|
|
$blockingSubjectCount = 0;
|
|
$failedSubjectCount = 0;
|
|
|
|
foreach ($subjectOutcomes as $outcome) {
|
|
$outcome = $outcome instanceof CompareSubjectOutcome ? $outcome->toArray() : $outcome;
|
|
|
|
if (! is_array($outcome)) {
|
|
continue;
|
|
}
|
|
|
|
$reason = $this->nonEmptyString($outcome['reason'] ?? null);
|
|
$category = $this->nonEmptyString($outcome['category'] ?? null);
|
|
$actionability = $this->nonEmptyString($outcome['actionability'] ?? null);
|
|
$readinessImpact = $this->nonEmptyString($outcome['readiness_impact'] ?? null);
|
|
|
|
$this->increment($counts['by_reason'], $reason);
|
|
$this->increment($counts['by_category'], $category);
|
|
$this->increment($counts['by_actionability'], $actionability);
|
|
$this->increment($counts['by_readiness_impact'], $readinessImpact);
|
|
|
|
if (in_array($category, [CompareResultCategory::Verified->value, CompareResultCategory::DriftDetected->value], true)) {
|
|
$verifiedSubjectCount++;
|
|
}
|
|
|
|
if (in_array($readinessImpact, [CompareResultReadinessImpact::CustomerBlocker->value, CompareResultReadinessImpact::InternalBlocker->value], true)) {
|
|
$blockingSubjectCount++;
|
|
}
|
|
|
|
if ($category === CompareResultCategory::Failed->value) {
|
|
$failedSubjectCount++;
|
|
}
|
|
}
|
|
|
|
foreach ($counts as &$bucket) {
|
|
ksort($bucket);
|
|
}
|
|
unset($bucket);
|
|
|
|
$hasWarnings = $warningsRecorded || $resumeTokenPresent || $uncoveredTypes !== [];
|
|
$runOutcome = $this->runOutcome(
|
|
driftFindingsCount: $driftFindingsCount,
|
|
verifiedSubjectCount: $verifiedSubjectCount,
|
|
blockingSubjectCount: $blockingSubjectCount,
|
|
failedSubjectCount: $failedSubjectCount,
|
|
hasWarnings: $hasWarnings,
|
|
);
|
|
|
|
return [
|
|
'run_outcome' => $runOutcome->value,
|
|
'operation_outcome' => in_array($runOutcome, [CompareRunOutcome::Completed, CompareRunOutcome::CompletedWithDrift], true)
|
|
? OperationRunOutcome::Succeeded->value
|
|
: OperationRunOutcome::PartiallySucceeded->value,
|
|
'counts' => $counts,
|
|
];
|
|
}
|
|
|
|
private function runOutcome(
|
|
int $driftFindingsCount,
|
|
int $verifiedSubjectCount,
|
|
int $blockingSubjectCount,
|
|
int $failedSubjectCount,
|
|
bool $hasWarnings,
|
|
): CompareRunOutcome {
|
|
if ($failedSubjectCount > 0 && $verifiedSubjectCount === 0 && $driftFindingsCount === 0) {
|
|
return CompareRunOutcome::Failed;
|
|
}
|
|
|
|
if ($blockingSubjectCount > 0 && $verifiedSubjectCount === 0 && $driftFindingsCount === 0) {
|
|
return CompareRunOutcome::Blocked;
|
|
}
|
|
|
|
if ($hasWarnings || $blockingSubjectCount > 0 || $failedSubjectCount > 0) {
|
|
return CompareRunOutcome::Partial;
|
|
}
|
|
|
|
return $driftFindingsCount > 0
|
|
? CompareRunOutcome::CompletedWithDrift
|
|
: CompareRunOutcome::Completed;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, int> $counts
|
|
*/
|
|
private function increment(array &$counts, ?string $key): void
|
|
{
|
|
if ($key === null) {
|
|
return;
|
|
}
|
|
|
|
$counts[$key] = ($counts[$key] ?? 0) + 1;
|
|
}
|
|
|
|
private function nonEmptyString(mixed $value): ?string
|
|
{
|
|
if (! is_string($value) || trim($value) === '') {
|
|
return null;
|
|
}
|
|
|
|
return trim($value);
|
|
}
|
|
}
|