diff --git a/api/search.php b/api/search.php index 882b8be..fb0b04d 100644 --- a/api/search.php +++ b/api/search.php @@ -9,7 +9,13 @@ $input = dbnToolsJsonInput(12000); $language = dbnToolsNormalizeLanguage($input['language'] ?? 'en'); dbnToolsWithTelemetry('search', $language, function () use ($input, $language): array { - $query = dbnToolsString($input, 'query', 2000); - $limit = max(1, min(10, (int)($input['limit'] ?? 6))); - return (new DbnLegalToolsService())->search($query, $language, $limit); + $query = dbnToolsString($input, 'query', 2000); + $limit = max(1, min(10, (int)($input['limit'] ?? 6))); + $temporalMode = in_array($input['temporal_mode'] ?? '', ['legal_conservative', 'disabled'], true) + ? $input['temporal_mode'] + : 'disabled'; + $asOfDate = isset($input['as_of_date']) && preg_match('/^\d{4}(-\d{2}(-\d{2})?)?$/', $input['as_of_date']) + ? $input['as_of_date'] + : null; + return (new DbnLegalToolsService())->search($query, $language, $limit, $temporalMode, $asOfDate); }); diff --git a/includes/LegalTools.php b/includes/LegalTools.php index 935b6c8..bbdc7ee 100644 --- a/includes/LegalTools.php +++ b/includes/LegalTools.php @@ -15,13 +15,19 @@ final class DbnLegalToolsService $this->azure = $azure ?: new DbnAzureOpenAiGateway(); } - public function search(string $query, string $language = 'en', int $limit = 6): array - { + public function search( + string $query, + string $language = 'en', + int $limit = 6, + string $temporalMode = 'disabled', + ?string $asOfDate = null + ): array { $query = trim($query); if (mb_strlen($query, 'UTF-8') < 3) { dbnToolsAbort('Search query must be at least 3 characters.', 422, 'query_too_short'); } $limit = max(1, min(10, $limit)); + $temporalMode = in_array($temporalMode, ['legal_conservative', 'disabled'], true) ? $temporalMode : 'disabled'; $trace = [ $this->trace('Query interpretation', 'Searching Do Better Norge private corpus plus the subscribed family-legal package.', 'complete'), @@ -56,6 +62,16 @@ final class DbnLegalToolsService 'min_private' => 0, 'include_beta_website' => true, ]); + + // Apply temporal reranking after retrieval (optional) + if ($temporalMode === 'legal_conservative' && !empty($chunks)) { + $temporalLayerPath = __DIR__ . '/../../ai-portal/platform/includes/LegalTemporalLayer.php'; + if (file_exists($temporalLayerPath)) { + require_once $temporalLayerPath; + $layer = new LegalTemporalLayer(['temporal_mode' => $temporalMode]); + $chunks = $layer->rerank($chunks, $query, $asOfDate); + } + } } catch (Throwable $e) { $retrievalNote = 'SQL keyword fallback after ClientRagPipeline error'; $trace[] = $this->trace('Search fallback', 'Pipeline retrieval failed; using direct SQL keyword fallback without storing the query.', 'warning'); @@ -483,16 +499,24 @@ PROMPT; $score = isset($chunk['similarity']) ? round((float)$chunk['similarity'], 4) : null; $rawExcerpt = dbnToolsExcerpt((string)($chunk['content'] ?? ''), 620); return [ - 'title' => $title, - 'excerpt' => $docSummary ?? $rawExcerpt, - 'chunk_text' => $rawExcerpt, - 'package_or_corpus' => (string)($chunk['source_name'] ?? $chunk['source_type'] ?? 'Do Better Norge'), - 'score' => $score, - 'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null, - 'chunk_id' => isset($chunk['id']) ? (int)$chunk['id'] : null, - 'section' => $chunk['section_title'] ?? null, - 'authority_type' => $chunk['authority_type'] ?? null, - 'jurisdiction' => $chunk['jurisdiction'] ?? null, + 'title' => $title, + 'excerpt' => $docSummary ?? $rawExcerpt, + 'chunk_text' => $rawExcerpt, + 'package_or_corpus' => (string)($chunk['source_name'] ?? $chunk['source_type'] ?? 'Do Better Norge'), + 'score' => $score, + 'document_id' => isset($chunk['document_id']) ? (int)$chunk['document_id'] : null, + 'chunk_id' => isset($chunk['id']) ? (int)$chunk['id'] : null, + 'section' => $chunk['section_title'] ?? null, + 'authority_type' => $chunk['authority_type'] ?? null, + 'jurisdiction' => $chunk['jurisdiction'] ?? null, + // Temporal annotations (present when temporal_mode = 'legal_conservative') + 'temporal_state' => $chunk['temporal_state'] ?? null, + 'temporal_kind' => $chunk['temporal_kind'] ?? null, + 'temporal_reason' => $chunk['temporal_reason'] ?? null, + 'currentness_warning' => $chunk['currentness_warning'] ?? null, + 'valid_from' => $chunk['valid_from'] ?? null, + 'valid_until' => $chunk['valid_until'] ?? null, + 'is_current_version' => $chunk['is_current_version'] ?? null, ]; }