You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
3.7 KiB
130 lines
3.7 KiB
"""
|
|
routers/meta.py — /health, /stats, /tags, /graph endpoints.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter
|
|
import httpx
|
|
|
|
from core.database import get_pool
|
|
from core.settings import Settings
|
|
from models.responses import HealthResponse, StatsResponse, TagCount, GraphResponse, GraphNode, GraphEdge
|
|
|
|
router = APIRouter(tags=['meta'])
|
|
|
|
|
|
def _get_settings() -> Settings:
|
|
from main import app_settings
|
|
return app_settings
|
|
|
|
|
|
@router.get('/health', response_model=HealthResponse)
|
|
async def health():
|
|
settings = _get_settings()
|
|
db_status = 'ok'
|
|
ollama_status = 'ok'
|
|
|
|
try:
|
|
pool = await get_pool()
|
|
async with pool.acquire() as conn:
|
|
await conn.fetchval('SELECT 1')
|
|
except Exception:
|
|
db_status = 'error'
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
resp = await client.get(f'{settings.ollama_url}/api/tags')
|
|
if resp.status_code != 200:
|
|
ollama_status = 'error'
|
|
except Exception:
|
|
ollama_status = 'unavailable'
|
|
|
|
overall = 'ok' if db_status == 'ok' else 'degraded'
|
|
return HealthResponse(
|
|
status=overall,
|
|
database=db_status,
|
|
ollama=ollama_status,
|
|
version=settings.app_version,
|
|
)
|
|
|
|
|
|
@router.get('/stats', response_model=StatsResponse)
|
|
async def stats():
|
|
settings = _get_settings()
|
|
pool = await get_pool()
|
|
async with pool.acquire() as conn:
|
|
docs = await conn.fetchval('SELECT COUNT(*) FROM documents')
|
|
chunks = await conn.fetchval('SELECT COUNT(*) FROM chunks')
|
|
relations = await conn.fetchval('SELECT COUNT(*) FROM relations')
|
|
tags_count = await conn.fetchval(
|
|
"SELECT COUNT(DISTINCT tag) FROM documents, unnest(tags) AS tag"
|
|
)
|
|
last_indexed = await conn.fetchval(
|
|
'SELECT MAX(indexed_at) FROM documents'
|
|
)
|
|
return StatsResponse(
|
|
total_documents=docs or 0,
|
|
total_chunks=chunks or 0,
|
|
total_relations=relations or 0,
|
|
total_tags=tags_count or 0,
|
|
last_indexed=last_indexed,
|
|
embedding_model=settings.embedding_model,
|
|
chat_model=settings.chat_model,
|
|
)
|
|
|
|
|
|
@router.get('/tags', response_model=list[TagCount])
|
|
async def list_tags():
|
|
pool = await get_pool()
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch(
|
|
"""
|
|
SELECT tag, COUNT(*) AS count
|
|
FROM documents, unnest(tags) AS tag
|
|
GROUP BY tag
|
|
ORDER BY count DESC, tag
|
|
"""
|
|
)
|
|
return [TagCount(tag=row['tag'], count=row['count']) for row in rows]
|
|
|
|
|
|
@router.get('/graph', response_model=GraphResponse)
|
|
async def knowledge_graph(limit: int = 200):
|
|
pool = await get_pool()
|
|
async with pool.acquire() as conn:
|
|
doc_rows = await conn.fetch(
|
|
'SELECT id, title, path, tags, word_count FROM documents LIMIT $1',
|
|
limit,
|
|
)
|
|
rel_rows = await conn.fetch(
|
|
"""
|
|
SELECT r.source_doc_id::text, r.target_doc_id::text, r.relation_type, r.label
|
|
FROM relations r
|
|
WHERE r.target_doc_id IS NOT NULL
|
|
LIMIT $1
|
|
""",
|
|
limit * 3,
|
|
)
|
|
|
|
nodes = [
|
|
GraphNode(
|
|
id=str(row['id']),
|
|
title=row['title'] or '',
|
|
path=row['path'],
|
|
tags=list(row['tags'] or []),
|
|
word_count=row['word_count'],
|
|
)
|
|
for row in doc_rows
|
|
]
|
|
edges = [
|
|
GraphEdge(
|
|
source=row['source_doc_id'],
|
|
target=row['target_doc_id'],
|
|
relation_type=row['relation_type'],
|
|
label=row['label'],
|
|
)
|
|
for row in rel_rows
|
|
]
|
|
return GraphResponse(nodes=nodes, edges=edges)
|