diff --git a/TODO.md b/TODO.md index 93caa82..a49e99b 100644 --- a/TODO.md +++ b/TODO.md @@ -41,12 +41,3 @@ Check for duplicate or highly similar documents using vector similarity and grap 3. Check graph relationships 4. Return candidates with similarity scores -## System Statistics - -#### `GET /stats` -Get system statistics (wiki pages, neo4j nodes, qdrant vectors). - -**Implementation needed:** -- Query Neo4j for node count -- Query Qdrant for vector count -- Query Wiki.js for page count diff --git a/src/main.py b/src/main.py index 2855c65..34f1b3a 100644 --- a/src/main.py +++ b/src/main.py @@ -18,7 +18,7 @@ from pathlib import Path from src.config import Settings, get_settings, __version__ from src.core.dependencies import ( - verify_api_key, QdrantDep, WikiJSDep, OllamaDep, Neo4jDep + verify_api_key, QdrantDep, WikiJSDep, OllamaDep, Neo4jDep, PaperlessDep ) from src.core.multi_tenancy import DEFAULT_USER @@ -85,6 +85,14 @@ class HealthResponse(BaseModel): services: Dict[str, Any] +class StatsResponse(BaseModel): + """System statistics response model.""" + neo4j: Dict[str, int] + qdrant: Dict[str, Any] + wiki_pages: int + paperless: Dict[str, Any] + + # Routes @app.get("/", tags=["Root"]) async def root() -> Dict[str, str]: @@ -141,6 +149,85 @@ async def health(settings: Settings = Depends(get_settings)) -> HealthResponse: ) +@app.get("/stats", response_model=StatsResponse, tags=["System"]) +async def stats( + user: str = Query(default=DEFAULT_USER, description="User identifier"), + neo4j: Neo4jDep = None, + qdrant: QdrantDep = None, + wikijs: WikiJSDep = None, + paperless: PaperlessDep = None, + api_key: str = Depends(verify_api_key) +) -> StatsResponse: + """ + Get system statistics. + + Returns counts for: + - Neo4j: nodes by type (Document, Entity, Collection, Search) + - Qdrant: vectors per collection + - Wiki.js: total page count + - Paperless: documents, tags, correspondents, document types + """ + # Neo4j node counts by label + neo4j_stats = {} + try: + for label in ["Document", "Entity", "Collection", "Search"]: + result = await neo4j.execute_query( + f"MATCH (n:{label}) RETURN count(n) as count" + ) + neo4j_stats[label.lower() + "_nodes"] = result[0]["count"] if result else 0 + except Exception as e: + logger.error(f"Failed to get Neo4j stats: {e}") + neo4j_stats = {"error": str(e)} + + # Qdrant collection stats + qdrant_stats = {} + try: + collections = await qdrant.list_collections() + qdrant_stats["collections"] = len(collections) + qdrant_stats["total_vectors"] = sum(c.get("vectors_count", 0) for c in collections) + qdrant_stats["by_collection"] = { + c["name"]: c["vectors_count"] for c in collections + } + except Exception as e: + logger.error(f"Failed to get Qdrant stats: {e}") + qdrant_stats = {"error": str(e)} + + # Wiki.js page count + wiki_pages = 0 + try: + pages = await wikijs.list_all_pages(user) + wiki_pages = len(pages) + except Exception as e: + logger.warning(f"Failed to get Wiki.js stats: {e}") + + # Paperless-ngx document stats + paperless_stats = {} + try: + # Get document count (page_size=1 for efficiency, we just need the count) + docs_result = await paperless.list_documents(page_size=1) + paperless_stats["documents"] = docs_result.get("count", 0) + + # Get metadata counts + tags = await paperless.list_tags() + paperless_stats["tags"] = len(tags) + + correspondents = await paperless.list_correspondents() + paperless_stats["correspondents"] = len(correspondents) + + doc_types = await paperless.list_document_types() + paperless_stats["document_types"] = len(doc_types) + except Exception as e: + logger.warning(f"Failed to get Paperless stats: {e}") + paperless_stats = {"error": str(e)} + + return StatsResponse( + neo4j=neo4j_stats, + qdrant=qdrant_stats, + wiki_pages=wiki_pages, + paperless=paperless_stats + ) + + @app.post("/ingest/check-updates", tags=["Ingestion"]) async def check_updates( documents: Dict[str, Any],