57 lines
2.8 KiB
PHP
57 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Ai;
|
|
|
|
use Illuminate\Database\Query\Builder;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
final class EvidenceRepository
|
|
{
|
|
public function eligible(): Builder
|
|
{
|
|
return DB::table('ai_sources as s')->join('documents as d', 'd.id', '=', 's.document_id')
|
|
->where('s.status', 'approved')->where('s.purpose', 'investment_information')
|
|
->where('s.valid_until', '>=', now()->toDateString())->where('s.source_date', '<=', now()->toDateString())
|
|
->where('d.status', 'active')->whereNull('d.deleted_at')->whereIn('d.data_classification', ['public', 'internal'])
|
|
->where('d.malware_scan_status', 'clean')->whereColumn('d.malware_scanned_sha256', 'd.sha256')
|
|
->whereColumn('s.document_version', 'd.version_no')->whereColumn('s.document_checksum', 'd.sha256');
|
|
}
|
|
|
|
/** @param array<string, mixed> $filters
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function search(string $query, string $language, array $filters = []): array
|
|
{
|
|
$configuration = $language === 'pt' ? 'portuguese' : 'english';
|
|
$builder = $this->eligible()->join('ai_chunks as c', 'c.source_id', '=', 's.id')
|
|
->where('s.language', $language)->whereRaw('c.lexical @@ websearch_to_tsquery(?::regconfig, ?)', [$configuration, $query])
|
|
->select(['c.id as chunk_id', 's.id as source_id', 's.document_id', 's.document_version', 's.title', 's.language',
|
|
's.nature', 's.source_url', 's.source_date', 's.valid_until', 'c.content', 'c.checksum'])
|
|
->selectRaw('ts_rank_cd(c.lexical, websearch_to_tsquery(?::regconfig, ?)) as rank', [$configuration, $query]);
|
|
if (! empty($filters['source_id'])) {
|
|
$builder->where('s.id', $filters['source_id']);
|
|
}
|
|
if (! empty($filters['nature'])) {
|
|
$builder->where('s.nature', $filters['nature']);
|
|
}
|
|
|
|
return array_values($builder->orderByDesc('rank')->orderBy('c.id')->limit((int) config('investmoz_ai.retrieval_limit', 5))
|
|
->get()->map(fn ($row) => (array) $row)->all());
|
|
}
|
|
|
|
/** @return list<array<string, mixed>> */
|
|
public function excerpts(string $id): array
|
|
{
|
|
return array_values($this->eligible()->join('ai_chunks as c', 'c.source_id', '=', 's.id')->where('s.id', $id)
|
|
->select(['c.id as chunk_id', 's.id as source_id', 's.document_id', 's.document_version', 's.title', 's.language',
|
|
's.nature', 's.source_url', 's.source_date', 's.valid_until', 'c.content', 'c.checksum'])
|
|
->orderBy('c.position')->limit(3)->get()->map(fn ($row) => (array) $row)->all());
|
|
}
|
|
|
|
/** @param array<array-key, string> $ids */
|
|
public function allStillEligible(array $ids): bool
|
|
{
|
|
return $ids === [] || $this->eligible()->whereIn('s.id', $ids)->count() === count(array_unique($ids));
|
|
}
|
|
}
|