Protokoll #27153

ID27153
Zeitstempel2025-12-28 02:32:52.032776
Clientroot
IP145.224.72.140
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens7,347 (Input: 1,657, Output: 5,690)
Dauer88 ms
Request-Zeit2025-12-28 02:32:52.032776
Response-Zeit2025-12-28 02:32:52.120708

Request

{
    "event": "PreToolUse",
    "tool_name": "Edit",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/Docs\/HybridSearchService.php",
        "old_string": "    \/** Enriches vector results with SQL data and applies filters. *\/\n    private function enrichAndFilter(array $vectorResults, array $filters): array\n    {\n        $results = [];\n        $minScore = $filters['min_score'] ?? 0.3;\n\n        foreach ($vectorResults as $vr) {\n            \/\/ Apply minimum score filter\n            if ($vr['score'] < $minScore) {\n                continue;\n            }\n\n            $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);\n            if ($chunkId === 0) {\n                continue;\n            }\n\n            \/\/ Get full chunk data from DB\n            $chunk = $this->getChunkWithDocument($chunkId);\n            if ($chunk === null) {\n                continue;\n            }\n\n            \/\/ Apply entity filter\n            if (isset($filters['entity_name'])) {\n                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n                $found = false;\n                foreach ($entities as $entity) {\n                    if (isset($entity['name']) && stripos($entity['name'], $filters['entity_name']) !== false) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            \/\/ Apply entity type filter\n            if (isset($filters['entity_type'])) {\n                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n                $found = false;\n                foreach ($entities as $entity) {\n                    if (isset($entity['type']) && strtoupper($entity['type']) === strtoupper($filters['entity_type'])) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            \/\/ Apply keyword filter\n            if (isset($filters['keyword'])) {\n                $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);\n                $found = false;\n                foreach ($keywords as $kw) {\n                    if (stripos($kw, $filters['keyword']) !== false) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            $results[] = [\n                'chunk_id' => $chunkId,\n                'doc_id' => (int) $chunk['dokumentation_id'],\n                'path' => $chunk['doc_path'] ?? '',\n                'title' => $chunk['doc_title'] ?? '',\n                'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',\n                'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),\n                'taxonomy' => $this->decodeJsonArray($chunk['taxonomy_path'] ?? null),\n                'entities' => $this->decodeJsonArray($chunk['entities'] ?? null),\n                'keywords' => $this->decodeJsonArray($chunk['keywords'] ?? null),\n                'score' => $vr['score'],\n                'relevance_score' => $vr['score'], \/\/ Will be adjusted in rerank\n            ];\n        }\n\n        return $results;\n    }",
        "new_string": "    \/** Enriches vector results with SQL data and applies filters. *\/\n    private function enrichAndFilter(array $vectorResults, array $filters): array\n    {\n        $results = [];\n        $minScore = $filters['min_score'] ?? 0.3;\n        foreach ($vectorResults as $vr) {\n            if ($vr['score'] < $minScore) {\n                continue;\n            }\n            $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);\n            if ($chunkId === 0) {\n                continue;\n            }\n            $chunk = $this->getChunkWithDocument($chunkId);\n            if ($chunk === null) {\n                continue;\n            }\n            \/\/ Apply entity\/keyword filters\n            if (!$this->matchesFilters($chunk, $filters)) {\n                continue;\n            }\n            $results[] = [\n                'chunk_id' => $chunkId, 'doc_id' => (int) $chunk['dokumentation_id'],\n                'path' => $chunk['doc_path'] ?? '', 'title' => $chunk['doc_title'] ?? '',\n                'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',\n                'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),\n                'taxonomy' => $this->decodeJsonArray($chunk['taxonomy_path'] ?? null),\n                'entities' => $this->decodeJsonArray($chunk['entities'] ?? null),\n                'keywords' => $this->decodeJsonArray($chunk['keywords'] ?? null),\n                'score' => $vr['score'], 'relevance_score' => $vr['score'],\n            ];\n        }\n        return $results;\n    }\n\n    \/** Checks if chunk matches entity\/keyword filters. *\/\n    private function matchesFilters(array $chunk, array $filters): bool\n    {\n        if (isset($filters['entity_name'])) {\n            $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n            $found = false;\n            foreach ($entities as $e) {\n                if (isset($e['name']) && stripos($e['name'], $filters['entity_name']) !== false) {\n                    $found = true;\n                    break;\n                }\n            }\n            if (!$found) {\n                return false;\n            }\n        }\n        if (isset($filters['entity_type'])) {\n            $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n            $found = false;\n            foreach ($entities as $e) {\n                if (isset($e['type']) && strtoupper($e['type']) === strtoupper($filters['entity_type'])) {\n                    $found = true;\n                    break;\n                }\n            }\n            if (!$found) {\n                return false;\n            }\n        }\n        if (isset($filters['keyword'])) {\n            $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);\n            $found = false;\n            foreach ($keywords as $kw) {\n                if (stripos($kw, $filters['keyword']) !== false) {\n                    $found = true;\n                    break;\n                }\n            }\n            if (!$found) {\n                return false;\n            }\n        }\n        return true;\n    }"
    }
}

