Backup #318
| ID | 318 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Controller/Api/ExplorerController.php |
| Version | 7 |
| Typ |
modified |
| Größe | 18.5 KB |
| Hash | 0957f19f376a29b68d24d56fe2a36ec7f3119d3fd77cc1aabfe96be7ed59fcc2 |
| Datum | 2025-12-22 08:06:23 |
| Geändert von | claude-code-hook |
| Grund | Claude 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;
/**
* 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;
public function __construct()
{
$this->db = DatabaseFactory::dev();
}
/**
* 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 = $_GET['category'] ?? '';
$status = $_GET['status'] ?? '';
$search = $_GET['search'] ?? '';
$limit = min((int) ($_GET['limit'] ?? 50), 100);
$offset = (int) ($_GET['offset'] ?? 0);
$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());
}
}
/**
* POST /api/v1/explorer/suche
*/
public function suche(): void
... (37 weitere Zeilen)
Vollständig herunterladen
Aktionen
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