From be2bed99274c08bd5f7ff00ca5ee8637fb9e93bc Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 10 Jan 2026 17:03:44 +0100 Subject: [PATCH] Update qdrant_points.py, ingestion_processor.py, and import_markdown.py to version 4.1.0: Enhance edge ID generation by incorporating target_section for improved multigraph support and symmetry integrity. Update documentation and logging for clarity, ensuring consistent ID generation across phases and compatibility with the ingestion workflow. --- app/core/database/qdrant_points.py | 18 ++++--- app/core/ingestion/ingestion_processor.py | 66 ++++++++++++++++++----- scripts/import_markdown.py | 18 +++++-- 3 files changed, 78 insertions(+), 24 deletions(-) diff --git a/app/core/database/qdrant_points.py b/app/core/database/qdrant_points.py index 6cefd1c..f5b7716 100644 --- a/app/core/database/qdrant_points.py +++ b/app/core/database/qdrant_points.py @@ -2,7 +2,7 @@ FILE: app/core/database/qdrant_points.py DESCRIPTION: Object-Mapper für Qdrant. Konvertiert JSON-Payloads (Notes, Chunks, Edges) in PointStructs und generiert deterministische UUIDs. -VERSION: 4.0.0 (WP-24c: Gold-Standard Identity - 4-Parameter-ID) +VERSION: 4.1.0 (WP-24c: Gold-Standard Identity v4.1.0 - target_section Support) STATUS: Active DEPENDENCIES: qdrant_client, uuid, os, app.core.graph.graph_utils LAST_ANALYSIS: 2026-01-10 @@ -95,11 +95,12 @@ def _normalize_edge_payload(pl: dict) -> dict: def points_for_edges(prefix: str, edge_payloads: List[dict]) -> Tuple[str, List[rest.PointStruct]]: """ Konvertiert Kanten-Payloads in PointStructs. - WP-24c v4.0.0: Nutzt die zentrale _mk_edge_id Funktion aus graph_utils. + WP-24c v4.1.0: Nutzt die zentrale _mk_edge_id Funktion aus graph_utils. Dies eliminiert den ID-Drift zwischen manuellen und virtuellen Kanten. - GOLD-STANDARD v4.0.0: Die ID-Generierung verwendet STRICT nur die 4 Parameter - (kind, source_id, target_id, scope). rule_id und variant werden ignoriert. + GOLD-STANDARD v4.1.0: Die ID-Generierung verwendet 4 Parameter + optional target_section + (kind, source_id, target_id, scope, target_section). + rule_id und variant werden ignoriert, target_section fließt ein (Multigraph-Support). """ _, _, edges_col = _names(prefix) points: List[rest.PointStruct] = [] @@ -107,23 +108,26 @@ def points_for_edges(prefix: str, edge_payloads: List[dict]) -> Tuple[str, List[ for raw in edge_payloads: pl = _normalize_edge_payload(raw) - # Extraktion der Identitäts-Parameter (GOLD-STANDARD v4.0.0: nur 4 Parameter) + # Extraktion der Identitäts-Parameter (GOLD-STANDARD v4.1.0) kind = pl.get("kind", "edge") s = pl.get("source_id", "unknown-src") t = pl.get("target_id", "unknown-tgt") scope = pl.get("scope", "note") + target_section = pl.get("target_section") # WP-24c v4.1.0: target_section für Section-Links # Hinweis: rule_id und variant werden im Payload gespeichert, # fließen aber NICHT in die ID-Generierung ein (v4.0.0 Standard) + # target_section fließt in die ID ein (v4.1.0: Multigraph-Support für Section-Links) try: # Aufruf der Single-Source-of-Truth für IDs - # GOLD-STANDARD v4.0.0: Nur 4 Parameter werden verwendet + # GOLD-STANDARD v4.1.0: 4 Parameter + optional target_section point_id = _mk_edge_id( kind=kind, s=s, t=t, - scope=scope + scope=scope, + target_section=target_section ) # Synchronisierung des Payloads mit der berechneten ID diff --git a/app/core/ingestion/ingestion_processor.py b/app/core/ingestion/ingestion_processor.py index 07df591..f6b103e 100644 --- a/app/core/ingestion/ingestion_processor.py +++ b/app/core/ingestion/ingestion_processor.py @@ -4,12 +4,13 @@ DESCRIPTION: Der zentrale IngestionService (Orchestrator). WP-25a: Integration der Mixture of Experts (MoE) Architektur. WP-15b: Two-Pass Workflow mit globalem Kontext-Cache. WP-20/22: Cloud-Resilienz und Content-Lifecycle integriert. - AUDIT v4.0.0: - - GOLD-STANDARD v4.0.0: Phase 2 verwendet exakt dieselbe 4-Parameter-ID wie Phase 1. + AUDIT v4.1.0: + - GOLD-STANDARD v4.1.0: Symmetrie-Integrität korrigiert (note_id, source_id, kind, target_section). + - Phase 2 verwendet exakt dieselbe ID-Generierung wie Phase 1 (inkl. target_section). - Authority-Check in Phase 2 prüft mit konsistenter ID-Generierung. - Eliminiert Duplikate durch inkonsistente ID-Generierung (Steinzeitaxt-Problem). - Beibehaltung der strikten 2-Phasen-Strategie (Authority-First). -VERSION: 4.0.0 (WP-24c: Gold-Standard Identity) +VERSION: 4.1.0 (WP-24c: Gold-Standard Identity v4.1.0) STATUS: Active """ import logging @@ -146,21 +147,31 @@ class IngestionService: logger.info(f"🔄 PHASE 2: Validiere {len(self.symmetry_buffer)} Symmetrien gegen Live-DB...") final_virtuals = [] for v_edge in self.symmetry_buffer: - src, tgt, kind = v_edge.get("note_id"), v_edge.get("target_id"), v_edge.get("kind") - if not src or not tgt: continue + # WP-24c v4.1.0: Korrekte Extraktion der Identitäts-Parameter + src = v_edge.get("source_id") or v_edge.get("note_id") # source_id hat Priorität + tgt = v_edge.get("target_id") + kind = v_edge.get("kind") + scope = v_edge.get("scope", "note") + target_section = v_edge.get("target_section") # WP-24c v4.1.0: target_section berücksichtigen + + if not all([src, tgt, kind]): + continue - # WP-24c v4.0.0: Nutzung der zentralisierten ID-Logik aus graph_utils - # GOLD-STANDARD: Exakt 4 Parameter (kind, source, target, scope) + # WP-24c v4.1.0: Nutzung der zentralisierten ID-Logik aus graph_utils + # GOLD-STANDARD v4.1.0: ID-Generierung muss absolut synchron zu Phase 1 sein + # - Wenn target_section vorhanden, muss es in die ID einfließen + # - Dies stellt sicher, dass der Authority-Check korrekt funktioniert try: - v_id = _mk_edge_id(kind, src, tgt, "note") + v_id = _mk_edge_id(kind, src, tgt, scope, target_section=target_section) except ValueError: continue # AUTHORITY-CHECK: Nur schreiben, wenn keine manuelle Kante existiert - # Prüft mit exakt derselben 4-Parameter-ID, die in Phase 1 verwendet wurde + # Prüft mit exakt derselben ID, die in Phase 1 verwendet wurde (inkl. target_section) if not is_explicit_edge_present(self.client, self.prefix, v_id): final_virtuals.append(v_edge) - logger.info(f" 🔄 [SYMMETRY] Add inverse: {src} --({kind})--> {tgt}") + section_info = f" (section: {target_section})" if target_section else "" + logger.info(f" 🔄 [SYMMETRY] Add inverse: {src} --({kind})--> {tgt}{section_info}") else: logger.info(f" 🛡️ [PROTECTED] Manuelle Kante gefunden. Symmetrie für {kind} unterdrückt.") @@ -245,14 +256,41 @@ class IngestionService: if not self._is_valid_id(t_id): continue resolved_kind = edge_registry.resolve(e.get("kind", "related_to"), provenance="explicit") - e.update({"kind": resolved_kind, "target_id": t_id, "origin_note_id": note_id, "virtual": False}) + # WP-24c v4.1.0: target_section aus dem Edge-Payload extrahieren und beibehalten + target_section = e.get("target_section") + e.update({ + "kind": resolved_kind, + "relation": resolved_kind, # Konsistenz: kind und relation identisch + "target_id": t_id, + "source_id": e.get("source_id") or note_id, # Sicherstellen, dass source_id gesetzt ist + "origin_note_id": note_id, + "virtual": False + }) explicit_edges.append(e) - # Symmetrie puffern + # Symmetrie puffern (WP-24c v4.1.0: Korrekte Symmetrie-Integrität) inv_kind = edge_registry.get_inverse(resolved_kind) if inv_kind and t_id != note_id: - v_edge = e.copy() - v_edge.update({"note_id": t_id, "target_id": note_id, "kind": inv_kind, "virtual": True, "origin_note_id": note_id}) + # GOLD-STANDARD v4.1.0: Symmetrie-Integrität + # - note_id: Besitzer-Wechsel zum Link-Ziel + # - source_id: Neue Quelle (Note-ID des Link-Ziels) + # - target_id: Ursprüngliche Quelle (note_id) + # - kind/relation: Invers setzen + # - target_section: Beibehalten (falls vorhanden) + # - scope: Immer "note" für Symmetrien (Note-Level Backbone) + v_edge = { + "note_id": t_id, # Besitzer-Wechsel: Symmetrie gehört zum Link-Ziel + "source_id": t_id, # Neue Quelle ist das Link-Ziel + "target_id": note_id, # Ziel ist die ursprüngliche Quelle + "kind": inv_kind, # Inverser Kanten-Typ + "relation": inv_kind, # Konsistenz: kind und relation identisch + "scope": "note", # Symmetrien sind immer Note-Level + "virtual": True, + "origin_note_id": note_id, # Tracking: Woher kommt die Symmetrie + } + # target_section beibehalten, falls vorhanden (für Section-Links) + if target_section: + v_edge["target_section"] = target_section self.symmetry_buffer.append(v_edge) # DB Upsert diff --git a/scripts/import_markdown.py b/scripts/import_markdown.py index 107372b..f50ff44 100644 --- a/scripts/import_markdown.py +++ b/scripts/import_markdown.py @@ -2,9 +2,9 @@ # -*- coding: utf-8 -*- """ FILE: scripts/import_markdown.py -VERSION: 2.6.1 (2026-01-10) +VERSION: 2.6.2 (WP-24c: Gold-Standard v4.1.0) STATUS: Active (Core) -COMPATIBILITY: IngestionProcessor v3.4.2+, graph_utils v1.6.2+ +COMPATIBILITY: IngestionProcessor v4.0.0+, graph_utils v4.1.0+ Zweck: ------- @@ -108,7 +108,19 @@ async def main_async(args): # Diese Liste stellt sicher, dass keine System-Leichen oder temporäre Dateien # den Graphen korrumpieren oder zu ID-Kollisionen führen. files = [] - ignore_list = [".trash", ".obsidian", ".sync", "templates", "_system", ".git"] + + # WP-24c v4.1.0: MINDNET_IGNORE_FOLDERS aus Umgebungsvariable + # Format: Komma-separierte Liste von Ordnernamen (z.B. "trash,temp,archive") + env_ignore = os.getenv("MINDNET_IGNORE_FOLDERS", "") + env_ignore_list = [f.strip() for f in env_ignore.split(",") if f.strip()] if env_ignore else [] + + # Standard-Ignore-Liste (System-Ordner) + default_ignore_list = [".trash", ".obsidian", ".sync", "templates", "_system", ".git"] + + # Kombinierte Ignore-Liste (Umgebungsvariable hat Priorität, wird mit Defaults kombiniert) + ignore_list = list(set(default_ignore_list + env_ignore_list)) + + logger.info(f"📁 Ignore-Liste: {ignore_list}") for f in all_files_raw: f_str = str(f)