Response

{
    "tool_response": {
        "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/src\/Infrastructure\/Docs\/HybridSearchService.php",
        "oldString": "    \/** Enriches vector results with SQL data and applies filters. *\/\n    private function enrichAndFilter(array $vectorResults, array $filters): array\n    {\n        $results = [];\n        $minScore = $filters['min_score'] ?? 0.3;\n\n        foreach ($vectorResults as $vr) {\n            \/\/ Apply minimum score filter\n            if ($vr['score'] < $minScore) {\n                continue;\n            }\n\n            $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);\n            if ($chunkId === 0) {\n                continue;\n            }\n\n            \/\/ Get full chunk data from DB\n            $chunk = $this->getChunkWithDocument($chunkId);\n            if ($chunk === null) {\n                continue;\n            }\n\n            \/\/ Apply entity filter\n            if (isset($filters['entity_name'])) {\n                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n                $found = false;\n                foreach ($entities as $entity) {\n                    if (isset($entity['name']) && stripos($entity['name'], $filters['entity_name']) !== false) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            \/\/ Apply entity type filter\n            if (isset($filters['entity_type'])) {\n                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n                $found = false;\n                foreach ($entities as $entity) {\n                    if (isset($entity['type']) && strtoupper($entity['type']) === strtoupper($filters['entity_type'])) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            \/\/ Apply keyword filter\n            if (isset($filters['keyword'])) {\n                $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);\n                $found = false;\n                foreach ($keywords as $kw) {\n                    if (stripos($kw, $filters['keyword']) !== false) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            $results[] = [\n                'chunk_id' => $chunkId,\n                'doc_id' => (int) $chunk['dokumentation_id'],\n                'path' => $chunk['doc_path'] ?? '',\n                'title' => $chunk['doc_title'] ?? '',\n                'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',\n                'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),\n                'taxonomy' => $this->decodeJsonArray($chunk['taxonomy_path'] ?? null),\n                'entities' => $this->decodeJsonArray($chunk['entities'] ?? null),\n                'keywords' => $this->decodeJsonArray($chunk['keywords'] ?? null),\n                'score' => $vr['score'],\n                'relevance_score' => $vr['score'], \/\/ Will be adjusted in rerank\n            ];\n        }\n\n        return $results;\n    }",
        "newString": "    \/** Enriches vector results with SQL data and applies filters. *\/\n    private function enrichAndFilter(array $vectorResults, array $filters): array\n    {\n        $results = [];\n        $minScore = $filters['min_score'] ?? 0.3;\n        foreach ($vectorResults as $vr) {\n            if ($vr['score'] < $minScore) {\n                continue;\n            }\n            $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);\n            if ($chunkId === 0) {\n                continue;\n            }\n            $chunk = $this->getChunkWithDocument($chunkId);\n            if ($chunk === null) {\n                continue;\n            }\n            \/\/ Apply entity\/keyword filters\n            if (!$this->matchesFilters($chunk, $filters)) {\n                continue;\n            }\n            $results[] = [\n                'chunk_id' => $chunkId, 'doc_id' => (int) $chunk['dokumentation_id'],\n                'path' => $chunk['doc_path'] ?? '', 'title' => $chunk['doc_title'] ?? '',\n                'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',\n                'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),\n                'taxonomy' => $this->decodeJsonArray($chunk['taxonomy_path'] ?? null),\n                'entities' => $this->decodeJsonArray($chunk['entities'] ?? null),\n                'keywords' => $this->decodeJsonArray($chunk['keywords'] ?? null),\n                'score' => $vr['score'], 'relevance_score' => $vr['score'],\n            ];\n        }\n        return $results;\n    }\n\n    \/** Checks if chunk matches entity\/keyword filters. *\/\n    private function matchesFilters(array $chunk, array $filters): bool\n    {\n        if (isset($filters['entity_name'])) {\n            $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n            $found = false;\n            foreach ($entities as $e) {\n                if (isset($e['name']) && stripos($e['name'], $filters['entity_name']) !== false) {\n                    $found = true;\n                    break;\n                }\n            }\n            if (!$found) {\n                return false;\n            }\n        }\n        if (isset($filters['entity_type'])) {\n            $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n            $found = false;\n            foreach ($entities as $e) {\n                if (isset($e['type']) && strtoupper($e['type']) === strtoupper($filters['entity_type'])) {\n                    $found = true;\n                    break;\n                }\n            }\n            if (!$found) {\n                return false;\n            }\n        }\n        if (isset($filters['keyword'])) {\n            $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);\n            $found = false;\n            foreach ($keywords as $kw) {\n                if (stripos($kw, $filters['keyword']) !== false) {\n                    $found = true;\n                    break;\n                }\n            }\n            if (!$found) {\n                return false;\n            }\n        }\n        return true;\n    }",
        "originalFile": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Infrastructure\\Docs;\n\n\/\/ @responsibility: Hybrid-Suche kombiniert Qdrant-Vektoren mit SQL-Filtern\n\nuse Domain\\Service\\SearchServiceInterface;\nuse Infrastructure\\AI\\OllamaService;\nuse Infrastructure\\Config\\CredentialService;\nuse Infrastructure\\Traits\\JsonDecodeTrait;\nuse PDO;\nuse RuntimeException;\n\nfinal class HybridSearchService implements SearchServiceInterface\n{\n    use JsonDecodeTrait;\n\n    private const string COLLECTION = 'dokumentation_chunks';\n    private const int TIMEOUT = 30;\n\n    private string $qdrantHost;\n\n    public function __construct(\n        private PDO $pdo,\n        private OllamaService $ollama\n    ) {\n        $this->qdrantHost = CredentialService::getQdrantHost();\n    }\n\n    \/** Hybrid search combining semantic vectors with SQL filters. *\/\n    public function search(string $query, array $filters = [], int $limit = 10): array\n    {\n        \/\/ Stage 1: Semantic search in Qdrant\n        $vectorResults = $this->semanticSearch($query, $filters, $limit * 3);\n\n        if (empty($vectorResults)) {\n            return [];\n        }\n\n        \/\/ Stage 2: Enrich with SQL data and apply filters\n        $enrichedResults = $this->enrichAndFilter($vectorResults, $filters);\n\n        \/\/ Stage 3: Re-rank based on combined score\n        $rankedResults = $this->rerank($enrichedResults, $query);\n\n        return array_slice($rankedResults, 0, $limit);\n    }\n\n    \/** Searches within a specific taxonomy category. *\/\n    public function searchByCategory(string $query, string $category, int $limit = 10): array\n    {\n        return $this->search($query, ['taxonomy_category' => $category], $limit);\n    }\n\n    \/** Searches for chunks containing a specific entity. *\/\n    public function searchByEntity(string $query, string $entityName, int $limit = 10): array\n    {\n        return $this->search($query, ['entity_name' => $entityName], $limit);\n    }\n\n    \/** Gets all available taxonomy categories with counts. *\/\n    public function getTaxonomyCategories(): array\n    {\n        $stmt = $this->pdo->query('\n            SELECT taxonomy_category as category, COUNT(*) as count\n            FROM dokumentation_chunks\n            WHERE taxonomy_category IS NOT NULL\n            GROUP BY taxonomy_category\n            ORDER BY count DESC\n        ');\n\n        return $stmt->fetchAll(PDO::FETCH_ASSOC);\n    }\n\n    \/** Gets all entities grouped by type. *\/\n    public function getEntitiesByType(): array\n    {\n        $stmt = $this->pdo->query(\"\n            SELECT entities FROM dokumentation_chunks\n            WHERE entities IS NOT NULL AND entities != '[]'\n        \");\n\n        $byType = [];\n\n        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {\n            $entities = $this->decodeJsonArray($row['entities'] ?? null);\n            foreach ($entities as $entity) {\n                if (isset($entity['name'], $entity['type'])) {\n                    $type = $entity['type'];\n                    if (!isset($byType[$type])) {\n                        $byType[$type] = [];\n                    }\n                    if (!in_array($entity['name'], $byType[$type], true)) {\n                        $byType[$type][] = $entity['name'];\n                    }\n                }\n            }\n        }\n\n        return $byType;\n    }\n\n    \/** Suggests related searches based on current results. *\/\n    public function suggestRelatedSearches(array $results): array\n    {\n        $suggestions = [];\n\n        foreach ($results as $result) {\n            \/\/ Add keywords from results\n            foreach ($result['keywords'] ?? [] as $keyword) {\n                if (!in_array($keyword, $suggestions, true)) {\n                    $suggestions[] = $keyword;\n                }\n            }\n\n            \/\/ Add entity names\n            foreach ($result['entities'] ?? [] as $entity) {\n                if (isset($entity['name']) && !in_array($entity['name'], $suggestions, true)) {\n                    $suggestions[] = $entity['name'];\n                }\n            }\n        }\n\n        return array_slice($suggestions, 0, 5);\n    }\n\n    \/** Performs semantic search in Qdrant. *\/\n    private function semanticSearch(string $query, array $filters, int $limit): array\n    {\n        $embedding = $this->ollama->getEmbedding($query);\n\n        $url = sprintf('%s\/collections\/%s\/points\/search', $this->qdrantHost, self::COLLECTION);\n\n        $payload = [\n            'vector' => array_values($embedding),\n            'limit' => $limit,\n            'with_payload' => true,\n        ];\n\n        \/\/ Add Qdrant filter if taxonomy category specified\n        if (isset($filters['taxonomy_category'])) {\n            $payload['filter'] = [\n                'must' => [\n                    [\n                        'key' => 'taxonomy_category',\n                        'match' => ['value' => $filters['taxonomy_category']],\n                    ],\n                ],\n            ];\n        }\n\n        try {\n            $response = $this->makeRequest($url, $payload, 'POST');\n\n            if (!isset($response['result']) || !is_array($response['result'])) {\n                return [];\n            }\n\n            return array_map(static function (array $item): array {\n                return [\n                    'id' => (string) $item['id'],\n                    'score' => (float) ($item['score'] ?? 0),\n                    'payload' => is_array($item['payload'] ?? null) ? $item['payload'] : [],\n                ];\n            }, $response['result']);\n        } catch (RuntimeException) {\n            return [];\n        }\n    }\n\n    \/** Enriches vector results with SQL data and applies filters. *\/\n    private function enrichAndFilter(array $vectorResults, array $filters): array\n    {\n        $results = [];\n        $minScore = $filters['min_score'] ?? 0.3;\n\n        foreach ($vectorResults as $vr) {\n            \/\/ Apply minimum score filter\n            if ($vr['score'] < $minScore) {\n                continue;\n            }\n\n            $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);\n            if ($chunkId === 0) {\n                continue;\n            }\n\n            \/\/ Get full chunk data from DB\n            $chunk = $this->getChunkWithDocument($chunkId);\n            if ($chunk === null) {\n                continue;\n            }\n\n            \/\/ Apply entity filter\n            if (isset($filters['entity_name'])) {\n                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n                $found = false;\n                foreach ($entities as $entity) {\n                    if (isset($entity['name']) && stripos($entity['name'], $filters['entity_name']) !== false) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            \/\/ Apply entity type filter\n            if (isset($filters['entity_type'])) {\n                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);\n                $found = false;\n                foreach ($entities as $entity) {\n                    if (isset($entity['type']) && strtoupper($entity['type']) === strtoupper($filters['entity_type'])) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            \/\/ Apply keyword filter\n            if (isset($filters['keyword'])) {\n                $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);\n                $found = false;\n                foreach ($keywords as $kw) {\n                    if (stripos($kw, $filters['keyword']) !== false) {\n                        $found = true;\n                        break;\n                    }\n                }\n                if (!$found) {\n                    continue;\n                }\n            }\n\n            $results[] = [\n                'chunk_id' => $chunkId,\n                'doc_id' => (int) $chunk['dokumentation_id'],\n                'path' => $chunk['doc_path'] ?? '',\n                'title' => $chunk['doc_title'] ?? '',\n                'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',\n                'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),\n                'taxonomy' => $this->decodeJsonArray($chunk['taxonomy_path'] ?? null),\n                'entities' => $this->decodeJsonArray($chunk['entities'] ?? null),\n                'keywords' => $this->decodeJsonArray($chunk['keywords'] ?? null),\n                'score' => $vr['score'],\n                'relevance_score' => $vr['score'], \/\/ Will be adjusted in rerank\n            ];\n        }\n\n        return $results;\n    }\n\n    \/** Re-ranks results based on combined semantic and structural relevance. *\/\n    private function rerank(array $results, string $query): array\n    {\n        $queryWords = array_filter(preg_split('\/\\s+\/', strtolower($query)) ?: []);\n\n        foreach ($results as &$result) {\n            $boost = 0.0;\n\n            \/\/ Boost for keyword matches\n            foreach ($result['keywords'] as $keyword) {\n                foreach ($queryWords as $word) {\n                    if (stripos($keyword, $word) !== false) {\n                        $boost += 0.05;\n                    }\n                }\n            }\n\n            \/\/ Boost for entity matches\n            foreach ($result['entities'] as $entity) {\n                if (isset($entity['name'])) {\n                    foreach ($queryWords as $word) {\n                        if (stripos($entity['name'], $word) !== false) {\n                            $boost += 0.03;\n                        }\n                    }\n                }\n            }\n\n            \/\/ Boost for title matches\n            foreach ($queryWords as $word) {\n                if (stripos($result['title'], $word) !== false) {\n                    $boost += 0.1;\n                }\n            }\n\n            $res... [TRUNCATED-0f32640b3b500487]",
        "structuredPatch": [
            {
                "oldStart": 175,
                "oldLines": 87,
                "newStart": 175,
                "newLines": 81,
                "lines": [
                    "     {",
                    "         $results = [];",
                    "         $minScore = $filters['min_score'] ?? 0.3;",
                    "-",
                    "         foreach ($vectorResults as $vr) {",
                    "-            \/\/ Apply minimum score filter",
                    "             if ($vr['score'] < $minScore) {",
                    "                 continue;",
                    "             }",
                    "-",
                    "             $chunkId = (int) ($vr['payload']['chunk_id'] ?? 0);",
                    "             if ($chunkId === 0) {",
                    "                 continue;",
                    "             }",
                    "-",
                    "-            \/\/ Get full chunk data from DB",
                    "             $chunk = $this->getChunkWithDocument($chunkId);",
                    "             if ($chunk === null) {",
                    "                 continue;",
                    "             }",
                    "-",
                    "-            \/\/ Apply entity filter",
                    "-            if (isset($filters['entity_name'])) {",
                    "-                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);",
                    "-                $found = false;",
                    "-                foreach ($entities as $entity) {",
                    "-                    if (isset($entity['name']) && stripos($entity['name'], $filters['entity_name']) !== false) {",
                    "-                        $found = true;",
                    "-                        break;",
                    "-                    }",
                    "-                }",
                    "-                if (!$found) {",
                    "-                    continue;",
                    "-                }",
                    "+            \/\/ Apply entity\/keyword filters",
                    "+            if (!$this->matchesFilters($chunk, $filters)) {",
                    "+                continue;",
                    "             }",
                    "-",
                    "-            \/\/ Apply entity type filter",
                    "-            if (isset($filters['entity_type'])) {",
                    "-                $entities = $this->decodeJsonArray($chunk['entities'] ?? null);",
                    "-                $found = false;",
                    "-                foreach ($entities as $entity) {",
                    "-                    if (isset($entity['type']) && strtoupper($entity['type']) === strtoupper($filters['entity_type'])) {",
                    "-                        $found = true;",
                    "-                        break;",
                    "-                    }",
                    "-                }",
                    "-                if (!$found) {",
                    "-                    continue;",
                    "-                }",
                    "-            }",
                    "-",
                    "-            \/\/ Apply keyword filter",
                    "-            if (isset($filters['keyword'])) {",
                    "-                $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);",
                    "-                $found = false;",
                    "-                foreach ($keywords as $kw) {",
                    "-                    if (stripos($kw, $filters['keyword']) !== false) {",
                    "-                        $found = true;",
                    "-                        break;",
                    "-                    }",
                    "-                }",
                    "-                if (!$found) {",
                    "-                    continue;",
                    "-                }",
                    "-            }",
                    "-",
                    "             $results[] = [",
                    "-                'chunk_id' => $chunkId,",
                    "-                'doc_id' => (int) $chunk['dokumentation_id'],",
                    "-                'path' => $chunk['doc_path'] ?? '',",
                    "-                'title' => $chunk['doc_title'] ?? '',",
                    "+                'chunk_id' => $chunkId, 'doc_id' => (int) $chunk['dokumentation_id'],",
                    "+                'path' => $chunk['doc_path'] ?? '', 'title' => $chunk['doc_title'] ?? '',",
                    "                 'content' => $chunk['content_clean'] ?? $chunk['content'] ?? '',",
                    "                 'heading_path' => $this->decodeJsonArray($chunk['heading_path'] ?? null),",
                    "                 'taxonomy' => $this->decodeJsonArray($chunk['taxonomy_path'] ?? null),",
                    "                 'entities' => $this->decodeJsonArray($chunk['entities'] ?? null),",
                    "                 'keywords' => $this->decodeJsonArray($chunk['keywords'] ?? null),",
                    "-                'score' => $vr['score'],",
                    "-                'relevance_score' => $vr['score'], \/\/ Will be adjusted in rerank",
                    "+                'score' => $vr['score'], 'relevance_score' => $vr['score'],",
                    "             ];",
                    "         }",
                    "-",
                    "         return $results;",
                    "     }",
                    " ",
                    "+    \/** Checks if chunk matches entity\/keyword filters. *\/",
                    "+    private function matchesFilters(array $chunk, array $filters): bool",
                    "+    {",
                    "+        if (isset($filters['entity_name'])) {",
                    "+            $entities = $this->decodeJsonArray($chunk['entities'] ?? null);",
                    "+            $found = false;",
                    "+            foreach ($entities as $e) {",
                    "+                if (isset($e['name']) && stripos($e['name'], $filters['entity_name']) !== false) {",
                    "+                    $found = true;",
                    "+                    break;",
                    "+                }",
                    "+            }",
                    "+            if (!$found) {",
                    "+                return false;",
                    "+            }",
                    "+        }",
                    "+        if (isset($filters['entity_type'])) {",
                    "+            $entities = $this->decodeJsonArray($chunk['entities'] ?? null);",
                    "+            $found = false;",
                    "+            foreach ($entities as $e) {",
                    "+                if (isset($e['type']) && strtoupper($e['type']) === strtoupper($filters['entity_type'])) {",
                    "+                    $found = true;",
                    "+                    break;",
                    "+                }",
                    "+            }",
                    "+            if (!$found) {",
                    "+                return false;",
                    "+            }",
                    "+        }",
                    "+        if (isset($filters['keyword'])) {",
                    "+            $keywords = $this->decodeJsonArray($chunk['keywords'] ?? null);",
                    "+            $found = false;",
                    "+            foreach ($keywords as $kw) {",
                    "+                if (stripos($kw, $filters['keyword']) !== false) {",
                    "+                    $found = true;",
                    "+                    break;",
                    "+                }",
                    "+            }",
                    "+            if (!$found) {",
                    "+                return false;",
                    "+            }",
                    "+        }",
                    "+        return true;",
                    "+    }",
                    "+",
                    "     \/** Re-ranks results based on combined semantic and structural relevance. *\/",
                    "     private function rerank(array $results, string $query): array",
                    "     {"
                ]
            }
        ],
        "userModified": false,
        "replaceAll": false
    }
}
← Vorheriger Zur Liste Nächster →