const formatHours = (value) => `${Number(value).toFixed(1)} h`; const create = (tag, className, text) => { const node = document.createElement(tag); if (className) node.className = className; if (text !== undefined) node.textContent = text; return node; }; const load = async () => { const response = await fetch("./data/analysis.json"); if (!response.ok) throw new Error("analysis.json konnte nicht geladen werden"); return response.json(); }; const renderHero = (data) => { document.querySelector("#headline").textContent = data.executive_summary.headline; const panel = document.querySelector("#heroPanel"); const stats = [ ["Best Estimate", formatHours(data.work_estimate.best_estimate_hours)], ["Aktive Projekte", String(data.work_estimate.active_project_count)], ["Top Empfehlung", data.rankings.top_recommendation.name], ["Analysefenster", `${data.meta.window_start} bis ${data.meta.window_end}`], ]; stats.forEach(([label, value]) => { const row = create("div", "summary-stat"); row.append(create("span", "", label)); row.append(create("strong", "", value)); panel.append(row); }); }; const renderFindings = (data) => { const host = document.querySelector("#findingCards"); data.executive_summary.key_findings.forEach((finding, index) => { const card = create("article", "card"); card.append(create("div", "label", `Antwort ${index + 1}`)); card.append(create("p", "supporting", finding)); host.append(card); }); }; const renderWork = (data) => { const host = document.querySelector("#workCards"); const cards = [ ["Best Estimate", formatHours(data.work_estimate.best_estimate_hours), "Zentralwert aus Session-Bloecken, Commit-Spans und Artefakt-Bonus."], ["Untergrenze", formatHours(data.work_estimate.lower_bound_hours), "Konservative Sicht: nur ein Teil der Session-Zeit zaehlt als aktive Build-Zeit."], ["Obergrenze", formatHours(data.work_estimate.upper_bound_hours), "Erweitert um stille Vor-/Nacharbeit, verworfene Versuche und uncommitted Arbeit."], ]; cards.forEach(([label, metric, support]) => { const card = create("article", "card"); card.append(create("div", "label", label)); card.append(create("p", "metric", metric)); card.append(create("p", "supporting", support)); host.append(card); }); const methodList = document.querySelector("#methodList"); data.work_estimate.method.forEach((line) => methodList.append(create("li", "", line))); const uncertaintyList = document.querySelector("#uncertaintyList"); data.work_estimate.uncertainties.forEach((line) => uncertaintyList.append(create("li", "", line))); }; const renderWorkflow = (data) => { const workflow = data.workflow_patterns; const narrative = document.querySelector("#workflowNarrative"); [workflow.startup_pattern, workflow.iteration_pattern, workflow.parallel_vs_serial.evidence].forEach((line) => { narrative.append(create("p", "", line)); }); const total = workflow.prototyping_vs_refinement.prototype_signals + workflow.prototyping_vs_refinement.refinement_signals; const protoPct = total ? (workflow.prototyping_vs_refinement.prototype_signals / total) * 100 : 0; const refinePct = 100 - protoPct; const chart = document.querySelector("#ratioChart"); [ ["Prototyping", protoPct, "proto"], ["Refinement", refinePct, "refine"], ].forEach(([label, pct, klass]) => { const row = create("div", "mini-row"); row.append(create("div", "", label)); const track = create("div", "track"); const fill = create("span"); fill.className = klass; fill.style.width = `${pct}%`; track.append(fill); row.append(track); row.append(create("div", "muted", `${pct.toFixed(0)}%`)); chart.append(row); }); document.querySelector("#ratioNote").textContent = workflow.prototyping_vs_refinement.interpretation; const cards = [ ["Seriell vs. Parallel", workflow.parallel_vs_serial.label], ["Eval-Loops", workflow.evaluation_loops.interpretation], ["Bevorzugte Projektarten", workflow.preferred_project_types.join(" • ")], ["Meta-Systeme", workflow.meta_systems.join(" • ")], ]; const host = document.querySelector("#workflowCards"); cards.forEach(([label, support]) => { const card = create("article", "card"); card.append(create("div", "label", label)); card.append(create("p", "supporting", support)); host.append(card); }); }; const renderClusters = (data) => { const grid = document.querySelector("#clusterGrid"); data.project_clusters.forEach((cluster) => { const card = create("article", "cluster-card"); card.append(create("div", "label", `${cluster.count} Projekte`)); card.append(create("h3", "", cluster.name)); card.append(create("p", "supporting", cluster.summary)); const pills = create("div", "pill-row"); cluster.projects.forEach((project) => { pills.append(create("span", "pill", `${project.name} · ${project.commit_count}`)); }); card.append(pills); grid.append(card); }); const tableHost = document.querySelector("#projectTable"); const table = create("table"); table.innerHTML = ` Projekt Cluster Commits Sessions Status `; const tbody = table.querySelector("tbody"); data.projects.slice(0, 12).forEach((project) => { const row = document.createElement("tr"); row.innerHTML = ` ${project.name} ${project.cluster} ${project.commit_count} ${project.session_count} ${project.status} `; tbody.append(row); }); tableHost.append(table); }; const renderTimeline = (data) => { const host = document.querySelector("#timeline"); const grid = create("div", "timeline-grid"); data.timeline.forEach((day) => { const cell = create("article", `day-cell level-${day.intensity}`); const date = new Date(day.date); const label = date.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" }); const header = create("div", "day-head"); header.append(create("span", "", label)); header.append(create("span", "", `${day.projects.length} Projekte`)); cell.append(header); const projects = create("div", "day-projects"); if (day.projects.length === 0) { projects.append(create("span", "muted", "ruhiger Tag")); } else { day.projects.slice(0, 3).forEach((project) => { projects.append(create("span", "", `${project.name} · ${project.session_count} Session`)); }); } cell.append(projects); grid.append(cell); }); host.append(grid); }; const renderRanking = (data) => { const top = data.rankings.top_recommendation; const main = document.querySelector("#topRecommendation"); main.append(create("div", "badge", "Top Empfehlung")); main.append(create("h3", "", top.name)); main.append(create("p", "metric", `${top.score.toFixed(2)} / 5`)); main.append(create("p", "supporting", top.summary)); const stack = create("div", "mini-stack"); Object.entries(top.scores).forEach(([label, value]) => { const row = create("div", "mini-row"); row.append(create("div", "", label.replace("_", " "))); const track = create("div", "track"); const fill = create("span"); fill.style.width = `${(value / 5) * 100}%`; track.append(fill); row.append(track); row.append(create("div", "muted", value.toFixed(1))); stack.append(row); }); main.append(stack); const topThree = document.querySelector("#topThree"); data.rankings.top_three.forEach((project, index) => { const item = create("div", "rank-item"); item.innerHTML = `#${index + 1} ${project.name}

${project.summary}

`; topThree.append(item); }); const rankingTable = document.querySelector("#rankingTable"); const table = create("table"); table.innerHTML = ` Projekt Score Commits Kernaussage `; const tbody = table.querySelector("tbody"); data.rankings.shortlist.forEach((project) => { const row = document.createElement("tr"); row.innerHTML = ` ${project.name} ${project.score.toFixed(2)} ${project.commit_count} ${project.summary} `; tbody.append(row); }); rankingTable.append(table); }; const renderEvidence = (data) => { const host = document.querySelector("#evidenceGrid"); data.evidence.forEach((entry) => { const card = create("article", "evidence-card"); card.append(create("div", "label", entry.project)); card.append(create("p", "supporting", `${entry.commits} Commits im Fenster · letzter Commit ${entry.last_commit ?? "n/a"}`)); const pills = create("div", "pill-row"); Object.entries(entry.artifacts).forEach(([key, value]) => pills.append(create("span", "pill", `${key}: ${value}`))); card.append(pills); const list = create("ul", "bullet-list"); entry.sample_messages.forEach((message) => list.append(create("li", "", message))); card.append(list); host.append(card); }); }; load() .then((data) => { renderHero(data); renderFindings(data); renderWork(data); renderWorkflow(data); renderClusters(data); renderTimeline(data); renderRanking(data); renderEvidence(data); }) .catch((error) => { document.body.innerHTML = `

Ladefehler

${error.message}

`; });