101 lines
4.4 KiB
Python
101 lines
4.4 KiB
Python
"""
|
|
FILE: app/core/ingestion/ingestion_db.py
|
|
DESCRIPTION: Datenbank-Schnittstelle für Note-Metadaten und Artefakt-Prüfung.
|
|
WP-14: Umstellung auf zentrale database-Infrastruktur.
|
|
WP-20/22: Cloud-Resilienz und Fehlerbehandlung.
|
|
WP-24c: Implementierung der herkunftsbasierten Lösch-Logik (Origin-Purge).
|
|
Verhindert das versehentliche Löschen von inversen Kanten beim Re-Import.
|
|
Integration der Authority-Prüfung für Point-IDs zur Symmetrie-Validierung.
|
|
VERSION: 2.2.1 (WP-24c: Robust Authority Lookup)
|
|
STATUS: Active
|
|
"""
|
|
import logging
|
|
from typing import Optional, Tuple, List
|
|
from qdrant_client import QdrantClient
|
|
from qdrant_client.http import models as rest
|
|
|
|
# Import der modularisierten Namen-Logik zur Sicherstellung der Konsistenz
|
|
from app.core.database import collection_names
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def fetch_note_payload(client: QdrantClient, prefix: str, note_id: str) -> Optional[dict]:
|
|
"""Holt die Metadaten einer Note aus Qdrant via Scroll."""
|
|
notes_col, _, _ = collection_names(prefix)
|
|
try:
|
|
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)
|
|
return pts[0].payload if pts else None
|
|
except Exception as e:
|
|
logger.debug(f"Note {note_id} not found: {e}")
|
|
return None
|
|
|
|
def artifacts_missing(client: QdrantClient, prefix: str, note_id: str) -> Tuple[bool, bool]:
|
|
"""Prüft Qdrant aktiv auf vorhandene Chunks und Edges für eine Note."""
|
|
_, chunks_col, edges_col = collection_names(prefix)
|
|
try:
|
|
f = rest.Filter(must=[rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))])
|
|
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)
|
|
return (not bool(c_pts)), (not bool(e_pts))
|
|
except Exception as e:
|
|
logger.error(f"Error checking artifacts for {note_id}: {e}")
|
|
return True, True
|
|
|
|
def is_explicit_edge_present(client: QdrantClient, prefix: str, edge_id: str) -> bool:
|
|
"""
|
|
WP-24c: Prüft via Point-ID, ob bereits eine explizite Kante existiert.
|
|
Wird vom IngestionProcessor in Phase 2 genutzt, um das Überschreiben
|
|
von manuellem Wissen durch virtuelle Symmetrie-Kanten zu verhindern.
|
|
"""
|
|
if not edge_id: return False
|
|
|
|
_, _, edges_col = collection_names(prefix)
|
|
try:
|
|
# retrieve ist der schnellste Weg, um einen spezifischen Punkt via ID zu laden
|
|
res = client.retrieve(
|
|
collection_name=edges_col,
|
|
ids=[edge_id],
|
|
with_payload=True
|
|
)
|
|
# Wenn der Punkt existiert und NICHT virtuell ist, handelt es sich um eine Nutzer-Autorität
|
|
if res and len(res) > 0:
|
|
payload = res[0].payload
|
|
if not payload.get("virtual", False):
|
|
return True
|
|
return False
|
|
except Exception as e:
|
|
logger.debug(f"Authority check for {edge_id} failed: {e}")
|
|
return False
|
|
|
|
def purge_artifacts(client: QdrantClient, prefix: str, note_id: str):
|
|
"""
|
|
WP-24c: Selektives Löschen von Artefakten vor einem Re-Import.
|
|
Implementiert das Origin-Purge-Prinzip zur Sicherung der bidirektionalen Graph-Integrität.
|
|
"""
|
|
_, chunks_col, edges_col = collection_names(prefix)
|
|
|
|
try:
|
|
# 1. Chunks löschen (immer fest an die note_id gebunden)
|
|
chunks_filter = rest.Filter(must=[
|
|
rest.FieldCondition(key="note_id", match=rest.MatchValue(value=note_id))
|
|
])
|
|
client.delete(
|
|
collection_name=chunks_col,
|
|
points_selector=rest.FilterSelector(filter=chunks_filter)
|
|
)
|
|
|
|
# 2. WP-24c: Kanten löschen (HERKUNFTS-BASIERT via origin_note_id)
|
|
# Wir löschen alle Kanten, die von DIESER Note erzeugt wurden.
|
|
edges_filter = rest.Filter(must=[
|
|
rest.FieldCondition(key="origin_note_id", match=rest.MatchValue(value=note_id))
|
|
])
|
|
client.delete(
|
|
collection_name=edges_col,
|
|
points_selector=rest.FilterSelector(filter=edges_filter)
|
|
)
|
|
|
|
logger.info(f"🧹 [PURGE] Global artifacts owned by '{note_id}' cleared.")
|
|
|
|
except Exception as e:
|
|
logger.error(f"❌ [PURGE ERROR] Failed to clear artifacts for {note_id}: {e}") |