#!/usr/bin/env python3 """Extract the Stackchain Magazine photo catalog into a normalized table (CSV + JSON). Source: /root/stackchain_catalog.md (the 'vee-supply-2026-09-21' magazine transcription) Read the whole file here; the result is evidence, not a claim. """ import csv import json import os import re SRC = "/root/stackchain_catalog.md" OUT_DIR = "/root/.hermes/wizard-outputs/ledger" OS = os.path.join(OUT_DIR, "stackchain_catalog.csv") JS = os.path.join(OUT_DIR, "stackchain_catalog.json") with open(SRC,encoding="utf-8") as f: raw = f.read() # Split into entry blocks on '## N. —
' headers. entry_re = re.compile(r"^## (\d+)\. (\S+\.\S+) — (.+)$", re.M) parts = entry_re.split(raw) # parts: [head, num, fname, title, body, num, fname, title, body, ...] entries = [] for i in range(1, len(parts), 4): num = parts[i] fname = parts[i+1] title = parts[i+2] body = parts[i+3] # Type m = re.search(r"^\s*-\s*\*\*Type:\*\*\s*(.*)$", body, re.M) typ = m.group(1).strip() if m else "" # Text: capture the first fenced code block (the exact transcription) tm = re.search(r"```(.*?)```", body, re.S) text_block = tm.group(1) if tm else "" text_lines = [ln.strip() for ln in text_block.splitlines() if ln.strip()] text_preview = (text_lines[0][:160] + ("…" if len(text_lines[0]) > 160 else "")) if text_lines else "" # Identifiers (remainder of the Identifiers line) im = re.search(r"^\s*-\s*\*\*Identifiers:\*\*\s*(.*)$", body, re.M) ident = im.group(1).strip() if im else "" entries.append({ "id": int(num), "filename": fname, "section": title.strip(), "type": typ, "text_preview": text_preview, "identifiers": ident, }) os.makedirs(OUT_DIR, exist_ok=True) with open(OS, "w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=["id","filename","section","type","text_preview","identifiers"]) w.writeheader() for e in entries: w.writerow(e) with open(JS, "w", encoding="utf-8") as f: json.dump(entries, f, indent=2, ensure_ascii=False) print("entries:", len(entries)) for e in entries: print(f"{e['id']:>2} | {e['filename']:<24} | {e['section']:<28} | {e['type']:<28} | {e['text_preview'][:60]!r}")