#!/usr/bin/env python3 """Render groups.json + records.json + options_by_entry.json as one self-contained page: REFERENCE.html. Jacobs' page (../../jacobs/claude/page.py) with each scene's advice shown, 'advice:N' option sources, and a second part for the pilot: the options this advice book supplies for catalogue v2 entries.""" import html, json, pathlib here = pathlib.Path(__file__).parent d = json.loads((here / 'groups.json').read_text()) recs = {r['chunk']: r for r in json.loads((here / 'records.json').read_text())} obe = json.loads((here / 'options_by_entry.json').read_text()) new_defs = obe.pop('_new_entries') stories = json.loads((here.parent / 'stories.json').read_text()) cat = {e['id']: e for e in json.loads((here.parent.parent / 'catalogue/v2/catalogue.json').read_text())['entries']} e = html.escape SRC = {'archetype': 'archetype', 'filled': 'filled gap'} def src(o): kind, _, n = o['source'].partition(':') if kind in ('scene', 'advice'): label = 'from scene' if kind == 'scene' else 'advice in scene' return f'{label} {n}' + ('' if o['in_group'] else ' (outside this group)') return SRC[o['source']] def moves(m): return ' '.join(f'{e(k)} {e(v)}' for k, v in m.items()) def advice(a): return (f'
  • {a["stance"]} {e(a["move"])} → {e(a["outcome"])}' f'
    {e(a["quote"])}
  • ') def scene(c): r = recs[c] adv = f'' if r['advice'] else '' return (f'
    {c} · {e(stories[r["story"]]["title"].split(":")[0])} — {e(r["predicament"])}' f'

    {e(r["summary"])}

    {adv}
    ') out = [] for g in d['groups']: out.append(f'''

    {g["id"]} · {e(g["name"])}

    {e(g["predicament"])}

    {e(g["sentence"])} · {len(g["members"])} scenes from {len(g["stories"])} letters ({e(", ".join(t.split(":")[0] for t in g["story_titles"]))}) · stage {g["stage"]}

    Options

      {"".join(f"
    1. {e(o['text'])} {src(o)}
      {moves(o['moves'])}
    2. " for o in g["options"])}

    Scenes

    {"".join(scene(m["chunk"]) for m in g["members"])}
    ''') un = ''.join(scene(c) for c in d['unassigned']) def entry(k, opts): if k.startswith('new:'): head, dfn = f'{k} (proposed)', new_defs[k[4:]] else: head, dfn = f'{k} · {e(cat[k]["name"])}', cat[k]['definition'] n = sum(o['stated'] for o in opts) rows = ''.join(f'
  • {e(o["text"])} {e(o["source"])} · ' f'{"stated" if o["stated"] else "inferred"}
    {moves(o["moves"])}' f'
    {e(o["evidence"])}
  • ' for o in opts) return f'
    {head} — {len(opts)} options, {n} stated

    {e(dfn)}

      {rows}
    ' allopts = [o for v in obe.values() for o in v] pilot = ''.join(entry(k, v) for k, v in obe.items()) page = f''' Lorimer Reference Groups

    Letters from a Self-Made Merchant: reference groups and advice pilot

    George Horace Lorimer, 1902 (Project Gutenberg #21959). 20 letters, 186 scenes cut by the same rule as chunk.py. Summaries, predicaments, advice, groups and options written by Claude by reading every scene: no inference. A comparison baseline, not a pack. Option moves give a direction only, never a number.

    {"".join(out)}

    Unassigned ({len(d["unassigned"])})

    Scenes that fit no situation recurring in three or more letters. Scenes 179 to 185 are the publisher's advertisements, left in by the split.

    {un}

    Pilot: options by catalogue entry

    {len(allopts)} options from the book's advice, {sum(o["stated"] for o in allopts)} of them with every direction stated by the quoted text. {sum(not k.startswith("new:") for k in obe)} catalogue v2 entries receive options; {sum(k.startswith("new:") for k in obe)} proposed new entries hold the rest.

    {pilot}
    ''' (here / 'REFERENCE.html').write_text(page, encoding='utf-8') print('wrote', here / 'REFERENCE.html', len(page), 'bytes')