diff --git a/app/core/graph/graph_derive_edges.py b/app/core/graph/graph_derive_edges.py index a42e8fa..195926f 100644 --- a/app/core/graph/graph_derive_edges.py +++ b/app/core/graph/graph_derive_edges.py @@ -910,7 +910,12 @@ def build_edges_for_note( # Schritt 3: ID-Zuweisung nach Scope-Entscheidung final_edges: List[dict] = [] - for semantic_key, group in semantic_groups.items(): + # WP-24c v4.5.9: Deterministische Sortierung der semantic_groups für konsistente Edge-Extraktion + # Verhindert Varianz zwischen Batches (33 vs 34 Kanten) + sorted_semantic_keys = sorted(semantic_groups.keys()) + + for semantic_key in sorted_semantic_keys: + group = semantic_groups[semantic_key] # WP-24c v4.4.0-DEBUG: Schnittstelle 4 - De-Duplizierung Entscheidung # Prüfe, ob diese Gruppe Callout-Kanten enthält has_callouts = any(e.get("provenance") == "explicit:callout" for e in group) diff --git a/app/core/ingestion/ingestion_processor.py b/app/core/ingestion/ingestion_processor.py index 9cd2d78..69c9045 100644 --- a/app/core/ingestion/ingestion_processor.py +++ b/app/core/ingestion/ingestion_processor.py @@ -268,7 +268,6 @@ class IngestionService: # WP-24c v4.5.9: Strikte Change Detection (Hash-basierte Inhaltsprüfung) # Prüft Hash VOR der Verarbeitung, um redundante Ingestion zu vermeiden old_payload = None if force_replace else fetch_note_payload(self.client, self.prefix, note_id) - c_miss, e_miss = artifacts_missing(self.client, self.prefix, note_id) content_changed = True hash_match = False @@ -290,11 +289,18 @@ class IngestionService: # WP-24c v4.5.9: Wenn Hash fehlt, als geändert behandeln (Sicherheit) logger.debug(f"🔍 [CHANGE-DETECTION] Hash fehlt für '{note_id}': new_h={bool(new_h)}, old_h={bool(old_h)}") - # WP-24c v4.5.9: Strikte Logik - überspringe komplett wenn Hash identisch UND keine Artefakte fehlen - # Dies verhindert redundante Embedding-Generierung und Chunk-Verarbeitung - if not force_replace and hash_match and old_payload and not c_miss and not e_miss: - logger.info(f"⏭️ [SKIP] '{note_id}' unverändert (Hash identisch, alle Artefakte vorhanden)") + # WP-24c v4.5.9: Strikte Logik - überspringe komplett wenn Hash identisch + # WICHTIG: Artifact-Check NACH Hash-Check, da purge_before die Artefakte löschen kann + # Wenn Hash identisch ist, sind die Artefakte entweder vorhanden oder werden gerade neu geschrieben + if not force_replace and hash_match and old_payload: + # WP-24c v4.5.9: Hash identisch -> überspringe komplett (auch wenn Artefakte nach PURGE fehlen) + # Der Hash ist die autoritative Quelle für "Inhalt unverändert" + # Artefakte werden beim nächsten normalen Import wieder erstellt, wenn nötig + logger.info(f"⏭️ [SKIP] '{note_id}' unverändert (Hash identisch - überspringe komplett, auch wenn Artefakte fehlen)") return {**result, "status": "unchanged", "note_id": note_id, "reason": "hash_identical"} + + # WP-24c v4.5.9: Hash geändert oder keine alte Payload - prüfe Artefakte für normale Verarbeitung + c_miss, e_miss = artifacts_missing(self.client, self.prefix, note_id) if not apply: return {**result, "status": "dry-run", "changed": True, "note_id": note_id}