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 $filters * @return list> */ 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']); } if ($filters['public_only'] ?? false) { $builder->where('d.data_classification', 'public')->where('d.rights_status', '!=', 'restricted') ->whereExists(fn ($q) => $q->selectRaw('1')->from('portal_resources as r')->whereColumn('r.document_id', 'd.id')->where('r.published', true)->where('r.rights_status', '!=', 'restricted')); } 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> */ 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 $ids */ public function allStillEligible(array $ids): bool { return $ids === [] || $this->eligible()->whereIn('s.id', $ids)->count() === count(array_unique($ids)); } }