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>
118 lines
4.1 KiB
HTML
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 ? ' · ' + 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> |