story-to-pack: add the corpora and the catalogue v2/v3

Fourteen corpora split and classified: aesop, bierce, chekhov, holmes,
keefe, lawson, lorimer, maupassant, nobody, plaintales, poe, torchy,
wallingford and winesburg, each with its splitter and the hand-written
groups and pages; catalogue v2 and v3; and the shared splitters
gutenberg_chunks.py, se_split.py and se_build.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019rwKTmug58sEsJ72AuWsEi
This commit is contained in:
JesseMarkowitz
2026-10-07 06:05:51 -04:00
co-authored by Claude Opus 5.5
parent e5617b86ba
commit 3a4758172d
566 changed files with 303901 additions and 0 deletions
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Render groups.json + records.json + options_by_entry.json as one self-contained page: REFERENCE.html."""
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())}
stories = json.loads((here.parent / 'stories.json').read_text())
obe = json.loads((here / 'options_by_entry.json').read_text())
new = obe.pop('_new_entries')
e = html.escape
SRC = {'archetype': 'archetype', 'filled': 'filled gap'}
def src(o):
if o['source'].startswith('scene:'):
n = o['source'].split(':')[1]
return f'from fable {n}' + ('' if o['in_group'] else ' <span class="warn">(outside this group)</span>')
return SRC[o['source']]
def moves(m): return ' '.join(f'<code>{e(k)} {e(v)}</code>' for k, v in m.items())
def scene(c):
r = recs[c]
moral = f'<br><i>Moral:</i> {e(r["moral"])}' if r['moral'] else ''
return (f'<details><summary><b>{c}</b> · <i>{e(stories[r["story"]]["title"])}</i> — {e(r["predicament"])}</summary>'
f'<p class="sum">{e(r["summary"])}{moral}</p></details>')
def section(g):
tag = ' <span class="warn">small group, below the 13-fable rule</span>' if g['small'] else ''
return f'''<section><h2>{g["id"]} · {e(g["name"])}{tag}</h2>
<p class="pred">{e(g["predicament"])}</p>
<p class="meta">{e(g["sentence"])} · {len(g["members"])} fables · stage <b>{g["stage"]}</b></p>
<h3>Options</h3><ol>{"".join(f"<li>{e(o['text'])} <span class='src'>{src(o)}</span><br>{moves(o['moves'])}</li>" for o in g["options"])}</ol>
<h3>Fables</h3>{"".join(scene(m["chunk"]) for m in g["members"])}</section>'''
big = ''.join(section(g) for g in d['groups'] if not g['small'])
small = ''.join(section(g) for g in d['groups'] if g['small'])
un = ''.join(scene(c) for c in d['unassigned'])
def entry(k, v):
name = new[k]['name'] + ' <span class="src">(new)</span>' if k in new else e(k)
rows = ''.join(f'<li>{e(o["text"])} {moves(o["moves"])} <span class="src">fable {o["source"].split(":")[1]} · '
f'{"<b>stated</b>" if o["stated"] else "inferred"}</span><br><span class="sum">“{e(o["evidence"])}”</span></li>'
for o in v)
return f'<details><summary><b>{name}</b> — {len(v)} options, {sum(o["stated"] for o in v)} stated</summary><ul>{rows}</ul></details>'
opts = [o for v in obe.values() for o in v]
meas = ''.join(entry(k, v) for k, v in obe.items())
page = f'''<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Aesop Reference Groups</title>
<style>
:root{{--bg:#fbfaf7;--fg:#1d1d1b;--mute:#6b6760;--line:#e2ded6;--acc:#2f5d8a;--warn:#a4481c}}
@media (prefers-color-scheme:dark){{:root:not([data-theme="light"]){{--bg:#191917;--fg:#e8e6e1;--mute:#a09c94;--line:#34332f;--acc:#8ab4dc;--warn:#e6915f}}}}
:root[data-theme="dark"]{{--bg:#191917;--fg:#e8e6e1;--mute:#a09c94;--line:#34332f;--acc:#8ab4dc;--warn:#e6915f}}
body{{background:var(--bg);color:var(--fg);font:16px/1.5 Georgia,serif;max-width:52rem;margin:0 auto;padding:1rem 16px 4rem}}
h1{{font-size:1.5rem}} h2{{font-size:1.15rem;margin:0 0 .3rem}} h3{{font-size:.8rem;text-transform:uppercase;letter-spacing:.06em;color:var(--mute);margin:.9rem 0 .3rem}}
section{{border-top:1px solid var(--line);padding:1.2rem 0}} .pred{{font-size:1.05rem;margin:.2rem 0}}
.meta,.src,.sum{{color:var(--mute);font-size:.88rem}} .warn{{color:var(--warn);font-size:.85rem}}
code{{font:12px ui-monospace,monospace;border:1px solid var(--line);border-radius:3px;padding:0 .3em;margin-right:.2em;overflow-wrap:anywhere}}
li{{margin:.35rem 0}} details{{margin:.2rem 0}} summary{{cursor:pointer}} .sum{{margin:.2rem 0 .5rem 1.2rem}}
</style></head><body>
<h1>Aesop's Fables: reference groups</h1>
<p class="meta"><i>Three Hundred Aesop's Fables</i>, tr. George Fyler Townsend, 1867 (Project Gutenberg). 313 fables, one scene
per fable. Summaries, predicaments, groups, options and the options measurement written by Claude reading every fable:
no inference of any kind. A comparison baseline, not a pack. Option moves give a direction only, never a number.</p>
{big}
<section><h2>Small groups (5-12 fables)</h2><p class="meta">Situations that recur, but below the 13-fable rule this corpus's size sets.</p></section>
{small}
<section><h2>Unassigned ({len(d["unassigned"])})</h2><p class="meta">Fables that fit no situation recurring in five or more fables.</p>{un}</section>
<section><h2>Fables as an option source</h2>
<p class="meta">{len(opts)} options for {len(obe)} catalogue situations, {sum(o["stated"] for o in opts)} of them
<b>stated</b>: the fable's own outcome or moral shows where every move goes. The rest are inferred.</p>{meas}</section>
</body></html>'''
(here / 'REFERENCE.html').write_text(page, encoding='utf-8')
print('wrote', here / 'REFERENCE.html', len(page), 'bytes')