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.

This commit is contained in:
Lars 2026-01-10 17:03:44 +01:00
parent 2da98e8e37
commit be2bed9927
3 changed files with 78 additions and 24 deletions

View File

@ -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

View File

@ -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
# WP-24c v4.0.0: Nutzung der zentralisierten ID-Logik aus graph_utils
# GOLD-STANDARD: Exakt 4 Parameter (kind, source, target, scope)
if not all([src, tgt, kind]):
continue
# 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

View File

@ -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)