From a780104b3c203a48661c4c55ada7c75971ff9dff Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 11 Jan 2026 14:07:16 +0100 Subject: [PATCH] Enhance edge processing in graph_derive_edges.py for version 4.2.9: Finalize chunk attribution with synchronization to "Semantic First" signal. Collect callout keys from candidate pool before text scan to prevent duplicates. Update callout extraction logic to ensure strict adherence to existing chunk callouts, improving deduplication and processing efficiency. --- app/core/graph/graph_derive_edges.py | 49 +++++++++++++++++++++------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/app/core/graph/graph_derive_edges.py b/app/core/graph/graph_derive_edges.py index 052710a..70578b3 100644 --- a/app/core/graph/graph_derive_edges.py +++ b/app/core/graph/graph_derive_edges.py @@ -210,6 +210,8 @@ def build_edges_for_note( Erzeugt und aggregiert alle Kanten für eine Note. WP-24c v4.2.0: Unterstützt Note-Scope Extraktions-Zonen. WP-24c v4.2.7: Chunk-Attribution für Callouts über candidate_pool mit explicit:callout Provenance. + WP-24c v4.2.9: Finalisierung der Chunk-Attribution - Synchronisation mit "Semantic First" Signal. + Callout-Keys werden VOR dem globalen Scan aus candidate_pool gesammelt. Args: note_id: ID der Note @@ -290,9 +292,28 @@ def build_edges_for_note( defaults = get_edge_defaults_for(note_type, reg) refs_all: List[str] = [] - # WP-24c v4.2.1: Sammle alle Callout-Keys aus Chunks für Smart Logic + # WP-24c v4.2.9: Sammle alle Callout-Keys aus Chunks für Smart Logic + # WICHTIG: Diese Menge muss VOR dem globalen Scan vollständig sein all_chunk_callout_keys: Set[Tuple[str, str, Optional[str]]] = set() + # WP-24c v4.2.9: PHASE 1: Sammle alle Callout-Keys aus candidate_pool VOR Text-Scan + # Dies stellt sicher, dass bereits geerntete Callouts nicht dupliziert werden + for ch in chunks: + cid = _get(ch, "chunk_id", "id") + if not cid: continue + + # B. Candidate Pool (WP-15b Validierte KI-Kanten) + # WP-24c v4.2.9: Sammle Callout-Keys VOR Text-Scan für Synchronisation + pool = ch.get("candidate_pool") or ch.get("candidate_edges") or [] + for cand in pool: + raw_t, k, p = cand.get("to"), cand.get("kind", "related_to"), cand.get("provenance", "semantic_ai") + t, sec = parse_link_target(raw_t, note_id) + if t and p == "explicit:callout": + # WP-24c v4.2.9: Markiere als bereits auf Chunk-Ebene verarbeitet + # Dies verhindert, dass der globale Scan diese Kante als Note-Scope neu anlegt + all_chunk_callout_keys.add((k, t, sec)) + + # WP-24c v4.2.9: PHASE 2: Verarbeite Chunks und erstelle Kanten for ch in chunks: cid = _get(ch, "chunk_id", "id") if not cid: continue @@ -314,17 +335,12 @@ def build_edges_for_note( edges.append(_edge(k, "chunk", cid, t, note_id, payload)) # B. Candidate Pool (WP-15b Validierte KI-Kanten) - # WP-24c v4.2.7: Sammle Callout-Keys für Chunk-Attribution + # WP-24c v4.2.9: Erstelle Kanten aus candidate_pool (Keys bereits in Phase 1 gesammelt) pool = ch.get("candidate_pool") or ch.get("candidate_edges") or [] for cand in pool: raw_t, k, p = cand.get("to"), cand.get("kind", "related_to"), cand.get("provenance", "semantic_ai") t, sec = parse_link_target(raw_t, note_id) if t: - # WP-24c v4.2.7: Wenn Provenance explicit:callout, füge zu all_chunk_callout_keys hinzu - # Dadurch weiß die globale Extraktion, dass diese Kante bereits auf Chunk-Ebene versorgt ist - if p == "explicit:callout": - all_chunk_callout_keys.add((k, t, sec)) - # WP-24c v4.1.0: target_section fließt nun fest in die ID-Generierung ein payload = { "chunk_id": cid, @@ -334,14 +350,22 @@ def build_edges_for_note( if sec: payload["target_section"] = sec edges.append(_edge(k, "chunk", cid, t, note_id, payload)) - # C. Callouts (> [!edge]) - WP-24c v4.2.1: Sammle für Smart Logic + # C. Callouts (> [!edge]) - WP-24c v4.2.9: Fallback für Callouts im gereinigten Text + # HINWEIS: Da der Text bereits gereinigt wurde (Clean-Context), werden hier typischerweise + # keine Callouts mehr gefunden. Falls doch, prüfe gegen all_chunk_callout_keys. call_pairs, rem2 = extract_callout_relations(rem) for k, raw_t in call_pairs: t, sec = parse_link_target(raw_t, note_id) if not t: continue + # WP-24c v4.2.9: Prüfe, ob dieser Callout bereits im candidate_pool erfasst wurde + callout_key = (k, t, sec) + if callout_key in all_chunk_callout_keys: + # Bereits im candidate_pool erfasst -> überspringe (wird mit chunk-Scope angelegt) + continue + # WP-24c v4.2.1: Tracke Callout für spätere Deduplizierung (global sammeln) - all_chunk_callout_keys.add((k, t, sec)) + all_chunk_callout_keys.add(callout_key) # WP-24c v4.1.0: target_section fließt nun fest in die ID-Generierung ein payload = { @@ -402,14 +426,15 @@ def build_edges_for_note( # 4) WP-24c v4.2.0: Note-Scope Edges hinzufügen (VOR De-Duplizierung) edges.extend(note_scope_edges) - # 5) WP-24c v4.2.1: Callout-Extraktion aus Markdown (NACH Chunk-Verarbeitung) - # Smart Logic: Nur Callouts, die NICHT in Chunks vorkommen, werden mit scope: "note" angelegt + # 5) WP-24c v4.2.9: Callout-Extraktion aus Markdown (NACH Chunk-Verarbeitung) + # Deduplizierungs-Garantie: Nur Callouts, die NICHT in all_chunk_callout_keys sind, + # werden mit scope: "note" angelegt. Dies verhindert Duplikate für bereits geerntete Callouts. callout_edges_from_markdown: List[dict] = [] if markdown_body: callout_edges_from_markdown = extract_callouts_from_markdown( markdown_body, note_id, - existing_chunk_callouts=all_chunk_callout_keys + existing_chunk_callouts=all_chunk_callout_keys # WP-24c v4.2.9: Strikte Respektierung ) edges.extend(callout_edges_from_markdown)