← Session 10 — Website planning + local dashboard build
thinking (78 chars)
Now the core: research_data.py (read-only parsers), views.py, api.py, urls.py.
Scaffold done. Now the read-only data layer.
tool: TodoWrite ok
input
{
"todos": [
{
"content": "Write RH-WEBSITE.md plan (with DO-NOT-WRITE rule at top)",
"status": "completed",
"activeForm": "Writing RH-WEBSITE.md plan"
},
{
"content": "Install web deps (Django, markdown) + vendor Plotly/MathJax JS",
"status": "completed",
"activeForm": "Installing web dependencies"
},
{
"content": "Scaffold Django project in web/",
"status": "completed",
"activeForm": "Scaffolding Django project"
},
{
"content": "Implement research data parsers (read-only)",
"status": "in_progress",
"activeForm": "Implementing data parsers"
},
{
"content": "Implement views + API endpoints + feedback system",
"status": "pending",
"activeForm": "Implementing views and API"
},
{
"content": "Build templates, CSS, JS (sidebar layout, charts, live polling)",
"status": "pending",
"activeForm": "Building templates and assets"
},
{
"content": "Migrate DB, start server detached, verify pages",
"status": "pending",
"activeForm": "Starting and verifying server"
},
{
"content": "Commit website files (web/ + RH-WEBSITE.md only)",
"status": "pending",
"activeForm": "Committing website files"
}
]
}
result 2026-08-19 17:56:03Z → 2026-08-23 13:55:56Z
{
"oldTodos": [],
"newTodos": [
"content='Write LiSeries.lean \u00a70\u2013\u00a72: seriesTerm/seriesFun defs, norm comparison, tail bound, seriesSummable' status='in_progress' active_form='Writing LiSeries.lean core (defs + summability)'",
"content='\u00a73\u2013\u00a74: finite subtype balls, ball-avoidance at non-zero points, seriesFun analytic off the zeros' status='pending' active_form='Proving analyticity off the zeros'",
"content='\u00a75: isolation at each zero + reduced-series analyticity + polar-part subtraction' status='pending' active_form='Proving isolation + reduced series'",
"content='Iterate lake build to GREEN; root-import LiSeries' status='pending' active_form='Building to GREEN'",
"content='Axiom audit session-028 (3 standard axioms only)' status='pending' active_form='Running axiom audit'",
"content='Session-end protocol: journal-028, STATE.md, NGUYEN.md, db, commit' status='pending' active_form='Closing session 028'"
],
"cleared": false
}
tool: Write ok
input
{
"file_path": "D:\\Python\\Quantum\\web\\dashboard\\research_data.py",
"content": "\"\"\"\nRead-only access layer for the NGUYEN-RH-1 research archive.\n\nHARD RULE (RH-WEBSITE.md \u00a70): every handle opened here is READ-ONLY.\nSQLite is opened with mode=ro; files are opened 'r'. Nothing in this\nmodule may write to REPO_ROOT or RESEARCH_DIR.\n\"\"\"\nimport csv\nimport json\nimport re\nimport sqlite3\nimport subprocess\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\nimport markdown\nfrom django.conf import settings\n\nREPO = Path(settings.REPO_ROOT)\nRESEARCH = Path(settings.RESEARCH_DIR)\nDATA = RESEARCH / \"numerics\" / \"data\"\nDB = RESEARCH / \"research.db\"\n\nMAX_CHART_POINTS = 3000\nPREVIEW_ROWS = 25\n\n\n# ---------------------------------------------------------------- files\n\ndef read_text(path):\n return Path(path).read_text(encoding=\"utf-8\", errors=\"replace\")\n\n\ndef mtime(path):\n p = Path(path)\n return p.stat().st_mtime if p.exists() else None\n\n\ndef _iso(ts):\n return datetime.fromtimestamp(ts, tz=timezone.utc).isoformat(timespec=\"seconds\")\n\n\n# ---------------------------------------------------------------- sqlite (read-only)\n\ndef _query(sql, params=()):\n con = sqlite3.connect(f\"file:{DB}?mode=ro\", uri=True)\n try:\n con.row_factory = sqlite3.Row\n return [dict(r) for r in con.execute(sql, params)]\n finally:\n con.close()\n\n\ndef get_results():\n return _query(\"SELECT * FROM results ORDER BY id\")\n\n\ndef get_experiments():\n return _query(\"SELECT * FROM experiments ORDER BY id\")\n\n\ndef get_derivations():\n return _query(\"SELECT * FROM derivations ORDER BY id\")\n\n\ndef get_graveyard():\n return _query(\"SELECT * FROM graveyard ORDER BY id\")\n\n\ndef get_sessions():\n return _query(\"SELECT * FROM sessions ORDER BY id\")\n\n\ndef get_result(rid):\n rows = _query(\"SELECT * FROM results WHERE id = ?\", (rid,))\n return rows[0] if rows else None\n\n\ndef get_experiment(eid):\n rows = _query(\"SELECT * FROM experiments WHERE id = ?\", (eid,))\n return rows[0] if rows else None\n\n\n# ---------------------------------------------------------------- STATE.md / NGUYEN.md\n\ndef parse_state():\n \"\"\"Split STATE.md into its '## SECTION' blocks.\"\"\"\n path = REPO / \"STATE.md\"\n if not path.exists():\n return {}\n sections, current = {}, \"_header\"\n for line in read_text(path).splitlines():\n m = re.match(r\"^##\\s+(.*)\", line)\n if m:\n current = m.group(1).strip()\n sections[current] = []\n else:\n sections.setdefault(current, []).append(line)\n return {k: \"\\n\".join(v).strip() for k, v in sections.items()}\n\n\ndef parse_checklist():\n \"\"\"Parse NGUYEN.md \u00a75 phases and their checkbox tasks.\"\"\"\n path = REPO / \"NGUYEN.md\"\n if not path.exists():\n return {\"phases\": [], \"done\": 0, \"partial\": 0, \"open\": 0}\n text = read_text(path)\n m = re.search(r\"## 5\\..*?(?=\\n## 6\\.)\", text, re.S)\n section = m.group(0) if m else text\n\n phases, phase = [], None\n totals = {\"done\": 0, \"partial\": 0, \"open\": 0}\n for line in section.splitlines():\n pm = re.match(r\"###\\s+(\u2705|\u2b1c|\ud83d\udd36)?\\s*(PHASE\\s+\\d+.*)\", line)\n if pm:\n phase = {\"name\": pm.group(2).strip(), \"marker\": pm.group(1) or \"\",\n \"done\": 0, \"partial\": 0, \"open\": 0}\n phases.append(phase)\n continue\n tm = re.match(r\"- \\[(x|~| )\\]\", line)\n if tm:\n key = {\"x\": \"done\", \"~\": \"partial\", \" \": \"open\"}[tm.group(1)]\n totals[key] += 1\n if phase is not None:\n phase[key] += 1\n return {\"phases\": phases, **totals}\n\n\n# ---------------------------------------------------------------- markdown\n\n_MATH_RE = re.compile(\n r\"(\\$\\$.*?\\$\\$|\\\\\\[.*?\\\\\\]|\\\\\\(.*?\\\\\\)|\\$[^$\\n]+?\\$)\", re.S)\n\n\ndef render_markdown(text):\n \"\"\"Render markdown, protecting $...$ math spans for MathJax.\"\"\"\n store = []\n\n def stash(m):\n store.append(m.group(0))\n return f\"@@MATH{len(store) - 1}@@\"\n\n protected = _MATH_RE.sub(stash, text)\n html = markdown.markdown(\n protected, extensions=[\"tables\", \"fenced_code\", \"sane_lists\"])\n for i, s in enumerate(store):\n html = html.replace(f\"@@MATH{i}@@\", s)\n return html\n\n\n# ---------------------------------------------------------------- markdown collections\n\nMD_COLLECTIONS = {\n \"results\": RESEARCH / \"results\",\n \"derivations\": RESEARCH / \"derivations\",\n \"graveyard\": RESEARCH / \"graveyard\",\n \"journal\": RESEARCH / \"journal\",\n \"resources\": RESEARCH / \"resources\",\n}\n\n_SAFE_NAME = re.compile(r\"^[\\w\\-.()\u2013\u2014' \u039e\u03c0\u03b6\u03bb\u03b8\u03b3=,]+$\")\n\n\ndef collection_list(kind):\n folder = MD_COLLECTIONS[kind]\n out = []\n for p in sorted(folder.glob(\"*.md\")):\n if p.name == \"index.md\":\n continue\n out.append({\n \"name\": p.stem,\n \"file\": p.name,\n \"title\": _first_heading(p) or p.stem,\n \"mtime\": _iso(p.stat().st_mtime),\n })\n return out\n\n\ndef _first_heading(path):\n try:\n for line in read_text(path).splitlines():\n if line.startswith(\"# \"):\n return line[2:].strip()\n if line.strip():\n break\n except OSError:\n pass\n return None\n\n\ndef collection_item(kind, name):\n \"\"\"Render one markdown file from a collection. Path-traversal safe.\"\"\"\n folder = MD_COLLECTIONS[kind]\n if not _SAFE_NAME.match(name):\n return None\n path = (folder / (name + \".md\")).resolve()\n if folder.resolve() not in path.parents or not path.exists():\n return None\n return {\n \"name\": name,\n \"title\": _first_heading(path) or name,\n \"html\": render_markdown(read_text(path)),\n \"mtime\": _iso(path.stat().st_mtime),\n }\n\n\n# ---------------------------------------------------------------- experiments\n\ndef experiment_sidecar(exp_row):\n \"\"\"Load the JSON metadata sidecar for an experiment row.\"\"\"\n jf = exp_row.get(\"json_file\")\n if not jf:\n return None\n path = (REPO / jf).resolve()\n if REPO.resolve() not in path.parents or not path.exists():\n return None\n try:\n return json.loads(read_text(path))\n except (json.JSONDecodeError, OSError):\n return None\n\n\ndef _csv_path(exp_row):\n cf = exp_row.get(\"csv_file\")\n if not cf:\n return None\n path = (REPO / cf).resolve()\n if REPO.resolve() not in path.parents or not path.exists():\n return None\n return path\n\n\ndef csv_preview(exp_row, n=PREVIEW_ROWS):\n path = _csv_path(exp_row)\n if not path:\n return None\n with open(path, newline=\"\", encoding=\"utf-8\", errors=\"replace\") as fh:\n rows = list(csv.reader(fh))\n if not rows:\n return None\n return {\"header\": rows[0], \"rows\": rows[1:n + 1],\n \"total\": max(0, len(rows) - 1)}\n\n\n# Chart configs: which CSV columns to plot per experiment.\n# y may be a list of column names; log_y switches the y axis to log scale.\nCHARTS = {\n \"E-002\": {\"title\": \"First 10\u2074 zeta zero ordinates \u03b3\u2099\",\n \"x\": \"n\", \"y\": [\"gamma_n\"]},\n \"E-008\": {\"title\": \"Li coefficients \u03bb\u2099 (zero side, n \u2264 2000)\",\n \"x\": \"n\", \"y\": [\"lambda_n\"]},\n \"E-009b\": {\"title\": \"Galerkin spectrum \u03bb\u2c7c of Suzuki A_a (log scale)\",\n \"x\": \"a\", \"y\": [\"lambda_j\"], \"log_y\": True},\n \"E-011\": {\"title\": \"Li coefficients \u03bb\u2099 (arithmetic side, n \u2264 2000)\",\n \"x\": \"n\", \"y\": [\"lambda_n\"]},\n \"E-015\": {\"title\": \"Li coefficients \u03bb\u2099 (arithmetic side, n \u2264 3000)\",\n \"x\": \"n\", \"y\": [\"lambda_n\"]},\n \"E-018\": {\"title\": \"Descent profile |M| / |R\u2080| (log scale)\",\n \"x\": \"x\", \"y\": [\"ratio_M_R0\"], \"log_y\": True},\n \"E-021\": {\"title\": \"S\u221e(n) and S_f(n) curves\",\n \"x\": \"n\", \"y\": [\"S_inf\", \"S_f\"]},\n \"E-023\": {\"title\": \"Margin curve: traced vs measured constants\",\n \"x\": \"n\", \"y\": [\"margin_traced\", \"margin_measured\"]},\n}\n\n\ndef chart_data(exp_row):\n \"\"\"Return Plotly-ready series for an experiment, or None.\"\"\"\n cfg = CHARTS.get(exp_row[\"id\"])\n path = _csv_path(exp_row)\n if not path:\n return None\n with open(path, newline=\"\", encoding=\"utf-8\", errors=\"replace\") as fh:\n reader = csv.DictReader(fh)\n fields = reader.fieldnames or []\n if cfg is None:\n nums = _numeric_columns(path, fields)\n if len(nums) < 2:\n return None\n cfg = {\"title\": \"Data plot\", \"x\": nums[0], \"y\": nums[1:3]}\n if cfg[\"x\"] not in fields:\n return None\n cols = {c: [] for c in [cfg[\"x\"], *cfg[\"y\"]]}\n for row in reader:\n for c in cols:\n cols[c].append(row.get(c, \"\"))\n\n n = len(cols[cfg[\"x\"]])\n step = max(1, n // MAX_CHART_POINTS)\n traces = []\n for col in cfg[\"y\"]:\n traces.append({\n \"name\": col,\n \"x\": _to_floats(cols[cfg[\"x\"]][::step]),\n \"y\": _to_floats(cols[col][::step]),\n })\n return {\"title\": cfg.get(\"title\", \"Data plot\"),\n \"traces\": traces,\n \"log_y\": bool(cfg.get(\"log_y\")),\n \"points\": n, \"shown\": len(traces[0][\"x\"]) if traces else 0}\n\n\ndef _numeric_columns(path, fields):\n with open(path, newline=\"\", encoding=\"utf-8\", errors=\"replace\") as fh:\n reader = csv.DictReader(fh)\n sample = [row for _, row in zip(range(50), reader)]\n nums = []\n for f in fields:\n if sample and all(_is_float(r.get(f, \"\")) for r in sample if r.get(f) != \"\"):\n nums.append(f)\n return nums\n\n\ndef _is_float(s):\n try:\n float(s)\n return True\n except (TypeError, ValueError):\n return False\n\n\ndef _to_floats(values):\n out = []\n for v in values:\n try:\n out.append(float(v))\n except (TypeError, ValueError):\n out.append(None)\n return out\n\n\n# ---------------------------------------------------------------- live runs / git\n\ndef run_logs():\n out = []\n for p in sorted(DATA.glob(\"*-run.log\")):\n st = p.stat()\n out.append({\"name\": p.name, \"size\": st.st_size,\n \"mtime\": st.st_mtime, \"mtime_iso\": _iso(st.st_mtime)})\n return out\n\n\n_LOG_NAME = re.compile(r\"^[\\w-]+-run\\.log$\")\n\n\ndef tail_log(name, n=200):\n \"\"\"Last n lines of a run log. Whitelisted to *-run.log inside DATA.\"\"\"\n if not _LOG_NAME.match(name):\n return None\n path = (DATA / name).resolve()\n if DATA.resolve() not in path.parents or not path.exists():\n return None\n lines = read_text(path).splitlines()\n return {\"name\": name, \"lines\": lines[-n:], \"total\": len(lines),\n \"mtime\": _iso(path.stat().st_mtime)}\n\n\ndef git_log(n=12):\n try:\n out = subprocess.run(\n [\"git\", \"-C\", str(REPO), \"log\", \"--oneline\", \"-n\", str(n)],\n capture_output=True, text=True, timeout=10)\n return [l for l in out.stdout.splitlines() if l.strip()]\n except (OSError, subprocess.SubprocessError):\n return []\n\n\ndef summary_stats():\n return {\n \"results\": len(get_results()),\n \"experiments\": len(get_experiments()),\n \"derivations\": len(get_derivations()),\n \"graveyard\": len(get_graveyard()),\n \"sessions\": len(get_sessions()),\n \"checklist\": parse_checklist(),\n }\n"
}
result 2026-08-19 17:56:03Z → 2026-08-27 05:07:19Z
{
"type": "create",
"filePath": "D:\\Python\\Quantum\\research\\paper\\build_letter_docx.py",
"content": "# build_letter_docx.py \u2014 one-off: Letter_To_Professor_Lagarias.txt -> formal letter .docx\n# Reuses the LaTeX->OMML machinery from build_note_docx.py.\nimport re\nimport sys\nfrom pathlib import Path\n\nfrom docx import Document\n\nsys.path.insert(0, str(Path(__file__).parent))\nfrom build_note_docx import add_display, add_rich, flush_para # noqa: E402\n\nSRC = Path(r\"D:\\Python\\Quantum\\research\\paper\\Letter_To_Professor_Lagarias.txt\")\nOUT = SRC.with_suffix(\".docx\")\n\nLETTER_DATE = \"August 27, 2026\"\nSENDER = [\"Dan T. Nguyen\", \"Independent researcher\"]\nRECIPIENT = [\"Professor Jeffrey C. Lagarias\",\n \"Department of Mathematics\",\n \"University of Michigan\",\n \"Ann Arbor, MI 48109\"]\n\n\ndef block_para(doc, lines, space_before=None):\n p = doc.add_paragraph()\n if space_before is not None:\n p.paragraph_format.space_before = space_before\n for i, ln in enumerate(lines):\n if i:\n p.add_run().add_break()\n p.add_run(ln)\n return p\n\n\ndef main():\n doc = Document()\n doc.core_properties.title = \"Letter to Professor Lagarias\"\n doc.core_properties.author = \"Dan T. Nguyen\"\n\n # --- formal letter head ---\n block_para(doc, SENDER)\n block_para(doc, [LETTER_DATE]).paragraph_format.space_before = __import__(\n \"docx\").shared.Pt(18)\n block_para(doc, RECIPIENT).paragraph_format.space_before = __import__(\n \"docx\").shared.Pt(18)\n doc.add_paragraph() # blank line before salutation\n\n lines = SRC.read_text(encoding=\"utf-8\").splitlines()\n\n # salutation (source line 1)\n doc.add_paragraph(lines[0].strip())\n\n # body: after salutation up to \"Sincerely,\"\n closing_idx = next(i for i, ln in enumerate(lines)\n if ln.strip() == \"Sincerely,\")\n body = lines[2:closing_idx]\n\n para, i, nmath = [], 0, 0\n while i < len(body):\n s = body[i].strip()\n if s == \"$$\":\n flush_para(doc, para)\n para = []\n blk, i = [], i + 1\n while i < len(body) and body[i].strip() != \"$$\":\n blk.append(body[i].strip())\n i += 1\n i += 1\n add_display(doc, \" \".join(blk))\n nmath += 1\n continue\n if re.match(r\"^\\d+\\.\\s\", s):\n flush_para(doc, para)\n para = []\n add_rich(doc.add_paragraph(style=\"List Number\"),\n re.sub(r\"^\\d+\\.\\s*\", \"\", s))\n elif s == \"\":\n flush_para(doc, para)\n para = []\n else:\n para.append(body[i])\n i += 1\n flush_para(doc, para)\n\n # --- closing ---\n doc.add_paragraph(\"Sincerely,\")\n doc.add_paragraph()\n doc.add_paragraph()\n sig = doc.add_paragraph()\n for j, ln in enumerate([ln.strip() for ln in lines[closing_idx + 1:]\n if ln.strip()]):\n if j:\n sig.add_run().add_break()\n add_rich(sig, ln)\n\n doc.save(OUT)\n print(f\"saved {OUT} ({nmath} display equations)\")\n\n bad = 0\n chk = Document(str(OUT))\n for p in chk.paragraphs:\n t = \"\".join(r.text for r in p.runs)\n if \"\\\\(\" in t or \"\\\\frac\" in t or \"\\\\tau\" in t:\n print(f\" !! RAW LATEX LEFT: {t[:70]}\")\n bad += 1\n print(\"verification: clean\" if bad == 0 else f\"verification: {bad} problems\")\n\n\nif __name__ == \"__main__\":\n main()\n",
"structuredPatch": [],
"originalFile": null
}
raw content_json (database row)
[{"type":"thinking","thinking":"Now the core: research_data.py (read-only parsers), views.py, api.py, urls.py.","signature":""},{"type":"text","text":"Scaffold done. Now the read-only data layer."},{"type":"tool_use","id":"TodoWrite_33","name":"TodoWrite","input":{"todos":[{"content":"Write RH-WEBSITE.md plan (with DO-NOT-WRITE rule at top)","status":"completed","activeForm":"Writing RH-WEBSITE.md plan"},{"content":"Install web deps (Django, markdown) + vendor Plotly/MathJax JS","status":"completed","activeForm":"Installing web dependencies"},{"content":"Scaffold Django project in web/","status":"completed","activeForm":"Scaffolding Django project"},{"content":"Implement research data parsers (read-only)","status":"in_progress","activeForm":"Implementing data parsers"},{"content":"Implement views + API endpoints + feedback system","status":"pending","activeForm":"Implementing views and API"},{"content":"Build templates, CSS, JS (sidebar layout, charts, live polling)","status":"pending","activeForm":"Building templates and assets"},{"content":"Migrate DB, start server detached, verify pages","status":"pending","activeForm":"Starting and verifying server"},{"content":"Commit website files (web/ + RH-WEBSITE.md only)","status":"pending","activeForm":"Committing website files"}]},"extra_content":null},{"type":"tool_use","id":"Write_34","name":"Write","input":{"file_path":"D:\\Python\\Quantum\\web\\dashboard\\research_data.py","content":"\"\"\"\nRead-only access layer for the NGUYEN-RH-1 research archive.\n\nHARD RULE (RH-WEBSITE.md §0): every handle opened here is READ-ONLY.\nSQLite is opened with mode=ro; files are opened 'r'. Nothing in this\nmodule may write to REPO_ROOT or RESEARCH_DIR.\n\"\"\"\nimport csv\nimport json\nimport re\nimport sqlite3\nimport subprocess\nfrom datetime import datetime, timezone\nfrom pathlib import Path\n\nimport markdown\nfrom django.conf import settings\n\nREPO = Path(settings.REPO_ROOT)\nRESEARCH = Path(settings.RESEARCH_DIR)\nDATA = RESEARCH / \"numerics\" / \"data\"\nDB = RESEARCH / \"research.db\"\n\nMAX_CHART_POINTS = 3000\nPREVIEW_ROWS = 25\n\n\n# ---------------------------------------------------------------- files\n\ndef read_text(path):\n return Path(path).read_text(encoding=\"utf-8\", errors=\"replace\")\n\n\ndef mtime(path):\n p = Path(path)\n return p.stat().st_mtime if p.exists() else None\n\n\ndef _iso(ts):\n return datetime.fromtimestamp(ts, tz=timezone.utc).isoformat(timespec=\"seconds\")\n\n\n# ---------------------------------------------------------------- sqlite (read-only)\n\ndef _query(sql, params=()):\n con = sqlite3.connect(f\"file:{DB}?mode=ro\", uri=True)\n try:\n con.row_factory = sqlite3.Row\n return [dict(r) for r in con.execute(sql, params)]\n finally:\n con.close()\n\n\ndef get_results():\n return _query(\"SELECT * FROM results ORDER BY id\")\n\n\ndef get_experiments():\n return _query(\"SELECT * FROM experiments ORDER BY id\")\n\n\ndef get_derivations():\n return _query(\"SELECT * FROM derivations ORDER BY id\")\n\n\ndef get_graveyard():\n return _query(\"SELECT * FROM graveyard ORDER BY id\")\n\n\ndef get_sessions():\n return _query(\"SELECT * FROM sessions ORDER BY id\")\n\n\ndef get_result(rid):\n rows = _query(\"SELECT * FROM results WHERE id = ?\", (rid,))\n return rows[0] if rows else None\n\n\ndef get_experiment(eid):\n rows = _query(\"SELECT * FROM experiments WHERE id = ?\", (eid,))\n return rows[0] if rows else None\n\n\n# ---------------------------------------------------------------- STATE.md / NGUYEN.md\n\ndef parse_state():\n \"\"\"Split STATE.md into its '## SECTION' blocks.\"\"\"\n path = REPO / \"STATE.md\"\n if not path.exists():\n return {}\n sections, current = {}, \"_header\"\n for line in read_text(path).splitlines():\n m = re.match(r\"^##\\s+(.*)\", line)\n if m:\n current = m.group(1).strip()\n sections[current] = []\n else:\n sections.setdefault(current, []).append(line)\n return {k: \"\\n\".join(v).strip() for k, v in sections.items()}\n\n\ndef parse_checklist():\n \"\"\"Parse NGUYEN.md §5 phases and their checkbox tasks.\"\"\"\n path = REPO / \"NGUYEN.md\"\n if not path.exists():\n return {\"phases\": [], \"done\": 0, \"partial\": 0, \"open\": 0}\n text = read_text(path)\n m = re.search(r\"## 5\\..*?(?=\\n## 6\\.)\", text, re.S)\n section = m.group(0) if m else text\n\n phases, phase = [], None\n totals = {\"done\": 0, \"partial\": 0, \"open\": 0}\n for line in section.splitlines():\n pm = re.match(r\"###\\s+(✅|⬜|🔶)?\\s*(PHASE\\s+\\d+.*)\", line)\n if pm:\n phase = {\"name\": pm.group(2).strip(), \"marker\": pm.group(1) or \"\",\n \"done\": 0, \"partial\": 0, \"open\": 0}\n phases.append(phase)\n continue\n tm = re.match(r\"- \\[(x|~| )\\]\", line)\n if tm:\n key = {\"x\": \"done\", \"~\": \"partial\", \" \": \"open\"}[tm.group(1)]\n totals[key] += 1\n if phase is not None:\n phase[key] += 1\n return {\"phases\": phases, **totals}\n\n\n# ---------------------------------------------------------------- markdown\n\n_MATH_RE = re.compile(\n r\"(\\$\\$.*?\\$\\$|\\\\\\[.*?\\\\\\]|\\\\\\(.*?\\\\\\)|\\$[^$\\n]+?\\$)\", re.S)\n\n\ndef render_markdown(text):\n \"\"\"Render markdown, protecting $...$ math spans for MathJax.\"\"\"\n store = []\n\n def stash(m):\n store.append(m.group(0))\n return f\"@@MATH{len(store) - 1}@@\"\n\n protected = _MATH_RE.sub(stash, text)\n html = markdown.markdown(\n protected, extensions=[\"tables\", \"fenced_code\", \"sane_lists\"])\n for i, s in enumerate(store):\n html = html.replace(f\"@@MATH{i}@@\", s)\n return html\n\n\n# ---------------------------------------------------------------- markdown collections\n\nMD_COLLECTIONS = {\n \"results\": RESEARCH / \"results\",\n \"derivations\": RESEARCH / \"derivations\",\n \"graveyard\": RESEARCH / \"graveyard\",\n \"journal\": RESEARCH / \"journal\",\n \"resources\": RESEARCH / \"resources\",\n}\n\n_SAFE_NAME = re.compile(r\"^[\\w\\-.()–—' Ξπζλθγ=,]+$\")\n\n\ndef collection_list(kind):\n folder = MD_COLLECTIONS[kind]\n out = []\n for p in sorted(folder.glob(\"*.md\")):\n if p.name == \"index.md\":\n continue\n out.append({\n \"name\": p.stem,\n \"file\": p.name,\n \"title\": _first_heading(p) or p.stem,\n \"mtime\": _iso(p.stat().st_mtime),\n })\n return out\n\n\ndef _first_heading(path):\n try:\n for line in read_text(path).splitlines():\n if line.startswith(\"# \"):\n return line[2:].strip()\n if line.strip():\n break\n except OSError:\n pass\n return None\n\n\ndef collection_item(kind, name):\n \"\"\"Render one markdown file from a collection. Path-traversal safe.\"\"\"\n folder = MD_COLLECTIONS[kind]\n if not _SAFE_NAME.match(name):\n return None\n path = (folder / (name + \".md\")).resolve()\n if folder.resolve() not in path.parents or not path.exists():\n return None\n return {\n \"name\": name,\n \"title\": _first_heading(path) or name,\n \"html\": render_markdown(read_text(path)),\n \"mtime\": _iso(path.stat().st_mtime),\n }\n\n\n# ---------------------------------------------------------------- experiments\n\ndef experiment_sidecar(exp_row):\n \"\"\"Load the JSON metadata sidecar for an experiment row.\"\"\"\n jf = exp_row.get(\"json_file\")\n if not jf:\n return None\n path = (REPO / jf).resolve()\n if REPO.resolve() not in path.parents or not path.exists():\n return None\n try:\n return json.loads(read_text(path))\n except (json.JSONDecodeError, OSError):\n return None\n\n\ndef _csv_path(exp_row):\n cf = exp_row.get(\"csv_file\")\n if not cf:\n return None\n path = (REPO / cf).resolve()\n if REPO.resolve() not in path.parents or not path.exists():\n return None\n return path\n\n\ndef csv_preview(exp_row, n=PREVIEW_ROWS):\n path = _csv_path(exp_row)\n if not path:\n return None\n with open(path, newline=\"\", encoding=\"utf-8\", errors=\"replace\") as fh:\n rows = list(csv.reader(fh))\n if not rows:\n return None\n return {\"header\": rows[0], \"rows\": rows[1:n + 1],\n \"total\": max(0, len(rows) - 1)}\n\n\n# Chart configs: which CSV columns to plot per experiment.\n# y may be a list of column names; log_y switches the y axis to log scale.\nCHARTS = {\n \"E-002\": {\"title\": \"First 10⁴ zeta zero ordinates γₙ\",\n \"x\": \"n\", \"y\": [\"gamma_n\"]},\n \"E-008\": {\"title\": \"Li coefficients λₙ (zero side, n ≤ 2000)\",\n \"x\": \"n\", \"y\": [\"lambda_n\"]},\n \"E-009b\": {\"title\": \"Galerkin spectrum λⱼ of Suzuki A_a (log scale)\",\n \"x\": \"a\", \"y\": [\"lambda_j\"], \"log_y\": True},\n \"E-011\": {\"title\": \"Li coefficients λₙ (arithmetic side, n ≤ 2000)\",\n \"x\": \"n\", \"y\": [\"lambda_n\"]},\n \"E-015\": {\"title\": \"Li coefficients λₙ (arithmetic side, n ≤ 3000)\",\n \"x\": \"n\", \"y\": [\"lambda_n\"]},\n \"E-018\": {\"title\": \"Descent profile |M| / |R₀| (log scale)\",\n \"x\": \"x\", \"y\": [\"ratio_M_R0\"], \"log_y\": True},\n \"E-021\": {\"title\": \"S∞(n) and S_f(n) curves\",\n \"x\": \"n\", \"y\": [\"S_inf\", \"S_f\"]},\n \"E-023\": {\"title\": \"Margin curve: traced vs measured constants\",\n \"x\": \"n\", \"y\": [\"margin_traced\", \"margin_measured\"]},\n}\n\n\ndef chart_data(exp_row):\n \"\"\"Return Plotly-ready series for an experiment, or None.\"\"\"\n cfg = CHARTS.get(exp_row[\"id\"])\n path = _csv_path(exp_row)\n if not path:\n return None\n with open(path, newline=\"\", encoding=\"utf-8\", errors=\"replace\") as fh:\n reader = csv.DictReader(fh)\n fields = reader.fieldnames or []\n if cfg is None:\n nums = _numeric_columns(path, fields)\n if len(nums) < 2:\n return None\n cfg = {\"title\": \"Data plot\", \"x\": nums[0], \"y\": nums[1:3]}\n if cfg[\"x\"] not in fields:\n return None\n cols = {c: [] for c in [cfg[\"x\"], *cfg[\"y\"]]}\n for row in reader:\n for c in cols:\n cols[c].append(row.get(c, \"\"))\n\n n = len(cols[cfg[\"x\"]])\n step = max(1, n // MAX_CHART_POINTS)\n traces = []\n for col in cfg[\"y\"]:\n traces.append({\n \"name\": col,\n \"x\": _to_floats(cols[cfg[\"x\"]][::step]),\n \"y\": _to_floats(cols[col][::step]),\n })\n return {\"title\": cfg.get(\"title\", \"Data plot\"),\n \"traces\": traces,\n \"log_y\": bool(cfg.get(\"log_y\")),\n \"points\": n, \"shown\": len(traces[0][\"x\"]) if traces else 0}\n\n\ndef _numeric_columns(path, fields):\n with open(path, newline=\"\", encoding=\"utf-8\", errors=\"replace\") as fh:\n reader = csv.DictReader(fh)\n sample = [row for _, row in zip(range(50), reader)]\n nums = []\n for f in fields:\n if sample and all(_is_float(r.get(f, \"\")) for r in sample if r.get(f) != \"\"):\n nums.append(f)\n return nums\n\n\ndef _is_float(s):\n try:\n float(s)\n return True\n except (TypeError, ValueError):\n return False\n\n\ndef _to_floats(values):\n out = []\n for v in values:\n try:\n out.append(float(v))\n except (TypeError, ValueError):\n out.append(None)\n return out\n\n\n# ---------------------------------------------------------------- live runs / git\n\ndef run_logs():\n out = []\n for p in sorted(DATA.glob(\"*-run.log\")):\n st = p.stat()\n out.append({\"name\": p.name, \"size\": st.st_size,\n \"mtime\": st.st_mtime, \"mtime_iso\": _iso(st.st_mtime)})\n return out\n\n\n_LOG_NAME = re.compile(r\"^[\\w-]+-run\\.log$\")\n\n\ndef tail_log(name, n=200):\n \"\"\"Last n lines of a run log. Whitelisted to *-run.log inside DATA.\"\"\"\n if not _LOG_NAME.match(name):\n return None\n path = (DATA / name).resolve()\n if DATA.resolve() not in path.parents or not path.exists():\n return None\n lines = read_text(path).splitlines()\n return {\"name\": name, \"lines\": lines[-n:], \"total\": len(lines),\n \"mtime\": _iso(path.stat().st_mtime)}\n\n\ndef git_log(n=12):\n try:\n out = subprocess.run(\n [\"git\", \"-C\", str(REPO), \"log\", \"--oneline\", \"-n\", str(n)],\n capture_output=True, text=True, timeout=10)\n return [l for l in out.stdout.splitlines() if l.strip()]\n except (OSError, subprocess.SubprocessError):\n return []\n\n\ndef summary_stats():\n return {\n \"results\": len(get_results()),\n \"experiments\": len(get_experiments()),\n \"derivations\": len(get_derivations()),\n \"graveyard\": len(get_graveyard()),\n \"sessions\": len(get_sessions()),\n \"checklist\": parse_checklist(),\n }\n"},"extra_content":null}]