View of rewrites

This commit is contained in:
2026-04-20 16:16:46 +02:00
parent 8ff73d32ae
commit 2f8880d785
4 changed files with 233 additions and 1 deletions
+132
View File
@@ -611,6 +611,49 @@ async def conflicts(request: Request) -> dict:
return {"conflicts": [format_row(r) for r in rows]}
@app.get("/kg-log")
async def kg_log(request: Request, limit: int = 100, offset: int = 0, op: str = "") -> dict:
"""Return recent knowledge graph write log entries, newest first."""
pool = request.app.state.pool
query = """
SELECT id, op, concept_token, parent_token, prev_parent_token,
dim_token, is_isa, confidence, source, resolution_queue_id, created_at
FROM kg_write_log
{where}
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
"""
count_query = "SELECT COUNT(*) FROM kg_write_log {where}"
if op:
where = "WHERE op = $3"
async with pool.acquire() as conn:
rows = await conn.fetch(query.format(where=where), limit, offset, op)
total = await conn.fetchval(count_query.format(where=where), op)
else:
where = ""
async with pool.acquire() as conn:
rows = await conn.fetch(query.format(where=where), limit, offset)
total = await conn.fetchval(count_query.format(where=where))
def fmt(r):
return {
"id": r["id"],
"op": r["op"],
"concept": r["concept_token"],
"parent": r["parent_token"],
"prev_parent": r["prev_parent_token"],
"dimension": r["dim_token"],
"is_isa": r["is_isa"],
"confidence": round(r["confidence"], 3),
"source": r["source"],
"resolution_queue_id": r["resolution_queue_id"],
"created_at": r["created_at"].isoformat() if r["created_at"] else None,
}
return {"total": total, "offset": offset, "limit": limit, "entries": [fmt(r) for r in rows]}
# ---------------------------------------------------------------------------
# /test — scenario seeding and reset (for integration testing)
# ---------------------------------------------------------------------------
@@ -679,6 +722,17 @@ ADMIN_HTML = """<!DOCTYPE html>
pre {{ background: #f4f4f4; border: 1px solid #e0e0e0; border-radius: 3px; padding: 1em; overflow: auto; font-size: 0.85em; max-height: 400px; }}
.status-ok {{ color: #2a7a2a; }}
.status-err {{ color: #b00; }}
table {{ width: 100%; border-collapse: collapse; font-size: 0.82em; }}
th {{ text-align: left; border-bottom: 2px solid #ddd; padding: 4px 8px; font-size: 0.75em; text-transform: uppercase; letter-spacing: 0.04em; color: #666; }}
td {{ border-bottom: 1px solid #f0f0f0; padding: 4px 8px; vertical-align: top; }}
tr:hover td {{ background: #fafafa; }}
.op-insert {{ color: #2a7a2a; font-weight: bold; }}
.op-rewrite {{ color: #c07000; font-weight: bold; }}
.op-decompose {{ color: #5050cc; font-weight: bold; }}
.op-reclassify {{ color: #c02060; font-weight: bold; }}
.log-controls {{ display: flex; gap: 1em; align-items: center; margin: 0.5em 0 1em; }}
.log-controls select, .log-controls button {{ font-family: monospace; padding: 4px 10px; }}
.log-nav {{ margin-top: 0.5em; display: flex; gap: 1em; align-items: center; font-size: 0.85em; }}
footer {{ margin-top: 3em; padding-top: 1em; border-top: 1px solid #ddd; font-size: 0.78em; color: #888; }}
footer a {{ color: #888; }}
</style>
@@ -705,6 +759,26 @@ ADMIN_HTML = """<!DOCTYPE html>
<h2>Pending conflicts</h2>
<pre id="conflicts-pre">Loading…</pre>
<h2>Knowledge graph write log</h2>
<div class="log-controls">
<label>Op filter:
<select id="log-op-filter" onchange="loadLog(0)">
<option value="">all</option>
<option value="insert">insert</option>
<option value="rewrite">rewrite</option>
<option value="decompose">decompose</option>
<option value="reclassify">reclassify</option>
</select>
</label>
<button onclick="loadLog(0)">Refresh</button>
</div>
<div id="log-table-wrap">Loading…</div>
<div class="log-nav" id="log-nav" style="display:none">
<button id="log-prev" onclick="logPage(-1)" disabled>← prev</button>
<span id="log-page-info"></span>
<button id="log-next" onclick="logPage(1)">next →</button>
</div>
<footer>
<strong>Vocabulary source:</strong>
Princeton University &ldquo;About WordNet.&rdquo; <em>WordNet.</em> Princeton University. 2010.
@@ -772,8 +846,66 @@ ADMIN_HTML = """<!DOCTYPE html>
}}
}}
const LOG_PAGE_SIZE = 50;
let logOffset = 0;
let logTotal = 0;
async function loadLog(offset) {{
logOffset = offset;
const op = document.getElementById('log-op-filter').value;
const params = new URLSearchParams({{limit: LOG_PAGE_SIZE, offset: logOffset}});
if (op) params.set('op', op);
const r = await fetch('/kg-log?' + params);
const d = await r.json();
logTotal = d.total;
const opClass = {{insert:'op-insert', rewrite:'op-rewrite', decompose:'op-decompose', reclassify:'op-reclassify'}};
if (!d.entries.length) {{
document.getElementById('log-table-wrap').innerHTML = '<em>No entries yet.</em>';
document.getElementById('log-nav').style.display = 'none';
return;
}}
let html = '<table><thead><tr>'
+ '<th>#</th><th>Time</th><th>Op</th><th>Concept</th>'
+ '<th>Dimension</th><th>Parent</th><th>Prev parent</th>'
+ '<th>is-a</th><th>Conf</th><th>Source</th>'
+ '</tr></thead><tbody>';
for (const e of d.entries) {{
const ts = e.created_at ? e.created_at.replace('T',' ').slice(0,19) : '';
const cls = opClass[e.op] || '';
html += `<tr>
<td>${{e.id}}</td>
<td>${{ts}}</td>
<td><span class="${{cls}}">${{e.op}}</span></td>
<td>${{e.concept}}</td>
<td>${{e.dimension}}</td>
<td>${{e.parent}}</td>
<td>${{e.prev_parent || ''}}</td>
<td>${{e.is_isa ? 'yes' : ''}}</td>
<td>${{e.confidence}}</td>
<td>${{e.source}}</td>
</tr>`;
}}
html += '</tbody></table>';
document.getElementById('log-table-wrap').innerHTML = html;
const nav = document.getElementById('log-nav');
nav.style.display = 'flex';
document.getElementById('log-prev').disabled = logOffset === 0;
document.getElementById('log-next').disabled = logOffset + LOG_PAGE_SIZE >= logTotal;
document.getElementById('log-page-info').textContent =
`${{logOffset + 1}}${{Math.min(logOffset + LOG_PAGE_SIZE, logTotal)}} of ${{logTotal}}`;
}}
function logPage(dir) {{
loadLog(Math.max(0, logOffset + dir * LOG_PAGE_SIZE));
}}
loadStats();
loadConflicts();
loadLog(0);
</script>
</body>
</html>