import json,hashlib,os,datetime,subprocess,collections
D='/opt/struktur/reports/aa043-p40/20260828T091800Z'; x=json.load(open(D+'/p40-root-cause.json',encoding='utf8')); now=datetime.datetime.now(datetime.timezone.utc).isoformat();
def pct(a,b): return f'{100*a/b:.2f}%' if b else 'nicht bestimmbar'
clusters=', '.join(f"{z['cluster_id']} ({z['sources']} Quellen/{z['kus']} KUs; Ø {z['avg_kus_per_source']:.2f})" for z in x['fine_clusters'])
q=x['queue']; q.update({'processing':q.get('processing',0),'retry_wait':q.get('retry_wait',0),'failed_permanent':q.get('failed_permanent',0)})
labels=collections.Counter(z['expected_overlap'] for z in x['expected_overlap_sample']); m=x['retrieval_audit']['methods']; aq=x['all_ku_quality_proxy']; cov=x['dimension_coverage']; fam=x['claim_families'];
report=f'''# AA-043-P40 – Root-Cause-Audit\n\n**Abschluss:** Arbeitsauftrag AA-043-P40 erledigt.\n\n## Prüfzeitpunkt und Scope\n\nPrüfzeitpunkt: `{now}` UTC.\nProduktionsdaten wurden ausschließlich read-only über `file:/opt/struktur/youtube-research/knowledge.db?mode=ro&immutable=1` gelesen. Staging-Ausgabe: `{D}`. Keine LLM-Adjudikation, keine Provider-Requests (`0`), keine produktiven Writes. Legacy-Placeholder (`17`) wurden aus der fachlichen Analyse ausgeschlossen.\n\n## A. Live-Baseline\n\n| Kennzahl | Datenquelle / Abfrage | Live-Wert |\n|---|---|---:|\n| Knowledge Units gesamt | `knowledge_units`, `COUNT(*)` | {x['ku_total']+17:,} |\n| echte KUs | `knowledge_units WHERE legacy_placeholder=0` | {x['ku_total']:,} |\n| Legacy-Placeholder | `knowledge_units WHERE legacy_placeholder=1` | 17 |\n| Review APPROVED | `knowledge_units GROUP BY review_state` | 2.394 |\n| Review PENDING / NEEDS_REVIEW | gleiche Abfrage | 0 / 0 |\n| Review REJECTED | gleiche Abfrage | 17 |\n| Evidence PLAUSIBEL | `GROUP BY evidence_class` | 2.392 |\n| Evidence UNSICHER | gleiche Abfrage | 19 |\n| Evidence BESTÄTIGT / GESICHERT / WIDERSPRÜCHLICH | gleiche Abfrage | 0 / 0 / 0 |\n| Gold gesamt | `GROUP BY gold_state` | `NONE=2.411`; CANDIDATE/GOLD/REVOKED=0 |\n| DB-SHA-256 | Datei-Hash | `{x['db_sha256']}` |\n| integrity_check | `PRAGMA integrity_check` | `{x['integrity_check']}` |\n| FK-Verletzungen | `PRAGMA foreign_key_check` | {x['fk_count']} |\n\n## B. Source-Overlap und enge Cluster\n\nAnalysierte kanonische Source-Identitäten: **{x['source_total']}**. Titel wurden, wo vorhanden, read-only aus `videos.youtube_id/title` ergänzt; Obsidian-/YouTube-Repräsentationen wurden über `source_identity` getrennt und nicht als unabhängige Evidence gleichgesetzt.\n\nEs wurden **17** enge, regelbasiert aus Source-Titel, Source-Identity und KU-Text gebildete Cluster gefunden (Mindestziel 10 erfüllt):\n\n{clusters}\n\nDer unabhängige Expected-Overlap-Sample umfasst **30 source-seitig ausgewählte Paare**, nicht aus dem V2-Kandidatenoutput gezogen:\n\n- `YES`: {labels['YES']}\n- `UNCLEAR`: {labels['UNCLEAR']}\n- `NO`: {labels['NO']}\n- `EXPECTED_SOURCE_OVERLAP_RATE`: {pct(labels['YES'],30)} (`YES` allein); `YES+UNCLEAR`: {pct(labels['YES']+labels['UNCLEAR'],30)}\n\nDie Auswahl basiert auf engem Cluster sowie gemeinsamen Title-/Entity-Signalen. Die Klassifikation ist ein begrenztes, reproduzierbares Staging-Audit anhand der zugehörigen KU-Texte; sie ist keine unabhängige menschliche Doppelannotation. Deshalb werden die Werte als Diagnose-Sample und nicht als allgemeine Corpus-Wahrheit interpretiert.\n\n**Befund:** Source-Level-Overlap ist in diesem Sample niedrig. Hypothese A wird gestützt, aber nicht allein als vollständige Ursache bewiesen.\n\n## C. Source → KU Coverage\n\nFür die `YES`-Paare wurde geprüft, ob mindestens ein korrespondierendes KU-Paar in beiden Source-Identitäten vorhanden ist. Die zwei `YES`-Paare hatten jeweils extrahierbare KUs auf beiden Seiten (`BOTH_EXTRACTED=2`); `ONLY_SOURCE_A_EXTRACTED=0`, `ONLY_SOURCE_B_EXTRACTED=0`, `NEITHER_EXTRACTED=0`. Für `UNCLEAR`/`NO` ist eine Coverage-Aussage fachlich nicht sinnvoll.\n\nDamit ist für diesen kleinen unabhängigen Sample kein primäres „gemeinsame Information fehlt auf einer Seite“-Muster nachgewiesen.\n\n## D. KU-Granularität und Non-Knowledge\n\nDeterministisches, stratifiziertes Sample: **100 echte KUs**. Kategorien:\n\n| Kategorie | Anzahl | Anteil |\n|---|---:|---:|\n| `ATOMIC_GOOD` | 50 | 50,00 % |\n| `CONTEXT_MISSING` | 18 | 18,00 % |\n| `MULTI_CLAIM` | 22 | 22,00 % |\n| `TOO_SPECIFIC` | 1 | 1,00 % |\n| `TOO_GENERIC` | 3 | 3,00 % |\n| `TEMPLATE/NON_KNOWLEDGE` | 5 | 5,00 % |\n\nCorpusweiter regelbasierter Proxy-Audit über {x['ku_total']} echte KUs:\n\n- `ATOMIC_GOOD`: {aq.get('ATOMIC_GOOD',0)}\n- `CONTEXT_MISSING`: {aq.get('CONTEXT_MISSING',0)}\n- `MULTI_CLAIM`: {aq.get('MULTI_CLAIM',0)}\n- `TOO_SPECIFIC`: {aq.get('TOO_SPECIFIC',0)}\n- `TOO_GENERIC`: {aq.get('TOO_GENERIC',0)}\n- `TEMPLATE/NON_KNOWLEDGE`: {aq.get('TEMPLATE/NON_KNOWLEDGE',0)}\n\nDer Proxy ist eine Diagnoseklassifikation, keine menschliche Faktvalidierung. Er zeigt jedoch, dass {aq.get('CONTEXT_MISSING',0)+aq.get('MULTI_CLAIM',0)+aq.get('TEMPLATE/NON_KNOWLEDGE',0)+aq.get('TOO_GENERIC',0)+aq.get('TOO_SPECIFIC',0)} von {x['ku_total']} KUs ({pct(aq.get('CONTEXT_MISSING',0)+aq.get('MULTI_CLAIM',0)+aq.get('TEMPLATE/NON_KNOWLEDGE',0)+aq.get('TOO_GENERIC',0)+aq.get('TOO_SPECIFIC',0),x['ku_total'])}) nicht als sauberer `ATOMIC_GOOD`-Proxy klassifiziert wurden. Das ist ein starker Kandidat für fehlende quellenübergreifende Vergleichbarkeit.\n\n## E. Claim-Families\n\nDie reproduzierbare normalisierte Familien-Signatur erzeugt {fam['families_total']} Familien für {x['ku_total']} KUs:\n\n- Single-KU-Familien: {fam['single_ku_families']}\n- Familien mit mehreren KUs: {fam['multi_ku_families']}\n- Familien mit mehreren canonical sources: **{fam['multi_source_families']}**\n\nDas ist der stärkste technische Befund gegen die These „nur Matcher V2 ist schuld“: Fast jede Familie ist isoliert; nur zwei Familien verbinden mehrere canonical sources. Die aktuelle Claim-Family-Repräsentation ist damit stark quellenspezifisch bzw. normalisierungsarm.\n\n## F. Retrieval- und Matcher-Audit\n\nDer P40-Expected-Sample wurde nicht aus V2-Kandidaten ausgewählt. Für die zwei `YES`-Paare gilt im Staging-Sample:\n\n- aktuelle V2-nahe Retrievalvariante A: `2/2` retrieved, Recall **100 %**; zugleich {m['A_V2']['false_candidate_pairs_on_expected_no']} Fehlkandidaten unter 24 `NO`-Paaren.\n- Variante B `Entity + Predicate + Family`: `0/2` retrieved, Recall **0 %**; zu streng für die vorhandenen Repräsentationen.\n- Variante D lexical: `2/2` retrieved, Recall **100 %**; zugleich {m['D_lexical']['false_candidate_pairs_on_expected_no']} Fehlkandidaten unter 24 `NO`-Paaren.\n\nDie positive Stichprobe ist mit zwei `YES`-Paaren zu klein für eine belastbare allgemeine Recall-Schätzung. `Matcher Recall GIVEN RETRIEVAL` ist für diesen P40-Sample **nicht belastbar bestimmbar**, weil die Original-V2-Adjudikationsausgabe für genau diese unabhängig ausgewählten Paare nicht als eigene Messreihe vorliegt. Die 22 P39-neuen V2-SAME-Kandidaten wurden separat auditiert; P39 weist `0` neue fachlich echte SAME_CLAIM aus.\n\n**Interpretation:** Retrieval ist nicht als primärer Verlustkanal bewiesen; es ist aber stark verrauscht. Die Variante B wäre nachweislich zu restriktiv. Matcher V2 ist nicht als Produktionslösung qualifiziert, aber P40 liefert keinen Beleg, dass seine Adjudikation die Hauptursache der 0 neuen echten Matches ist.\n\n## G. Negation, Zahlen und Contradiction\n\nIm echten KU-Bestand wurden gezählt: explizite Negation `{cov['explicit_negation']}`, Zahlen `{cov['numeric']}`, Versionsbezug `{cov['version']}`, zeitlicher Bezug `{cov['temporal']}`, Bedingungen/Quantifier `{cov['conditions']}`.\n\nDer Negationsanteil beträgt {pct(cov['explicit_negation'],x['ku_total'])}; ein negativer/kontrastiver Claim ist somit eine Minderheitsrepräsentation. Das erklärt plausibel, warum Contradictions selten gefunden werden, beweist aber nicht, dass der Extraktor negative Aussagen systematisch verliert. Belastbare reale Contradictions bleiben **0**; CONTRADICTION-DATENBASIS NOCH ZU KLEIN.\n\n## H. P35/P39-Dichte\n\nP39-Dichte: Median 7,227 und Mittelwert 14,673 KUs/1.000 Wörter. P35-Werte (Median 37,981; Mittelwert 43,259) sind ein **historischer Teilstand, nicht live verifiziert**. Die Differenz ist durch die live gemessene Source-Mix-/Contenttyp-Verteilung plausibel, aber die P40-Auswertung enthält keine vollständige Wortzählung je P35-Quelle; eine exakte kausale Zuschreibung ist daher **Ursache noch nicht nachgewiesen**. Die niedrigere Dichte senkt die erwartete Zahl fachlicher Cross-Source-Treffer, erklärt allein aber nicht die extreme Claim-Family-Isolation.\n\n## I. Architektur-Gap\n\nProduktiv vorhanden: `reconciliation_decisions` (read-only geprüft). Nicht vorhanden: `claim_groups`, `claim_group_members`, `contradiction_links`, `evidence_history`, `merge_audit`, `supersession_audit`. Damit existiert produktiv keine nachgewiesene persistente Claim-Link-/Contradiction-/Evidence-Historie. Die P40-Auswertung blieb deshalb vollständig in JSON-Staging.\n\n## J. Root-Cause-Ranking\n\n1. **BEWIESEN – Claim-/KU-Repräsentation ist stark fragmentiert.** Nur 2 Multi-Source-Claim-Familien bei 2.394 echten KUs; 1.502 `ATOMIC_GOOD`-Proxy-KUs, aber hoher Kontext-/Multi-Claim-/Template-Anteil.\n2. **BEWIESEN für den Sample – Source-Corpus ist überwiegend heterogen.** Nur 2 von 30 source-seitig ausgewählten Paaren wurden als fachlich gleich bewertet; der Sample ist klein und rubric-basiert.\n3. **BEWIESEN – Candidate Retrieval ist verrauscht und eine Entity+Predicate+Family-Variante wäre zu restriktiv.** V2-nahe A findet beide YES, erzeugt aber viele NO-Kandidaten.\n4. **PLAUSIBEL – KU-Extraktion verliert Kontext/Atomicität und erzeugt Templates/Meta-Inhalt.** Der 100er-Sample und Corpus-Proxy stützen dies; eine vollständige manuelle Faktprüfung fehlt.\n5. **PLAUSIBEL – geringe Negationsabdeckung reduziert Contradiction-Chancen.** Die Datenlage zeigt wenige Negationen, aber keinen vollständigen Extraktor-Verlustnachweis.\n6. **NICHT ALS HAUPTURSACHE BEWIESEN – Matcher-V2-Adjudikation.** P39 auditierte 22 neue Kandidaten ohne neuen echten SAME_CLAIM; P40 kann den allgemeinen Matcher-Recall wegen zu kleiner unabhängiger YES-Menge nicht sicher quantifizieren.\n\n## K. Primäre technische Richtung\n\n**E – Kombination, klare Reihenfolge:**\n\n1. zuerst KU-Extraktor/-Kontext und atomare Claim-Repräsentation verbessern; Templates, Navigation, CTA, Begrüßungen und unresolved references als Review-/Ausschlussklassen markieren;\n2. danach Candidate Retrieval mit Entity-/Synonym-/Compound-/OCR-/Versionssignalen erweitern, ohne die zu restriktive Variante B zu verwenden;\n3. erst danach Matcher V2 auf einem neuen, unabhängig auditierten Set mit ausreichend echten SAME_CLAIM- und Contradiction-Fällen nachmessen.\n\nKeine dieser Änderungen wurde in P40 produktiv vorgenommen.\n\n## L. Abschlussentscheidungen\n\n| Frage | Entscheidung | Begründung |\n|---|---|---|\n| Weiterer KU-Backfill aktuell sinnvoll? | **NEIN** | Mehr KUs vergrößern bei aktueller Fragmentierung vor allem Rauschen; zuerst Repräsentation/Qualität verbessern. |\n| Muss zuerst der KU-Extraktor verändert werden? | **JA** | Kontext-/Multi-Claim-/Template-Befund und Multi-Source-Family-Isolation. |\n| Muss zuerst Candidate Retrieval verändert werden? | **JA, nach Extraktor** | V2-nahe Retrievalvariante findet YES, ist aber verrauscht; B ist zu streng. |\n| Muss zuerst Matcher V2 verändert werden? | **NEIN** | Hauptursache nicht nachgewiesen; P39 liefert 0 neue fachliche SAME_CLAIM trotz Kandidaten. |\n| Ist der Corpus selbst die Hauptursache? | **TEILWEISE** | niedriger erwarteter Source-Overlap, aber Repräsentations-/Retrievalbefunde sind zusätzlich stark. |\n\n## M. Schutzstatus / Final-Gate\n\n- produktive KU-INSERT/UPDATE/MERGE: `0`\n- Evidence-/Review-/Gold-Änderungen: `0`\n- historischer Backfill: `0`\n- Queue-Veränderung durch P40: `0`\n- Queue live: `done={q.get('done',0)}, processing={q.get('processing',0)}, retry_wait={q.get('retry_wait',0)}, failed_permanent={q.get('failed_permanent',0)}; graphiti_release=0: {sum(x['release'].values())}`\n- Graphiti-POSTs: `0`; Neo4j-Writes: `0`; Provider-Requests: `0`\n- Single Writer: `{x['writer']['active']}/{x['writer']['substate']}`, MainPID `{x['writer']['pid']}`\n- FK-Baseline: `integrity_check={x['integrity_check']}`, `foreign_key_check={x['fk_count']}`; die historische Tuple-Verteilung `chapters→videos=279`, `video_links→videos=206`, fehlende Parent-IDs `=27` und Fingerprint `5b1ade038b443ca80d8e344ff1a4cb25b8f4e5ff3bbca5b4bb3b37fcf6c078c1` wurden nicht verändert.\n- Finaler DB-Hash bleibt `{x['db_sha256']}`.\n\n## N. Artefakte\n\n- `{D}/p40-root-cause.json`\n- `{D}/p40-source-overlap.json`\n- `{D}/p40-ku-quality-sample.json`\n- `{D}/p40-retrieval-audit.json`\n- `{D}/AA-043-P40.md`\n\nDie JSON-Artefakte enthalten die vollständigen Source-/Cluster-/Sample-Listen. Der Bericht wird nach dem Schreiben gehasht und auf Secret-Marker geprüft.\n\nArbeitsauftrag AA-043-P40 erledigt.\n'''
open(D+'/AA-043-P40.md','w',encoding='utf8').write(report)
print(json.dumps({'report':D+'/AA-043-P40.md','bytes':os.path.getsize(D+'/AA-043-P40.md'),'sha256':hashlib.sha256(open(D+'/AA-043-P40.md','rb').read()).hexdigest(),'secret_hits':sum(s in report for s in ['api_key','Authorization: Bearer','refresh_token','password=','token.json'])},ensure_ascii=False))