Monorepo for Aesthetic.Computer
aesthetic.computer
1{
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# KidLisp Canonical Attribution Review\n",
8 "\n",
9 "Notebook-first review flow for anonymous-to-canonical KidLisp attribution candidates.\n",
10 "\n",
11 "Scope for this phase:\n",
12 "1. Load candidate data + market-style stats\n",
13 "2. Filter/sort candidates for manual review\n",
14 "3. Save decisions and generate a dry-run patch plan JSON\n",
15 "\n",
16 "This notebook does **not** apply any live patches.\n"
17 ]
18 },
19 {
20 "cell_type": "code",
21 "execution_count": null,
22 "metadata": {},
23 "outputs": [],
24 "source": [
25 "from __future__ import annotations\n",
26 "\n",
27 "import json\n",
28 "from collections import Counter\n",
29 "from datetime import datetime, timezone\n",
30 "from pathlib import Path\n",
31 "from typing import Dict, List\n",
32 "\n",
33 "from IPython.display import HTML, display\n",
34 "\n",
35 "\n",
36 "def find_repo_root(start: Path | None = None) -> Path:\n",
37 " start = (start or Path.cwd()).resolve()\n",
38 " for candidate in [start, *start.parents]:\n",
39 " if (candidate / 'reports').exists() and (candidate / 'notebook').exists():\n",
40 " return candidate\n",
41 " raise RuntimeError('Could not find repo root from current working directory')\n",
42 "\n",
43 "\n",
44 "ROOT = find_repo_root()\n",
45 "INPUT_PATH = ROOT / 'reports' / '2026-03-12-jeffrey-kidlisp-attribution-candidates-with-media.json'\n",
46 "DECISIONS_PATH = ROOT / 'reports' / '2026-03-12-kidlisp-canonical-decisions.json'\n",
47 "PATCH_PLAN_PATH = ROOT / 'reports' / '2026-03-12-kidlisp-canonical-patch-plan.json'\n",
48 "\n",
49 "\n",
50 "def load_dataset(path: Path = INPUT_PATH) -> dict:\n",
51 " payload = json.loads(path.read_text())\n",
52 " rows = payload.get('candidates', [])\n",
53 " for row in rows:\n",
54 " row['_pair_id'] = f\"{row.get('anonCode','?')}->{row.get('matchedJeffCode','?')}\"\n",
55 " row['_confidence_group'] = str(row.get('confidence', '')).split('_')[0]\n",
56 " payload['candidates'] = rows\n",
57 " return payload\n",
58 "\n",
59 "\n",
60 "data = load_dataset()\n",
61 "rows = data['candidates']\n",
62 "ROW_BY_ID = {r['_pair_id']: r for r in rows}\n",
63 "\n",
64 "print('repo_root :', ROOT)\n",
65 "print('input :', INPUT_PATH)\n",
66 "print('decisions :', DECISIONS_PATH)\n",
67 "print('patch_plan :', PATCH_PLAN_PATH)\n",
68 "print('generated_at :', data.get('generatedAt'))\n",
69 "print('candidate_count:', len(rows))\n"
70 ]
71 },
72 {
73 "cell_type": "code",
74 "execution_count": null,
75 "metadata": {},
76 "outputs": [],
77 "source": [
78 "def dataset_summary(rows: List[dict]) -> dict:\n",
79 " confidence = Counter(r.get('confidence', 'unknown') for r in rows)\n",
80 " total_anon_hits = sum(int(r.get('anonHits') or 0) for r in rows)\n",
81 " total_match_hits = sum(int(r.get('matchedJeffHits') or 0) for r in rows)\n",
82 " time_close_1h = sum(1 for r in rows if (r.get('timeDiffHours') is not None and float(r['timeDiffHours']) <= 1))\n",
83 " exact_like = sum(1 for r in rows if str(r.get('confidence', '')).startswith('high_exact'))\n",
84 " return {\n",
85 " 'confidence': dict(confidence),\n",
86 " 'total_anon_hits': total_anon_hits,\n",
87 " 'total_match_hits': total_match_hits,\n",
88 " 'time_close_1h': time_close_1h,\n",
89 " 'exact_like': exact_like,\n",
90 " }\n",
91 "\n",
92 "\n",
93 "summary = dataset_summary(rows)\n",
94 "summary\n"
95 ]
96 },
97 {
98 "cell_type": "code",
99 "execution_count": null,
100 "metadata": {},
101 "outputs": [],
102 "source": [
103 "def select_candidates(\n",
104 " rows: List[dict],\n",
105 " mode: str = 'all',\n",
106 " min_hits: int = 0,\n",
107 " max_time_diff_hours: float | None = None,\n",
108 " sort_by: str = 'hits_desc',\n",
109 " limit: int | None = 50,\n",
110 ") -> List[dict]:\n",
111 " mode = mode.strip().lower()\n",
112 "\n",
113 " def mode_ok(r: dict) -> bool:\n",
114 " c = str(r.get('confidence', ''))\n",
115 " hits = int(r.get('anonHits') or 0)\n",
116 " tdh = r.get('timeDiffHours')\n",
117 " is_time_close = (tdh is not None and float(tdh) <= 1)\n",
118 " if mode == 'all':\n",
119 " return True\n",
120 " if mode == 'exact':\n",
121 " return c.startswith('high_exact')\n",
122 " if mode == 'canonical':\n",
123 " return 'canonical' in c\n",
124 " if mode == 'high_impact':\n",
125 " return hits >= 1000\n",
126 " if mode == 'time_close':\n",
127 " return is_time_close\n",
128 " if mode == 'review_priority':\n",
129 " return (c.startswith('high_exact') or (hits >= 1000 and 'canonical' in c) or is_time_close)\n",
130 " raise ValueError(f'Unknown mode: {mode}')\n",
131 "\n",
132 " filtered = []\n",
133 " for r in rows:\n",
134 " if int(r.get('anonHits') or 0) < int(min_hits):\n",
135 " continue\n",
136 " if max_time_diff_hours is not None:\n",
137 " tdh = r.get('timeDiffHours')\n",
138 " if tdh is None or float(tdh) > float(max_time_diff_hours):\n",
139 " continue\n",
140 " if mode_ok(r):\n",
141 " filtered.append(r)\n",
142 "\n",
143 " if sort_by == 'hits_desc':\n",
144 " filtered.sort(key=lambda r: int(r.get('anonHits') or 0), reverse=True)\n",
145 " elif sort_by == 'time_asc':\n",
146 " filtered.sort(key=lambda r: float(r.get('timeDiffHours') or 999999.0))\n",
147 " elif sort_by == 'confidence_then_hits':\n",
148 " weight = {'high_exact': 0, 'medium_canonical_timeclose': 1, 'medium_canonical': 2}\n",
149 " filtered.sort(\n",
150 " key=lambda r: (\n",
151 " weight.get(str(r.get('confidence')), 9),\n",
152 " -int(r.get('anonHits') or 0),\n",
153 " )\n",
154 " )\n",
155 " else:\n",
156 " raise ValueError(f'Unknown sort_by: {sort_by}')\n",
157 "\n",
158 " if limit is not None:\n",
159 " return filtered[: int(limit)]\n",
160 " return filtered\n",
161 "\n",
162 "\n",
163 "# Quick examples:\n",
164 "print('priority rows:', len(select_candidates(rows, mode='review_priority', limit=None)))\n",
165 "print('exact rows :', len(select_candidates(rows, mode='exact', limit=None)))\n",
166 "print('high impact :', len(select_candidates(rows, mode='high_impact', limit=None)))\n"
167 ]
168 },
169 {
170 "cell_type": "code",
171 "execution_count": null,
172 "metadata": {},
173 "outputs": [],
174 "source": [
175 "def _escape(value: object) -> str:\n",
176 " text = str(value if value is not None else '')\n",
177 " return (\n",
178 " text.replace('&', '&')\n",
179 " .replace('<', '<')\n",
180 " .replace('>', '>')\n",
181 " .replace('\"', '"')\n",
182 " )\n",
183 "\n",
184 "\n",
185 "def render_candidates_table(candidates: List[dict], decisions: Dict[str, dict] | None = None, title: str = ''):\n",
186 " decisions = decisions or {}\n",
187 "\n",
188 " css = \"\"\"\n",
189 " <style>\n",
190 " table.ac-review { border-collapse: collapse; width: 100%; font-size: 12px; }\n",
191 " .ac-review th, .ac-review td { border: 1px solid #d8dee4; padding: 6px; vertical-align: top; }\n",
192 " .ac-review th { background: #f6f8fa; text-align: left; }\n",
193 " .ac-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }\n",
194 " .ac-muted { color: #57606a; }\n",
195 " .ac-hit { font-weight: 600; }\n",
196 " img.ac-thumb { width: 96px; height: 96px; object-fit: cover; border-radius: 6px; border: 1px solid #d0d7de; }\n",
197 " .ac-title { margin: 6px 0 10px; font-weight: 600; }\n",
198 " </style>\n",
199 " \"\"\"\n",
200 "\n",
201 " parts = [css]\n",
202 " if title:\n",
203 " parts.append(f\"<div class='ac-title'>{_escape(title)}</div>\")\n",
204 "\n",
205 " parts.append('<table class=\"ac-review\">')\n",
206 " parts.append('<thead><tr>')\n",
207 " headers = ['pair', 'confidence', 'hits', 'time diff (h)', 'anonymous', 'canonical', 'reason', 'decision']\n",
208 " for h in headers:\n",
209 " parts.append(f'<th>{_escape(h)}</th>')\n",
210 " parts.append('</tr></thead><tbody>')\n",
211 "\n",
212 " for r in candidates:\n",
213 " pid = r['_pair_id']\n",
214 " d = decisions.get(pid, {})\n",
215 " decision_text = d.get('decision', '')\n",
216 " if d.get('note'):\n",
217 " decision_text += f\" | {_escape(d['note'])}\"\n",
218 "\n",
219 " parts.append('<tr>')\n",
220 " parts.append(f\"<td class='ac-code'>{_escape(pid)}</td>\")\n",
221 " parts.append(f\"<td>{_escape(r.get('confidence'))}</td>\")\n",
222 " parts.append(\n",
223 " '<td>'\n",
224 " f\"<span class='ac-hit'>{_escape(r.get('anonHits'))}</span>\"\n",
225 " f\" <span class='ac-muted'>(match {_escape(r.get('matchedJeffHits'))})</span>\"\n",
226 " '</td>'\n",
227 " )\n",
228 " parts.append(f\"<td>{_escape(r.get('timeDiffHours'))}</td>\")\n",
229 "\n",
230 " parts.append(\n",
231 " '<td>'\n",
232 " f\"<div class='ac-code'>${_escape(r.get('anonCode'))}</div>\"\n",
233 " f\"<img class='ac-thumb' src='{_escape(r.get('anonStill') or r.get('anonThumb') or '')}' alt='anon'/>\"\n",
234 " '</td>'\n",
235 " )\n",
236 " parts.append(\n",
237 " '<td>'\n",
238 " f\"<div class='ac-code'>${_escape(r.get('matchedJeffCode'))}</div>\"\n",
239 " f\"<img class='ac-thumb' src='{_escape(r.get('matchStill') or r.get('matchThumb') or '')}' alt='match'/>\"\n",
240 " '</td>'\n",
241 " )\n",
242 " parts.append(f\"<td>{_escape(r.get('reason'))}</td>\")\n",
243 " parts.append(f\"<td>{_escape(decision_text)}</td>\")\n",
244 " parts.append('</tr>')\n",
245 "\n",
246 " parts.append('</tbody></table>')\n",
247 " display(HTML(''.join(parts)))\n",
248 "\n",
249 "\n",
250 "preview = select_candidates(rows, mode='review_priority', sort_by='confidence_then_hits', limit=20)\n",
251 "render_candidates_table(preview, title='Review priority (top 20)')\n"
252 ]
253 },
254 {
255 "cell_type": "code",
256 "execution_count": null,
257 "metadata": {},
258 "outputs": [],
259 "source": [
260 "VALID_DECISIONS = {'approve', 'hold', 'reject'}\n",
261 "\n",
262 "\n",
263 "def _now_iso() -> str:\n",
264 " return datetime.now(timezone.utc).replace(microsecond=0).isoformat()\n",
265 "\n",
266 "\n",
267 "def load_decisions(path: Path = DECISIONS_PATH) -> Dict[str, dict]:\n",
268 " if not path.exists():\n",
269 " return {}\n",
270 " payload = json.loads(path.read_text())\n",
271 " return payload.get('decisions', {})\n",
272 "\n",
273 "\n",
274 "def save_decisions(decisions: Dict[str, dict], path: Path = DECISIONS_PATH) -> Path:\n",
275 " payload = {\n",
276 " 'generatedAt': _now_iso(),\n",
277 " 'source': str(INPUT_PATH.relative_to(ROOT)),\n",
278 " 'count': len(decisions),\n",
279 " 'decisions': decisions,\n",
280 " }\n",
281 " path.write_text(json.dumps(payload, indent=2) + '\\n')\n",
282 " return path\n",
283 "\n",
284 "\n",
285 "def set_decision(\n",
286 " anon_code: str,\n",
287 " matched_code: str,\n",
288 " decision: str,\n",
289 " note: str = '',\n",
290 " reviewer: str = '@jeffrey',\n",
291 "):\n",
292 " decision = decision.strip().lower()\n",
293 " if decision not in VALID_DECISIONS:\n",
294 " raise ValueError(f'decision must be one of {sorted(VALID_DECISIONS)}')\n",
295 "\n",
296 " pair_id = f\"{anon_code}->{matched_code}\"\n",
297 " if pair_id not in ROW_BY_ID:\n",
298 " raise KeyError(f'Unknown pair: {pair_id}')\n",
299 "\n",
300 " decisions = load_decisions()\n",
301 " decisions[pair_id] = {\n",
302 " 'decision': decision,\n",
303 " 'note': note,\n",
304 " 'reviewer': reviewer,\n",
305 " 'decidedAt': _now_iso(),\n",
306 " }\n",
307 " save_decisions(decisions)\n",
308 " return decisions[pair_id]\n",
309 "\n",
310 "\n",
311 "def clear_decision(anon_code: str, matched_code: str):\n",
312 " pair_id = f\"{anon_code}->{matched_code}\"\n",
313 " decisions = load_decisions()\n",
314 " removed = decisions.pop(pair_id, None)\n",
315 " save_decisions(decisions)\n",
316 " return removed\n",
317 "\n",
318 "\n",
319 "def bulk_set_decision(candidates: List[dict], decision: str, note: str = '', reviewer: str = '@jeffrey') -> int:\n",
320 " decision = decision.strip().lower()\n",
321 " if decision not in VALID_DECISIONS:\n",
322 " raise ValueError(f'decision must be one of {sorted(VALID_DECISIONS)}')\n",
323 " decisions = load_decisions()\n",
324 " count = 0\n",
325 " for row in candidates:\n",
326 " decisions[row['_pair_id']] = {\n",
327 " 'decision': decision,\n",
328 " 'note': note,\n",
329 " 'reviewer': reviewer,\n",
330 " 'decidedAt': _now_iso(),\n",
331 " }\n",
332 " count += 1\n",
333 " save_decisions(decisions)\n",
334 " return count\n",
335 "\n",
336 "\n",
337 "def decisions_summary() -> dict:\n",
338 " decisions = load_decisions()\n",
339 " return {\n",
340 " 'path': str(DECISIONS_PATH.relative_to(ROOT)),\n",
341 " 'total': len(decisions),\n",
342 " 'by_decision': dict(Counter(v.get('decision', 'unknown') for v in decisions.values())),\n",
343 " }\n",
344 "\n",
345 "\n",
346 "# Optional seed: auto-approve exact matches only.\n",
347 "# exact_rows = select_candidates(rows, mode='exact', limit=None)\n",
348 "# bulk_set_decision(exact_rows, decision='approve', note='exact source match')\n",
349 "\n",
350 "# Example manual decisions:\n",
351 "# set_decision('bop', 'pi5', 'hold', note='high impact canonical; verify lineage')\n",
352 "# set_decision('ceo', 'puf', 'hold', note='high impact canonical; verify lineage')\n",
353 "\n",
354 "decisions = load_decisions()\n",
355 "print(decisions_summary())\n"
356 ]
357 },
358 {
359 "cell_type": "code",
360 "execution_count": null,
361 "metadata": {},
362 "outputs": [],
363 "source": [
364 "def build_patch_plan(rows: List[dict], decisions: Dict[str, dict]) -> List[dict]:\n",
365 " plan = []\n",
366 " for row in rows:\n",
367 " pair_id = row['_pair_id']\n",
368 " d = decisions.get(pair_id)\n",
369 " if not d or d.get('decision') != 'approve':\n",
370 " continue\n",
371 "\n",
372 " plan.append(\n",
373 " {\n",
374 " 'pair_id': pair_id,\n",
375 " 'from_code': row.get('anonCode'),\n",
376 " 'to_code': row.get('matchedJeffCode'),\n",
377 " 'confidence': row.get('confidence'),\n",
378 " 'anon_hits': row.get('anonHits'),\n",
379 " 'matched_hits': row.get('matchedJeffHits'),\n",
380 " 'time_diff_hours': row.get('timeDiffHours'),\n",
381 " 'reason': row.get('reason'),\n",
382 " 'source_preview': row.get('sourcePreview'),\n",
383 " 'evidence': {\n",
384 " 'anon_when': row.get('anonWhen'),\n",
385 " 'matched_when': row.get('matchedJeffWhen'),\n",
386 " 'anon_thumb': row.get('anonThumb'),\n",
387 " 'anon_still': row.get('anonStill'),\n",
388 " 'match_thumb': row.get('matchThumb'),\n",
389 " 'match_still': row.get('matchStill'),\n",
390 " },\n",
391 " 'decision': d,\n",
392 " }\n",
393 " )\n",
394 " return plan\n",
395 "\n",
396 "\n",
397 "def write_patch_plan(path: Path = PATCH_PLAN_PATH):\n",
398 " decisions = load_decisions()\n",
399 " plan_rows = build_patch_plan(rows, decisions)\n",
400 "\n",
401 " payload = {\n",
402 " 'generatedAt': _now_iso(),\n",
403 " 'mode': 'dry-run',\n",
404 " 'source': str(INPUT_PATH.relative_to(ROOT)),\n",
405 " 'decisionsSource': str(DECISIONS_PATH.relative_to(ROOT)),\n",
406 " 'approvedCount': len(plan_rows),\n",
407 " 'entries': plan_rows,\n",
408 " }\n",
409 "\n",
410 " path.write_text(json.dumps(payload, indent=2) + '\\n')\n",
411 " return {\n",
412 " 'patchPlanPath': str(path.relative_to(ROOT)),\n",
413 " 'approvedCount': len(plan_rows),\n",
414 " 'totalDecisions': len(decisions),\n",
415 " }\n",
416 "\n",
417 "\n",
418 "write_patch_plan()\n"
419 ]
420 },
421 {
422 "cell_type": "markdown",
423 "metadata": {},
424 "source": [
425 "## Suggested Usage\n",
426 "\n",
427 "1. Preview priority rows:\n",
428 " - `preview = select_candidates(rows, mode='review_priority', sort_by='confidence_then_hits', limit=20)`\n",
429 " - `render_candidates_table(preview, decisions=load_decisions())`\n",
430 "2. Make decisions:\n",
431 " - `set_decision('r53', 'fm3', 'approve', note='exact source match')`\n",
432 " - `set_decision('bop', 'pi5', 'hold', note='manual lineage check needed')`\n",
433 "3. Emit dry-run patch plan JSON:\n",
434 " - `write_patch_plan()`\n",
435 "\n",
436 "Outputs:\n",
437 "- `reports/2026-03-12-kidlisp-canonical-decisions.json`\n",
438 "- `reports/2026-03-12-kidlisp-canonical-patch-plan.json`\n"
439 ]
440 }
441 ],
442 "metadata": {
443 "kernelspec": {
444 "display_name": "Python 3",
445 "language": "python",
446 "name": "python3"
447 },
448 "language_info": {
449 "name": "python",
450 "version": "3.11.13",
451 "mimetype": "text/x-python",
452 "codemirror_mode": {
453 "name": "ipython",
454 "version": 3
455 },
456 "pygments_lexer": "ipython3",
457 "nbconvert_exporter": "python",
458 "file_extension": ".py"
459 }
460 },
461 "nbformat": 4,
462 "nbformat_minor": 5
463}