""" derive_edges.py v1.9.0 Zweck: - Ableiten von Edges aus Note + Chunks. - Rückwärtskompatibel zu bisherigen Funktionen, inkl. Parametern: build_edges_for_note(parsed, note_id, chunks, note_scope_refs=False, ...) - Neu: Type-Registry wird optional benutzt, um fehlende Standardkanten je 'type' (edge_defaults) zu ergänzen, ohne vorhandene explizite Kanten zu überschreiben. """ from __future__ import annotations from typing import Any, Dict, List, Optional, Set, Tuple # Annahme: parser liefert wikilinks und frontmatter; unverändert zum Stand vor der Erweiterung try: from app.core.parser import extract_wikilinks # type: ignore except Exception: def extract_wikilinks(text: str) -> List[str]: # Minimaler Fallback – im Produktivcode steht eure echte Implementierung. import re return re.findall(r"\[\[([^\]]+)\]\]", text or "") # Optional: Registry try: from app.core.type_registry import get_edge_defaults_for_type except Exception: def get_edge_defaults_for_type(note_type: str) -> List[str]: return [] # Edge-Helper (UUIDv5, etc.) werden hier nicht neu definiert; wir liefern nur die # Edge-Strukturen zurück. Upsert passiert über eure bestehenden qdrant_points-Utilities. def _edge( edge_type: str, src_scope: str, src_id: str, dst_scope: str, dst_id: str, note_id: str, extra: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: e = { "type": edge_type, "src_scope": src_scope, "src_id": src_id, "dst_scope": dst_scope, "dst_id": dst_id, "note_id": note_id, } if extra: e.update(extra) return e def build_edges_for_note( *, note_id: str, note_type: Optional[str], chunks: List[Dict[str, Any]], frontmatter: Dict[str, Any], body_text: str, note_scope_refs: bool = False, ) -> List[Dict[str, Any]]: """ Erzeugt die Standardkanten: - belongs_to (Chunk→Note) - prev/next (Chunk-Sequenz) - references (aus [[...]]; optional: note_scope_refs=True → an Note statt an Chunk hängen) - backlink (inverse der references) - depends_on (aus FM 'depends_on': [...]) - assigned_to (aus FM 'assigned_to': [...]) Zusätzlich: - Type-Registry edge_defaults ergänzt fehlende Kanten als 'type_default' (z. B. 'related_to' ohne Ziel bleibt ungenutzt – nur, wenn interpretierbar). Rückgabewert: Liste von Edge-Dicts. Upsert übernimmt euer Qdrant-Utility. """ edges: List[Dict[str, Any]] = [] # belongs_to + prev/next for i, ch in enumerate(chunks): cid = ch.get("chunk_id") or f"{note_id}#{i+1}" edges.append(_edge("belongs_to", "chunk", cid, "note", note_id, note_id)) if i > 0: prev_cid = chunks[i - 1].get("chunk_id") or f"{note_id}#{i}" edges.append(_edge("prev", "chunk", cid, "chunk", prev_cid, note_id)) edges.append(_edge("next", "chunk", prev_cid, "chunk", cid, note_id)) # references/backlink refs: Set[str] = set(extract_wikilinks(body_text or "")) if note_scope_refs: # Referenzen auf Note-Ebene for ref in sorted(refs): edges.append(_edge("references", "note", note_id, "note", ref, note_id)) edges.append(_edge("backlink", "note", ref, "note", note_id, note_id)) else: # Referenzen pro Chunk (wenn sinnvoll) for ch in chunks: cid = ch.get("chunk_id") ctext = (ch.get("text") or "") + "\n" + (ch.get("window") or "") cset: Set[str] = set(extract_wikilinks(ctext)) for ref in sorted(cset): edges.append(_edge("references", "chunk", cid, "note", ref, note_id)) # Backlink auf Note-Ebene edges.append(_edge("backlink", "note", ref, "note", note_id, note_id)) # depends_on / assigned_to aus Frontmatter fm_dep = frontmatter.get("depends_on") or [] if isinstance(fm_dep, list): for d in fm_dep: edges.append(_edge("depends_on", "note", note_id, "note", str(d), note_id)) fm_ass = frontmatter.get("assigned_to") or [] if isinstance(fm_ass, list): for a in fm_ass: edges.append(_edge("assigned_to", "note", note_id, "person", str(a), note_id)) # Type-Defaults (nur ergänzend, wenn interpretierbar) if note_type: defaults = get_edge_defaults_for_type(note_type) # Aus defaults lassen sich ohne konkretes Ziel nur symbolische Kanten ableiten. # Wir fügen hier KEINE künstlichen Zielnoten hinzu; Registry ist vor allem # für spätere WP-04-Gewichtungen gedacht. Wenn ihr konkrete Regeln definieren # wollt (z. B. „task → belongs_to project aus frontmatter.project_id“), # kann dies hier ergänzt werden (mit echten Ziel-IDs). # Aktuell also: keine „leeren“ Kanten ohne Ziel hinzufügen. return edges