""" Report Builder — Generates markdown reports from research registries. Produces: - RESEARCH_CATALOG.md — Full catalog of questions, hypotheses, evidence - EVIDENCE_MATRIX.md — Overview of evidence status per question - OPEN_QUESTIONS.md — All unanswered questions - CLAIM_REGISTER.md — All claims with status - DISSERTATION_MAP.md — Dissertation chapter structure mapped to research """ from __future__ import annotations import json from datetime import datetime from pathlib import Path from typing import Any from .registry import REPO_ROOT, ResearchQuestion, ResearchRegistry GENERATED_DIR = REPO_ROOT / "research" / "generated" EVIDENCE_DIR = REPO_ROOT / "research" / "registry" / "evidence" class EvidenceRecord: """Typed wrapper for an EVID-*.json record.""" def __init__(self, data: dict[str, Any]) -> None: self.evidence_id = data.get("evidence_id", "") self.experiment_id = data.get("experiment_id") self.claim_id = data.get("claim_id") self.hypothesis_id = data.get("hypothesis_id") self.status = data.get("status", "untested") self.result_summary = data.get("result_summary", "") class ReportBuilder: """Generates markdown reports from the research registry and evidence records.""" def __init__(self, registry: ResearchRegistry): self.registry = registry self._evidence_records: dict[str, EvidenceRecord] = {} self._load_evidence_records() def _load_evidence_records(self) -> None: """Load all EVID-*.json files from the evidence directory.""" self._evidence_records = {} if not EVIDENCE_DIR.exists(): return for path in sorted(EVIDENCE_DIR.glob("EVID-*.json")): try: data = json.loads(path.read_text(encoding="utf-8")) ev = EvidenceRecord(data) if ev.evidence_id: self._evidence_records[ev.evidence_id] = ev except Exception: continue def _evidence_for_question(self, question_id: str) -> set[str]: """Return all evidence IDs linked to a research question. Links are followed via hypothesis_id and claim_id in the evidence records, matching the canonical B5D-SEF data model. """ result: set[str] = set() hypotheses = self.registry.hypotheses_for_question(question_id) hypothesis_ids = {h.id for h in hypotheses} claims = self.registry.claims_for_question(question_id) claim_ids = {c.id for c in claims} for ev in self._evidence_records.values(): if ev.hypothesis_id in hypothesis_ids: result.add(ev.evidence_id) if ev.claim_id in claim_ids: result.add(ev.evidence_id) return result def _evidence_for_hypothesis(self, hypothesis_id: str) -> set[str]: return { ev.evidence_id for ev in self._evidence_records.values() if ev.hypothesis_id == hypothesis_id } def _evidence_for_claim(self, claim_id: str) -> set[str]: return { ev.evidence_id for ev in self._evidence_records.values() if ev.claim_id == claim_id } def build_research_catalog(self) -> str: """Generate a complete research catalog.""" lines = [ "# Brain-5D Research Catalog", "", f"*Generiert am {datetime.now().strftime('%Y-%m-%d')}*", "", "## Übersicht", "", f"- **Forschungsfragen:** {len(self.registry.questions)}", f"- **Hypothesen:** {len(self.registry.hypotheses)}", f"- **Claims:** {len(self.registry.claims)}", f"- **Literaturquellen:** {len(self.registry.sources)}", "", "---", "", ] domains: dict[str, list[ResearchQuestion]] = {} for q in self.registry.questions.values(): domains.setdefault(q.domain, []).append(q) for domain in sorted(domains.keys()): lines.extend([f"## {domain}", ""]) for q in domains[domain]: hypotheses = self.registry.hypotheses_for_question(q.id) claims = self.registry.claims_for_question(q.id) sources = self.registry.sources_for_question(q.id) lines.extend( [ f"### {q.id}", "", f"**Frage:** {q.question}", "", f"**Status:** {q.status}", f"**Relevanz:** {q.relevance}", "", ] ) if q.answer.current: lines.extend( [ "**Aktuelle Antwort:**", "", f"> {q.answer.current}", "", f"*Konfidenz: {q.answer.confidence}*", "", ] ) # Collect evidence IDs from hypotheses and claims linked to this question question_evidence = self._evidence_for_question(q.id) if hypotheses: lines.append("**Hypothesen:**") for h in hypotheses: h_evidence = sorted(self._evidence_for_hypothesis(h.id)) ev_tag = ( f" — Evidenz: {', '.join(h_evidence)}" if h_evidence else "" ) lines.append( f"- `{h.id}`: {h.hypothesis} *({h.status})*{ev_tag}" ) lines.append("") if claims: lines.append("**Claims:**") for c in claims: c_evidence = sorted(self._evidence_for_claim(c.id)) ev_tag = ( f" — Evidenz: {', '.join(c_evidence)}" if c_evidence else "" ) lines.append( f"- `{c.id}`: {c.claim} *({c.status}, {c.confidence})*{ev_tag}" ) lines.append("") if sources: lines.append("**Literatur:**") for s in sources: lines.append( f"- `{s.source_id}`: {s.authors[0]} et al. ({s.year})" ) lines.append("") if question_evidence: lines.append( f"**Evidenzen:** {', '.join(sorted(question_evidence))}" ) lines.append("") lines.append("---") lines.append("") return "\n".join(lines) def build_evidence_matrix(self) -> str: """Generate an evidence matrix showing status per question.""" lines = [ "# Brain-5D Evidence Matrix", "", f"*Generiert am {datetime.now().strftime('%Y-%m-%d')}*", "", "| Forschungsfrage | Hypothese | Literatur | Experimente | Evidenz | Antwort |", "|----------------|-----------|-----------|-------------|---------|---------|", ] for q in self.registry.questions.values(): hypotheses = self.registry.hypotheses_for_question(q.id) sources = self.registry.sources_for_question(q.id) claims = self.registry.claims_for_question(q.id) h_text = ", ".join(f"`{h.id}`" for h in hypotheses) or "—" s_text = str(len(sources)) c_text = ", ".join(f"`{c.id}`" for c in claims) if claims else "—" question_evidence = sorted(self._evidence_for_question(q.id)) ev_text = ( ", ".join(f"`{e}`" for e in question_evidence) if question_evidence else "—" ) answer_text = q.answer.confidence if q.answer.current else "offen" lines.append( f"| `{q.id}` | {h_text} | {s_text} | {c_text} | {ev_text} | {answer_text} |" ) supported = sum( 1 for c in self.registry.claims.values() if c.status == "supported" ) refuted = sum(1 for c in self.registry.claims.values() if c.status == "refuted") untested = sum( 1 for c in self.registry.claims.values() if c.status == "untested" ) inconclusive = sum( 1 for c in self.registry.claims.values() if c.status == "inconclusive" ) lines.extend( [ "", "## Zusammenfassung", "", "| Status | Anzahl |", "|--------|--------|", f"| ✅ Supported | {supported} |", f"| ❌ Refuted | {refuted} |", f"| 🔄 Inconclusive | {inconclusive} |", f"| ⬜ Untested | {untested} |", f"| **Gesamt** | **{len(self.registry.claims)}** |", "", "---", "*Automatisch generiert — Theorie, Beobachtung und Interpretation sind strikt getrennt.*", ] ) return "\n".join(lines) def build_open_questions(self) -> str: """Generate a document listing all unanswered research questions.""" lines = [ "# Brain-5D Open Questions", "", f"*Generiert am {datetime.now().strftime('%Y-%m-%d')}*", "", "Die folgenden Forschungsfragen sind noch offen und warten auf experimentelle Evidenz.", "", ] unresolved = [ question for question in self.registry.questions.values() if question.status in {"open", "in_progress", "inconclusive"} ] for q in unresolved: lines.extend( [ f"## {q.id}", "", f"**Domäne:** {q.domain}", "", f"**Frage:** {q.question}", "", f"**Relevanz:** {q.relevance}", "", ] ) sources = self.registry.sources_for_question(q.id) if sources: lines.append("**Literatur:**") for s in sources: lines.append(f"- `{s.source_id}`: {s.authors[0]} et al. ({s.year})") lines.append("") hypotheses = self.registry.hypotheses_for_question(q.id) if hypotheses: lines.append("**Hypothesen:**") for h in hypotheses: lines.append(f"- `{h.id}`: {h.hypothesis}") lines.append("") lines.append("---") lines.append("") lines.append(f"*Insgesamt {len(unresolved)} offene Fragen.*") return "\n".join(lines) def build_claim_register(self) -> str: """Generate a register of all scientific claims.""" lines = [ "# Brain-5D Claim Register", "", f"*Generiert am {datetime.now().strftime('%Y-%m-%d')}*", "", "| Claim | Status | Konfidenz | Evidenzen | Experimente |", "|-------|--------|-----------|-----------|-------------|", ] for c in self.registry.claims.values(): icon = { "supported": "✅", "refuted": "❌", "inconclusive": "🔄", "untested": "⬜", }.get(c.status, "⬜") evidence_count = len(self._evidence_for_claim(c.id)) lines.append( f"| `{c.id}`: {c.claim[:80]}... | {icon} {c.status} | " f"{c.confidence} | {evidence_count} | {len(c.experiments)} |" ) lines.append("") return "\n".join(lines) def build_dissertation_map(self) -> str: """Generate a dissertation structure mapped to research entities.""" chapters: dict[str, dict[str, Any]] = { "Kapitel 1 – Theorie und Grundlagen": { "questions": ["RQ-SNN-001", "RQ-SNN-002", "RQ-DET-001"], "sources": [ "SRC-IZHIKEVICH-2003", "SRC-GERSTNER-2014", "SRC-MAASS-1997", ], "description": "Einführung in SNN-Theorie, Izhikevich-Modell, deterministische Dynamik", }, "Kapitel 2 – Plastizität und Lernen": { "questions": [ "RQ-SNN-004", "RQ-SNN-005", "RQ-STDP-001", "RQ-STDP-002", "RQ-HOM-001", "RQ-HOM-002", ], "sources": [ "SRC-SONG-ABBOTT-2000", "SRC-BI-POO-1998", "SRC-TURRIGIANO-2008", "SRC-HEBB-1949", ], "description": "STDP, Homeostase, Interaktion, Lernleistung", }, "Kapitel 3 – 5D-Raum und Topologie": { "questions": ["RQ-5D-001", "RQ-5D-002", "RQ-5D-003", "RQ-5D-004"], "sources": [], "description": "Dimensionsablation, Signalpropagation, Modularität, Informationstheorie", }, "Kapitel 4 – Persistenz und Speicherung": { "questions": [ "RQ-STORAGE-001", "RQ-STORAGE-002", "RQ-STORAGE-003", "RQ-STORAGE-004", ], "sources": [], "description": ".b5d-Format, verlustfreie Serialisierung, Speicherdichte, Skalierung", }, "Kapitel 5 – Selbstorganisation": { "questions": ["RQ-SELF-001", "RQ-SELF-002", "RQ-STRUCT-001"], "sources": [], "description": "Emergenz, Clusterbildung, Pruning, Sprouting", }, "Kapitel 6 – Skalierung": { "questions": ["RQ-SCALE-001"], "sources": ["SRC-MARKRAM-2015"], "description": "Skalierung von 5k auf Millionen Neuronen", }, "Kapitel 7 – Gedächtnis und Embodiment": { "questions": ["RQ-MEM-001", "RQ-EMB-001", "RQ-LLM-001"], "sources": [], "description": "Synaptisches Gedächtnis, Sensor-Aktor-Schleife, Language Organ", }, "Kapitel 8 – Autorenschaft und Epistemologie": { "questions": ["RQ-ETH-001", "RQ-ETH-002", "RQ-EPIST-001"], "sources": [], "description": "Verteilte Autorenschaft, Kontrollverlust, maschinelle Erkenntnis", }, } lines = [ "# Brain-5D Dissertation Map", "", f"*Generiert am {datetime.now().strftime('%Y-%m-%d')}*", "", "Diese Karte zeigt, wie die Forschungsergebnisse von Brain-5D in eine", "Dissertationsstruktur eingeordnet werden können.", "", ] for chapter, info in chapters.items(): lines.extend([f"## {chapter}", "", info["description"], ""]) questions = [ self.registry.questions[qid] for qid in info["questions"] if qid in self.registry.questions ] if questions: lines.append("**Forschungsfragen:**") for q in questions: lines.append(f"- `{q.id}`: {q.question[:80]}... *({q.status})*") lines.append("") sources = [ self.registry.sources[sid] for sid in info["sources"] if sid in self.registry.sources ] if sources: lines.append("**Literatur:**") for s in sources: lines.append(f"- `{s.source_id}`: {s.authors[0]} et al. ({s.year})") lines.append("") lines.append("---") lines.append("") return "\n".join(lines) def write_all(self, output_dir: Path | None = None) -> dict[str, Path]: """Generate and write all reports. Returns dict of name -> path.""" out = output_dir or GENERATED_DIR out.mkdir(parents=True, exist_ok=True) reports = { "RESEARCH_CATALOG.md": self.build_research_catalog, "EVIDENCE_MATRIX.md": self.build_evidence_matrix, "OPEN_QUESTIONS.md": self.build_open_questions, "CLAIM_REGISTER.md": self.build_claim_register, "DISSERTATION_MAP.md": self.build_dissertation_map, } paths: dict[str, Path] = {} for name, builder in reports.items(): path = out / name path.write_text(builder(), encoding="utf-8") paths[name] = path return paths