""" 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-24c: Implementierung der herkunftsbasierten Lösch-Logik (Origin-Purge). Verhindert das versehentliche Löschen von inversen Kanten beim Re-Import. VERSION: 2.1.0 (WP-24c: Protected Purge Logic) STATUS: Active """ import logging from typing import Optional, Tuple 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: # Filter für die Existenz-Prüfung (Klassisch via note_id) 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 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) # Wir löschen alle Kanten, die von DIESER Note erzeugt wurden (origin_note_id). # Dies umfasst: # - Alle ausgehenden Kanten (A -> B) # - Alle inversen Kanten, die diese Note in anderen Notizen "deponiert" hat (B -> A) # Fremde inverse Kanten (C -> A) bleiben erhalten. 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}")