Files
Investidor-V2/app/Services/Ai/InvestorGuidanceEvidence.php
T
nala 1106509116
tests / ci (push) Canceled after 0s
vrsao 2
2026-10-08 14:02:01 +02:00

85 lines
5.4 KiB
PHP

<?php
namespace App\Services\Ai;
use App\Models\User;
use App\Services\VirtualDesk;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class InvestorGuidanceEvidence
{
public const FIELDS = ['market_evidence', 'location_evidence', 'resource_evidence', 'funding_evidence'];
/** @return array<string,array<string,mixed>> */
public function forCase(User $actor, \stdClass $case, VirtualDesk $desk): array
{
$privateScope = ! $desk->owns($actor, $case, false) && ! $actor->can('manage-portal');
$documents = DB::table('desk_documents as l')->join('documents as d', 'd.id', '=', 'l.document_id')->where('l.case_id', $case->id)->whereNull('d.deleted_at')
->when($privateScope, fn ($q) => $q->where(fn ($p) => $p->whereNull('l.request_id')->orWhereIn('l.request_id', DB::table('desk_requests')->where('analyst_id', $actor->id)->select('id'))))
->get(['l.id as link_id', 'l.document_id', 'l.category', 'l.valid_until', 'd.file_name', 'd.version_no', 'd.sha256', 'd.status', 'd.malware_scan_status', 'd.malware_scanned_sha256', 'd.storage_key']);
$reviews = DB::table('desk_events as e')->join('users as u', 'u.id', '=', 'e.actor_id')->where('e.case_id', $case->id)->where('e.action', 'document_reviewed')
->when($privateScope, fn ($q) => $q->where(fn ($p) => $p->whereNull('e.request_id')->orWhereIn('e.request_id', DB::table('desk_requests')->where('analyst_id', $actor->id)->select('id'))))
->orderByDesc('e.event_order')->get(['e.id', 'e.payload', 'e.created_at', 'e.actor_id', 'u.name']);
$result = [];
foreach (self::FIELDS as $field) {
$records = [];
foreach ($documents as $doc) {
$latest = null;
foreach ($reviews as $review) {
$payload = $desk->decode($review->payload);
if (($payload['link_id'] ?? null) === $doc->link_id) {
$latest = ['id' => $review->id, 'author_id' => $review->actor_id, 'author' => $review->name, 'date' => $review->created_at, 'payload' => $payload];
break;
}
}
if ($doc->category !== $field && ! ($latest && in_array($field, $latest['payload']['supports'] ?? [], true))) {
continue;
}
// A later review replaces the earlier scope. Merely choosing a
// document category does not verify a preparation assumption.
if ($latest && ! in_array($field, $latest['payload']['supports'] ?? [], true)) {
$latest = null;
}
$state = 'attached_unchecked';
if ($doc->valid_until && $doc->valid_until < now()->toDateString()) {
$state = 'expired';
} elseif ($latest && (($latest['payload']['sha256'] ?? null) !== trim($doc->sha256) || ($latest['payload']['document_version'] ?? null) !== (int) $doc->version_no)) {
$state = 'superseded';
} elseif ($latest) {
$path = realpath(Storage::disk('local')->path($doc->storage_key));
$root = realpath(Storage::disk('local')->path(''));
if (! $path || ! $root || ! str_starts_with($path, $root.DIRECTORY_SEPARATOR) || ! is_file($path) || ! is_string($hash = hash_file('sha256', $path)) || ! hash_equals(trim($doc->sha256), $hash)) {
$state = 'superseded';
} elseif ($doc->status !== 'active' || $doc->malware_scan_status !== 'clean' || trim($doc->sha256) !== $doc->malware_scanned_sha256) {
$state = 'technical_unavailable';
} else {
$state = match ($latest['payload']['outcome'] ?? '') {
'internal_verified' => 'internally_verified',
'contradiction_identified' => 'contradiction',
default => 'issue_identified',
};
}
}
$records[] = ['document_id' => $doc->document_id, 'link_id' => $doc->link_id, 'name' => $doc->file_name, 'state' => $state, 'version' => (int) $doc->version_no, 'hash' => trim($doc->sha256), 'valid_until' => $doc->valid_until,
'review' => $latest ? ['event_id' => $latest['id'], 'author_id' => $latest['author_id'], 'author' => $latest['author'], 'date' => $latest['date'], 'what_checked' => $latest['payload']['what_checked'], 'evidence' => $latest['payload']['evidence'], 'scope' => $latest['payload']['scope']] : null];
}
if ($records) {
// A specific current contradiction takes precedence; other valid evidence
// remains visible instead of erasing expired or replaced originals.
$states = array_column($records, 'state');
$state = 'attached_unchecked';
foreach (['contradiction', 'internally_verified', 'issue_identified', 'superseded', 'expired', 'technical_unavailable'] as $candidate) {
if (in_array($candidate, $states, true)) {
$state = $candidate;
break;
}
}
$result[$field] = ['state' => $state, 'documents' => $records];
}
}
return $result;
}
}