Replace clustering with a catalogue, and hand-write the references to judge it against
The pipeline's embed-and-cluster step is dead, and this commit holds both the evidence for that and the step proposed to replace it. Predicaments. Scenes are re-described as "what the person is up against", with no names, jobs or places, then embedded and clustered (redescribe.py, topic_words.py, topic_share.py). The pilot chose qwen3:14b over 3b by reading both side by side. Two defects the pilot exposed are fixed: split.py missed titles in quotes and a contents subtitle after a dash, so three stories had been merged into their neighbours, and strip_names.py read New York place names as people. The corrected corpus is probe/v2 (97 stories, 839 scenes); carry_summaries.py reuses the 829 unchanged v1 summaries. Topic share fell from 20% to 13% at k=60, short of the pre-registered 10%. Hand references. Three corpora were read scene by scene and written up by hand, under the same prompt rules the local models get, as a baseline to judge them against: O. Henry (probe/v2/claude, 839 scenes, 20 situations), Wharton's Descent of Man (probe/wharton, 262 scenes, 16 groups) and Jacobs's The Lady of the Barge (probe/jacobs, 157 scenes, 19 groups). Each has its own README and a readable page. No inference was used for any of them. Catalogue. probe/catalogue maps every hand group in the three references onto 36 situation entries, with an answer key per corpus and one recurrence rule applied to all three. classify.py assigns a scene one entry or none, leave-one-corpus- out; score.py checks it against the key, with a self-test on random labels. Why clustering is out: hand-written predicaments, embedded and clustered exactly as the model's were, agree with the hand grouping at ARI 0.05 — no better than the 14B text's 0.07. Better rewriting cannot rescue it. Embeddings cannot even shortlist: the hand label is the nearest entry 13% of the time and in the top 8 half the time. The classification runs are not here. The dev and test runs are pre-registered in probe/catalogue/README.md with the bar set beforehand, and are blocked on the inference host, whose GPU has fallen off the PCIe bus three times. The 30-scene partial output in out/ is not a result. Review page. The situation review is now a browser page rather than JSON edited by hand (review_page.py, review_page_logic.cjs with Node tests, format schema v2). It has never been rendered in a real browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014BygvsUXV9eU6oHkTCkKZ1
This commit is contained in:
co-authored by
Claude Opus 5
parent
fa3769d0fe
commit
e5617b86ba
@@ -0,0 +1,108 @@
|
||||
# Situation catalogue: classify, don't cluster
|
||||
|
||||
Built 2026-09-17 from the three hand references: O. Henry (`../v2/claude/`), Wharton (`../wharton/claude/`)
|
||||
and Jacobs (`../jacobs/claude/`). This is the step that replaces embed-and-cluster in the proposed pipeline:
|
||||
a model assigns each scene's predicament one catalogue entry, or `none`.
|
||||
|
||||
## Files
|
||||
|
||||
| File | What |
|
||||
| --- | --- |
|
||||
| `catalogue_src.py` | The 36 entries (definition, includes, excludes, tone, recurs) and `SOURCES`, which maps every hand group in the three references to exactly one entry. Running it checks the mapping and writes the rest. No inference. |
|
||||
| `catalogue.json` | The entries, with up to three worked examples per corpus, each tagged with its corpus |
|
||||
| `labels-<corpus>.json` | Every scene's catalogue label from the hand grouping, or `none`. This is the answer key for scoring |
|
||||
| `recurrence.log` | One recurrence rule applied to all three: at least 5 scenes, at least max(3, 4% of the stories) stories, and no story over 50% |
|
||||
| `classify.py` | Classifies one scene per request through `../ollama.py`. Leave-one-corpus-out: the examples never come from the corpus being classified. `--k 0` offers the whole catalogue |
|
||||
| `score.py` | Scores classifier output against `labels-*.json`. `--selftest` checks the scorer on hand-vs-hand and on random labels |
|
||||
| `emb/` | Cached nomic-embed-text vectors for the catalogue and the O. Henry 14B predicaments |
|
||||
| `out/` | Classifier output |
|
||||
|
||||
## Results so far
|
||||
|
||||
**1. One rule for all three corpora** (`recurrence.log`). O. Henry keeps 16 recurring situations and
|
||||
Wharton keeps 16. Jacobs falls from its hand-counted 19 to **6**, because its groups had used a two-story
|
||||
minimum. With 12 stories, three stories a group is the least that means "recurs", and most of its
|
||||
groups do not reach it. Only `rival`, `strings` and `newcomer` are found in all three corpora; ten more
|
||||
are in two. A 20-30 story corpus is a real floor.
|
||||
|
||||
**2. Clustering fails even on the best text.** My 839 hand-written O. Henry predicaments were
|
||||
embedded exactly as `embed.py` embedded the 14B ones (`../v2/claude/predicament_embeddings-claude.json`),
|
||||
and the same k = 60 k-means was run on them (`../v2/claude/compare-claude-embeddings.log`). The groups
|
||||
agree with my own hand grouping at **ARI 0.05** (0.05-0.05 over four seeds), no better than the 14B text's
|
||||
0.07. The fault is the embed-and-cluster step, not the predicament text. Better rewriting will not
|
||||
rescue clustering.
|
||||
|
||||
**3. Embeddings cannot even shortlist.** With the 14B predicaments against the catalogue definitions, the
|
||||
hand label is the nearest entry for 13% of situated scenes, is in the top 8 for 50%, and is in the top 20
|
||||
for 83%. So `classify.py` now offers the whole catalogue (`--k 0`), with the catalogue in the system prompt
|
||||
so the server can reuse the cached prefix.
|
||||
|
||||
**4. Classifier pilot: 40 scenes only, too small to judge** (`pilot40.log`; qwen3:14b, shortlist k = 8,
|
||||
14B predicaments). The model called 34 of 40 scenes situated where the hand labels have 14. Where both
|
||||
called a scene situated, its label was right in 3 of 13 (23%). Kappa was 0.09. The prompt has since been
|
||||
changed: the whole catalogue, a note to expect about half `none`, and a fit rating from 1 to 3 that
|
||||
`score.py --minfit=` can threshold. The changed prompt has not been measured.
|
||||
|
||||
**4b. The changed prompt, on 30 scenes before the host hung** (`out/ohenry-q14-dev1.partial.json`, scored
|
||||
in `pilot40.log`): it tells situated scenes from `none` better (precision 67%, recall 100%, kappa 0.23), but
|
||||
where both call a scene situated, its label is right in only 2 of 14. Reading the confusions, some of its
|
||||
labels are defensible. I labelled a whole story arc with one situation (every scene of *A Lickpenny Lover* is
|
||||
`station`), while the model judges each scene alone (`pretence`, `unspoken`). And the 14B predicaments
|
||||
("A man struggles to reconcile his pride with...") are too vague to classify from. So the next development
|
||||
round should (a) also give the classifier the scene summary, or the predicaments either side, and (b) score
|
||||
at the level a pack uses: which situations recur, with what members, rather than scene-exact agreement.
|
||||
|
||||
## Not done: the inference host's GPU fell off the bus
|
||||
|
||||
At **22:19:07 on 2026-09-17**, the inference host's GPU fell off the PCIe bus (kernel: `Xid 79`, then `Xid 154, GPU Reset
|
||||
Required`). It happened in the middle of a `classify.py` development request whose prompt was about 2,450 tokens (the
|
||||
whole catalogue). Over the ten minutes before, calls had slowed from about 2 s a scene to about 20 s. This is the
|
||||
second such fault; the first was on 2026-09-13. **No power or link loggers were running**, because SSH was refused
|
||||
while the key was out of the agent, so there is no evidence either way on power. After the fault, Ollama carried on
|
||||
on the CPU. That is why nomic-embed-text still answered, and finding 2 was computed on CPU embeddings, which does
|
||||
not change the method. All client processes were stopped. The host needs a reboot (reset required). The NVIDIA
|
||||
driver asks for `nvidia-bug-report.sh` to be run as root before the module is unloaded.
|
||||
|
||||
The prompt design matters for this host: offering the whole catalogue makes every call a long prefill. Either keep
|
||||
the catalogue as a cached prefix (the current `classify.py`, which puts it in the system prompt; untested on the
|
||||
GPU), or shortlist with something better than embeddings, or run with a lower power limit and the loggers on.
|
||||
|
||||
### Third fault, with the loggers running: 2026-09-18 01:00:03
|
||||
|
||||
After a reboot, with the power limit set to 200 W (from 280 W) and all three loggers running, a 20-scene pilot
|
||||
(`classify.py ohenry q14 --limit 20 --tag=-dev1`, catalogue as the cached system prompt) lost the GPU about 15 s in.
|
||||
The logs are in `gpu-logs/*-2026-09-18-0056.*`. From the link/power CSV:
|
||||
|
||||
| Time | Power | Utilisation | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| 00:59:54 | 309.31 W | 49% | enforced limit 200 W; throttle reason 0x4 (SW power cap) |
|
||||
| 00:59:55 to 00:59:59 | about 178 to 202 W | 100% | sustained prefill |
|
||||
| 01:00:02 | 174.96 W | | last good sample |
|
||||
| 01:00:03 | | | "GPU is lost"; dmon shows `-` from here |
|
||||
|
||||
The temperature never went above 39 °C, so the cause is not thermal. The link was PCIe gen 3 x4. The card drew
|
||||
309 W for a sample despite a 200 W cap, and then dropped after about ten seconds of load at the cap. That points to
|
||||
power delivery to the card or the OCuLink dock, not to the prompt or the software. A lower cap did not prevent it.
|
||||
**No more classification runs on that host until its hardware has been looked at.** The pilot output is not a result.
|
||||
|
||||
`gpu-logs/nvidia-bug-report-2026-09-18.log.gz` is the driver's report, taken after the fault. The kernel log in it
|
||||
shows no AER or PCIe error messages at any fault. That is not evidence of a clean link: at boot the kernel logs `_OSC: platform does not support [SHPCHotplug AER LTR DPC]`, so the firmware never gives the OS AER control and link errors would not be reported. There
|
||||
was an `Xid 32` (corrupted push-buffer stream) from Ollama at 00:54:48, five minutes before this drop, and an `Xid 13`
|
||||
graphics exception on 2026-09-13, before the first drop.
|
||||
|
||||
## Next, once the host is healthy
|
||||
|
||||
The plan below is fixed before any of the runs, so they cannot be tuned after the fact.
|
||||
|
||||
- **Development slice:** O. Henry scenes 0-119. Tune the prompt and the `--minfit` threshold only here.
|
||||
(`classify.py ohenry q14 --limit 120 --tag=-dev1`)
|
||||
- **Test:** O. Henry scenes 120-838, run once (`--start 120 --tag=-test`). Score with
|
||||
`score.py out/ohenry-q14-test.json --range=120:839`. Then run the same prompt on the hand-written
|
||||
predicaments (`ohenry claude`) to separate rewrite quality from classification quality. Then run
|
||||
`wharton claude` and `jacobs claude`, which have no local-model predicaments yet, to see how the
|
||||
catalogue transfers.
|
||||
- **What counts as success** is precision per situation, not the number of situations that pass:
|
||||
`--selftest` shows random labels also "pass" all 16. The bar is set before the test run: for the
|
||||
situations both hand and model call recurring, a median model precision of at least 60%.
|
||||
- At about 2 s a scene, O. Henry takes about 30 minutes a text set. That is a long run, so start the
|
||||
loggers first.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,271 @@
|
||||
"""The situation catalogue: a closed list a model classifies scenes against, instead of clustering them.
|
||||
|
||||
Seeded from three hand-made references (O. Henry ../v2/claude, Wharton ../wharton/claude, Jacobs ../jacobs/claude).
|
||||
Every hand group in those references maps to exactly one catalogue entry (SOURCES). Running this file:
|
||||
1. checks the mapping is total and every entry has at least one source,
|
||||
2. writes catalogue.json (entries + worked examples, tagged by corpus so a classifier can leave a corpus out),
|
||||
3. writes labels-<corpus>.json: every scene's catalogue label from the hand grouping, or "none",
|
||||
4. applies ONE recurrence rule to all three corpora and writes recurrence.log.
|
||||
No inference.
|
||||
"""
|
||||
import collections, json, math, pathlib, sys
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
PROBE = HERE.parent
|
||||
|
||||
E = lambda id, name, definition, includes, excludes, tone='any', recurs=True: dict(
|
||||
id=id, name=name, definition=definition, includes=includes, excludes=excludes, tone=tone, recurs=recurs)
|
||||
|
||||
CATALOGUE = [
|
||||
E('rival', 'A rival for the same person',
|
||||
'Someone else wants the person you want, and only one of you can have them.',
|
||||
'suitors competing; fear of losing a partner to someone else; temptation to strike at the rival',
|
||||
'competing for money or a prize that is not a person (your_scheme)'),
|
||||
E('strings', 'Help with strings attached',
|
||||
'Help, money or a chance is offered to you, but only on terms that will cost you something you value.',
|
||||
'family money conditional on obeying them; a backer who now wants something; a windfall with a hidden cost',
|
||||
'the terms require doing something you think wrong (compromise_for_money); a stranger\'s trap (sure_thing)'),
|
||||
E('compromise_for_money', 'Money for going along',
|
||||
'You can have money, success or a meal if you go along with something you think wrong or false.',
|
||||
'selling out your standards for pay; the client wants the flattering or fake version; a compromising offer when you are hungry',
|
||||
'the cost is only inconvenient, not wrong (strings)'),
|
||||
E('sure_thing', 'The sure thing',
|
||||
'A plausible person offers an easy fortune that is probably a trap set for you.',
|
||||
'confidence tricks aimed at you; too-good-to-be-true bargains; strangers who befriend you for your money',
|
||||
'you are the one running the scheme (your_scheme)'),
|
||||
E('your_scheme', 'Your scheme turns on you',
|
||||
'You are running a scheme or quietly tipping a deal your way, and it may be about to turn on you.',
|
||||
'cheating a partner; rigging a contest; a trick coming apart in front of the people you meant to fool',
|
||||
'someone else is scheming against you (sure_thing)'),
|
||||
E('after_your_money', 'People after your money',
|
||||
'You have money, or are thought to, and people around you are suddenly courting it.',
|
||||
'heirs circling; flatterers and hangers-on; pressure to reveal a hidden windfall',
|
||||
'a single stranger\'s con (sure_thing)'),
|
||||
E('past_comes_back', 'The past comes back',
|
||||
'Someone from your past, or your partner\'s, turns up with a claim or a hold that threatens your present life.',
|
||||
'blackmail over an old secret; a former spouse or intimate who still has a claim; an old associate who could expose you',
|
||||
'you simply discover something a person close to you hid (what_they_hid)'),
|
||||
E('hiding_from_close', 'Hiding it from someone close',
|
||||
'You are hiding something, and someone close to you keeps innocently coming near it.',
|
||||
'keeping a guilty or embarrassing secret from a spouse or family; near misses',
|
||||
'the secret belongs to someone else (their_secret)'),
|
||||
E('their_secret', 'Keeping someone else\'s secret',
|
||||
'You have been drawn into another person\'s secret and must decide whether to lie, stay silent or give it up.',
|
||||
'covering for a friend or employer; being questioned by a suspicious superior',
|
||||
'your own secret (hiding_from_close)'),
|
||||
E('what_they_hid', 'What they hid',
|
||||
'You discover that a person close to you has been hiding something, and must decide what it changes.',
|
||||
'a partner\'s concealed past or disappointment; a friend\'s lie revealed',
|
||||
'the hidden thing arrives as a claim or threat from outside (past_comes_back)'),
|
||||
E('pretence', 'Passing for more than you are',
|
||||
'You are pretending to be more, or other, than you are, to impress someone, and may be found out.',
|
||||
'dressing and talking above your station; invented titles or histories; a thrilling story nobody can check',
|
||||
'you are genuinely above or below the other person and must decide what to show (station)'),
|
||||
E('caught_out', 'Caught out',
|
||||
'You have been caught, or are about to be seen, doing or being what you meant nobody to see.',
|
||||
'a private arrangement about to be judged publicly; caught somewhere you should not be; a joke taken in earnest',
|
||||
'you are blamed for something you did not do (wrongly_judged)'),
|
||||
E('wrongly_judged', 'What it looked like',
|
||||
'Someone whose opinion matters has judged you by appearances, for something you did not do, and will not hear you out.',
|
||||
'an innocent moment misread; suspected of a theft; taken for a fraud because of how you look',
|
||||
'you really did it (caught_out)'),
|
||||
E('let_another_take_blame', 'Someone else is taking the blame',
|
||||
'Suspicion has fallen on someone innocent for what you did, and you could let it stay there.',
|
||||
'an innocent person punished for your act; a stranger\'s blunder gives you a scapegoat',
|
||||
'you are the innocent one (wrongly_judged)'),
|
||||
E('cant_prove_it', 'Sure, but cannot prove it',
|
||||
'You are sure who wronged you, and you cannot prove it.',
|
||||
'accusing without evidence; authorities brushing you off; trying to trap the suspect',
|
||||
'you are the one accused (wrongly_judged)'),
|
||||
E('station', 'Across the gap in station',
|
||||
'Someone well above or below you in station wants you, and each of you must decide how much to show of where you really stand.',
|
||||
'courting or marrying across class; ashamed of your origins before a grander partner; a rich suitor not believed',
|
||||
'pretending to a station you do not have (pretence)'),
|
||||
E('home_unseen', 'Unseen at home',
|
||||
'You and a partner at home have stopped really seeing each other, and one of you wants to be noticed.',
|
||||
'neglect; taking a spouse for granted; a partner whose blind approval or coarseness makes nothing matter',
|
||||
'open quarrels about blame (peace_at_home)'),
|
||||
E('peace_at_home', 'Keeping the peace at home',
|
||||
'Someone at home blames you or demands their way, and you must decide whether to give way to keep the peace.',
|
||||
'being held responsible for household trouble; backing down before a spouse or elder; household quarrels',
|
||||
'loneliness without conflict (home_unseen); an outsider in the house (unwelcome_in_home)'),
|
||||
E('unwelcome_in_home', 'An unwelcome guest',
|
||||
'Someone you do not want has settled into your home, and you cannot seem to make them go.',
|
||||
'a relative or intruder who stays; having to make polite room for someone who holds power over the household',
|
||||
'a former partner\'s claim (past_comes_back)'),
|
||||
E('power_over_you', 'Under someone\'s thumb',
|
||||
'Someone with power over you treats you with contempt, or must be humoured though you resent them, and you want respect or to turn the tables.',
|
||||
'a superior who humiliates you; having to be civil and helpful to someone you fear or dislike because of your position',
|
||||
'a well-meaning benefactor (benefactor)'),
|
||||
E('benefactor', 'Help nobody asked for',
|
||||
'Someone with money wants to do good to someone below them on their own terms: either you are offered such help, or you are the one offering it.',
|
||||
'charity with a sermon or a price in pride; reformers who stop you working; a rich person seeking gratitude or restitution',
|
||||
'a conditional offer between equals (strings)'),
|
||||
E('patron', 'A handshake from the top',
|
||||
'Someone powerful could be seen with you, and getting that notice will cost you.',
|
||||
'currying favour with a great man; spending to be seen with the important; wanting a patron at your wedding',
|
||||
'being humiliated by them (power_over_you)'),
|
||||
E('pressed_to_wrong', 'Pressed to do wrong',
|
||||
'Someone is pressing you to do something you believe is wrong or dangerous, including turning on a friend you owe.',
|
||||
'pressure to testify against or turn in a friend; a loved one begging you to do something wicked; being invited into a crooked scheme',
|
||||
'you are merely paid to compromise your standards (compromise_for_money)'),
|
||||
E('go_between', 'Carrying someone else\'s case',
|
||||
'You are asked to carry another person\'s claim or warning to a third party, knowing it may cost you with one side or the other.',
|
||||
'pleading a case you doubt; passing on an unwelcome message; warning an outsider about a relative',
|
||||
'keeping a secret (their_secret)'),
|
||||
E('sacrifice', 'Giving it up for them',
|
||||
'You can help or protect someone you care for only by giving up something of your own, and they may never know.',
|
||||
'selling a treasure for a gift; secret humble work; giving your savings or name away for another',
|
||||
'being paid to give something up (strings)'),
|
||||
E('owning_up', 'Owning up',
|
||||
'You once wronged someone, and now face them, or a stand-in, and must decide how much to admit or how to make amends.',
|
||||
'confession; seeking forgiveness; a public show of devotion to win someone back',
|
||||
'an innocent is blamed right now for what you did (let_another_take_blame)'),
|
||||
E('own_words_back', 'Held to your own words',
|
||||
'A principle, promise or rule you once used for your own ends is now turned against you.',
|
||||
'hoist by your own code; a promise you never expected to be called in', '', recurs=False),
|
||||
E('newcomer', 'New to the place',
|
||||
'You are new to a place or household that ignores you, warns you, or leaves you lost, and you must find your footing.',
|
||||
'arriving in the city; a new post with warnings about the person in charge; waking somewhere strange',
|
||||
'news from the place you left (homecoming)'),
|
||||
E('homecoming', 'Word from home',
|
||||
'The people you left want you back, and word of them has reached you.',
|
||||
'a messenger from home; a parent waiting; a reunion with an old friend from where you came from',
|
||||
'a claim or threat from the past (past_comes_back)'),
|
||||
E('dread', 'Something in the dark',
|
||||
'Something frightening may be close by, and you must decide whether to face it.',
|
||||
'fear of what a wish or deed has brought; noises in the dark; a thing you must undo before it arrives',
|
||||
'ordinary social danger', tone='horror'),
|
||||
E('waiting', 'Waiting on their decision',
|
||||
'Your hopes hang on a decision entirely in another\'s hands, and nothing you do seems to hurry it.',
|
||||
'waiting for a proposal, a verdict or a signal; pretending indifference while it hangs',
|
||||
'you are the one who cannot speak (unspoken)'),
|
||||
E('unspoken', 'You cannot say it',
|
||||
'Someone you want is waiting for you to say it, and you cannot find the words or the moment.',
|
||||
'shy lovers; a single short chance to speak; feelings hidden behind a business arrangement',
|
||||
'waiting for the other to decide (waiting)'),
|
||||
E('overlooked', 'Beside the admired one',
|
||||
'You are the plain or overlooked one beside someone admired, and want, once, to be noticed.',
|
||||
'the plain co-worker; going to dances on someone else\'s arm',
|
||||
'a partner at home who ignores you (home_unseen)'),
|
||||
E('sign', 'A sign',
|
||||
'Someone in a bad way wants a sign to tell them what to do, and one seems to have turned up.',
|
||||
'fortune-tellers, omens, mysterious messages, reading a coincidence as meant for you',
|
||||
'real supernatural danger (dread)'),
|
||||
E('despised', 'The one nobody thinks much of',
|
||||
'Someone everybody looks down on wants a chance, and may be the only one who can help.',
|
||||
'a tramp, a foreigner, a crook or a failure who turns out to be the helper',
|
||||
'someone powerful (patron)'),
|
||||
E('habit', 'The old habit',
|
||||
'An old habit gives you away at exactly the moment you need to be someone new.',
|
||||
'a former trade\'s manner resurfacing; an old craving at the worst moment',
|
||||
'', recurs=False),
|
||||
]
|
||||
|
||||
# every hand group in the three references -> one catalogue id
|
||||
SOURCES = {
|
||||
'ohenry': {'pose': 'pretence', 'benefactor': 'benefactor', 'taken_for_granted': 'home_unseen', 'station': 'station',
|
||||
'newcomer': 'newcomer', 'easy_money': 'sure_thing', 'sacrifice': 'sacrifice', 'rival': 'rival',
|
||||
'price': 'compromise_for_money', 'despised': 'despised', 'loyalty': 'pressed_to_wrong',
|
||||
'appearances': 'wrongly_judged', 'sign': 'sign', 'strings': 'strings', 'honest_work': 'compromise_for_money',
|
||||
'unspoken': 'unspoken', 'overlooked': 'overlooked', 'homecoming': 'homecoming', 'patron': 'patron', 'habit': 'habit'},
|
||||
'wharton': {'tainted_money': 'compromise_for_money', 'strings_attached': 'strings', 'old_attachment': 'past_comes_back',
|
||||
'what_they_hid': 'what_they_hid', 'keep_their_secret': 'their_secret', 'exposed': 'caught_out',
|
||||
'civil_to_them': 'power_over_you', 'plead_their_case': 'go_between', 'own_words_back': 'own_words_back',
|
||||
'give_it_up': 'sacrifice', 'keep_the_peace': 'peace_at_home', 'unseen_at_home': 'home_unseen',
|
||||
'new_and_warned': 'newcomer', 'the_rival': 'rival', 'owning_up': 'owning_up', 'waiting_on_them': 'waiting'},
|
||||
'jacobs': {'G01': 'past_comes_back', 'G02': 'hiding_from_close', 'G03': 'let_another_take_blame', 'G04': 'rival',
|
||||
'G05': 'your_scheme', 'G06': 'your_scheme', 'G07': 'after_your_money', 'G08': 'strings', 'G09': 'your_scheme',
|
||||
'G10': 'power_over_you', 'G11': 'unwelcome_in_home', 'G12': 'dread', 'G13': 'pressed_to_wrong',
|
||||
'G14': 'pretence', 'G15': 'caught_out', 'G16': 'peace_at_home', 'G17': 'cant_prove_it', 'G18': 'newcomer',
|
||||
'G19': 'owning_up'},
|
||||
}
|
||||
|
||||
def load_corpus(name):
|
||||
"""-> chunks, {chunk: predicament}, {chunk: hand group id}"""
|
||||
if name == 'ohenry':
|
||||
d = PROBE / 'v2'
|
||||
sys.path.insert(0, str(d / 'claude')); from assign import ASSIGN
|
||||
pred = {x['chunk']: x['summary'] for x in json.load(open(d / 'claude/predicaments-claude.json'))['summaries']}
|
||||
return json.load(open(d / 'chunks.json')), pred, dict(ASSIGN)
|
||||
d = PROBE / name
|
||||
groups = json.load(open(d / 'claude/groups.json'))['groups']
|
||||
hand = {}
|
||||
for g in groups:
|
||||
for m in g['members']:
|
||||
c = m if isinstance(m, int) else m['chunk']
|
||||
assert c not in hand, (name, c); hand[c] = g['id']
|
||||
if name == 'wharton':
|
||||
pred = {x['chunk']: x['summary'] for x in json.load(open(d / 'claude/predicaments.json'))['summaries']}
|
||||
else:
|
||||
rec = json.load(open(d / 'claude/records.json')); rec = rec if isinstance(rec, list) else rec['records']
|
||||
pred = {r['chunk']: r['predicament'] for r in rec}
|
||||
return json.load(open(d / 'chunks.json')), pred, hand
|
||||
|
||||
CORPORA = ['ohenry', 'wharton', 'jacobs']
|
||||
|
||||
# ---- one recurrence rule for all three --------------------------------------------------------------
|
||||
def min_stories(n_stories): return max(3, math.ceil(0.04 * n_stories)) # 3 for 10-12 stories, 4 for 97
|
||||
MIN_SCENES, MAX_DOMINANT = 5, 0.50
|
||||
def recurs(members, story):
|
||||
st = collections.Counter(story[c] for c in members)
|
||||
return len(members) >= MIN_SCENES and len(st) >= min_stories(len(set(story))) and max(st.values()) / len(members) <= MAX_DOMINANT, len(st), (max(st.values()) / len(members) if members else 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ids = [e['id'] for e in CATALOGUE]
|
||||
assert len(ids) == len(set(ids)), 'duplicate id'
|
||||
problems = []
|
||||
for corpus, m in SOURCES.items():
|
||||
for g, c in m.items():
|
||||
if c not in ids: problems.append(f'{corpus}:{g} -> unknown {c}')
|
||||
used = {c for m in SOURCES.values() for c in m.values()}
|
||||
problems += [f'catalogue entry {i} has no source group' for i in ids if i not in used]
|
||||
for e in CATALOGUE:
|
||||
for x in e['excludes'].split(';'):
|
||||
for i in ids:
|
||||
pass
|
||||
examples = collections.defaultdict(list)
|
||||
log = []
|
||||
for corpus in CORPORA:
|
||||
chunks, pred, hand = load_corpus(corpus)
|
||||
unmapped = sorted({g for g in hand.values() if g not in SOURCES[corpus]})
|
||||
if unmapped: problems.append(f'{corpus}: hand groups not in SOURCES: {unmapped}')
|
||||
labels = {i: SOURCES[corpus].get(hand[i], 'none') if i in hand else 'none' for i in range(len(chunks))}
|
||||
(HERE / f'labels-{corpus}.json').write_text(json.dumps(
|
||||
{'corpus': corpus, 'from': 'hand groups mapped through catalogue_src.SOURCES',
|
||||
'labels': [{'chunk': i, 'story': chunks[i]['story'], 'label': labels[i], 'predicament': pred[i]} for i in range(len(chunks))]},
|
||||
indent=0, ensure_ascii=False))
|
||||
# examples: up to 3 per (entry, corpus), spread across stories, taken from the hand members
|
||||
per = collections.defaultdict(list)
|
||||
for i in sorted(labels):
|
||||
if labels[i] != 'none': per[labels[i]].append(i)
|
||||
for lab, mem in per.items():
|
||||
seen = set()
|
||||
for i in mem:
|
||||
if chunks[i]['story'] in seen: continue
|
||||
seen.add(chunks[i]['story']); examples[lab].append({'corpus': corpus, 'chunk': i, 'predicament': pred[i]})
|
||||
if len(seen) == 3: break
|
||||
story = [c['story'] for c in chunks]
|
||||
ns = len(set(story))
|
||||
log.append(f'\n== {corpus}: {ns} stories, {len(chunks)} scenes; rule: >= {MIN_SCENES} scenes, >= {min_stories(ns)} stories, no story over {MAX_DOMINANT:.0%}')
|
||||
log.append(f' scenes labelled: {sum(l != "none" for l in labels.values())} ({sum(l != "none" for l in labels.values())/len(chunks):.0%})')
|
||||
passing = []
|
||||
for lab in sorted(per, key=lambda k: -len(per[k])):
|
||||
ok, nst, dom = recurs(per[lab], story)
|
||||
passing += [lab] if ok else []
|
||||
log.append(f' {"PASS" if ok else " "} {lab:24s} {len(per[lab]):3d} scenes {nst:3d} stories top story {dom:4.0%}')
|
||||
log.append(f' -> {len(passing)} recurring situations of {len(per)} present')
|
||||
cover = collections.defaultdict(set)
|
||||
for corpus, m in SOURCES.items():
|
||||
for c in m.values(): cover[c].add(corpus)
|
||||
log.append('\n== catalogue coverage: entries found in 3 / 2 / 1 corpora')
|
||||
for k in (3, 2, 1):
|
||||
log.append(f' {k}: ' + ', '.join(i for i in ids if len(cover[i]) == k))
|
||||
if problems:
|
||||
print('\n'.join(problems)); sys.exit(1)
|
||||
out = {'about': 'Situation catalogue for story-to-pack, seeded by hand from O. Henry, Wharton and Jacobs references (2026-09-17). '
|
||||
'A classifier assigns each scene predicament one entry or "none". Examples carry their corpus so a test '
|
||||
'on one corpus can use only the others.',
|
||||
'entries': [dict(e, corpora=sorted(cover[e['id']]), examples=examples[e['id']]) for e in CATALOGUE]}
|
||||
(HERE / 'catalogue.json').write_text(json.dumps(out, indent=1, ensure_ascii=False))
|
||||
(HERE / 'recurrence.log').write_text('\n'.join(log).lstrip() + '\n')
|
||||
print(f'catalogue.json: {len(CATALOGUE)} entries'); print('\n'.join(log))
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Classify scene predicaments against the catalogue, one scene at a time.
|
||||
|
||||
STP_OLLAMA=http://<host>:11434 python3 classify.py <corpus> <text-set> [--limit N]
|
||||
corpus ohenry | wharton | jacobs
|
||||
text-set claude (the hand-written predicaments) | q14 (qwen3:14b predicaments, O. Henry only)
|
||||
|
||||
Leave-one-corpus-out: the worked examples shown never come from the corpus being classified, so this
|
||||
tests the catalogue on a corpus it has not seen. Embeddings (nomic-embed-text) only narrow the choice:
|
||||
the scene's K nearest entries, plus "none", go to the model. Checkpoints after every scene; resumes.
|
||||
Writes out/<corpus>-<text-set>.json.
|
||||
"""
|
||||
import argparse, json, math, pathlib, re, sys, time
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
PROBE = HERE.parent
|
||||
sys.path.insert(0, str(PROBE))
|
||||
import ollama
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('corpus'); ap.add_argument('textset')
|
||||
ap.add_argument('--limit', type=int); ap.add_argument('--k', type=int, default=0, help='0 = offer the whole catalogue')
|
||||
ap.add_argument('--start', type=int, default=0)
|
||||
ap.add_argument('--tag', default='')
|
||||
ap.add_argument('--model', default='qwen3:14b')
|
||||
args = ap.parse_args()
|
||||
EMB = 'nomic-embed-text'
|
||||
|
||||
cat = json.load(open(HERE / 'catalogue.json'))['entries']
|
||||
labels = json.load(open(HERE / f'labels-{args.corpus}.json'))['labels']
|
||||
if args.textset == 'claude':
|
||||
texts = [x['predicament'] for x in labels]
|
||||
elif args.textset == 'q14':
|
||||
assert args.corpus == 'ohenry'
|
||||
q = {x['chunk']: x['summary'] for x in json.load(open(PROBE / 'v2/predicaments_clean.json'))['summaries']}
|
||||
texts = [q[x['chunk']] for x in labels]
|
||||
else:
|
||||
sys.exit('text-set must be claude or q14')
|
||||
|
||||
def norm(v):
|
||||
m = math.sqrt(sum(x * x for x in v)) or 1.0
|
||||
return [x / m for x in v]
|
||||
|
||||
# embeddings, cached
|
||||
def cached_embed(name, items):
|
||||
p = HERE / 'emb' / f'{name}.json'; p.parent.mkdir(exist_ok=True)
|
||||
if p.exists():
|
||||
d = json.load(open(p))
|
||||
if d['texts'] == items: return d['vectors']
|
||||
vecs = []
|
||||
for i in range(0, len(items), 16):
|
||||
vecs += ollama.embed(EMB, ['search_query: ' + t if name != 'catalogue' else 'search_document: ' + t for t in items[i:i + 16]])
|
||||
p.write_text(json.dumps({'model': EMB, 'texts': items, 'vectors': vecs}))
|
||||
return vecs
|
||||
|
||||
entry_text = [f"{e['name']}. {e['definition']} Includes: {e['includes']}." for e in cat]
|
||||
E = [norm(v) for v in cached_embed('catalogue', entry_text)]
|
||||
S = [norm(v) for v in cached_embed(f'{args.corpus}-{args.textset}', texts)]
|
||||
|
||||
def examples_for(e):
|
||||
ex = [x for x in e['examples'] if x['corpus'] != args.corpus][:2]
|
||||
return ''.join(f'\n e.g. "{x["predicament"]}"' for x in ex)
|
||||
|
||||
SYSTEM = """You sort scenes from short stories into recurring SITUATIONS for a life-simulation game.
|
||||
|
||||
You get one scene, described as its main person's predicament, and a short list of candidate situations.
|
||||
Answer with the ONE candidate that the predicament is an instance of, or "none".
|
||||
|
||||
Choose "none" when the scene is narration, scene-setting, a description of a place or type of person, or a
|
||||
one-off turn that is not really any of the candidates. A shared word or setting is not enough: the
|
||||
predicament itself -- what the person is up against and what they want -- must match the situation.
|
||||
|
||||
Most scenes in a story are NOT one of these situations: expect about half to be "none".
|
||||
Also rate how clearly the predicament is an instance of your choice: 3 = plainly this situation,
|
||||
2 = arguably, 1 = only loosely.
|
||||
|
||||
Reply with JSON only: {"label": "<candidate id or none>", "fit": <1, 2 or 3>}"""
|
||||
|
||||
out_path = HERE / 'out' / f'{args.corpus}-{args.textset}{args.tag}.json'; out_path.parent.mkdir(exist_ok=True)
|
||||
state = json.load(open(out_path)) if out_path.exists() else {'model': args.model, 'k': args.k, 'results': []}
|
||||
done = {r['chunk'] for r in state['results']}
|
||||
n = len(texts) if not args.limit else min(args.limit, len(texts))
|
||||
t0 = time.time(); new = 0
|
||||
for i in range(args.start, n):
|
||||
if labels[i]['chunk'] in done: continue
|
||||
order = sorted(range(len(cat)), key=lambda j: -sum(a * b for a, b in zip(S[i], E[j])))
|
||||
sims = order[:args.k] if args.k else list(range(len(cat)))
|
||||
cands = '\n'.join(f"- {cat[j]['id']}: {cat[j]['definition']}{examples_for(cat[j])}" for j in sims)
|
||||
# candidates go in the system prompt, identical from scene to scene when the whole catalogue is offered,
|
||||
# so the server can reuse its cached prefix; the predicament comes last
|
||||
system = SYSTEM + '\n\nCandidates:\n' + cands + '\n- none: not one of these situations'
|
||||
user = f'Predicament: "{texts[i]}"'
|
||||
raw, _, _ = ollama.chat(args.model, system, user, num_ctx=4096, num_predict=40, fmt='json',
|
||||
think=False if args.model.startswith('qwen3') else None)
|
||||
try:
|
||||
j = json.loads(raw); lab = str(j.get('label', '')).strip(); fit = j.get('fit')
|
||||
except Exception:
|
||||
m = re.search(r'"label"\s*:\s*"([^"]+)"', raw); lab = m.group(1) if m else ''; fit = None
|
||||
valid = {cat[j]['id'] for j in sims} | {'none'}
|
||||
state['results'].append({'chunk': labels[i]['chunk'], 'label': lab if lab in valid else 'none',
|
||||
'fit': fit, 'raw': raw if lab not in valid else None, 'candidates': [cat[j]['id'] for j in sims],
|
||||
'nearest': cat[sims[0]]['id']})
|
||||
new += 1
|
||||
if new % 10 == 0 or i == n - 1:
|
||||
out_path.write_text(json.dumps(state, indent=0))
|
||||
el = time.time() - t0
|
||||
print(f' {len(state["results"])}/{n} {el:.0f}s {el / new:.2f}s/scene', flush=True)
|
||||
out_path.write_text(json.dumps(state, indent=0))
|
||||
print('done', out_path.name, len(state['results']))
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,948 @@
|
||||
{
|
||||
"corpus": "jacobs",
|
||||
"from": "hand groups mapped through catalogue_src.SOURCES",
|
||||
"labels": [
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"label": "peace_at_home",
|
||||
"predicament": "Someone waiting up for a relative who comes in late wants to reproach him, and gets cheek back."
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"label": "peace_at_home",
|
||||
"predicament": "A man learns a relative has promised a stranger his hospitality without asking, and fears what his spouse will say."
|
||||
},
|
||||
{
|
||||
"chunk": 2,
|
||||
"story": 0,
|
||||
"label": "unwelcome_in_home",
|
||||
"predicament": "A man who wants to turn an unwelcome guest away cannot find the nerve to say it."
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"label": "peace_at_home",
|
||||
"predicament": "A man who has let something happen under his roof fears his spouse will hold him responsible for it."
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"label": "rival",
|
||||
"predicament": "A young man who brought the person he is courting along is pushed aside by an older man who takes charge of her."
|
||||
},
|
||||
{
|
||||
"chunk": 5,
|
||||
"story": 0,
|
||||
"label": "rival",
|
||||
"predicament": "A jealous suitor tries to put his rival in his place, and only makes himself look childish."
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"label": "none",
|
||||
"predicament": "A spurned man realises he holds something over the rival who humiliated him, and decides to use it."
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 0,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man hiding an innocent but compromising secret is met by his spouse, while the one person who could clear him refuses to."
|
||||
},
|
||||
{
|
||||
"chunk": 8,
|
||||
"story": 0,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man tries to hurry his spouse away from a secret while someone signals behind his back that something is up."
|
||||
},
|
||||
{
|
||||
"chunk": 9,
|
||||
"story": 0,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man caught with an innocent secret is disowned by the one person who could explain it."
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 0,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A man whose spite has got an innocent person abused must decide whether to own up and defend her."
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 0,
|
||||
"label": "owning_up",
|
||||
"predicament": "A man tries to win back someone he wronged with a dangerous public show of devotion."
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 0,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man pretends to be in danger to win sympathy, and the one who saved him sees through it."
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 0,
|
||||
"label": "owning_up",
|
||||
"predicament": "Two men who quarrelled over a woman are both left out in the cold by the women they wronged."
|
||||
},
|
||||
{
|
||||
"chunk": 14,
|
||||
"story": 1,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man losing a friendly contest tries to distract from his mistake, and is caught at it by his family."
|
||||
},
|
||||
{
|
||||
"chunk": 15,
|
||||
"story": 1,
|
||||
"label": "none",
|
||||
"predicament": "A guest who wants to leave a dangerous subject alone is coaxed by his hosts' curiosity into bringing it out."
|
||||
},
|
||||
{
|
||||
"chunk": 16,
|
||||
"story": 1,
|
||||
"label": "strings",
|
||||
"predicament": "Someone offered a thing that grants wishes is warned by the man who used it that it brings sorrow."
|
||||
},
|
||||
{
|
||||
"chunk": 17,
|
||||
"story": 1,
|
||||
"label": "strings",
|
||||
"predicament": "A man is offered a dangerous gift by someone who begs him to destroy it, and wants to keep it anyway."
|
||||
},
|
||||
{
|
||||
"chunk": 18,
|
||||
"story": 1,
|
||||
"label": "strings",
|
||||
"predicament": "A man half-believing in a promise of easy money must decide whether to test it and what to ask for."
|
||||
},
|
||||
{
|
||||
"chunk": 19,
|
||||
"story": 1,
|
||||
"label": "dread",
|
||||
"predicament": "A family that tempted fate laughs off their fears in daylight while one of them cannot shake them."
|
||||
},
|
||||
{
|
||||
"chunk": 20,
|
||||
"story": 1,
|
||||
"label": "strings",
|
||||
"predicament": "A family waiting on a hoped-for windfall sees a stranger approach who might bring it, or bad news."
|
||||
},
|
||||
{
|
||||
"chunk": 21,
|
||||
"story": 1,
|
||||
"label": "none",
|
||||
"predicament": "Parents receive news that their only child has been killed, from someone who cannot bring himself to say it."
|
||||
},
|
||||
{
|
||||
"chunk": 22,
|
||||
"story": 1,
|
||||
"label": "strings",
|
||||
"predicament": "A man learns the money he wished for has come as compensation for his child's death."
|
||||
},
|
||||
{
|
||||
"chunk": 23,
|
||||
"story": 1,
|
||||
"label": "pressed_to_wrong",
|
||||
"predicament": "A grieving woman seizes on a desperate chance to undo a death, and her spouse is afraid of what it would bring back."
|
||||
},
|
||||
{
|
||||
"chunk": 24,
|
||||
"story": 1,
|
||||
"label": "pressed_to_wrong",
|
||||
"predicament": "A man pressed by his grieving spouse must decide whether to do something he believes is wicked and dangerous."
|
||||
},
|
||||
{
|
||||
"chunk": 25,
|
||||
"story": 1,
|
||||
"label": "dread",
|
||||
"predicament": "A man who dreads what his wish may bring home must stop his spouse from opening the door to it."
|
||||
},
|
||||
{
|
||||
"chunk": 26,
|
||||
"story": 1,
|
||||
"label": "dread",
|
||||
"predicament": "A man must use his last chance to undo what his spouse most longs for, before she lets it in."
|
||||
},
|
||||
{
|
||||
"chunk": 27,
|
||||
"story": 2,
|
||||
"label": "after_your_money",
|
||||
"predicament": "A man who has hoarded his savings all his life falls ill far from anywhere he could spend them."
|
||||
},
|
||||
{
|
||||
"chunk": 28,
|
||||
"story": 2,
|
||||
"label": "after_your_money",
|
||||
"predicament": "A dying miser, surrounded by people hoping to inherit, is determined that nobody will get his money."
|
||||
},
|
||||
{
|
||||
"chunk": 29,
|
||||
"story": 2,
|
||||
"label": "pressed_to_wrong",
|
||||
"predicament": "Someone who spots a colleague quietly scheming to take unclaimed money must decide whether to expose him or join in."
|
||||
},
|
||||
{
|
||||
"chunk": 30,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "Two partners convinced a weaker person is secretly holding a fortune want to find it before anyone else learns of it."
|
||||
},
|
||||
{
|
||||
"chunk": 31,
|
||||
"story": 2,
|
||||
"label": "cant_prove_it",
|
||||
"predicament": "A schemer whose secret searches keep failing decides to confront the person he suspects directly."
|
||||
},
|
||||
{
|
||||
"chunk": 32,
|
||||
"story": 2,
|
||||
"label": "after_your_money",
|
||||
"predicament": "Someone suspected of hiding a windfall is pressed with bribes and threats by people who have no right to it."
|
||||
},
|
||||
{
|
||||
"chunk": 33,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "Someone forced to share a hidden windfall admits it is now stuck where neither partner can get at it."
|
||||
},
|
||||
{
|
||||
"chunk": 34,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "Distrustful partners stare at a fortune they cannot reach without getting caught, and blame each other for it."
|
||||
},
|
||||
{
|
||||
"chunk": 35,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man wants something a superior owns without revealing why, and tries flattery to get it."
|
||||
},
|
||||
{
|
||||
"chunk": 36,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A buyer who cannot reveal why he wants something is made to pay more by a seller who senses his eagerness."
|
||||
},
|
||||
{
|
||||
"chunk": 37,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who wants to cheat his partner out of a shared prize is watched too closely to slip away."
|
||||
},
|
||||
{
|
||||
"chunk": 38,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man trying to slip away with a shared prize is held up until the partner he meant to cheat catches him."
|
||||
},
|
||||
{
|
||||
"chunk": 39,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who schemed to cheat a partner finds the prize gone and realises he may have been cheated himself."
|
||||
},
|
||||
{
|
||||
"chunk": 40,
|
||||
"story": 2,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A schemer who was outwitted must stand and smile at the people who got the better of him."
|
||||
},
|
||||
{
|
||||
"chunk": 41,
|
||||
"story": 3,
|
||||
"label": "none",
|
||||
"predicament": "A man about to marry is needled by a dependent relative who hints at something to his discredit."
|
||||
},
|
||||
{
|
||||
"chunk": 42,
|
||||
"story": 3,
|
||||
"label": "none",
|
||||
"predicament": "A man in desperate debt makes veiled hints to the relative he hopes will pay it."
|
||||
},
|
||||
{
|
||||
"chunk": 43,
|
||||
"story": 3,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A man is blackmailed by a relative with old letters that could cost him the person he loves."
|
||||
},
|
||||
{
|
||||
"chunk": 44,
|
||||
"story": 3,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A man who has done something terrible in anger must cover for a relative's sudden disappearance."
|
||||
},
|
||||
{
|
||||
"chunk": 45,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man with a guilty secret must keep calm while someone he loves insists on lingering right at the spot."
|
||||
},
|
||||
{
|
||||
"chunk": 46,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man hiding a crime hears it discussed innocently and betrays his feelings with an outburst."
|
||||
},
|
||||
{
|
||||
"chunk": 47,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A guilty man tries to hurry someone away from the scene of his crime, and she insists on staying."
|
||||
},
|
||||
{
|
||||
"chunk": 48,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man hiding something learns it may be discovered by an innocent accident, and must not show his fear."
|
||||
},
|
||||
{
|
||||
"chunk": 49,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man must retrieve something from where his secret lies before anyone else goes looking."
|
||||
},
|
||||
{
|
||||
"chunk": 50,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man secretly searching where his crime lies keeps pulling up reminders of it."
|
||||
},
|
||||
{
|
||||
"chunk": 51,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man who has failed to fix things quietly must now involve others while still keeping his secret."
|
||||
},
|
||||
{
|
||||
"chunk": 52,
|
||||
"story": 3,
|
||||
"label": "hiding_from_close",
|
||||
"predicament": "A man hiding something is offered help he cannot accept without being found out."
|
||||
},
|
||||
{
|
||||
"chunk": 53,
|
||||
"story": 3,
|
||||
"label": "none",
|
||||
"predicament": "A man who must face the thing he did alone gathers his nerve while others watch and wonder."
|
||||
},
|
||||
{
|
||||
"chunk": 54,
|
||||
"story": 3,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who hid a crime is dragged down by it at the moment he tries to undo its consequences."
|
||||
},
|
||||
{
|
||||
"chunk": 55,
|
||||
"story": 4,
|
||||
"label": "peace_at_home",
|
||||
"predicament": "A woman who has lost something valuable suspects the people around her, and resents her spouse for not caring as much."
|
||||
},
|
||||
{
|
||||
"chunk": 56,
|
||||
"story": 4,
|
||||
"label": "cant_prove_it",
|
||||
"predicament": "Someone who has lost something accuses the most defenceless person nearby, and punishes her before any proof."
|
||||
},
|
||||
{
|
||||
"chunk": 57,
|
||||
"story": 4,
|
||||
"label": "peace_at_home",
|
||||
"predicament": "A man who thinks a household fuss is foolish keeps defending the accused and is scolded for taking her side."
|
||||
},
|
||||
{
|
||||
"chunk": 58,
|
||||
"story": 4,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man who finds a family inquiry ridiculous cannot keep a straight face in front of its self-important leader."
|
||||
},
|
||||
{
|
||||
"chunk": 59,
|
||||
"story": 4,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A man who sees an innocent neighbour blamed for evidence he himself left must decide whether to own up."
|
||||
},
|
||||
{
|
||||
"chunk": 60,
|
||||
"story": 4,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man who knows more than he says can barely hide his amusement from the self-important person running the inquiry."
|
||||
},
|
||||
{
|
||||
"chunk": 61,
|
||||
"story": 4,
|
||||
"label": "cant_prove_it",
|
||||
"predicament": "A self-important man, teased that a neighbour fancies him, resolves to prove his point by trapping her."
|
||||
},
|
||||
{
|
||||
"chunk": 62,
|
||||
"story": 4,
|
||||
"label": "pressed_to_wrong",
|
||||
"predicament": "A man refuses to let his home be used to entrap a friend, so the schemer plans to go behind his back."
|
||||
},
|
||||
{
|
||||
"chunk": 63,
|
||||
"story": 4,
|
||||
"label": "unwelcome_in_home",
|
||||
"predicament": "A man stuck with an unwelcome relative in his home sees a chance to have him disgraced and driven off."
|
||||
},
|
||||
{
|
||||
"chunk": 64,
|
||||
"story": 4,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man who has trapped his rival in an embarrassing hiding place decides to make the most of it."
|
||||
},
|
||||
{
|
||||
"chunk": 65,
|
||||
"story": 4,
|
||||
"label": "caught_out",
|
||||
"predicament": "A trapped man must listen in silence while someone tells a neighbour a story about him that he cannot deny."
|
||||
},
|
||||
{
|
||||
"chunk": 66,
|
||||
"story": 4,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man caught somewhere he should not be is handed an embarrassing explanation, and has no better one to offer."
|
||||
},
|
||||
{
|
||||
"chunk": 67,
|
||||
"story": 5,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A man found out in wrongdoing is ordered by his partner to give up everything and leave by morning."
|
||||
},
|
||||
{
|
||||
"chunk": 68,
|
||||
"story": 5,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A man offered a harsh but fair way out of his disgrace wants a quieter bargain the other will not give."
|
||||
},
|
||||
{
|
||||
"chunk": 69,
|
||||
"story": 5,
|
||||
"label": "pressed_to_wrong",
|
||||
"predicament": "A man facing ruin threatens the only person who can expose him, and must choose whether to go through with it."
|
||||
},
|
||||
{
|
||||
"chunk": 70,
|
||||
"story": 5,
|
||||
"label": "none",
|
||||
"predicament": "A man who has just done something terrible in rage panics and must steady himself when a figure of the law appears."
|
||||
},
|
||||
{
|
||||
"chunk": 71,
|
||||
"story": 5,
|
||||
"label": "none",
|
||||
"predicament": "A man with a crime behind him and a few hours' start must decide what to take and where to run."
|
||||
},
|
||||
{
|
||||
"chunk": 72,
|
||||
"story": 5,
|
||||
"label": "none",
|
||||
"predicament": "A fugitive planning his escape finds that every choice might be the one that gets him caught."
|
||||
},
|
||||
{
|
||||
"chunk": 73,
|
||||
"story": 5,
|
||||
"label": "dread",
|
||||
"predicament": "A man alone with what he has done hears noises in the dark and fears it has come back for him."
|
||||
},
|
||||
{
|
||||
"chunk": 74,
|
||||
"story": 5,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A guilty man realises a stranger's blunder gives him someone else to blame."
|
||||
},
|
||||
{
|
||||
"chunk": 75,
|
||||
"story": 5,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A man framing an innocent person must act the outraged victim in front of the law."
|
||||
},
|
||||
{
|
||||
"chunk": 76,
|
||||
"story": 5,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A man must play the shocked innocent while the person he framed pleads the truth."
|
||||
},
|
||||
{
|
||||
"chunk": 77,
|
||||
"story": 5,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "Someone falsely accused points to the real culprit, who must hold his nerve and keep up his story."
|
||||
},
|
||||
{
|
||||
"chunk": 78,
|
||||
"story": 5,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who thought he had got away with it learns the one witness who could hang him is still alive."
|
||||
},
|
||||
{
|
||||
"chunk": 79,
|
||||
"story": 5,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man whose deceit has collapsed can only watch as the truth comes out and he is taken away."
|
||||
},
|
||||
{
|
||||
"chunk": 80,
|
||||
"story": 6,
|
||||
"label": "none",
|
||||
"predicament": "A penniless newcomer forces his way into a place he does not belong, counting on a secret to back him."
|
||||
},
|
||||
{
|
||||
"chunk": 81,
|
||||
"story": 6,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A man who has built a respectable new life is found by an old associate who threatens to expose his past."
|
||||
},
|
||||
{
|
||||
"chunk": 82,
|
||||
"story": 6,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A man pays off a blackmailer and learns the price will be half of everything, for good."
|
||||
},
|
||||
{
|
||||
"chunk": 83,
|
||||
"story": 6,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A reformed man warns a blackmailer not to push him too far, while the blackmailer sets his sights on the man's family."
|
||||
},
|
||||
{
|
||||
"chunk": 84,
|
||||
"story": 6,
|
||||
"label": "none",
|
||||
"predicament": "A man who has seized power over another knows the danger he is in, and guards himself even as he gloats."
|
||||
},
|
||||
{
|
||||
"chunk": 85,
|
||||
"story": 6,
|
||||
"label": "unwelcome_in_home",
|
||||
"predicament": "A household must make polite space for someone who holds them hostage."
|
||||
},
|
||||
{
|
||||
"chunk": 86,
|
||||
"story": 6,
|
||||
"label": "unwelcome_in_home",
|
||||
"predicament": "Someone watches a person she trusts give way at every turn to an intruder, and demands that he stand up for himself."
|
||||
},
|
||||
{
|
||||
"chunk": 87,
|
||||
"story": 6,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A man who is being blackmailed hears his tormentor threaten someone he loves, and must choose between swallowing it and hitting back."
|
||||
},
|
||||
{
|
||||
"chunk": 88,
|
||||
"story": 6,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A blackmailer's hold depends on his victim's life, and that victim suddenly collapses."
|
||||
},
|
||||
{
|
||||
"chunk": 89,
|
||||
"story": 6,
|
||||
"label": "none",
|
||||
"predicament": "A blackmailer whose victim is dying must grab what he can before his hold disappears."
|
||||
},
|
||||
{
|
||||
"chunk": 90,
|
||||
"story": 6,
|
||||
"label": "past_comes_back",
|
||||
"predicament": "A dying man pressed by his tormentor bargains for time and for provision for someone he loves."
|
||||
},
|
||||
{
|
||||
"chunk": 91,
|
||||
"story": 6,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who thinks his helpless victim is at his mercy leans in close to hear the secret."
|
||||
},
|
||||
{
|
||||
"chunk": 92,
|
||||
"story": 6,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man who pretended to be helpless strikes at the tormentor who threatened his family."
|
||||
},
|
||||
{
|
||||
"chunk": 93,
|
||||
"story": 6,
|
||||
"label": "let_another_take_blame",
|
||||
"predicament": "A man who has done a desperate thing arranges things so that suspicion falls elsewhere."
|
||||
},
|
||||
{
|
||||
"chunk": 94,
|
||||
"story": 7,
|
||||
"label": "none",
|
||||
"predicament": "A craftsman told his work looks like something else quietly fixes it before anyone else notices."
|
||||
},
|
||||
{
|
||||
"chunk": 95,
|
||||
"story": 7,
|
||||
"label": "pretence",
|
||||
"predicament": "An old man looking for a free drink offers a story in exchange."
|
||||
},
|
||||
{
|
||||
"chunk": 96,
|
||||
"story": 7,
|
||||
"label": "dread",
|
||||
"predicament": "People who laughed at a distant danger panic when it seems to have arrived on their doorstep."
|
||||
},
|
||||
{
|
||||
"chunk": 97,
|
||||
"story": 7,
|
||||
"label": "dread",
|
||||
"predicament": "Someone must choose between keeping himself safe behind a barred door and letting in a stranger in danger."
|
||||
},
|
||||
{
|
||||
"chunk": 98,
|
||||
"story": 7,
|
||||
"label": "pretence",
|
||||
"predicament": "A stranger with a thrilling tale of danger is rewarded by a frightened crowd without anyone checking it."
|
||||
},
|
||||
{
|
||||
"chunk": 99,
|
||||
"story": 7,
|
||||
"label": "dread",
|
||||
"predicament": "A community living in fear of an unseen threat ignores the one doubter, because he has a bad name."
|
||||
},
|
||||
{
|
||||
"chunk": 100,
|
||||
"story": 7,
|
||||
"label": "cant_prove_it",
|
||||
"predicament": "A man who has suffered a loss goes to the authorities for help and is brushed off as a nuisance."
|
||||
},
|
||||
{
|
||||
"chunk": 101,
|
||||
"story": 7,
|
||||
"label": "none",
|
||||
"predicament": "A community suffering repeated losses cannot get help, and cannot agree how to protect itself."
|
||||
},
|
||||
{
|
||||
"chunk": 102,
|
||||
"story": 7,
|
||||
"label": "pretence",
|
||||
"predicament": "A man tells a dramatic story of narrow escape to an audience eager to believe it."
|
||||
},
|
||||
{
|
||||
"chunk": 103,
|
||||
"story": 7,
|
||||
"label": "pretence",
|
||||
"predicament": "A man caught in a boastful lie is 'backed up' by a rival, who now has him trapped by it."
|
||||
},
|
||||
{
|
||||
"chunk": 104,
|
||||
"story": 7,
|
||||
"label": "none",
|
||||
"predicament": "People told their fears are nonsense by outsiders decide to take matters into their own hands."
|
||||
},
|
||||
{
|
||||
"chunk": 105,
|
||||
"story": 7,
|
||||
"label": "none",
|
||||
"predicament": "A band that set out to face a danger returns relieved to have found nothing, and is then called to face it for real."
|
||||
},
|
||||
{
|
||||
"chunk": 106,
|
||||
"story": 7,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man with something to hide has an armed crowd force its way into his home."
|
||||
},
|
||||
{
|
||||
"chunk": 107,
|
||||
"story": 7,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man caught with the evidence of his thefts brazens it out and dares his accusers to prove anything."
|
||||
},
|
||||
{
|
||||
"chunk": 108,
|
||||
"story": 7,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man accused of wrongdoing turns his accusers' own exaggerations into his defence."
|
||||
},
|
||||
{
|
||||
"chunk": 109,
|
||||
"story": 7,
|
||||
"label": "cant_prove_it",
|
||||
"predicament": "People who know they were swindled cannot prove it, and must watch the swindler profit."
|
||||
},
|
||||
{
|
||||
"chunk": 110,
|
||||
"story": 8,
|
||||
"label": "none",
|
||||
"predicament": "A man nervously setting out to do something that matters to him is stopped by a friend with questions."
|
||||
},
|
||||
{
|
||||
"chunk": 111,
|
||||
"story": 8,
|
||||
"label": "rival",
|
||||
"predicament": "A man discovers a friend is after the same person he is, on the same day, and each races to get there first."
|
||||
},
|
||||
{
|
||||
"chunk": 112,
|
||||
"story": 8,
|
||||
"label": "rival",
|
||||
"predicament": "Two rivals agree someone must go first, and neither will be the one to wait."
|
||||
},
|
||||
{
|
||||
"chunk": 113,
|
||||
"story": 8,
|
||||
"label": "rival",
|
||||
"predicament": "Two rivals for the same prize trade claims of priority, and each suspects the other of a trick."
|
||||
},
|
||||
{
|
||||
"chunk": 114,
|
||||
"story": 8,
|
||||
"label": "your_scheme",
|
||||
"predicament": "Two rivals agree to settle who goes first by a game, and one slips in a condition the other does not examine."
|
||||
},
|
||||
{
|
||||
"chunk": 115,
|
||||
"story": 8,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who tried to tip a contest in his favour loses it through his own oversight."
|
||||
},
|
||||
{
|
||||
"chunk": 116,
|
||||
"story": 8,
|
||||
"label": "rival",
|
||||
"predicament": "A man who lost the right to try first must wait while his rival takes his chance."
|
||||
},
|
||||
{
|
||||
"chunk": 117,
|
||||
"story": 8,
|
||||
"label": "rival",
|
||||
"predicament": "A man finds the rival he lost to has been refused, but can still use the terms of their bargain to keep him out."
|
||||
},
|
||||
{
|
||||
"chunk": 118,
|
||||
"story": 8,
|
||||
"label": "rival",
|
||||
"predicament": "A man bound by a promise he made watches a rival use it to keep him from what he wants."
|
||||
},
|
||||
{
|
||||
"chunk": 119,
|
||||
"story": 8,
|
||||
"label": "caught_out",
|
||||
"predicament": "A man who has been avoiding someone he cares for must explain why, and the only honest answer is embarrassing."
|
||||
},
|
||||
{
|
||||
"chunk": 120,
|
||||
"story": 8,
|
||||
"label": "none",
|
||||
"predicament": "A man bound by a rash promise finds the person he hoped to win is willing to help him get round it."
|
||||
},
|
||||
{
|
||||
"chunk": 121,
|
||||
"story": 8,
|
||||
"label": "none",
|
||||
"predicament": "Someone arranges to accept the suitor she prefers in a way his rival cannot object to."
|
||||
},
|
||||
{
|
||||
"chunk": 122,
|
||||
"story": 9,
|
||||
"label": "newcomer",
|
||||
"predicament": "A man wakes up somewhere strange with no idea how he got there."
|
||||
},
|
||||
{
|
||||
"chunk": 123,
|
||||
"story": 9,
|
||||
"label": "newcomer",
|
||||
"predicament": "A man in an unknown place looks for his one companion and a first clue to how they came to be there."
|
||||
},
|
||||
{
|
||||
"chunk": 124,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man who has always been obeyed tries to give orders somewhere nobody knows or believes who he is."
|
||||
},
|
||||
{
|
||||
"chunk": 125,
|
||||
"story": 9,
|
||||
"label": "your_scheme",
|
||||
"predicament": "Two people in trouble together try to put on a united front and end up blaming each other."
|
||||
},
|
||||
{
|
||||
"chunk": 126,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "Two people who claim to be respected professionals are treated as impostors and forced to do menial work."
|
||||
},
|
||||
{
|
||||
"chunk": 127,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "People brought low insist on their old status, and are mocked for it by those around them."
|
||||
},
|
||||
{
|
||||
"chunk": 128,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A humiliated man pretends to submit while he quietly prepares his revenge."
|
||||
},
|
||||
{
|
||||
"chunk": 129,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man mocked for claiming skill is suddenly needed for it, and names his price."
|
||||
},
|
||||
{
|
||||
"chunk": 130,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man who has gained the upper hand over those who humiliated him enjoys every moment of it."
|
||||
},
|
||||
{
|
||||
"chunk": 131,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man who has secretly caused the trouble only he can cure makes the most of it."
|
||||
},
|
||||
{
|
||||
"chunk": 132,
|
||||
"story": 9,
|
||||
"label": "none",
|
||||
"predicament": "A man who swore he would never pay the person he scorned is forced by suffering to haggle with him."
|
||||
},
|
||||
{
|
||||
"chunk": 133,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "People who scorned two outsiders now depend on them completely, and pay for the privilege."
|
||||
},
|
||||
{
|
||||
"chunk": 134,
|
||||
"story": 9,
|
||||
"label": "power_over_you",
|
||||
"predicament": "A man who got even with his tormentors leaves them knowing they were had."
|
||||
},
|
||||
{
|
||||
"chunk": 135,
|
||||
"story": 10,
|
||||
"label": "strings",
|
||||
"predicament": "Someone given news that might change their life cannot take it in, while the people around them itch to know."
|
||||
},
|
||||
{
|
||||
"chunk": 136,
|
||||
"story": 10,
|
||||
"label": "strings",
|
||||
"predicament": "Someone who has never had luck is told of a windfall and is afraid to believe it."
|
||||
},
|
||||
{
|
||||
"chunk": 137,
|
||||
"story": 10,
|
||||
"label": "strings",
|
||||
"predicament": "A man sees a relative come into money and resents that luck never comes his way."
|
||||
},
|
||||
{
|
||||
"chunk": 138,
|
||||
"story": 10,
|
||||
"label": "after_your_money",
|
||||
"predicament": "A man flattered by new friends is told they are really after a relative's money."
|
||||
},
|
||||
{
|
||||
"chunk": 139,
|
||||
"story": 10,
|
||||
"label": "after_your_money",
|
||||
"predicament": "A man trying to expose flattering suitors trips over his own family's sensibilities."
|
||||
},
|
||||
{
|
||||
"chunk": 140,
|
||||
"story": 10,
|
||||
"label": "rival",
|
||||
"predicament": "Two rivals courting the same person keep topping each other's compliments while the family grows fed up."
|
||||
},
|
||||
{
|
||||
"chunk": 141,
|
||||
"story": 10,
|
||||
"label": "rival",
|
||||
"predicament": "A slow-talking man losing a contest to a glib rival looks for a trick instead of better words."
|
||||
},
|
||||
{
|
||||
"chunk": 142,
|
||||
"story": 10,
|
||||
"label": "rival",
|
||||
"predicament": "A man hires someone to spread a false rumour that will drive off his rival, and the hireling drives a hard bargain."
|
||||
},
|
||||
{
|
||||
"chunk": 143,
|
||||
"story": 10,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man courting for money pretends not to care about it and flatters the one he courts by letting her win."
|
||||
},
|
||||
{
|
||||
"chunk": 144,
|
||||
"story": 10,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A schemer who thinks his deal is done walks away gloating too soon."
|
||||
},
|
||||
{
|
||||
"chunk": 145,
|
||||
"story": 10,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man makes a public show of selfless devotion, sure that it will cost him nothing."
|
||||
},
|
||||
{
|
||||
"chunk": 146,
|
||||
"story": 10,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A schemer whose plan has worked is thanked for his goodness while the ground starts to shift under him."
|
||||
},
|
||||
{
|
||||
"chunk": 147,
|
||||
"story": 10,
|
||||
"label": "your_scheme",
|
||||
"predicament": "A man who faked a disaster to win a prize learns the disaster is real, and he is bound to his promise."
|
||||
},
|
||||
{
|
||||
"chunk": 148,
|
||||
"story": 11,
|
||||
"label": "pretence",
|
||||
"predicament": "A sceptic among believers is pressed to admit whether he has ever seen anything he could not explain."
|
||||
},
|
||||
{
|
||||
"chunk": 149,
|
||||
"story": 11,
|
||||
"label": "newcomer",
|
||||
"predicament": "A young man who arrived to find no one expecting him has to fill empty days in a strange place."
|
||||
},
|
||||
{
|
||||
"chunk": 150,
|
||||
"story": 11,
|
||||
"label": "newcomer",
|
||||
"predicament": "A traveller lost in the dark and the cold must find shelter before night closes in."
|
||||
},
|
||||
{
|
||||
"chunk": 151,
|
||||
"story": 11,
|
||||
"label": "dread",
|
||||
"predicament": "A worn-out traveller must choose between a long walk on and a nearby shelter he has been warned against."
|
||||
},
|
||||
{
|
||||
"chunk": 152,
|
||||
"story": 11,
|
||||
"label": "dread",
|
||||
"predicament": "A stranger taken in by odd hosts who seem keen to keep him wonders whether he has walked into a trap."
|
||||
},
|
||||
{
|
||||
"chunk": 153,
|
||||
"story": 11,
|
||||
"label": "dread",
|
||||
"predicament": "A guest must sit down in the dark with someone he cannot see, on hosts' terms he does not understand."
|
||||
},
|
||||
{
|
||||
"chunk": 154,
|
||||
"story": 11,
|
||||
"label": "dread",
|
||||
"predicament": "A guest left alone with a stranger he cannot see grows afraid, and must decide whether to trust his hosts."
|
||||
},
|
||||
{
|
||||
"chunk": 155,
|
||||
"story": 11,
|
||||
"label": "dread",
|
||||
"predicament": "A frightened man sees what he feared, and learns it is someone who was hurt and ashamed to be seen."
|
||||
},
|
||||
{
|
||||
"chunk": 156,
|
||||
"story": 11,
|
||||
"label": "owning_up",
|
||||
"predicament": "A man who shrank from someone must make amends, and finds they deserve his respect."
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
Pilot, qwen3:14b, shortlist k=8, 14B predicaments, O. Henry scenes 0-39. The output file was later deleted;
|
||||
these figures were copied by hand from the session transcript (score.py output):
|
||||
situated? precision 38% recall 93% (model calls 34, hand 14)
|
||||
label right where both situated: 23% of 13
|
||||
kappa over all labels incl. none: 0.09
|
||||
embedding-only baseline (nearest entry) on hand-situated scenes: 36%
|
||||
hand label inside the shortlist offered: 71%
|
||||
ohenry-q14-dev1.partial: 30 scenes
|
||||
situated? precision 67% recall 100% (model calls 21, hand 14)
|
||||
label right where both situated: 14% of 14
|
||||
kappa over all labels incl. none: 0.23
|
||||
ohenry-q14-dev1.partial fit>=3: 30 scenes
|
||||
situated? precision 62% recall 57% (model calls 13, hand 14)
|
||||
label right where both situated: 0% of 8
|
||||
kappa over all labels incl. none: 0.09
|
||||
6 hand=none model=unspoken fit=3 | Someone and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weig
|
||||
9 hand=home_unseen model=home_unseen fit=2 | Someone longs for connection but is trapped in a life of monotony and shallow conversation.
|
||||
10 hand=none model=caught_out fit=3 | A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confront
|
||||
11 hand=none model=caught_out fit=2 | A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but
|
||||
12 hand=none model=their_secret fit=3 | A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with cou
|
||||
13 hand=none model=owning_up fit=3 | Someone must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety
|
||||
14 hand=none model=compromise_for_money fit=2 | A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circl
|
||||
16 hand=station model=compromise_for_money fit=2 | A man finds himself trapped in an awkward encounter with a shopgirl whose icy beauty challenges his composure,
|
||||
17 hand=station model=pretence fit=2 | A man struggling with his pride and social conventions finds himself awkwardly drawn to a shopgirl, determined
|
||||
18 hand=station model=compromise_for_money fit=2 | A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to e
|
||||
19 hand=station model=unspoken fit=3 | A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life
|
||||
20 hand=station model=pretence fit=3 | A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his word
|
||||
21 hand=station model=strings fit=2 | A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain jou
|
||||
22 hand=station model=pretence fit=3 | A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connect
|
||||
23 hand=none model=hiding_from_close fit=3 | Big someone must navigate the rigid codes of his underground world while seeking a way to break free and claim
|
||||
24 hand=home_unseen model=home_unseen fit=2 | A man struggles to balance his cultural identity and personal life while navigating the expectations of his co
|
||||
25 hand=home_unseen model=habit fit=3 | A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsi
|
||||
26 hand=home_unseen model=hiding_from_close fit=3 | A man struggling to reconcile his desire to display his wife publicly with his fear of judgment finds himself
|
||||
27 hand=home_unseen model=what_they_hid fit=3 | A man struggles to reconcile his pride with the growing awareness of his wife's hidden talents and the shame o
|
||||
28 hand=home_unseen model=hiding_from_close fit=3 | A man struggles to reconcile his quiet life with the sudden, overwhelming attention his wife receives, threate
|
||||
29 hand=home_unseen model=peace_at_home fit=3 | A man faces a choice between social obligation and personal pride, seeking to assert his dignity while preserv
|
||||
@@ -0,0 +1,68 @@
|
||||
== ohenry: 97 stories, 839 scenes; rule: >= 5 scenes, >= 4 stories, no story over 50%
|
||||
scenes labelled: 475 (57%)
|
||||
PASS pretence 52 scenes 12 stories top story 15%
|
||||
PASS benefactor 48 scenes 14 stories top story 17%
|
||||
PASS home_unseen 47 scenes 11 stories top story 15%
|
||||
PASS station 47 scenes 10 stories top story 17%
|
||||
PASS compromise_for_money 38 scenes 13 stories top story 18%
|
||||
PASS newcomer 32 scenes 8 stories top story 22%
|
||||
PASS sure_thing 30 scenes 7 stories top story 23%
|
||||
PASS sacrifice 25 scenes 8 stories top story 28%
|
||||
PASS rival 24 scenes 6 stories top story 29%
|
||||
PASS despised 20 scenes 7 stories top story 30%
|
||||
PASS pressed_to_wrong 19 scenes 5 stories top story 32%
|
||||
PASS wrongly_judged 19 scenes 7 stories top story 26%
|
||||
PASS sign 17 scenes 5 stories top story 35%
|
||||
strings 15 scenes 4 stories top story 53%
|
||||
PASS unspoken 13 scenes 4 stories top story 38%
|
||||
overlooked 9 scenes 3 stories top story 67%
|
||||
PASS homecoming 8 scenes 4 stories top story 50%
|
||||
PASS patron 8 scenes 4 stories top story 50%
|
||||
habit 4 scenes 3 stories top story 50%
|
||||
-> 16 recurring situations of 19 present
|
||||
|
||||
== wharton: 10 stories, 262 scenes; rule: >= 5 scenes, >= 3 stories, no story over 50%
|
||||
scenes labelled: 129 (49%)
|
||||
PASS caught_out 12 scenes 4 stories top story 50%
|
||||
PASS compromise_for_money 12 scenes 4 stories top story 50%
|
||||
PASS past_comes_back 12 scenes 3 stories top story 50%
|
||||
PASS what_they_hid 10 scenes 6 stories top story 20%
|
||||
PASS peace_at_home 8 scenes 5 stories top story 38%
|
||||
PASS newcomer 8 scenes 3 stories top story 50%
|
||||
PASS rival 8 scenes 4 stories top story 50%
|
||||
PASS power_over_you 8 scenes 4 stories top story 50%
|
||||
PASS sacrifice 8 scenes 5 stories top story 38%
|
||||
PASS owning_up 8 scenes 4 stories top story 38%
|
||||
PASS home_unseen 7 scenes 4 stories top story 43%
|
||||
PASS go_between 6 scenes 3 stories top story 50%
|
||||
PASS waiting 6 scenes 3 stories top story 50%
|
||||
PASS own_words_back 6 scenes 3 stories top story 50%
|
||||
PASS strings 5 scenes 4 stories top story 40%
|
||||
PASS their_secret 5 scenes 3 stories top story 40%
|
||||
-> 16 recurring situations of 16 present
|
||||
|
||||
== jacobs: 12 stories, 157 scenes; rule: >= 5 scenes, >= 3 stories, no story over 50%
|
||||
scenes labelled: 137 (87%)
|
||||
PASS your_scheme 23 scenes 8 stories top story 39%
|
||||
PASS dread 12 scenes 4 stories top story 42%
|
||||
rival 11 scenes 3 stories top story 55%
|
||||
power_over_you 11 scenes 3 stories top story 82%
|
||||
hiding_from_close 10 scenes 2 stories top story 80%
|
||||
PASS caught_out 10 scenes 5 stories top story 40%
|
||||
PASS let_another_take_blame 8 scenes 5 stories top story 50%
|
||||
strings 8 scenes 2 stories top story 62%
|
||||
past_comes_back 8 scenes 3 stories top story 62%
|
||||
peace_at_home 5 scenes 2 stories top story 60%
|
||||
PASS pressed_to_wrong 5 scenes 4 stories top story 40%
|
||||
after_your_money 5 scenes 2 stories top story 60%
|
||||
PASS cant_prove_it 5 scenes 3 stories top story 40%
|
||||
pretence 5 scenes 2 stories top story 80%
|
||||
unwelcome_in_home 4 scenes 3 stories top story 50%
|
||||
newcomer 4 scenes 2 stories top story 50%
|
||||
owning_up 3 scenes 2 stories top story 67%
|
||||
-> 6 recurring situations of 17 present
|
||||
|
||||
== catalogue coverage: entries found in 3 / 2 / 1 corpora
|
||||
3: rival, strings, newcomer
|
||||
2: compromise_for_money, past_comes_back, pretence, caught_out, home_unseen, peace_at_home, power_over_you, pressed_to_wrong, sacrifice, owning_up
|
||||
1: sure_thing, your_scheme, after_your_money, hiding_from_close, their_secret, what_they_hid, wrongly_judged, let_another_take_blame, cant_prove_it, station, unwelcome_in_home, benefactor, patron, go_between, own_words_back, homecoming, dread, waiting, unspoken, overlooked, sign, despised, habit
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Score classifier output against the hand labels. No inference.
|
||||
|
||||
python3 score.py out/ohenry-q14.json [more files] # or --selftest
|
||||
|
||||
Reports: (1) does it find the situated scenes (label vs none); (2) on scenes both call situated, is the
|
||||
label right; (3) Cohen's kappa over everything; (4) the embedding-only baseline (nearest entry, no
|
||||
model) on the same scenes; (5) the recurring situations a pack would get under the one shared rule,
|
||||
from the model's labels against the hand labels. The recall of the K-nearest shortlist is reported
|
||||
too: a hand label that was never offered cannot be chosen.
|
||||
"""
|
||||
import collections, json, pathlib, random, sys
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
from catalogue_src import recurs, load_corpus
|
||||
|
||||
def kappa(a, b):
|
||||
n = len(a); po = sum(x == y for x, y in zip(a, b)) / n
|
||||
ca, cb = collections.Counter(a), collections.Counter(b)
|
||||
pe = sum(ca[k] * cb[k] for k in ca) / n / n
|
||||
return (po - pe) / (1 - pe) if pe < 1 else 1.0
|
||||
|
||||
def score(corpus, hand, pred, nearest=None, cands=None, name=''):
|
||||
chunks = sorted(pred)
|
||||
h = [hand[c] for c in chunks]; p = [pred[c] for c in chunks]
|
||||
tp = sum(x != 'none' and y != 'none' for x, y in zip(h, p))
|
||||
prec = tp / max(1, sum(y != 'none' for y in p)); rec = tp / max(1, sum(x != 'none' for x in h))
|
||||
both = [(x, y) for x, y in zip(h, p) if x != 'none' and y != 'none']
|
||||
acc = sum(x == y for x, y in both) / max(1, len(both))
|
||||
lines = [f'{name}: {len(chunks)} scenes',
|
||||
f' situated? precision {prec:.0%} recall {rec:.0%} (model calls {sum(y != "none" for y in p)}, hand {sum(x != "none" for x in h)})',
|
||||
f' label right where both situated: {acc:.0%} of {len(both)}',
|
||||
f' kappa over all labels incl. none: {kappa(h, p):.2f}']
|
||||
if nearest:
|
||||
sit = [c for c in chunks if hand[c] != 'none']
|
||||
lines.append(f' embedding-only baseline (nearest entry) on hand-situated scenes: {sum(nearest[c] == hand[c] for c in sit) / max(1, len(sit)):.0%}')
|
||||
lines.append(f' hand label inside the shortlist offered: {sum(hand[c] in cands[c] for c in sit) / max(1, len(sit)):.0%}')
|
||||
chunks_all, _, _ = load_corpus(corpus); story = [c['story'] for c in chunks_all]
|
||||
def passing(lab):
|
||||
mem = collections.defaultdict(list)
|
||||
for c, l in lab.items():
|
||||
if l != 'none': mem[l].append(c)
|
||||
return {l: m for l, m in mem.items() if recurs(m, story)[0]}
|
||||
ph = passing({c: hand[c] for c in chunks}); pp = passing({c: pred[c] for c in chunks})
|
||||
common = set(ph) & set(pp)
|
||||
lines.append(f' recurring situations: hand {len(ph)}, model {len(pp)}, both {len(common)}')
|
||||
lines.append(f' model only: {", ".join(sorted(set(pp) - set(ph))) or "-"}')
|
||||
lines.append(f' hand only: {", ".join(sorted(set(ph) - set(pp))) or "-"}')
|
||||
for l in sorted(common, key=lambda l: -len(ph[l])):
|
||||
a, b = set(ph[l]), set(pp[l])
|
||||
lines.append(f' {l:24s} hand {len(a):3d} model {len(b):3d} overlap {len(a & b):3d} (model precision {len(a & b) / len(b):.0%}, recall {len(a & b) / len(a):.0%})')
|
||||
return '\n'.join(lines)
|
||||
|
||||
if __name__ == '__main__':
|
||||
out = []
|
||||
if sys.argv[1:] == ['--selftest']:
|
||||
lab = json.load(open(HERE / 'labels-ohenry.json'))['labels']; hand = {x['chunk']: x['label'] for x in lab}
|
||||
out.append(score('ohenry', hand, dict(hand), name='SELFTEST hand vs itself (expect 100% / kappa 1.00)'))
|
||||
rnd = random.Random(1); ids = sorted(set(hand.values()))
|
||||
out.append(score('ohenry', hand, {c: rnd.choice(ids) for c in hand}, name='SELFTEST random labels (expect kappa ~0)'))
|
||||
for f in sys.argv[1:]:
|
||||
if f.startswith('--'): continue
|
||||
corpus = pathlib.Path(f).stem.split('-')[0]
|
||||
lab = json.load(open(HERE / f'labels-{corpus}.json'))['labels']; hand = {x['chunk']: x['label'] for x in lab}
|
||||
res = json.load(open(f))['results']
|
||||
minfit = int(next((a.split('=')[1] for a in sys.argv if a.startswith('--minfit=')), 0))
|
||||
rng = next((a.split('=')[1] for a in sys.argv if a.startswith('--range=')), None)
|
||||
if rng: lo, hi = map(int, rng.split(':')); res = [r for r in res if lo <= r['chunk'] < hi]
|
||||
pred = {r['chunk']: (r['label'] if (r.get('fit') or 3) >= minfit else 'none') for r in res}
|
||||
out.append(score(corpus, hand, pred, {r['chunk']: r['nearest'] for r in res}, {r['chunk']: r['candidates'] for r in res}, name=pathlib.Path(f).stem + (f' fit>={minfit}' if minfit else '') + (f' scenes {rng}' if rng else '')))
|
||||
print('\n\n'.join(out))
|
||||
Reference in New Issue
Block a user