Backup #351

ID351
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Controller/Api/ExplorerController.php
Version9
Typ modified
Größe18.6 KB
Hashc439946b6a6e6c87d7a19ba746c5c26e9484de7c29c94d7af2738c8ebdb9d3dc
Datum2025-12-22 08:18:29
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

namespace Controller\Api;

use Framework\Controller;
use Infrastructure\Config\DatabaseFactory;
use Infrastructure\Docs\HybridSearchService;
use Infrastructure\Persistence\SystemExplorerRepository;

/**
 * ExplorerController - REST-API für Doc2Vector Pipeline
 *
 * Endpoints für Dokumente, Seiten, Chunks, Taxonomie und Suche.
 * Uses ki_dev database for dokumentation tables.
 */
class ExplorerController extends Controller
{
    private \PDO $db;
    private SystemExplorerRepository $repository;

    public function __construct()
    {
        $this->db = DatabaseFactory::dev();
        $this->repository = new SystemExplorerRepository();
    }

    /**
     * GET /api/v1/explorer/stats
     */
    public function stats(): void
    {
        try {
            // Dokumente (depth=0)
            $dokumenteCount = (int) $this->db->query(
                'SELECT COUNT(*) FROM dokumentation WHERE depth = 0'
            )->fetchColumn();

            // Seiten (depth>0)
            $seitenCount = (int) $this->db->query(
                'SELECT COUNT(*) FROM dokumentation WHERE depth > 0'
            )->fetchColumn();

            // Chunk-Statistiken
            $chunkStats = $this->db->query(
                'SELECT
                    COUNT(*) as total,
                    COALESCE(SUM(token_count), 0) as tokens,
                    SUM(CASE WHEN analysis_status = "completed" THEN 1 ELSE 0 END) as analyzed,
                    SUM(CASE WHEN qdrant_id IS NOT NULL THEN 1 ELSE 0 END) as synced
                 FROM dokumentation_chunks'
            )->fetch();

            // Taxonomie-Kategorien
            $taxonomyCategories = $this->db->query(
                'SELECT taxonomy_category, COUNT(*) as count
                 FROM dokumentation_chunks
                 WHERE taxonomy_category IS NOT NULL
                 GROUP BY taxonomy_category
                 ORDER BY count DESC'
            )->fetchAll();

            $this->json([
                'success' => true,
                'data' => [
                    'dokumente' => $dokumenteCount,
                    'seiten' => $seitenCount,
                    'chunks' => [
                        'total' => (int) $chunkStats['total'],
                        'tokens' => (int) $chunkStats['tokens'],
                        'analyzed' => (int) $chunkStats['analyzed'],
                        'synced' => (int) $chunkStats['synced'],
                    ],
                    'taxonomy_categories' => $taxonomyCategories,
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/dokumente
     */
    public function listDokumente(): void
    {
        try {
            $dokumente = $this->db->query(
                'SELECT d.id, d.title, d.path, d.created_at, d.updated_at,
                        (SELECT COUNT(*) FROM dokumentation WHERE parent_id = d.id) as seiten_count,
                        (SELECT COUNT(*) FROM dokumentation_chunks WHERE dokumentation_id IN
                            (SELECT id FROM dokumentation WHERE parent_id = d.id OR id = d.id)) as total_chunks,
                        (SELECT COALESCE(SUM(token_count), 0) FROM dokumentation_chunks WHERE dokumentation_id IN
                            (SELECT id FROM dokumentation WHERE parent_id = d.id OR id = d.id)) as total_tokens
                 FROM dokumentation d
                 WHERE d.depth = 0
                 ORDER BY d.title'
            )->fetchAll();

            $this->json([
                'success' => true,
                'data' => $dokumente,
                'meta' => [
                    'total' => count($dokumente),
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/dokumente/{id}
     */
    public function getDokument(string $id): void
    {
        try {
            $stmt = $this->db->prepare(
                'SELECT * FROM dokumentation WHERE id = :id AND depth = 0'
            );
            $stmt->execute(['id' => (int) $id]);
            $dokument = $stmt->fetch();

            if ($dokument === false) {
                $this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404);

                return;
            }

            // Seiten
            $stmt = $this->db->prepare(
                'SELECT s.id, s.title, s.path, s.depth,
                        (SELECT COUNT(*) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as chunks_count,
                        (SELECT COALESCE(SUM(token_count), 0) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as token_count
                 FROM dokumentation s
                 WHERE s.parent_id = :id
                 ORDER BY s.title'
            );
            $stmt->execute(['id' => (int) $id]);
            $seiten = $stmt->fetchAll();

            // Taxonomie
            $stmt = $this->db->prepare(
                'SELECT taxonomy_category, COUNT(*) as count
                 FROM dokumentation_chunks
                 WHERE dokumentation_id IN (
                     SELECT id FROM dokumentation WHERE id = :id OR parent_id = :id2
                 )
                 AND taxonomy_category IS NOT NULL
                 GROUP BY taxonomy_category
                 ORDER BY count DESC'
            );
            $stmt->execute(['id' => (int) $id, 'id2' => (int) $id]);
            $taxonomy = $stmt->fetchAll();

            $this->json([
                'success' => true,
                'data' => [
                    'dokument' => $dokument,
                    'seiten' => $seiten,
                    'taxonomy' => $taxonomy,
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/seiten
     */
    public function listSeiten(): void
    {
        try {
            $search = $this->getString('search');
            $parentId = $this->getString('parent_id');
            $limit = $this->getLimit(100, 50);
            $offset = $this->getInt('offset');

            $sql = 'SELECT s.id, s.title, s.path, s.depth, s.parent_id,
                           p.title as parent_title,
                           (SELECT COUNT(*) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as chunks_count,
                           (SELECT COALESCE(SUM(token_count), 0) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as token_count
                    FROM dokumentation s
                    LEFT JOIN dokumentation p ON s.parent_id = p.id
                    WHERE s.depth > 0';

            $countSql = 'SELECT COUNT(*) FROM dokumentation s WHERE s.depth > 0';
            $params = [];

            if ($search !== '') {
                $sql .= ' AND (s.title LIKE :search OR s.path LIKE :search2)';
                $countSql .= ' AND (s.title LIKE :search OR s.path LIKE :search2)';
                $params['search'] = '%' . $search . '%';
                $params['search2'] = '%' . $search . '%';
            }

            if ($parentId !== '') {
                $sql .= ' AND s.parent_id = :parent';
                $countSql .= ' AND s.parent_id = :parent';
                $params['parent'] = (int) $parentId;
            }

            $stmt = $this->db->prepare($countSql);
            $stmt->execute($params);
            $total = (int) $stmt->fetchColumn();

            $sql .= ' ORDER BY p.title, s.depth, s.title LIMIT :limit OFFSET :offset';
            $params['limit'] = $limit;
            $params['offset'] = $offset;

            $stmt = $this->db->prepare($sql);
            foreach ($params as $key => $value) {
                $stmt->bindValue($key, $value, is_int($value) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
            }
            $stmt->execute();
            $seiten = $stmt->fetchAll();

            $this->json([
                'success' => true,
                'data' => $seiten,
                'meta' => [
                    'total' => $total,
                    'limit' => $limit,
                    'offset' => $offset,
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/seiten/{id}
     */
    public function getSeite(string $id): void
    {
        try {
            $stmt = $this->db->prepare(
                'SELECT s.*, p.title as parent_title, p.path as parent_path
                 FROM dokumentation s
                 LEFT JOIN dokumentation p ON s.parent_id = p.id
                 WHERE s.id = :id'
            );
            $stmt->execute(['id' => (int) $id]);
            $seite = $stmt->fetch();

            if ($seite === false) {
                $this->json(['success' => false, 'error' => 'Seite nicht gefunden'], 404);

                return;
            }

            // Chunks
            $stmt = $this->db->prepare(
                'SELECT c.id, c.chunk_index, c.content, c.token_count, c.taxonomy_category,
                        c.taxonomy_path, c.entities, c.keywords, c.analysis_status, c.qdrant_id
                 FROM dokumentation_chunks c
                 WHERE c.dokumentation_id = :id
                 ORDER BY c.chunk_index'
            );
            $stmt->execute(['id' => (int) $id]);
            $chunks = $stmt->fetchAll();

            // Decode JSON
            foreach ($chunks as &$c) {
                $c['entities'] = $this->decodeJson($c['entities'] ?? null);
                $c['keywords'] = $this->decodeJson($c['keywords'] ?? null);
                $c['taxonomy_path'] = $this->decodeJson($c['taxonomy_path'] ?? null);
            }

            // Unterseiten
            $stmt = $this->db->prepare(
                'SELECT id, title, path, depth FROM dokumentation WHERE parent_id = :id ORDER BY title'
            );
            $stmt->execute(['id' => (int) $id]);
            $unterseiten = $stmt->fetchAll();

            $this->json([
                'success' => true,
                'data' => [
                    'seite' => $seite,
                    'chunks' => $chunks,
                    'unterseiten' => $unterseiten,
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/chunks
     */
    public function listChunks(): void
    {
        try {
            $category = $this->getString('category');
            $status = $this->getString('status');
            $search = $this->getString('search');
            $limit = $this->getLimit(100, 50);
            $offset = $this->getInt('offset');

            $sql = 'SELECT c.id, c.chunk_index, c.content, c.token_count, c.taxonomy_category,
                           c.analysis_status, c.qdrant_id, c.created_at,
                           d.id as dokumentation_id, d.title as dokument_title, d.path as dokument_path
                    FROM dokumentation_chunks c
                    JOIN dokumentation d ON c.dokumentation_id = d.id
                    WHERE 1=1';

            $countSql = 'SELECT COUNT(*) FROM dokumentation_chunks c WHERE 1=1';
            $params = [];

            if ($category !== '') {
                $sql .= ' AND c.taxonomy_category = :category';
                $countSql .= ' AND c.taxonomy_category = :category';
                $params['category'] = $category;
            }

            if ($status !== '') {
                $sql .= ' AND c.analysis_status = :status';
                $countSql .= ' AND c.analysis_status = :status';
                $params['status'] = $status;
            }

            if ($search !== '') {
                $sql .= ' AND (c.content LIKE :search OR c.keywords LIKE :search2)';
                $countSql .= ' AND (c.content LIKE :search OR c.keywords LIKE :search2)';
                $params['search'] = '%' . $search . '%';
                $params['search2'] = '%' . $search . '%';
            }

            $stmt = $this->db->prepare($countSql);
            $stmt->execute($params);
            $total = (int) $stmt->fetchColumn();

            $sql .= ' ORDER BY c.created_at DESC LIMIT :limit OFFSET :offset';
            $params['limit'] = $limit;
            $params['offset'] = $offset;

            $stmt = $this->db->prepare($sql);
            foreach ($params as $key => $value) {
                $stmt->bindValue($key, $value, is_int($value) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
            }
            $stmt->execute();
            $chunks = $stmt->fetchAll();

            $this->json([
                'success' => true,
                'data' => $chunks,
                'meta' => [
                    'total' => $total,
                    'limit' => $limit,
                    'offset' => $offset,
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/chunks/{id}
     */
    public function getChunk(string $id): void
    {
        try {
            $stmt = $this->db->prepare(
                'SELECT c.*, d.title as dokument_title, d.path as dokument_path, d.id as dokument_id
                 FROM dokumentation_chunks c
                 JOIN dokumentation d ON c.dokumentation_id = d.id
                 WHERE c.id = :id'
            );
            $stmt->execute(['id' => (int) $id]);
            $chunk = $stmt->fetch();

            if ($chunk === false) {
                $this->json(['success' => false, 'error' => 'Chunk nicht gefunden'], 404);

                return;
            }

            // Decode JSON
            $chunk['entities'] = $this->decodeJson($chunk['entities'] ?? null);
            $chunk['keywords'] = $this->decodeJson($chunk['keywords'] ?? null);
            $chunk['taxonomy_path'] = $this->decodeJson($chunk['taxonomy_path'] ?? null);
            $chunk['heading_path'] = $this->decodeJson($chunk['heading_path'] ?? null);

            $this->json([
                'success' => true,
                'data' => $chunk,
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/taxonomie
     */
    public function taxonomie(): void
    {
        try {
            // Kategorien mit Counts
            $categories = $this->db->query(
                'SELECT taxonomy_category, COUNT(*) as chunk_count,
                        COALESCE(SUM(token_count), 0) as token_count
                 FROM dokumentation_chunks
                 WHERE taxonomy_category IS NOT NULL
                 GROUP BY taxonomy_category
                 ORDER BY chunk_count DESC'
            )->fetchAll();

            // Top Keywords
            $keywordsRaw = $this->db->query(
                'SELECT keywords FROM dokumentation_chunks WHERE keywords IS NOT NULL'
            )->fetchAll(\PDO::FETCH_COLUMN);

            $keywordCounts = [];
            foreach ($keywordsRaw as $json) {
                $keywords = $this->decodeJson($json);
                foreach ($keywords as $kw) {
                    $kw = strtolower(trim($kw));
                    if ($kw !== '') {
                        $keywordCounts[$kw] = ($keywordCounts[$kw] ?? 0) + 1;
                    }
                }
            }
            arsort($keywordCounts);
            $topKeywords = array_slice($keywordCounts, 0, 50, true);

            $this->json([
                'success' => true,
                'data' => [
                    'categories' => $categories,
                    'top_keywords' => $topKeywords,
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**
     * GET /api/v1/explorer/entities
     */
    public function entities(): void
    {
        try {
            // Entities aus allen Chunks aggregieren
            $entitiesRaw = $this->db->query(
                'SELECT entities FROM dokumentation_chunks WHERE entities IS NOT NULL'
            )->fetchAll(\PDO::FETCH_COLUMN);

            $entityCounts = [];
            foreach ($entitiesRaw as $json) {
                $entities = $this->decodeJson($json);
                foreach ($entities as $entity) {
                    $name = $entity['name'] ?? '';
                    $type = $entity['type'] ?? 'OTHER';
                    if ($name !== '') {
                        $key = $name . '|' . $type;
                        if (!isset($entityCounts[$key])) {
                            $entityCounts[$key] = [
                                'name' => $name,
                                'type' => $type,
                                'count' => 0,
                            ];
                        }
                        $entityCounts[$key]['count']++;
                    }
                }
            }

            // Sortieren nach Count
            usort($entityCounts, fn ($a, $b) => $b['count'] <=> $a['count']);

            // Nach Typ gruppieren
            $byType = [];
            foreach ($entityCounts as $entity) {
                $type = $entity['type'];
                if (!isset($byType[$type])) {
                    $byType[$type] = [];
                }
                $byType[$type][] = $entity;
            }

            $this->json([
                'success' => true,
                'data' => [
                    'entities' => array_slice($entityCounts, 0, 100),
                    'by_type' => $byType,
                    'total' => count($entityCounts),
                ],
            ]);
        } catch (\Exception $e) {
            $this->jsonError($e->getMessage());
        }
    }

    /**

... (40 weitere Zeilen)

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
2066 45 modified 9.6 KB 2025-12-28 23:30
2065 44 modified 9.5 KB 2025-12-28 23:30
2064 43 modified 9.4 KB 2025-12-28 23:30
2063 42 modified 9.4 KB 2025-12-28 23:30
2062 41 modified 9.3 KB 2025-12-28 23:29
2061 40 modified 9.2 KB 2025-12-28 23:29
2060 39 modified 9.1 KB 2025-12-28 23:29
2059 38 modified 9.0 KB 2025-12-28 23:29
2058 37 modified 8.9 KB 2025-12-28 23:29
2057 36 modified 8.8 KB 2025-12-28 23:29
1811 35 modified 8.7 KB 2025-12-27 15:30
1810 34 modified 8.7 KB 2025-12-27 15:30
1809 33 modified 8.6 KB 2025-12-27 15:30
1808 32 modified 8.6 KB 2025-12-27 15:30
1807 31 modified 8.6 KB 2025-12-27 15:30
1503 30 modified 8.6 KB 2025-12-25 17:49
1242 29 modified 8.6 KB 2025-12-25 12:34
1241 28 modified 8.6 KB 2025-12-25 12:34
1240 27 modified 8.6 KB 2025-12-25 12:34
1239 26 modified 8.6 KB 2025-12-25 12:34
1238 25 modified 8.5 KB 2025-12-25 12:34
1237 24 modified 8.5 KB 2025-12-25 12:34
1236 23 modified 8.5 KB 2025-12-25 12:34
1235 22 modified 8.5 KB 2025-12-25 12:34
1234 21 modified 8.5 KB 2025-12-25 12:34
1232 20 modified 8.0 KB 2025-12-25 12:33
702 19 modified 8.0 KB 2025-12-23 07:53
625 18 modified 8.0 KB 2025-12-23 04:43
624 17 modified 7.9 KB 2025-12-23 04:43
622 16 modified 7.9 KB 2025-12-23 04:43
591 15 modified 7.9 KB 2025-12-23 04:25
532 14 modified 13.4 KB 2025-12-22 22:18
356 13 modified 15.0 KB 2025-12-22 08:19
354 12 modified 16.0 KB 2025-12-22 08:18
353 11 modified 16.6 KB 2025-12-22 08:18
352 10 modified 17.3 KB 2025-12-22 08:18
351 9 modified 18.6 KB 2025-12-22 08:18
350 8 modified 18.4 KB 2025-12-22 08:16
318 7 modified 18.5 KB 2025-12-22 08:06
317 6 modified 18.5 KB 2025-12-22 08:06
295 5 modified 18.5 KB 2025-12-22 08:03
294 4 modified 18.5 KB 2025-12-22 08:03
285 3 modified 18.5 KB 2025-12-22 07:59
284 2 modified 18.5 KB 2025-12-22 07:59
42 1 modified 19.2 KB 2025-12-20 17:24

← Zurück zur Übersicht