Files
ietf-draft-analyzer/docs/observatory/gaps.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

118 lines
4.1 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gaps - 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">Dashboard</a>
<a href="explorer.html">Explorer</a>
<a href="gaps.html" class="active">Gaps</a>
<a href="timeline.html">Timeline</a>
</nav>
</div>
</div>
<div class="container">
<h2 style="margin-bottom:16px">Coverage Gaps</h2>
<p class="dim" style="margin-bottom:20px">Areas, problems, or technical challenges not adequately addressed by existing standards documents.</p>
<div class="controls">
<div class="controls-row">
<select id="sevFilter" style="padding:8px;border:1px solid var(--border);border-radius:6px;font-size:0.85rem">
<option value="">All severities</option>
<option value="critical">Critical</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
<input type="text" class="search-box" id="gapSearch" placeholder="Filter gaps..." style="max-width:400px">
</div>
</div>
<div id="gapsList"></div>
<h2 style="margin:32px 0 16px">Gap History</h2>
<p class="dim" style="margin-bottom:20px">How gaps have evolved across observatory snapshots.</p>
<div class="panel">
<table>
<thead>
<tr><th>Snapshot</th><th>Topic</th><th>Severity</th><th>Status</th></tr>
</thead>
<tbody id="historyBody"></tbody>
</table>
</div>
</div>
<script>
function escHtml(s) { const d = document.createElement('div'); d.textContent = s || ''; return d.innerHTML; }
let GAPS_DATA = null;
function renderGaps() {
const sev = document.getElementById('sevFilter').value;
const q = document.getElementById('gapSearch').value.toLowerCase().trim();
const list = document.getElementById('gapsList');
list.innerHTML = '';
let current = GAPS_DATA.current || [];
if (sev) current = current.filter(g => g.severity === sev);
if (q) current = current.filter(g => (g.topic + ' ' + g.description + ' ' + (g.category || '')).toLowerCase().includes(q));
if (current.length === 0) {
list.innerHTML = '<p class="dim" style="padding:16px">No gaps match the current filters.</p>';
return;
}
const order = {'critical': 0, 'high': 1, 'medium': 2, 'low': 3};
current.sort((a, b) => (order[a.severity] || 2) - (order[b.severity] || 2));
current.forEach(g => {
const cls = (g.severity === 'critical' || g.severity === 'high') ? g.severity : '';
list.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 || 'medium').toUpperCase() + '</span>' +
(g.category ? ' &middot; ' + escHtml(g.category) : '') +
(g.evidence ? '<br><em>' + escHtml(g.evidence) + '</em>' : '') +
'</div></div>';
});
}
async function init() {
GAPS_DATA = await fetch('../data/gaps.json').then(r => r.json());
document.getElementById('sevFilter').onchange = renderGaps;
document.getElementById('gapSearch').oninput = renderGaps;
renderGaps();
// History table
const history = GAPS_DATA.history || [];
const tbody = document.getElementById('historyBody');
if (history.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="dim">No history recorded yet.</td></tr>';
} else {
history.slice(-50).reverse().forEach(h => {
const tr = document.createElement('tr');
tr.innerHTML =
'<td class="dim">' + (h.snapshot_at || h.recorded_at || '').substring(0, 10) + '</td>' +
'<td>' + escHtml(h.gap_topic) + '</td>' +
'<td><span class="sev-' + (h.severity || 'medium') + '">' + (h.severity || 'medium').toUpperCase() + '</span></td>' +
'<td>' + escHtml(h.status || 'open') + '</td>';
tbody.appendChild(tr);
});
}
}
init();
</script>
</body>
</html>