Files
Investidor-V2/app/Services/OpportunityCompleteness.php

60 lines
2.8 KiB
PHP

<?php
namespace App\Services;
use Opis\JsonSchema\Validator;
/** Transparent field completion, separate from validation, approval and external decisions. */
class OpportunityCompleteness
{
/** @param array<string,mixed> $payload
* @return array<string,mixed> */
public function calculate(array $payload): array
{
$schema = app(OpportunityCanonicalContract::class)->schema(false, $payload);
$validator = new Validator;
$paths = [];
$walk = function (mixed $value, array $node, string $path) use (&$walk, &$paths, $schema, $validator): void {
if (isset($node['$ref'])) {
$node = $schema['$defs'][basename($node['$ref'])] ?? $node;
}
$requirements = $node['required'] ?? [];
foreach ($node['allOf'] ?? [] as $condition) {
if (! isset($condition['if'])) {
$walk($value, $condition, $path);
continue;
}
$test = $condition['if'];
if (str_contains(json_encode($test, JSON_THROW_ON_ERROR), '#/$defs/')) {
$test['$defs'] = $schema['$defs'];
}
$actual = json_decode(json_encode(is_array($value) && ! array_is_list($value) ? (object) $value : $value, JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR);
$yes = $validator->validate($actual, json_decode(json_encode($test, JSON_THROW_ON_ERROR), false, 512, JSON_THROW_ON_ERROR))->isValid();
$walk($value, $condition[$yes ? 'then' : 'else'] ?? [], $path);
}
foreach ($requirements as $key) {
$v = is_array($value) ? ($value[$key] ?? null) : null;
$paths[ltrim($path.'.'.$key, '.')] = $v !== null && $v !== '' && $v !== [];
}
foreach ($node['properties'] ?? [] as $key => $child) {
if (is_array($value) && array_key_exists($key, $value)) {
$walk($value[$key], $child, ltrim($path.'.'.$key, '.'));
}
}
if (isset($node['items']) && is_array($value) && array_is_list($value)) {
foreach ($value as $i => $child) {
$walk($child, $node['items'], $path.'.'.$i);
}
}
};
$walk($payload, ['$ref' => '#/$defs/opportunityCreate'], '');
$filled = count(array_filter($paths));
$total = count($paths);
return ['percentage' => $total ? round(100 * $filled / $total, 2) : 0, 'filled' => $filled, 'applicable' => $total,
'missing_paths' => array_keys(array_filter($paths, fn ($filled) => ! $filled)),
'rule' => 'required-applicable-fields-v1', 'scope' => 'Required fields and activated schema conditions; optional/hidden modules excluded. Presence is not validation or approval.'];
}
}