#!/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'{"".join(advice(a) for a in r["advice"])}
' 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"- {e(o['text'])} {src(o)}
{moves(o['moves'])} " 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')