Files
ietf-draft-analyzer/docs/index.html
Christian Nennemann d6beb9c0a0 v0.3.0: Gap-to-Draft pipeline, Living Standards Observatory, blog series
Gap-to-Draft Pipeline (ietf pipeline):
- Context builder assembles ideas, RFC foundations, similar drafts, ecosystem vision
- Generator produces outlines + sections using rich context with Claude
- Quality gates: novelty (embedding similarity), references, format, self-rating
- Family coordinator generates 5-draft ecosystem (AEM/ATD/HITL/AEPB/APAE)
- I-D formatter with proper headers, references, 72-char wrapping

Living Standards Observatory (ietf observatory):
- Source abstraction with IETF + W3C fetchers
- 7-step update pipeline: snapshot, fetch, analyze, embed, ideas, gaps, record
- Static GitHub Pages dashboard (explorer, gap tracker, timeline)
- Weekly CI/CD automation via GitHub Actions

Also includes:
- 361 drafts (expanded from 260 with 6 new keywords), 403 authors, 1,262 ideas, 12 gaps
- Blog series (8 posts planned), reports, arXiv paper figures
- Agent team infrastructure (CLAUDE.md, scripts, dev journal)
- 5 new DB tables, schema migration, ~15 new query methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:48:57 +01:00

110 lines
4.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Living Standards Observatory</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<div class="header">
<div class="container">
<h1>Living Standards Observatory</h1>
<nav>
<a href="index.html" class="active">Dashboard</a>
<a href="observatory/explorer.html">Explorer</a>
<a href="observatory/gaps.html">Gaps</a>
<a href="observatory/timeline.html">Timeline</a>
</nav>
</div>
</div>
<div class="container">
<div class="cards" id="metricsCards">
<div class="card"><div class="label">Total Documents</div><div class="value" id="totalDocs">--</div><div class="sub" id="sourceSub"></div></div>
<div class="card"><div class="label">Standards Bodies</div><div class="value" id="sourceCount">--</div><div class="sub">Active sources</div></div>
<div class="card"><div class="label">Open Gaps</div><div class="value" id="gapCount">--</div><div class="sub">Identified coverage gaps</div></div>
<div class="card"><div class="label">Ideas Extracted</div><div class="value" id="ideaCount">--</div><div class="sub">Technical contributions</div></div>
<div class="card"><div class="label">Authors Tracked</div><div class="value" id="authorCount">--</div><div class="sub">Individual contributors</div></div>
<div class="card"><div class="label">Last Update</div><div class="value" id="lastUpdate" style="font-size:1rem">--</div><div class="sub" id="updateSub"></div></div>
</div>
<div class="panel">
<div class="panel-header">Top Rated Documents</div>
<table>
<thead>
<tr><th>Score</th><th>Document</th><th>Source</th><th>Date</th><th>Categories</th></tr>
</thead>
<tbody id="topDrafts"></tbody>
</table>
</div>
<div class="panel">
<div class="panel-header">Critical &amp; High Severity Gaps</div>
<div id="gapsList" style="padding: 16px;"></div>
</div>
</div>
<script>
function escHtml(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
function scoreBadge(s) {
const cls = s >= 4.0 ? 'score-high' : s >= 3.0 ? 'score-mid' : 'score-low';
return '<span class="score-badge ' + cls + '">' + s.toFixed(1) + '</span>';
}
async function init() {
const [obs, drafts, gaps] = await Promise.all([
fetch('data/observatory.json').then(r => r.json()),
fetch('data/drafts.json').then(r => r.json()),
fetch('data/gaps.json').then(r => r.json()),
]);
// Metrics
document.getElementById('totalDocs').textContent = obs.total_docs;
const srcNames = Object.keys(obs.sources || {});
document.getElementById('sourceCount').textContent = srcNames.length || 1;
document.getElementById('sourceSub').textContent = srcNames.map(s => s.toUpperCase() + ': ' + (obs.sources[s] || 0)).join(' | ') || '';
document.getElementById('gapCount').textContent = obs.gaps_count;
document.getElementById('ideaCount').textContent = obs.ideas;
document.getElementById('authorCount').textContent = obs.authors;
if (obs.last_update) {
document.getElementById('lastUpdate').textContent = obs.last_update.substring(0, 10);
}
// Top drafts
const top = drafts.sort((a, b) => b.score - a.score).slice(0, 15);
const tbody = document.getElementById('topDrafts');
top.forEach(d => {
const tr = document.createElement('tr');
const srcClass = 'source-' + (d.source || 'ietf');
tr.innerHTML =
'<td>' + scoreBadge(d.score) + '</td>' +
'<td><a href="' + escHtml(d.url) + '" target="_blank">' + escHtml(d.name) + '</a><br><span class="dim">' + escHtml(d.title.substring(0,80)) + '</span></td>' +
'<td><span class="source-badge ' + srcClass + '">' + (d.source || 'ietf').toUpperCase() + '</span></td>' +
'<td class="dim">' + d.date + '</td>' +
'<td>' + d.categories.map(c => '<span class="cat-badge">' + escHtml(c) + '</span>').join('') + '</td>';
tbody.appendChild(tr);
});
// Gaps
const gapsList = document.getElementById('gapsList');
const critical = (gaps.current || []).filter(g => g.severity === 'critical' || g.severity === 'high');
if (critical.length === 0) {
gapsList.innerHTML = '<p class="dim">No critical or high severity gaps found.</p>';
} else {
critical.forEach(g => {
const cls = g.severity === 'critical' ? 'critical' : 'high';
gapsList.innerHTML +=
'<div class="gap-card ' + cls + '">' +
'<h3>' + escHtml(g.topic) + '</h3>' +
'<p>' + escHtml(g.description) + '</p>' +
'<div class="meta"><span class="sev-' + g.severity + '">' + g.severity.toUpperCase() + '</span> &middot; ' + escHtml(g.category || '') + '</div>' +
'</div>';
});
}
}
init();
</script>
</body>
</html>