2025-12-27 10:25:35 +01:00
|
|
|
"""
|
|
|
|
|
FILE: app/core/ingestion/ingestion_db.py
|
|
|
|
|
DESCRIPTION: Datenbank-Schnittstelle für Note-Metadaten und Artefakt-Prüfung.
|
2026-01-10 07:45:43 +01:00
|
|
|
WP-14: Umstellung auf zentrale database-Infrastruktur.
|
2026-01-10 10:56:47 +01:00
|
|
|
WP-24c: Integration der Authority-Prüfung für Point-IDs.
|
|
|
|
|
Ermöglicht dem Prozessor die Unterscheidung zwischen
|
|
|
|
|
manueller Nutzer-Autorität und virtuellen Symmetrien.
|
|
|
|
|
VERSION: 2.2.0 (WP-24c: Authority Lookup Integration)
|
2026-01-09 14:41:50 +01:00
|
|
|
STATUS: Active
|
2025-12-27 10:25:35 +01:00
|
|
|
"""
|
2026-01-09 14:41:50 +01:00
|
|
|
import logging
|
2026-01-09 21:07:02 +01:00
|
|
|
from typing import Optional, Tuple, List
|
2025-12-27 10:25:35 +01:00
|
|
|
from qdrant_client import QdrantClient
|
|
|
|
|
from qdrant_client.http import models as rest
|
2026-01-10 07:45:43 +01:00
|
|
|
|
|
|
|
|
# Import der modularisierten Namen-Logik zur Sicherstellung der Konsistenz
|
2025-12-27 19:47:23 +01:00
|
|
|
from app.core.database import collection_names
|
|
|
|
|
|
2026-01-09 14:41:50 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2025-12-27 10:25:35 +01:00
|
|
|
def fetch_note_payload(client: QdrantClient, prefix: str, note_id: str) -> Optional[dict]:
|
2026-01-10 10:56:47 +01:00
|
|
|
"""
|
|
|
|
|
Holt die Metadaten einer Note aus Qdrant via Scroll-API.
|
|
|
|
|
Wird primär für die Change-Detection (Hash-Vergleich) genutzt.
|
|
|
|
|
"""
|
2025-12-27 19:47:23 +01:00
|
|
|
notes_col, _, _ = collection_names(prefix)
|
2025-12-27 10:25:35 +01:00
|
|
|
try:
|
2026-01-10 10:56:47 +01:00
|
|
|
f = rest.Filter(must=[
|
|
|
|
|
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
|
|
|
|
])
|
|
|
|
|
pts, _ = client.scroll(
|
|
|
|
|
collection_name=notes_col,
|
|
|
|
|
scroll_filter=f,
|
|
|
|
|
limit=1,
|
|
|
|
|
with_payload=True
|
|
|
|
|
)
|
2025-12-27 10:25:35 +01:00
|
|
|
return pts[0].payload if pts else None
|
2026-01-09 14:41:50 +01:00
|
|
|
except Exception as e:
|
2026-01-10 10:56:47 +01:00
|
|
|
logger.debug(f"Note {note_id} not found or error during fetch: {e}")
|
2026-01-09 14:41:50 +01:00
|
|
|
return None
|
2025-12-27 10:25:35 +01:00
|
|
|
|
|
|
|
|
def artifacts_missing(client: QdrantClient, prefix: str, note_id: str) -> Tuple[bool, bool]:
|
2026-01-10 10:56:47 +01:00
|
|
|
"""
|
|
|
|
|
Prüft Qdrant aktiv auf vorhandene Chunks und Edges für eine Note.
|
|
|
|
|
Gibt (chunks_missing, edges_missing) als Boolean-Tupel zurück.
|
|
|
|
|
"""
|
2025-12-27 19:47:23 +01:00
|
|
|
_, chunks_col, edges_col = collection_names(prefix)
|
2025-12-27 10:25:35 +01:00
|
|
|
try:
|
2026-01-10 10:56:47 +01:00
|
|
|
# Filter für die note_id Suche
|
|
|
|
|
f = rest.Filter(must=[
|
|
|
|
|
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
|
|
|
|
])
|
2025-12-27 19:47:23 +01:00
|
|
|
c_pts, _ = client.scroll(collection_name=chunks_col, scroll_filter=f, limit=1)
|
|
|
|
|
e_pts, _ = client.scroll(collection_name=edges_col, scroll_filter=f, limit=1)
|
2025-12-27 10:25:35 +01:00
|
|
|
return (not bool(c_pts)), (not bool(e_pts))
|
2026-01-09 14:41:50 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Error checking artifacts for {note_id}: {e}")
|
|
|
|
|
return True, True
|
2025-12-27 10:25:35 +01:00
|
|
|
|
2026-01-09 21:07:02 +01:00
|
|
|
def is_explicit_edge_present(client: QdrantClient, prefix: str, edge_id: str) -> bool:
|
|
|
|
|
"""
|
2026-01-10 07:25:43 +01:00
|
|
|
WP-24c: Prüft via Point-ID, ob bereits eine explizite Kante existiert.
|
2026-01-10 08:06:07 +01:00
|
|
|
Wird vom IngestionProcessor in Phase 2 genutzt, um das Überschreiben
|
|
|
|
|
von manuellem Wissen durch virtuelle Symmetrie-Kanten zu verhindern.
|
|
|
|
|
|
2026-01-10 10:56:47 +01:00
|
|
|
Args:
|
|
|
|
|
edge_id: Die deterministisch berechnete UUID der Kante.
|
|
|
|
|
Returns:
|
|
|
|
|
True, wenn eine physische Kante (virtual=False) existiert.
|
|
|
|
|
"""
|
|
|
|
|
if not edge_id:
|
|
|
|
|
return False
|
|
|
|
|
|
2026-01-09 21:07:02 +01:00
|
|
|
_, _, edges_col = collection_names(prefix)
|
|
|
|
|
try:
|
2026-01-10 10:56:47 +01:00
|
|
|
# retrieve ist die effizienteste Methode für den Zugriff via ID
|
2026-01-10 08:06:07 +01:00
|
|
|
res = client.retrieve(
|
|
|
|
|
collection_name=edges_col,
|
|
|
|
|
ids=[edge_id],
|
|
|
|
|
with_payload=True
|
|
|
|
|
)
|
2026-01-10 10:56:47 +01:00
|
|
|
|
2026-01-10 08:06:07 +01:00
|
|
|
if res and len(res) > 0:
|
2026-01-10 10:56:47 +01:00
|
|
|
# Wir prüfen das 'virtual' Flag im Payload
|
|
|
|
|
is_virtual = res[0].payload.get("virtual", False)
|
|
|
|
|
if not is_virtual:
|
|
|
|
|
return True # Es ist eine explizite Nutzer-Kante
|
|
|
|
|
|
2026-01-09 21:07:02 +01:00
|
|
|
return False
|
2026-01-10 08:06:07 +01:00
|
|
|
except Exception as e:
|
2026-01-10 10:56:47 +01:00
|
|
|
logger.debug(f"Authority check failed for ID {edge_id}: {e}")
|
2026-01-09 21:07:02 +01:00
|
|
|
return False
|
|
|
|
|
|
2025-12-27 10:25:35 +01:00
|
|
|
def purge_artifacts(client: QdrantClient, prefix: str, note_id: str):
|
2026-01-10 08:06:07 +01:00
|
|
|
"""
|
2026-01-10 10:56:47 +01:00
|
|
|
Löscht verwaiste Chunks und Edges einer Note vor einem Re-Import.
|
|
|
|
|
Stellt sicher, dass keine Duplikate bei Inhaltsänderungen entstehen.
|
2026-01-10 08:06:07 +01:00
|
|
|
"""
|
2025-12-27 19:47:23 +01:00
|
|
|
_, chunks_col, edges_col = collection_names(prefix)
|
2026-01-09 14:41:50 +01:00
|
|
|
try:
|
2026-01-10 10:56:47 +01:00
|
|
|
f = rest.Filter(must=[
|
2026-01-10 08:06:07 +01:00
|
|
|
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
|
|
|
|
])
|
2026-01-10 10:56:47 +01:00
|
|
|
# Chunks löschen
|
2026-01-10 08:06:07 +01:00
|
|
|
client.delete(
|
|
|
|
|
collection_name=chunks_col,
|
2026-01-10 10:56:47 +01:00
|
|
|
points_selector=rest.FilterSelector(filter=f)
|
2026-01-10 08:06:07 +01:00
|
|
|
)
|
2026-01-10 10:56:47 +01:00
|
|
|
# Edges löschen
|
2026-01-10 08:06:07 +01:00
|
|
|
client.delete(
|
|
|
|
|
collection_name=edges_col,
|
2026-01-10 10:56:47 +01:00
|
|
|
points_selector=rest.FilterSelector(filter=f)
|
2026-01-10 08:06:07 +01:00
|
|
|
)
|
2026-01-10 10:56:47 +01:00
|
|
|
logger.info(f"🧹 [PURGE] Local artifacts for '{note_id}' cleared.")
|
2026-01-09 14:41:50 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"❌ [PURGE ERROR] Failed to clear artifacts for {note_id}: {e}")
|