diff --git a/app/core/ingestion/ingestion_processor.py b/app/core/ingestion/ingestion_processor.py index 1c4263d..525dc73 100644 --- a/app/core/ingestion/ingestion_processor.py +++ b/app/core/ingestion/ingestion_processor.py @@ -269,8 +269,20 @@ class IngestionService: # 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) - # WP-24c v4.5.9-DEBUG: Erweiterte Diagnose-Logs für Change-Detection (INFO-Level für Sichtbarkeit) - logger.info(f"🔍 [CHANGE-DETECTION] Start für '{note_id}': force_replace={force_replace}, old_payload={old_payload is not None}") + # WP-24c v4.5.10: Prüfe auf ID-Kollisionen (zwei Dateien mit derselben note_id) + if old_payload and not force_replace: + old_path = old_payload.get("path", "") + if old_path and old_path != normalized_file_path: + # ID-Kollision erkannt: Zwei verschiedene Dateien haben dieselbe note_id + logger.error( + f"❌ [ID-KOLLISION] Kritischer Fehler: Die note_id '{note_id}' wird bereits von einer anderen Datei verwendet!\n" + f" Bereits vorhanden: '{old_path}'\n" + f" Konflikt mit: '{normalized_file_path}'\n" + f" Lösung: Bitte ändern Sie die 'id' im Frontmatter einer der beiden Dateien, um eine eindeutige ID zu gewährleisten." + ) + return {**result, "status": "error", "error": "id_collision", "note_id": note_id, "existing_path": old_path, "conflicting_path": normalized_file_path} + + logger.debug(f"🔍 [CHANGE-DETECTION] Start für '{note_id}': force_replace={force_replace}, old_payload={old_payload is not None}") content_changed = True hash_match = False @@ -283,16 +295,16 @@ class IngestionService: # WP-24c v4.5.9-DEBUG: Detaillierte Hash-Diagnose (INFO-Level) logger.info(f"🔍 [CHANGE-DETECTION] Hash-Vergleich für '{note_id}':") - logger.info(f" -> Hash-Key: '{h_key}'") - logger.info(f" -> Active Hash-Mode: '{self.active_hash_mode or 'full'}'") - logger.info(f" -> New Hash vorhanden: {bool(new_h)}") - logger.info(f" -> Old Hash vorhanden: {bool(old_h)}") + logger.debug(f" -> Hash-Key: '{h_key}'") + logger.debug(f" -> Active Hash-Mode: '{self.active_hash_mode or 'full'}'") + logger.debug(f" -> New Hash vorhanden: {bool(new_h)}") + logger.debug(f" -> Old Hash vorhanden: {bool(old_h)}") if new_h: - logger.info(f" -> New Hash (erste 32 Zeichen): {new_h[:32]}...") + logger.debug(f" -> New Hash (erste 32 Zeichen): {new_h[:32]}...") if old_h: - logger.info(f" -> Old Hash (erste 32 Zeichen): {old_h[:32]}...") - logger.info(f" -> Verfügbare Hash-Keys in new: {list(note_pl.get('hashes', {}).keys())}") - logger.info(f" -> Verfügbare Hash-Keys in old: {list(old_payload.get('hashes', {}).keys())}") + logger.debug(f" -> Old Hash (erste 32 Zeichen): {old_h[:32]}...") + logger.debug(f" -> Verfügbare Hash-Keys in new: {list(note_pl.get('hashes', {}).keys())}") + logger.debug(f" -> Verfügbare Hash-Keys in old: {list(old_payload.get('hashes', {}).keys())}") if new_h and old_h: hash_match = (new_h == old_h) @@ -304,48 +316,48 @@ class IngestionService: # Finde erste unterschiedliche Position diff_pos = next((i for i, (a, b) in enumerate(zip(new_h, old_h)) if a != b), None) if diff_pos is not None: - logger.info(f" -> Hash-Unterschied: Erste unterschiedliche Position: {diff_pos}") + logger.debug(f" -> Hash-Unterschied: Erste unterschiedliche Position: {diff_pos}") else: - logger.info(f" -> Hash-Unterschied: Längen unterschiedlich (new={len(new_h)}, old={len(old_h)})") + logger.debug(f" -> Hash-Unterschied: Längen unterschiedlich (new={len(new_h)}, old={len(old_h)})") - # WP-24c v4.5.9-DEBUG: Logge Hash-Input für Diagnose + # WP-24c v4.5.10: Logge Hash-Input für Diagnose (DEBUG-Level) # WICHTIG: _get_hash_source_content benötigt ein Dictionary, nicht das ParsedNote-Objekt! from app.core.ingestion.ingestion_note_payload import _get_hash_source_content, _as_dict hash_mode = self.active_hash_mode or 'full' # Konvertiere parsed zu Dictionary für _get_hash_source_content parsed_dict = _as_dict(parsed) hash_input = _get_hash_source_content(parsed_dict, hash_mode) - logger.info(f" -> Hash-Input (erste 200 Zeichen): {hash_input[:200]}...") - logger.info(f" -> Hash-Input Länge: {len(hash_input)}") + logger.debug(f" -> Hash-Input (erste 200 Zeichen): {hash_input[:200]}...") + logger.debug(f" -> Hash-Input Länge: {len(hash_input)}") - # WP-24c v4.5.9-DEBUG: Vergleiche auch Body-Länge und Frontmatter + # WP-24c v4.5.10: Vergleiche auch Body-Länge und Frontmatter (DEBUG-Level) # Verwende parsed.body statt note_pl.get("body") new_body = str(getattr(parsed, "body", "") or "").strip() old_body = str(old_payload.get("body", "")).strip() if old_payload else "" - logger.info(f" -> Body-Länge: new={len(new_body)}, old={len(old_body)}") + logger.debug(f" -> Body-Länge: new={len(new_body)}, old={len(old_body)}") if len(new_body) != len(old_body): - logger.warning(f" -> ⚠️ Body-Länge unterschiedlich! Mögliche Ursache: Parsing-Unterschiede") + logger.debug(f" -> ⚠️ Body-Länge unterschiedlich! Mögliche Ursache: Parsing-Unterschiede") # Verwende parsed.frontmatter statt note_pl.get("frontmatter") new_fm = getattr(parsed, "frontmatter", {}) or {} old_fm = old_payload.get("frontmatter", {}) if old_payload else {} - logger.info(f" -> Frontmatter-Keys: new={sorted(new_fm.keys())}, old={sorted(old_fm.keys())}") + logger.debug(f" -> Frontmatter-Keys: new={sorted(new_fm.keys())}, old={sorted(old_fm.keys())}") # Prüfe relevante Frontmatter-Felder relevant_keys = ["title", "type", "status", "tags", "chunking_profile", "chunk_profile", "retriever_weight", "split_level", "strict_heading_split"] for key in relevant_keys: new_val = new_fm.get(key) if isinstance(new_fm, dict) else getattr(new_fm, key, None) old_val = old_fm.get(key) if isinstance(old_fm, dict) else None if new_val != old_val: - logger.warning(f" -> ⚠️ Frontmatter '{key}' unterschiedlich: new={new_val}, old={old_val}") + logger.debug(f" -> ⚠️ Frontmatter '{key}' unterschiedlich: new={new_val}, old={old_val}") else: - # WP-24c v4.5.9: Wenn Hash fehlt, als geändert behandeln (Sicherheit) - logger.warning(f"⚠️ [CHANGE-DETECTION] Hash fehlt für '{note_id}': new_h={bool(new_h)}, old_h={bool(old_h)}") - logger.info(f" -> Grund: Hash wird als 'geändert' behandelt, da Hash-Werte fehlen") + # WP-24c v4.5.10: 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)}") + logger.debug(f" -> Grund: Hash wird als 'geändert' behandelt, da Hash-Werte fehlen") else: if force_replace: - logger.info(f"🔍 [CHANGE-DETECTION] '{note_id}': force_replace=True -> überspringe Hash-Check") + logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': force_replace=True -> überspringe Hash-Check") elif not old_payload: - logger.warning(f"🔍 [CHANGE-DETECTION] '{note_id}': ⚠️ Keine alte Payload gefunden -> erste Verarbeitung oder gelöscht") + logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': ⚠️ Keine alte Payload gefunden -> erste Verarbeitung oder gelöscht") # 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 @@ -357,12 +369,12 @@ class IngestionService: 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"} elif not force_replace and old_payload and not hash_match: - # WP-24c v4.5.9-DEBUG: Hash geändert - erlaube Verarbeitung - logger.info(f"🔍 [CHANGE-DETECTION] '{note_id}': Hash geändert -> erlaube Verarbeitung") + # WP-24c v4.5.10: Hash geändert - erlaube Verarbeitung (DEBUG-Level) + logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': Hash geändert -> erlaube Verarbeitung") - # WP-24c v4.5.9: Hash geändert oder keine alte Payload - prüfe Artefakte für normale Verarbeitung + # WP-24c v4.5.10: 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) - logger.info(f"🔍 [CHANGE-DETECTION] '{note_id}': Artifact-Check: c_miss={c_miss}, e_miss={e_miss}") + logger.debug(f"🔍 [CHANGE-DETECTION] '{note_id}': Artifact-Check: c_miss={c_miss}, e_miss={e_miss}") if not apply: return {**result, "status": "dry-run", "changed": True, "note_id": note_id}