Compare commits
2
Commits
main
...
e5617b86ba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5617b86ba | ||
|
|
fa3769d0fe |
@@ -0,0 +1,14 @@
|
||||
# story-to-pack lives on its own branch and is never imported by the game.
|
||||
|
||||
# The repo root ignores *.log. Here the run logs are the record of results, so keep them.
|
||||
!*.log
|
||||
|
||||
# Inference-host telemetry: large, and it names the host.
|
||||
probe/gpu-logs/
|
||||
|
||||
# Rebuildable caches — gate2.py, gate3.py and signals.py recreate them on first use.
|
||||
probe/sim.pkl
|
||||
probe/null.pkl
|
||||
probe/null-samples.pkl
|
||||
probe/coassoc.json
|
||||
__pycache__/
|
||||
@@ -0,0 +1,275 @@
|
||||
# story-to-pack — design note
|
||||
|
||||
A tool that builds a playable The Ladder content pack from a corpus of stories.
|
||||
|
||||
**Status (2026-09-14): direction agreed, nothing built.** The decisions below were made in
|
||||
conversation on 2026-09-10 and had until now existed only in that session's transcript. The
|
||||
architecture rests on one assumption that has so far **failed** its first test (see
|
||||
[Assumptions](#assumptions-under-test)); nothing past the probes should be built until it passes.
|
||||
|
||||
This is a design note, not a specification. It records what was decided and why, so that the next
|
||||
step is argued from it rather than re-derived.
|
||||
|
||||
---
|
||||
|
||||
## What it is for
|
||||
|
||||
Feed it a body of fiction set in one world — say 20–30 short stories — plus a handful of structural
|
||||
numbers, and get back a content pack the game can load and that its own conformance checks judge
|
||||
viable. Then play it, hand-edit it, and retune it from play logs.
|
||||
|
||||
The Ladder's founding commitment (Planning/01 §9) is that a new setting is purely data. This tool
|
||||
tests how far that goes: whether "data" can come from a corpus rather than a designer.
|
||||
|
||||
## Decisions
|
||||
|
||||
| Area | Decision |
|
||||
| --- | --- |
|
||||
| **Inputs** | A thematically coherent corpus (one world), 20–30 stories of 2–15 pages. Plus: number of stages, turns per stage, number of promotions, number of characters, and a one-line player role written by the designer. Early development uses much less text. |
|
||||
| **Corpus** | Saved permanently beside the pack. Read once, at fixed depth. **Provenance recorded:** which chunks fed which event, even though nothing reads it yet. |
|
||||
| **Artifact** | **One.** The generated pack is the source of truth from the moment it is written. The designer and the tool both edit that same file in place. Seed text is a seed, not a source: there is no regenerate-and-merge. |
|
||||
| **Pipeline** | Chunk → embed → cluster → select recurring situations → extract prose → assign state paths and directions → search numbers → verify → repair. |
|
||||
| **The model's job** | Selection and classification. **It never chooses a number.** Its generative tasks are entity substitution and, only where needed, filling out options. |
|
||||
| **Inference** | Once, up front, checkpointed and cached. The numeric search and every repair iteration are pure computation and never call a model. This is what makes a small local model viable. |
|
||||
| **Stat vocabulary** | A fixed mechanical spine (currency, debt, standing, two skills, flags, notable, per-character relationship stats). The corpus supplies the *labels*, not the structure. |
|
||||
| **Cast** | Recurring **roles** harvested across the corpus (the authority, the rival, the peer), not people from any one story. Characters are minted for the roles and entity names are substituted in extracted prose. |
|
||||
| **Stages** | Assigned automatically by the protagonist's standing in each scene, binned and balanced across stages. Overridable by hand. |
|
||||
| **Event count** | Derived from turns per stage, with a sufficiency report *before* inference is spent: "this corpus yields N situations, the pack needs M". |
|
||||
| **Options** | 3–5 per event: 1–3 fixed archetypes ("say nothing"), 1–3 drawn from other scenes in the cluster, and 1–3 generated only to fill gaps. Deduplicated **mechanically** — two options with the same effects are one option. |
|
||||
| **Description variants** | An event can carry several descriptions, sharing one general option set. This needs an engine change (see Open questions). |
|
||||
| **Viability** | Fully automatic — a human does not have to play a pack for it to count as viable. A **graded score**, not pass/fail, so extra time improves it. The search **always keeps the best-scoring candidate**, never the most recent. When it cannot reach viable, it reports precisely which checks it could not satisfy. |
|
||||
| **Time and quality** | Start fast and iterate; later, overnight runs for higher quality. Time buys **numeric search depth and prose fidelity** — not corpus reach. |
|
||||
| **Retuning** | Deterministic, driven by the play log `npm run analyze` already reads. Not model-mediated. |
|
||||
| **Extension** | Pulling more corpus into an existing pack is **out of scope** for now. Provenance is recorded so it stays possible. |
|
||||
| **Home** | `theladder/tools/`, never imported by `src/`, `content/` or `test/`. Its own dependencies stay out of the game. |
|
||||
| **Hardware** | Must be able to run on a low-powered local inference host, taking longer if it has to. |
|
||||
|
||||
## Why the hard part is numbers, not prose
|
||||
|
||||
Counted from the two hand-written packs:
|
||||
|
||||
| | Corporate Ladder | The Frontier |
|
||||
| --- | --- | --- |
|
||||
| Prose strings | 219 | 211 |
|
||||
| Numeric decisions | 347 | 427 |
|
||||
| Stable ids | 125 | 120 |
|
||||
|
||||
A pack is roughly 35% prose and 65% numbers, and every defect the project has hit — the debt trap,
|
||||
the rent retune, the negotiation cliff — lived in the numbers. The corpus supplies the prose; the
|
||||
tool has to invent the rest, and has to be able to show it is not a trap.
|
||||
|
||||
Randomising The Frontier's numbers while keeping its prose and structure: random magnitudes pass the
|
||||
conformance checks 3% of the time, random directions 0%, and a crude four-rule repair loop 42% (5 of
|
||||
12 runs converged). The search space is not hopeless, but it is not a broad basin either — hence a
|
||||
real search against a graded score.
|
||||
|
||||
## Why recurrence is the central problem
|
||||
|
||||
Hand-written packs are almost entirely **recurring** situations: 29 of 31 events in Corporate Ladder
|
||||
and 28 of 29 in The Frontier can fire more than once, and a 41-turn playtest saw 12 distinct
|
||||
situations an average of 3.4 times each. Stories are the opposite: every scene happens once.
|
||||
|
||||
So selection is not "pick 30 good scenes". It is **find the situations this corpus keeps returning
|
||||
to**. Twenty stories in one world should contain eight variations on "a superior asks you to cover
|
||||
something up"; clustering those is what yields one recurring event with several descriptions. One
|
||||
long novel is the hard case and is not the target.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- **Planning/01 §10 is not violated by a hosted model here.** §10 says the *game* must never require
|
||||
a hosted inference service. The generator is a build-time tool whose output is plain data with no
|
||||
runtime dependency; a shipped `.html` still plays offline. Record this in theladder's DECISIONS
|
||||
when the tool lands there.
|
||||
- **There are two provider slots, not one.** Embeddings and generation are configured separately.
|
||||
Anthropic offers generation but no embeddings API, so any mix must keep embeddings local.
|
||||
- **The inference host is shared.** Other work sends requests to the same server. Clients run one
|
||||
request at a time with a pause between requests, set a short `keep_alive`, and read the host from
|
||||
the environment (`STP_OLLAMA`) — never from a tracked file. Every run is agreed with its owner
|
||||
before it starts.
|
||||
|
||||
## Assumptions under test
|
||||
|
||||
The architecture rests on three assumptions. All three are cheap to test, and the first could
|
||||
invalidate everything else.
|
||||
|
||||
### 1. A coherent corpus clusters into recurring situations — FAILED in naive form
|
||||
|
||||
**Test (2026-09-10):** O. Henry's *Four Million* cycle — four Gutenberg volumes, 94 stories, 244,690
|
||||
words — cut into 838 scenes of ~300 words, embedded with `nomic-embed-text`, clustered with k-means.
|
||||
|
||||
**Result:** at the best k, 18 clusters were both tight and drawn from four or more stories. But
|
||||
reading them showed most were 60–79% **one** story plus stragglers. Measured by concentration, only
|
||||
**3 of 18** were genuinely recurring, covering **2% of the corpus**. At scene length, embedding
|
||||
similarity tracks *which story a scene is from* — names, setting, narrator voice — not *what kind of
|
||||
situation it is*.
|
||||
|
||||
**Fix under test:** summarise each scene into one generic sentence with no proper names, embed the
|
||||
summary, and cluster those instead. This probe is in `probe/`.
|
||||
|
||||
**Baseline re-measured (2026-09-14)** on the rebuilt corpus — prose embeddings made on a GPU host,
|
||||
same model, same 838 chunks. Seed 11 at k = 100 reproduces the 2026-09-10 figures exactly (18 sharp
|
||||
clusters spanning ≥ 4 stories, 3 genuinely recurring, 2% coverage). Seeds 12 and 13 give 7 and 7,
|
||||
covering 8% and 10%: the old 2% was one unlucky initialisation, and the true baseline is poor rather
|
||||
than hopeless.
|
||||
|
||||
**Pass mark, fixed before any summary existed.** Judged at **k = 60 and k = 100**, medians over
|
||||
three seeds, summary embeddings against prose embeddings as reported by `probe/measure.py`:
|
||||
|
||||
1. Median dominant-story share of clusters of size ≥ 5 falls to **40% or below** at both k.
|
||||
2. **Tight-recurring coverage** — recurring clusters (≥ 4 stories, dominant ≤ 40%) whose coherence is
|
||||
at or above that file's median cluster — reaches **at least 10% of the corpus** at both k. (A
|
||||
relative bar was written first and dropped once the baseline came in at 2% and 0%, where "double"
|
||||
means nothing. 10% of 838 scenes is ~84 — enough raw material for a stage's worth of situations.)
|
||||
3. Reading three sampled tight-recurring clusters, each is recognisably **one kind of situation**,
|
||||
not a prose register ("men walking through the city") or a vocabulary cluster.
|
||||
|
||||
The prose baseline numbers the first two are judged against are in `probe/measure-prose.log`.
|
||||
|
||||
**Result (2026-09-14): criteria 1 and 2 pass decisively; criterion 3 passes only in part.**
|
||||
|
||||
| k | Median dominant share, prose → summaries | Tight-recurring coverage, prose → summaries |
|
||||
| --- | --- | --- |
|
||||
| 60 | 57% → **22%** | 2% → **26%** |
|
||||
| 100 | 67% → **28%** | 0% → **19%** |
|
||||
|
||||
Pipeline as run: `qwen2.5:3b-instruct` summaries with the v2 prompt (no example roles), names removed
|
||||
by `strip_names.py`, embedded with `nomic-embed-text`. Logs: `probe/measure-summary.log`,
|
||||
`probe/sample-k60.log`, `probe/sample-k100.log`.
|
||||
|
||||
Reading the seeded samples (three at each k; one cluster appeared at both, so five distinct):
|
||||
|
||||
- **Clearly one situation (2):** a spouse scheming against or confronting a spouse over infidelity
|
||||
(15 scenes, 10 stories); a woman made the centre of attention while men compete for her (5 scenes,
|
||||
5 stories).
|
||||
- **Broadly one situation (2):** a secret identity or past revealed in a romantic encounter, often over
|
||||
dinner (15 scenes, 13 stories); hosts and guests jockeying for social rank at a dinner (6, 6).
|
||||
- **Not a situation (1):** fortune-tellers, adventurers, a found tyre, a graft inquiry — held together
|
||||
by "seeks", "fortune" and "mysterious" and dominated by three stories. It is the same cluster at
|
||||
both k.
|
||||
|
||||
The rule as written requires every sample to read as one situation, so **strictly this is not a full
|
||||
pass**, and it is recorded that way rather than rounded up.
|
||||
|
||||
**Follow-up: the selection step is designed and tested in [`SELECTION.md`](SELECTION.md).** A
|
||||
two-signal gate (cross-story cohesion, and no single word dominating) accepted 10 clusters across
|
||||
development and test, all of them situations; it rejects the vocabulary clusters this result found.
|
||||
But it keeps only 5 of ~40 candidates — far short of what a pack needs — so recall is the open problem.
|
||||
Three recall-recovery methods were then tested on a fresh partition and **none met its pre-registered
|
||||
bar**: re-clustering found nothing new, consensus clustering found only sharp situations (3 of 3) but
|
||||
too few, and prune-then-grow found 10 distinct situations with too much noise. The finding that
|
||||
matters is that the gate is size-dependent in both directions, so it has to be made size-invariant
|
||||
before recall can be recovered.
|
||||
A second gate, G2, built for that (cohesion against a same-size random baseline, a significance test
|
||||
for shared words), **failed validation on fresh data**: it accepts three to four times as many
|
||||
clusters as the first gate at about the same precision (0.79), but still rejects every cluster under
|
||||
10 members and still lets growth run to the cap. A fixed threshold on a z-score reverses the size bias
|
||||
rather than removing it.
|
||||
A third gate, G3 (a percentile against same-size random sets, plus a mandatory per-member floor),
|
||||
**failed all four validation criteria and did worse than G2**: 17 accepted across fresh partitions,
|
||||
only 8 of them situations. The random-set baseline turned out to be the wrong comparison — any
|
||||
k-means cluster beats random scenes — and the per-member floor is noisiest, and so most lenient,
|
||||
exactly where clusters are smallest.
|
||||
A fourth gate, G4, had the inference model read each cluster and say which summaries share one
|
||||
situation. It **failed with both `qwen2.5:3b-instruct` and `qwen3:14b`** (primary precision 0.48 and
|
||||
0.52 against G2's 0.73): the models answer with abstractions ("power dynamics", "someone wants
|
||||
something from someone") that most summaries fit. Observed but not pre-registered: the 14B model AND
|
||||
G2 accepted 10 clusters, all situations — precise, narrow, and untested on fresh data. The substantive conclusion: summarising
|
||||
before embedding **does** break the story-identity effect that sank the naive design, and most of
|
||||
what clusters is recognisably situational — the recurrence premise is alive, not proven. The design
|
||||
needs a **selection step that rejects vocabulary clusters**, not only a clustering step.
|
||||
|
||||
Known defects in this run, none large enough to change the verdict:
|
||||
|
||||
- `first_sentence` cut 7 of 838 summaries that begin with an initial ("E. Rushmore Coglan…") to
|
||||
"E." — single-letter initials and "Gen." are not handled.
|
||||
- Heavy use of "someone" is itself a shared token across summaries with many characters. Whether it
|
||||
pulls clusters together has not been measured; a replacement by role or a neutral mix would test it.
|
||||
- A few names that are also common words survive stripping ("Silver", "Cherry", "Grace", "the Kid").
|
||||
|
||||
**Why k ≥ 60, and why a tightness filter.** The first version of this pass mark said "median dominant
|
||||
≤ 40% at k ≥ 40" — and the prose baseline, the approach known to fail, passed it at k = 40 (36%).
|
||||
Two things were wrong. Small k forces clusters to merge stories whatever the embeddings encode: on
|
||||
synthetic vectors carrying nothing but story identity, `measure.py` reports 36% at k = 25, 53% at k
|
||||
= 40 and 100% at k ≥ 60, while vectors with planted cross-story situations report 11–17% at every k
|
||||
≥ 40. And without a tightness filter, the loose clusters the 2026-09-10 reading identified as prose
|
||||
register count as recurring. The tightness cutoff is relative to each file's own median because an
|
||||
absolute cosine threshold does not transfer between 300-word scenes and one-sentence summaries.
|
||||
|
||||
If it fails, the recurrence premise needs rethinking before any design goes further.
|
||||
|
||||
### 2. A small model can classify against a closed path vocabulary
|
||||
|
||||
Roughly 100+ calls per pack deciding which state paths an option moves and in which direction.
|
||||
Malformed or wrong output at a meaningful rate changes the design (batching, retries, a bigger
|
||||
model). **Untested.**
|
||||
|
||||
### 3. A small model can substitute entity names without wrecking prose
|
||||
|
||||
The main generative task, run on every description. **Untested.**
|
||||
|
||||
The summarisation probe in assumption 1 incidentally gives the first evidence on 2 and 3: it asks a
|
||||
small model for constrained, name-free output and the results can be read.
|
||||
|
||||
**First evidence (2026-09-14, `qwen2.5:3b-instruct`, two 40-scene pilots):**
|
||||
|
||||
- **It copies examples from the prompt into the output.** A prompt that illustrated roles with "a
|
||||
clerk", "a landlady", "a rich man", "a city park" got those phrases back 18 times — in scenes
|
||||
containing **none** of them, every one invented. For this probe that is worse than noise: shared
|
||||
invented phrases make unrelated scenes cluster together and fake exactly the recurrence being
|
||||
measured. Removing the examples brought it to 1. **Rule: prompts for a small model name no example
|
||||
of the output vocabulary.**
|
||||
- **It does not follow "never use a name."** 25 of 40 summaries kept names with the first prompt,
|
||||
and 27 of 40 with a stricter one plus an automatic retry — the retry fired 35 times, nearly doubled
|
||||
the run, and fixed almost nothing. Names are therefore removed **deterministically** afterwards
|
||||
(`probe/strip_names.py`): a lexicon built from the corpus, of words seen capitalised mid-sentence
|
||||
and almost never in lowercase. It misses names that are also common words ("Bill", "the Kid",
|
||||
"New" in "New York"). That bears directly on assumption 3: **entity substitution should be done by
|
||||
code over a lexicon, with the model's output treated as untrusted.**
|
||||
- **Throughput is not the constraint.** On a GTX 1080 Ti, a scene summary takes ~0.6 s of model time
|
||||
(about 1.1 s including a deliberate pause between requests), and embedding runs at ~16 texts/s.
|
||||
A 838-scene corpus summarises in roughly a quarter of an hour.
|
||||
|
||||
## Open questions
|
||||
|
||||
- **Description variants need an engine change.** Events carry one description today. Scope it
|
||||
before building, and record it in theladder's DECISIONS — it is the first engine change content
|
||||
generation would force.
|
||||
- **The test strategies hardcode the stat spine.** `theladder/test/fixtures/strategies.js` reads the
|
||||
literal path `money` and the prefix `relationships.`. A pack that named its currency `coin` would
|
||||
make four of six archetypes stop discriminating while the sweep still ran and reported. The fixed
|
||||
spine satisfies this by construction; it is also a coupling that should be made explicit.
|
||||
- **Options versus variants.** A cluster-derived option is tied to the scene it came from and may
|
||||
read wrongly under the event's other descriptions. The more variants an event has, the more its
|
||||
options must come from the fixed archetypes.
|
||||
- **More options, harder search.** Moving from ~3.1 to ~4 options per event adds roughly 90 effect
|
||||
magnitudes to a search that already converged only 5 times in 12 with crude repair.
|
||||
- **Prompts may not transfer between models.** Prompts developed against a capable model are
|
||||
typically under-specified for a 3B one. The small-model question stays open until it is tested on
|
||||
a small model, however well a larger one does.
|
||||
- **Story splitting is per-volume fiddly.** Segmentation needs no model, but each source's format
|
||||
needs its own pattern. A production tool needs a report when a volume does not split cleanly.
|
||||
The probe's `split.py` yields 94 stories from 98 contents entries: it silently drops titles
|
||||
containing quotation marks or an em dash ("LITTLE SPECK IN GARNERED FRUIT", "THE THING'S THE PLAY",
|
||||
"WHAT YOU WANT", "THE GUILTY PARTY — AN EAST SIDE TRAGEDY"). The 2026-09-10 run dropped the same
|
||||
ones, so it is left alone to keep the before/after comparison exact.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
story-to-pack/
|
||||
DESIGN.md this note
|
||||
recovered/ the 2026-09-10 scripts, as recovered from the transcript; not re-run
|
||||
probe/ the working probe for assumption 1
|
||||
corpus/ Gutenberg source texts (rebuildable; see recovered/README.md)
|
||||
split.py volumes -> stories.json
|
||||
chunk.py stories -> chunks.json (~300-word scenes)
|
||||
ollama.py the only module that talks to an inference host
|
||||
embed.py texts -> vectors, checkpointed
|
||||
summarise.py scenes -> one generic sentence each, checkpointed
|
||||
strip_names.py summaries -> summaries with names replaced, from a corpus-built lexicon
|
||||
measure.py clustering concentration report for any embedding file
|
||||
sample_clusters.py seeded sample of tight-recurring clusters, for reading (criterion 3)
|
||||
summaries-v1-pilot.json, summaries-v2-pilot-retry.json the two 40-scene pilots, kept as evidence
|
||||
*.log run and measurement output
|
||||
gpu-logs/ the inference host's power/link/kernel logs for the 2026-09-14 run
|
||||
```
|
||||
@@ -0,0 +1,103 @@
|
||||
# story-to-pack
|
||||
|
||||
Research toward a tool that builds a The Ladder content pack from a corpus of stories.
|
||||
|
||||
**Independent of the game.** Nothing in `src/`, `content/` or `test/` imports anything here, and
|
||||
nothing here is part of `npm test` or `npm run build`. It lives on the `story-to-pack` branch so it is
|
||||
versioned alongside the pack format it targets, without touching `main`.
|
||||
|
||||
- **What was decided and why:** [`DESIGN.md`](DESIGN.md)
|
||||
- **How situations are selected, and every test of it:** [`SELECTION.md`](SELECTION.md)
|
||||
- **The 2026-09-10 scripts, recovered from a transcript:** [`recovered/`](recovered/README.md)
|
||||
|
||||
## Status
|
||||
|
||||
Selection was settled as **gate G2 followed by a structured human review** (see below). Every automated
|
||||
alternative tried — three more geometric gates and two model-judged gates — was worse on fresh,
|
||||
blind-labelled data. The first real review then found G2's groups were **topics** (artists, police,
|
||||
hotels), not situations. **Now under test:** re-describe every scene as its main person's
|
||||
**predicament** with qwen3:14b, regroup, and read the groups (SELECTION.md, "Predicaments"). Nothing
|
||||
downstream of selection (paths and directions, numbers, pack output) is built yet.
|
||||
|
||||
## Layout
|
||||
|
||||
`probe/` holds the v1 data every G1–G4 result was measured on. `probe/v2/` is the corrected corpus
|
||||
(97 stories, 839 scenes: v1's splitter merged three quoted-title stories into their neighbours), built by
|
||||
running the same scripts from inside `v2/` (`python3 ../split.py`, and so on).
|
||||
|
||||
```
|
||||
probe/
|
||||
corpus/ O. Henry's Four Million cycle, Project Gutenberg (public domain)
|
||||
split.py, chunk.py volumes -> stories.json -> chunks.json (838 scenes in v1, 839 in v2)
|
||||
carry_summaries.py v1 summaries onto v2 scenes; only new scene text goes to the model
|
||||
redescribe.py scene -> its main person's predicament, via the model -> predicaments.json
|
||||
topic_words.py, topic_share.py how much groups are held together by a job, relationship or place word
|
||||
pilot_page.py side-by-side page for the 3B / 14B predicament pilot -> pilot-compare.html
|
||||
groups_page.py reading page for regrouped predicaments -> v2/groups-*.html
|
||||
ollama.py the only module that talks to an inference host (host from STP_OLLAMA)
|
||||
summarise.py scene -> one sentence, via the model -> summaries.json
|
||||
strip_names.py names removed deterministically -> summaries_clean.json
|
||||
embed.py texts -> vectors -> embeddings.json, summary_embeddings.json
|
||||
measure.py, signals.py, gate2.py, gate3.py, judge.py … the selection experiments
|
||||
labels-*.json, sheet-*.txt, candidates-*.json, *-map.json blind labels and what they labelled
|
||||
review.py G2 on a partition -> REVIEW.html and candidates.json
|
||||
review_page.py renders the page; picks out role words for each group
|
||||
review_page.html the page template
|
||||
review_page_logic.cjs the rules the page applies (also run by the Node tests)
|
||||
review_format.py the saved review's format, checks and conversion
|
||||
apply_review.py saved review -> situations.json
|
||||
test_review.py, test_review_page.mjs tests
|
||||
review/<run>/ REVIEW.html, candidates.json, and situations.json once applied
|
||||
```
|
||||
|
||||
Not committed (see `.gitignore`): the inference host's power and kernel logs, and caches that the
|
||||
scripts rebuild on first use (`sim.pkl`, `null*.pkl`, `coassoc.json`). The embeddings are committed
|
||||
(about 16 MB) because recreating them needs the inference host.
|
||||
|
||||
## Running
|
||||
|
||||
Everything runs from `probe/` with Python 3 and no third-party packages.
|
||||
|
||||
Anything that calls a model reads the host from the environment and never from a file:
|
||||
|
||||
```sh
|
||||
export STP_OLLAMA=http://<inference-host>:11434
|
||||
```
|
||||
|
||||
## Reviewing situations
|
||||
|
||||
The review happens in a web page. Nobody edits JSON.
|
||||
|
||||
```sh
|
||||
cd probe
|
||||
python3 review.py # k = 60, seed 24 -> review/k60-s24/REVIEW.html
|
||||
```
|
||||
|
||||
1. **Open `review/k60-s24/REVIEW.html` in a browser** (double-click it; nothing to install). The first
|
||||
screen explains the job with examples.
|
||||
2. **One group per screen.** For each: keep it, drop it, or mark it the same as a group already kept;
|
||||
untick scenes that don't fit; and fill in "A ___ wants ___ from ___", with the people named in its
|
||||
scenes offered as click-to-fill words. The page shows as you go whether a group still has enough
|
||||
scenes, and colours each group in the strip across the top. Keys: K keep, D drop, ← →.
|
||||
3. **Progress saves in the browser** after every change, so you can close it and come back.
|
||||
4. **"Finish & save"** lists anything unfinished and saves `review-k60-s24.json`, usually to Downloads.
|
||||
5. **Then:**
|
||||
|
||||
```sh
|
||||
python3 apply_review.py review/k60-s24
|
||||
```
|
||||
|
||||
It finds the saved review in Downloads by itself, checks everything again, and names any problem the
|
||||
way the page does ("Group 4: kept, but its name is not finished"). Nothing is written until the
|
||||
review passes; then it writes **`situations.json`**: each situation's sentence and roles, the scenes
|
||||
that show it with their stories, and which groups it came from.
|
||||
|
||||
`candidates.json` in the run directory is the program's record for the checker; leave it alone.
|
||||
|
||||
```sh
|
||||
python3 -m unittest test_review # the saved review's checks, the page renderer, apply_review.py
|
||||
node --test test_review_page.mjs # the rules the page applies as you review
|
||||
```
|
||||
|
||||
The page's drawing code is only checked for syntax and for starting up; how it looks and behaves in a
|
||||
real browser has to be tried by a person.
|
||||
@@ -0,0 +1,891 @@
|
||||
# story-to-pack — selecting situations
|
||||
|
||||
> **Decision (2026-09-15): selection is gate G2 followed by a structured human review.** `probe/review.py`
|
||||
> writes `REVIEW.html`, a page where the reviewer keeps, drops or merges each group, unticks scenes and
|
||||
> names the situation; the page saves a review file, and `probe/apply_review.py` checks it and writes
|
||||
> `situations.json` for the next stage. See the README.
|
||||
>
|
||||
> The first version asked the reviewer to read a long Markdown file and edit JSON by hand. The first
|
||||
> real attempt stopped before a single entry: what to decide was unclear, there was too much to read at
|
||||
> once, hand-edited JSON broke easily, and the wording was hard to write. The page answers each of those.
|
||||
> The record below is how the G2 decision was reached: four gate designs and two model-judged gates,
|
||||
> each tested against blind labels.
|
||||
>
|
||||
> **Superseded in part (2026-09-15), see "Predicaments" at the end:** reading the page, the reviewer
|
||||
> found the groups were topics, not situations.
|
||||
|
||||
The step after clustering: from the clusters of summarised scenes, choose the ones that are genuine
|
||||
**recurring situations** — the raw material of a recurring event — and reject the rest. Companion to
|
||||
`DESIGN.md`; this is the step its assumption-1 result called for.
|
||||
|
||||
**Status (2026-09-14): designed and pre-registered below, before any feature was computed or any
|
||||
label was read against a feature.** Results are appended at the end, and nothing above the Results
|
||||
heading is edited after the test runs, except to fix a typo.
|
||||
|
||||
---
|
||||
|
||||
## The problem, from the assumption-1 result
|
||||
|
||||
Summarise-then-embed broke the story-identity effect: clusters now draw from many stories. But of five
|
||||
sampled tight-recurring clusters, one was a **vocabulary cluster** — fortune-tellers, adventurers, a
|
||||
found tyre, a graft inquiry — held together by "seeks", "fortune", "mysterious", and mostly from three
|
||||
stories. The concentration and tightness filters in `measure.py` let it through. A selector has to
|
||||
reject clusters like that one and keep clusters like "a spouse schemes against or confronts a spouse
|
||||
over infidelity".
|
||||
|
||||
## Constraints
|
||||
|
||||
- **No inference.** Selection uses only what is already on disk: the summary embeddings, the prose
|
||||
embeddings, the summary text, and which story each scene came from. It is pure computation, like the
|
||||
numeric search, so it can be re-run and tuned for free. (It also keeps the inference host free for
|
||||
other work.)
|
||||
- **Deterministic.** Seeded clustering; the same inputs give the same selection.
|
||||
- **Pure Python**, like the rest of the probe.
|
||||
|
||||
## What a selected situation is
|
||||
|
||||
A set of scenes, from **at least 4 different stories**, with no story contributing more than 40%,
|
||||
that a reader can name as **one social transaction**: who wants what from whom. It becomes one
|
||||
recurring event; its member scenes become that event's description variants and option material.
|
||||
|
||||
Rejected: story-dominated clusters (already mechanical), **vocabulary clusters** (shared words, no
|
||||
shared transaction), **register clusters** (shared tone or setting, e.g. "men walking through the
|
||||
city"), and **near-duplicates** of an already-selected situation.
|
||||
|
||||
## Candidate signals
|
||||
|
||||
All computed per cluster, from existing vectors and text. Each carries a pre-registered prediction of
|
||||
which way it moves for a genuine situation.
|
||||
|
||||
| # | Signal | Definition | Prediction for a situation |
|
||||
| --- | --- | --- | --- |
|
||||
| S1 | **Cross-story cohesion** | Mean summary-embedding cosine over member pairs from *different* stories | Higher |
|
||||
| S2 | **Cross/within ratio** | S1 divided by the mean cosine over member pairs from the *same* story (1.0 if there are none) | Closer to 1 — a situation is as similar across stories as within one; a cluster held together by a few stories' scenes is not |
|
||||
| S3 | **Effective stories** | Inverse Simpson index of the member story counts, divided by cluster size | Higher — members spread evenly rather than a few stories plus stragglers |
|
||||
| S4 | **Prose agreement** | Mean *prose*-embedding cosine over cross-story member pairs, minus the corpus-wide mean cross-story prose cosine | Higher — a second, independent view: scenes of the same situation should read a little alike even in the original text |
|
||||
| S5 | **Stability** | Mean pairwise co-association of members across 12 k-means runs (k from 50 to 100, several seeds): the share of runs in which two members land together | Higher |
|
||||
| S6 | **Word dependence** | Share of members containing the cluster's single most over-represented content word (log-odds against the whole summary set, stopwords and "someone" excluded) | Higher for a **vocabulary** cluster — one word doing the work |
|
||||
|
||||
S6 is predicted to indicate the failure, the others to indicate success.
|
||||
|
||||
## Protocol
|
||||
|
||||
1. **Candidates.** Cluster the summary embeddings with k = 60. A candidate is a cluster with ≥ 5
|
||||
members, ≥ 4 distinct stories and dominant share ≤ 40% (`measure.py`'s "recurring"; the relative
|
||||
tightness filter is *not* applied — the selector replaces it).
|
||||
2. **Two candidate sets from two partitions.** Development: seed 11. Test: seed 12.
|
||||
3. **Blind labels.** Each set is dumped to a sheet showing, per cluster, only its member summaries in
|
||||
shuffled order — no story titles, no scores, no sizes beyond the lines themselves. Each cluster is
|
||||
labelled by reading:
|
||||
- **2 — one situation:** one nameable social transaction that most members (roughly 70%+)
|
||||
instantiate. The label records its name.
|
||||
- **1 — broad:** a shared setting or theme, but the transaction varies across members.
|
||||
- **0 — not a situation:** vocabulary, register, or nothing nameable.
|
||||
|
||||
The test sheet is labelled before any signal is computed for the test set.
|
||||
4. **Choose the rule on development only.** Compute S1–S6 for the development set. Candidate rules
|
||||
are single thresholds on one signal, or an AND of two. The chosen rule is the one with the highest
|
||||
**precision** (share of accepted clusters labelled 2), subject to accepting at least half of the
|
||||
development set's label-2 clusters; ties go to the simpler rule. Freeze it here, in writing, before
|
||||
step 5.
|
||||
|
||||
*Detail fixed after the labels were written and before any signal was computed:* thresholds are
|
||||
placed midway between adjacent observed development values, never on one. Ties beyond simplicity
|
||||
break on more label-2 clusters accepted, then on precision for label ≥ 1. Two reference points
|
||||
are reported but cannot be chosen: accepting every candidate, and `measure.py`'s tightness filter
|
||||
(coherence at or above the partition's median sized cluster), called B0.
|
||||
5. **Test once.** Apply the frozen rule to the test set. Report precision (label 2, and label ≥ 1),
|
||||
recall of label-2 clusters, and the number accepted. No re-tuning after seeing test results; any
|
||||
later change is a new rule and needs new data to be tested on.
|
||||
6. **Near-duplicates.** Among accepted clusters, merge any pair whose centroids have cosine ≥ a
|
||||
threshold chosen on development by reading the labels' names. Report how many merges happened on
|
||||
the test set and whether each merged pair's names agree.
|
||||
|
||||
## Known limits of this test
|
||||
|
||||
- **The labels are one reader's judgement** — the model running the probe. They are written to a
|
||||
file so the designer can spot-check or overrule them, and the verdict should be read with that in
|
||||
mind.
|
||||
- **Development and test come from the same corpus.** Two partitions of the same 838 scenes share
|
||||
situations, so the test measures stability across clustering, not generalisation to a new corpus.
|
||||
A genuinely held-out test needs a second corpus, which needs embedding, which needs the inference
|
||||
host.
|
||||
- **k is fixed at 60** for the test. The selector's behaviour at other k is not tested.
|
||||
|
||||
## Frozen rule (written before any test signal was computed)
|
||||
|
||||
Chosen on development (40 candidates, 5 labelled 2; must accept ≥ 3), by `probe/select_rule.py choose`:
|
||||
|
||||
> **Accept a candidate when S1 ≥ 0.6119 AND S6 ≤ 0.1603.**
|
||||
> Cross-story cohesion at least 0.6119, and no single over-represented word in more than 16% of members.
|
||||
|
||||
| On development | Accepted | Precision (2) | Precision (≥ 1) | Recall (2) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Accept every candidate | 40 | 0.12 | 0.62 | 1.00 |
|
||||
| B0, tightness filter | 17 | 0.24 | 0.76 | 0.80 |
|
||||
| S1 alone (best single) | 9 | 0.44 | 0.89 | 0.80 |
|
||||
| **Frozen rule** | **5** | **0.60** | **1.00** | **0.60** |
|
||||
|
||||
Accepted on development: D04 (2), D09 (1), D13 (2), D16 (2), D33 (1). Rejected label-2: D15 (S1 0.603),
|
||||
D30 (S6 0.20). Full leaderboard: `probe/choose-dev.log`; signals: `probe/signals-dev.json`.
|
||||
|
||||
Observations recorded now, so they cannot be shaped by the test:
|
||||
|
||||
- **This is very likely overfit.** Five positives, fine-grained thresholds, and an AND of two signals
|
||||
is exactly the setting in which a rule memorises its development set. The test is what decides.
|
||||
- **S1 does most of the work.** It is the best single signal by a wide margin, and it alone rejects
|
||||
the vocabulary cluster that motivated this step (D18, S1 0.605).
|
||||
- **S6 did not behave as predicted.** Its most over-represented word is usually a *story's* word
|
||||
("juggler", "furs", "eviction"), not generic vocabulary, and D18's is low (0.11). It catches D39
|
||||
("young", 1.00) — a vocabulary cluster of a different kind. Its contribution to the frozen rule is
|
||||
a single cut, and may not carry over.
|
||||
- **S2, S3 and S4 were weak on development**, and S5 middling. None of the predicted directions was
|
||||
strongly wrong, but only S1 separated well.
|
||||
|
||||
**Near-duplicates (step 6): centroid cosine cannot do it, so no automatic merge is applied.** Among
|
||||
the five clusters the rule accepts on development, no two share a situation, so there was nothing
|
||||
there to calibrate on. Across all 26 development candidates labelled ≥ 1, the one clear duplicate —
|
||||
D13 and D30, both "a run-in with the police" — has centroid cosine **0.9026**, while five pairs of
|
||||
plainly different situations score higher, up to **0.9326** ("money and marriage" against "schemes,
|
||||
cons and theft"). D05/D23 (artists) sit at 0.9005. Any threshold that merges the duplicate merges
|
||||
different situations first. Summary embeddings place situations about money, romance and conflict
|
||||
in one dense region, so closeness between centroids is not identity of situation.
|
||||
|
||||
Decision, fixed before the test: the test reports, **by label name**, whether any two accepted
|
||||
clusters are the same situation, as a measurement of how often duplicates survive selection. A
|
||||
merge method is an open problem; member overlap across partitions (co-association between two
|
||||
clusters' members) is the candidate to try next, and is untested.
|
||||
|
||||
## Results
|
||||
|
||||
**Test run once, 2026-09-14,** on the seed-12 partition (41 candidates, 5 labelled 2), frozen rule
|
||||
unchanged. Logs: `probe/signals-test.json`, `probe/apply-test.log`.
|
||||
|
||||
| On test | Accepted | Precision (2) | Precision (≥ 1) | Recall (2) |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Accept every candidate | 41 | 0.12 | 0.51 | 1.00 |
|
||||
| B0, tightness filter | 18 | 0.17 | 0.44 | 0.60 |
|
||||
| **Frozen rule** | **5** | **0.40** | **1.00** | **0.40** |
|
||||
|
||||
Accepted: T35 (2) coercion over money; T39 (2) spouse against spouse over infidelity; T05 (1) schemes
|
||||
and cons over money; T07 (1) courtship; T25 (1) trying to impress or win someone's favour.
|
||||
Rejected label-2: T16 newcomer seeks acceptance (S1 0.598), T17 proposal or engagement (S1 0.596),
|
||||
T30 run-in with the police (S1 0.604, S6 0.23).
|
||||
|
||||
### Verdict
|
||||
|
||||
**The selector is precise and narrow.** Across both sets it accepted 10 clusters, and **all 10 are
|
||||
situations or broad situational themes; none is a vocabulary, register or grab-bag cluster.**
|
||||
Accepting everything gives about half that. The tightness filter used in the assumption-1 test is
|
||||
*worse* than accepting everything on the test set (0.44 against 0.51) — it keeps vocabulary clusters,
|
||||
which are tight.
|
||||
|
||||
**It does not find most of the situations.** Recall of label-2 clusters fell from 0.60 to 0.40, and
|
||||
precision for label 2 from 0.60 to 0.40 — the expected overfitting. The rejected label-2 clusters sit
|
||||
just under the S1 cut (0.596–0.604), so the threshold is doing real work at a place where situations
|
||||
and non-situations overlap. **Five accepted clusters out of 41 is far short of the ~15 situations
|
||||
per stage a pack needs**, so as it stands selection would make the sufficiency report fail on this
|
||||
corpus.
|
||||
|
||||
**S6 earned its place on the test set, in the way it failed to on development.** Two test clusters are
|
||||
held together by a single word — T10 "caliph" (S6 0.70) and T18 "young" (1.00) — and both have high
|
||||
cross-story cohesion (S1 0.628, 0.626). S1 alone would accept them; S6 rejects them. (Observation made
|
||||
after the test, not a re-tuning: S1 ≥ 0.6119 alone would accept 10 test clusters, 2 labelled 2 and 6 labelled ≥ 1.)
|
||||
Its original motivation, D18, was caught by S1 instead. Both failure modes exist, and each signal
|
||||
catches one.
|
||||
|
||||
**Duplicates among accepted clusters:** no two carry the same label name. T07 (courtship) and T25
|
||||
(trying to impress or win someone's favour) overlap in substance; a reader could reasonably call them
|
||||
one situation. Automatic merging remains unsolved (see step 6).
|
||||
|
||||
### What this means for the design
|
||||
|
||||
1. **Two-signal selection is a sound first gate, not a complete step.** It should run as a *filter
|
||||
that is allowed to reject* — its precision is what matters for pack quality — followed by a
|
||||
second stage that recovers recall.
|
||||
2. **Recall is the next problem, and clustering is the likely cause, not selection.** The rejected
|
||||
situations (police run-ins, proposals, social climbing) are recognisable but diluted by
|
||||
off-situation members at k = 60. Candidates to test, all without inference: re-cluster the members
|
||||
of rejected clusters at finer k and re-apply the gate; select from the union of several partitions,
|
||||
using co-association to keep only members that consistently travel together; or grow an accepted
|
||||
cluster's core by adding nearest cross-story members while S1 stays above the cut.
|
||||
3. **Merging duplicates needs member overlap, not centroid distance.**
|
||||
4. **These labels are one reader's.** Every number above rests on them. They are in
|
||||
`probe/labels-dev.json` and `probe/labels-test.json`, with the sheets they were read from, and are
|
||||
worth a designer's spot-check — especially the 1-versus-0 boundary, which is the least certain.
|
||||
5. **The test is within one corpus.** Generalisation to a different corpus is untested and needs
|
||||
embeddings, which needs the inference host.
|
||||
|
||||
---
|
||||
|
||||
## Recovering recall (pre-registered 2026-09-14, before any recovery method was run)
|
||||
|
||||
The gate is precise and keeps too few. Three ways to find more situations, compared on **fresh data**,
|
||||
because the test set above is spent. Still no inference.
|
||||
|
||||
### Fixed before running
|
||||
|
||||
- **The gate does not change.** `S1 ≥ 0.6119 AND S6 ≤ 0.1603`, plus the candidate conditions (≥ 5
|
||||
members, ≥ 4 stories, dominant ≤ 40%). "Passes the gate" below means all of these.
|
||||
- **A property of the gate, noted in advance:** S6 counts a word only if at least two members share
|
||||
it, so a cluster of fewer than 13 members fails whenever any content word repeats (2/12 = 0.167). In
|
||||
effect the gate requires ~13 members. The methods are built to produce clusters of that size;
|
||||
anything smaller is expected to fail, and that is the gate's behaviour, not a method's.
|
||||
- **Fresh partition:** k = 60, **seed 13** — neither development (11) nor test (12).
|
||||
- **Baseline (B):** the gate applied to seed 13's candidates, as before.
|
||||
- **M1 — re-cluster the rejected.** Every scene not in a B-accepted cluster is pooled and re-clustered
|
||||
with k-means, k = round(pool size / 20), seed 13. Result: B's accepted clusters plus pool clusters
|
||||
that pass the gate.
|
||||
- **M2 — consensus clustering.** Each scene is represented by its co-association row over the 12 cached
|
||||
runs (k 50–100, seeds 21 and 22 — no overlap with 11, 12 or 13): how often it landed with every
|
||||
other scene. Those rows are clustered with k-means, k = 60, seed 13. Result: clusters that pass the gate.
|
||||
- **M3 — prune, then grow.** For each seed-13 candidate: if it fails the gate, repeatedly remove the
|
||||
member with the lowest mean similarity to members from *other* stories, until it passes or falls to
|
||||
13 members (then it is dropped). Then grow every passing cluster: add the non-member scene nearest
|
||||
its centroid while the result still passes; stop at the first addition that would fail, or at 30
|
||||
members. Scenes may end up in more than one M3 cluster; the overlap is reported.
|
||||
|
||||
### Evaluation
|
||||
|
||||
- Every cluster accepted by any method is pooled (identical member sets once), given a shuffled id, and
|
||||
dumped to a blind sheet showing only member summaries. Which method produced a cluster is written
|
||||
to a separate map file that is not read until labelling is finished.
|
||||
- Labels use the same 2 / 1 / 0 scale. Names are reused verbatim from the development and test label
|
||||
files wherever the situation is the same, so duplicate situations can be counted by name.
|
||||
- Per method: clusters accepted; precision for label ≥ 1 and for label 2; **distinct situations**
|
||||
(distinct names among label ≥ 1); scene coverage; overlap between accepted clusters.
|
||||
- **Success, per method:** at least **twice** B's accepted clusters, **and** precision (≥ 1) of at least
|
||||
**0.80**, **and** at least twice B's distinct situations. For scale: a two-stage pack wants ~30.
|
||||
|
||||
### Known limits
|
||||
|
||||
The reader has now read every summary several times; labels are blind to method and signals, not to
|
||||
content. The methods and the gate are evaluated on one corpus.
|
||||
|
||||
### Results (2026-09-14)
|
||||
|
||||
Seed 13 gave 42 candidates. 21 distinct accepted clusters were labelled blind (`probe/labels-recover.json`,
|
||||
3 labelled 2, 13 labelled 1, 5 labelled 0), then scored by `probe/evaluate_recover.py`:
|
||||
|
||||
| Method | Accepted | Precision (≥ 1) | Precision (2) | Distinct situations | Scene coverage | Success |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| B, gate on candidates | 4 | 1.00 | 0.00 | 4 | 13% | — |
|
||||
| M1, re-cluster the rejected | 4 | 1.00 | 0.00 | 4 | 13% | **fail** — 0 new |
|
||||
| M2, consensus clustering | 3 | 1.00 | **1.00** | 3 | 5% | **fail** — too few |
|
||||
| M3, prune then grow | 16 | 0.69 | 0.00 | 10 | 45% | **fail** — precision |
|
||||
|
||||
**No method meets the pre-registered bar.**
|
||||
|
||||
- **M1 adds nothing.** Re-clustering the 729 scenes outside B's clusters at k = 36 produced no cluster
|
||||
that passes the gate. The gate's selectivity does not come from dilution by one bad partition.
|
||||
- **M2 is the only method that produced sharp situations.** All three of its clusters are labelled 2
|
||||
— a wealthy suitor weighed against love, a spouse against a spouse over infidelity, coercion by
|
||||
threat — at 14–17 members, against B's 13–38. Consensus over many partitions isolates the scenes
|
||||
that genuinely travel together. It produces too few clusters to be the selection step on its own.
|
||||
- **M3 finds the most, and the gate cannot stop it diluting.** Pruning recovered 12 rejected
|
||||
candidates and M3 reached 10 distinct situations (B: 4), but 5 of its 16 clusters are not
|
||||
situations. **Growth was stopped by the 30-member cap in 10 of 16 clusters, not by the gate.**
|
||||
That exposes the gate's second size effect: S6 is a share, so it gets *easier* to satisfy as a
|
||||
cluster grows, and S1 moves slowly. The gate calibrated on k = 60 clusters does not constrain a
|
||||
cluster that is being built up one scene at a time.
|
||||
- **Duplicates survive.** Across methods, "a spouse against a spouse" appears three times (R006, R015)
|
||||
and courtship twice within M3 (R008, R010); 68 scenes sit in more than one M3 cluster.
|
||||
- **The seed-13 baseline itself accepted no label-2 cluster** (0 of 4), where development and test
|
||||
each had 2–3. The baseline's precision for label 2 is unstable across partitions.
|
||||
|
||||
### What this means
|
||||
|
||||
The gate, as frozen, is **size-dependent in both directions**: it structurally rejects clusters under
|
||||
~13 members and barely constrains clusters above ~25. Any method that changes cluster size — pruning,
|
||||
growing, finer re-clustering — changes what the gate measures. Every recovery method was, in effect,
|
||||
testing the gate at sizes it was not calibrated for.
|
||||
|
||||
Observation only, not pre-registered and not a result: B and M2 together would give 7 clusters,
|
||||
6 distinct situations, all labelled ≥ 1, and 3 labelled 2.
|
||||
|
||||
Directions this points to, none tested:
|
||||
|
||||
1. **Make the gate size-invariant before anything else.** Replace S6's share with a count or a
|
||||
significance test against the corpus, and judge S1 relative to what a random cluster of the same
|
||||
size scores. Then re-validate — which needs fresh labels on fresh partitions.
|
||||
2. **Use consensus (M2) to form clusters, not k-means.** It was the only source of sharp situations.
|
||||
Running more co-association partitions (more seeds, more k) and extracting cores at several
|
||||
agreement levels may raise its yield without losing precision.
|
||||
3. **Grow only with a size-invariant stopping rule.** M3's recall is real; its stopping rule is not.
|
||||
|
||||
---
|
||||
|
||||
## A size-invariant gate, G2 (pre-registered 2026-09-14, before any G2 signal was computed)
|
||||
|
||||
### Why each old signal depended on size
|
||||
|
||||
- **S6** is a share with a floor of 2 members: 2/size. Under ~13 members any repeated word fails the
|
||||
cut; above ~25 almost nothing does. A share is not evidence unless the count is improbable.
|
||||
- **S1**'s *expectation* does not depend on size — it is a mean over pairs — but its *noise* does:
|
||||
small clusters reach a high S1 by chance, which is part of why k-means' small clusters look tight.
|
||||
- **Nothing looked at individual members.** Adding one off-situation scene to 30 moves S1 by
|
||||
roughly a thirtieth of the difference, so growth was never stopped.
|
||||
|
||||
### G2 signals
|
||||
|
||||
| Signal | Definition | Why it is size-invariant |
|
||||
| --- | --- | --- |
|
||||
| **S1** | Unchanged: mean summary cosine over cross-story member pairs | Unbiased at any size |
|
||||
| **Z1** | (S1 − μₘ) / σₘ, where μₘ and σₘ come from random member sets of the same size m (≥ 4 stories, dominant ≤ 40%): 300 samples for m ≤ 40, 120 above, sizes 5–150, seeded | Requires more evidence where chance does more |
|
||||
| **MMIN** | The lowest, over members, of a member's mean cosine to members of *other* stories | A per-member quantity; one diluting member fails it at any size |
|
||||
| **W** | Among words shared by ≥ 2 members, those whose count is significant by a hypergeometric upper-tail test at Bonferroni 0.01 over the corpus vocabulary; W is the largest share among them (0 if none) | Counts only improbable sharing, then measures how dominant it is |
|
||||
|
||||
Word extraction, stopwords and the corpus are identical to `signals.py`. Pairwise similarities are
|
||||
precomputed once. Candidate conditions (≥ 5 members, ≥ 4 stories, dominant ≤ 40%) still apply.
|
||||
|
||||
### Choosing thresholds
|
||||
|
||||
- **Tuning set:** every labelled cluster so far — development (40), test (41) and the recovery pool
|
||||
(21): 102 clusters, 63 labelled ≥ 1, sizes 5–39. All have been seen; none is used for validation.
|
||||
- **Rule form:** S1 ≥ a AND Z1 ≥ b AND MMIN ≥ c AND W < d, where each term may be switched off. Grid:
|
||||
a and c at the deciles of the tuning set's values or off; b ∈ {off, 1, 2, 3, 4, 5}; d ∈ {off, 0.25,
|
||||
0.30, 0.35, 0.40, 0.50, 0.60}.
|
||||
- **Objective:** the most tuning clusters labelled ≥ 1 accepted, subject to precision (≥ 1) of at least
|
||||
**0.85**; ties on more label-2 accepted, then fewer active terms. The objective changes from the
|
||||
first gate (which maximised label-2 precision) because the failure now is recall, and label 1 —
|
||||
a situational theme — is usable material for a pack.
|
||||
- The tuning report also gives, per size band (≤ 12, 13–25, ≥ 26), how many clusters the old and new
|
||||
gates accept and at what precision.
|
||||
|
||||
### Validation on fresh data (nothing below has been computed)
|
||||
|
||||
- **V1, small clusters:** k = 100, seed 15; 30 candidates sampled at random (seed 15).
|
||||
- **V2, large clusters:** k = 40, seed 14; 30 candidates sampled at random (seed 14).
|
||||
- **V3, growth:** k = 60, seed 16; every candidate G2 accepts is grown by adding the non-member nearest
|
||||
its centroid while the result still passes G2, up to 45 members. The stop reason — gate or cap — is
|
||||
recorded.
|
||||
- All V1 and V2 samples and all V3 grown clusters are pooled, deduplicated, shuffled and labelled blind,
|
||||
with the set and method in a separate map file.
|
||||
- **Success, all three required:**
|
||||
1. G2's precision (≥ 1) is at least **0.80 on V1 and on V2 separately**, and G2 accepts at least 3
|
||||
clusters in each.
|
||||
2. In V1, G2 accepts **more** small clusters than the old gate.
|
||||
3. In V3, **most growths are stopped by the gate, not the cap**, and grown clusters have precision
|
||||
(≥ 1) of at least 0.80.
|
||||
|
||||
The old gate is scored on V1 and V2 alongside, for comparison.
|
||||
|
||||
### Frozen G2 (written after tuning, before any validation cluster was built)
|
||||
|
||||
`probe/tune_gate2.py`, log `probe/tune-gate2.log`. (Correction: the tuning set has 62 clusters
|
||||
labelled ≥ 1, not 63.)
|
||||
|
||||
> **Accept a candidate when S1 ≥ 0.6037 AND Z1 ≥ 5 AND W < 0.60.** MMIN is off.
|
||||
|
||||
| Tuning set, 102 clusters | Accepted | Label ≥ 1 | Precision (≥ 1) | Label 2 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Accept all | 102 | 62 | 0.61 | 13 |
|
||||
| Old gate | 31 | 26 | 0.84 | 8 |
|
||||
| **G2** | **40** | **34** | **0.85** | **9** |
|
||||
|
||||
| Size band | Clusters (≥ 1) | Old gate: accepted / P1+ | G2: accepted / P1+ |
|
||||
| --- | --- | --- | --- |
|
||||
| ≤ 12 | 20 (7) | 0 / — | 1 / 1.00 |
|
||||
| 13–25 | 52 (34) | 13 / 0.92 | 18 / 0.89 |
|
||||
| ≥ 26 | 30 (21) | 18 / 0.78 | 21 / 0.81 |
|
||||
|
||||
**Observations and predictions, recorded before validation:**
|
||||
|
||||
- **G2 is not size-invariant at the small end, and the objective chose that.** Z1 ≥ 5 is a fixed bar
|
||||
on a statistic whose value, for a fixed true cohesion, *grows* with size (σₘ shrinks as m grows).
|
||||
It replaces S6's structural rejection of small clusters with a milder one: 1 small cluster accepted
|
||||
instead of 0. **Prediction: criterion 2 passes narrowly or fails, and V1 yields fewer than 3
|
||||
accepted clusters, failing criterion 1.**
|
||||
- **MMIN, the signal designed to stop dilution, was not selected**, and W's cut (0.60) is loose enough
|
||||
to act only on near-total single-word clusters. Growing a cluster raises Z1 and barely moves S1.
|
||||
**Prediction: most V3 growths run to the cap, failing criterion 3.**
|
||||
- The gain over the old gate is real but modest, and it is in the mid and large bands: 8 more clusters
|
||||
labelled ≥ 1 accepted at unchanged precision.
|
||||
- The thresholds are not changed in light of these predictions. The validation tests the rule the
|
||||
pre-registered procedure produced.
|
||||
|
||||
### Validation results (2026-09-14)
|
||||
|
||||
73 fresh clusters labelled blind (`probe/labels-validate.json`: 6 labelled 2, 33 labelled 1, 34
|
||||
labelled 0), scored by `probe/validate_gate2.py score` (log `probe/validate-score.log`).
|
||||
|
||||
| Set | Sampled (≥ 1) | Old gate: accepted / ≥ 1 / P1+ | G2: accepted / ≥ 1 / P1+ |
|
||||
| --- | --- | --- | --- |
|
||||
| V1, k = 100 | 30 (13) | 3 / 2 / 0.67 | **10 / 8 / 0.80** (2 labelled 2) |
|
||||
| V2, k = 40 | 30 (16) | 1 / 1 / 1.00 | 4 / 3 / 0.75 |
|
||||
| V3, grown | 13 | — | 13 grown: **11 stopped by the cap**, 2 by the gate; 10 ≥ 1, P1+ 0.77 |
|
||||
|
||||
| Criterion | Result |
|
||||
| --- | --- |
|
||||
| 1. P1+ ≥ 0.80 and ≥ 3 accepted, on V1 and V2 | **fail** — V1 passes (0.80, 10), V2 does not (0.75, 4) |
|
||||
| 2. G2 accepts more V1 clusters than the old gate | **pass** — 10 against 3 |
|
||||
| 3. V3 mostly stopped by the gate, P1+ ≥ 0.80 | **fail** — 2 of 13, 0.77 |
|
||||
| **Overall** | **fail** |
|
||||
|
||||
By size, across V1 and V2 together:
|
||||
|
||||
| Size | Old gate: accepted (≥ 1) | G2: accepted (≥ 1) |
|
||||
| --- | --- | --- |
|
||||
| ≤ 9 | 0 | **0** — including two clusters labelled 2 (5 and 6 members) |
|
||||
| 10–12 | 0 | 3 (3) |
|
||||
| 13–25 | 3 (2) | 7 (5) |
|
||||
| ≥ 26 | 1 (1) | 4 (3) |
|
||||
| All | 4 (3), P1+ 0.75 | 14 (11), P1+ 0.79 |
|
||||
|
||||
### Verdict on G2
|
||||
|
||||
**G2 is a real improvement on the old gate, and it is not size-invariant.** It accepts three to four
|
||||
times as many clusters at slightly better precision, and it opens the 10–25 member range the old gate
|
||||
all but closed. Both pre-registered predictions of failure held:
|
||||
|
||||
- **The small end is still closed below 10 members.** Z1 ≥ 5 is unreachable for a cluster of 5–9
|
||||
however cohesive it is. The two sharpest small clusters on the sheet — a fare dispute (5 members,
|
||||
label 2) and a spouse against a spouse (6 members, label 2) — were both rejected.
|
||||
- **Growth is still unconstrained.** Growing a cluster raises Z1 and barely moves S1, so both gates
|
||||
accept a 45-member cluster as readily as a 15-member one. The one per-member signal built to stop
|
||||
this, MMIN, was not selected by the tuning objective.
|
||||
- **Large clusters are still mostly rejected** (4 of 30 in V2), including clear themes such as the
|
||||
saloon, dining rooms and illness.
|
||||
|
||||
**What was learned about size-invariance itself:** a fixed threshold on a z-score is not size-invariant;
|
||||
it reverses the old bias rather than removing it. A size-invariant cohesion test needs a threshold
|
||||
that scales with the null — for example a fixed *percentile* of the same-size random distribution
|
||||
combined with an effect-size floor on S1 — and a stopping rule for growth has to be a per-member
|
||||
test that the tuning cannot switch off. Both are untested proposals, and testing them needs another
|
||||
fresh partition and another round of blind labels, because every cluster labelled so far has now
|
||||
informed a design decision.
|
||||
|
||||
---
|
||||
|
||||
## G3: a percentile of the same-size null, and a mandatory member floor (pre-registered 2026-09-14, before any G3 signal was computed)
|
||||
|
||||
### Changes from G2
|
||||
|
||||
- **Z1 is replaced by PCT**: the fraction of same-size random member sets (≥ 4 stories, dominant ≤ 40%)
|
||||
whose S1 is below the cluster's. 1,000 samples per size for 5–60 members, 200 for 61–150, seeded.
|
||||
A percentile threshold asks "is this cluster as unusual as the top x% of random sets *of its own
|
||||
size*", which a z-score with a fixed bar does not.
|
||||
- **S1 floor is mandatory.** At large sizes the null is narrow and PCT saturates at 1.0 for any
|
||||
cluster slightly above average, so an effect-size floor has to carry the weight there.
|
||||
- **MMIN is mandatory**: every member's mean cosine to members of other stories must reach the floor.
|
||||
It is the growth stop; tuning chooses its value but cannot switch it off.
|
||||
- **W** is unchanged from G2 and remains optional.
|
||||
|
||||
### Tuning
|
||||
|
||||
- **Tuning set: all 175 labelled clusters** — development 40, test 41, recovery 21, G2 validation 73.
|
||||
- **Rule form:** S1 ≥ a AND PCT ≥ p AND MMIN ≥ c AND W < d. a and c at the tuning set's deciles (no "off");
|
||||
p ∈ {0.90, 0.95, 0.975, 0.99, 0.999}; d ∈ {off, 0.30, 0.40, 0.50, 0.60}.
|
||||
- **Objective unchanged from G2:** most clusters labelled ≥ 1 accepted, subject to precision (≥ 1) of at
|
||||
least 0.85; ties on more label-2 accepted, then fewer active terms.
|
||||
|
||||
**Risk named in advance:** MMIN is a minimum over per-member means. Small clusters have noisier
|
||||
member means but fewer of them; large clusters have steadier means but more chances to contain one
|
||||
low member. Its net size effect is not predictable, and it could re-introduce a size bias of its own.
|
||||
|
||||
### Validation on fresh partitions (seeds never used before)
|
||||
|
||||
- **V0, very small:** k = 150, seed 20; 25 candidates sampled (seed 20).
|
||||
- **V1, small:** k = 100, seed 17; 20 sampled (seed 17).
|
||||
- **V2, large:** k = 40, seed 18; 20 sampled (seed 18).
|
||||
- **V3, growth:** k = 60, seed 19; every G3-accepted candidate grown toward its nearest scene while it
|
||||
passes G3, cap 45, stop reason recorded.
|
||||
- Pooled, deduplicated, shuffled and labelled blind as before. The old gate and G2 are scored on the
|
||||
same clusters.
|
||||
|
||||
**Success, all four required:**
|
||||
|
||||
1. G3 precision (≥ 1) ≥ **0.80** and ≥ 3 accepted, on V1 and on V2 separately.
|
||||
2. **Small clusters:** among V0 and V1 clusters of ≤ 9 members, G3 accepts **at least 2**, with precision
|
||||
(≥ 1) of at least **0.67**, and more than G2 accepts.
|
||||
3. **Growth:** most V3 growths stopped by the gate, and grown clusters have precision (≥ 1) ≥ 0.80.
|
||||
4. G3 accepts at least as many clusters labelled ≥ 1 across V0–V2 as G2, at precision no lower than G2's.
|
||||
|
||||
### Frozen G3 (written after tuning, before any validation cluster was built)
|
||||
|
||||
`probe/tune_gate3.py`, log `probe/tune-gate3.log`.
|
||||
|
||||
> **Accept a candidate when S1 ≥ 0.6175 AND PCT ≥ 0.90 AND MMIN ≥ 0.5296 AND W < 0.60.**
|
||||
|
||||
| Tuning set, 175 clusters (101 ≥ 1, 19 label 2) | Accepted | Label ≥ 1 | Precision (≥ 1) | Label 2 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Old gate | 47 | 38 | 0.81 | 9 |
|
||||
| G2 | 67 | 55 | 0.82 | 12 |
|
||||
| **G3** | **47** | **40** | **0.85** | **13** |
|
||||
|
||||
| Size band | Clusters (≥ 1) | Old: accepted/≥ 1 | G2 | G3 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ≤ 9 | 26 (5) | 0/0 | 0/0 | **4/3** |
|
||||
| 10–12 | 21 (11) | 0/0 | 4/4 | 4/4 |
|
||||
| 13–25 | 75 (47) | 17/15 | 26/22 | 17/17 |
|
||||
| ≥ 26 | 53 (38) | 30/23 | 37/29 | 22/16 |
|
||||
|
||||
**Observations and predictions, recorded before validation:**
|
||||
|
||||
- **G3 is the first gate to accept any cluster under 10 members** (4 on tuning, 3 of them ≥ 1).
|
||||
**Prediction: criterion 2 passes.**
|
||||
- **The percentile threshold settled at its loosest value, 0.90, and the S1 floor rose** from G2's 0.6037
|
||||
to 0.6175. The floor, not the percentile, is doing most of the work at mid and large sizes, and G3
|
||||
accepts 15 fewer large clusters than G2 on tuning. **Prediction: criterion 4 fails** — G3 finds fewer
|
||||
clusters labelled ≥ 1 than G2 (40 against 55 on tuning), though at higher precision. Criterion 1 is
|
||||
at risk on V2 for the same reason.
|
||||
- **MMIN at 0.53 is the growth stop.** Whether a nearest-to-centroid scene falls below it is not
|
||||
predictable from tuning, which contains no grown-under-G3 clusters. No prediction for criterion 3.
|
||||
- The trade is visible already: G3 exchanges recall at the large end for precision everywhere and for
|
||||
admission at the small end. Whether that is the right trade for a pack is a design question the
|
||||
validation cannot answer.
|
||||
|
||||
### Validation results (2026-09-14)
|
||||
|
||||
75 fresh clusters labelled blind (`probe/labels-validate3.json`: 4 labelled 2, 24 labelled 1, 47
|
||||
labelled 0), scored by `probe/validate_gate3.py score` (log `probe/validate3-score.log`).
|
||||
|
||||
| Set | Sampled (≥ 1) | Old: acc / ≥ 1 / P1+ | G2: acc / ≥ 1 / P1+ | G3: acc / ≥ 1 / P1+ |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| V0, k = 150 | 25 (5) | 2 / 1 / 0.50 | 8 / 4 / 0.50 | 11 / 4 / 0.36 |
|
||||
| V1, k = 100 | 20 (5) | 1 / 0 / 0.00 | 1 / 1 / 1.00 | 4 / 2 / 0.50 |
|
||||
| V2, k = 40 | 20 (12) | 0 / 0 / — | 4 / 4 / 1.00 | 2 / 2 / 1.00 |
|
||||
| ≤ 9 members, V0 + V1 | 27 (4) | 1 / 0 / 0.00 | 3 / 1 / 0.33 | 11 / 3 / 0.27 |
|
||||
| V0–V2 pooled | 65 (22) | 3 / 1 / 0.33 | **13 / 9 / 0.69** | 17 / 8 / 0.47 |
|
||||
| V3, grown under G3 | 10 | — | — | 7 stopped by the cap, 3 by the gate; P1+ 0.60 |
|
||||
|
||||
**All four criteria fail**, and G3 is worse than G2 on fresh data: more clusters accepted, fewer of
|
||||
them situations. G3's gain on the tuning set (P1+ 0.85, 13 label-2) did not survive a new partition.
|
||||
|
||||
### Why G3 failed — measured, not inferred
|
||||
|
||||
- **PCT carries no information.** Across all 175 tuning clusters, 173 reach PCT ≥ 0.90 and 141 reach
|
||||
1.0 — including 72 of the 74 clusters labelled 0. **The random-set null is the wrong null.** k-means
|
||||
groups each scene with its nearest neighbours, so every cluster it produces is far tighter than a
|
||||
random set of scenes of the same size, whether or not it is a situation. A cohesion test has to ask
|
||||
whether a cluster is tighter than *other clusters of its size*, not than random scenes.
|
||||
- **MMIN barely separates labels and favours small clusters.** Median MMIN is 0.536 for label 0,
|
||||
0.543 for label 1 and 0.564 for label 2; by size it is 0.547 for ≤ 9 members, 0.537 for 10–25 and
|
||||
0.552 for ≥ 26. A minimum over few, noisy per-member means is not reliably lower for small clusters,
|
||||
so the floor admitted 11 small clusters of which 8 are not situations. This is the risk recorded
|
||||
before tuning.
|
||||
- **Growth is still not stopped by the gate** (7 of 10 to the cap). A nearest-to-centroid scene
|
||||
typically clears a member floor calibrated on whole clusters.
|
||||
|
||||
### Where this leaves selection
|
||||
|
||||
Across three gates and five blind-labelled evaluations, **G2 remains the best**: pooled precision
|
||||
(≥ 1) of 0.79 on its own validation and 0.69 here, with the fewest non-situations admitted.
|
||||
None of the three is size-invariant, and none stops growth.
|
||||
|
||||
Two conclusions look robust enough to build on:
|
||||
|
||||
1. **Cohesion alone does not identify a situation.** Clusters labelled 0, 1 and 2 overlap heavily on
|
||||
every cohesion statistic tried (S1, Z1, PCT, MMIN). What separates the best-labelled clusters is
|
||||
visible to a reader — one transaction — and is not a geometric property of summary embeddings at
|
||||
the resolution tried.
|
||||
2. **The labelling budget is the binding constraint.** Each gate needs fresh partitions and 70–80 new
|
||||
blind labels to test honestly, and every labelled cluster so far has informed a design. Continuing
|
||||
to iterate gate designs on this corpus without a different kind of evidence is unlikely to converge.
|
||||
|
||||
Candidate directions, none tested: a null built from **k-means clusters of the same size on
|
||||
shuffled-story data** rather than random scenes; a selection step that uses the **inference model to
|
||||
name each cluster's transaction** and checks agreement across members (needs the GPU); or accepting
|
||||
G2 as the gate and moving the problem downstream to event construction, where a human pass over
|
||||
~40 candidate clusters is cheap compared with the labelling this probe has already required.
|
||||
|
||||
---
|
||||
|
||||
## G4: the model reads the cluster (pre-registered 2026-09-14, before any model call)
|
||||
|
||||
The finding above is that what makes a cluster a situation is visible to a reader and not to the
|
||||
geometry. So let a reader decide — the inference model — and measure it against the blind labels.
|
||||
|
||||
### Method
|
||||
|
||||
- For each cluster, up to **15 member summaries** (a seeded sample when larger), shuffled and numbered,
|
||||
go to the model in one call with the prompt below. It returns JSON: the single social transaction
|
||||
most of them clearly show, or NONE, and the numbers of the summaries that clearly show it.
|
||||
- **FIT** = (number of valid summary numbers listed) / (number shown); 0 when the answer is NONE or
|
||||
the reply cannot be parsed. Parse failures are counted and reported.
|
||||
- **Gate: accept when FIT ≥ θ**, plus the usual candidate conditions (≥ 5 members, ≥ 4 stories,
|
||||
dominant ≤ 40%), which every labelled cluster already meets.
|
||||
- Deterministic: temperature 0, 2,048-token context, `format: json`. One request at a time, as before.
|
||||
- **The prompt contains no example of a situation** — the 3B model copied the examples it was given
|
||||
in the summarisation pilot.
|
||||
|
||||
Prompt (system), fixed:
|
||||
|
||||
> You read short summaries of scenes from different stories and decide whether they share one kind
|
||||
> of social situation.
|
||||
>
|
||||
> A social situation is a specific transaction between people: who wants what from whom, and what
|
||||
> is at stake. Describe it in your own words.
|
||||
>
|
||||
> Rules:
|
||||
> - Find the single situation that the largest number of summaries clearly show.
|
||||
> - It is normal for many summaries not to fit. Include a summary only if it clearly shows that
|
||||
> situation.
|
||||
> - A shared place, mood, profession or word is not a situation.
|
||||
> - If no situation is clearly shown by at least three summaries, the situation is NONE.
|
||||
>
|
||||
> Reply with JSON only: {"situation": "<at most 10 words, or NONE>", "fits": [<numbers of the
|
||||
> summaries that clearly show it>]}
|
||||
|
||||
The user message is the numbered list. For `qwen3:14b` it ends with `/no_think`, and any think block
|
||||
is stripped before parsing.
|
||||
|
||||
### Models
|
||||
|
||||
- **Primary: `qwen2.5:3b-instruct`** — the design requires a small local model; the verdict rests on it.
|
||||
- **Secondary: `qwen3:14b`** — reported, to show whether capability is what limits the result.
|
||||
|
||||
### Data — no new labels
|
||||
|
||||
The model is never fitted to labels; the only fitted quantity is θ.
|
||||
|
||||
- **θ is chosen on development (40 clusters)** only, with the objective used for G2 and G3: most
|
||||
clusters labelled ≥ 1 accepted, subject to precision (≥ 1) ≥ 0.85; ties on more label-2 accepted,
|
||||
then the higher θ. Grid: θ ∈ {0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0}.
|
||||
- **Primary evaluation: the G2 and G3 validation sets, 148 clusters**, which tuned neither G2 nor G4,
|
||||
so G2 can be compared fairly on them. **Secondary:** test and recovery, 62 clusters.
|
||||
- Identical member sets appearing in more than one file are judged once.
|
||||
- **Pilot, allowed once:** the first 5 development clusters, to check the JSON parses. If more than 1
|
||||
of 5 fails to parse, one prompt revision is allowed and recorded here before any other call.
|
||||
|
||||
### Success, on the 148-cluster primary set, with the 3B model
|
||||
|
||||
1. Precision (≥ 1) at least **0.80**.
|
||||
2. **More** clusters labelled ≥ 1 accepted than G2 accepts on the same set.
|
||||
3. Among accepted clusters of ≤ 9 members, precision (≥ 1) at least 0.67, if there are at least 3.
|
||||
|
||||
Also reported: G4 AND G2; FIT distribution by label; the situations named for accepted clusters
|
||||
against the label names; and the 14B results against the same bar.
|
||||
|
||||
**Pilot (21:23):** 5 of 5 replies parsed with the 3B model, so the prompt is unchanged. About 1.6 s
|
||||
per cluster. Noted without drawing a conclusion from five: the situations named are often generic
|
||||
("Confrontation and hidden motives" for D04, a label-2 spouse-against-spouse cluster, FIT 0.47), and
|
||||
a label-1 argument cluster scored FIT 0.87.
|
||||
|
||||
### Results, `qwen2.5:3b-instruct` (21:24–21:29, 245 distinct clusters in 286 s, 1 parse failure)
|
||||
|
||||
Log `probe/evaluate-judge-3b.log`. θ chosen on development: **0.6** (9 accepted, P1+ 0.89).
|
||||
|
||||
| Primary set, 148 clusters (67 ≥ 1) | Accepted | ≥ 1 | Label 2 | P1+ | Recall |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Accept all | 148 | 67 | 10 | 0.45 | 1.00 |
|
||||
| **G2** | **49** | **36** | 5 | **0.73** | **0.54** |
|
||||
| G4, FIT ≥ 0.6 | 50 | 24 | 4 | 0.48 | 0.36 |
|
||||
| G4 AND G2 | 18 | 11 | 1 | 0.61 | 0.16 |
|
||||
|
||||
On the secondary set (62) G4 reaches P1+ 0.71 against G2's 0.82. **All three criteria fail**
|
||||
(P1+ 0.48; 24 ≥ 1 against G2's 36; 13 accepted small clusters at P1+ 0.23).
|
||||
|
||||
**FIT does not separate the labels:** mean 0.39 for label 0, 0.49 for label 1 — and **0.42 for label
|
||||
2**, lower than for label 1. Precision at the primary set (0.48) is barely above accepting everything
|
||||
(0.45).
|
||||
|
||||
**Why, visible in the replies:** the 3B model names situations at a level of abstraction where almost
|
||||
anything fits — "confrontation", "power dynamics in social hierarchies", "tension between parties",
|
||||
"coercion and manipulation" — and then lists most summaries as fitting it. A 5-member cluster labelled
|
||||
0 scored FIT 1.00 as "power struggle/conflict". When it is specific it is often right ("marital
|
||||
conflict" for a spouse-against-spouse cluster, "con or scam", "argue over payment"), but specificity
|
||||
is not what FIT rewards. The rule against shared place, mood and profession was followed; a rule
|
||||
against *abstraction* was never written, and the model supplies abstraction freely.
|
||||
|
||||
### 14B pilot and the one allowed revision
|
||||
|
||||
The `qwen3:14b` pilot failed to parse **5 of 5**: every reply's content was empty. The model spent its
|
||||
whole 200-token budget in a separate thinking channel (Ollama returns it outside `content`), and the
|
||||
`/no_think` suffix did not stop it. **Revision, the one allowed by the pilot rule, affecting only the
|
||||
14B run:** requests set Ollama's `think: false` and allow 300 output tokens. The prompt text,
|
||||
sampling, θ procedure and success criteria are unchanged. The five empty results were deleted before
|
||||
re-running the pilot.
|
||||
|
||||
Re-run pilot: **5 of 5 parsed**, about 2.4 s per cluster. The situations named are more specific than
|
||||
the 3B model's ("Wealthy person pressures someone into marriage", "Struggling artist facing societal or
|
||||
personal challenges"), though an abstract one still scored FIT 0.80 ("conflict between individuals with
|
||||
differing perspectives").
|
||||
|
||||
### Results, `qwen3:14b` (21:31–21:45, 245 clusters in 832 s, 0 parse failures)
|
||||
|
||||
Log `probe/evaluate-judge-14b.log`. θ chosen on development: **0.6** (8 accepted, P1+ 0.88).
|
||||
|
||||
| Primary set, 148 clusters (67 ≥ 1) | Accepted | ≥ 1 | Label 2 | P1+ | Recall |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Accept all | 148 | 67 | 10 | 0.45 | 1.00 |
|
||||
| **G2** | **49** | **36** | 5 | **0.73** | **0.54** |
|
||||
| G4 (14B), FIT ≥ 0.6 | 42 | 22 | 4 | 0.52 | 0.33 |
|
||||
| G4 (14B) AND G2 | 10 | 10 | 1 | 1.00 | 0.15 |
|
||||
|
||||
By size, G4 (14B) on the primary set: ≤ 9 members 17 accepted, P1+ 0.29; 10–25, 14 accepted, P1+ 0.50;
|
||||
≥ 26, 11 accepted, **P1+ 0.91**. Secondary set (62): G4 P1+ 0.60 against G2's 0.82.
|
||||
|
||||
**All three criteria fail with the 14B model as well** (P1+ 0.52; 22 ≥ 1 against G2's 36; 17 small
|
||||
clusters accepted at P1+ 0.29).
|
||||
|
||||
FIT now rises with the label — mean 0.40, 0.47, 0.52 for labels 0, 1, 2 — where the 3B model's did
|
||||
not, so capability helps. But the separation is small next to the spread, and the 14B model still
|
||||
names abstractions that fit almost anything ("Someone wants something from someone else", "Power
|
||||
dynamics between individuals", "Someone holds power over someone else"), most often for small
|
||||
clusters.
|
||||
|
||||
GPU host during both runs: no faults logged; peak 284.9 W (1 s), mean 163 W under load, 65 °C, PCIe
|
||||
Gen 3 x4 throughout. Logs in `probe/gpu-logs/*2122*`.
|
||||
|
||||
### Verdict on G4
|
||||
|
||||
**Neither model's judgement is a better gate than G2**, and the pre-registered verdict (on the 3B
|
||||
model) is a fail. The failure is specific and repeatable: asked for "the single situation most
|
||||
summaries share", a model answers at whatever level of abstraction makes the most summaries fit, and
|
||||
FIT rewards exactly that. Small clusters make it worse, because with 5–9 summaries an abstraction that
|
||||
covers four or five of them is always available.
|
||||
|
||||
Two observations, recorded as observations because neither was a pre-registered criterion:
|
||||
|
||||
- **G4 (14B) AND G2 accepted 10 clusters on the primary set, and all 10 are labelled ≥ 1.** The two
|
||||
gates fail in different ways — G2 admits cohesive clusters that are not situations, the model admits
|
||||
abstract "situations" that are not cohesive — and their intersection is precise. It is also
|
||||
narrow: recall 0.15.
|
||||
- **For clusters of 26 or more, the 14B model's precision is 0.91.** The abstraction problem is a
|
||||
small-cluster problem: 15 shown summaries rarely all fit one vague phrase *and* get listed.
|
||||
|
||||
What would test the obvious next step, none of it done: a prompt that asks for the situation **in
|
||||
terms of roles and a concrete want** and rejects answers without both; scoring the *specificity* of
|
||||
the named situation (for example, requiring the named situation to be closer to the member summaries
|
||||
than to the corpus average in embedding space); and G2 AND G4 evaluated as a pre-registered gate on a
|
||||
fresh partition, since every labelled cluster now informs this observation.
|
||||
|
||||
---
|
||||
|
||||
## Predicaments (recorded 2026-09-15, before any model call)
|
||||
|
||||
### What the first real review found
|
||||
|
||||
The designer opened `REVIEW.html` for run k60-s24 and read four groups closely:
|
||||
|
||||
- **Group 1 is artists.** It holds at least three different situations: an artist in a new place using
|
||||
the art to get on, being paid for the work against what the work is worth, and plain money trouble.
|
||||
- **Group 2 is police and court officers.** Inside it: being coerced, with the police watching or
|
||||
arriving; and a crime that is stopped or found out.
|
||||
- **Group 6 is flirting and infatuation.**
|
||||
- **Extra group 1 is room and board**: hotels, dinners and the like.
|
||||
|
||||
None of these is "one person wanting something specific from another". Checked across all 20 groups,
|
||||
most are held together by who is in the scenes or where they happen: *artist* in 44% of group 1's
|
||||
scenes, *policeman* in 54% of group 3's, *uncle* and *lawyer* in group 4, *marriage* and *wedding* in
|
||||
group 8, *restaurant* and *customers* in group 12, *hotel* in 41% of extra group 1.
|
||||
|
||||
**Why every earlier test missed it.** A topic group is as cohesive and as spread across stories as a
|
||||
situation group, so cohesion, story spread, word dominance and the model's own judgement all passed it.
|
||||
Summaries keep jobs and places, and the embeddings group on them. It took a person reading the groups.
|
||||
|
||||
### Decisions (designer, 2026-09-15)
|
||||
|
||||
1. **The unit is a predicament**, not a two-person transaction: what the main person is up against and
|
||||
what they want out of it, as The Ladder's existing events are (the rent bouncing, the mail cart).
|
||||
The sub-themes the designer found inside groups 1 and 2 are this kind of thing.
|
||||
2. **Re-describe every scene as a predicament, then regroup**, rather than splitting topic groups by hand.
|
||||
|
||||
### Method, fixed before any call
|
||||
|
||||
- `probe/redescribe.py` gives the model each scene's own prose (not its summary) and the prompt in that
|
||||
script: one sentence, at most 20 words, no names, jobs, places, businesses or story-specific objects,
|
||||
saying what the person is up against and what they want. No example of the output vocabulary.
|
||||
- Names are then removed deterministically (`strip_names.py`), the sentences embedded (`embed.py`), and
|
||||
the embeddings clustered at k = 60.
|
||||
- **Pilot first:** the first 40 scenes, with both `qwen2.5:3b-instruct` and `qwen3:14b`, read by the
|
||||
designer side by side before any full run. The full run uses the model the designer prefers.
|
||||
|
||||
### How it is judged
|
||||
|
||||
- **Topic share** (`probe/topic_share.py`): for each candidate group, the share of its scenes whose
|
||||
original summary contains the group's most common job, relationship or place word; median over groups
|
||||
and seeds 11–13 at k = 60. The baseline on the summary embeddings is measured before any call and
|
||||
recorded below. **Pass: the median falls to half the baseline or less**, with at least 30 candidate
|
||||
groups remaining at k = 60 (enough material for a two-stage pack).
|
||||
- **Leakage:** how many predicament sentences still contain a job, relationship or place word. Reported,
|
||||
not gated.
|
||||
- **The real test is the designer reading the regrouped top groups** in the review page: most should
|
||||
read as one predicament. Numbers alone passed topic groups before.
|
||||
|
||||
### Baseline (measured 2026-09-15, before any model call)
|
||||
|
||||
A first measurement counted words for a person with no role at all ("man", "woman", "girl"), and scored
|
||||
one group 90% topic-bound for saying "man". Those words (`GENERIC_PEOPLE` in `probe/topic_words.py`)
|
||||
were taken out of the measure, and the baseline re-measured, still before any predicament was generated.
|
||||
|
||||
`python3 topic_share.py summary_embeddings.json` (`probe/topic-share-baseline.log`):
|
||||
|
||||
| Seed | Candidate groups | Median top topic-word share |
|
||||
| --- | --- | --- |
|
||||
| 11 | 40 | 16% |
|
||||
| 12 | 41 | 20% |
|
||||
| 13 | 42 | 22% |
|
||||
| **Median** | **41** | **20%** |
|
||||
|
||||
Most topic-bound at seed 11: *wife* 47% of 15, *dinner* 47% of 15, *policeman* 40% of 15 and 38% of 16,
|
||||
*brother* 38% of 8, *artist* 36% of 11. **Pass mark for the predicament groups: median 10% or less, with
|
||||
at least 30 candidate groups.**
|
||||
|
||||
### Pilot (2026-09-15)
|
||||
|
||||
First 40 scenes, one request at a time on the GPU host (3B 39 s, 14B 111 s; no GPU faults; logs in
|
||||
`probe/gpu-logs/*-2026-09-15-1803*`). Read side by side in `probe/pilot-compare.html` (`pilot_page.py`).
|
||||
|
||||
| | qwen2.5:3b-instruct | qwen3:14b |
|
||||
| --- | --- | --- |
|
||||
| Has a job, relationship or place word | 15 of 40 | 13 of 40 (mostly *wife*, *husband*) |
|
||||
| Mean words (limit 20) | 14 | 25; 34 of 40 over |
|
||||
| Most common opening | "The Kid seeks…" ×3 | "A man struggles…" ×6 |
|
||||
|
||||
3B mostly retells the plot, keeping names, objects and places. 14B writes something close to a
|
||||
predicament but ignores the length limit and repeats a few openings. **Designer's read: scenes 1–10,
|
||||
14B dramatically better in every one; stopped there. The full run uses qwen3:14b.**
|
||||
|
||||
Found while reading: `split.py` skips a title written in quotation marks, so three stories were
|
||||
merged into the story before them. "Little Speck in Garnered Fruit" is inside "Dougherty's
|
||||
Eye-Opener", "The Guilty Party" inside "A Harlem Tragedy", and "What You Want" inside "The Duel". The
|
||||
corpus has 97 stories, not 94. Separately, `strip_names.py` treats *York*, *Manhattan* and *Broadway*
|
||||
as personal names, which gives "New someone" in 29 of 838 summaries.
|
||||
|
||||
### Corrections before the full run (2026-09-15, designer's go-ahead)
|
||||
|
||||
The v1 data and every G1–G4 record stay as they were, in `probe/`. The corrected corpus is built in
|
||||
`probe/v2/`, running the same scripts from that directory.
|
||||
|
||||
- **Split.** `split.py` accepts a quoted title and matches a contents entry by the part before a dash
|
||||
(“THE GUILTY PARTY”—AN EAST SIDE TRAGEDY). Result: **97 stories, 839 scenes**. Exactly the three
|
||||
missing stories were added, nothing was lost, and only their three host stories changed.
|
||||
- **Summaries carried over.** 829 of the 839 scenes have text identical to a v1 scene and keep that
|
||||
v1 summary verbatim (`carry_summaries.py`). The other 10 are summarised with the v1 model and prompt.
|
||||
- **Name stripper.** New York places (New York, Manhattan, Broadway, Harlem, the Bowery, and a few
|
||||
others) become "the city" before names are looked for, and days and holidays are never names. Months
|
||||
stay ambiguous ("May" is a person in scene 5) and are left alone.
|
||||
- **The baseline is re-measured on v2** (the v2 summaries, re-embedded), because both fixes change
|
||||
what is embedded. It is recorded below before any predicament is read.
|
||||
- **No trimming to 20 words.** Tried on the 14B pilot: cutting at the last clause break within 20
|
||||
words removed what the person is up against in most cut sentences ("…with a promise of future money"
|
||||
without "but his dishonesty sparks a violent confrontation"), and 10 of 40 had no clean break.
|
||||
The full 14B sentence is embedded.
|
||||
|
||||
### Result (2026-09-15)
|
||||
|
||||
Full run: 839 scenes re-described by `qwen3:14b`, one request at a time, 37 minutes on the GPU host
|
||||
(peak 270 W, mean 136 W, 65 °C, no faults; logs in `probe/gpu-logs/*-2026-09-15-1831*`). Sentences
|
||||
average 25 words. 219 of 839 still contain a job, relationship or place word, most often a relationship
|
||||
(*wife*, *husband*) — reported, not gated.
|
||||
|
||||
`topic_share.py`, k = 60, seeds 11–13, both measured on the v2 corpus:
|
||||
|
||||
| | Candidate groups | Median top topic-word share |
|
||||
| --- | --- | --- |
|
||||
| v2 summaries (baseline, `topic-share-baseline-v2.log`) | 45 | **20%** |
|
||||
| Predicaments (`topic-share-predicaments.log`) | 42 | **13%** |
|
||||
|
||||
**The pre-registered pass mark is not met.** It asked for half the baseline or less (10%); the measure
|
||||
fell by about a third, from 20% to 13%. The group-count mark is met (42 ≥ 30). The most topic-bound
|
||||
groups are also much weaker than the baseline's: the worst predicament group is *room* at 40% of 5
|
||||
scenes, against the baseline's *bride* at 62% of 8 and *policeman* at 60% of 20.
|
||||
|
||||
Not a pass and not a wash, so the decision goes to the reading test that this whole step exists for:
|
||||
`groups_page.py` builds `v2/groups-predicaments-k60-s24.html`, which lists each group's predicament
|
||||
sentences with each scene's original summary beneath, most cohesive group first, and asks the designer
|
||||
to mark each group **one predicament**, **two or three mixed**, or **not a predicament**. The openings
|
||||
repeat ("A man must" 45 times, "A man struggles" 37), so the risk to watch for is a group held together
|
||||
by that phrasing rather than by a predicament.
|
||||
@@ -0,0 +1,16 @@
|
||||
test: 41 candidates, 5 labelled 2
|
||||
FROZEN RULE S1 >= 0.6119 AND S6 <= 0.1603
|
||||
accepted 5 P2 0.40 P1+ 1.00 R2 0.40
|
||||
baseline accept all candidates accepted 41 P2 0.12 P1+ 0.51 R2 1.00
|
||||
baseline B0: tightness filter accepted 18 P2 0.17 P1+ 0.44 R2 0.60
|
||||
|
||||
accepted:
|
||||
T05 label 1 schemes, cons and theft over money
|
||||
T07 label 1 courtship
|
||||
T25 label 1 trying to impress or win someone's favour
|
||||
T35 label 2 coercion over money: threats, bribes, demands for payment
|
||||
T39 label 2 a spouse schemes against or confronts a spouse over infidelity
|
||||
rejected label-2:
|
||||
T16 a newcomer seeks acceptance in a social circle
|
||||
T17 a proposal or engagement, accepted or refused
|
||||
T30 a run-in with the police
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Check a saved review and turn it into situations.json.
|
||||
|
||||
python3 apply_review.py review/k60-s24 # finds the saved review itself
|
||||
python3 apply_review.py review/k60-s24 ~/Downloads/review-k60-s24.json
|
||||
|
||||
Without a file named, it uses the newest review-<run>*.json in the run directory, and
|
||||
otherwise the newest one in ~/Downloads, where a browser saves it. It prints every problem
|
||||
at once and writes nothing until the review passes; then it writes situations.json into the
|
||||
run directory.
|
||||
"""
|
||||
import datetime, json, os, pathlib, sys
|
||||
import review_format as rf
|
||||
|
||||
|
||||
def downloads_dir():
|
||||
return pathlib.Path(os.environ.get('STP_DOWNLOADS', pathlib.Path.home() / 'Downloads'))
|
||||
|
||||
|
||||
def find_review(run_dir, run_id):
|
||||
for folder in (run_dir, downloads_dir()):
|
||||
if folder.is_dir():
|
||||
found = sorted(folder.glob(f'review-{run_id}*.json'), key=lambda p: p.stat().st_mtime, reverse=True)
|
||||
if found:
|
||||
return found[0]
|
||||
return None
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) not in (2, 3):
|
||||
print(__doc__.strip())
|
||||
return 2
|
||||
run_dir = pathlib.Path(argv[1])
|
||||
cand_path = run_dir / 'candidates.json'
|
||||
if not cand_path.exists():
|
||||
print(f'{run_dir} has no candidates.json. Check the folder name, or run review.py first.')
|
||||
return 1
|
||||
candidates = json.loads(cand_path.read_text(encoding='utf-8'))
|
||||
run_id = candidates['run']['id']
|
||||
|
||||
review_path = pathlib.Path(argv[2]).expanduser() if len(argv) == 3 else find_review(run_dir, run_id)
|
||||
if review_path is None or not review_path.exists():
|
||||
print(f'No saved review found for run {run_id}.')
|
||||
print(f'Open {run_dir / "REVIEW.html"} in a browser, and at the end press "Save review file".')
|
||||
print(f'It saves review-{run_id}.json into Downloads, where this looks for it.')
|
||||
return 1
|
||||
print(f'reading {review_path}')
|
||||
try:
|
||||
form = json.loads(review_path.read_text(encoding='utf-8'))
|
||||
except json.JSONDecodeError:
|
||||
print('That file is damaged and cannot be read. Save the review again from REVIEW.html; '
|
||||
'your progress is still in the browser.')
|
||||
return 1
|
||||
|
||||
errors, warnings, result = rf.validate(candidates, form)
|
||||
for w in warnings:
|
||||
print(f'note: {w}')
|
||||
if errors:
|
||||
print(f'\nThe review is not finished. {len(errors)} thing(s) to sort out in REVIEW.html, '
|
||||
f'then save the review file again:')
|
||||
for e in errors:
|
||||
print(f' - {e}')
|
||||
return 1
|
||||
|
||||
result['applied'] = datetime.datetime.now().astimezone().isoformat(timespec='seconds')
|
||||
result['review_file'] = str(review_path)
|
||||
out = run_dir / 'situations.json'
|
||||
out.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding='utf-8')
|
||||
c = result['counts']
|
||||
print(f'\n{out}: {c["situations"]} situations ({c["kept"]} kept, {c["same"]} merged in, '
|
||||
f'{c["dropped"]} dropped, {c["left_out"]} extras left out).')
|
||||
for s in result['situations']:
|
||||
print(f' {s["id"]} {s["situation"]} ({s["size"]} scenes, {s["stories"]} stories)')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,824 @@
|
||||
{
|
||||
"D01": [
|
||||
105,
|
||||
232,
|
||||
241,
|
||||
242,
|
||||
248,
|
||||
271,
|
||||
277,
|
||||
336,
|
||||
387,
|
||||
390,
|
||||
418,
|
||||
451,
|
||||
465,
|
||||
528,
|
||||
568,
|
||||
709,
|
||||
778,
|
||||
782,
|
||||
790,
|
||||
820
|
||||
],
|
||||
"D02": [
|
||||
63,
|
||||
81,
|
||||
222,
|
||||
240,
|
||||
251,
|
||||
281,
|
||||
296,
|
||||
297,
|
||||
386,
|
||||
481,
|
||||
544,
|
||||
551,
|
||||
589,
|
||||
745
|
||||
],
|
||||
"D03": [
|
||||
20,
|
||||
57,
|
||||
65,
|
||||
125,
|
||||
135,
|
||||
178,
|
||||
196,
|
||||
198,
|
||||
326,
|
||||
353,
|
||||
359,
|
||||
361,
|
||||
364,
|
||||
373,
|
||||
375,
|
||||
380,
|
||||
381,
|
||||
459,
|
||||
519,
|
||||
540,
|
||||
543,
|
||||
639,
|
||||
640,
|
||||
642,
|
||||
692,
|
||||
739,
|
||||
779,
|
||||
799,
|
||||
802,
|
||||
819,
|
||||
833
|
||||
],
|
||||
"D04": [
|
||||
41,
|
||||
127,
|
||||
163,
|
||||
204,
|
||||
205,
|
||||
302,
|
||||
304,
|
||||
305,
|
||||
322,
|
||||
475,
|
||||
483,
|
||||
590,
|
||||
761,
|
||||
763,
|
||||
770
|
||||
],
|
||||
"D05": [
|
||||
73,
|
||||
82,
|
||||
156,
|
||||
157,
|
||||
158,
|
||||
225,
|
||||
389,
|
||||
430,
|
||||
485,
|
||||
486,
|
||||
487,
|
||||
488,
|
||||
514,
|
||||
653,
|
||||
787
|
||||
],
|
||||
"D06": [
|
||||
84,
|
||||
85,
|
||||
86,
|
||||
87,
|
||||
88,
|
||||
106,
|
||||
141,
|
||||
162,
|
||||
255,
|
||||
290,
|
||||
306,
|
||||
314,
|
||||
319,
|
||||
325,
|
||||
383,
|
||||
384,
|
||||
385,
|
||||
394,
|
||||
413,
|
||||
450,
|
||||
523,
|
||||
658,
|
||||
707
|
||||
],
|
||||
"D07": [
|
||||
37,
|
||||
40,
|
||||
42,
|
||||
62,
|
||||
151,
|
||||
154,
|
||||
175,
|
||||
199,
|
||||
201,
|
||||
215,
|
||||
218,
|
||||
223,
|
||||
254,
|
||||
264,
|
||||
267,
|
||||
269,
|
||||
279,
|
||||
345,
|
||||
348,
|
||||
350,
|
||||
352,
|
||||
354,
|
||||
370,
|
||||
371,
|
||||
453,
|
||||
479,
|
||||
521,
|
||||
567,
|
||||
595,
|
||||
700,
|
||||
815,
|
||||
821,
|
||||
822,
|
||||
826
|
||||
],
|
||||
"D08": [
|
||||
75,
|
||||
76,
|
||||
246,
|
||||
591,
|
||||
645,
|
||||
666,
|
||||
670,
|
||||
789
|
||||
],
|
||||
"D09": [
|
||||
133,
|
||||
134,
|
||||
144,
|
||||
168,
|
||||
192,
|
||||
207,
|
||||
276,
|
||||
376,
|
||||
379,
|
||||
399,
|
||||
461,
|
||||
517,
|
||||
545,
|
||||
619,
|
||||
625,
|
||||
649,
|
||||
731,
|
||||
805,
|
||||
809,
|
||||
810
|
||||
],
|
||||
"D10": [
|
||||
7,
|
||||
8,
|
||||
96,
|
||||
99,
|
||||
171,
|
||||
217,
|
||||
219,
|
||||
250,
|
||||
253,
|
||||
414,
|
||||
433,
|
||||
436,
|
||||
502,
|
||||
554,
|
||||
570,
|
||||
689
|
||||
],
|
||||
"D11": [
|
||||
21,
|
||||
104,
|
||||
124,
|
||||
138,
|
||||
140,
|
||||
208,
|
||||
263,
|
||||
282,
|
||||
330,
|
||||
331,
|
||||
369,
|
||||
377,
|
||||
401,
|
||||
437,
|
||||
440,
|
||||
492,
|
||||
518,
|
||||
569,
|
||||
629,
|
||||
633,
|
||||
634,
|
||||
636,
|
||||
637,
|
||||
638,
|
||||
641,
|
||||
643,
|
||||
646,
|
||||
687,
|
||||
727,
|
||||
736,
|
||||
804,
|
||||
811,
|
||||
831,
|
||||
832,
|
||||
834,
|
||||
835
|
||||
],
|
||||
"D12": [
|
||||
160,
|
||||
480,
|
||||
495,
|
||||
627,
|
||||
628,
|
||||
706,
|
||||
729,
|
||||
730,
|
||||
750,
|
||||
807,
|
||||
816
|
||||
],
|
||||
"D13": [
|
||||
11,
|
||||
173,
|
||||
265,
|
||||
367,
|
||||
398,
|
||||
407,
|
||||
500,
|
||||
511,
|
||||
512,
|
||||
515,
|
||||
696,
|
||||
698,
|
||||
699,
|
||||
718,
|
||||
771,
|
||||
830
|
||||
],
|
||||
"D14": [
|
||||
109,
|
||||
164,
|
||||
249,
|
||||
270,
|
||||
315,
|
||||
316,
|
||||
374,
|
||||
539,
|
||||
541,
|
||||
562,
|
||||
564,
|
||||
583,
|
||||
671,
|
||||
708,
|
||||
717,
|
||||
719,
|
||||
743,
|
||||
747
|
||||
],
|
||||
"D15": [
|
||||
9,
|
||||
103,
|
||||
110,
|
||||
111,
|
||||
112,
|
||||
228,
|
||||
259,
|
||||
288,
|
||||
312,
|
||||
448,
|
||||
469,
|
||||
477,
|
||||
478,
|
||||
557,
|
||||
616,
|
||||
620,
|
||||
621,
|
||||
623,
|
||||
624,
|
||||
711,
|
||||
737,
|
||||
786,
|
||||
827,
|
||||
829
|
||||
],
|
||||
"D16": [
|
||||
38,
|
||||
121,
|
||||
129,
|
||||
148,
|
||||
200,
|
||||
363,
|
||||
647,
|
||||
663,
|
||||
680,
|
||||
694,
|
||||
725,
|
||||
726,
|
||||
769
|
||||
],
|
||||
"D17": [
|
||||
14,
|
||||
292,
|
||||
346,
|
||||
524,
|
||||
525,
|
||||
527,
|
||||
529,
|
||||
575,
|
||||
664,
|
||||
744
|
||||
],
|
||||
"D18": [
|
||||
172,
|
||||
224,
|
||||
236,
|
||||
237,
|
||||
243,
|
||||
247,
|
||||
323,
|
||||
412,
|
||||
417,
|
||||
420,
|
||||
443,
|
||||
445,
|
||||
447,
|
||||
449,
|
||||
695,
|
||||
775,
|
||||
777,
|
||||
818
|
||||
],
|
||||
"D19": [
|
||||
98,
|
||||
142,
|
||||
226,
|
||||
238,
|
||||
262,
|
||||
309,
|
||||
338,
|
||||
349,
|
||||
351,
|
||||
404,
|
||||
419,
|
||||
602,
|
||||
659,
|
||||
665,
|
||||
764
|
||||
],
|
||||
"D20": [
|
||||
6,
|
||||
68,
|
||||
116,
|
||||
145,
|
||||
318,
|
||||
548,
|
||||
667,
|
||||
668,
|
||||
793,
|
||||
795
|
||||
],
|
||||
"D21": [
|
||||
28,
|
||||
67,
|
||||
136,
|
||||
177,
|
||||
185,
|
||||
260,
|
||||
272,
|
||||
278,
|
||||
494,
|
||||
598,
|
||||
600,
|
||||
604,
|
||||
632,
|
||||
688,
|
||||
704,
|
||||
705,
|
||||
713,
|
||||
746,
|
||||
783,
|
||||
796
|
||||
],
|
||||
"D22": [
|
||||
12,
|
||||
18,
|
||||
22,
|
||||
29,
|
||||
44,
|
||||
53,
|
||||
54,
|
||||
66,
|
||||
69,
|
||||
128,
|
||||
131,
|
||||
187,
|
||||
227,
|
||||
230,
|
||||
231,
|
||||
285,
|
||||
287,
|
||||
303,
|
||||
347,
|
||||
392,
|
||||
397,
|
||||
406,
|
||||
410,
|
||||
482,
|
||||
538,
|
||||
542,
|
||||
560,
|
||||
572,
|
||||
574,
|
||||
662,
|
||||
710,
|
||||
766,
|
||||
785,
|
||||
814
|
||||
],
|
||||
"D23": [
|
||||
123,
|
||||
176,
|
||||
391,
|
||||
516,
|
||||
652,
|
||||
654,
|
||||
655,
|
||||
791,
|
||||
792,
|
||||
794,
|
||||
800
|
||||
],
|
||||
"D24": [
|
||||
27,
|
||||
74,
|
||||
77,
|
||||
150,
|
||||
165,
|
||||
180,
|
||||
229,
|
||||
234,
|
||||
235,
|
||||
252,
|
||||
273,
|
||||
308,
|
||||
321,
|
||||
355,
|
||||
358,
|
||||
382,
|
||||
415,
|
||||
452,
|
||||
460,
|
||||
508,
|
||||
520,
|
||||
558,
|
||||
566,
|
||||
577,
|
||||
650,
|
||||
660,
|
||||
672,
|
||||
679,
|
||||
728,
|
||||
733,
|
||||
773,
|
||||
774,
|
||||
776
|
||||
],
|
||||
"D25": [
|
||||
5,
|
||||
17,
|
||||
46,
|
||||
58,
|
||||
59,
|
||||
60,
|
||||
78,
|
||||
139,
|
||||
212,
|
||||
221,
|
||||
342,
|
||||
362,
|
||||
372,
|
||||
378,
|
||||
435,
|
||||
439,
|
||||
489,
|
||||
522,
|
||||
537,
|
||||
571,
|
||||
582,
|
||||
587,
|
||||
685,
|
||||
734
|
||||
],
|
||||
"D26": [
|
||||
33,
|
||||
209,
|
||||
210,
|
||||
211,
|
||||
213,
|
||||
216,
|
||||
310,
|
||||
311,
|
||||
337,
|
||||
396,
|
||||
499,
|
||||
741,
|
||||
753
|
||||
],
|
||||
"D27": [
|
||||
23,
|
||||
174,
|
||||
261,
|
||||
266,
|
||||
298,
|
||||
299,
|
||||
360,
|
||||
368,
|
||||
509,
|
||||
576,
|
||||
603,
|
||||
607,
|
||||
644,
|
||||
735,
|
||||
806
|
||||
],
|
||||
"D28": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
143,
|
||||
220,
|
||||
274,
|
||||
466,
|
||||
505,
|
||||
712,
|
||||
732,
|
||||
837
|
||||
],
|
||||
"D29": [
|
||||
51,
|
||||
61,
|
||||
97,
|
||||
130,
|
||||
132,
|
||||
147,
|
||||
149,
|
||||
153,
|
||||
182,
|
||||
239,
|
||||
257,
|
||||
472,
|
||||
473,
|
||||
474,
|
||||
484,
|
||||
536,
|
||||
561,
|
||||
618,
|
||||
828
|
||||
],
|
||||
"D30": [
|
||||
3,
|
||||
56,
|
||||
101,
|
||||
214,
|
||||
408,
|
||||
409,
|
||||
411,
|
||||
441,
|
||||
471,
|
||||
476,
|
||||
510,
|
||||
597,
|
||||
599,
|
||||
701,
|
||||
702
|
||||
],
|
||||
"D31": [
|
||||
47,
|
||||
70,
|
||||
113,
|
||||
170,
|
||||
275,
|
||||
343,
|
||||
416,
|
||||
421,
|
||||
422,
|
||||
424,
|
||||
470,
|
||||
497,
|
||||
549,
|
||||
563,
|
||||
565,
|
||||
683,
|
||||
749,
|
||||
754,
|
||||
755,
|
||||
784,
|
||||
808,
|
||||
836
|
||||
],
|
||||
"D32": [
|
||||
19,
|
||||
24,
|
||||
25,
|
||||
100,
|
||||
159,
|
||||
506,
|
||||
559,
|
||||
605,
|
||||
673,
|
||||
674,
|
||||
675,
|
||||
676,
|
||||
677,
|
||||
684,
|
||||
781
|
||||
],
|
||||
"D33": [
|
||||
137,
|
||||
186,
|
||||
301,
|
||||
356,
|
||||
425,
|
||||
427,
|
||||
596,
|
||||
606,
|
||||
608,
|
||||
631,
|
||||
651,
|
||||
657,
|
||||
691,
|
||||
738,
|
||||
803
|
||||
],
|
||||
"D34": [
|
||||
32,
|
||||
108,
|
||||
126,
|
||||
146,
|
||||
365,
|
||||
530,
|
||||
531,
|
||||
532,
|
||||
533,
|
||||
534,
|
||||
535,
|
||||
588,
|
||||
592,
|
||||
593,
|
||||
615,
|
||||
681,
|
||||
682,
|
||||
720,
|
||||
722,
|
||||
723,
|
||||
724,
|
||||
751,
|
||||
767,
|
||||
768
|
||||
],
|
||||
"D35": [
|
||||
16,
|
||||
26,
|
||||
39,
|
||||
52,
|
||||
55,
|
||||
71,
|
||||
107,
|
||||
166,
|
||||
167,
|
||||
258,
|
||||
289,
|
||||
291,
|
||||
400,
|
||||
426,
|
||||
446,
|
||||
493,
|
||||
550,
|
||||
573,
|
||||
584,
|
||||
586,
|
||||
622,
|
||||
648,
|
||||
661,
|
||||
686,
|
||||
690,
|
||||
697,
|
||||
801,
|
||||
817
|
||||
],
|
||||
"D36": [
|
||||
102,
|
||||
152,
|
||||
203,
|
||||
284,
|
||||
327,
|
||||
797
|
||||
],
|
||||
"D37": [
|
||||
10,
|
||||
80,
|
||||
115,
|
||||
161,
|
||||
313,
|
||||
317,
|
||||
357,
|
||||
395,
|
||||
444,
|
||||
498,
|
||||
513,
|
||||
556,
|
||||
594,
|
||||
693,
|
||||
703,
|
||||
740,
|
||||
765,
|
||||
825
|
||||
],
|
||||
"D38": [
|
||||
50,
|
||||
64,
|
||||
83,
|
||||
114,
|
||||
122,
|
||||
206,
|
||||
307,
|
||||
341,
|
||||
429,
|
||||
431,
|
||||
434,
|
||||
496,
|
||||
714,
|
||||
715,
|
||||
716,
|
||||
772
|
||||
],
|
||||
"D39": [
|
||||
13,
|
||||
43,
|
||||
45,
|
||||
48,
|
||||
49,
|
||||
120,
|
||||
244,
|
||||
339,
|
||||
340,
|
||||
344,
|
||||
464,
|
||||
578,
|
||||
579,
|
||||
580,
|
||||
581,
|
||||
610,
|
||||
611,
|
||||
612,
|
||||
613,
|
||||
614
|
||||
],
|
||||
"D40": [
|
||||
36,
|
||||
79,
|
||||
117,
|
||||
118,
|
||||
119,
|
||||
155,
|
||||
393,
|
||||
428,
|
||||
432,
|
||||
504,
|
||||
507,
|
||||
546,
|
||||
547,
|
||||
553,
|
||||
780
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,805 @@
|
||||
{
|
||||
"T01": [
|
||||
90,
|
||||
162,
|
||||
178,
|
||||
188,
|
||||
292,
|
||||
443,
|
||||
503,
|
||||
658,
|
||||
811,
|
||||
812
|
||||
],
|
||||
"T02": [
|
||||
116,
|
||||
125,
|
||||
214,
|
||||
227,
|
||||
261,
|
||||
288,
|
||||
312,
|
||||
363,
|
||||
375,
|
||||
448,
|
||||
478,
|
||||
547,
|
||||
593,
|
||||
594,
|
||||
647,
|
||||
702,
|
||||
703,
|
||||
711,
|
||||
721,
|
||||
786,
|
||||
829,
|
||||
831,
|
||||
832
|
||||
],
|
||||
"T03": [
|
||||
240,
|
||||
277,
|
||||
279,
|
||||
280,
|
||||
281,
|
||||
342,
|
||||
447,
|
||||
685,
|
||||
782,
|
||||
820,
|
||||
821
|
||||
],
|
||||
"T04": [
|
||||
3,
|
||||
73,
|
||||
74,
|
||||
110,
|
||||
111,
|
||||
113,
|
||||
115,
|
||||
161,
|
||||
271,
|
||||
272,
|
||||
309,
|
||||
336,
|
||||
395,
|
||||
444,
|
||||
600,
|
||||
679,
|
||||
693,
|
||||
696,
|
||||
698,
|
||||
765
|
||||
],
|
||||
"T05": [
|
||||
37,
|
||||
58,
|
||||
75,
|
||||
151,
|
||||
172,
|
||||
173,
|
||||
175,
|
||||
177,
|
||||
199,
|
||||
201,
|
||||
218,
|
||||
236,
|
||||
251,
|
||||
264,
|
||||
265,
|
||||
267,
|
||||
323,
|
||||
328,
|
||||
361,
|
||||
367,
|
||||
370,
|
||||
371,
|
||||
415,
|
||||
479,
|
||||
500,
|
||||
540,
|
||||
543,
|
||||
777,
|
||||
815,
|
||||
822
|
||||
],
|
||||
"T06": [
|
||||
140,
|
||||
146,
|
||||
183,
|
||||
184,
|
||||
186,
|
||||
190,
|
||||
195,
|
||||
197,
|
||||
402,
|
||||
403,
|
||||
404,
|
||||
405,
|
||||
410
|
||||
],
|
||||
"T07": [
|
||||
18,
|
||||
21,
|
||||
39,
|
||||
44,
|
||||
53,
|
||||
106,
|
||||
107,
|
||||
126,
|
||||
160,
|
||||
167,
|
||||
285,
|
||||
373,
|
||||
392,
|
||||
400,
|
||||
406,
|
||||
482,
|
||||
532,
|
||||
560,
|
||||
574,
|
||||
608,
|
||||
622,
|
||||
627,
|
||||
642,
|
||||
710,
|
||||
783,
|
||||
785,
|
||||
801,
|
||||
816,
|
||||
830
|
||||
],
|
||||
"T08": [
|
||||
103,
|
||||
134,
|
||||
165,
|
||||
360,
|
||||
477,
|
||||
545,
|
||||
571,
|
||||
576,
|
||||
577,
|
||||
602,
|
||||
625,
|
||||
632,
|
||||
729,
|
||||
810
|
||||
],
|
||||
"T09": [
|
||||
9,
|
||||
71,
|
||||
114,
|
||||
132,
|
||||
133,
|
||||
144,
|
||||
166,
|
||||
182,
|
||||
192,
|
||||
238,
|
||||
239,
|
||||
256,
|
||||
257,
|
||||
259,
|
||||
289,
|
||||
303,
|
||||
341,
|
||||
356,
|
||||
419,
|
||||
480,
|
||||
501,
|
||||
550,
|
||||
557,
|
||||
561,
|
||||
617,
|
||||
618,
|
||||
619,
|
||||
620,
|
||||
621,
|
||||
623,
|
||||
624,
|
||||
648,
|
||||
651,
|
||||
660,
|
||||
661,
|
||||
665,
|
||||
691,
|
||||
730,
|
||||
750,
|
||||
751,
|
||||
805,
|
||||
807,
|
||||
809
|
||||
],
|
||||
"T10": [
|
||||
86,
|
||||
337,
|
||||
358,
|
||||
359,
|
||||
364,
|
||||
435,
|
||||
439,
|
||||
440,
|
||||
441,
|
||||
650
|
||||
],
|
||||
"T11": [
|
||||
108,
|
||||
325,
|
||||
326,
|
||||
484,
|
||||
531,
|
||||
558,
|
||||
615,
|
||||
795,
|
||||
800
|
||||
],
|
||||
"T12": [
|
||||
27,
|
||||
168,
|
||||
180,
|
||||
207,
|
||||
378,
|
||||
399,
|
||||
452,
|
||||
461,
|
||||
506,
|
||||
517,
|
||||
598,
|
||||
603,
|
||||
659,
|
||||
674,
|
||||
677,
|
||||
688,
|
||||
731,
|
||||
742,
|
||||
749,
|
||||
806,
|
||||
808
|
||||
],
|
||||
"T13": [
|
||||
297,
|
||||
343,
|
||||
344,
|
||||
345,
|
||||
348,
|
||||
533,
|
||||
534,
|
||||
555,
|
||||
744
|
||||
],
|
||||
"T14": [
|
||||
5,
|
||||
46,
|
||||
60,
|
||||
78,
|
||||
139,
|
||||
174,
|
||||
196,
|
||||
212,
|
||||
221,
|
||||
230,
|
||||
333,
|
||||
362,
|
||||
369,
|
||||
372,
|
||||
423,
|
||||
489,
|
||||
492,
|
||||
522,
|
||||
537,
|
||||
569,
|
||||
587,
|
||||
609,
|
||||
635,
|
||||
641,
|
||||
643,
|
||||
734,
|
||||
735,
|
||||
797
|
||||
],
|
||||
"T15": [
|
||||
25,
|
||||
77,
|
||||
245,
|
||||
252,
|
||||
275,
|
||||
276,
|
||||
306,
|
||||
317,
|
||||
322,
|
||||
355,
|
||||
416,
|
||||
421,
|
||||
422,
|
||||
424,
|
||||
490,
|
||||
520,
|
||||
538,
|
||||
544,
|
||||
549,
|
||||
551,
|
||||
559,
|
||||
566,
|
||||
605,
|
||||
649,
|
||||
683,
|
||||
728,
|
||||
754,
|
||||
774,
|
||||
776,
|
||||
825,
|
||||
826,
|
||||
836
|
||||
],
|
||||
"T16": [
|
||||
23,
|
||||
24,
|
||||
26,
|
||||
64,
|
||||
65,
|
||||
136,
|
||||
143,
|
||||
185,
|
||||
202,
|
||||
494,
|
||||
496,
|
||||
606,
|
||||
798
|
||||
],
|
||||
"T17": [
|
||||
17,
|
||||
22,
|
||||
30,
|
||||
35,
|
||||
47,
|
||||
127,
|
||||
170,
|
||||
269,
|
||||
283,
|
||||
287,
|
||||
380,
|
||||
381,
|
||||
401,
|
||||
426,
|
||||
427,
|
||||
526,
|
||||
542,
|
||||
556,
|
||||
585,
|
||||
596,
|
||||
631,
|
||||
639,
|
||||
646,
|
||||
692,
|
||||
736,
|
||||
755,
|
||||
779,
|
||||
799,
|
||||
802,
|
||||
803
|
||||
],
|
||||
"T18": [
|
||||
19,
|
||||
43,
|
||||
45,
|
||||
48,
|
||||
49,
|
||||
159,
|
||||
339,
|
||||
340,
|
||||
365,
|
||||
578,
|
||||
579,
|
||||
580,
|
||||
581,
|
||||
610,
|
||||
611,
|
||||
612,
|
||||
613,
|
||||
614
|
||||
],
|
||||
"T19": [
|
||||
15,
|
||||
331,
|
||||
366,
|
||||
377,
|
||||
379,
|
||||
529,
|
||||
626,
|
||||
634,
|
||||
636,
|
||||
637,
|
||||
638,
|
||||
644,
|
||||
687
|
||||
],
|
||||
"T20": [
|
||||
96,
|
||||
217,
|
||||
219,
|
||||
220,
|
||||
250,
|
||||
274,
|
||||
313,
|
||||
432,
|
||||
433,
|
||||
502,
|
||||
505,
|
||||
689
|
||||
],
|
||||
"T21": [
|
||||
232,
|
||||
528,
|
||||
582,
|
||||
668,
|
||||
675,
|
||||
676,
|
||||
684
|
||||
],
|
||||
"T22": [
|
||||
222,
|
||||
329,
|
||||
425,
|
||||
568,
|
||||
775
|
||||
],
|
||||
"T23": [
|
||||
7,
|
||||
8,
|
||||
124,
|
||||
171,
|
||||
206,
|
||||
253,
|
||||
282,
|
||||
308,
|
||||
316,
|
||||
338,
|
||||
398,
|
||||
414,
|
||||
418,
|
||||
420,
|
||||
434,
|
||||
436,
|
||||
466,
|
||||
504,
|
||||
509,
|
||||
514,
|
||||
553,
|
||||
554,
|
||||
564,
|
||||
570,
|
||||
712,
|
||||
714,
|
||||
716,
|
||||
741
|
||||
],
|
||||
"T24": [
|
||||
142,
|
||||
446,
|
||||
565,
|
||||
584,
|
||||
588,
|
||||
686,
|
||||
697,
|
||||
704,
|
||||
732
|
||||
],
|
||||
"T25": [
|
||||
13,
|
||||
16,
|
||||
20,
|
||||
28,
|
||||
29,
|
||||
88,
|
||||
135,
|
||||
141,
|
||||
266,
|
||||
332,
|
||||
368,
|
||||
495,
|
||||
573,
|
||||
586,
|
||||
604,
|
||||
607,
|
||||
628,
|
||||
629,
|
||||
630,
|
||||
690,
|
||||
727,
|
||||
748,
|
||||
759,
|
||||
784,
|
||||
817,
|
||||
818,
|
||||
819
|
||||
],
|
||||
"T26": [
|
||||
189,
|
||||
191,
|
||||
193,
|
||||
194,
|
||||
203,
|
||||
204,
|
||||
205,
|
||||
247,
|
||||
263,
|
||||
284,
|
||||
592,
|
||||
595,
|
||||
669,
|
||||
695,
|
||||
709,
|
||||
804
|
||||
],
|
||||
"T27": [
|
||||
79,
|
||||
98,
|
||||
99,
|
||||
109,
|
||||
164,
|
||||
249,
|
||||
270,
|
||||
374,
|
||||
539,
|
||||
541,
|
||||
562,
|
||||
583,
|
||||
664,
|
||||
671,
|
||||
719,
|
||||
740,
|
||||
743,
|
||||
773
|
||||
],
|
||||
"T28": [
|
||||
181,
|
||||
346,
|
||||
347,
|
||||
349,
|
||||
350,
|
||||
353,
|
||||
460,
|
||||
518,
|
||||
535,
|
||||
722,
|
||||
726,
|
||||
737,
|
||||
738,
|
||||
814
|
||||
],
|
||||
"T29": [
|
||||
155,
|
||||
233,
|
||||
234,
|
||||
262,
|
||||
546,
|
||||
780,
|
||||
834
|
||||
],
|
||||
"T30": [
|
||||
11,
|
||||
243,
|
||||
315,
|
||||
407,
|
||||
511,
|
||||
512,
|
||||
515,
|
||||
589,
|
||||
699,
|
||||
700,
|
||||
720,
|
||||
723,
|
||||
747
|
||||
],
|
||||
"T31": [
|
||||
31,
|
||||
32,
|
||||
34,
|
||||
296,
|
||||
530,
|
||||
681,
|
||||
682,
|
||||
724,
|
||||
725,
|
||||
752,
|
||||
766,
|
||||
767,
|
||||
768
|
||||
],
|
||||
"T32": [
|
||||
76,
|
||||
100,
|
||||
105,
|
||||
128,
|
||||
235,
|
||||
237,
|
||||
246,
|
||||
327,
|
||||
382,
|
||||
397,
|
||||
472,
|
||||
473,
|
||||
474,
|
||||
483,
|
||||
507,
|
||||
548,
|
||||
645,
|
||||
666,
|
||||
667,
|
||||
670,
|
||||
678,
|
||||
708,
|
||||
717,
|
||||
718,
|
||||
770,
|
||||
788,
|
||||
789,
|
||||
790,
|
||||
792,
|
||||
793,
|
||||
828
|
||||
],
|
||||
"T33": [
|
||||
69,
|
||||
70,
|
||||
80,
|
||||
101,
|
||||
129,
|
||||
226,
|
||||
396,
|
||||
408,
|
||||
470,
|
||||
471,
|
||||
476,
|
||||
497,
|
||||
498,
|
||||
499,
|
||||
510,
|
||||
527,
|
||||
563,
|
||||
597,
|
||||
599,
|
||||
701,
|
||||
769,
|
||||
771,
|
||||
813
|
||||
],
|
||||
"T34": [
|
||||
4,
|
||||
118,
|
||||
229,
|
||||
351,
|
||||
417,
|
||||
449,
|
||||
450,
|
||||
656,
|
||||
657,
|
||||
772,
|
||||
781
|
||||
],
|
||||
"T35": [
|
||||
10,
|
||||
12,
|
||||
38,
|
||||
42,
|
||||
59,
|
||||
62,
|
||||
121,
|
||||
324,
|
||||
352,
|
||||
354,
|
||||
409,
|
||||
662,
|
||||
680,
|
||||
694,
|
||||
739,
|
||||
823
|
||||
],
|
||||
"T36": [
|
||||
123,
|
||||
209,
|
||||
254,
|
||||
521,
|
||||
791
|
||||
],
|
||||
"T37": [
|
||||
67,
|
||||
68,
|
||||
104,
|
||||
137,
|
||||
169,
|
||||
231,
|
||||
260,
|
||||
273,
|
||||
278,
|
||||
298,
|
||||
321,
|
||||
429,
|
||||
431,
|
||||
493,
|
||||
508,
|
||||
572,
|
||||
705,
|
||||
713,
|
||||
733,
|
||||
745,
|
||||
746,
|
||||
796
|
||||
],
|
||||
"T38": [
|
||||
57,
|
||||
61,
|
||||
63,
|
||||
66,
|
||||
97,
|
||||
120,
|
||||
130,
|
||||
147,
|
||||
150,
|
||||
198,
|
||||
242,
|
||||
244,
|
||||
314,
|
||||
438,
|
||||
445,
|
||||
464,
|
||||
519,
|
||||
536,
|
||||
601,
|
||||
640,
|
||||
824,
|
||||
827,
|
||||
833
|
||||
],
|
||||
"T39": [
|
||||
40,
|
||||
41,
|
||||
72,
|
||||
131,
|
||||
163,
|
||||
187,
|
||||
268,
|
||||
293,
|
||||
301,
|
||||
302,
|
||||
304,
|
||||
305,
|
||||
391,
|
||||
411,
|
||||
475,
|
||||
567,
|
||||
590,
|
||||
663,
|
||||
756,
|
||||
762,
|
||||
763
|
||||
],
|
||||
"T40": [
|
||||
122,
|
||||
299,
|
||||
412,
|
||||
707,
|
||||
778
|
||||
],
|
||||
"T41": [
|
||||
82,
|
||||
102,
|
||||
156,
|
||||
157,
|
||||
158,
|
||||
225,
|
||||
290,
|
||||
430,
|
||||
485,
|
||||
486,
|
||||
487,
|
||||
488,
|
||||
516,
|
||||
652,
|
||||
653,
|
||||
654,
|
||||
655,
|
||||
787,
|
||||
794
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"""Carry v1 scene summaries over to a re-split corpus, summarising only scenes whose text is new.
|
||||
|
||||
cd v2 && STP_OLLAMA=http://host:11434 python3 ../carry_summaries.py ../summaries.json summaries.json
|
||||
|
||||
The v1 split merged three quoted-title stories into their neighbours (SELECTION.md, "Pilot"). Re-splitting
|
||||
renumbers every later scene but leaves almost every scene's text identical, so a scene whose text matches
|
||||
a v1 scene keeps its v1 summary verbatim, and only the rest go to the model, with summarise.py's model
|
||||
and prompt. Output has summarise.py's layout, in the new scene order, with 'carried' marking which is which.
|
||||
"""
|
||||
import json, pathlib, re, sys, time
|
||||
import ollama
|
||||
|
||||
src, dst = pathlib.Path(sys.argv[1]), pathlib.Path(sys.argv[2])
|
||||
old = json.loads(src.read_text(encoding='utf-8'))
|
||||
old_chunks = json.loads((src.parent / 'chunks.json').read_text(encoding='utf-8'))
|
||||
chunks = json.loads(pathlib.Path('chunks.json').read_text(encoding='utf-8'))
|
||||
|
||||
# summarise.py runs on import, so its prompt is read from the file; it must be the one v1 was made with.
|
||||
code = (pathlib.Path(__file__).parent / 'summarise.py').read_text(encoding='utf-8')
|
||||
SYSTEM = re.search(r'SYSTEM = """(.*?)"""', code, re.S).group(1)
|
||||
END = re.compile(r'(?<!\bMr)(?<!\bMrs)(?<!\bMs)(?<!\bDr)(?<!\bSt)(?<!\bNo)[.!?](?:\s|$)')
|
||||
if SYSTEM != old['prompt']:
|
||||
sys.exit('summarise.py prompt differs from the one v1 summaries were made with')
|
||||
|
||||
by_text = {old_chunks[x['chunk']]['text']: x for x in old['summaries']}
|
||||
out, fresh, t0 = [], 0, time.time()
|
||||
for i, c in enumerate(chunks):
|
||||
prev = by_text.get(c['text'])
|
||||
if prev:
|
||||
out.append({'chunk': i, 'story': c['story'], 'summary': prev['summary'],
|
||||
'model_output': prev['model_output'], 'carried': prev['chunk']})
|
||||
continue
|
||||
text, _, _ = ollama.chat(old['model'], SYSTEM, c['text'])
|
||||
t = re.sub(r'\s+', ' ', text).strip().strip('"')
|
||||
m = END.search(t)
|
||||
out.append({'chunk': i, 'story': c['story'], 'summary': t[:m.end()].strip() if m else t,
|
||||
'model_output': text, 'carried': None})
|
||||
fresh += 1
|
||||
print(f' new scene {i} ({c["title"]}): {out[-1]["summary"]}', flush=True)
|
||||
|
||||
dst.write_text(json.dumps({'model': old['model'], 'prompt': SYSTEM, 'summaries': out}, indent=1), encoding='utf-8')
|
||||
print(f'{len(out)} summaries: {len(out) - fresh} carried from {src}, {fresh} new, {time.time() - t0:.0f}s')
|
||||
@@ -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))
|
||||
@@ -0,0 +1,30 @@
|
||||
dev: 40 candidates, 5 labelled 2, rule must accept >= 3 of them
|
||||
|
||||
baseline accept all candidates accepted 40 P2 0.12 P1+ 0.62 R2 1.00
|
||||
baseline B0: tightness filter accepted 17 P2 0.24 P1+ 0.76 R2 0.80
|
||||
|
||||
best single-signal rule per signal:
|
||||
S1 >= 0.6153 accepted 9 P2 0.44 P1+ 0.89 R2 0.80
|
||||
S2 >= 0.9083 accepted 20 P2 0.15 P1+ 0.60 R2 0.60
|
||||
S3 >= 0.4580 accepted 31 P2 0.16 P1+ 0.68 R2 1.00
|
||||
S4 >= 0.0295 accepted 14 P2 0.21 P1+ 0.64 R2 0.60
|
||||
S5 >= 0.1733 accepted 10 P2 0.30 P1+ 0.60 R2 0.60
|
||||
S6 <= 0.2137 accepted 33 P2 0.15 P1+ 0.70 R2 1.00
|
||||
|
||||
leaderboard (all eligible rules):
|
||||
S1 >= 0.6119 AND S6 <= 0.1603 accepted 5 P2 0.60 P1+ 1.00 R2 0.60
|
||||
S1 >= 0.6119 AND S6 <= 0.1742 accepted 5 P2 0.60 P1+ 1.00 R2 0.60
|
||||
S1 >= 0.6153 AND S6 <= 0.1603 accepted 5 P2 0.60 P1+ 1.00 R2 0.60
|
||||
S1 >= 0.6153 AND S6 <= 0.1742 accepted 5 P2 0.60 P1+ 1.00 R2 0.60
|
||||
S1 >= 0.6153 AND S5 >= 0.1733 accepted 5 P2 0.60 P1+ 0.80 R2 0.60
|
||||
S1 >= 0.6119 AND S6 <= 0.2137 accepted 7 P2 0.57 P1+ 1.00 R2 0.80
|
||||
S1 >= 0.6119 AND S6 <= 0.3012 accepted 7 P2 0.57 P1+ 1.00 R2 0.80
|
||||
S1 >= 0.6153 AND S6 <= 0.2137 accepted 7 P2 0.57 P1+ 1.00 R2 0.80
|
||||
S1 >= 0.6153 AND S6 <= 0.3012 accepted 7 P2 0.57 P1+ 1.00 R2 0.80
|
||||
S1 >= 0.6153 AND S5 >= 0.1278 accepted 7 P2 0.57 P1+ 0.86 R2 0.80
|
||||
S1 >= 0.6153 AND S5 >= 0.1318 accepted 7 P2 0.57 P1+ 0.86 R2 0.80
|
||||
S1 >= 0.6153 AND S5 >= 0.1356 accepted 7 P2 0.57 P1+ 0.86 R2 0.80
|
||||
|
||||
CHOSEN: S1 >= 0.6119 AND S6 <= 0.1603 -> rule-frozen.json
|
||||
accepted on dev: D04(2), D09(1), D13(2), D16(2), D33(1)
|
||||
exit 0
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Cut stories into scene-sized chunks on paragraph boundaries."""
|
||||
import json, pathlib, re
|
||||
|
||||
TARGET = 320 # words; a scene is a situation with people in it
|
||||
|
||||
stories = json.loads(pathlib.Path('stories.json').read_text(encoding='utf-8'))
|
||||
chunks = []
|
||||
for si, s in enumerate(stories):
|
||||
paras = [p.strip() for p in re.split(r'\n\s*\n', s['text']) if p.strip()]
|
||||
buf, n = [], 0
|
||||
for p in paras:
|
||||
w = len(p.split())
|
||||
if n + w > TARGET and buf:
|
||||
chunks.append({'story': si, 'title': s['title'], 'volume': s['volume'],
|
||||
'text': ' '.join(buf), 'words': n})
|
||||
buf, n = [], 0
|
||||
buf.append(p); n += w
|
||||
if buf and n >= 80:
|
||||
chunks.append({'story': si, 'title': s['title'], 'volume': s['volume'],
|
||||
'text': ' '.join(buf), 'words': n})
|
||||
|
||||
pathlib.Path('chunks.json').write_text(json.dumps(chunks), encoding='utf-8')
|
||||
ws = sorted(c['words'] for c in chunks)
|
||||
print(f'chunks: {len(chunks)} from {len(stories)} stories')
|
||||
print(f'words/chunk min {ws[0]} median {ws[len(ws)//2]} max {ws[-1]}')
|
||||
print(f'total words: {sum(ws):,}')
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,80 @@
|
||||
"""Cluster scene embeddings and report what the clusters actually contain.
|
||||
|
||||
The question this answers: does a thematically coherent corpus contain
|
||||
RECURRING situations, or 800 singletons? A pack needs ~15 repeatable
|
||||
situations per stage; a cluster that draws from many different stories is a
|
||||
recurring situation, one that draws from a single story is just a scene.
|
||||
"""
|
||||
import json, math, pathlib, random, sys
|
||||
|
||||
K = int(sys.argv[1]) if len(sys.argv) > 1 else 40
|
||||
SEED = 11
|
||||
|
||||
chunks = json.loads(pathlib.Path('chunks.json').read_text(encoding='utf-8'))
|
||||
vecs = json.loads(pathlib.Path('embeddings.json').read_text(encoding='utf-8'))
|
||||
n = min(len(chunks), len(vecs))
|
||||
chunks, vecs = chunks[:n], vecs[:n]
|
||||
print(f'clustering {n} chunks into k={K}\n')
|
||||
|
||||
def norm(v):
|
||||
m = math.sqrt(sum(x * x for x in v)) or 1.0
|
||||
return [x / m for x in v]
|
||||
vecs = [norm(v) for v in vecs]
|
||||
dim = len(vecs[0])
|
||||
|
||||
def dot(a, b): return sum(x * y for x, y in zip(a, b))
|
||||
|
||||
# k-means++ init on cosine distance (vectors are unit length, so dot == cos)
|
||||
random.seed(SEED)
|
||||
cent = [vecs[random.randrange(n)]]
|
||||
while len(cent) < K:
|
||||
d2 = [min((1 - dot(v, c)) for c in cent) ** 2 for v in vecs]
|
||||
tot = sum(d2) or 1.0
|
||||
r = random.random() * tot
|
||||
acc = 0.0
|
||||
for i, x in enumerate(d2):
|
||||
acc += x
|
||||
if acc >= r:
|
||||
cent.append(vecs[i]); break
|
||||
else:
|
||||
cent.append(vecs[random.randrange(n)])
|
||||
|
||||
assign = [0] * n
|
||||
for it in range(30):
|
||||
moved = 0
|
||||
for i, v in enumerate(vecs):
|
||||
best, bs = 0, -2.0
|
||||
for k, c in enumerate(cent):
|
||||
s = dot(v, c)
|
||||
if s > bs: bs, best = s, k
|
||||
if assign[i] != best: assign[i] = best; moved += 1
|
||||
for k in range(K):
|
||||
mem = [vecs[i] for i in range(n) if assign[i] == k]
|
||||
if not mem: continue
|
||||
cent[k] = norm([sum(m[d] for m in mem) / len(mem) for d in range(dim)])
|
||||
if moved == 0: break
|
||||
|
||||
groups = {}
|
||||
for i, k in enumerate(assign): groups.setdefault(k, []).append(i)
|
||||
|
||||
rows = []
|
||||
for k, idx in groups.items():
|
||||
stories = {chunks[i]['story'] for i in idx}
|
||||
coh = sum(dot(vecs[i], cent[k]) for i in idx) / len(idx)
|
||||
rows.append({'k': k, 'size': len(idx), 'stories': len(stories), 'coh': coh, 'idx': idx})
|
||||
rows.sort(key=lambda r: (-r['stories'], -r['size']))
|
||||
|
||||
print(f"{'size':>5} {'stories':>8} {'coh':>6} sample titles")
|
||||
print('-' * 92)
|
||||
for r in rows:
|
||||
titles = []
|
||||
for i in r['idx']:
|
||||
t = chunks[i]['title']
|
||||
if t not in titles: titles.append(t)
|
||||
print(f"{r['size']:5} {r['stories']:8} {r['coh']:6.3f} {', '.join(titles[:4])[:66]}")
|
||||
|
||||
multi = [r for r in rows if r['stories'] >= 4]
|
||||
print(f"\nclusters drawing on >=4 different stories: {len(multi)} of {K}")
|
||||
print(f"clusters that are essentially one story: {sum(1 for r in rows if r['stories'] <= 2)}")
|
||||
pathlib.Path('clusters.json').write_text(json.dumps(
|
||||
[{'k': r['k'], 'size': r['size'], 'stories': r['stories'], 'coh': r['coh'], 'idx': r['idx']} for r in rows]))
|
||||
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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,56 @@
|
||||
838 texts, resuming at 0, model nomic-embed-text
|
||||
16/838 2s (7.22/s)
|
||||
32/838 3s (10.12/s)
|
||||
48/838 4s (11.75/s)
|
||||
64/838 5s (12.87/s)
|
||||
80/838 6s (13.54/s)
|
||||
96/838 7s (14.07/s)
|
||||
112/838 8s (14.48/s)
|
||||
128/838 9s (14.78/s)
|
||||
144/838 10s (15.01/s)
|
||||
160/838 11s (15.15/s)
|
||||
176/838 12s (15.29/s)
|
||||
192/838 12s (15.41/s)
|
||||
208/838 13s (15.52/s)
|
||||
224/838 14s (15.61/s)
|
||||
240/838 15s (15.70/s)
|
||||
256/838 16s (15.77/s)
|
||||
272/838 17s (15.83/s)
|
||||
288/838 18s (15.88/s)
|
||||
304/838 19s (15.91/s)
|
||||
320/838 20s (15.94/s)
|
||||
336/838 21s (15.98/s)
|
||||
352/838 22s (15.99/s)
|
||||
368/838 23s (16.01/s)
|
||||
384/838 24s (16.00/s)
|
||||
400/838 25s (16.00/s)
|
||||
416/838 26s (16.00/s)
|
||||
432/838 27s (16.03/s)
|
||||
448/838 28s (16.04/s)
|
||||
464/838 29s (16.07/s)
|
||||
480/838 30s (16.07/s)
|
||||
496/838 31s (16.07/s)
|
||||
512/838 32s (16.08/s)
|
||||
528/838 33s (16.08/s)
|
||||
544/838 34s (16.08/s)
|
||||
560/838 35s (16.08/s)
|
||||
576/838 36s (16.07/s)
|
||||
592/838 37s (16.06/s)
|
||||
608/838 38s (16.06/s)
|
||||
624/838 39s (16.07/s)
|
||||
640/838 40s (16.05/s)
|
||||
656/838 41s (16.04/s)
|
||||
672/838 42s (16.03/s)
|
||||
688/838 43s (16.02/s)
|
||||
704/838 44s (16.01/s)
|
||||
720/838 45s (16.00/s)
|
||||
736/838 46s (15.98/s)
|
||||
752/838 47s (15.96/s)
|
||||
768/838 48s (15.95/s)
|
||||
784/838 49s (15.94/s)
|
||||
800/838 50s (15.93/s)
|
||||
816/838 51s (15.91/s)
|
||||
832/838 52s (15.90/s)
|
||||
838/838 53s (15.76/s)
|
||||
done: 838 vectors, 53s this session
|
||||
exit 0
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Embed texts, checkpointing after every batch so an interrupted run resumes.
|
||||
|
||||
python3 embed.py chunks.json embeddings.json # the scenes' prose
|
||||
python3 embed.py summaries.json summary_embeddings.json # one-line situations
|
||||
python3 embed.py chunks.json embeddings.json --limit 48 # pilot
|
||||
|
||||
Input is a JSON list of objects with a 'text' field (chunks.json), or of
|
||||
strings/objects with a 'summary' field (summaries.json).
|
||||
"""
|
||||
import argparse, json, os, pathlib, time
|
||||
import ollama
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('src'); ap.add_argument('dst')
|
||||
ap.add_argument('--limit', type=int, default=None)
|
||||
ap.add_argument('--batch', type=int, default=16)
|
||||
args = ap.parse_args()
|
||||
MODEL = os.environ.get('STP_EMBED_MODEL', 'nomic-embed-text')
|
||||
|
||||
items = json.loads(pathlib.Path(args.src).read_text(encoding='utf-8'))
|
||||
if isinstance(items, dict): items = items['summaries']
|
||||
texts = [x if isinstance(x, str) else (x.get('summary') or x['text']) for x in items]
|
||||
if args.limit: texts = texts[:args.limit]
|
||||
|
||||
out = pathlib.Path(args.dst)
|
||||
state = json.loads(out.read_text()) if out.exists() else {'model': MODEL, 'vectors': []}
|
||||
if state['model'] != MODEL: raise SystemExit(f'{out} was made with {state["model"]}, not {MODEL}')
|
||||
done = state['vectors']
|
||||
print(f'{len(texts)} texts, resuming at {len(done)}, model {MODEL}', flush=True)
|
||||
|
||||
t0, start = time.time(), len(done)
|
||||
for i in range(start, len(texts), args.batch):
|
||||
done.extend(ollama.embed(MODEL, texts[i:i + args.batch]))
|
||||
out.write_text(json.dumps(state))
|
||||
el = time.time() - t0
|
||||
print(f' {len(done)}/{len(texts)} {el:.0f}s ({(len(done) - start) / max(el, 1):.2f}/s)', flush=True)
|
||||
print(f'done: {len(done)} vectors, {time.time() - t0:.0f}s this session')
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"file": "embeddings.json",
|
||||
"chunks": 838,
|
||||
"rows": [
|
||||
{
|
||||
"k": 25,
|
||||
"sized": 25,
|
||||
"median_dominant": 0.25,
|
||||
"recurring": 21,
|
||||
"coverage": 0.9284009546539379,
|
||||
"tight_recurring": 9,
|
||||
"tight_coverage": 0.2863961813842482
|
||||
},
|
||||
{
|
||||
"k": 40,
|
||||
"sized": 39,
|
||||
"median_dominant": 0.35714285714285715,
|
||||
"recurring": 21,
|
||||
"coverage": 0.7088305489260143,
|
||||
"tight_recurring": 5,
|
||||
"tight_coverage": 0.09069212410501193
|
||||
},
|
||||
{
|
||||
"k": 60,
|
||||
"sized": 55,
|
||||
"median_dominant": 0.5714285714285714,
|
||||
"recurring": 20,
|
||||
"coverage": 0.5501193317422435,
|
||||
"tight_recurring": 3,
|
||||
"tight_coverage": 0.02028639618138425
|
||||
},
|
||||
{
|
||||
"k": 100,
|
||||
"sized": 78,
|
||||
"median_dominant": 0.6666666666666666,
|
||||
"recurring": 16,
|
||||
"coverage": 0.29594272076372313,
|
||||
"tight_recurring": 0,
|
||||
"tight_coverage": 0.0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
qwen3:14b: 250 labelled cluster rows; parse failures 0
|
||||
theta chosen on dev: 0.6
|
||||
dev FIT >= 0.3 accepted 29 >=1 19 label2 5 P1+ 0.66 recall 0.76
|
||||
dev FIT >= 0.4 accepted 28 >=1 18 label2 5 P1+ 0.64 recall 0.72
|
||||
dev FIT >= 0.5 accepted 14 >=1 10 label2 2 P1+ 0.71 recall 0.40
|
||||
dev FIT >= 0.6 accepted 8 >=1 7 label2 2 P1+ 0.88 recall 0.28
|
||||
dev FIT >= 0.7 accepted 4 >=1 4 label2 1 P1+ 1.00 recall 0.16
|
||||
dev FIT >= 0.8 accepted 2 >=1 2 label2 0 P1+ 1.00 recall 0.08
|
||||
dev FIT >= 0.9 accepted 0 >=1 0 label2 0 P1+ 0.00 recall 0.00
|
||||
dev FIT >= 1.0 accepted 0 >=1 0 label2 0 P1+ 0.00 recall 0.00
|
||||
|
||||
mean FIT by label (all rows):
|
||||
label 0: n=121 mean 0.40 median 0.42
|
||||
label 1: n=106 mean 0.47 median 0.47
|
||||
label 2: n= 23 mean 0.52 median 0.53
|
||||
|
||||
PRIMARY: validate + validate3: 148 clusters, 67 labelled >=1
|
||||
all accepted 148 >=1 67 label2 10 P1+ 0.45 recall 1.00
|
||||
G2 accepted 49 >=1 36 label2 5 P1+ 0.73 recall 0.54
|
||||
G4 FIT >= 0.6 accepted 42 >=1 22 label2 4 P1+ 0.52 recall 0.33
|
||||
G4 AND G2 accepted 10 >=1 10 label2 1 P1+ 1.00 recall 0.15
|
||||
G4 size <=9 accepted 17 >=1 5 label2 2 P1+ 0.29 recall 0.56 (of 48)
|
||||
G4 size 10-25 accepted 14 >=1 7 label2 2 P1+ 0.50 recall 0.23 (of 63)
|
||||
G4 size >=26 accepted 11 >=1 10 label2 0 P1+ 0.91 recall 0.37 (of 37)
|
||||
|
||||
secondary: test + recover: 62 clusters, 37 labelled >=1
|
||||
all accepted 62 >=1 37 label2 8 P1+ 0.60 recall 1.00
|
||||
G2 accepted 28 >=1 23 label2 5 P1+ 0.82 recall 0.62
|
||||
G4 FIT >= 0.6 accepted 15 >=1 9 label2 1 P1+ 0.60 recall 0.24
|
||||
G4 AND G2 accepted 11 >=1 8 label2 1 P1+ 0.73 recall 0.22
|
||||
G4 size <=9 accepted 1 >=1 0 label2 0 P1+ 0.00 recall 0.00 (of 8)
|
||||
G4 size 10-25 accepted 5 >=1 3 label2 1 P1+ 0.60 recall 0.15 (of 30)
|
||||
G4 size >=26 accepted 9 >=1 6 label2 0 P1+ 0.67 recall 0.35 (of 24)
|
||||
|
||||
criterion 1 (P1+ >= 0.80): fail (0.52)
|
||||
criterion 2 (more >=1 than G2): fail (22 vs 36)
|
||||
criterion 3 (<=9 members P1+ >= 0.67 if >= 3 accepted): fail (17 accepted, P1+ 0.29)
|
||||
OVERALL: fail
|
||||
|
||||
accepted on the primary set, model situation vs label name:
|
||||
Y026 size 6 label 0 fit 1.00 model: Someone wants something from someone else, but there is | label:
|
||||
Y052 size 23 label 2 fit 1.00 model: Planning or attending a dinner with hidden intentions | label: an invitation to dine
|
||||
X002 size 10 label 1 fit 0.90 model: Someone seeks artistic validation or support from anoth | label: struggling artists and money
|
||||
Y010 size 18 label 1 fit 0.87 model: Artist seeking financial support or patronage | label: struggling artists and money
|
||||
Y053 size 45 label 1 fit 0.87 model: Conflict between individuals over personal issues | label: a brawl or showdown
|
||||
X015 size 38 label 0 fit 0.80 model: Seeking something from someone in a city setting | label:
|
||||
X030 size 18 label 1 fit 0.80 model: Confrontation between individuals or groups leading to | label: a brawl or showdown
|
||||
X055 size 21 label 2 fit 0.80 model: Marital conflict and tension | label: a spouse schemes against or confronts a spouse over infidelity
|
||||
Y008 size 5 label 0 fit 0.80 model: Someone holds power over someone else | label:
|
||||
Y011 size 15 label 0 fit 0.80 model: Someone confronts someone else over a personal issue | label:
|
||||
Y034 size 5 label 0 fit 0.80 model: conflict between perception and reality | label:
|
||||
X038 size 8 label 0 fit 0.75 model: Struggling to find acceptance or connection in a hostil | label:
|
||||
X048 size 20 label 1 fit 0.73 model: Financial transaction with potential conflict | label: money owed and repayment
|
||||
X058 size 16 label 0 fit 0.73 model: A host welcomes a visitor with mixed emotions and inten | label:
|
||||
Y017 size 45 label 1 fit 0.73 model: Invitation to dinner with hidden intentions | label: an invitation to dine
|
||||
Y073 size 36 label 1 fit 0.73 model: Someone demands information or action from another, who | label: writers and editors
|
||||
X013 size 7 label 1 fit 0.71 model: Seeking financial advice | label: what to do with money, and whose advice to take
|
||||
X024 size 7 label 0 fit 0.71 model: Demanding repayment or compensation | label:
|
||||
Y030 size 7 label 1 fit 0.71 model: Social interaction at a dining establishment | label: who holds sway in a dining room or hotel
|
||||
X020 size 24 label 0 fit 0.67 model: secretive interactions between individuals | label:
|
||||
X050 size 45 label 1 fit 0.67 model: Financial transactions involving coercion or manipulati | label: schemes, cons and theft over money
|
||||
X059 size 19 label 1 fit 0.67 model: Marriage proposals and preparations | label: a proposal or engagement, accepted or refused
|
||||
X068 size 6 label 2 fit 0.67 model: Marital conflict and confrontation | label: a spouse schemes against or confronts a spouse over infidelity
|
||||
X072 size 17 label 0 fit 0.67 model: Resentment over unreciprocated affection | label:
|
||||
Y031 size 6 label 0 fit 0.67 model: Older person mentoring younger person | label:
|
||||
Y036 size 6 label 0 fit 0.67 model: envy and desire for wealth or power | label:
|
||||
Y040 size 6 label 1 fit 0.67 model: Competing for social status through dining experiences | label: who holds sway in a dining room or hotel
|
||||
Y068 size 6 label 0 fit 0.67 model: Conflict between individuals with opposing goals | label:
|
||||
X010 size 11 label 0 fit 0.64 model: competition for status or position in a city | label:
|
||||
Y050 size 8 label 0 fit 0.62 model: Someone trying to win someone else's affection | label:
|
||||
Y063 size 8 label 0 fit 0.62 model: Power dynamics between individuals | label:
|
||||
X009 size 45 label 1 fit 0.60 model: conflict over marriage proposals and social expectation | label: a proposal or engagement, accepted or refused
|
||||
X012 size 10 label 0 fit 0.60 model: A person of high status seeking to help someone in need | label:
|
||||
X014 size 10 label 0 fit 0.60 model: Someone threatens someone else | label:
|
||||
X037 size 5 label 0 fit 0.60 model: conflict between individuals due to past relationships | label:
|
||||
X042 size 5 label 2 fit 0.60 model: Dispute over payment for a service | label: a public quarrel with a shopkeeper, driver or official
|
||||
X043 size 38 label 1 fit 0.60 model: Dinner party with social dynamics | label: who holds sway in a dining room or hotel
|
||||
X056 size 45 label 1 fit 0.60 model: Attempting to gain favor or influence through social in | label: trying to impress or win someone's favour
|
||||
X057 size 45 label 1 fit 0.60 model: Someone tries to manipulate someone else for personal g | label: schemes, cons and theft over money
|
||||
Y021 size 45 label 1 fit 0.60 model: Someone manipulates someone into a transaction for pers | label: schemes, cons and theft over money
|
||||
Y033 size 5 label 0 fit 0.60 model: Someone is suspected of a crime they did not commit | label:
|
||||
Y043 size 31 label 1 fit 0.60 model: Confrontation over a dispute or accusation | label: a run-in with the police
|
||||
@@ -0,0 +1,90 @@
|
||||
qwen2.5:3b-instruct: 250 labelled cluster rows; parse failures 1
|
||||
theta chosen on dev: 0.6
|
||||
dev FIT >= 0.3 accepted 22 >=1 15 label2 4 P1+ 0.68 recall 0.60
|
||||
dev FIT >= 0.4 accepted 20 >=1 13 label2 3 P1+ 0.65 recall 0.52
|
||||
dev FIT >= 0.5 accepted 16 >=1 11 label2 2 P1+ 0.69 recall 0.44
|
||||
dev FIT >= 0.6 accepted 9 >=1 8 label2 2 P1+ 0.89 recall 0.32
|
||||
dev FIT >= 0.7 accepted 6 >=1 5 label2 2 P1+ 0.83 recall 0.20
|
||||
dev FIT >= 0.8 accepted 4 >=1 4 label2 2 P1+ 1.00 recall 0.16
|
||||
dev FIT >= 0.9 accepted 2 >=1 2 label2 1 P1+ 1.00 recall 0.08
|
||||
dev FIT >= 1.0 accepted 1 >=1 1 label2 0 P1+ 1.00 recall 0.04
|
||||
|
||||
mean FIT by label (all rows):
|
||||
label 0: n=121 mean 0.39 median 0.40
|
||||
label 1: n=106 mean 0.49 median 0.50
|
||||
label 2: n= 23 mean 0.42 median 0.33
|
||||
|
||||
PRIMARY: validate + validate3: 148 clusters, 67 labelled >=1
|
||||
all accepted 148 >=1 67 label2 10 P1+ 0.45 recall 1.00
|
||||
G2 accepted 49 >=1 36 label2 5 P1+ 0.73 recall 0.54
|
||||
G4 FIT >= 0.6 accepted 50 >=1 24 label2 4 P1+ 0.48 recall 0.36
|
||||
G4 AND G2 accepted 18 >=1 11 label2 1 P1+ 0.61 recall 0.16
|
||||
G4 size <=9 accepted 13 >=1 3 label2 1 P1+ 0.23 recall 0.33 (of 48)
|
||||
G4 size 10-25 accepted 24 >=1 12 label2 3 P1+ 0.50 recall 0.39 (of 63)
|
||||
G4 size >=26 accepted 13 >=1 9 label2 0 P1+ 0.69 recall 0.33 (of 37)
|
||||
|
||||
secondary: test + recover: 62 clusters, 37 labelled >=1
|
||||
all accepted 62 >=1 37 label2 8 P1+ 0.60 recall 1.00
|
||||
G2 accepted 28 >=1 23 label2 5 P1+ 0.82 recall 0.62
|
||||
G4 FIT >= 0.6 accepted 21 >=1 15 label2 0 P1+ 0.71 recall 0.41
|
||||
G4 AND G2 accepted 14 >=1 12 label2 0 P1+ 0.86 recall 0.32
|
||||
G4 size <=9 accepted 1 >=1 0 label2 0 P1+ 0.00 recall 0.00 (of 8)
|
||||
G4 size 10-25 accepted 8 >=1 5 label2 0 P1+ 0.62 recall 0.25 (of 30)
|
||||
G4 size >=26 accepted 12 >=1 10 label2 0 P1+ 0.83 recall 0.59 (of 24)
|
||||
|
||||
criterion 1 (P1+ >= 0.80): fail (0.48)
|
||||
criterion 2 (more >=1 than G2): fail (24 vs 36)
|
||||
criterion 3 (<=9 members P1+ >= 0.67 if >= 3 accepted): fail (13 accepted, P1+ 0.23)
|
||||
OVERALL: fail
|
||||
|
||||
accepted on the primary set, model situation vs label name:
|
||||
X003 size 39 label 0 fit 1.00 model: demand and confrontation | label:
|
||||
X019 size 45 label 1 fit 1.00 model: tension between unequal parties | label: a public quarrel with a shopkeeper, driver or official
|
||||
X067 size 23 label 0 fit 1.00 model: power struggle/conflict over resources | label:
|
||||
Y006 size 45 label 1 fit 1.00 model: manipulation and exploitation | label: schemes, cons and theft over money
|
||||
Y008 size 5 label 0 fit 1.00 model: power struggle/conflict | label:
|
||||
Y017 size 45 label 1 fit 1.00 model: coercion and manipulation for personal gain | label: an invitation to dine
|
||||
Y032 size 13 label 1 fit 1.00 model: confrontation over a personal issue | label: a run-in with the police
|
||||
Y040 size 6 label 1 fit 1.00 model: people seek social status or attention | label: who holds sway in a dining room or hotel
|
||||
Y044 size 5 label 0 fit 1.00 model: dinner party | label:
|
||||
Y047 size 18 label 2 fit 1.00 model: competing for status or position | label: working girls: jobs, wages and standing
|
||||
Y053 size 45 label 1 fit 1.00 model: confrontation | label: a brawl or showdown
|
||||
X035 size 45 label 0 fit 0.93 model: Tension between parties | label:
|
||||
X043 size 38 label 1 fit 0.93 model: dinner party/hosting | label: who holds sway in a dining room or hotel
|
||||
Y071 size 21 label 1 fit 0.93 model: Negotiation and deal-making | label: schemes, cons and theft over money
|
||||
Y074 size 11 label 1 fit 0.91 model: courtship and social status | label: working girls: jobs, wages and standing
|
||||
Y019 size 10 label 0 fit 0.90 model: seeking novelty or adventure | label:
|
||||
Y063 size 8 label 0 fit 0.88 model: power dynamics in social hierarchies | label:
|
||||
Y052 size 23 label 2 fit 0.87 model: dinner plans and dynamics | label: an invitation to dine
|
||||
X028 size 6 label 0 fit 0.83 model: power dynamics in social hierarchies | label:
|
||||
X012 size 10 label 0 fit 0.80 model: people seeking or offering help or support | label:
|
||||
Y001 size 23 label 0 fit 0.80 model: Money disputes and transactions | label:
|
||||
Y066 size 17 label 0 fit 0.80 model: power dynamics in luxury vs. poverty | label:
|
||||
Y070 size 5 label 0 fit 0.80 model: people pursuing their livelihood or interests | label:
|
||||
Y002 size 9 label 0 fit 0.78 model: manipulation and hidden agendas | label:
|
||||
X011 size 43 label 1 fit 0.73 model: NEGOTIATION OVER RESOURCES OR SERVICES | label: drinking in the saloon
|
||||
X020 size 24 label 0 fit 0.73 model: people hiding or avoiding attention | label:
|
||||
X033 size 45 label 0 fit 0.73 model: negotiation and payment | label:
|
||||
X036 size 47 label 1 fit 0.73 model: reveal death to family | label: illness, loss and a missing relative
|
||||
X048 size 20 label 1 fit 0.73 model: financial transaction or dispute | label: money owed and repayment
|
||||
X049 size 25 label 0 fit 0.73 model: coercion or manipulation | label:
|
||||
X055 size 21 label 2 fit 0.73 model: marital conflict | label: a spouse schemes against or confronts a spouse over infidelity
|
||||
X058 size 16 label 0 fit 0.73 model: welcoming and revealing history | label:
|
||||
Y011 size 15 label 0 fit 0.73 model: conflict between individuals seeking different outcomes | label:
|
||||
Y022 size 24 label 1 fit 0.73 model: con or scam | label: schemes, cons and theft over money
|
||||
Y062 size 45 label 1 fit 0.73 model: Confrontation over a personal relationship | label: a spouse schemes against or confronts a spouse over infidelity
|
||||
X039 size 11 label 1 fit 0.73 model: Workplace conflict and exploitation | label: working girls: jobs, wages and standing
|
||||
X024 size 7 label 0 fit 0.71 model: NEGOCIACTION | label:
|
||||
Y030 size 7 label 1 fit 0.71 model: dining and socializing | label: who holds sway in a dining room or hotel
|
||||
X053 size 16 label 0 fit 0.67 model: exploit and exploitation | label:
|
||||
X057 size 45 label 1 fit 0.67 model: NEGOTIATION AND POWER PLAY | label: schemes, cons and theft over money
|
||||
X059 size 19 label 1 fit 0.67 model: Wedding elopement and deception | label: a proposal or engagement, accepted or refused
|
||||
X063 size 6 label 0 fit 0.67 model: confrontation | label:
|
||||
Y048 size 12 label 0 fit 0.67 model: seeking advice or guidance | label:
|
||||
Y055 size 6 label 0 fit 0.67 model: confrontation | label:
|
||||
Y020 size 11 label 0 fit 0.64 model: seeking connections or companionship | label:
|
||||
Y029 size 8 label 0 fit 0.62 model: dinner party with hidden agendas | label:
|
||||
X041 size 17 label 1 fit 0.60 model: manipulation and control | label: money and marriage: wealthy suitors, inheritances
|
||||
X042 size 5 label 2 fit 0.60 model: argue over payment | label: a public quarrel with a shopkeeper, driver or official
|
||||
X045 size 25 label 1 fit 0.60 model: seeking/stuck in job/searching employment | label: struggling artists and money
|
||||
X073 size 45 label 0 fit 0.60 model: coercion and manipulation | label:
|
||||
@@ -0,0 +1,42 @@
|
||||
method acc P1+ P2 #2 distinct dup names cover multi success
|
||||
B 4 1.00 0.00 0 4 0 13% 0 -
|
||||
M1 4 1.00 0.00 0 4 0 13% 0 fail
|
||||
M2 3 1.00 1.00 3 3 0 5% 0 fail
|
||||
M3 16 0.69 0.00 0 10 1 45% 68 fail
|
||||
|
||||
per method, accepted clusters:
|
||||
|
||||
B:
|
||||
R001 size 13 label 1 an inheritance or fortune and what it obliges
|
||||
R006 size 36 label 1 a spouse schemes against or confronts a spouse over infidelity
|
||||
R007 size 38 label 1 schemes, cons and theft over money
|
||||
R012 size 22 label 1 who holds sway in a dining room or hotel
|
||||
|
||||
M1:
|
||||
R001 size 13 label 1 an inheritance or fortune and what it obliges
|
||||
R006 size 36 label 1 a spouse schemes against or confronts a spouse over infidelity
|
||||
R007 size 38 label 1 schemes, cons and theft over money
|
||||
R012 size 22 label 1 who holds sway in a dining room or hotel
|
||||
|
||||
M2:
|
||||
R011 size 14 label 2 money and marriage: wealthy suitors, inheritances
|
||||
R015 size 15 label 2 a spouse schemes against or confronts a spouse over infidelity
|
||||
R020 size 17 label 2 someone coerces someone with a threat
|
||||
|
||||
M3:
|
||||
R002 size 30 label 0 -
|
||||
R003 size 30 label 1 someone coerces someone with a threat
|
||||
R004 size 28 label 1 a run-in with the police
|
||||
R005 size 30 label 0 -
|
||||
R006 size 36 label 1 a spouse schemes against or confronts a spouse over infidelity
|
||||
R007 size 38 label 1 schemes, cons and theft over money
|
||||
R008 size 30 label 1 courtship
|
||||
R009 size 30 label 1 coercion over money: threats, bribes, demands for payment
|
||||
R010 size 37 label 1 courtship
|
||||
R013 size 30 label 0 -
|
||||
R014 size 30 label 1 drinking in the saloon
|
||||
R016 size 17 label 0 -
|
||||
R017 size 30 label 1 money owed and repayment
|
||||
R018 size 39 label 1 trying to impress or win someone's favour
|
||||
R019 size 30 label 1 who holds sway in a dining room or hotel
|
||||
R021 size 30 label 0 -
|
||||
@@ -0,0 +1,90 @@
|
||||
"""Score G4 (model-judged FIT) against the blind labels (SELECTION.md, "G4").
|
||||
|
||||
python3 evaluate_judge.py qwen2.5:3b-instruct
|
||||
"""
|
||||
import hashlib, json, pathlib, statistics, sys
|
||||
import gate2 as g
|
||||
|
||||
MODEL = sys.argv[1]
|
||||
judged = json.loads(pathlib.Path(f"judge-{MODEL.replace(':', '_').replace('/', '_')}.json").read_text())
|
||||
G2 = json.loads(pathlib.Path('gate2-frozen.json').read_text())
|
||||
|
||||
def key(members):
|
||||
return hashlib.sha1(','.join(map(str, sorted(members))).encode()).hexdigest()[:16]
|
||||
|
||||
sources = [('dev', 'candidates-dev.json', 'labels-dev.json', False), ('test', 'candidates-test.json', 'labels-test.json', False),
|
||||
('recover', 'recover-map.json', 'labels-recover.json', True), ('validate', 'validate-map.json', 'labels-validate.json', True),
|
||||
('validate3', 'validate3-map.json', 'labels-validate3.json', True)]
|
||||
rows = []
|
||||
for name, cfile, lfile, is_map in sources:
|
||||
cand = json.loads(pathlib.Path(cfile).read_text())
|
||||
lab = json.loads(pathlib.Path(lfile).read_text())
|
||||
for cid, v in cand.items():
|
||||
members = v['members'] if is_map else v
|
||||
j = judged.get(key(members))
|
||||
if j is None: continue
|
||||
sig = g.signals(members)
|
||||
rows.append({'set': name, 'id': cid, 'label': lab[cid]['label'], 'name': lab[cid]['name'],
|
||||
'size': len(members), 'fit': j['fit'], 'parsed': j['parsed'], 'situation': j['situation'],
|
||||
'g2': g.g2_passes(sig, G2)})
|
||||
|
||||
def score(sub, accept):
|
||||
acc = [r for r in sub if accept(r)]
|
||||
n1 = sum(r['label'] >= 1 for r in acc)
|
||||
return {'accepted': len(acc), 'label1plus': n1, 'label2': sum(r['label'] == 2 for r in acc),
|
||||
'P1+': n1 / len(acc) if acc else 0.0,
|
||||
'recall1+': n1 / max(1, sum(r['label'] >= 1 for r in sub))}
|
||||
|
||||
def line(label, s):
|
||||
return (f" {label:22} accepted {s['accepted']:3} >=1 {s['label1plus']:3} label2 {s['label2']:2} "
|
||||
f"P1+ {s['P1+']:.2f} recall {s['recall1+']:.2f}")
|
||||
|
||||
dev = [r for r in rows if r['set'] == 'dev']
|
||||
best = None
|
||||
for theta in (0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0):
|
||||
s = score(dev, lambda r: r['fit'] >= theta)
|
||||
if s['accepted'] == 0 or s['P1+'] < 0.85: continue
|
||||
k = (s['label1plus'], s['label2'], theta)
|
||||
if best is None or k > best[0]: best = (k, theta)
|
||||
theta = best[1] if best else None
|
||||
print(f'{MODEL}: {len(rows)} labelled cluster rows; parse failures {sum(not r["parsed"] for r in rows)}')
|
||||
print(f'theta chosen on dev: {theta}' + ('' if theta is not None else ' (no theta reaches P1+ >= 0.85 on dev)'))
|
||||
for t in (0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0):
|
||||
print(line(f'dev FIT >= {t}', score(dev, lambda r: r['fit'] >= t)))
|
||||
|
||||
print('\nmean FIT by label (all rows):')
|
||||
for lab in (0, 1, 2):
|
||||
f = [r['fit'] for r in rows if r['label'] == lab]
|
||||
print(f' label {lab}: n={len(f):3} mean {statistics.mean(f):.2f} median {statistics.median(f):.2f}')
|
||||
|
||||
if theta is not None:
|
||||
g4 = lambda r: r['fit'] >= theta
|
||||
for title, names in (('PRIMARY: validate + validate3', ('validate', 'validate3')), ('secondary: test + recover', ('test', 'recover'))):
|
||||
sub = [r for r in rows if r['set'] in names]
|
||||
print(f'\n{title}: {len(sub)} clusters, {sum(r["label"] >= 1 for r in sub)} labelled >=1')
|
||||
print(line('all', score(sub, lambda r: True)))
|
||||
print(line('G2', score(sub, lambda r: r['g2'])))
|
||||
print(line(f'G4 FIT >= {theta}', score(sub, g4)))
|
||||
print(line('G4 AND G2', score(sub, lambda r: g4(r) and r['g2'])))
|
||||
for band, lo, hi in (('<=9', 0, 9), ('10-25', 10, 25), ('>=26', 26, 999)):
|
||||
b = [r for r in sub if lo <= r['size'] <= hi]
|
||||
print(line(f' G4 size {band}', score(b, g4)) + f' (of {len(b)})')
|
||||
|
||||
prim = [r for r in rows if r['set'] in ('validate', 'validate3')]
|
||||
s4, s2 = score(prim, g4), score(prim, lambda r: r['g2'])
|
||||
small = score([r for r in prim if r['size'] <= 9], g4)
|
||||
c1 = s4['P1+'] >= 0.80
|
||||
c2 = s4['label1plus'] > s2['label1plus']
|
||||
c3 = small['accepted'] < 3 or small['P1+'] >= 0.67
|
||||
print(f'\ncriterion 1 (P1+ >= 0.80): {"PASS" if c1 else "fail"} ({s4["P1+"]:.2f})')
|
||||
print(f'criterion 2 (more >=1 than G2): {"PASS" if c2 else "fail"} ({s4["label1plus"]} vs {s2["label1plus"]})')
|
||||
print(f'criterion 3 (<=9 members P1+ >= 0.67 if >= 3 accepted): {"PASS" if c3 else "fail"} '
|
||||
f'({small["accepted"]} accepted, P1+ {small["P1+"]:.2f})')
|
||||
print(f'OVERALL: {"PASS" if c1 and c2 and c3 else "fail"}')
|
||||
|
||||
print('\naccepted on the primary set, model situation vs label name:')
|
||||
for r in sorted(prim, key=lambda r: -r['fit']):
|
||||
if g4(r):
|
||||
print(f" {r['id']} size {r['size']:2} label {r['label']} fit {r['fit']:.2f} model: {r['situation'][:55]:55} | label: {r['name']}")
|
||||
pathlib.Path(f"judge-result-{MODEL.replace(':', '_')}.json").write_text(json.dumps(
|
||||
{'theta': theta, 'primary_G4': s4, 'primary_G2': s2, 'small': small, 'criteria': [c1, c2, c3]}, indent=1))
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Score the recall-recovery methods against the blind labels (SELECTION.md, "Recovering recall").
|
||||
|
||||
python3 evaluate_recover.py
|
||||
"""
|
||||
import collections, json, pathlib
|
||||
|
||||
mapping = json.loads(pathlib.Path('recover-map.json').read_text())
|
||||
labels = json.loads(pathlib.Path('labels-recover.json').read_text())
|
||||
assert all(labels[r]['label'] in (0, 1, 2) for r in mapping), 'labels-recover.json is incomplete'
|
||||
N_SCENES = 838
|
||||
|
||||
stats = {}
|
||||
for method in ('B', 'M1', 'M2', 'M3'):
|
||||
ids = [r for r, e in mapping.items() if method in e['methods']]
|
||||
lab = [labels[r]['label'] for r in ids]
|
||||
names = [labels[r]['name'].strip().lower() for r in ids if labels[r]['label'] >= 1]
|
||||
scenes = collections.Counter(i for r in ids for i in mapping[r]['members'])
|
||||
stats[method] = {
|
||||
'accepted': len(ids),
|
||||
'precision1plus': sum(l >= 1 for l in lab) / len(ids) if ids else 0.0,
|
||||
'precision2': sum(l == 2 for l in lab) / len(ids) if ids else 0.0,
|
||||
'label2': sum(l == 2 for l in lab),
|
||||
'distinct_situations': len(set(names)),
|
||||
'repeated_names': sum(c - 1 for c in collections.Counter(names).values() if c > 1),
|
||||
'coverage': len(scenes) / N_SCENES,
|
||||
'scenes_in_several': sum(v > 1 for v in scenes.values()),
|
||||
'ids': ids,
|
||||
}
|
||||
|
||||
b = stats['B']
|
||||
print(f"{'method':6} {'acc':>4} {'P1+':>5} {'P2':>5} {'#2':>3} {'distinct':>8} {'dup names':>9} {'cover':>6} {'multi':>5} success")
|
||||
for m, s in stats.items():
|
||||
ok = (m != 'B' and s['accepted'] >= 2 * b['accepted'] and s['precision1plus'] >= 0.80
|
||||
and s['distinct_situations'] >= 2 * b['distinct_situations'])
|
||||
print(f"{m:6} {s['accepted']:4} {s['precision1plus']:5.2f} {s['precision2']:5.2f} {s['label2']:3} "
|
||||
f"{s['distinct_situations']:8} {s['repeated_names']:9} {s['coverage']:6.0%} {s['scenes_in_several']:5} "
|
||||
f"{'-' if m == 'B' else ('PASS' if ok else 'fail')}")
|
||||
|
||||
print('\nper method, accepted clusters:')
|
||||
for m, s in stats.items():
|
||||
print(f'\n{m}:')
|
||||
for r in s['ids']:
|
||||
e = mapping[r]
|
||||
print(f" {r} size {len(e['members']):2} label {labels[r]['label']} {labels[r]['name'] or '-'}")
|
||||
pathlib.Path('recover-result.json').write_text(json.dumps(
|
||||
{m: {k: v for k, v in s.items() if k != 'ids'} for m, s in stats.items()}, indent=1))
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"S1": 0.6036626536817667,
|
||||
"Z1": 5,
|
||||
"MMIN": null,
|
||||
"W": 0.6
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,182 @@
|
||||
"""G2, the size-invariant gate: signals, the old gate for comparison, and k-means. No inference.
|
||||
|
||||
Import it from the probe directory. On first import it builds and caches pairwise similarities
|
||||
(sim.pkl) and the same-size random baseline for Z1 (null.pkl).
|
||||
|
||||
S1 mean summary cosine over cross-story member pairs
|
||||
Z1 S1 as a z-score against random member sets of the same size
|
||||
MMIN lowest member-level mean cosine to members of other stories
|
||||
W largest share among words whose sharing is significant (hypergeometric, Bonferroni 0.01)
|
||||
"""
|
||||
import collections, json, math, pathlib, pickle, random, re
|
||||
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
MIN_SIZE, MIN_STORIES, MAX_DOMINANT = 5, 4, 0.40
|
||||
|
||||
chunks = json.loads((HERE / 'chunks.json').read_text(encoding='utf-8'))
|
||||
_raw = json.loads((HERE / 'summary_embeddings.json').read_text())
|
||||
_vecs = _raw['vectors'] if isinstance(_raw, dict) else _raw
|
||||
n = min(len(chunks), len(_vecs))
|
||||
|
||||
def norm(v):
|
||||
m = math.sqrt(sum(x * x for x in v)) or 1.0
|
||||
return [x / m for x in v]
|
||||
|
||||
V = [norm(v) for v in _vecs[:n]]
|
||||
dim = len(V[0])
|
||||
story = [chunks[i]['story'] for i in range(n)]
|
||||
text = {x['chunk']: x['summary'] for x in
|
||||
json.loads((HERE / 'summaries_clean.json').read_text(encoding='utf-8'))['summaries']}
|
||||
|
||||
def dot(a, b):
|
||||
s = 0.0
|
||||
for x, y in zip(a, b): s += x * y
|
||||
return s
|
||||
|
||||
_sim_path = HERE / 'sim.pkl'
|
||||
if _sim_path.exists():
|
||||
SIM = pickle.loads(_sim_path.read_bytes())
|
||||
else:
|
||||
SIM = [[0.0] * n for _ in range(n)]
|
||||
for a in range(n):
|
||||
va, row = V[a], SIM[a]
|
||||
for b in range(a, n):
|
||||
s = dot(va, V[b])
|
||||
row[b] = s
|
||||
SIM[b][a] = s
|
||||
_sim_path.write_bytes(pickle.dumps(SIM))
|
||||
|
||||
# Identical to signals.py.
|
||||
STOP = set("""a an the and or but of to in on at for with by from as into over under about after before
|
||||
while during than then that this these those it its his her hers him he she they them their theirs we our
|
||||
you your i me my is are was were be been being has have had do does did not no nor so such who whom whose
|
||||
which what when where why how all any each both either neither one two some other others another more most
|
||||
less least very just only also even yet still now up down out off away back again once own same too can
|
||||
could will would shall should may might must upon despite toward towards between among amid amidst
|
||||
someone someone's someone’s""".split())
|
||||
docs = [set(re.findall(r"[a-z][a-z'’]+", text[i].lower())) - STOP for i in range(n)]
|
||||
df = collections.Counter(w for d in docs for w in d)
|
||||
ALPHA = 0.01 / len(df)
|
||||
|
||||
def describe(m):
|
||||
counts = collections.Counter(story[i] for i in m)
|
||||
return len(m), len(counts), max(counts.values()) / len(m)
|
||||
|
||||
def eligible(m):
|
||||
size, nst, dom = describe(m)
|
||||
return size >= MIN_SIZE and nst >= MIN_STORIES and dom <= MAX_DOMINANT
|
||||
|
||||
def cohesion(m):
|
||||
"""(S1, MMIN)."""
|
||||
tot, cnt, lowest = 0.0, 0, None
|
||||
for a in m:
|
||||
s, c, row = 0.0, 0, SIM[a]
|
||||
for b in m:
|
||||
if story[a] != story[b]:
|
||||
s += row[b]; c += 1
|
||||
if c:
|
||||
tot += s; cnt += c
|
||||
mean = s / c
|
||||
lowest = mean if lowest is None else min(lowest, mean)
|
||||
return (tot / cnt if cnt else 0.0), (lowest if lowest is not None else 0.0)
|
||||
|
||||
# Same-size random baseline for Z1.
|
||||
_null_path = HERE / 'null.pkl'
|
||||
if _null_path.exists():
|
||||
NULL = pickle.loads(_null_path.read_bytes())
|
||||
else:
|
||||
rnd, NULL = random.Random(2026), {}
|
||||
for m in range(5, 151):
|
||||
samples, need = [], 300 if m <= 40 else 120
|
||||
while len(samples) < need:
|
||||
members = rnd.sample(range(n), m)
|
||||
if eligible(members):
|
||||
samples.append(cohesion(members)[0])
|
||||
mu = sum(samples) / len(samples)
|
||||
sd = math.sqrt(sum((x - mu) ** 2 for x in samples) / (len(samples) - 1))
|
||||
NULL[m] = (mu, sd)
|
||||
_null_path.write_bytes(pickle.dumps(NULL))
|
||||
|
||||
def _lchoose(a, b):
|
||||
return math.lgamma(a + 1) - math.lgamma(b + 1) - math.lgamma(a - b + 1)
|
||||
|
||||
def hyper_sf(c, m, k, total):
|
||||
"""P(X >= c) for X ~ Hypergeometric(total, k successes, m draws)."""
|
||||
top = min(m, k)
|
||||
if c > top: return 0.0
|
||||
logs = [_lchoose(k, x) + _lchoose(total - k, m - x) - _lchoose(total, m)
|
||||
for x in range(c, top + 1) if m - x <= total - k]
|
||||
if not logs: return 0.0
|
||||
mx = max(logs)
|
||||
return math.exp(mx) * sum(math.exp(l - mx) for l in logs)
|
||||
|
||||
def word_dominance(m):
|
||||
"""(W, word): largest member share among significantly shared words."""
|
||||
size = len(m)
|
||||
cw = collections.Counter(w for i in m for w in docs[i])
|
||||
best = (0.0, '')
|
||||
for w, c in cw.items():
|
||||
if c < 2: continue
|
||||
if hyper_sf(c, size, df[w], n) <= ALPHA and c / size > best[0]:
|
||||
best = (c / size, w)
|
||||
return best
|
||||
|
||||
def old_s6(m):
|
||||
"""The first gate's S6, identical to signals.py."""
|
||||
size = len(m)
|
||||
cw = collections.Counter(w for i in m for w in docs[i])
|
||||
best = None
|
||||
for w, c in cw.items():
|
||||
if c < 2: continue
|
||||
score = math.log((c + .5) / (size - c + .5)) - math.log((df[w] + .5) / (n - df[w] + .5))
|
||||
if best is None or score > best[0]: best = (score, c)
|
||||
return best[1] / size if best else 0.0
|
||||
|
||||
def signals(m):
|
||||
size, nst, dom = describe(m)
|
||||
s1, mmin = cohesion(m)
|
||||
mu, sd = NULL[min(max(size, 5), 150)]
|
||||
w, word = word_dominance(m)
|
||||
return {'size': size, 'stories': nst, 'dominant': dom, 'S1': s1, 'Z1': (s1 - mu) / sd,
|
||||
'MMIN': mmin, 'W': w, 'W_word': word, 'S6_old': old_s6(m)}
|
||||
|
||||
OLD_GATE = json.loads((HERE / 'rule-frozen.json').read_text())
|
||||
_OLD = {s: t for s, _, t in OLD_GATE['terms']}
|
||||
|
||||
def old_passes(sig):
|
||||
return sig['size'] >= MIN_SIZE and sig['stories'] >= MIN_STORIES and sig['dominant'] <= MAX_DOMINANT \
|
||||
and sig['S1'] >= _OLD['S1'] and sig['S6_old'] <= _OLD['S6']
|
||||
|
||||
def g2_passes(sig, gate):
|
||||
if not (sig['size'] >= MIN_SIZE and sig['stories'] >= MIN_STORIES and sig['dominant'] <= MAX_DOMINANT):
|
||||
return False
|
||||
if gate.get('S1') is not None and sig['S1'] < gate['S1']: return False
|
||||
if gate.get('Z1') is not None and sig['Z1'] < gate['Z1']: return False
|
||||
if gate.get('MMIN') is not None and sig['MMIN'] < gate['MMIN']: return False
|
||||
if gate.get('W') is not None and sig['W'] >= gate['W']: return False
|
||||
return True
|
||||
|
||||
def kmeans(k, seed, iters=15):
|
||||
"""Identical to measure.py's k-means over the summary vectors."""
|
||||
rnd = random.Random(seed)
|
||||
cent = [V[i] for i in rnd.sample(range(n), k)]
|
||||
assign = [-1] * n
|
||||
for _ in range(iters):
|
||||
moved = 0
|
||||
for i, v in enumerate(V):
|
||||
best, bs = 0, -2.0
|
||||
for j, c in enumerate(cent):
|
||||
s = dot(v, c)
|
||||
if s > bs: bs, best = s, j
|
||||
if assign[i] != best: assign[i] = best; moved += 1
|
||||
if moved == 0: break
|
||||
groups = collections.defaultdict(list)
|
||||
for i, j in enumerate(assign): groups[j].append(i)
|
||||
for j, mem in groups.items():
|
||||
cent[j] = norm([sum(V[i][d] for i in mem) / len(mem) for d in range(dim)])
|
||||
groups = collections.defaultdict(list)
|
||||
for i, j in enumerate(assign): groups[j].append(i)
|
||||
return [sorted(g) for g in groups.values()]
|
||||
|
||||
def centroid(m):
|
||||
return norm([sum(V[i][d] for i in m) / len(m) for d in range(dim)])
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"S1": 0.6175176123715982,
|
||||
"PCT": 0.9,
|
||||
"MMIN": 0.5295953845017977,
|
||||
"W": 0.6
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
"""G3: S1 floor, percentile against the same-size null, mandatory member floor (MMIN), optional W.
|
||||
|
||||
Builds on gate2 (similarities, word test, k-means). On first import it samples the same-size null
|
||||
distribution of S1 and caches the sorted samples in null-samples.pkl.
|
||||
"""
|
||||
import bisect, pathlib, pickle, random
|
||||
import gate2 as g
|
||||
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
_path = HERE / 'null-samples.pkl'
|
||||
if _path.exists():
|
||||
NULLS = pickle.loads(_path.read_bytes())
|
||||
else:
|
||||
rnd, NULLS = random.Random(3033), {}
|
||||
for m in range(5, 151):
|
||||
need, samples = (1000 if m <= 60 else 200), []
|
||||
while len(samples) < need:
|
||||
members = rnd.sample(range(g.n), m)
|
||||
if g.eligible(members):
|
||||
samples.append(g.cohesion(members)[0])
|
||||
NULLS[m] = sorted(samples)
|
||||
_path.write_bytes(pickle.dumps(NULLS))
|
||||
|
||||
def percentile(s1, size):
|
||||
samples = NULLS[min(max(size, 5), 150)]
|
||||
return bisect.bisect_left(samples, s1) / len(samples)
|
||||
|
||||
def signals(m):
|
||||
sig = g.signals(m) # size, stories, dominant, S1, Z1, MMIN, W, W_word, S6_old
|
||||
sig['PCT'] = percentile(sig['S1'], sig['size'])
|
||||
return sig
|
||||
|
||||
def passes(sig, gate):
|
||||
if not (sig['size'] >= g.MIN_SIZE and sig['stories'] >= g.MIN_STORIES and sig['dominant'] <= g.MAX_DOMINANT):
|
||||
return False
|
||||
if sig['S1'] < gate['S1'] or sig['PCT'] < gate['PCT'] or sig['MMIN'] < gate['MMIN']:
|
||||
return False
|
||||
if gate.get('W') is not None and sig['W'] >= gate['W']:
|
||||
return False
|
||||
return True
|
||||
@@ -0,0 +1,138 @@
|
||||
"""A reading page for regrouped predicaments: does each group read as one predicament?
|
||||
|
||||
cd v2 && python3 ../groups_page.py predicament_embeddings.json predicaments_clean.json > groups-k60-s24.html
|
||||
|
||||
Clusters the embeddings at k = 60, seed 24 (the seed the first review used, kept apart from the
|
||||
measurement seeds 11-13), keeps the candidate groups (at least 5 scenes, 4 stories, no story over 40%),
|
||||
and lists them most cohesive first. Each scene shows its predicament sentence, the scene's original
|
||||
summary underneath, and its story. The reviewer marks each group: one predicament, a few mixed, or not
|
||||
a predicament, with an optional name and note. Marks are kept in the browser and saved as a JSON file.
|
||||
|
||||
Naming a kept group ("A ___ wants ___ from ___") is left for the full review page, once groups read well.
|
||||
No inference.
|
||||
"""
|
||||
import collections, html, json, pathlib, re, sys
|
||||
from topic_words import TOPIC_WORDS
|
||||
|
||||
emb_path, text_path = sys.argv[1], sys.argv[2]
|
||||
K, SEED = 60, 24
|
||||
sys.argv = ['measure.py', emb_path]
|
||||
exec(pathlib.Path(__file__).with_name('measure.py').read_text(encoding='utf-8').split("print(f'{path.name}")[0])
|
||||
# now defined: chunks, V, n, story, kmeans, describe, MIN_SIZE, MIN_STORIES, MAX_DOMINANT
|
||||
|
||||
pred = {x['chunk']: x['summary'] for x in json.loads(pathlib.Path(text_path).read_text(encoding='utf-8'))['summaries']}
|
||||
orig = {x['chunk']: x['summary'] for x in
|
||||
json.loads(pathlib.Path('summaries_clean.json').read_text(encoding='utf-8'))['summaries']}
|
||||
|
||||
|
||||
def cross_story_cohesion(group):
|
||||
tot = cnt = 0
|
||||
for a in group:
|
||||
for b in group:
|
||||
if a < b and story[a] != story[b]:
|
||||
tot += sum(x * y for x, y in zip(V[a], V[b])); cnt += 1
|
||||
return tot / cnt if cnt else 0.0
|
||||
|
||||
|
||||
groups = []
|
||||
for g in kmeans(K, SEED):
|
||||
size, nst, dom, _ = describe(g)
|
||||
if size < MIN_SIZE or nst < MIN_STORIES or dom > MAX_DOMINANT:
|
||||
continue
|
||||
topics = collections.Counter(w for i in g for w in set(re.findall(r"[a-zé]+", orig.get(i, '').lower())) & TOPIC_WORDS)
|
||||
word, hits = topics.most_common(1)[0] if topics else ('-', 0)
|
||||
groups.append({'members': g, 'stories': nst, 'cohesion': cross_story_cohesion(g), 'topic': (word, hits / size)})
|
||||
groups.sort(key=lambda x: -x['cohesion'])
|
||||
run = f'{pathlib.Path(emb_path).stem}-k{K}-s{SEED}'
|
||||
|
||||
sections = []
|
||||
for gi, grp in enumerate(groups, 1):
|
||||
rows = ''.join(
|
||||
f'<li><span class="p">{html.escape(pred.get(i, ""))}</span>'
|
||||
f'<span class="o">{html.escape(orig.get(i, ""))}</span>'
|
||||
f'<span class="t">{html.escape(chunks[i]["title"].title())}</span></li>'
|
||||
for i in grp['members'])
|
||||
word, share = grp['topic']
|
||||
sections.append(f'''<section id="g{gi}" data-g="{gi}">
|
||||
<h2>Group {gi}</h2>
|
||||
<p class="meta">{len(grp["members"])} scenes from {grp["stories"]} stories · cohesion {grp["cohesion"]:.3f} ·
|
||||
most common job, relationship or place word in the original summaries: <b>{html.escape(word)}</b>, {share:.0%} of scenes</p>
|
||||
<ol>{rows}</ol>
|
||||
<div class="verdict">
|
||||
<label><input type="radio" name="v{gi}" value="one"> One predicament</label>
|
||||
<label><input type="radio" name="v{gi}" value="mixed"> Two or three mixed together</label>
|
||||
<label><input type="radio" name="v{gi}" value="none"> Not a predicament</label>
|
||||
<input type="text" class="name" data-g="{gi}" placeholder="Short name, if it is one (optional)" maxlength="80">
|
||||
<input type="text" class="note" data-g="{gi}" placeholder="Note (optional)" maxlength="300">
|
||||
</div></section>''')
|
||||
|
||||
index = ''.join(f'<a href="#g{i}" data-g="{i}">{i}</a>' for i in range(1, len(groups) + 1))
|
||||
data = json.dumps({'run': run, 'groups': [{'group': i, 'chunks': grp['members']} for i, grp in enumerate(groups, 1)]})
|
||||
|
||||
print(f'''<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Predicament groups</title>
|
||||
<style>
|
||||
:root{{--bg:#f7f6f2;--card:#fff;--ink:#222;--dim:#666;--line:#ddd;--one:#dff0e3;--mixed:#fdf1d6;--none:#f6dede;--accent:#2f6f4f}}
|
||||
@media (prefers-color-scheme:dark){{:root{{--bg:#1b1b1d;--card:#252528;--ink:#e6e6e6;--dim:#9a9a9a;--line:#3a3a3e;--one:#233d2a;--mixed:#443a1c;--none:#472828;--accent:#7cc39c}}}}
|
||||
body{{background:var(--bg);color:var(--ink);font:15px/1.5 system-ui,sans-serif;margin:0;padding:24px 16px}}
|
||||
main{{max-width:900px;margin:auto}} h1{{margin:0 0 6px}} .dim,.meta{{color:var(--dim)}} .meta{{font-size:13px;margin:0 0 8px}}
|
||||
#bar{{position:sticky;top:0;background:var(--bg);border-bottom:1px solid var(--line);padding:8px 0;z-index:1}}
|
||||
#index{{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}}
|
||||
#index a{{min-width:26px;text-align:center;font-size:12px;border:1px solid var(--line);border-radius:4px;color:var(--ink);text-decoration:none;padding:1px 3px}}
|
||||
#index a.one{{background:var(--one)}} #index a.mixed{{background:var(--mixed)}} #index a.none{{background:var(--none)}}
|
||||
button{{font:inherit;padding:4px 12px;border-radius:6px;border:1px solid var(--accent);background:var(--accent);color:var(--bg);cursor:pointer}}
|
||||
section{{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:12px 16px;margin:16px 0}}
|
||||
section.one{{border-left:6px solid #3a9a63}} section.mixed{{border-left:6px solid #c9962b}} section.none{{border-left:6px solid #c45050}}
|
||||
h2{{margin:0 0 2px;font-size:18px}} ol{{padding-left:22px;margin:6px 0}} li{{margin:0 0 8px}}
|
||||
.p{{display:block}} .o{{display:block;font-size:13px;color:var(--dim)}} .t{{display:block;font-size:12px;color:var(--dim);font-style:italic}}
|
||||
.verdict{{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;border-top:1px solid var(--line);padding-top:8px}}
|
||||
.verdict input[type=text]{{flex:1 1 220px;min-width:0;font:inherit;padding:4px 6px;border:1px solid var(--line);border-radius:4px;background:var(--bg);color:var(--ink)}}
|
||||
</style>
|
||||
<main>
|
||||
<h1>Predicament groups</h1>
|
||||
<p class="dim">Every scene was re-described as its main person's predicament and the scenes were regrouped by
|
||||
those sentences. Each line below is one scene: its predicament, the scene's original summary under it, and
|
||||
its story. Read a group and mark whether most of its scenes are <b>one predicament</b> (the same kind of
|
||||
trouble, and wanting the same kind of way out), <b>two or three mixed together</b>, or <b>not a
|
||||
predicament</b> (held together by wording, a topic, or nothing). Most cohesive groups first; the first ten
|
||||
or so are enough to judge. Marks save in this browser as you go; "Save marks" downloads them for the program.</p>
|
||||
<div id="bar"><span id="tally"></span> <button id="save">Save marks</button><div id="index">{index}</div></div>
|
||||
{"".join(sections)}
|
||||
</main>
|
||||
<script>
|
||||
const DATA={data.replace("</", "<\\\\/")};
|
||||
const KEY='stp-groups:'+DATA.run;
|
||||
let marks={{}}; try{{marks=JSON.parse(localStorage.getItem(KEY)||'{{}}')}}catch(e){{}}
|
||||
function mark(g){{return marks[g]||(marks[g]={{verdict:'',name:'',note:''}})}}
|
||||
function paint(){{
|
||||
const c={{one:0,mixed:0,none:0}};
|
||||
for(const {{group:g}} of DATA.groups){{
|
||||
const m=marks[g]||{{}}, v=m.verdict||'';
|
||||
if(v) c[v]++;
|
||||
document.getElementById('g'+g).className=v;
|
||||
document.querySelector(`#index a[data-g="${{g}}"]`).className=v;
|
||||
}}
|
||||
const done=c.one+c.mixed+c.none;
|
||||
document.getElementById('tally').textContent=`Marked ${{done}} of ${{DATA.groups.length}}: one predicament ${{c.one}}, mixed ${{c.mixed}}, not ${{c.none}}`;
|
||||
}}
|
||||
function store(){{try{{localStorage.setItem(KEY,JSON.stringify(marks))}}catch(e){{}} paint()}}
|
||||
for(const [g,m] of Object.entries(marks)){{
|
||||
const r=document.querySelector(`input[name="v${{g}}"][value="${{m.verdict}}"]`); if(r) r.checked=true;
|
||||
const n=document.querySelector(`.name[data-g="${{g}}"]`); if(n) n.value=m.name||'';
|
||||
const t=document.querySelector(`.note[data-g="${{g}}"]`); if(t) t.value=m.note||'';
|
||||
}}
|
||||
document.addEventListener('change',e=>{{
|
||||
if(e.target.type==='radio'){{mark(e.target.name.slice(1)).verdict=e.target.value; store()}}
|
||||
}});
|
||||
document.addEventListener('input',e=>{{
|
||||
const g=e.target.dataset.g; if(!g) return;
|
||||
mark(g)[e.target.classList.contains('name')?'name':'note']=e.target.value; store();
|
||||
}});
|
||||
document.getElementById('save').onclick=()=>{{
|
||||
const out={{run:DATA.run,saved:new Date().toISOString(),groups:DATA.groups.map(x=>({{...x,...(marks[x.group]||{{verdict:'',name:'',note:''}})}}))}};
|
||||
const a=document.createElement('a');
|
||||
a.href=URL.createObjectURL(new Blob([JSON.stringify(out,null,1)],{{type:'application/json'}}));
|
||||
a.download=`groups-marks-${{DATA.run}}.json`; a.click(); URL.revokeObjectURL(a.href);
|
||||
}};
|
||||
paint();
|
||||
</script>''')
|
||||
@@ -0,0 +1,43 @@
|
||||
# Jacobs reference set: *The Lady of the Barge* (1902)
|
||||
|
||||
A hand-made comparison baseline for the story-to-pack pipeline, the Jacobs twin of the O. Henry
|
||||
reference in `../v2/claude/`. Every summary, predicament, group and option here was written by
|
||||
Claude reading the scenes. **No local inference was used.** Compare the local-model runs against it.
|
||||
Do not treat it as ground truth: it is one careful reading.
|
||||
|
||||
| File | What it is |
|
||||
| --- | --- |
|
||||
| `corpus/lady-of-the-barge.html` | Standard Ebooks single-page text (public domain) |
|
||||
| `split_se.py` | → `stories.json` (12 stories, split on `<h2>`) and `chunks.json` (157 scenes, same 320-word paragraph rule as `../chunk.py`) |
|
||||
| `dump.py` | `python3 dump.py A B` prints scenes A..B for reading |
|
||||
| `claude/batches/sNN.json` | per story: `{chunk, story, summary, predicament}`, the same schema as `../v2/claude/batches/` |
|
||||
| `claude/groups_src.py` | the groups, options and per-story protagonist standing |
|
||||
| `claude/build.py` | checks everything, then writes `claude/records.json` and `claude/groups.json` |
|
||||
| `claude/page.py` | → `claude/REFERENCE.html`, the readable page |
|
||||
|
||||
Rebuild: `python3 split_se.py && cd claude && python3 build.py && python3 page.py`.
|
||||
|
||||
## What is in it
|
||||
|
||||
- **157 predicaments**: one sentence each (median 19 words, max 24), written to the `redescribe.py`
|
||||
rules: no names, jobs, places or story-only objects. The `topic_words` leak check flags 23 of 157.
|
||||
All 23 are role words (rival, partner, guest, neighbour, suitor, newcomer), kept on purpose because
|
||||
the design's cast is roles.
|
||||
- **19 groups**, each a situation that recurs in **two or more stories**. Each scene sits in at most
|
||||
one group: 137 are assigned and 20 fit nothing that recurs.
|
||||
- **Options**: 3 to 5 per group, 79 in all. 61 come from what someone in a scene did (`scene:N`),
|
||||
10 are archetypes and 8 fill gaps. **8 scene options cite a scene outside their own group.** That
|
||||
is looser than DESIGN.md's "drawn from other scenes in the cluster", and `groups.json` marks each
|
||||
one `in_group: false`.
|
||||
- **Moves**: spine paths with a direction only (`+`, `-`, `risk`, `set`). There are no magnitudes,
|
||||
as the design requires. Skill labels proposed for this corpus: `nerve` and `cunning`.
|
||||
- **Stage** comes from protagonist standing, judged **per story, not per scene**. That is the
|
||||
coarsest judgement in the set, recorded in `STANDING` in `groups_src.py`.
|
||||
|
||||
## How this corpus differs from O. Henry
|
||||
|
||||
It is small, 12 stories against 97. It also mixes registers: comic waterfront and village yarns,
|
||||
plus four horror or crime stories (*The Monkey's Paw*, *The Well*, *In the Library*,
|
||||
*Captain Rogers*). So several groups straddle the two registers. G09 "Your scheme turns on you"
|
||||
holds both a comic fake-drowning and a murderer caught out. A tool that groups by register or
|
||||
mood, rather than by predicament, should split them.
|
||||
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,86 @@
|
||||
[
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"summary": "A skipper waiting up for his late-returning mate and brother-in-law has just lost a shouted argument with a neighbouring crew; the mate arrives and answers his rebuke with cheek.",
|
||||
"predicament": "Someone waiting up for a relative who comes in late wants to reproach him, and gets cheek back."
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"summary": "The mate tells the skipper he has invited his sweetheart on the voyage without asking; the skipper is appalled and asks what his wife will say.",
|
||||
"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,
|
||||
"summary": "The skipper tries to refuse the young woman passage but cannot bring himself to say so, and ends up eating supper with the couple.",
|
||||
"predicament": "A man who wants to turn an unwelcome guest away cannot find the nerve to say it."
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"summary": "The girl balks at her cramped quarters but stays; the skipper warns that his wife will blame him, and the mate says his sister can mind her own business.",
|
||||
"predicament": "A man who has let something happen under his roof fears his spouse will hold him responsible for it."
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"summary": "The skipper insists on chaperoning the girl at meals himself, leaving the mate to steer and eat cold leftovers while she laughs at the skipper's stories.",
|
||||
"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,
|
||||
"summary": "Jealous, the mate hints at the skipper's wife, is mocked by the pair, is rude to the girl, and is sent away.",
|
||||
"predicament": "A jealous suitor tries to put his rival in his place, and only makes himself look childish."
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"summary": "The girl snubs the mate as 'only the mate'; he turns the tables by threatening to let the skipper explain her presence to his wife alone.",
|
||||
"predicament": "A spurned man realises he holds something over the rival who humiliated him, and decides to use it."
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 0,
|
||||
"summary": "The mate refuses to back the skipper's story; the skipper hides the girl below as they arrive to find his wife waiting on the quay.",
|
||||
"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,
|
||||
"summary": "The mate pulls meaningful faces behind the skipper's back until the wife grows suspicious; the skipper invents a toothache and tries to steer her away.",
|
||||
"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,
|
||||
"summary": "The wife stamps her foot, the hidden girl appears, and the mate lets his brother-in-law take the blame while pretending to shield him.",
|
||||
"predicament": "A man caught with an innocent secret is disowned by the one person who could explain it."
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 0,
|
||||
"summary": "The wife abuses the girl, knocks her purse into the water, and orders her off; the mate, now remorseful, takes responsibility and defends her.",
|
||||
"predicament": "A man whose spite has got an innocent person abused must decide whether to own up and defend her."
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 0,
|
||||
"summary": "The mate dives repeatedly for the purse to win the girl back; she begs him to come out but refuses to make up.",
|
||||
"predicament": "A man tries to win back someone he wronged with a dangerous public show of devotion."
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 0,
|
||||
"summary": "The mate fakes drowning; the skipper rescues him, sees him wink while the girl kisses him, and the wife boxes his ears.",
|
||||
"predicament": "A man pretends to be in danger to win sympathy, and the one who saved him sees through it."
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 0,
|
||||
"summary": "The wife takes the girl home to breakfast, leaving both men soaked, while the skipper fends off the mate's apologies with a boathook.",
|
||||
"predicament": "Two men who quarrelled over a woman are both left out in the cold by the women they wronged."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{"chunk":14,"story":1,"summary":"An old man losing at chess to his son grumbles at the weather and their remote house while waiting for a guest, who arrives: an old friend back from long service abroad.","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,"summary":"Warmed by whisky, the guest tells tales of distant lands; pressed by his host, he reluctantly produces a dried monkey's paw he calls magic.","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,"summary":"The guest explains the paw grants three men three wishes each, that he has had his, and that the last owner wished for death; asked if he would wish again, he wavers.","predicament":"Someone offered a thing that grants wishes is warned by the man who used it that it brings sorrow."},
|
||||
{"chunk":17,"story":1,"summary":"The guest throws the paw on the fire; the host snatches it back and keeps it despite repeated warnings to wish sensibly or not at all.","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,"summary":"Egged on by his joking son, the old man wishes for two hundred pounds to clear the house; the paw twists in his hand, but no money appears.","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,"summary":"An uneasy night passes; in the morning the family laugh off their fears, though the father recalls that the wishes come true in ways that look like coincidence.","predicament":"A family that tempted fate laughs off their fears in daylight while one of them cannot shake them."},
|
||||
{"chunk":20,"story":1,"summary":"The son leaves for work; later the mother, half-hoping for money, sees a well-dressed stranger hesitate several times at the gate before coming in.","predicament":"A family waiting on a hoped-for windfall sees a stranger approach who might bring it, or bad news."},
|
||||
{"chunk":21,"story":1,"summary":"The stranger, from the son's employers, tells the parents their only son has been killed in the machinery.","predicament":"Parents receive news that their only child has been killed, from someone who cannot bring himself to say it."},
|
||||
{"chunk":22,"story":1,"summary":"The firm denies liability but offers compensation of exactly two hundred pounds; the father collapses. They bury their son and sink into silent grief.","predicament":"A man learns the money he wished for has come as compensation for his child's death."},
|
||||
{"chunk":23,"story":1,"summary":"A week later the grieving mother remembers the paw's other two wishes and demands that her husband wish their son alive; he calls it madness.","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,"summary":"The father tells her their son was mutilated beyond recognition, but she insists; terrified, he fetches the paw and makes the second wish.","predicament":"A man pressed by his grieving spouse must decide whether to do something he believes is wicked and dangerous."},
|
||||
{"chunk":25,"story":1,"summary":"Nothing seems to happen; then a stealthy knock comes at the door, and the mother cries that it is their son while the father holds her back.","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,"summary":"The mother breaks free and struggles with the bolt; the father finds the paw and makes the third wish, and the door opens on an empty road.","predicament":"A man must use his last chance to undo what his spouse most longs for, before she lets it in."}
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{"chunk":27,"story":2,"summary":"An old watchman recalls a shipmate, an aged miser worth six hundred pounds, who fell ill a month out on the voyage home.","predicament":"A man who has hoarded his savings all his life falls ill far from anywhere he could spend them."},
|
||||
{"chunk":28,"story":2,"summary":"The dying miser refuses to believe he is dying and leave his money; his shipmates circle hopefully, and one finds a money belt on him and guesses his plan to be buried with it.","predicament":"A dying miser, surrounded by people hoping to inherit, is determined that nobody will get his money."},
|
||||
{"chunk":29,"story":2,"summary":"The miser dies boasting that he had a boy throw his banknotes overboard; one sailor scares the boy into silence, and the narrator threatens to tell unless let in on the scheme.","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,"summary":"The schemer reveals he thinks the boy kept the money; the two agree to split it, and search the boy's bunk and pockets at night without success.","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,"summary":"A night search is foiled when a ticklish shipmate wakes screaming; a week of prodding the boy finds nothing, so they corner him and demand the money outright.","predicament":"A schemer whose secret searches keep failing decides to confront the person he suspects directly."},
|
||||
{"chunk":32,"story":2,"summary":"The boy insists he threw the money overboard; they search him, offer him a third, then threaten to have him searched ashore and jailed.","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,"summary":"When the sailor heads to tell the captain, the boy gives in: he kept the money but, frightened by the searches, hid it in an officer's mattress where he cannot now reach it.","predicament":"Someone forced to share a hidden windfall admits it is now stuck where neither partner can get at it."},
|
||||
{"chunk":34,"story":2,"summary":"Neither the man nor the boy can get aft unseen, the officer sleeps on the money every night, and the two partners blame each other.","predicament":"Distrustful partners stare at a fortune they cannot reach without getting caught, and blame each other for it."},
|
||||
{"chunk":35,"story":2,"summary":"The boy proposes the man fake an accident to get quartered aft; the man refuses and secretly flatters the officer, asking to buy his mattress as a keepsake.","predicament":"A man wants something a superior owns without revealing why, and tries flattery to get it."},
|
||||
{"chunk":36,"story":2,"summary":"The officer plays up the mattress's value and the sailor, afraid to seem eager, settles at two pounds, to be handed over ashore.","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,"summary":"The sailor now plans to cut the boy out and split with the narrator alone, but the suspicious boy shadows him all the way into port.","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,"summary":"The officer delays the hand-over with a long speech until the boy arrives, sees the sack, and refuses to let them leave without his share.","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,"summary":"The boy stays on deck while the men cut the mattress open below; it is empty, and when they come up the boy has vanished.","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,"summary":"The boy never returns, not even for his pay, and the officer smilingly asks the sailor how he slept.","predicament":"A schemer who was outwitted must stand and smile at the people who got the better of him."}
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
[
|
||||
{"chunk":41,"story":3,"summary":"A rich man soon to marry sits with his idle, indebted cousin, who needles him about his wealth and his fiancée's goodness.","predicament":"A man about to marry is needled by a dependent relative who hints at something to his discredit."},
|
||||
{"chunk":42,"story":3,"summary":"The cousin hints that the fiancée would drop him if she knew what he really was, then says he needs fifteen hundred pounds within a fortnight or faces prison.","predicament":"A man in desperate debt makes veiled hints to the relative he hopes will pay it."},
|
||||
{"chunk":43,"story":3,"summary":"Refused, the cousin offers to sell compromising old letters, threatening to sell them to a rival suitor; the rich man seizes him and is stopped only by a servant entering.","predicament":"A man is blackmailed by a relative with old letters that could cost him the person he loves."},
|
||||
{"chunk":44,"story":3,"summary":"The cousin walks out into the park to let him think it over; the rich man follows, and later tells his mother the cousin has gone for good after a quarrel over money.","predicament":"A man who has done something terrible in anger must cover for a relative's sudden disappearance."},
|
||||
{"chunk":45,"story":3,"summary":"The man walks with his fiancée to a disused well in the park, her favourite spot; he urges her not to sit on its edge.","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,"summary":"She asks about the missing cousin and defends him; the man bursts out that the cousin was a blackmailer, a liar and a cur.","predicament":"A man hiding a crime hears it discussed innocently and betrays his feelings with an outburst."},
|
||||
{"chunk":47,"story":3,"summary":"She is startled by a whisper she imagines saying 'Jem, help me out'; shaken, he tries to take her indoors, but she insists on staying.","predicament":"A guilty man tries to hurry someone away from the scene of his crime, and she insists on staying."},
|
||||
{"chunk":48,"story":3,"summary":"She drops her mother's diamond bracelet down the well and wants it drained; he looks at her in horror, then covers it by saying he feared she was falling.","predicament":"A man hiding something learns it may be discovered by an innocent accident, and must not show his fear."},
|
||||
{"chunk":49,"story":3,"summary":"He promises to recover the bracelet himself by morning and makes her swear to tell no one; his mother suggests filling the well in.","predicament":"A man must retrieve something from where his secret lies before anyone else goes looking."},
|
||||
{"chunk":50,"story":3,"summary":"At night he secretly fishes the well with a line and hook, and dredges up keys instead of the bracelet.","predicament":"A man secretly searching where his crime lies keeps pulling up reminders of it."},
|
||||
{"chunk":51,"story":3,"summary":"The hook snags; he gives up, spends a sleepless night, and at dawn meets his grooms, whom he has ordered to lower him into the well.","predicament":"A man who has failed to fix things quietly must now involve others while still keeping his secret."},
|
||||
{"chunk":52,"story":3,"summary":"His groom offers to go down instead, since the master looks ill; he refuses curtly and starts to test the air with a candle.","predicament":"A man hiding something is offered help he cannot accept without being found out."},
|
||||
{"chunk":53,"story":3,"summary":"The candle is lost; he stops the groom fetching another, strips, and sits a long time on the well's edge before going down.","predicament":"A man who must face the thing he did alone gathers his nerve while others watch and wonder."},
|
||||
{"chunk":54,"story":3,"summary":"Below, something drags at the rope; the grooms haul up the dead cousin's face with their master behind it, let go in horror, and both fall back into the water.","predicament":"A man who hid a crime is dragged down by it at the moment he tries to undo its consequences."}
|
||||
]
|
||||
@@ -0,0 +1,74 @@
|
||||
[
|
||||
{
|
||||
"chunk": 55,
|
||||
"story": 4,
|
||||
"summary": "A farmer's wife, her husband, and her retired-policeman uncle discuss a stolen brooch; the uncle has been hiding under their bed hoping to catch the thief, and the wife casts suspicion on a neighbour while accusing her husband of not caring.",
|
||||
"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,
|
||||
"summary": "The uncle declares everyone guilty until proven innocent; the wife recounts missing the brooch, accusing and sacking her servant, who cried on her box.",
|
||||
"predicament": "Someone who has lost something accuses the most defenceless person nearby, and punishes her before any proof."
|
||||
},
|
||||
{
|
||||
"chunk": 57,
|
||||
"story": 4,
|
||||
"summary": "The servant's box held nothing; the husband defends her and is snapped at, while the uncle, pencil and notebook out, pursues the investigation with great ceremony.",
|
||||
"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,
|
||||
"summary": "The husband eats a lozenge from the evidence box and cannot stop laughing; the uncle establishes that no one could have entered the bedroom unheard.",
|
||||
"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,
|
||||
"summary": "Suspicion turns to a widow who visited that morning; the uncle finds muddy footprints on the stairs, which are in fact the husband's, and he stays guiltily quiet.",
|
||||
"predicament": "A man who sees an innocent neighbour blamed for evidence he himself left must decide whether to own up."
|
||||
},
|
||||
{
|
||||
"chunk": 60,
|
||||
"story": 4,
|
||||
"summary": "The husband slyly deflects the footprint evidence, then exchanges silent signals with his wife until the uncle demands to know what is funny.",
|
||||
"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,
|
||||
"summary": "The husband reveals he thinks the widow is after the uncle, not the brooch; the uncle is outraged, then proposes inviting her to tea to trap her into confessing.",
|
||||
"predicament": "A self-important man, teased that a neighbour fancies him, resolves to prove his point by trapping her."
|
||||
},
|
||||
{
|
||||
"chunk": 62,
|
||||
"story": 4,
|
||||
"summary": "The husband refuses to have a guest trapped in his house; the uncle secretly tells his niece he will search the widow's cottage without leave.",
|
||||
"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,
|
||||
"summary": "The husband, who resents the uncle living with them for his wife's hopes of an inheritance, plans to let the widow catch the uncle searching her house, and shadows him.",
|
||||
"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,
|
||||
"summary": "The husband brings the widow home early; the uncle, trapped inside, hides in the larder, and the husband settles in for beer and begins praising him.",
|
||||
"predicament": "A man who has trapped his rival in an embarrassing hiding place decides to make the most of it."
|
||||
},
|
||||
{
|
||||
"chunk": 65,
|
||||
"story": 4,
|
||||
"summary": "Within the uncle's hearing, the husband tells the widow the uncle is shy and devoted to her, and has been asking whether she likes jewellery.",
|
||||
"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,
|
||||
"summary": "Asked for bread and cheese, the widow opens the larder, the uncle bursts out, and her friend takes his hiding there as a bashful suitor's courtship.",
|
||||
"predicament": "A man caught somewhere he should not be is handed an embarrassing explanation, and has no better one to offer."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{"chunk":67,"story":5,"summary":"Late at night one co-owner of a house and a business sits hating his partner, who has discovered his embezzlement and ordered him to leave both by morning.","predicament":"A man found out in wrongdoing is ordered by his partner to give up everything and leave by morning."},
|
||||
{"chunk":68,"story":5,"summary":"The honest partner refuses to cover the theft quietly, offers the exile two hundred pounds, and mentions the servants are away until morning.","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,"summary":"The exile demands the matter be hushed up, draws a sword from the wall, and stabs his partner when he refuses.","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,"summary":"Sickened, the killer turns out the lights, flees to the gate, and sees a constable coming up the road.","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,"summary":"He bids the constable good night, returns inside, and gathers his cash and valuables, reckoning he has eight hours' start.","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,"summary":"He plans his escape, weighing a crowded district against the seaside, and finds every choice may mean life or death.","predicament":"A fugitive planning his escape finds that every choice might be the one that gets him caught."},
|
||||
{"chunk":73,"story":5,"summary":"At one o'clock he hears creaks on the stairs, and fears the dead man is moving about the house.","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,"summary":"Finding the library door open, he locks someone inside, then realises it is a burglar he can blame for the killing.","predicament":"A guilty man realises a stranger's blunder gives him someone else to blame."},
|
||||
{"chunk":75,"story":5,"summary":"He fires through the door, raises the alarm, and the police break in and seize the terrified intruder.","predicament":"A man framing an innocent person must act the outraged victim in front of the law."},
|
||||
{"chunk":76,"story":5,"summary":"The intruder swears the body was there when he arrived; the sergeant uncovers it, and the killer feigns horror.","predicament":"A man must play the shocked innocent while the person he framed pleads the truth."},
|
||||
{"chunk":77,"story":5,"summary":"The killer names his friend and accuses the intruder, who accuses him back; the sergeant seems to side with the killer.","predicament":"Someone falsely accused points to the real culprit, who must hold his nerve and keep up his story."},
|
||||
{"chunk":78,"story":5,"summary":"The sergeant takes the pistol, handcuffs the killer without warning, and sends for a doctor, since the victim is not dead.","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,"summary":"The dying partner opens his eyes and speaks; the sergeant takes notes and leads the killer away.","predicament":"A man whose deceit has collapsed can only watch as the truth comes out and he is taken away."}
|
||||
]
|
||||
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"chunk": 80,
|
||||
"story": 6,
|
||||
"summary": "A ragged stranger swaggers into the best inn in a small town, bullies the waiter who tries to turn him out, and demands to see the landlord.",
|
||||
"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,
|
||||
"summary": "The stranger greets the landlord by another name, a wanted pirate's, and demands clothes, lodging and money in return for silence.",
|
||||
"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,
|
||||
"summary": "The landlord gives him money; the blackmailer draws a pistol, declares he will take half of everything from now on, and settles in.",
|
||||
"predicament": "A man pays off a blackmailer and learns the price will be half of everything, for good."
|
||||
},
|
||||
{
|
||||
"chunk": 83,
|
||||
"story": 6,
|
||||
"summary": "The landlord says he has lived honestly for fifteen years and warns the man not to wake the devil in him; the blackmailer meets the landlord's stepdaughter and says he will stay.",
|
||||
"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,
|
||||
"summary": "The blackmailer orders the best supper and room, and sleeps with his door barricaded and a pistol under his pillow.",
|
||||
"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,
|
||||
"summary": "He pushes in at the family breakfast table, eyes the stepdaughter, hints at the landlord's past before her, and goes out to buy new clothes.",
|
||||
"predicament": "A household must make polite space for someone who holds them hostage."
|
||||
},
|
||||
{
|
||||
"chunk": 86,
|
||||
"story": 6,
|
||||
"summary": "Within a month the blackmailer runs the inn; the landlord, failing in health, backs him in sacking a loyal old servant, and his daughter demands to know who is master.",
|
||||
"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,
|
||||
"summary": "The landlord sides with the blackmailer, who threatens to ruin the girl and have the landlord hanged; the landlord pleads for peace.",
|
||||
"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,
|
||||
"summary": "The inn decays under the blackmailer's rule; the landlord collapses with a fit, which upsets the blackmailer's plans.",
|
||||
"predicament": "A blackmailer's hold depends on his victim's life, and that victim suddenly collapses."
|
||||
},
|
||||
{
|
||||
"chunk": 89,
|
||||
"story": 6,
|
||||
"summary": "The landlord lies dying, anxious to please the blackmailer, while his daughter nurses him; the blackmailer resolves to get the hidden money before he dies.",
|
||||
"predicament": "A blackmailer whose victim is dying must grab what he can before his hold disappears."
|
||||
},
|
||||
{
|
||||
"chunk": 90,
|
||||
"story": 6,
|
||||
"summary": "The blackmailer threatens to inform; the dying man begs for time and promises to reveal his money that night if his daughter gets a share.",
|
||||
"predicament": "A dying man pressed by his tormentor bargains for time and for provision for someone he loves."
|
||||
},
|
||||
{
|
||||
"chunk": 91,
|
||||
"story": 6,
|
||||
"summary": "At night the blackmailer drugs the nurse and demands the money; the landlord extracts his promise about the daughter.",
|
||||
"predicament": "A man who thinks his helpless victim is at his mercy leans in close to hear the secret."
|
||||
},
|
||||
{
|
||||
"chunk": 92,
|
||||
"story": 6,
|
||||
"summary": "The 'dying' landlord seizes and strangles him, revealing he has been shamming weakness and planning to blame the killing on the blackmailer's own followers.",
|
||||
"predicament": "A man who pretended to be helpless strikes at the tormentor who threatened his family."
|
||||
},
|
||||
{
|
||||
"chunk": 93,
|
||||
"story": 6,
|
||||
"summary": "He stages the scene as a robbery and returns to bed as the drugged nurse unwittingly provides his alibi.",
|
||||
"predicament": "A man who has done a desperate thing arranges things so that suspicion falls elsewhere."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{"chunk":94,"story":7,"summary":"An old villager chats with a travelling painter repainting the inn sign, which had been mistaken for a portrait of a king.","predicament":"A craftsman told his work looks like something else quietly fixes it before anyone else notices."},
|
||||
{"chunk":95,"story":7,"summary":"The painter shares his beer, and the old man begins the tale of a travelling circus that stopped in the village.","predicament":"An old man looking for a free drink offers a story in exchange."},
|
||||
{"chunk":96,"story":7,"summary":"News comes that the circus tiger has escaped nearby; a tramp comes running to the inn shouting that the tiger is after him, and the drinkers rush indoors.","predicament":"People who laughed at a distant danger panic when it seems to have arrived on their doorstep."},
|
||||
{"chunk":97,"story":7,"summary":"The landlord bars the door against the tramp; a man knocks the landlord down to get out to protect the families, and the tramp gets in.","predicament":"Someone must choose between keeping himself safe behind a barred door and letting in a stranger in danger."},
|
||||
{"chunk":98,"story":7,"summary":"The tramp, revived with free brandy, tells a vivid tale of fleeing the tiger, and the men are afraid to go home.","predicament":"A stranger with a thrilling tale of danger is rewarded by a frightened crowd without anyone checking it."},
|
||||
{"chunk":99,"story":7,"summary":"The men sleep at the inn; the tramp is gone by morning; the village lives in fear, though the local rogue laughs at the whole thing.","predicament":"A community living in fear of an unseen threat ignores the one doubter, because he has a bad name."},
|
||||
{"chunk":100,"story":7,"summary":"A villager's hens are taken; he carries the evidence to the constable in the next village, who shoves him away as drunk.","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,"summary":"Pigs vanish night after night; the authorities do nothing, and neighbours squabble over whether to take animals indoors.","predicament":"A community suffering repeated losses cannot get help, and cannot agree how to protect itself."},
|
||||
{"chunk":102,"story":7,"summary":"Children claim sightings, and a villager staggers in describing how the tiger chased him up a tree.","predicament":"A man tells a dramatic story of narrow escape to an audience eager to believe it."},
|
||||
{"chunk":103,"story":7,"summary":"When one man questions the story, the rogue 'confirms' it, claiming he watched from another tree; the storyteller, caught out, goes home silent.","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,"summary":"The neighbouring town and the clergyman say there was no tiger; after more losses a former militiaman raises a band of seventeen to hunt it.","predicament":"People told their fears are nonsense by outsiders decide to take matters into their own hands."},
|
||||
{"chunk":105,"story":7,"summary":"The hunt searches the wood timidly and finds nothing; back at the inn, an old man reports the tiger has just gone into the rogue's cottage.","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,"summary":"The armed men crowd into the rogue's cottage, pushing him ahead of them, despite his protests.","predicament":"A man with something to hide has an armed crowd force its way into his home."},
|
||||
{"chunk":107,"story":7,"summary":"His kitchen is full of pork and poultry; accused of theft, he brazenly claims to have gone into the meat trade.","predicament":"A man caught with the evidence of his thefts brazens it out and dares his accusers to prove anything."},
|
||||
{"chunk":108,"story":7,"summary":"He turns their own tiger sightings against them: those who swore they saw it would have to repeat it on oath.","predicament":"A man accused of wrongdoing turns his accusers' own exaggerations into his defence."},
|
||||
{"chunk":109,"story":7,"summary":"He claims to have receipts, offers to sell them the meat, and gets away with it; he and the tramp turn out to have known each other.","predicament":"People who know they were swindled cannot prove it, and must watch the swindler profit."}
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
[
|
||||
{"chunk":110,"story":8,"summary":"A retired officer, dressed with great care, sets out on a hot afternoon and overtakes a friend who stares uneasily at his loud tie.","predicament":"A man nervously setting out to do something that matters to him is stopped by a friend with questions."},
|
||||
{"chunk":111,"story":8,"summary":"The officer confides he is going to propose to a widow; his friend, equally dressed up, says he is on the same errand, and the two race along the road.","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,"summary":"Followed by jeering boys, they stop and agree they cannot propose together, but neither will yield the day, each pleading an excuse.","predicament":"Two rivals agree someone must go first, and neither will be the one to wait."},
|
||||
{"chunk":113,"story":8,"summary":"Each claims precedence; the officer vows that if they go together he will match any proposal on the spot, and refuses to be put off.","predicament":"Two rivals for the same prize trade claims of priority, and each suspects the other of a trick."},
|
||||
{"chunk":114,"story":8,"summary":"The officer proposes they play one game of chess for the right to propose first; the friend agrees, on condition that if the winner is refused, the loser may not propose without his permission.","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,"summary":"The officer offers his rival a large brandy, which the rival sets aside; after a long game the rival checkmates him with a bishop the officer had forgotten.","predicament":"A man who tried to tip a contest in his favour loses it through his own oversight."},
|
||||
{"chunk":116,"story":8,"summary":"The winner tidies himself in the loser's house and goes off to propose; the loser waits up all night and next morning goes to hear the result.","predicament":"A man who lost the right to try first must wait while his rival takes his chance."},
|
||||
{"chunk":117,"story":8,"summary":"The rival was refused, but holds the officer to the condition and plans to keep proposing until the widow accepts him.","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,"summary":"The rival says he will warn the widow off the officer, suggests the officer court her companion instead, and the officer storms out and sulks for weeks.","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,"summary":"Meeting the widow by chance, the officer confesses the chess game to explain his absence; she is indignant, then laughs.","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,"summary":"She says she is angry with him for losing, and proposes a plan so that he can win at cards what he lost at chess, under the rival's eyes.","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,"summary":"Playing cards, the widow asks the officer if he would like her hand; he takes it and kisses it, she 'meant the cards', then lets him keep it, and the rival is left fuming.","predicament":"Someone arranges to accept the suitor she prefers in a way his rival cannot object to."}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{"chunk":122,"story":9,"summary":"A man wakes up thirsty in a cramped, reeking bunk, with a stranger's face swearing at him, and cannot tell where he is.","predicament":"A man wakes up somewhere strange with no idea how he got there."},
|
||||
{"chunk":123,"story":9,"summary":"He realises he is on a ship, dressed in rags; he finds his friend among the sleepers and learns the ship's name.","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,"summary":"Seasick and far from land, he goes aft and demands to be taken home; the officer calls him mad and orders him forward.","predicament":"A man who has always been obeyed tries to give orders somewhere nobody knows or believes who he is."},
|
||||
{"chunk":125,"story":9,"summary":"His friend tries to threaten legal consequences but is too sick to finish; the two bicker over which of them was drunk.","predicament":"Two people in trouble together try to put on a united front and end up blaming each other."},
|
||||
{"chunk":126,"story":9,"summary":"The captain says they signed on as cook and seaman, calls them drunk impostors, and threatens jail; they are put to work.","predicament":"Two people who claim to be respected professionals are treated as impostors and forced to do menial work."},
|
||||
{"chunk":127,"story":9,"summary":"The crew mocks their claims of being drugged and of sobriety; their airs make the officers dislike them all the more.","predicament":"People brought low insist on their old status, and are mocked for it by those around them."},
|
||||
{"chunk":128,"story":9,"summary":"The captain humiliates the cook; the cook obeys, tidies the cabin, and comes back with a secret smile and something in his pocket, telling his friend it won't be for long.","predicament":"A humiliated man pretends to submit while he quietly prepares his revenge."},
|
||||
{"chunk":129,"story":9,"summary":"The captain is struck with violent pains; the cook refuses to treat him, as he 'only shipped as cook', until he agrees to pay a professional fee.","predicament":"A man mocked for claiming skill is suddenly needed for it, and names his price."},
|
||||
{"chunk":130,"story":9,"summary":"The cook prescribes, collects his fee, scares the captain about making a will, and has the mate sent out for snuffling.","predicament":"A man who has gained the upper hand over those who humiliated him enjoys every moment of it."},
|
||||
{"chunk":131,"story":9,"summary":"The cook hints he dosed the food from the medicine chest; the mate falls ill too, and the cook demands to be treated as surgeon.","predicament":"A man who has secretly caused the trouble only he can cure makes the most of it."},
|
||||
{"chunk":132,"story":9,"summary":"The mate haggles over the fee; the second officer falls ill too; the sick captain scolds the sick mate for being ill at all.","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,"summary":"The cook treats and weakens all three officers; the captain and mate pay the other man to draw up their wills; they recover slowly as they reach port.","predicament":"People who scorned two outsiders now depend on them completely, and pay for the privilege."},
|
||||
{"chunk":134,"story":9,"summary":"Ashore, the cook tells the captain he ate something that disagreed with him, and leaves with a wave.","predicament":"A man who got even with his tormentors leaves them knowing they were had."}
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{"chunk":135,"story":10,"summary":"At a crowded family breakfast, a poor widow living with her sister's family gets a letter and reads it so slowly that the others nearly burst with impatience.","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,"summary":"The widow fears the letter is a hoax; her brother-in-law thinks so too, until her sister reads it and rejoices.","predicament":"Someone who has never had luck is told of a windfall and is afraid to believe it."},
|
||||
{"chunk":137,"story":10,"summary":"The widow has inherited two thousand pounds from a former employer; the brother-in-law grumbles that nobody ever left him anything, and spreads the news through the town.","predicament":"A man sees a relative come into money and resents that luck never comes his way."},
|
||||
{"chunk":138,"story":10,"summary":"Local men suddenly befriend the brother-in-law; his wife points out that two old dealers are courting the heiress for her money.","predicament":"A man flattered by new friends is told they are really after a relative's money."},
|
||||
{"chunk":139,"story":10,"summary":"The brother-in-law watches the two suitors flatter the widow and tries to undercut them by announcing her age, nearly giving away his wife's.","predicament":"A man trying to expose flattering suitors trips over his own family's sensibilities."},
|
||||
{"chunk":140,"story":10,"summary":"The suitors outbid each other with compliments; the wife's temper frays, and the visitors are shown out with the door shut hard.","predicament":"Two rivals courting the same person keep topping each other's compliments while the family grows fed up."},
|
||||
{"chunk":141,"story":10,"summary":"The family, wanting to keep the money, fail to warn the widow; the slow dealer, losing ground to his glib rival, hatches a plan and enlists a small-time dealer.","predicament":"A slow-talking man losing a contest to a glib rival looks for a trick instead of better words."},
|
||||
{"chunk":142,"story":10,"summary":"The dealer bribes an accomplice, who holds out for a bigger fee, to tell the rival in confidence that the widow has lost her fortune; the rival swallows it.","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,"summary":"With the rival gone, the dealer courts the widow alone, preaching the virtues of poverty, and loses seven games of draughts to her on purpose.","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,"summary":"He blows her kisses and fusses over a bumped head, invites himself back, and walks home sure the money is his.","predicament":"A schemer who thinks his deal is done walks away gloating too soon."},
|
||||
{"chunk":145,"story":10,"summary":"He spreads the rumour of her ruin through the town, then proposes publicly 'now that she is poor', to look noble.","predicament":"A man makes a public show of selfless devotion, sure that it will cost him nothing."},
|
||||
{"chunk":146,"story":10,"summary":"She accepts; the family, touched, apologise for misjudging him, then mention a letter that arrived.","predicament":"A schemer whose plan has worked is thanked for his goodness while the ground starts to shift under him."},
|
||||
{"chunk":147,"story":10,"summary":"The lawyers' letter says a later will was found and she has nothing; the dealer, now engaged to a poor woman, is left alone with her to court.","predicament":"A man who faked a disaster to win a prize learns the disaster is real, and he is bound to his promise."}
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
[
|
||||
{
|
||||
"chunk": 148,
|
||||
"story": 11,
|
||||
"summary": "In an inn, men trade ghost stories; a sceptical old sailor mocks sailors' visions, then admits one uncanny experience on land.",
|
||||
"predicament": "A sceptic among believers is pressed to admit whether he has ever seen anything he could not explain."
|
||||
},
|
||||
{
|
||||
"chunk": 149,
|
||||
"story": 11,
|
||||
"summary": "As a young man back from abroad, the narrator found his relatives away, took lodgings at a dull inn, and set out on a long country walk.",
|
||||
"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,
|
||||
"summary": "He loses his way in the marshes as fog and night come down, and at last finds a road and hears footsteps.",
|
||||
"predicament": "A traveller lost in the dark and the cold must find shelter before night closes in."
|
||||
},
|
||||
{
|
||||
"chunk": 151,
|
||||
"story": 11,
|
||||
"summary": "A countryman sets him on the road but warns him away from a lit house nearby, said to hide something monstrous; tired, he goes to it anyway.",
|
||||
"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,
|
||||
"summary": "A grim old woman and a huge old man answer; after a furtive exchange the man insists he stay for dinner, and he feels trapped.",
|
||||
"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,
|
||||
"summary": "The host says his son will dine with them in the dark because of his eyes; he puts out the candles, and a strange voice speaks.",
|
||||
"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,
|
||||
"summary": "The host leaves him alone with the unseen son, and the guest thinks he sees wild eyes shining at him across the table.",
|
||||
"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,
|
||||
"summary": "The screen falls, and firelight shows the son's ruined face; the father lights the candles and explains his son was disfigured in a fire.",
|
||||
"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,
|
||||
"summary": "The guest apologises warmly; they celebrate the son's birthday until midnight, and the father toasts the children his son saved.",
|
||||
"predicament": "A man who shrank from someone must make amends, and finds they deserve his respect."
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Assemble records.json and groups.json from batches/ and groups_src.py, and check them."""
|
||||
import collections, glob, json, pathlib
|
||||
from groups_src import G, STANDING
|
||||
here = pathlib.Path(__file__).parent
|
||||
chunks = json.loads((here.parent / 'chunks.json').read_text())
|
||||
stories = json.loads((here.parent / 'stories.json').read_text())
|
||||
recs = [r for f in sorted(glob.glob(str(here / 'batches/s*.json'))) for r in json.loads(open(f).read())]
|
||||
assert [r['chunk'] for r in recs] == list(range(len(chunks))), 'every scene, once, in order'
|
||||
assert all(chunks[r['chunk']]['story'] == r['story'] for r in recs)
|
||||
|
||||
seen = collections.Counter(m for g in G for m in g['members'])
|
||||
dup = [m for m, n in seen.items() if n > 1]
|
||||
assert not dup, f'scenes in two groups: {dup}'
|
||||
by = {r['chunk']: r for r in recs}
|
||||
out = []
|
||||
for g in G:
|
||||
assert 3 <= len(g['options']) <= 5, g['id']
|
||||
st = sorted({by[m]['story'] for m in g['members']})
|
||||
stand = collections.Counter(STANDING[by[m]['story']] for m in g['members'])
|
||||
out.append({
|
||||
'id': g['id'], 'name': g['name'], 'predicament': g['predicament'],
|
||||
'sentence': f"A {g['actor']} wants {g['wants']} from {g['counterpart']}.",
|
||||
'actor': g['actor'], 'wants': g['wants'], 'counterpart': g['counterpart'],
|
||||
'stories': st, 'story_titles': [stories[s]['title'] for s in st],
|
||||
'standing': dict(stand), 'stage': stand.most_common(1)[0][0],
|
||||
'members': [{'chunk': m, 'story': by[m]['story'], 'predicament': by[m]['predicament']} for m in g['members']],
|
||||
'options': [{'text': t, 'source': s, 'moves': mv,
|
||||
'in_group': (int(s.split(':')[1]) in g['members']) if s.startswith('scene:') else None}
|
||||
for t, s, mv in g['options']],
|
||||
})
|
||||
unassigned = [r['chunk'] for r in recs if r['chunk'] not in seen]
|
||||
(here / 'records.json').write_text(json.dumps(recs, indent=1, ensure_ascii=False))
|
||||
(here / 'groups.json').write_text(json.dumps({'groups': out, 'unassigned': unassigned}, indent=1, ensure_ascii=False))
|
||||
multi = sum(len(g['stories']) >= 2 for g in out)
|
||||
print(f'{len(recs)} scenes, {len(out)} groups ({multi} span 2+ stories), '
|
||||
f'{sum(seen.values())} assigned, {len(unassigned)} unassigned: {unassigned}')
|
||||
for g in out:
|
||||
print(f"{g['id']} {len(g['members']):2} scenes {len(g['stories'])} stories stage {g['stage']:6} {g['name']}")
|
||||
src = collections.Counter(o['source'].split(':')[0] for g in out for o in g['options'])
|
||||
print('option sources:', dict(src), '| scene options citing a scene outside their group:',
|
||||
sum(o['in_group'] is False for g in out for o in g['options']))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,192 @@
|
||||
"""Hand-built reference grouping of the Jacobs predicaments (Claude-written, no local inference).
|
||||
|
||||
Each scene is in at most one group; scenes that fit no recurring situation stay unassigned.
|
||||
Options follow DESIGN.md: 3-5 per group, each marked by where it came from --
|
||||
'archetype' a fixed, setting-free option
|
||||
'scene:N' what someone in scene N actually did (or refused to do)
|
||||
'filled' written to fill a gap the scenes leave
|
||||
`moves` names spine paths and a direction only (+ / - / risk). No magnitudes: the model never
|
||||
chooses a number. Skill labels proposed for this corpus: nerve, cunning.
|
||||
"""
|
||||
G = [
|
||||
dict(id='G01', name='Someone who knows your past names a price',
|
||||
predicament='Someone who knows what you did wants paying to keep quiet, and you have a life to lose.',
|
||||
actor='respectable person', wants='silence', counterpart='someone who knows their past',
|
||||
members=[43, 67, 68, 81, 82, 83, 87, 90],
|
||||
options=[
|
||||
('Pay what they ask.', 'scene:82', {'money':'-', 'standing':'+', 'relationships.blackmailer':'+'}),
|
||||
('Refuse, and dare them to tell.', 'scene:43', {'nerve':'+', 'standing':'risk'}),
|
||||
('Seem to give in, and wait for your moment.', 'scene:87', {'cunning':'+', 'relationships.family':'-'}),
|
||||
('Try to take the evidence off them by force.', 'scene:43', {'nerve':'+', 'flags.violent':'set', 'standing':'risk'}),
|
||||
('Tell the truth yourself before they can.', 'filled', {'standing':'-', 'relationships.blackmailer':'-'})]),
|
||||
dict(id='G02', name='Hiding something from someone close who keeps getting near it',
|
||||
predicament='You are hiding something, and someone you care for keeps innocently coming close to it.',
|
||||
actor='person with a secret', wants='to keep it hidden', counterpart='someone close to them',
|
||||
members=[7, 8, 45, 46, 47, 48, 49, 50, 51, 52],
|
||||
options=[
|
||||
('Change the subject and steer them away.', 'scene:47', {'cunning':'+', 'relationships.close':'-'}),
|
||||
('Keep your face still and say nothing.', 'archetype', {'nerve':'+'}),
|
||||
('Promise to deal with it yourself, alone.', 'scene:49', {'nerve':'+', 'flags.secret_kept':'set'}),
|
||||
('Tell them the truth.', 'filled', {'relationships.close':'risk', 'standing':'-'})]),
|
||||
dict(id='G03', name='Someone innocent is taking the blame for what you did',
|
||||
predicament='Suspicion has fallen on someone else for what you did, and you could let it stay there.',
|
||||
actor='person at fault', wants='to escape blame', counterpart='an innocent suspect',
|
||||
members=[10, 44, 59, 74, 75, 76, 77, 93],
|
||||
options=[
|
||||
('Own up.', 'scene:10', {'standing':'-', 'relationships.suspect':'+'}),
|
||||
('Say nothing and let it pass.', 'archetype', {'relationships.suspect':'-', 'flags.guilty_secret':'set'}),
|
||||
('Quietly steer suspicion away from them without confessing.', 'scene:60', {'cunning':'+'}),
|
||||
('Point at them outright.', 'scene:75', {'standing':'+', 'relationships.suspect':'-', 'flags.guilty_secret':'set'})]),
|
||||
dict(id='G04', name='A rival is after the same person',
|
||||
predicament='Someone else wants the person you want, and only one of you can have them.',
|
||||
actor='suitor', wants='the person they are courting', counterpart='a rival',
|
||||
members=[4, 5, 111, 112, 113, 116, 117, 118, 140, 141, 142],
|
||||
options=[
|
||||
('Race them to it.', 'scene:111', {'nerve':'+', 'relationships.rival':'-'}),
|
||||
('Settle it between you by a game or a bargain.', 'scene:114', {'relationships.rival':'+', 'standing':'risk'}),
|
||||
('Put the rival in a bad light.', 'scene:142', {'cunning':'+', 'relationships.rival':'-', 'money':'-'}),
|
||||
('Outdo their compliments.', 'scene:140', {'relationships.courted':'+'}),
|
||||
('Step aside.', 'archetype', {'relationships.rival':'+', 'relationships.courted':'-'})]),
|
||||
dict(id='G05', name='A deal you could tip in your favour',
|
||||
predicament='You want something from someone and could quietly tip the deal your way.',
|
||||
actor='bargainer', wants='the better end of a deal', counterpart='the other party',
|
||||
members=[35, 36, 114, 115, 143],
|
||||
options=[
|
||||
('Deal straight.', 'archetype', {'standing':'+'}),
|
||||
('Hide why you want it and haggle.', 'scene:36', {'cunning':'+', 'money':'-'}),
|
||||
('Butter them up first: let them win, fill their glass.', 'scene:143', {'cunning':'+', 'relationships.other':'+'}),
|
||||
('Slip in a condition they will not notice.', 'scene:114', {'cunning':'+', 'relationships.other':'risk'})]),
|
||||
dict(id='G06', name='Partners who do not trust each other over a shared prize',
|
||||
predicament='You and a partner are after the same prize, and neither of you trusts the other.',
|
||||
actor='partner', wants='their share, or all of it', counterpart='their partner',
|
||||
members=[30, 33, 34, 37, 38, 125],
|
||||
options=[
|
||||
('Split it fairly and keep your word.', 'archetype', {'relationships.partner':'+', 'money':'+'}),
|
||||
('Cut your partner out.', 'scene:37', {'cunning':'+', 'money':'risk', 'relationships.partner':'-'}),
|
||||
('Keep your partner where you can see them.', 'scene:38', {'nerve':'+'}),
|
||||
('Threaten to tell unless they cut you in.', 'scene:29', {'money':'+', 'relationships.partner':'-'})]),
|
||||
dict(id='G07', name='People are after your money',
|
||||
predicament='You have money, or people think you do, and everyone around you suddenly wants a piece.',
|
||||
actor='person with money', wants='to keep it', counterpart='people courting it',
|
||||
members=[27, 28, 32, 138, 139],
|
||||
options=[
|
||||
('Enjoy the attention while it lasts.', 'scene:138', {'relationships.flatterer':'+', 'money':'risk'}),
|
||||
('Tell them plainly what you think they are after.', 'filled', {'nerve':'+', 'relationships.flatterer':'-'}),
|
||||
('Make sure nobody gets it.', 'scene:28', {'money':'+', 'standing':'-'}),
|
||||
('Deny you have anything.', 'scene:32', {'cunning':'+'})]),
|
||||
dict(id='G08', name='A windfall with a catch',
|
||||
predicament='Something could change your fortunes overnight, and you are not sure what it will cost.',
|
||||
actor='person of small means', wants='a change of fortune', counterpart='whoever offers it',
|
||||
members=[16, 17, 18, 20, 22, 135, 136, 137],
|
||||
options=[
|
||||
('Try it and see.', 'scene:18', {'money':'+', 'flags.tempted_fate':'set'}),
|
||||
('Throw it away.', 'scene:17', {'nerve':'+'}),
|
||||
('Ask someone who knows before you decide.', 'scene:16', {'relationships.adviser':'+'}),
|
||||
('Keep it quiet until you are sure.', 'filled', {'cunning':'+'})]),
|
||||
dict(id='G09', name='Your scheme turns on you',
|
||||
predicament='A plan you were sure of is coming apart, and the people you meant to fool are watching.',
|
||||
actor='schemer', wants='to come out ahead', counterpart='the people they tried to fool',
|
||||
members=[12, 39, 40, 54, 78, 79, 88, 91, 144, 145, 146, 147],
|
||||
options=[
|
||||
('Keep smiling and take your loss.', 'scene:40', {'standing':'-', 'nerve':'+'}),
|
||||
('Brazen it out.', 'scene:107', {'nerve':'+', 'standing':'risk'}),
|
||||
('Double down on the plan.', 'scene:145', {'cunning':'+', 'money':'risk'}),
|
||||
('Own up and ask for mercy.', 'filled', {'standing':'-', 'relationships.fooled':'+'})]),
|
||||
dict(id='G10', name='Treated with contempt by those over you',
|
||||
predicament='People with power over you treat you with contempt, and you want to turn the tables.',
|
||||
actor='person brought low', wants='respect, or revenge', counterpart='those in charge',
|
||||
members=[64, 92, 124, 126, 127, 128, 129, 130, 131, 133, 134],
|
||||
options=[
|
||||
('Swallow it and do as you are told.', 'scene:128', {'relationships.authority':'+', 'standing':'-'}),
|
||||
('Insist on who you really are.', 'scene:124', {'standing':'risk', 'relationships.authority':'-'}),
|
||||
('Wait until they need you, then name your price.', 'scene:129', {'money':'+', 'cunning':'+'}),
|
||||
('Get even quietly.', 'scene:131', {'cunning':'+', 'relationships.authority':'-', 'flags.revenge':'set'})]),
|
||||
dict(id='G11', name='Someone unwelcome has settled into your home',
|
||||
predicament='Someone you do not want has settled into your home, and you cannot seem to make them go.',
|
||||
actor='householder', wants='them gone', counterpart='an unwelcome guest',
|
||||
members=[2, 63, 85, 86],
|
||||
options=[
|
||||
('Tell them to go.', 'filled', {'nerve':'+', 'relationships.guest':'-'}),
|
||||
('Put up with it.', 'scene:85', {'relationships.household':'-'}),
|
||||
('Arrange for them to disgrace themselves.', 'scene:63', {'cunning':'+', 'relationships.guest':'-'}),
|
||||
('Stand up for the people they are hurting.', 'scene:86', {'relationships.household':'+', 'nerve':'+'})]),
|
||||
dict(id='G12', name='Something may be waiting in the dark',
|
||||
predicament='Something frightening may be close by in the dark, and you must decide whether to face it.',
|
||||
actor='frightened person', wants='to be safe', counterpart='whatever is out there',
|
||||
members=[19, 25, 26, 73, 96, 97, 99, 151, 152, 153, 154, 155],
|
||||
options=[
|
||||
('Open the door and look.', 'scene:155', {'nerve':'+', 'flags.faced_fear':'set'}),
|
||||
('Bar the door and stay put.', 'scene:97', {'nerve':'-'}),
|
||||
('Go on through the dark anyway.', 'scene:151', {'nerve':'+', 'standing':'risk'}),
|
||||
('Laugh it off in front of the others.', 'scene:19', {'standing':'+'})]),
|
||||
dict(id='G13', name='Pressed to do something you believe is wrong',
|
||||
predicament='Someone is pressing you to do something you believe is wrong or dangerous.',
|
||||
actor='reluctant person', wants='to do right without losing them', counterpart='the one pressing',
|
||||
members=[23, 24, 29, 62, 69],
|
||||
options=[
|
||||
('Refuse flatly.', 'scene:62', {'nerve':'+', 'relationships.presser':'-'}),
|
||||
('Do it, to keep the peace.', 'scene:24', {'relationships.presser':'+', 'flags.did_wrong':'set'}),
|
||||
('Go along, for a share.', 'scene:29', {'money':'+', 'flags.did_wrong':'set'}),
|
||||
('Agree, then quietly undo it.', 'scene:26', {'cunning':'+', 'relationships.presser':'risk'})]),
|
||||
dict(id='G14', name='A story nobody can check',
|
||||
predicament='You have a story to tell that nobody can check, and people are ready to believe it.',
|
||||
actor='storyteller', wants='to be believed', counterpart='a listening crowd',
|
||||
members=[95, 98, 102, 103, 148],
|
||||
options=[
|
||||
('Tell it big.', 'scene:102', {'standing':'+', 'flags.tall_tale':'set'}),
|
||||
('Tell it straight.', 'archetype', {'standing':'+'}),
|
||||
('Get someone to vouch for you.', 'scene:103', {'relationships.voucher':'risk'}),
|
||||
('Say you have seen nothing worth telling.', 'scene:148', {'nerve':'+'})]),
|
||||
dict(id='G15', name='Caught where you should not be',
|
||||
predicament='You have been caught where you should not be, and need an explanation right now.',
|
||||
actor='person caught out', wants='to get away with it', counterpart='whoever caught them',
|
||||
members=[9, 14, 58, 60, 65, 66, 106, 107, 108, 119],
|
||||
options=[
|
||||
('Brazen it out.', 'scene:107', {'nerve':'+', 'standing':'risk'}),
|
||||
('Take the embarrassing explanation you are offered.', 'scene:66', {'standing':'-', 'relationships.catcher':'+'}),
|
||||
('Tell the plain truth.', 'scene:119', {'standing':'-', 'relationships.catcher':'+'}),
|
||||
('Say nothing and slip away.', 'archetype', {'relationships.catcher':'-'})]),
|
||||
dict(id='G16', name='Blamed at home',
|
||||
predicament='Someone at home holds you responsible for trouble you think is not your fault.',
|
||||
actor='person being blamed', wants='peace at home', counterpart='the one blaming them',
|
||||
members=[0, 1, 3, 55, 57],
|
||||
options=[
|
||||
('Put it on someone else.', 'scene:9', {'relationships.spouse':'+', 'relationships.other':'-'}),
|
||||
('Keep your head down until it blows over.', 'archetype', {'relationships.spouse':'-'}),
|
||||
('Speak up for yourself.', 'scene:57', {'nerve':'+', 'relationships.spouse':'risk'}),
|
||||
('Smooth it over with a peace offering.', 'filled', {'money':'-', 'relationships.spouse':'+'})]),
|
||||
dict(id='G17', name='You are sure who did it and cannot prove it',
|
||||
predicament='You are sure who wronged you, and you cannot prove it.',
|
||||
actor='wronged person', wants='what was taken, or justice', counterpart='the suspect',
|
||||
members=[31, 56, 61, 100, 109],
|
||||
options=[
|
||||
('Search them.', 'scene:31', {'relationships.suspect':'-', 'standing':'risk'}),
|
||||
('Accuse them openly.', 'scene:56', {'nerve':'+', 'relationships.suspect':'-'}),
|
||||
('Set a trap.', 'scene:61', {'cunning':'+'}),
|
||||
('Take it to the authorities.', 'scene:100', {'relationships.authority':'risk'}),
|
||||
('Let it drop.', 'archetype', {'money':'-', 'standing':'-'})]),
|
||||
dict(id='G18', name='Lost, far from anything you know',
|
||||
predicament='You find yourself lost, far from anyone you know, and must get your bearings.',
|
||||
actor='stranded person', wants='to get back', counterpart='whoever is nearby',
|
||||
members=[122, 123, 149, 150],
|
||||
options=[
|
||||
('Ask the first person you meet for help.', 'scene:151', {'relationships.stranger':'+'}),
|
||||
('Find your companion and make a plan together.', 'scene:123', {'relationships.companion':'+'}),
|
||||
('Push on alone.', 'scene:150', {'nerve':'+', 'standing':'risk'}),
|
||||
('Demand to be taken back at once.', 'scene:124', {'standing':'risk', 'relationships.authority':'-'})]),
|
||||
dict(id='G19', name='Making amends',
|
||||
predicament='You have wronged someone, and want them to forgive you.',
|
||||
actor='person in the wrong', wants='forgiveness', counterpart='the person they wronged',
|
||||
members=[11, 13, 156],
|
||||
options=[
|
||||
('Apologise plainly.', 'scene:156', {'relationships.wronged':'+'}),
|
||||
('Make a grand gesture.', 'scene:11', {'nerve':'+', 'relationships.wronged':'risk'}),
|
||||
('Pay for the damage.', 'filled', {'money':'-', 'relationships.wronged':'+'}),
|
||||
('Leave it and hope it is forgotten.', 'archetype', {'relationships.wronged':'-'})]),
|
||||
]
|
||||
|
||||
# Protagonist standing by story, for stage assignment (DESIGN.md "Stages"). Per story, not per
|
||||
# scene: a coarse first cut, and the one place this grouping uses a judgement not in the text.
|
||||
STANDING = {0:'middle', 1:'middle', 2:'low', 3:'high', 4:'middle', 5:'high', 6:'middle',
|
||||
7:'low', 8:'high', 9:'low', 10:'low', 11:'middle'}
|
||||
# Story 9's professionals are treated as crew for the whole voyage, so they rank low.
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Render groups.json + records.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())
|
||||
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 scene {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]
|
||||
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"])}</p></details>')
|
||||
out = []
|
||||
for g in d['groups']:
|
||||
out.append(f'''<section><h2>{g["id"]} · {e(g["name"])}</h2>
|
||||
<p class="pred">{e(g["predicament"])}</p>
|
||||
<p class="meta">{e(g["sentence"])} · {len(g["members"])} scenes from {len(g["stories"])} stories
|
||||
({e(", ".join(g["story_titles"]))}) · 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>Scenes</h3>{"".join(scene(m["chunk"]) for m in g["members"])}</section>''')
|
||||
un = ''.join(scene(c) for c in d['unassigned'])
|
||||
page = f'''<!doctype html><html lang="en"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Jacobs Reference Groups</title>
|
||||
<style>
|
||||
:root{{--bg:#fbfaf7;--fg:#1d1d1b;--mute:#6b6760;--line:#e2ded6;--acc:#2f5d8a;--warn:#a4481c}}
|
||||
@media (prefers-color-scheme:dark){{:root{{--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)}}
|
||||
code{{font:12px ui-monospace,monospace;border:1px solid var(--line);border-radius:3px;padding:0 .3em;margin-right:.2em}}
|
||||
li{{margin:.35rem 0}} details{{margin:.2rem 0}} summary{{cursor:pointer}} .sum{{margin:.2rem 0 .5rem 1.2rem}}
|
||||
</style></head><body>
|
||||
<h1>The Lady of the Barge: reference groups</h1>
|
||||
<p class="meta">W. W. Jacobs, 1902 (Standard Ebooks text). 12 stories, 157 scenes cut by the same rule as <code>chunk.py</code>.
|
||||
Summaries, predicaments, groups and options written by Claude by reading every scene: no local inference.
|
||||
A comparison baseline, not a pack. Option moves give a direction only, never a number.</p>
|
||||
{"".join(out)}
|
||||
<section><h2>Unassigned ({len(d["unassigned"])})</h2><p class="meta">Scenes that fit no situation recurring in two or more stories.</p>{un}</section>
|
||||
</body></html>'''
|
||||
(here / 'REFERENCE.html').write_text(page, encoding='utf-8')
|
||||
print('wrote', here / 'REFERENCE.html', len(page), 'bytes')
|
||||
@@ -0,0 +1,944 @@
|
||||
[
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"summary": "A skipper waiting up for his late-returning mate and brother-in-law has just lost a shouted argument with a neighbouring crew; the mate arrives and answers his rebuke with cheek.",
|
||||
"predicament": "Someone waiting up for a relative who comes in late wants to reproach him, and gets cheek back."
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"summary": "The mate tells the skipper he has invited his sweetheart on the voyage without asking; the skipper is appalled and asks what his wife will say.",
|
||||
"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,
|
||||
"summary": "The skipper tries to refuse the young woman passage but cannot bring himself to say so, and ends up eating supper with the couple.",
|
||||
"predicament": "A man who wants to turn an unwelcome guest away cannot find the nerve to say it."
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"summary": "The girl balks at her cramped quarters but stays; the skipper warns that his wife will blame him, and the mate says his sister can mind her own business.",
|
||||
"predicament": "A man who has let something happen under his roof fears his spouse will hold him responsible for it."
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"summary": "The skipper insists on chaperoning the girl at meals himself, leaving the mate to steer and eat cold leftovers while she laughs at the skipper's stories.",
|
||||
"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,
|
||||
"summary": "Jealous, the mate hints at the skipper's wife, is mocked by the pair, is rude to the girl, and is sent away.",
|
||||
"predicament": "A jealous suitor tries to put his rival in his place, and only makes himself look childish."
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"summary": "The girl snubs the mate as 'only the mate'; he turns the tables by threatening to let the skipper explain her presence to his wife alone.",
|
||||
"predicament": "A spurned man realises he holds something over the rival who humiliated him, and decides to use it."
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 0,
|
||||
"summary": "The mate refuses to back the skipper's story; the skipper hides the girl below as they arrive to find his wife waiting on the quay.",
|
||||
"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,
|
||||
"summary": "The mate pulls meaningful faces behind the skipper's back until the wife grows suspicious; the skipper invents a toothache and tries to steer her away.",
|
||||
"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,
|
||||
"summary": "The wife stamps her foot, the hidden girl appears, and the mate lets his brother-in-law take the blame while pretending to shield him.",
|
||||
"predicament": "A man caught with an innocent secret is disowned by the one person who could explain it."
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 0,
|
||||
"summary": "The wife abuses the girl, knocks her purse into the water, and orders her off; the mate, now remorseful, takes responsibility and defends her.",
|
||||
"predicament": "A man whose spite has got an innocent person abused must decide whether to own up and defend her."
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 0,
|
||||
"summary": "The mate dives repeatedly for the purse to win the girl back; she begs him to come out but refuses to make up.",
|
||||
"predicament": "A man tries to win back someone he wronged with a dangerous public show of devotion."
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 0,
|
||||
"summary": "The mate fakes drowning; the skipper rescues him, sees him wink while the girl kisses him, and the wife boxes his ears.",
|
||||
"predicament": "A man pretends to be in danger to win sympathy, and the one who saved him sees through it."
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 0,
|
||||
"summary": "The wife takes the girl home to breakfast, leaving both men soaked, while the skipper fends off the mate's apologies with a boathook.",
|
||||
"predicament": "Two men who quarrelled over a woman are both left out in the cold by the women they wronged."
|
||||
},
|
||||
{
|
||||
"chunk": 14,
|
||||
"story": 1,
|
||||
"summary": "An old man losing at chess to his son grumbles at the weather and their remote house while waiting for a guest, who arrives: an old friend back from long service abroad.",
|
||||
"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,
|
||||
"summary": "Warmed by whisky, the guest tells tales of distant lands; pressed by his host, he reluctantly produces a dried monkey's paw he calls magic.",
|
||||
"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,
|
||||
"summary": "The guest explains the paw grants three men three wishes each, that he has had his, and that the last owner wished for death; asked if he would wish again, he wavers.",
|
||||
"predicament": "Someone offered a thing that grants wishes is warned by the man who used it that it brings sorrow."
|
||||
},
|
||||
{
|
||||
"chunk": 17,
|
||||
"story": 1,
|
||||
"summary": "The guest throws the paw on the fire; the host snatches it back and keeps it despite repeated warnings to wish sensibly or not at all.",
|
||||
"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,
|
||||
"summary": "Egged on by his joking son, the old man wishes for two hundred pounds to clear the house; the paw twists in his hand, but no money appears.",
|
||||
"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,
|
||||
"summary": "An uneasy night passes; in the morning the family laugh off their fears, though the father recalls that the wishes come true in ways that look like coincidence.",
|
||||
"predicament": "A family that tempted fate laughs off their fears in daylight while one of them cannot shake them."
|
||||
},
|
||||
{
|
||||
"chunk": 20,
|
||||
"story": 1,
|
||||
"summary": "The son leaves for work; later the mother, half-hoping for money, sees a well-dressed stranger hesitate several times at the gate before coming in.",
|
||||
"predicament": "A family waiting on a hoped-for windfall sees a stranger approach who might bring it, or bad news."
|
||||
},
|
||||
{
|
||||
"chunk": 21,
|
||||
"story": 1,
|
||||
"summary": "The stranger, from the son's employers, tells the parents their only son has been killed in the machinery.",
|
||||
"predicament": "Parents receive news that their only child has been killed, from someone who cannot bring himself to say it."
|
||||
},
|
||||
{
|
||||
"chunk": 22,
|
||||
"story": 1,
|
||||
"summary": "The firm denies liability but offers compensation of exactly two hundred pounds; the father collapses. They bury their son and sink into silent grief.",
|
||||
"predicament": "A man learns the money he wished for has come as compensation for his child's death."
|
||||
},
|
||||
{
|
||||
"chunk": 23,
|
||||
"story": 1,
|
||||
"summary": "A week later the grieving mother remembers the paw's other two wishes and demands that her husband wish their son alive; he calls it madness.",
|
||||
"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,
|
||||
"summary": "The father tells her their son was mutilated beyond recognition, but she insists; terrified, he fetches the paw and makes the second wish.",
|
||||
"predicament": "A man pressed by his grieving spouse must decide whether to do something he believes is wicked and dangerous."
|
||||
},
|
||||
{
|
||||
"chunk": 25,
|
||||
"story": 1,
|
||||
"summary": "Nothing seems to happen; then a stealthy knock comes at the door, and the mother cries that it is their son while the father holds her back.",
|
||||
"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,
|
||||
"summary": "The mother breaks free and struggles with the bolt; the father finds the paw and makes the third wish, and the door opens on an empty road.",
|
||||
"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,
|
||||
"summary": "An old watchman recalls a shipmate, an aged miser worth six hundred pounds, who fell ill a month out on the voyage home.",
|
||||
"predicament": "A man who has hoarded his savings all his life falls ill far from anywhere he could spend them."
|
||||
},
|
||||
{
|
||||
"chunk": 28,
|
||||
"story": 2,
|
||||
"summary": "The dying miser refuses to believe he is dying and leave his money; his shipmates circle hopefully, and one finds a money belt on him and guesses his plan to be buried with it.",
|
||||
"predicament": "A dying miser, surrounded by people hoping to inherit, is determined that nobody will get his money."
|
||||
},
|
||||
{
|
||||
"chunk": 29,
|
||||
"story": 2,
|
||||
"summary": "The miser dies boasting that he had a boy throw his banknotes overboard; one sailor scares the boy into silence, and the narrator threatens to tell unless let in on the scheme.",
|
||||
"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,
|
||||
"summary": "The schemer reveals he thinks the boy kept the money; the two agree to split it, and search the boy's bunk and pockets at night without success.",
|
||||
"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,
|
||||
"summary": "A night search is foiled when a ticklish shipmate wakes screaming; a week of prodding the boy finds nothing, so they corner him and demand the money outright.",
|
||||
"predicament": "A schemer whose secret searches keep failing decides to confront the person he suspects directly."
|
||||
},
|
||||
{
|
||||
"chunk": 32,
|
||||
"story": 2,
|
||||
"summary": "The boy insists he threw the money overboard; they search him, offer him a third, then threaten to have him searched ashore and jailed.",
|
||||
"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,
|
||||
"summary": "When the sailor heads to tell the captain, the boy gives in: he kept the money but, frightened by the searches, hid it in an officer's mattress where he cannot now reach it.",
|
||||
"predicament": "Someone forced to share a hidden windfall admits it is now stuck where neither partner can get at it."
|
||||
},
|
||||
{
|
||||
"chunk": 34,
|
||||
"story": 2,
|
||||
"summary": "Neither the man nor the boy can get aft unseen, the officer sleeps on the money every night, and the two partners blame each other.",
|
||||
"predicament": "Distrustful partners stare at a fortune they cannot reach without getting caught, and blame each other for it."
|
||||
},
|
||||
{
|
||||
"chunk": 35,
|
||||
"story": 2,
|
||||
"summary": "The boy proposes the man fake an accident to get quartered aft; the man refuses and secretly flatters the officer, asking to buy his mattress as a keepsake.",
|
||||
"predicament": "A man wants something a superior owns without revealing why, and tries flattery to get it."
|
||||
},
|
||||
{
|
||||
"chunk": 36,
|
||||
"story": 2,
|
||||
"summary": "The officer plays up the mattress's value and the sailor, afraid to seem eager, settles at two pounds, to be handed over ashore.",
|
||||
"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,
|
||||
"summary": "The sailor now plans to cut the boy out and split with the narrator alone, but the suspicious boy shadows him all the way into port.",
|
||||
"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,
|
||||
"summary": "The officer delays the hand-over with a long speech until the boy arrives, sees the sack, and refuses to let them leave without his share.",
|
||||
"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,
|
||||
"summary": "The boy stays on deck while the men cut the mattress open below; it is empty, and when they come up the boy has vanished.",
|
||||
"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,
|
||||
"summary": "The boy never returns, not even for his pay, and the officer smilingly asks the sailor how he slept.",
|
||||
"predicament": "A schemer who was outwitted must stand and smile at the people who got the better of him."
|
||||
},
|
||||
{
|
||||
"chunk": 41,
|
||||
"story": 3,
|
||||
"summary": "A rich man soon to marry sits with his idle, indebted cousin, who needles him about his wealth and his fiancée's goodness.",
|
||||
"predicament": "A man about to marry is needled by a dependent relative who hints at something to his discredit."
|
||||
},
|
||||
{
|
||||
"chunk": 42,
|
||||
"story": 3,
|
||||
"summary": "The cousin hints that the fiancée would drop him if she knew what he really was, then says he needs fifteen hundred pounds within a fortnight or faces prison.",
|
||||
"predicament": "A man in desperate debt makes veiled hints to the relative he hopes will pay it."
|
||||
},
|
||||
{
|
||||
"chunk": 43,
|
||||
"story": 3,
|
||||
"summary": "Refused, the cousin offers to sell compromising old letters, threatening to sell them to a rival suitor; the rich man seizes him and is stopped only by a servant entering.",
|
||||
"predicament": "A man is blackmailed by a relative with old letters that could cost him the person he loves."
|
||||
},
|
||||
{
|
||||
"chunk": 44,
|
||||
"story": 3,
|
||||
"summary": "The cousin walks out into the park to let him think it over; the rich man follows, and later tells his mother the cousin has gone for good after a quarrel over money.",
|
||||
"predicament": "A man who has done something terrible in anger must cover for a relative's sudden disappearance."
|
||||
},
|
||||
{
|
||||
"chunk": 45,
|
||||
"story": 3,
|
||||
"summary": "The man walks with his fiancée to a disused well in the park, her favourite spot; he urges her not to sit on its edge.",
|
||||
"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,
|
||||
"summary": "She asks about the missing cousin and defends him; the man bursts out that the cousin was a blackmailer, a liar and a cur.",
|
||||
"predicament": "A man hiding a crime hears it discussed innocently and betrays his feelings with an outburst."
|
||||
},
|
||||
{
|
||||
"chunk": 47,
|
||||
"story": 3,
|
||||
"summary": "She is startled by a whisper she imagines saying 'Jem, help me out'; shaken, he tries to take her indoors, but she insists on staying.",
|
||||
"predicament": "A guilty man tries to hurry someone away from the scene of his crime, and she insists on staying."
|
||||
},
|
||||
{
|
||||
"chunk": 48,
|
||||
"story": 3,
|
||||
"summary": "She drops her mother's diamond bracelet down the well and wants it drained; he looks at her in horror, then covers it by saying he feared she was falling.",
|
||||
"predicament": "A man hiding something learns it may be discovered by an innocent accident, and must not show his fear."
|
||||
},
|
||||
{
|
||||
"chunk": 49,
|
||||
"story": 3,
|
||||
"summary": "He promises to recover the bracelet himself by morning and makes her swear to tell no one; his mother suggests filling the well in.",
|
||||
"predicament": "A man must retrieve something from where his secret lies before anyone else goes looking."
|
||||
},
|
||||
{
|
||||
"chunk": 50,
|
||||
"story": 3,
|
||||
"summary": "At night he secretly fishes the well with a line and hook, and dredges up keys instead of the bracelet.",
|
||||
"predicament": "A man secretly searching where his crime lies keeps pulling up reminders of it."
|
||||
},
|
||||
{
|
||||
"chunk": 51,
|
||||
"story": 3,
|
||||
"summary": "The hook snags; he gives up, spends a sleepless night, and at dawn meets his grooms, whom he has ordered to lower him into the well.",
|
||||
"predicament": "A man who has failed to fix things quietly must now involve others while still keeping his secret."
|
||||
},
|
||||
{
|
||||
"chunk": 52,
|
||||
"story": 3,
|
||||
"summary": "His groom offers to go down instead, since the master looks ill; he refuses curtly and starts to test the air with a candle.",
|
||||
"predicament": "A man hiding something is offered help he cannot accept without being found out."
|
||||
},
|
||||
{
|
||||
"chunk": 53,
|
||||
"story": 3,
|
||||
"summary": "The candle is lost; he stops the groom fetching another, strips, and sits a long time on the well's edge before going down.",
|
||||
"predicament": "A man who must face the thing he did alone gathers his nerve while others watch and wonder."
|
||||
},
|
||||
{
|
||||
"chunk": 54,
|
||||
"story": 3,
|
||||
"summary": "Below, something drags at the rope; the grooms haul up the dead cousin's face with their master behind it, let go in horror, and both fall back into the water.",
|
||||
"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,
|
||||
"summary": "A farmer's wife, her husband, and her retired-policeman uncle discuss a stolen brooch; the uncle has been hiding under their bed hoping to catch the thief, and the wife casts suspicion on a neighbour while accusing her husband of not caring.",
|
||||
"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,
|
||||
"summary": "The uncle declares everyone guilty until proven innocent; the wife recounts missing the brooch, accusing and sacking her servant, who cried on her box.",
|
||||
"predicament": "Someone who has lost something accuses the most defenceless person nearby, and punishes her before any proof."
|
||||
},
|
||||
{
|
||||
"chunk": 57,
|
||||
"story": 4,
|
||||
"summary": "The servant's box held nothing; the husband defends her and is snapped at, while the uncle, pencil and notebook out, pursues the investigation with great ceremony.",
|
||||
"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,
|
||||
"summary": "The husband eats a lozenge from the evidence box and cannot stop laughing; the uncle establishes that no one could have entered the bedroom unheard.",
|
||||
"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,
|
||||
"summary": "Suspicion turns to a widow who visited that morning; the uncle finds muddy footprints on the stairs, which are in fact the husband's, and he stays guiltily quiet.",
|
||||
"predicament": "A man who sees an innocent neighbour blamed for evidence he himself left must decide whether to own up."
|
||||
},
|
||||
{
|
||||
"chunk": 60,
|
||||
"story": 4,
|
||||
"summary": "The husband slyly deflects the footprint evidence, then exchanges silent signals with his wife until the uncle demands to know what is funny.",
|
||||
"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,
|
||||
"summary": "The husband reveals he thinks the widow is after the uncle, not the brooch; the uncle is outraged, then proposes inviting her to tea to trap her into confessing.",
|
||||
"predicament": "A self-important man, teased that a neighbour fancies him, resolves to prove his point by trapping her."
|
||||
},
|
||||
{
|
||||
"chunk": 62,
|
||||
"story": 4,
|
||||
"summary": "The husband refuses to have a guest trapped in his house; the uncle secretly tells his niece he will search the widow's cottage without leave.",
|
||||
"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,
|
||||
"summary": "The husband, who resents the uncle living with them for his wife's hopes of an inheritance, plans to let the widow catch the uncle searching her house, and shadows him.",
|
||||
"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,
|
||||
"summary": "The husband brings the widow home early; the uncle, trapped inside, hides in the larder, and the husband settles in for beer and begins praising him.",
|
||||
"predicament": "A man who has trapped his rival in an embarrassing hiding place decides to make the most of it."
|
||||
},
|
||||
{
|
||||
"chunk": 65,
|
||||
"story": 4,
|
||||
"summary": "Within the uncle's hearing, the husband tells the widow the uncle is shy and devoted to her, and has been asking whether she likes jewellery.",
|
||||
"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,
|
||||
"summary": "Asked for bread and cheese, the widow opens the larder, the uncle bursts out, and her friend takes his hiding there as a bashful suitor's courtship.",
|
||||
"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,
|
||||
"summary": "Late at night one co-owner of a house and a business sits hating his partner, who has discovered his embezzlement and ordered him to leave both by morning.",
|
||||
"predicament": "A man found out in wrongdoing is ordered by his partner to give up everything and leave by morning."
|
||||
},
|
||||
{
|
||||
"chunk": 68,
|
||||
"story": 5,
|
||||
"summary": "The honest partner refuses to cover the theft quietly, offers the exile two hundred pounds, and mentions the servants are away until morning.",
|
||||
"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,
|
||||
"summary": "The exile demands the matter be hushed up, draws a sword from the wall, and stabs his partner when he refuses.",
|
||||
"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,
|
||||
"summary": "Sickened, the killer turns out the lights, flees to the gate, and sees a constable coming up the road.",
|
||||
"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,
|
||||
"summary": "He bids the constable good night, returns inside, and gathers his cash and valuables, reckoning he has eight hours' start.",
|
||||
"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,
|
||||
"summary": "He plans his escape, weighing a crowded district against the seaside, and finds every choice may mean life or death.",
|
||||
"predicament": "A fugitive planning his escape finds that every choice might be the one that gets him caught."
|
||||
},
|
||||
{
|
||||
"chunk": 73,
|
||||
"story": 5,
|
||||
"summary": "At one o'clock he hears creaks on the stairs, and fears the dead man is moving about the house.",
|
||||
"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,
|
||||
"summary": "Finding the library door open, he locks someone inside, then realises it is a burglar he can blame for the killing.",
|
||||
"predicament": "A guilty man realises a stranger's blunder gives him someone else to blame."
|
||||
},
|
||||
{
|
||||
"chunk": 75,
|
||||
"story": 5,
|
||||
"summary": "He fires through the door, raises the alarm, and the police break in and seize the terrified intruder.",
|
||||
"predicament": "A man framing an innocent person must act the outraged victim in front of the law."
|
||||
},
|
||||
{
|
||||
"chunk": 76,
|
||||
"story": 5,
|
||||
"summary": "The intruder swears the body was there when he arrived; the sergeant uncovers it, and the killer feigns horror.",
|
||||
"predicament": "A man must play the shocked innocent while the person he framed pleads the truth."
|
||||
},
|
||||
{
|
||||
"chunk": 77,
|
||||
"story": 5,
|
||||
"summary": "The killer names his friend and accuses the intruder, who accuses him back; the sergeant seems to side with the killer.",
|
||||
"predicament": "Someone falsely accused points to the real culprit, who must hold his nerve and keep up his story."
|
||||
},
|
||||
{
|
||||
"chunk": 78,
|
||||
"story": 5,
|
||||
"summary": "The sergeant takes the pistol, handcuffs the killer without warning, and sends for a doctor, since the victim is not dead.",
|
||||
"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,
|
||||
"summary": "The dying partner opens his eyes and speaks; the sergeant takes notes and leads the killer away.",
|
||||
"predicament": "A man whose deceit has collapsed can only watch as the truth comes out and he is taken away."
|
||||
},
|
||||
{
|
||||
"chunk": 80,
|
||||
"story": 6,
|
||||
"summary": "A ragged stranger swaggers into the best inn in a small town, bullies the waiter who tries to turn him out, and demands to see the landlord.",
|
||||
"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,
|
||||
"summary": "The stranger greets the landlord by another name, a wanted pirate's, and demands clothes, lodging and money in return for silence.",
|
||||
"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,
|
||||
"summary": "The landlord gives him money; the blackmailer draws a pistol, declares he will take half of everything from now on, and settles in.",
|
||||
"predicament": "A man pays off a blackmailer and learns the price will be half of everything, for good."
|
||||
},
|
||||
{
|
||||
"chunk": 83,
|
||||
"story": 6,
|
||||
"summary": "The landlord says he has lived honestly for fifteen years and warns the man not to wake the devil in him; the blackmailer meets the landlord's stepdaughter and says he will stay.",
|
||||
"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,
|
||||
"summary": "The blackmailer orders the best supper and room, and sleeps with his door barricaded and a pistol under his pillow.",
|
||||
"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,
|
||||
"summary": "He pushes in at the family breakfast table, eyes the stepdaughter, hints at the landlord's past before her, and goes out to buy new clothes.",
|
||||
"predicament": "A household must make polite space for someone who holds them hostage."
|
||||
},
|
||||
{
|
||||
"chunk": 86,
|
||||
"story": 6,
|
||||
"summary": "Within a month the blackmailer runs the inn; the landlord, failing in health, backs him in sacking a loyal old servant, and his daughter demands to know who is master.",
|
||||
"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,
|
||||
"summary": "The landlord sides with the blackmailer, who threatens to ruin the girl and have the landlord hanged; the landlord pleads for peace.",
|
||||
"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,
|
||||
"summary": "The inn decays under the blackmailer's rule; the landlord collapses with a fit, which upsets the blackmailer's plans.",
|
||||
"predicament": "A blackmailer's hold depends on his victim's life, and that victim suddenly collapses."
|
||||
},
|
||||
{
|
||||
"chunk": 89,
|
||||
"story": 6,
|
||||
"summary": "The landlord lies dying, anxious to please the blackmailer, while his daughter nurses him; the blackmailer resolves to get the hidden money before he dies.",
|
||||
"predicament": "A blackmailer whose victim is dying must grab what he can before his hold disappears."
|
||||
},
|
||||
{
|
||||
"chunk": 90,
|
||||
"story": 6,
|
||||
"summary": "The blackmailer threatens to inform; the dying man begs for time and promises to reveal his money that night if his daughter gets a share.",
|
||||
"predicament": "A dying man pressed by his tormentor bargains for time and for provision for someone he loves."
|
||||
},
|
||||
{
|
||||
"chunk": 91,
|
||||
"story": 6,
|
||||
"summary": "At night the blackmailer drugs the nurse and demands the money; the landlord extracts his promise about the daughter.",
|
||||
"predicament": "A man who thinks his helpless victim is at his mercy leans in close to hear the secret."
|
||||
},
|
||||
{
|
||||
"chunk": 92,
|
||||
"story": 6,
|
||||
"summary": "The 'dying' landlord seizes and strangles him, revealing he has been shamming weakness and planning to blame the killing on the blackmailer's own followers.",
|
||||
"predicament": "A man who pretended to be helpless strikes at the tormentor who threatened his family."
|
||||
},
|
||||
{
|
||||
"chunk": 93,
|
||||
"story": 6,
|
||||
"summary": "He stages the scene as a robbery and returns to bed as the drugged nurse unwittingly provides his alibi.",
|
||||
"predicament": "A man who has done a desperate thing arranges things so that suspicion falls elsewhere."
|
||||
},
|
||||
{
|
||||
"chunk": 94,
|
||||
"story": 7,
|
||||
"summary": "An old villager chats with a travelling painter repainting the inn sign, which had been mistaken for a portrait of a king.",
|
||||
"predicament": "A craftsman told his work looks like something else quietly fixes it before anyone else notices."
|
||||
},
|
||||
{
|
||||
"chunk": 95,
|
||||
"story": 7,
|
||||
"summary": "The painter shares his beer, and the old man begins the tale of a travelling circus that stopped in the village.",
|
||||
"predicament": "An old man looking for a free drink offers a story in exchange."
|
||||
},
|
||||
{
|
||||
"chunk": 96,
|
||||
"story": 7,
|
||||
"summary": "News comes that the circus tiger has escaped nearby; a tramp comes running to the inn shouting that the tiger is after him, and the drinkers rush indoors.",
|
||||
"predicament": "People who laughed at a distant danger panic when it seems to have arrived on their doorstep."
|
||||
},
|
||||
{
|
||||
"chunk": 97,
|
||||
"story": 7,
|
||||
"summary": "The landlord bars the door against the tramp; a man knocks the landlord down to get out to protect the families, and the tramp gets in.",
|
||||
"predicament": "Someone must choose between keeping himself safe behind a barred door and letting in a stranger in danger."
|
||||
},
|
||||
{
|
||||
"chunk": 98,
|
||||
"story": 7,
|
||||
"summary": "The tramp, revived with free brandy, tells a vivid tale of fleeing the tiger, and the men are afraid to go home.",
|
||||
"predicament": "A stranger with a thrilling tale of danger is rewarded by a frightened crowd without anyone checking it."
|
||||
},
|
||||
{
|
||||
"chunk": 99,
|
||||
"story": 7,
|
||||
"summary": "The men sleep at the inn; the tramp is gone by morning; the village lives in fear, though the local rogue laughs at the whole thing.",
|
||||
"predicament": "A community living in fear of an unseen threat ignores the one doubter, because he has a bad name."
|
||||
},
|
||||
{
|
||||
"chunk": 100,
|
||||
"story": 7,
|
||||
"summary": "A villager's hens are taken; he carries the evidence to the constable in the next village, who shoves him away as drunk.",
|
||||
"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,
|
||||
"summary": "Pigs vanish night after night; the authorities do nothing, and neighbours squabble over whether to take animals indoors.",
|
||||
"predicament": "A community suffering repeated losses cannot get help, and cannot agree how to protect itself."
|
||||
},
|
||||
{
|
||||
"chunk": 102,
|
||||
"story": 7,
|
||||
"summary": "Children claim sightings, and a villager staggers in describing how the tiger chased him up a tree.",
|
||||
"predicament": "A man tells a dramatic story of narrow escape to an audience eager to believe it."
|
||||
},
|
||||
{
|
||||
"chunk": 103,
|
||||
"story": 7,
|
||||
"summary": "When one man questions the story, the rogue 'confirms' it, claiming he watched from another tree; the storyteller, caught out, goes home silent.",
|
||||
"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,
|
||||
"summary": "The neighbouring town and the clergyman say there was no tiger; after more losses a former militiaman raises a band of seventeen to hunt it.",
|
||||
"predicament": "People told their fears are nonsense by outsiders decide to take matters into their own hands."
|
||||
},
|
||||
{
|
||||
"chunk": 105,
|
||||
"story": 7,
|
||||
"summary": "The hunt searches the wood timidly and finds nothing; back at the inn, an old man reports the tiger has just gone into the rogue's cottage.",
|
||||
"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,
|
||||
"summary": "The armed men crowd into the rogue's cottage, pushing him ahead of them, despite his protests.",
|
||||
"predicament": "A man with something to hide has an armed crowd force its way into his home."
|
||||
},
|
||||
{
|
||||
"chunk": 107,
|
||||
"story": 7,
|
||||
"summary": "His kitchen is full of pork and poultry; accused of theft, he brazenly claims to have gone into the meat trade.",
|
||||
"predicament": "A man caught with the evidence of his thefts brazens it out and dares his accusers to prove anything."
|
||||
},
|
||||
{
|
||||
"chunk": 108,
|
||||
"story": 7,
|
||||
"summary": "He turns their own tiger sightings against them: those who swore they saw it would have to repeat it on oath.",
|
||||
"predicament": "A man accused of wrongdoing turns his accusers' own exaggerations into his defence."
|
||||
},
|
||||
{
|
||||
"chunk": 109,
|
||||
"story": 7,
|
||||
"summary": "He claims to have receipts, offers to sell them the meat, and gets away with it; he and the tramp turn out to have known each other.",
|
||||
"predicament": "People who know they were swindled cannot prove it, and must watch the swindler profit."
|
||||
},
|
||||
{
|
||||
"chunk": 110,
|
||||
"story": 8,
|
||||
"summary": "A retired officer, dressed with great care, sets out on a hot afternoon and overtakes a friend who stares uneasily at his loud tie.",
|
||||
"predicament": "A man nervously setting out to do something that matters to him is stopped by a friend with questions."
|
||||
},
|
||||
{
|
||||
"chunk": 111,
|
||||
"story": 8,
|
||||
"summary": "The officer confides he is going to propose to a widow; his friend, equally dressed up, says he is on the same errand, and the two race along the road.",
|
||||
"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,
|
||||
"summary": "Followed by jeering boys, they stop and agree they cannot propose together, but neither will yield the day, each pleading an excuse.",
|
||||
"predicament": "Two rivals agree someone must go first, and neither will be the one to wait."
|
||||
},
|
||||
{
|
||||
"chunk": 113,
|
||||
"story": 8,
|
||||
"summary": "Each claims precedence; the officer vows that if they go together he will match any proposal on the spot, and refuses to be put off.",
|
||||
"predicament": "Two rivals for the same prize trade claims of priority, and each suspects the other of a trick."
|
||||
},
|
||||
{
|
||||
"chunk": 114,
|
||||
"story": 8,
|
||||
"summary": "The officer proposes they play one game of chess for the right to propose first; the friend agrees, on condition that if the winner is refused, the loser may not propose without his permission.",
|
||||
"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,
|
||||
"summary": "The officer offers his rival a large brandy, which the rival sets aside; after a long game the rival checkmates him with a bishop the officer had forgotten.",
|
||||
"predicament": "A man who tried to tip a contest in his favour loses it through his own oversight."
|
||||
},
|
||||
{
|
||||
"chunk": 116,
|
||||
"story": 8,
|
||||
"summary": "The winner tidies himself in the loser's house and goes off to propose; the loser waits up all night and next morning goes to hear the result.",
|
||||
"predicament": "A man who lost the right to try first must wait while his rival takes his chance."
|
||||
},
|
||||
{
|
||||
"chunk": 117,
|
||||
"story": 8,
|
||||
"summary": "The rival was refused, but holds the officer to the condition and plans to keep proposing until the widow accepts him.",
|
||||
"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,
|
||||
"summary": "The rival says he will warn the widow off the officer, suggests the officer court her companion instead, and the officer storms out and sulks for weeks.",
|
||||
"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,
|
||||
"summary": "Meeting the widow by chance, the officer confesses the chess game to explain his absence; she is indignant, then laughs.",
|
||||
"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,
|
||||
"summary": "She says she is angry with him for losing, and proposes a plan so that he can win at cards what he lost at chess, under the rival's eyes.",
|
||||
"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,
|
||||
"summary": "Playing cards, the widow asks the officer if he would like her hand; he takes it and kisses it, she 'meant the cards', then lets him keep it, and the rival is left fuming.",
|
||||
"predicament": "Someone arranges to accept the suitor she prefers in a way his rival cannot object to."
|
||||
},
|
||||
{
|
||||
"chunk": 122,
|
||||
"story": 9,
|
||||
"summary": "A man wakes up thirsty in a cramped, reeking bunk, with a stranger's face swearing at him, and cannot tell where he is.",
|
||||
"predicament": "A man wakes up somewhere strange with no idea how he got there."
|
||||
},
|
||||
{
|
||||
"chunk": 123,
|
||||
"story": 9,
|
||||
"summary": "He realises he is on a ship, dressed in rags; he finds his friend among the sleepers and learns the ship's name.",
|
||||
"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,
|
||||
"summary": "Seasick and far from land, he goes aft and demands to be taken home; the officer calls him mad and orders him forward.",
|
||||
"predicament": "A man who has always been obeyed tries to give orders somewhere nobody knows or believes who he is."
|
||||
},
|
||||
{
|
||||
"chunk": 125,
|
||||
"story": 9,
|
||||
"summary": "His friend tries to threaten legal consequences but is too sick to finish; the two bicker over which of them was drunk.",
|
||||
"predicament": "Two people in trouble together try to put on a united front and end up blaming each other."
|
||||
},
|
||||
{
|
||||
"chunk": 126,
|
||||
"story": 9,
|
||||
"summary": "The captain says they signed on as cook and seaman, calls them drunk impostors, and threatens jail; they are put to work.",
|
||||
"predicament": "Two people who claim to be respected professionals are treated as impostors and forced to do menial work."
|
||||
},
|
||||
{
|
||||
"chunk": 127,
|
||||
"story": 9,
|
||||
"summary": "The crew mocks their claims of being drugged and of sobriety; their airs make the officers dislike them all the more.",
|
||||
"predicament": "People brought low insist on their old status, and are mocked for it by those around them."
|
||||
},
|
||||
{
|
||||
"chunk": 128,
|
||||
"story": 9,
|
||||
"summary": "The captain humiliates the cook; the cook obeys, tidies the cabin, and comes back with a secret smile and something in his pocket, telling his friend it won't be for long.",
|
||||
"predicament": "A humiliated man pretends to submit while he quietly prepares his revenge."
|
||||
},
|
||||
{
|
||||
"chunk": 129,
|
||||
"story": 9,
|
||||
"summary": "The captain is struck with violent pains; the cook refuses to treat him, as he 'only shipped as cook', until he agrees to pay a professional fee.",
|
||||
"predicament": "A man mocked for claiming skill is suddenly needed for it, and names his price."
|
||||
},
|
||||
{
|
||||
"chunk": 130,
|
||||
"story": 9,
|
||||
"summary": "The cook prescribes, collects his fee, scares the captain about making a will, and has the mate sent out for snuffling.",
|
||||
"predicament": "A man who has gained the upper hand over those who humiliated him enjoys every moment of it."
|
||||
},
|
||||
{
|
||||
"chunk": 131,
|
||||
"story": 9,
|
||||
"summary": "The cook hints he dosed the food from the medicine chest; the mate falls ill too, and the cook demands to be treated as surgeon.",
|
||||
"predicament": "A man who has secretly caused the trouble only he can cure makes the most of it."
|
||||
},
|
||||
{
|
||||
"chunk": 132,
|
||||
"story": 9,
|
||||
"summary": "The mate haggles over the fee; the second officer falls ill too; the sick captain scolds the sick mate for being ill at all.",
|
||||
"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,
|
||||
"summary": "The cook treats and weakens all three officers; the captain and mate pay the other man to draw up their wills; they recover slowly as they reach port.",
|
||||
"predicament": "People who scorned two outsiders now depend on them completely, and pay for the privilege."
|
||||
},
|
||||
{
|
||||
"chunk": 134,
|
||||
"story": 9,
|
||||
"summary": "Ashore, the cook tells the captain he ate something that disagreed with him, and leaves with a wave.",
|
||||
"predicament": "A man who got even with his tormentors leaves them knowing they were had."
|
||||
},
|
||||
{
|
||||
"chunk": 135,
|
||||
"story": 10,
|
||||
"summary": "At a crowded family breakfast, a poor widow living with her sister's family gets a letter and reads it so slowly that the others nearly burst with impatience.",
|
||||
"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,
|
||||
"summary": "The widow fears the letter is a hoax; her brother-in-law thinks so too, until her sister reads it and rejoices.",
|
||||
"predicament": "Someone who has never had luck is told of a windfall and is afraid to believe it."
|
||||
},
|
||||
{
|
||||
"chunk": 137,
|
||||
"story": 10,
|
||||
"summary": "The widow has inherited two thousand pounds from a former employer; the brother-in-law grumbles that nobody ever left him anything, and spreads the news through the town.",
|
||||
"predicament": "A man sees a relative come into money and resents that luck never comes his way."
|
||||
},
|
||||
{
|
||||
"chunk": 138,
|
||||
"story": 10,
|
||||
"summary": "Local men suddenly befriend the brother-in-law; his wife points out that two old dealers are courting the heiress for her money.",
|
||||
"predicament": "A man flattered by new friends is told they are really after a relative's money."
|
||||
},
|
||||
{
|
||||
"chunk": 139,
|
||||
"story": 10,
|
||||
"summary": "The brother-in-law watches the two suitors flatter the widow and tries to undercut them by announcing her age, nearly giving away his wife's.",
|
||||
"predicament": "A man trying to expose flattering suitors trips over his own family's sensibilities."
|
||||
},
|
||||
{
|
||||
"chunk": 140,
|
||||
"story": 10,
|
||||
"summary": "The suitors outbid each other with compliments; the wife's temper frays, and the visitors are shown out with the door shut hard.",
|
||||
"predicament": "Two rivals courting the same person keep topping each other's compliments while the family grows fed up."
|
||||
},
|
||||
{
|
||||
"chunk": 141,
|
||||
"story": 10,
|
||||
"summary": "The family, wanting to keep the money, fail to warn the widow; the slow dealer, losing ground to his glib rival, hatches a plan and enlists a small-time dealer.",
|
||||
"predicament": "A slow-talking man losing a contest to a glib rival looks for a trick instead of better words."
|
||||
},
|
||||
{
|
||||
"chunk": 142,
|
||||
"story": 10,
|
||||
"summary": "The dealer bribes an accomplice, who holds out for a bigger fee, to tell the rival in confidence that the widow has lost her fortune; the rival swallows it.",
|
||||
"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,
|
||||
"summary": "With the rival gone, the dealer courts the widow alone, preaching the virtues of poverty, and loses seven games of draughts to her on purpose.",
|
||||
"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,
|
||||
"summary": "He blows her kisses and fusses over a bumped head, invites himself back, and walks home sure the money is his.",
|
||||
"predicament": "A schemer who thinks his deal is done walks away gloating too soon."
|
||||
},
|
||||
{
|
||||
"chunk": 145,
|
||||
"story": 10,
|
||||
"summary": "He spreads the rumour of her ruin through the town, then proposes publicly 'now that she is poor', to look noble.",
|
||||
"predicament": "A man makes a public show of selfless devotion, sure that it will cost him nothing."
|
||||
},
|
||||
{
|
||||
"chunk": 146,
|
||||
"story": 10,
|
||||
"summary": "She accepts; the family, touched, apologise for misjudging him, then mention a letter that arrived.",
|
||||
"predicament": "A schemer whose plan has worked is thanked for his goodness while the ground starts to shift under him."
|
||||
},
|
||||
{
|
||||
"chunk": 147,
|
||||
"story": 10,
|
||||
"summary": "The lawyers' letter says a later will was found and she has nothing; the dealer, now engaged to a poor woman, is left alone with her to court.",
|
||||
"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,
|
||||
"summary": "In an inn, men trade ghost stories; a sceptical old sailor mocks sailors' visions, then admits one uncanny experience on land.",
|
||||
"predicament": "A sceptic among believers is pressed to admit whether he has ever seen anything he could not explain."
|
||||
},
|
||||
{
|
||||
"chunk": 149,
|
||||
"story": 11,
|
||||
"summary": "As a young man back from abroad, the narrator found his relatives away, took lodgings at a dull inn, and set out on a long country walk.",
|
||||
"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,
|
||||
"summary": "He loses his way in the marshes as fog and night come down, and at last finds a road and hears footsteps.",
|
||||
"predicament": "A traveller lost in the dark and the cold must find shelter before night closes in."
|
||||
},
|
||||
{
|
||||
"chunk": 151,
|
||||
"story": 11,
|
||||
"summary": "A countryman sets him on the road but warns him away from a lit house nearby, said to hide something monstrous; tired, he goes to it anyway.",
|
||||
"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,
|
||||
"summary": "A grim old woman and a huge old man answer; after a furtive exchange the man insists he stay for dinner, and he feels trapped.",
|
||||
"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,
|
||||
"summary": "The host says his son will dine with them in the dark because of his eyes; he puts out the candles, and a strange voice speaks.",
|
||||
"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,
|
||||
"summary": "The host leaves him alone with the unseen son, and the guest thinks he sees wild eyes shining at him across the table.",
|
||||
"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,
|
||||
"summary": "The screen falls, and firelight shows the son's ruined face; the father lights the candles and explains his son was disfigured in a fire.",
|
||||
"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,
|
||||
"summary": "The guest apologises warmly; they celebrate the son's birthday until midnight, and the father toasts the children his son saved.",
|
||||
"predicament": "A man who shrank from someone must make amends, and finds they deserve his respect."
|
||||
}
|
||||
]
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Print a range of scenes for reading. Usage: dump.py <first_chunk> <last_chunk>"""
|
||||
import json, sys
|
||||
chunks = json.load(open(__file__.rsplit('/',1)[0] + '/chunks.json'))
|
||||
a, b = int(sys.argv[1]), int(sys.argv[2])
|
||||
cur = None
|
||||
for i in range(a, min(b + 1, len(chunks))):
|
||||
c = chunks[i]
|
||||
if c['story'] != cur:
|
||||
cur = c['story']
|
||||
print(f"\n########## STORY {cur}: {c['title']} ##########")
|
||||
print(f"\n----- scene {i} (story {cur}) -----")
|
||||
print(c['text'].strip())
|
||||
@@ -0,0 +1,48 @@
|
||||
"""Split the Standard Ebooks single-page HTML of W. W. Jacobs, *The Lady of the Barge*, into stories,
|
||||
then cut scenes with the same rule as ../chunk.py (paragraph boundaries, ~320 words, tail >= 80).
|
||||
Deterministic: each story is a <section> with an <h2 epub:type="title">. Front and back matter
|
||||
(contents, imprint, colophon, uncopyright) are dropped by name."""
|
||||
import html, json, pathlib, re
|
||||
|
||||
SKIP = {'Table of Contents', 'Imprint', 'Colophon', 'Uncopyright'}
|
||||
TARGET = 320
|
||||
VOLUME = 'The Lady of the Barge (W. W. Jacobs, 1902)'
|
||||
|
||||
raw = pathlib.Path('corpus/lady-of-the-barge.html').read_text(encoding='utf-8')
|
||||
body = raw[raw.find('<body'):]
|
||||
parts = re.split(r'<h2[^>]*epub:type="title"[^>]*>(.*?)</h2>', body, flags=re.S)
|
||||
|
||||
def text_of(fragment):
|
||||
paras = []
|
||||
for p in re.findall(r'<p[^>]*>(.*?)</p>', fragment, flags=re.S):
|
||||
t = html.unescape(re.sub(r'<[^>]+>', '', p))
|
||||
t = re.sub(r'\s+', ' ', t).strip()
|
||||
if t: paras.append(t)
|
||||
return paras
|
||||
|
||||
stories = []
|
||||
for i in range(1, len(parts), 2):
|
||||
title = html.unescape(re.sub(r'<[^>]+>', '', parts[i])).strip()
|
||||
if title in SKIP: continue
|
||||
paras = text_of(parts[i + 1])
|
||||
stories.append({'volume': VOLUME, 'title': title, 'words': sum(len(p.split()) for p in paras),
|
||||
'text': '\n\n'.join(paras)})
|
||||
|
||||
chunks = []
|
||||
for si, s in enumerate(stories):
|
||||
buf, n = [], 0
|
||||
for p in s['text'].split('\n\n'):
|
||||
w = len(p.split())
|
||||
if n + w > TARGET and buf:
|
||||
chunks.append({'story': si, 'title': s['title'], 'volume': s['volume'], 'text': ' '.join(buf), 'words': n})
|
||||
buf, n = [], 0
|
||||
buf.append(p); n += w
|
||||
if buf and n >= 80:
|
||||
chunks.append({'story': si, 'title': s['title'], 'volume': s['volume'], 'text': ' '.join(buf), 'words': n})
|
||||
|
||||
pathlib.Path('stories.json').write_text(json.dumps(stories, indent=1, ensure_ascii=False), encoding='utf-8')
|
||||
pathlib.Path('chunks.json').write_text(json.dumps(chunks, ensure_ascii=False), encoding='utf-8')
|
||||
for si, s in enumerate(stories):
|
||||
print(f"{si:2} {s['title']:24} {s['words']:6} words {sum(c['story']==si for c in chunks):3} scenes")
|
||||
ws = sorted(c['words'] for c in chunks)
|
||||
print(f'stories {len(stories)} scenes {len(chunks)} words/scene min {ws[0]} median {ws[len(ws)//2]} max {ws[-1]} total {sum(ws):,}')
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
||||
qwen3:14b: 250 distinct clusters, 5 already judged
|
||||
10/250 28s parse failures 0 last: D15 fit 0.47 "eviction threat or action"
|
||||
20/250 58s parse failures 0 last: D25 fit 0.53 "Wealthy individual seeking social or personal advancement th"
|
||||
30/250 89s parse failures 0 last: D35 fit 0.47 "Someone is suspicious of another person's intentions"
|
||||
40/250 119s parse failures 0 last: T05 fit 0.73 "Attempting to obtain money through deception or coercion"
|
||||
50/250 147s parse failures 0 last: T15 fit 0.47 "Someone pressures someone else into an action against their "
|
||||
60/250 175s parse failures 0 last: T25 fit 0.33 "Someone trying to impress someone else with their qualities "
|
||||
70/250 204s parse failures 0 last: T35 fit 0.53 "Threats and demands for money"
|
||||
80/250 232s parse failures 0 last: R004 fit 0.60 "Confrontation between individuals over a personal or social "
|
||||
90/250 264s parse failures 0 last: R014 fit 0.73 "Drinking in a bar with social tension"
|
||||
100/250 296s parse failures 0 last: X003 fit 0.27 "Someone courts someone, while someone interrupts them"
|
||||
110/250 326s parse failures 0 last: X013 fit 0.71 "Seeking financial advice"
|
||||
120/250 355s parse failures 0 last: X023 fit 0.33 "Trying to impress someone through a dinner invitation"
|
||||
130/250 378s parse failures 0 last: X033 fit 0.53 "someone pressures someone else for personal gain"
|
||||
140/250 405s parse failures 0 last: X043 fit 0.60 "Dinner party with social dynamics"
|
||||
150/250 436s parse failures 0 last: X053 fit 0.27 "Exploitation of the naive or vulnerable"
|
||||
160/250 475s parse failures 0 last: X063 fit 0.50 "Confrontation between individuals over a disagreement or con"
|
||||
170/250 526s parse failures 0 last: X073 fit 0.53 "Manipulation for personal gain"
|
||||
180/250 586s parse failures 0 last: Y010 fit 0.87 "Artist seeking financial support or patronage"
|
||||
190/250 643s parse failures 0 last: Y020 fit 0.45 "Seeking relief from hardship in a city"
|
||||
200/250 690s parse failures 0 last: Y030 fit 0.71 "Social interaction at a dining establishment"
|
||||
210/250 722s parse failures 0 last: Y040 fit 0.67 "Competing for social status through dining experiences"
|
||||
220/250 764s parse failures 0 last: Y050 fit 0.62 "Someone trying to win someone else's affection"
|
||||
230/250 794s parse failures 0 last: Y060 fit 0.00 "NONE"
|
||||
240/250 818s parse failures 0 last: Y070 fit 0.00 "NONE"
|
||||
done: 245 judged this session in 832s; parse failures 0
|
||||
exit 0
|
||||
@@ -0,0 +1,27 @@
|
||||
qwen2.5:3b-instruct: 250 distinct clusters, 5 already judged
|
||||
10/250 11s parse failures 0 last: D15 fit 0.80 "Eviction and housing disputes"
|
||||
20/250 24s parse failures 1 last: D25 fit 0.33 "seeking advice or assistance"
|
||||
30/250 37s parse failures 1 last: D35 fit 0.73 "romantic interest or potential romance"
|
||||
40/250 48s parse failures 1 last: T05 fit 0.87 "con or scam"
|
||||
50/250 60s parse failures 1 last: T15 fit 0.40 "manipulation and hidden agenda"
|
||||
60/250 71s parse failures 1 last: T25 fit 0.40 "flattery and attention seeking"
|
||||
70/250 83s parse failures 1 last: T35 fit 0.20 "Demanding payment/repayment"
|
||||
80/250 94s parse failures 1 last: R004 fit 0.47 "confrontation over authority or status"
|
||||
90/250 108s parse failures 1 last: R014 fit 0.87 "drinking and socializing"
|
||||
100/250 120s parse failures 1 last: X003 fit 1.00 "demand and confrontation"
|
||||
110/250 131s parse failures 1 last: X013 fit 0.43 "seeking financial advice"
|
||||
120/250 142s parse failures 1 last: X023 fit 0.47 "social gathering or dinner party"
|
||||
130/250 153s parse failures 1 last: X033 fit 0.73 "negotiation and payment"
|
||||
140/250 165s parse failures 1 last: X043 fit 0.93 "dinner party/hosting"
|
||||
150/250 177s parse failures 1 last: X053 fit 0.67 "exploit and exploitation"
|
||||
160/250 189s parse failures 1 last: X063 fit 0.67 "confrontation"
|
||||
170/250 200s parse failures 1 last: X073 fit 0.60 "coercion and manipulation"
|
||||
180/250 214s parse failures 1 last: Y010 fit 0.00 "NONE"
|
||||
190/250 226s parse failures 1 last: Y020 fit 0.64 "seeking connections or companionship"
|
||||
200/250 236s parse failures 1 last: Y030 fit 0.71 "dining and socializing"
|
||||
210/250 248s parse failures 1 last: Y040 fit 1.00 "people seek social status or attention"
|
||||
220/250 258s parse failures 1 last: Y050 fit 0.25 "courtship and seduction"
|
||||
230/250 268s parse failures 1 last: Y060 fit 0.40 "confrontation over professional issue"
|
||||
240/250 280s parse failures 1 last: Y070 fit 0.80 "people pursuing their livelihood or interests"
|
||||
done: 245 judged this session in 286s; parse failures 1
|
||||
exit 0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"theta": 0.6,
|
||||
"primary_G4": {
|
||||
"accepted": 50,
|
||||
"label1plus": 24,
|
||||
"label2": 4,
|
||||
"P1+": 0.48,
|
||||
"recall1+": 0.3582089552238806
|
||||
},
|
||||
"primary_G2": {
|
||||
"accepted": 49,
|
||||
"label1plus": 36,
|
||||
"label2": 5,
|
||||
"P1+": 0.7346938775510204,
|
||||
"recall1+": 0.5373134328358209
|
||||
},
|
||||
"small": {
|
||||
"accepted": 13,
|
||||
"label1plus": 3,
|
||||
"label2": 1,
|
||||
"P1+": 0.23076923076923078,
|
||||
"recall1+": 0.3333333333333333
|
||||
},
|
||||
"criteria": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"theta": 0.6,
|
||||
"primary_G4": {
|
||||
"accepted": 42,
|
||||
"label1plus": 22,
|
||||
"label2": 4,
|
||||
"P1+": 0.5238095238095238,
|
||||
"recall1+": 0.3283582089552239
|
||||
},
|
||||
"primary_G2": {
|
||||
"accepted": 49,
|
||||
"label1plus": 36,
|
||||
"label2": 5,
|
||||
"P1+": 0.7346938775510204,
|
||||
"recall1+": 0.5373134328358209
|
||||
},
|
||||
"small": {
|
||||
"accepted": 17,
|
||||
"label1plus": 5,
|
||||
"label2": 2,
|
||||
"P1+": 0.29411764705882354,
|
||||
"recall1+": 0.5555555555555556
|
||||
},
|
||||
"criteria": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
"""G4: ask the model whether a cluster's summaries share one social situation (SELECTION.md, "G4").
|
||||
|
||||
STP_OLLAMA=http://host:11434 python3 judge.py qwen2.5:3b-instruct --pilot # first 5 dev clusters
|
||||
STP_OLLAMA=http://host:11434 python3 judge.py qwen2.5:3b-instruct # all labelled clusters
|
||||
|
||||
Writes judge-<model>.json, keyed by a hash of the member set, checkpointed after every call.
|
||||
"""
|
||||
import hashlib, json, pathlib, random, re, sys, time
|
||||
import ollama
|
||||
|
||||
MODEL = sys.argv[1]
|
||||
PILOT = '--pilot' in sys.argv
|
||||
SHOW = 15
|
||||
|
||||
SYSTEM = """You read short summaries of scenes from different stories and decide whether they share one kind of social situation.
|
||||
|
||||
A social situation is a specific transaction between people: who wants what from whom, and what is at stake. Describe it in your own words.
|
||||
|
||||
Rules:
|
||||
- Find the single situation that the largest number of summaries clearly show.
|
||||
- It is normal for many summaries not to fit. Include a summary only if it clearly shows that situation.
|
||||
- A shared place, mood, profession or word is not a situation.
|
||||
- If no situation is clearly shown by at least three summaries, the situation is NONE.
|
||||
|
||||
Reply with JSON only: {"situation": "<at most 10 words, or NONE>", "fits": [<numbers of the summaries that clearly show it>]}"""
|
||||
|
||||
text = {x['chunk']: x['summary'] for x in
|
||||
json.loads(pathlib.Path('summaries_clean.json').read_text(encoding='utf-8'))['summaries']}
|
||||
|
||||
def clusters():
|
||||
"""(set name, cluster id, members) for every labelled cluster, development first."""
|
||||
out = []
|
||||
for name in ('dev', 'test'):
|
||||
for cid, m in json.loads(pathlib.Path(f'candidates-{name}.json').read_text()).items():
|
||||
out.append((name, cid, m))
|
||||
for name, mapfile in (('recover', 'recover-map.json'), ('validate', 'validate-map.json'),
|
||||
('validate3', 'validate3-map.json')):
|
||||
for cid, e in json.loads(pathlib.Path(mapfile).read_text()).items():
|
||||
out.append((name, cid, e['members']))
|
||||
return out
|
||||
|
||||
def key(members):
|
||||
return hashlib.sha1(','.join(map(str, sorted(members))).encode()).hexdigest()[:16]
|
||||
|
||||
def parse(raw, shown):
|
||||
raw = re.sub(r'<think>.*?</think>', '', raw, flags=re.S).strip()
|
||||
m = re.search(r'\{.*\}', raw, flags=re.S)
|
||||
if not m: return None
|
||||
try:
|
||||
d = json.loads(m.group(0))
|
||||
except Exception:
|
||||
return None
|
||||
situation = str(d.get('situation', '')).strip()
|
||||
fits = d.get('fits', [])
|
||||
if not isinstance(fits, list): return None
|
||||
valid = sorted({int(f) for f in fits if isinstance(f, (int, float, str)) and str(f).strip().isdigit()
|
||||
and 1 <= int(f) <= shown})
|
||||
none = not situation or situation.upper().startswith('NONE')
|
||||
return {'situation': situation, 'fits': valid, 'fit': 0.0 if none else len(valid) / shown}
|
||||
|
||||
out_path = pathlib.Path(f"judge-{MODEL.replace(':', '_').replace('/', '_')}.json")
|
||||
cache = json.loads(out_path.read_text()) if out_path.exists() else {}
|
||||
todo, seen = [], set()
|
||||
for name, cid, members in clusters():
|
||||
k = key(members)
|
||||
if k in seen: continue
|
||||
seen.add(k)
|
||||
todo.append((name, cid, members, k))
|
||||
if PILOT: todo = todo[:5]
|
||||
print(f'{MODEL}: {len(todo)} distinct clusters, {sum(k in cache for *_, k in todo)} already judged', flush=True)
|
||||
|
||||
t0, done, failures = time.time(), 0, 0
|
||||
for name, cid, members, k in todo:
|
||||
if k in cache: continue
|
||||
sample = sorted(members)
|
||||
rnd = random.Random(int(k, 16))
|
||||
if len(sample) > SHOW: sample = rnd.sample(sample, SHOW)
|
||||
rnd.shuffle(sample)
|
||||
user = '\n'.join(f'{i + 1}. {text[c]}' for i, c in enumerate(sample))
|
||||
if MODEL.startswith('qwen3'): user += '\n\n/no_think'
|
||||
if MODEL.startswith('qwen3'):
|
||||
raw, gen, pre = ollama.chat(MODEL, SYSTEM, user, num_ctx=2048, num_predict=300, fmt='json', think=False)
|
||||
else:
|
||||
raw, gen, pre = ollama.chat(MODEL, SYSTEM, user, num_ctx=2048, num_predict=200, fmt='json')
|
||||
parsed = parse(raw, len(sample))
|
||||
failures += parsed is None
|
||||
cache[k] = {'set': name, 'id': cid, 'size': len(members), 'shown': len(sample), 'raw': raw,
|
||||
'parsed': parsed is not None, **(parsed or {'situation': '', 'fits': [], 'fit': 0.0})}
|
||||
out_path.write_text(json.dumps(cache, indent=1))
|
||||
done += 1
|
||||
if PILOT or done % 10 == 0:
|
||||
print(f' {done}/{len(todo)} {time.time() - t0:.0f}s parse failures {failures} '
|
||||
f'last: {cid} fit {cache[k]["fit"]:.2f} "{cache[k]["situation"][:60]}"', flush=True)
|
||||
print(f'done: {done} judged this session in {time.time() - t0:.0f}s; parse failures {failures}')
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"D01": {"label": 1, "name": "a heated disagreement over whose view is right"},
|
||||
"D02": {"label": 0, "name": ""},
|
||||
"D03": {"label": 1, "name": "money and marriage: wealthy suitors, inheritances"},
|
||||
"D04": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"D05": {"label": 1, "name": "struggling artists and money"},
|
||||
"D06": {"label": 1, "name": "writers and editors"},
|
||||
"D07": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"D08": {"label": 1, "name": "a relative ill, missing or in trouble"},
|
||||
"D09": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"D10": {"label": 0, "name": ""},
|
||||
"D11": {"label": 1, "name": "working girls: jobs, wages and standing"},
|
||||
"D12": {"label": 1, "name": "a woman at the centre of rival admirers"},
|
||||
"D13": {"label": 2, "name": "a run-in with the police"},
|
||||
"D14": {"label": 1, "name": "cabs, traffic and accidents in the street"},
|
||||
"D15": {"label": 2, "name": "landlord or housekeeper against tenant: rooms and eviction"},
|
||||
"D16": {"label": 2, "name": "someone coerces someone with a threat"},
|
||||
"D17": {"label": 0, "name": ""},
|
||||
"D18": {"label": 0, "name": ""},
|
||||
"D19": {"label": 0, "name": ""},
|
||||
"D20": {"label": 1, "name": "grief and loneliness at home"},
|
||||
"D21": {"label": 1, "name": "a reunion or first meeting"},
|
||||
"D22": {"label": 1, "name": "romantic pursuit and confrontation"},
|
||||
"D23": {"label": 1, "name": "artist and patron or model"},
|
||||
"D24": {"label": 0, "name": ""},
|
||||
"D25": {"label": 1, "name": "what to do with money, and whose advice to take"},
|
||||
"D26": {"label": 0, "name": ""},
|
||||
"D27": {"label": 1, "name": "climbing socially by appearances"},
|
||||
"D28": {"label": 0, "name": ""},
|
||||
"D29": {"label": 1, "name": "searching for a missing person, demanding information"},
|
||||
"D30": {"label": 2, "name": "a run-in with the police"},
|
||||
"D31": {"label": 0, "name": ""},
|
||||
"D32": {"label": 1, "name": "a meal given as patronage"},
|
||||
"D33": {"label": 1, "name": "a secret past revealed in a romance"},
|
||||
"D34": {"label": 0, "name": ""},
|
||||
"D35": {"label": 0, "name": ""},
|
||||
"D36": {"label": 0, "name": ""},
|
||||
"D37": {"label": 1, "name": "a public quarrel with a shopkeeper, driver or official"},
|
||||
"D38": {"label": 0, "name": ""},
|
||||
"D39": {"label": 0, "name": ""},
|
||||
"D40": {"label": 0, "name": ""}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"R001": {"label": 1, "name": "an inheritance or fortune and what it obliges"},
|
||||
"R002": {"label": 0, "name": ""},
|
||||
"R003": {"label": 1, "name": "someone coerces someone with a threat"},
|
||||
"R004": {"label": 1, "name": "a run-in with the police"},
|
||||
"R005": {"label": 0, "name": ""},
|
||||
"R006": {"label": 1, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"R007": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"R008": {"label": 1, "name": "courtship"},
|
||||
"R009": {"label": 1, "name": "coercion over money: threats, bribes, demands for payment"},
|
||||
"R010": {"label": 1, "name": "courtship"},
|
||||
"R011": {"label": 2, "name": "money and marriage: wealthy suitors, inheritances"},
|
||||
"R012": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"R013": {"label": 0, "name": ""},
|
||||
"R014": {"label": 1, "name": "drinking in the saloon"},
|
||||
"R015": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"R016": {"label": 0, "name": ""},
|
||||
"R017": {"label": 1, "name": "money owed and repayment"},
|
||||
"R018": {"label": 1, "name": "trying to impress or win someone's favour"},
|
||||
"R019": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"R020": {"label": 2, "name": "someone coerces someone with a threat"},
|
||||
"R021": {"label": 0, "name": ""}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"T01": {"label": 0, "name": ""},
|
||||
"T02": {"label": 0, "name": ""},
|
||||
"T03": {"label": 1, "name": "money owed and repayment"},
|
||||
"T04": {"label": 1, "name": "confrontations in the park and on the street"},
|
||||
"T05": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"T06": {"label": 1, "name": "actresses and the stage"},
|
||||
"T07": {"label": 1, "name": "courtship"},
|
||||
"T08": {"label": 0, "name": ""},
|
||||
"T09": {"label": 0, "name": ""},
|
||||
"T10": {"label": 0, "name": ""},
|
||||
"T11": {"label": 0, "name": ""},
|
||||
"T12": {"label": 1, "name": "hosting in a restaurant or bar"},
|
||||
"T13": {"label": 0, "name": ""},
|
||||
"T14": {"label": 0, "name": ""},
|
||||
"T15": {"label": 1, "name": "meals, drinks and who pays"},
|
||||
"T16": {"label": 2, "name": "a newcomer seeks acceptance in a social circle"},
|
||||
"T17": {"label": 2, "name": "a proposal or engagement, accepted or refused"},
|
||||
"T18": {"label": 0, "name": ""},
|
||||
"T19": {"label": 1, "name": "shop and service work and standing"},
|
||||
"T20": {"label": 1, "name": "sizing up a new town"},
|
||||
"T21": {"label": 0, "name": ""},
|
||||
"T22": {"label": 0, "name": ""},
|
||||
"T23": {"label": 0, "name": ""},
|
||||
"T24": {"label": 0, "name": ""},
|
||||
"T25": {"label": 1, "name": "trying to impress or win someone's favour"},
|
||||
"T26": {"label": 0, "name": ""},
|
||||
"T27": {"label": 1, "name": "cabs, traffic and accidents in the street"},
|
||||
"T28": {"label": 0, "name": ""},
|
||||
"T29": {"label": 0, "name": ""},
|
||||
"T30": {"label": 2, "name": "a run-in with the police"},
|
||||
"T31": {"label": 0, "name": ""},
|
||||
"T32": {"label": 1, "name": "illness, loss and a missing relative"},
|
||||
"T33": {"label": 1, "name": "a brawl or showdown"},
|
||||
"T34": {"label": 1, "name": "drinking in the saloon"},
|
||||
"T35": {"label": 2, "name": "coercion over money: threats, bribes, demands for payment"},
|
||||
"T36": {"label": 0, "name": ""},
|
||||
"T37": {"label": 1, "name": "a reunion or first meeting"},
|
||||
"T38": {"label": 0, "name": ""},
|
||||
"T39": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"T40": {"label": 0, "name": ""},
|
||||
"T41": {"label": 1, "name": "struggling artists and money"}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"X001": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"X002": {"label": 1, "name": "struggling artists and money"},
|
||||
"X003": {"label": 0, "name": ""},
|
||||
"X004": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"X005": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"X006": {"label": 1, "name": "a meal given as patronage"},
|
||||
"X007": {"label": 0, "name": ""},
|
||||
"X008": {"label": 2, "name": "a reunion or first meeting"},
|
||||
"X009": {"label": 1, "name": "a proposal or engagement, accepted or refused"},
|
||||
"X010": {"label": 0, "name": ""},
|
||||
"X011": {"label": 1, "name": "drinking in the saloon"},
|
||||
"X012": {"label": 0, "name": ""},
|
||||
"X013": {"label": 1, "name": "what to do with money, and whose advice to take"},
|
||||
"X014": {"label": 0, "name": ""},
|
||||
"X015": {"label": 0, "name": ""},
|
||||
"X016": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"X017": {"label": 0, "name": ""},
|
||||
"X018": {"label": 1, "name": "shop and service work and standing"},
|
||||
"X019": {"label": 1, "name": "a public quarrel with a shopkeeper, driver or official"},
|
||||
"X020": {"label": 0, "name": ""},
|
||||
"X021": {"label": 1, "name": "courtship"},
|
||||
"X022": {"label": 1, "name": "working girls: jobs, wages and standing"},
|
||||
"X023": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"X024": {"label": 0, "name": ""},
|
||||
"X025": {"label": 0, "name": ""},
|
||||
"X026": {"label": 0, "name": ""},
|
||||
"X027": {"label": 0, "name": ""},
|
||||
"X028": {"label": 0, "name": ""},
|
||||
"X029": {"label": 0, "name": ""},
|
||||
"X030": {"label": 1, "name": "a brawl or showdown"},
|
||||
"X031": {"label": 1, "name": "actresses and the stage"},
|
||||
"X032": {"label": 0, "name": ""},
|
||||
"X033": {"label": 0, "name": ""},
|
||||
"X034": {"label": 1, "name": "struggling artists and money"},
|
||||
"X035": {"label": 0, "name": ""},
|
||||
"X036": {"label": 1, "name": "illness, loss and a missing relative"},
|
||||
"X037": {"label": 0, "name": ""},
|
||||
"X038": {"label": 0, "name": ""},
|
||||
"X039": {"label": 1, "name": "working girls: jobs, wages and standing"},
|
||||
"X040": {"label": 0, "name": ""},
|
||||
"X041": {"label": 1, "name": "money and marriage: wealthy suitors, inheritances"},
|
||||
"X042": {"label": 2, "name": "a public quarrel with a shopkeeper, driver or official"},
|
||||
"X043": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"X044": {"label": 2, "name": "landlord or housekeeper against tenant: rooms and eviction"},
|
||||
"X045": {"label": 1, "name": "struggling artists and money"},
|
||||
"X046": {"label": 0, "name": ""},
|
||||
"X047": {"label": 1, "name": "family pressures over work and marriage"},
|
||||
"X048": {"label": 1, "name": "money owed and repayment"},
|
||||
"X049": {"label": 0, "name": ""},
|
||||
"X050": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"X051": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"X052": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"X053": {"label": 0, "name": ""},
|
||||
"X054": {"label": 0, "name": ""},
|
||||
"X055": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"X056": {"label": 1, "name": "trying to impress or win someone's favour"},
|
||||
"X057": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"X058": {"label": 0, "name": ""},
|
||||
"X059": {"label": 1, "name": "a proposal or engagement, accepted or refused"},
|
||||
"X060": {"label": 0, "name": ""},
|
||||
"X061": {"label": 0, "name": ""},
|
||||
"X062": {"label": 0, "name": ""},
|
||||
"X063": {"label": 0, "name": ""},
|
||||
"X064": {"label": 1, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"X065": {"label": 1, "name": "a heated disagreement over whose view is right"},
|
||||
"X066": {"label": 0, "name": ""},
|
||||
"X067": {"label": 0, "name": ""},
|
||||
"X068": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"X069": {"label": 1, "name": "trying to impress or win someone's favour"},
|
||||
"X070": {"label": 1, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"X071": {"label": 0, "name": ""},
|
||||
"X072": {"label": 0, "name": ""},
|
||||
"X073": {"label": 0, "name": ""}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"Y001": {"label": 0, "name": ""},
|
||||
"Y002": {"label": 0, "name": ""},
|
||||
"Y003": {"label": 1, "name": "struggling artists and money"},
|
||||
"Y004": {"label": 1, "name": "landlord or housekeeper against tenant: rooms and eviction"},
|
||||
"Y005": {"label": 0, "name": ""},
|
||||
"Y006": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"Y007": {"label": 0, "name": ""},
|
||||
"Y008": {"label": 0, "name": ""},
|
||||
"Y009": {"label": 0, "name": ""},
|
||||
"Y010": {"label": 1, "name": "struggling artists and money"},
|
||||
"Y011": {"label": 0, "name": ""},
|
||||
"Y012": {"label": 0, "name": ""},
|
||||
"Y013": {"label": 1, "name": "struggling artists and money"},
|
||||
"Y014": {"label": 0, "name": ""},
|
||||
"Y015": {"label": 0, "name": ""},
|
||||
"Y016": {"label": 1, "name": "a proposal or engagement, accepted or refused"},
|
||||
"Y017": {"label": 1, "name": "an invitation to dine"},
|
||||
"Y018": {"label": 0, "name": ""},
|
||||
"Y019": {"label": 0, "name": ""},
|
||||
"Y020": {"label": 0, "name": ""},
|
||||
"Y021": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"Y022": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"Y023": {"label": 1, "name": "working girls: jobs, wages and standing"},
|
||||
"Y024": {"label": 2, "name": "someone coerces someone with a threat"},
|
||||
"Y025": {"label": 0, "name": ""},
|
||||
"Y026": {"label": 0, "name": ""},
|
||||
"Y027": {"label": 0, "name": ""},
|
||||
"Y028": {"label": 1, "name": "actresses and the stage"},
|
||||
"Y029": {"label": 0, "name": ""},
|
||||
"Y030": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"Y031": {"label": 0, "name": ""},
|
||||
"Y032": {"label": 1, "name": "a run-in with the police"},
|
||||
"Y033": {"label": 0, "name": ""},
|
||||
"Y034": {"label": 0, "name": ""},
|
||||
"Y035": {"label": 0, "name": ""},
|
||||
"Y036": {"label": 0, "name": ""},
|
||||
"Y037": {"label": 0, "name": ""},
|
||||
"Y038": {"label": 1, "name": "someone coerces someone with a threat"},
|
||||
"Y039": {"label": 0, "name": ""},
|
||||
"Y040": {"label": 1, "name": "who holds sway in a dining room or hotel"},
|
||||
"Y041": {"label": 0, "name": ""},
|
||||
"Y042": {"label": 0, "name": ""},
|
||||
"Y043": {"label": 1, "name": "a run-in with the police"},
|
||||
"Y044": {"label": 0, "name": ""},
|
||||
"Y045": {"label": 0, "name": ""},
|
||||
"Y046": {"label": 0, "name": ""},
|
||||
"Y047": {"label": 2, "name": "working girls: jobs, wages and standing"},
|
||||
"Y048": {"label": 0, "name": ""},
|
||||
"Y049": {"label": 2, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"Y050": {"label": 0, "name": ""},
|
||||
"Y051": {"label": 0, "name": ""},
|
||||
"Y052": {"label": 2, "name": "an invitation to dine"},
|
||||
"Y053": {"label": 1, "name": "a brawl or showdown"},
|
||||
"Y054": {"label": 0, "name": ""},
|
||||
"Y055": {"label": 0, "name": ""},
|
||||
"Y056": {"label": 0, "name": ""},
|
||||
"Y057": {"label": 0, "name": ""},
|
||||
"Y058": {"label": 0, "name": ""},
|
||||
"Y059": {"label": 0, "name": ""},
|
||||
"Y060": {"label": 0, "name": ""},
|
||||
"Y061": {"label": 0, "name": ""},
|
||||
"Y062": {"label": 1, "name": "a spouse schemes against or confronts a spouse over infidelity"},
|
||||
"Y063": {"label": 0, "name": ""},
|
||||
"Y064": {"label": 1, "name": "a proposal or engagement, accepted or refused"},
|
||||
"Y065": {"label": 0, "name": ""},
|
||||
"Y066": {"label": 0, "name": ""},
|
||||
"Y067": {"label": 1, "name": "illness, loss and a missing relative"},
|
||||
"Y068": {"label": 0, "name": ""},
|
||||
"Y069": {"label": 1, "name": "trying to impress or win someone's favour"},
|
||||
"Y070": {"label": 0, "name": ""},
|
||||
"Y071": {"label": 1, "name": "schemes, cons and theft over money"},
|
||||
"Y072": {"label": 0, "name": ""},
|
||||
"Y073": {"label": 1, "name": "writers and editors"},
|
||||
"Y074": {"label": 1, "name": "working girls: jobs, wages and standing"},
|
||||
"Y075": {"label": 0, "name": ""}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Dump candidate clusters to blind label sheets (SELECTION.md, protocol steps 1-3).
|
||||
|
||||
python3 make_sheets.py
|
||||
|
||||
Writes, for development (seed 11) and test (seed 12):
|
||||
candidates-<set>.json cluster id -> member chunk indices (what signals are computed on)
|
||||
sheet-<set>.txt per cluster, member summaries only, shuffled; no titles, no scores
|
||||
labels-<set>.json an empty template to fill in: {id: {"label": null, "name": ""}}
|
||||
|
||||
Cluster ids are assigned in a shuffled order so position carries no information.
|
||||
"""
|
||||
import json, pathlib, random, sys
|
||||
|
||||
K = 60
|
||||
SETS = {'dev': 11, 'test': 12}
|
||||
|
||||
sys.argv = ['measure.py', 'summary_embeddings.json']
|
||||
exec(pathlib.Path('measure.py').read_text(encoding='utf-8').split("print(f'{path.name}")[0])
|
||||
|
||||
summaries = json.loads(pathlib.Path('summaries_clean.json').read_text(encoding='utf-8'))['summaries']
|
||||
text = {x['chunk']: x['summary'] for x in summaries}
|
||||
|
||||
for name, seed in SETS.items():
|
||||
cands = []
|
||||
for c in kmeans(K, seed):
|
||||
size, nst, dom, _ = describe(c)
|
||||
if size >= MIN_SIZE and nst >= MIN_STORIES and dom <= MAX_DOMINANT:
|
||||
cands.append(sorted(c))
|
||||
rnd = random.Random(seed * 1000 + 7)
|
||||
rnd.shuffle(cands)
|
||||
prefix = name[0].upper()
|
||||
ids = {f'{prefix}{i + 1:02d}': c for i, c in enumerate(cands)}
|
||||
|
||||
pathlib.Path(f'candidates-{name}.json').write_text(json.dumps(ids, indent=1))
|
||||
lines = []
|
||||
for cid, members in ids.items():
|
||||
shown = members[:]
|
||||
rnd.shuffle(shown)
|
||||
lines.append(f'=== {cid}')
|
||||
lines.extend(f' - {text[i]}' for i in shown)
|
||||
lines.append('')
|
||||
pathlib.Path(f'sheet-{name}.txt').write_text('\n'.join(lines), encoding='utf-8')
|
||||
labels = pathlib.Path(f'labels-{name}.json')
|
||||
if not labels.exists():
|
||||
labels.write_text(json.dumps({cid: {'label': None, 'name': ''} for cid in ids}, indent=1))
|
||||
print(f'{name}: seed {seed}, {len(ids)} candidate clusters, {sum(map(len, ids.values()))} scenes')
|
||||
@@ -0,0 +1,11 @@
|
||||
embeddings.json: 838 chunks from 94 stories, seeds (11, 12, 13)
|
||||
|
||||
k sized>=5 median dom recurring coverage tight rec tight cov
|
||||
----------------------------------------------------------------------
|
||||
25 25 0.25 21 93% 9 29%
|
||||
40 39 0.36 21 71% 5 9%
|
||||
60 55 0.57 20 55% 3 2%
|
||||
100 78 0.67 16 30% 0 0%
|
||||
|
||||
recurring = size>=5, stories>=4, dominant share<=40%; tight = coherence at or above this file's median cluster. Judge at k>=60. Wrote embeddings.measure.json
|
||||
exit 0
|
||||
@@ -0,0 +1,11 @@
|
||||
summary_embeddings.json: 838 chunks from 94 stories, seeds (11, 12, 13)
|
||||
|
||||
k sized>=5 median dom recurring coverage tight rec tight cov
|
||||
----------------------------------------------------------------------
|
||||
25 25 0.14 23 97% 11 40%
|
||||
40 36 0.17 32 95% 14 36%
|
||||
60 51 0.22 41 87% 18 26%
|
||||
100 70 0.28 50 73% 20 19%
|
||||
|
||||
recurring = size>=5, stories>=4, dominant share<=40%; tight = coherence at or above this file's median cluster. Judge at k>=60. Wrote summary_embeddings.measure.json
|
||||
exit 0
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Do the clusters in an embedding file capture recurring SITUATIONS?
|
||||
|
||||
python3 measure.py embeddings.json # prose embeddings (the 2026-09-10 baseline)
|
||||
python3 measure.py summary_embeddings.json # summarise-then-embed (the fix under test)
|
||||
|
||||
The 2026-09-10 probe counted distinct stories per cluster and was fooled: a
|
||||
cluster reporting 5 stories was routinely 60-79% ONE story plus stragglers.
|
||||
So this reports concentration, not just spread.
|
||||
|
||||
dominant share = largest single-story count / cluster size
|
||||
recurring = size >= MIN_SIZE, >= MIN_STORIES distinct stories,
|
||||
and dominant share <= MAX_DOMINANT
|
||||
|
||||
Pass mark: see DESIGN.md, "Assumptions under test". Judged at k = 60 and
|
||||
k = 100 only. Results are the median over SEEDS, so one lucky (or unlucky)
|
||||
initialisation cannot decide the test -- the 2026-09-10 "2% coverage" was
|
||||
seed 11 alone; seeds 12 and 13 give 8-10%.
|
||||
|
||||
Pure Python -- no numpy on this machine. Only chunk indices that have an
|
||||
embedding are used, so a partial (pilot) file is fine.
|
||||
"""
|
||||
import json, math, pathlib, random, statistics, sys
|
||||
|
||||
KS = (25, 40, 60, 100)
|
||||
SEEDS = (11, 12, 13)
|
||||
MIN_SIZE, MIN_STORIES, MAX_DOMINANT = 5, 4, 0.40
|
||||
|
||||
path = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else 'embeddings.json')
|
||||
chunks = json.loads(pathlib.Path('chunks.json').read_text(encoding='utf-8'))
|
||||
raw = json.loads(path.read_text(encoding='utf-8'))
|
||||
vecs = raw['vectors'] if isinstance(raw, dict) else raw
|
||||
n = min(len(chunks), len(vecs))
|
||||
|
||||
def norm(v):
|
||||
m = math.sqrt(sum(x * x for x in v)) or 1.0
|
||||
return [x / m for x in v]
|
||||
V = [norm(v) for v in vecs[:n]]
|
||||
dim = len(V[0])
|
||||
story = [chunks[i]['story'] for i in range(n)]
|
||||
|
||||
def dot(a, b):
|
||||
s = 0.0
|
||||
for x, y in zip(a, b): s += x * y
|
||||
return s
|
||||
|
||||
def kmeans(K, seed, iters=15):
|
||||
rnd = random.Random(seed)
|
||||
cent = [V[i] for i in rnd.sample(range(n), K)]
|
||||
assign = [-1] * n
|
||||
for _ in range(iters):
|
||||
moved = 0
|
||||
for i, v in enumerate(V):
|
||||
best, bs = 0, -2.0
|
||||
for k, c in enumerate(cent):
|
||||
s = dot(v, c)
|
||||
if s > bs: bs, best = s, k
|
||||
if assign[i] != best: assign[i] = best; moved += 1
|
||||
if moved == 0: break
|
||||
groups = {}
|
||||
for i, k in enumerate(assign): groups.setdefault(k, []).append(i)
|
||||
for k, mem in groups.items():
|
||||
cent[k] = norm([sum(V[i][d] for i in mem) / len(mem) for d in range(dim)])
|
||||
groups = {}
|
||||
for i, k in enumerate(assign): groups.setdefault(k, []).append(i)
|
||||
return list(groups.values())
|
||||
|
||||
def describe(cluster):
|
||||
counts = {}
|
||||
for i in cluster: counts[story[i]] = counts.get(story[i], 0) + 1
|
||||
cen = norm([sum(V[i][d] for i in cluster) / len(cluster) for d in range(dim)])
|
||||
coh = sum(dot(V[i], cen) for i in cluster) / len(cluster)
|
||||
return len(cluster), len(counts), max(counts.values()) / len(cluster), coh
|
||||
|
||||
# "Tight" is relative to the file's own median cluster coherence. An absolute
|
||||
# cutoff (the 2026-09-10 probe used 0.83) does not transfer between embedding
|
||||
# files: one-sentence summaries and 300-word scenes sit at different cosines.
|
||||
# Without it, loose clusters held together by prose register count as recurring.
|
||||
print(f'{path.name}: {n} chunks from {len(set(story))} stories, seeds {SEEDS}\n')
|
||||
print(f'{"k":>4} {"sized>=%d" % MIN_SIZE:>9} {"median dom":>11} {"recurring":>10} {"coverage":>9} {"tight rec":>10} {"tight cov":>10}')
|
||||
print('-' * 70)
|
||||
report = {'file': path.name, 'chunks': n, 'rows': []}
|
||||
for K in KS:
|
||||
if K * 2 > n: continue
|
||||
per_seed = []
|
||||
for seed in SEEDS:
|
||||
rows = [describe(c) for c in kmeans(K, seed)]
|
||||
sized = [r for r in rows if r[0] >= MIN_SIZE]
|
||||
rec = [r for r in sized if r[1] >= MIN_STORIES and r[2] <= MAX_DOMINANT]
|
||||
cut = statistics.median(r[3] for r in sized) if sized else 1.0
|
||||
tight = [r for r in rec if r[3] >= cut]
|
||||
per_seed.append((len(sized),
|
||||
statistics.median(r[2] for r in sized) if sized else 1.0,
|
||||
len(rec), sum(r[0] for r in rec) / n,
|
||||
len(tight), sum(r[0] for r in tight) / n))
|
||||
med = [statistics.median(s[j] for s in per_seed) for j in range(6)]
|
||||
print(f'{K:>4} {med[0]:>9.0f} {med[1]:>11.2f} {med[2]:>10.0f} {med[3]:>8.0%} {med[4]:>10.0f} {med[5]:>9.0%}')
|
||||
report['rows'].append({'k': K, 'sized': med[0], 'median_dominant': med[1],
|
||||
'recurring': med[2], 'coverage': med[3],
|
||||
'tight_recurring': med[4], 'tight_coverage': med[5]})
|
||||
out = path.with_name(path.stem + '.measure.json')
|
||||
out.write_text(json.dumps(report, indent=1))
|
||||
print(f'\nrecurring = size>={MIN_SIZE}, stories>={MIN_STORIES}, dominant share<={MAX_DOMINANT:.0%}; '
|
||||
f'tight = coherence at or above this file\'s median cluster. Judge at k>=60. Wrote {out.name}')
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Near-duplicate situations among accepted clusters (SELECTION.md protocol step 6).
|
||||
|
||||
python3 merge_dupes.py dev # list every accepted pair's centroid cosine, with label names
|
||||
python3 merge_dupes.py test 0.93 # merge pairs at or above a threshold chosen on dev
|
||||
|
||||
Centroids are the mean unit summary vector of each cluster. On dev the threshold is chosen by
|
||||
reading: the lowest cosine that still separates pairs whose names say "same situation" from pairs
|
||||
that do not. On test it is applied as given, and each merge is reported with both names so it can
|
||||
be checked.
|
||||
"""
|
||||
import json, pathlib, sys
|
||||
|
||||
set_name = sys.argv[1]
|
||||
threshold = float(sys.argv[2]) if len(sys.argv) > 2 else None
|
||||
|
||||
sys.argv = ['measure.py', 'summary_embeddings.json']
|
||||
exec(pathlib.Path('measure.py').read_text(encoding='utf-8').split("print(f'{path.name}")[0])
|
||||
sys.argv = ['select_rule.py']
|
||||
exec(pathlib.Path('select_rule.py').read_text(encoding='utf-8').split("mode = sys.argv")[0])
|
||||
|
||||
candidates = json.loads(pathlib.Path(f'candidates-{set_name}.json').read_text())
|
||||
frozen = json.loads(pathlib.Path('rule-frozen.json').read_text())
|
||||
terms = [tuple(t) for t in frozen['terms']]
|
||||
rows = {r['id']: r for r in load(set_name)}
|
||||
accepted = [cid for cid, r in rows.items() if all(passes(r, t) for t in terms)]
|
||||
|
||||
cent = {cid: norm([sum(V[i][d] for i in candidates[cid]) / len(candidates[cid]) for d in range(dim)])
|
||||
for cid in accepted}
|
||||
pairs = sorted(((dot(cent[a], cent[b]), a, b) for x, a in enumerate(accepted) for b in accepted[x + 1:]),
|
||||
reverse=True)
|
||||
|
||||
print(f'{set_name}: {len(accepted)} accepted clusters, {len(pairs)} pairs\n')
|
||||
for cos, a, b in pairs[:25]:
|
||||
mark = ' MERGE' if threshold is not None and cos >= threshold else ''
|
||||
print(f"{cos:.4f} {a}({rows[a]['label']}) {rows[a]['name'] or '-'}\n {b}({rows[b]['label']}) {rows[b]['name'] or '-'}{mark}")
|
||||
if threshold is not None:
|
||||
merged = [(a, b) for cos, a, b in pairs if cos >= threshold]
|
||||
print(f'\nthreshold {threshold}: {len(merged)} merges')
|
||||
@@ -0,0 +1,49 @@
|
||||
"""The one place that talks to an inference host.
|
||||
|
||||
The host is shared with other development work, so this is deliberately
|
||||
gentle: one request at a time, a fixed pause between requests, and a short
|
||||
keep_alive so the model is not left occupying the GPU after a run.
|
||||
|
||||
Configuration comes from the environment, never from a file in this tree:
|
||||
STP_OLLAMA base URL of an Ollama server, e.g. http://inference.lan:11434
|
||||
STP_PAUSE seconds between requests (default 0.5)
|
||||
"""
|
||||
import json, os, sys, time, urllib.request
|
||||
|
||||
BASE = os.environ.get('STP_OLLAMA', '').rstrip('/')
|
||||
PAUSE = float(os.environ.get('STP_PAUSE', '0.5'))
|
||||
KEEP_ALIVE = '2m'
|
||||
|
||||
if not BASE:
|
||||
sys.exit('STP_OLLAMA is not set -- refusing to guess an inference host')
|
||||
|
||||
def post(route, body, timeout=300):
|
||||
req = urllib.request.Request(BASE + route, data=json.dumps(body).encode(),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
for attempt in range(4):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
result = json.load(r)
|
||||
time.sleep(PAUSE)
|
||||
return result
|
||||
except Exception as e:
|
||||
if attempt == 3: raise
|
||||
print(f' retry {attempt + 1}: {e}', flush=True)
|
||||
time.sleep(5 * (attempt + 1))
|
||||
|
||||
def embed(model, texts):
|
||||
return post('/api/embed', {'model': model, 'input': texts, 'keep_alive': KEEP_ALIVE})['embeddings']
|
||||
|
||||
def chat(model, system, user, num_ctx=2048, num_predict=80, fmt=None, think=None):
|
||||
# num_ctx is pinned small on purpose: the 2026-09-10 hang was a 16k-context
|
||||
# variant prefilling on CPU. A scene is ~450 tokens with the prompt.
|
||||
body = {'model': model, 'stream': False, 'keep_alive': KEEP_ALIVE,
|
||||
'messages': [{'role': 'system', 'content': system},
|
||||
{'role': 'user', 'content': user}],
|
||||
'options': {'num_ctx': num_ctx, 'num_predict': num_predict, 'temperature': 0}}
|
||||
if fmt: body['format'] = fmt
|
||||
# Thinking models (qwen3) otherwise spend the token budget in a separate thinking
|
||||
# channel and return empty content.
|
||||
if think is not None: body['think'] = think
|
||||
d = post('/api/chat', body)
|
||||
return d['message']['content'].strip(), d.get('eval_duration', 0), d.get('prompt_eval_duration', 0)
|
||||
@@ -0,0 +1,351 @@
|
||||
{
|
||||
"model": "qwen3:14b",
|
||||
"prompt": "You describe the predicament of the main person in a passage of fiction, in ONE sentence.\n\nA predicament is what that person is up against and what they want out of it: the kind of trouble or opportunity that could happen to anyone, in any line of work, in any town.\n\nRules:\n- Exactly one sentence, at most 20 words.\n- Leave out everything that ties it to this story: no names, no jobs or professions, no places, no businesses, and no objects that only belong to this story.\n- Say what the person is up against, and what they want.\n- Do not describe the writing, the mood, or who is telling the story.\n- Output only the sentence.",
|
||||
"summaries": [
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"summary": "A man struggles to understand the overwhelming complexity of a city's collective voice, seeking clarity in a world filled with individual sounds but no unified message.",
|
||||
"model_output": "A man struggles to understand the overwhelming complexity of a city's collective voice, seeking clarity in a world filled with individual sounds but no unified message.",
|
||||
"raw": "A man struggles to understand the overwhelming complexity of a city's collective voice, seeking clarity in a world filled with individual sounds but no unified message.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"summary": "The person is trying to understand the city's hidden message, hoping to find a unified voice that reveals its true nature.",
|
||||
"model_output": "The person is trying to understand the city's hidden message, hoping to find a unified voice that reveals its true nature.",
|
||||
"raw": "The person is trying to understand the city's hidden message, hoping to find a unified voice that reveals its true nature.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 2,
|
||||
"story": 0,
|
||||
"summary": "A man determined to uncover the unique voice of a city faces the challenge of distinguishing its true character from the distractions and distractions of daily life.",
|
||||
"model_output": "A man determined to uncover the unique voice of a city faces the challenge of distinguishing its true character from the distractions and distractions of daily life.",
|
||||
"raw": "A man determined to uncover the unique voice of a city faces the challenge of distinguishing its true character from the distractions and distractions of daily life.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"summary": "A man tries to uncover the city's hidden voice through a cop, only to find that the cop's personal life and indifference to the city's soul leave him with no real answers.",
|
||||
"model_output": "A man tries to uncover the city's hidden voice through a cop, only to find that the cop's personal life and indifference to the city's soul leave him with no real answers.",
|
||||
"raw": "A man tries to uncover the city's hidden voice through a cop, only to find that the cop's personal life and indifference to the city's soul leave him with no real answers.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"summary": "A man seeks to capture the essence of a city's soul through a unique blend of its sounds, hoping to create a single, powerful vocalization that represents its spirit.",
|
||||
"model_output": "A man seeks to capture the essence of a city's soul through a unique blend of its sounds, hoping to create a single, powerful vocalization that represents its spirit.",
|
||||
"raw": "A man seeks to capture the essence of a city's soul through a unique blend of its sounds, hoping to create a single, powerful vocalization that represents its spirit.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 5,
|
||||
"story": 0,
|
||||
"summary": "A man seeks understanding from a city that refuses to speak, desperate to uncover a hidden truth that eludes everyone else.",
|
||||
"model_output": "A man seeks understanding from a city that refuses to speak, desperate to uncover a hidden truth that eludes everyone else.",
|
||||
"raw": "A man seeks understanding from a city that refuses to speak, desperate to uncover a hidden truth that eludes everyone else.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"summary": "Someone and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weight of the city's presence.",
|
||||
"model_output": "Aurelia and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weight of the city's presence.",
|
||||
"raw": "Aurelia and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weight of the city's presence.",
|
||||
"names_removed": [
|
||||
"Aurelia"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 1,
|
||||
"summary": "Someone must navigate the harsh realities of poverty, love, and war all at once in the crowded, unforgiving city.",
|
||||
"model_output": "John Hopkins must navigate the harsh realities of poverty, love, and war all at once in the crowded, unforgiving city.",
|
||||
"raw": "John Hopkins must navigate the harsh realities of poverty, love, and war all at once in the crowded, unforgiving city.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 8,
|
||||
"story": 1,
|
||||
"summary": "Someone must navigate the chaotic unpredictability of city life while clinging to the comfort of her familiar routines.",
|
||||
"model_output": "Mrs. Hopkins must navigate the chaotic unpredictability of city life while clinging to the comfort of her familiar routines.",
|
||||
"raw": "Mrs. Hopkins must navigate the chaotic unpredictability of city life while clinging to the comfort of her familiar routines.",
|
||||
"names_removed": [
|
||||
"Mrs.",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 9,
|
||||
"story": 1,
|
||||
"summary": "Someone longs for connection but is trapped in a life of monotony and shallow conversation.",
|
||||
"model_output": "John Hopkins longs for connection but is trapped in a life of monotony and shallow conversation.",
|
||||
"raw": "John Hopkins longs for connection but is trapped in a life of monotony and shallow conversation.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 1,
|
||||
"summary": "A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confrontation with a shopkeeper who sees his actions as proof of humanity's corruption.",
|
||||
"model_output": "A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confrontation with a shopkeeper who sees his actions as proof of humanity's corruption.",
|
||||
"raw": "A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confrontation with a shopkeeper who sees his actions as proof of humanity's corruption.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 1,
|
||||
"summary": "A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but must decide whether to trust his mysterious rescuer.",
|
||||
"model_output": "A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but must decide whether to trust his mysterious rescuer.",
|
||||
"raw": "A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but must decide whether to trust his mysterious rescuer.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 1,
|
||||
"summary": "A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with courage and discretion.",
|
||||
"model_output": "A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with courage and discretion.",
|
||||
"raw": "A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with courage and discretion.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 1,
|
||||
"summary": "Someone must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety to do so.",
|
||||
"model_output": "John Hopkins must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety to do so.",
|
||||
"raw": "John Hopkins must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety to do so.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 14,
|
||||
"story": 1,
|
||||
"summary": "A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circle.",
|
||||
"model_output": "A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circle.",
|
||||
"raw": "A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circle.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 15,
|
||||
"story": 2,
|
||||
"summary": "Someone, trapped in a monotonous job she despises, longs for a life that offers more than just the dull routine of selling gloves.",
|
||||
"model_output": "Masie, trapped in a monotonous job she despises, longs for a life that offers more than just the dull routine of selling gloves.",
|
||||
"raw": "Masie, trapped in a monotonous job she despises, longs for a life that offers more than just the dull routine of selling gloves.",
|
||||
"names_removed": [
|
||||
"Masie"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 16,
|
||||
"story": 2,
|
||||
"summary": "A man finds himself trapped in an awkward encounter with a shopgirl whose icy beauty challenges his composure, forcing him to navigate the delicate line between professional decorum and unexpected attraction.",
|
||||
"model_output": "A man finds himself trapped in an awkward encounter with a shopgirl whose icy beauty challenges his composure, forcing him to navigate the delicate line between professional decorum and unexpected attraction.",
|
||||
"raw": "A man finds himself trapped in an awkward encounter with a shopgirl whose icy beauty challenges his composure, forcing him to navigate the delicate line between professional decorum and unexpected attraction.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 17,
|
||||
"story": 2,
|
||||
"summary": "A man struggling with his pride and social conventions finds himself awkwardly drawn to a shopgirl, determined to win her favor despite his usual confidence.",
|
||||
"model_output": "A man struggling with his pride and social conventions finds himself awkwardly drawn to a shopgirl, determined to win her favor despite his usual confidence.",
|
||||
"raw": "A man struggling with his pride and social conventions finds himself awkwardly drawn to a shopgirl, determined to win her favor despite his usual confidence.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 18,
|
||||
"story": 2,
|
||||
"summary": "A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to explore new possibilities without disappointing her family or losing her sense of self.",
|
||||
"model_output": "A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to explore new possibilities without disappointing her family or losing her sense of self.",
|
||||
"raw": "A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to explore new possibilities without disappointing her family or losing her sense of self.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 19,
|
||||
"story": 2,
|
||||
"summary": "A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life struggles to navigate the uncertain path of love.",
|
||||
"model_output": "A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life struggles to navigate the uncertain path of love.",
|
||||
"raw": "A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life struggles to navigate the uncertain path of love.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 20,
|
||||
"story": 2,
|
||||
"summary": "A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his words and mocks his desperate, insincere pursuit.",
|
||||
"model_output": "A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his words and mocks his desperate, insincere pursuit.",
|
||||
"raw": "A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his words and mocks his desperate, insincere pursuit.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 21,
|
||||
"story": 2,
|
||||
"summary": "A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain journey with him.",
|
||||
"model_output": "A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain journey with him.",
|
||||
"raw": "A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain journey with him.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 22,
|
||||
"story": 2,
|
||||
"summary": "A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connection without giving in to the possibility of love.",
|
||||
"model_output": "A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connection without giving in to the possibility of love.",
|
||||
"raw": "A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connection without giving in to the possibility of love.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 23,
|
||||
"story": 3,
|
||||
"summary": "Big someone must navigate the rigid codes of his underground world while seeking a way to break free and claim a life beyond the shadows.",
|
||||
"model_output": "Big Jim Dougherty must navigate the rigid codes of his underground world while seeking a way to break free and claim a life beyond the shadows.",
|
||||
"raw": "Big Jim Dougherty must navigate the rigid codes of his underground world while seeking a way to break free and claim a life beyond the shadows.",
|
||||
"names_removed": [
|
||||
"Jim",
|
||||
"Dougherty"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 24,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to balance his cultural identity and personal life while navigating the expectations of his community and the quiet routine of his domestic existence.",
|
||||
"model_output": "A man struggles to balance his cultural identity and personal life while navigating the expectations of his community and the quiet routine of his domestic existence.",
|
||||
"raw": "A man struggles to balance his cultural identity and personal life while navigating the expectations of his community and the quiet routine of his domestic existence.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 25,
|
||||
"story": 3,
|
||||
"summary": "A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsider the balance between habit and connection.",
|
||||
"model_output": "A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsider the balance between habit and connection.",
|
||||
"raw": "A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsider the balance between habit and connection.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 26,
|
||||
"story": 3,
|
||||
"summary": "A man struggling to reconcile his desire to display his wife publicly with his fear of judgment finds himself torn between pride and secrecy.",
|
||||
"model_output": "A man struggling to reconcile his desire to display his wife publicly with his fear of judgment finds himself torn between pride and secrecy.",
|
||||
"raw": "A man struggling to reconcile his desire to display his wife publicly with his fear of judgment finds himself torn between pride and secrecy.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 27,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to reconcile his pride with the growing awareness of his wife's hidden talents and the shame of having underestimated her.",
|
||||
"model_output": "A man struggles to reconcile his pride with the growing awareness of his wife's hidden talents and the shame of having underestimated her.",
|
||||
"raw": "A man struggles to reconcile his pride with the growing awareness of his wife's hidden talents and the shame of having underestimated her.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 28,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to reconcile his quiet life with the sudden, overwhelming attention his wife receives, threatening to expose the hidden depths of their marriage.",
|
||||
"model_output": "A man struggles to reconcile his quiet life with the sudden, overwhelming attention his wife receives, threatening to expose the hidden depths of their marriage.",
|
||||
"raw": "A man struggles to reconcile his quiet life with the sudden, overwhelming attention his wife receives, threatening to expose the hidden depths of their marriage.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 29,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to maintain his pride and dignity after a public embarrassment, while trying to preserve his relationship and quiet his wife's growing happiness.",
|
||||
"model_output": "A man struggles to maintain his pride and dignity after a public embarrassment, while trying to preserve his relationship and quiet his wife's growing happiness.",
|
||||
"raw": "A man struggles to maintain his pride and dignity after a public embarrassment, while trying to preserve his relationship and quiet his wife's growing happiness.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 30,
|
||||
"story": 3,
|
||||
"summary": "A woman trapped in a love that bends her will longs for a simple peach, while her powerful but obedient husband struggles to fulfill her wish in a world that resists giving way.",
|
||||
"model_output": "A woman trapped in a love that bends her will longs for a simple peach, while her powerful but obedient husband struggles to fulfill her wish in a world that resists giving way.",
|
||||
"raw": "A woman trapped in a love that bends her will longs for a simple peach, while her powerful but obedient husband struggles to fulfill her wish in a world that resists giving way.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 31,
|
||||
"story": 3,
|
||||
"summary": "The Kid needs to find a peach for a woman who insists on it, but every vendor says it's too soon and offers only oranges instead.",
|
||||
"model_output": "The Kid needs to find a peach for a woman who insists on it, but every vendor says it's too soon and offers only oranges instead.",
|
||||
"raw": "The Kid needs to find a peach for a woman who insists on it, but every vendor says it's too soon and offers only oranges instead.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 32,
|
||||
"story": 3,
|
||||
"summary": "The Kid needs to convince the captain to raid a gambling den controlled by an old enemy, but he must do so quickly before the enemy discovers his betrayal.",
|
||||
"model_output": "The Kid needs to convince the captain to raid a gambling den controlled by an old enemy, but he must do so quickly before the enemy discovers his betrayal.",
|
||||
"raw": "The Kid needs to convince the captain to raid a gambling den controlled by an old enemy, but he must do so quickly before the enemy discovers his betrayal.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 33,
|
||||
"story": 3,
|
||||
"summary": "A man must prove his worth in a dangerous confrontation to claim a prize he's risking everything for.",
|
||||
"model_output": "A man must prove his worth in a dangerous confrontation to claim a prize he's risking everything for.",
|
||||
"raw": "A man must prove his worth in a dangerous confrontation to claim a prize he's risking everything for.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 34,
|
||||
"story": 3,
|
||||
"summary": "A man desperate to find a peach before it's too late races against time and rivals to secure his prize before it vanishes entirely.",
|
||||
"model_output": "A man desperate to find a peach before it's too late races against time and rivals to secure his prize before it vanishes entirely.",
|
||||
"raw": "A man desperate to find a peach before it's too late races against time and rivals to secure his prize before it vanishes entirely.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 35,
|
||||
"story": 3,
|
||||
"summary": "He risks danger to fetch a trivial gift for his bride, only to find her preference has changed.",
|
||||
"model_output": "He risks danger to fetch a trivial gift for his bride, only to find her preference has changed.",
|
||||
"raw": "He risks danger to fetch a trivial gift for his bride, only to find her preference has changed.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 36,
|
||||
"story": 4,
|
||||
"summary": "A man trapped in the monotony of city life longs for the genuine signs of spring, but finds only artificial reports replacing nature's quiet signals.",
|
||||
"model_output": "A man trapped in the monotony of city life longs for the genuine signs of spring, but finds only artificial reports replacing nature's quiet signals.",
|
||||
"raw": "A man trapped in the monotony of city life longs for the genuine signs of spring, but finds only artificial reports replacing nature's quiet signals.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 37,
|
||||
"story": 4,
|
||||
"summary": "Three men plot to steal a dollar from someone\u2019s wife, who unknowingly possesses it, forcing him to confront his friends' greed and protect her from their violent intentions.",
|
||||
"model_output": "Three men plot to steal a dollar from Mr. Peters's wife, who unknowingly possesses it, forcing him to confront his friends' greed and protect her from their violent intentions.",
|
||||
"raw": "Three men plot to steal a dollar from Mr. Peters's wife, who unknowingly possesses it, forcing him to confront his friends' greed and protect her from their violent intentions.",
|
||||
"names_removed": [
|
||||
"Mr.",
|
||||
"Peters's"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 38,
|
||||
"story": 4,
|
||||
"summary": "Someone must steal a case note from his wife's hidden stash to buy a dollar for beer, but he refuses to harm her despite his desperation.",
|
||||
"model_output": "Mr. Peters must steal a case note from his wife's hidden stash to buy a dollar for beer, but he refuses to harm her despite his desperation.",
|
||||
"raw": "Mr. Peters must steal a case note from his wife's hidden stash to buy a dollar for beer, but he refuses to harm her despite his desperation.",
|
||||
"names_removed": [
|
||||
"Mr.",
|
||||
"Peters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 39,
|
||||
"story": 4,
|
||||
"summary": "A man trying to manipulate a woman with false promises faces her sharp rejection and must find another way to achieve his goal.",
|
||||
"model_output": "A man trying to manipulate a woman with false promises faces her sharp rejection and must find another way to achieve his goal.",
|
||||
"raw": "A man trying to manipulate a woman with false promises faces her sharp rejection and must find another way to achieve his goal.",
|
||||
"names_removed": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,410 @@
|
||||
{
|
||||
"model": "qwen2.5:3b-instruct",
|
||||
"prompt": "You describe the predicament of the main person in a passage of fiction, in ONE sentence.\n\nA predicament is what that person is up against and what they want out of it: the kind of trouble or opportunity that could happen to anyone, in any line of work, in any town.\n\nRules:\n- Exactly one sentence, at most 20 words.\n- Leave out everything that ties it to this story: no names, no jobs or professions, no places, no businesses, and no objects that only belong to this story.\n- Say what the person is up against, and what they want.\n- Do not describe the writing, the mood, or who is telling the story.\n- Output only the sentence.",
|
||||
"summaries": [
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"summary": "Finding the voice of a big city, I sought in vain among school chants.",
|
||||
"model_output": "Finding the voice of a big city, I sought in vain among school chants.",
|
||||
"raw": "Finding the voice of a big city, I sought in vain among school chants.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"summary": "I want to know the city's voice, but it echoes like someone.",
|
||||
"model_output": "I want to know the city's voice, but it echoes like Philadelphia.",
|
||||
"raw": "I want to know the city's voice, but it echoes like Philadelphia.",
|
||||
"names_removed": [
|
||||
"Philadelphia"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 2,
|
||||
"story": 0,
|
||||
"summary": "I must find the unique voice of New someone, proving it doesn't treat me like a cigar.",
|
||||
"model_output": "I must find the unique voice of New York, proving it doesn't treat me like a cigar.",
|
||||
"raw": "I must find the unique voice of New York, proving it doesn't treat me like a cigar.",
|
||||
"names_removed": [
|
||||
"York"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"summary": "I want to know the city's cry, but the cop only follows orders.",
|
||||
"model_output": "I want to know the city's cry, but the cop only follows orders.",
|
||||
"raw": "I want to know the city's cry, but the cop only follows orders.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"summary": "Bill, find the essence of New someone\u2019s chaos and harmony.",
|
||||
"model_output": "Bill, find the essence of New York's chaos and harmony.",
|
||||
"raw": "Bill, find the essence of New York's chaos and harmony.",
|
||||
"names_removed": [
|
||||
"York's"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 5,
|
||||
"story": 0,
|
||||
"summary": "The poet seeks a hit with his poem, fearing rejection.",
|
||||
"model_output": "The poet seeks a hit with his poem, fearing rejection.",
|
||||
"raw": "The poet seeks a hit with his poem, fearing rejection.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"summary": "Someone waits alone, wanting her companion to speak.",
|
||||
"model_output": "Aurelia waits alone, wanting her companion to speak.",
|
||||
"raw": "Aurelia waits alone, wanting her companion to speak.",
|
||||
"names_removed": [
|
||||
"Aurelia"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 1,
|
||||
"summary": "Someone finds deepest life joys in a hidden quarter, not wealth.",
|
||||
"model_output": "John Hopkins finds deepest life joys in a hidden quarter, not wealth.",
|
||||
"raw": "John Hopkins finds deepest life joys in a hidden quarter, not wealth.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 8,
|
||||
"story": 1,
|
||||
"summary": "Someone struggles to balance her desires and fears in the fast-paced city.",
|
||||
"model_output": "Mrs. Hopkins struggles to balance her desires and fears in the fast-paced city.",
|
||||
"raw": "Mrs. Hopkins struggles to balance her desires and fears in the fast-paced city.",
|
||||
"names_removed": [
|
||||
"Mrs.",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 9,
|
||||
"story": 1,
|
||||
"summary": "Someone sought to inject conversation into his tasteless existence by buying a cigar.",
|
||||
"model_output": "John Hopkins sought to inject conversation into his tasteless existence by buying a cigar.",
|
||||
"raw": "John Hopkins sought to inject conversation into his tasteless existence by buying a cigar.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 1,
|
||||
"summary": "Someone buys spinach, finds no cash, challenges tobacconist someone.",
|
||||
"model_output": "Hopkins buys spinach, finds no cash, challenges tobacconist Freshmayer.",
|
||||
"raw": "Hopkins buys spinach, finds no cash, challenges tobacconist Freshmayer.",
|
||||
"names_removed": [
|
||||
"Hopkins",
|
||||
"Freshmayer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 1,
|
||||
"summary": "Someone sought revenge, but found himself pursued by law enforcers and a mysterious chauffeur in a racing car.",
|
||||
"model_output": "John Hopkins sought revenge, but found himself pursued by law enforcers and a mysterious chauffeur in a racing car.",
|
||||
"raw": "John Hopkins sought revenge, but found himself pursued by law enforcers and a mysterious chauffeur in a racing car.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 1,
|
||||
"summary": "Someone wants Milady's assistance against unseen attackers, while Milady seeks a brave companion.",
|
||||
"model_output": "Hopkins wants Milady's assistance against unseen attackers, while Milady seeks a brave companion.",
|
||||
"raw": "Hopkins wants Milady's assistance against unseen attackers, while Milady seeks a brave companion.",
|
||||
"names_removed": [
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 1,
|
||||
"summary": "Someone seeks a romantic encounter, only to be kidnapped by a mysterious lady.",
|
||||
"model_output": "John Hopkins seeks a romantic encounter, only to be kidnapped by a mysterious lady.",
|
||||
"raw": "John Hopkins seeks a romantic encounter, only to be kidnapped by a mysterious lady.",
|
||||
"names_removed": [
|
||||
"John",
|
||||
"Hopkins"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 14,
|
||||
"story": 1,
|
||||
"summary": "Bess wants her St. Bernard, but someone wants her to stop reading dangerous novels.",
|
||||
"model_output": "Bess wants her St. Bernard, but John wants her to stop reading dangerous novels.",
|
||||
"raw": "Bess wants her St. Bernard, but John wants her to stop reading dangerous novels.",
|
||||
"names_removed": [
|
||||
"John"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 15,
|
||||
"story": 2,
|
||||
"summary": "Someone, a young saleslady, seeks to navigate the complexities of human nature while hiding her true identity.",
|
||||
"model_output": "Masie, a young saleslady, seeks to navigate the complexities of human nature while hiding her true identity.",
|
||||
"raw": "Masie, a young saleslady, seeks to navigate the complexities of human nature while hiding her true identity.",
|
||||
"names_removed": [
|
||||
"Masie"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 16,
|
||||
"story": 2,
|
||||
"summary": "The shopgirl someone confronts the floorwalker someone with her flirtatious gaze, testing his resolve.",
|
||||
"model_output": "The shopgirl Masie confronts the floorwalker Irving Carter with her flirtatious gaze, testing his resolve.",
|
||||
"raw": "The shopgirl Masie confronts the floorwalker Irving Carter with her flirtatious gaze, testing his resolve.",
|
||||
"names_removed": [
|
||||
"Masie",
|
||||
"Irving",
|
||||
"Carter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 17,
|
||||
"story": 2,
|
||||
"summary": "Someone desires to charm a shopgirl despite feeling out of place.",
|
||||
"model_output": "Irving Carter desires to charm a shopgirl despite feeling out of place.",
|
||||
"raw": "Irving Carter desires to charm a shopgirl despite feeling out of place.",
|
||||
"names_removed": [
|
||||
"Irving",
|
||||
"Carter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 18,
|
||||
"story": 2,
|
||||
"summary": "Willie nervously asked for her company, hoping she'd agree to a date.",
|
||||
"model_output": "Willie nervously asked for her company, hoping she'd agree to a date.",
|
||||
"raw": "Willie nervously asked for her company, hoping she'd agree to a date.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 19,
|
||||
"story": 2,
|
||||
"summary": "Someone seeks love but fears rejection, while someone uses him to prove her worth.",
|
||||
"model_output": "Carter seeks love but fears rejection, while Masie uses him to prove her worth.",
|
||||
"raw": "Carter seeks love but fears rejection, while Masie uses him to prove her worth.",
|
||||
"names_removed": [
|
||||
"Carter",
|
||||
"Masie"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 20,
|
||||
"story": 2,
|
||||
"summary": "Someone faces someone\u2019s marriage proposal, wanting to see through his sincerity.",
|
||||
"model_output": "Masie faces Carter's marriage proposal, wanting to see through his sincerity.",
|
||||
"raw": "Masie faces Carter's marriage proposal, wanting to see through his sincerity.",
|
||||
"names_removed": [
|
||||
"Masie",
|
||||
"Carter's"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 21,
|
||||
"story": 2,
|
||||
"summary": "Someone looked up at someone, her eyes alight with a flicker of possibility.",
|
||||
"model_output": "Masie looked up at Carter, her eyes alight with a flicker of possibility.",
|
||||
"raw": "Masie looked up at Carter, her eyes alight with a flicker of possibility.",
|
||||
"names_removed": [
|
||||
"Masie",
|
||||
"Carter"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 22,
|
||||
"story": 2,
|
||||
"summary": "She wants to end their secret affair, but he feels a hidden joy.",
|
||||
"model_output": "She wants to end their secret affair, but he feels a hidden joy.",
|
||||
"raw": "She wants to end their secret affair, but he feels a hidden joy.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 23,
|
||||
"story": 3,
|
||||
"summary": "Big someone sought acceptance in someone\u2019s titled nobility of sportdom.",
|
||||
"model_output": "Big Jim Dougherty sought acceptance in Manhattan's titled nobility of sportdom.",
|
||||
"raw": "Big Jim Dougherty sought acceptance in Manhattan's titled nobility of sportdom.",
|
||||
"names_removed": [
|
||||
"Jim",
|
||||
"Dougherty",
|
||||
"Manhattan's"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 24,
|
||||
"story": 3,
|
||||
"summary": "He seeks a private life away from his public Oriental sport, yet feels trapped in public life.",
|
||||
"model_output": "He seeks a private life away from his public Oriental sport, yet feels trapped in public life.",
|
||||
"raw": "He seeks a private life away from his public Oriental sport, yet feels trapped in public life.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 25,
|
||||
"story": 3,
|
||||
"summary": "Big someone\" expected her to ask for a dinner date, not insist on one.",
|
||||
"model_output": "\"Big Jim\" expected her to ask for a dinner date, not insist on one.",
|
||||
"raw": "Big Jim\" expected her to ask for a dinner date, not insist on one.",
|
||||
"names_removed": [
|
||||
"Jim"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 26,
|
||||
"story": 3,
|
||||
"summary": "Big someone wanted to hide his wife's presence, but a mysterious bird-like being at her side made him change plans.",
|
||||
"model_output": "Big Jim Dougherty wanted to hide his wife's presence, but a mysterious bird-like being at her side made him change plans.",
|
||||
"raw": "Big Jim Dougherty wanted to hide his wife's presence, but a mysterious bird-like being at her side made him change plans.",
|
||||
"names_removed": [
|
||||
"Jim",
|
||||
"Dougherty"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 27,
|
||||
"story": 3,
|
||||
"summary": "Big someone seeks to impress his wife at a fancy restaurant, hoping her beauty and charm will win him praise.",
|
||||
"model_output": "Big Jim seeks to impress his wife at a fancy restaurant, hoping her beauty and charm will win him praise.",
|
||||
"raw": "Big Jim seeks to impress his wife at a fancy restaurant, hoping her beauty and charm will win him praise.",
|
||||
"names_removed": [
|
||||
"Jim"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 28,
|
||||
"story": 3,
|
||||
"summary": "The honorable someone seeks to expose a secret and win someone\u2019s heart, while \"Big someone\" struggles to keep up.",
|
||||
"model_output": "The honorable Patrick Corrigan seeks to expose a secret and win Mrs. Dougherty's heart, while \"Big Jim\" struggles to keep up.",
|
||||
"raw": "The honorable Patrick Corrigan seeks to expose a secret and win Mrs. Dougherty's heart, while \"Big Jim\" struggles to keep up.",
|
||||
"names_removed": [
|
||||
"Patrick",
|
||||
"Corrigan",
|
||||
"Mrs.",
|
||||
"Dougherty's",
|
||||
"Jim"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 29,
|
||||
"story": 3,
|
||||
"summary": "Someone seeks to impress someone with a bold gesture, but is thwarted by her quicker action.",
|
||||
"model_output": "Patrick seeks to impress Mrs. Dougherty with a bold gesture, but is thwarted by her quicker action.",
|
||||
"raw": "Patrick seeks to impress Mrs. Dougherty with a bold gesture, but is thwarted by her quicker action.",
|
||||
"names_removed": [
|
||||
"Patrick",
|
||||
"Mrs.",
|
||||
"Dougherty"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 30,
|
||||
"story": 3,
|
||||
"summary": "The bride desires her husband's affection, yet fears losing him to distant admirers.",
|
||||
"model_output": "The bride desires her husband's affection, yet fears losing him to distant admirers.",
|
||||
"raw": "The bride desires her husband's affection, yet fears losing him to distant admirers.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 31,
|
||||
"story": 3,
|
||||
"summary": "The Kid seeks a peach but finds oranges instead, rushing to a fruiterer just as the lights go out.",
|
||||
"model_output": "The Kid seeks a peach but finds oranges instead, rushing to a fruiterer just as the lights go out.",
|
||||
"raw": "The Kid seeks a peach but finds oranges instead, rushing to a fruiterer just as the lights go out.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 32,
|
||||
"story": 3,
|
||||
"summary": "Kid seeks to infiltrate someone\u2019s gambling den but finds it closed, needing help from the captain.",
|
||||
"model_output": "Kid seeks to infiltrate Denver Dick's gambling den but finds it closed, needing help from the captain.",
|
||||
"raw": "Kid seeks to infiltrate Denver Dick's gambling den but finds it closed, needing help from the captain.",
|
||||
"names_removed": [
|
||||
"Denver",
|
||||
"Dick's"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 33,
|
||||
"story": 3,
|
||||
"summary": "The captain seeks to lead a raid, but doubts success as the Kid's plan unfolds.",
|
||||
"model_output": "The captain seeks to lead a raid, but doubts success as the Kid's plan unfolds.",
|
||||
"raw": "The captain seeks to lead a raid, but doubts success as the Kid's plan unfolds.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 34,
|
||||
"story": 3,
|
||||
"summary": "The Kid seeks a last peach at a lavish dinner, fearing theft, while authorities arrive.",
|
||||
"model_output": "The Kid seeks a last peach at a lavish dinner, fearing theft, while authorities arrive.",
|
||||
"raw": "The Kid seeks a last peach at a lavish dinner, fearing theft, while authorities arrive.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 35,
|
||||
"story": 3,
|
||||
"summary": "The Kid seeks to please his bride by delivering a requested peach, despite its trivial nature.",
|
||||
"model_output": "The Kid seeks to please his bride by delivering a requested peach, despite its trivial nature.",
|
||||
"raw": "The Kid seeks to please his bride by delivering a requested peach, despite its trivial nature.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 36,
|
||||
"story": 4,
|
||||
"summary": "Stuck in stone walls, city man craves nature's first signs to confirm spring's arrival.",
|
||||
"model_output": "Stuck in stone walls, city man craves nature's first signs to confirm spring's arrival.",
|
||||
"raw": "Stuck in stone walls, city man craves nature's first signs to confirm spring's arrival.",
|
||||
"names_removed": []
|
||||
},
|
||||
{
|
||||
"chunk": 37,
|
||||
"story": 4,
|
||||
"summary": "Someone wants the dollar his wife has, but fears losing face among friends.",
|
||||
"model_output": "Mr. Peters wants the dollar his wife has, but fears losing face among friends.",
|
||||
"raw": "Mr. Peters wants the dollar his wife has, but fears losing face among friends.",
|
||||
"names_removed": [
|
||||
"Mr.",
|
||||
"Peters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 38,
|
||||
"story": 4,
|
||||
"summary": "Someone wants the dollar, but someone plans to get the case note instead.",
|
||||
"model_output": "Maguire wants the dollar, but Mr. Peters plans to get the case note instead.",
|
||||
"raw": "Maguire wants the dollar, but Mr. Peters plans to get the case note instead.",
|
||||
"names_removed": [
|
||||
"Maguire",
|
||||
"Mr.",
|
||||
"Peters"
|
||||
]
|
||||
},
|
||||
{
|
||||
"chunk": 39,
|
||||
"story": 4,
|
||||
"summary": "Someone schemes to charm someone away from a tea job, while she resists with a tough stance.",
|
||||
"model_output": "Mr. Peters schemes to charm Mrs. Peters away from a tea job, while she resists with a tough stance.",
|
||||
"raw": "Mr. Peters schemes to charm Mrs. Peters away from a tea job, while she resists with a tough stance.",
|
||||
"names_removed": [
|
||||
"Mr.",
|
||||
"Peters",
|
||||
"Mrs.",
|
||||
"Peters"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,862 @@
|
||||
<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Predicament pilot</title>
|
||||
<style>
|
||||
:root{--bg:#f7f6f2;--card:#fff;--ink:#222;--dim:#666;--line:#ddd;--mark:#ffe08a;--pick:#e3f1e6}
|
||||
@media (prefers-color-scheme:dark){:root{--bg:#1b1b1d;--card:#252528;--ink:#e6e6e6;--dim:#999;--line:#3a3a3e;--mark:#6b5415;--pick:#23402b}}
|
||||
body{background:var(--bg);color:var(--ink);font:15px/1.5 system-ui,sans-serif;margin:0;padding:24px 16px}
|
||||
main{max-width:980px;margin:auto} h1{margin:0 0 4px} .dim{color:var(--dim)}
|
||||
table{border-collapse:collapse;margin:12px 0;font-size:14px} td,th{border-bottom:1px solid var(--line);padding:4px 10px;text-align:left}
|
||||
.wrap{overflow-x:auto}
|
||||
#tally{position:sticky;top:0;background:var(--bg);padding:8px 0;border-bottom:1px solid var(--line);z-index:1}
|
||||
section{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:12px 14px;margin:14px 0}
|
||||
header .n{color:var(--dim);margin-right:4px} summary{cursor:pointer;color:var(--dim);margin:6px 0}
|
||||
.scene{white-space:pre-wrap;font-size:13px;color:var(--dim)}
|
||||
.pair{display:grid;grid-template-columns:1fr 1fr;gap:10px} @media (max-width:640px){.pair{grid-template-columns:1fr}}
|
||||
.pred{border:1px solid var(--line);border-radius:6px;padding:8px 10px} .pred p{margin:4px 0 8px}
|
||||
.pred:has(input:checked){background:var(--pick)} .who{font-size:12px;color:var(--dim)}
|
||||
mark{background:var(--mark);color:inherit;border-radius:2px} .both{display:block;margin-top:8px;font-size:13px;color:var(--dim)}
|
||||
</style>
|
||||
<main>
|
||||
<h1>Predicament pilot</h1>
|
||||
<p class="dim">The first 40 scenes, each re-described by two models as the main person's predicament. Names are
|
||||
already removed as the full run would. <mark>Highlighted</mark> words are jobs, relationships and places, the
|
||||
words the old groups formed around. Pick the better sentence in each scene, or "neither". The column order
|
||||
is fixed: qwen2.5:3b-instruct on the left.</p>
|
||||
<p class="dim">What to look for: does the sentence say what the person is up against and what they want? Could
|
||||
it happen to anyone, anywhere? Would two scenes with the same predicament end up with similar sentences?</p>
|
||||
<div class="wrap"><table><tr><th>Model</th><th>Has a highlighted word</th><th>Says "city"</th><th>Words, mean</th><th>Most common openings</th></tr><tr><td>qwen2.5:3b-instruct</td><td>15 of 40</td><td>5</td><td>14</td><td>“The Kid seeks…” ×3; “I want to…” ×2; “Someone wants the…” ×2</td></tr><tr><td>qwen3:14b</td><td>13 of 40</td><td>10</td><td>25</td><td>“A man struggles…” ×6; “A man tries…” ×3; “A man seeks…” ×2</td></tr></table></div>
|
||||
<div id="tally"></div>
|
||||
<section data-i="0">
|
||||
<header><span class="n">1</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: Someone, a schoolteacher, seeks the wisdom of her former students to understand the complex vocal messages of a bustling city.</summary>
|
||||
<p class="scene">Twenty-five years ago the school children used to chant their
|
||||
lessons. The manner of their delivery was a singsong recitative
|
||||
between the utterance of an Episcopal minister and the drone of a
|
||||
tired sawmill. I mean no disrespect. We must have lumber and sawdust. I remember one beautiful and instructive little lyric that emanated
|
||||
from the physiology class. The most striking line of it was this: "The shin-bone is the long-est bone in the hu-man bod-y." What an inestimable boon it would have been if all the corporeal
|
||||
and spiritual facts pertaining to man had thus been tunefully and
|
||||
logically inculcated in our youthful minds! But what we gained in
|
||||
anatomy, music and philosophy was meagre. The other day I became confused. I needed a ray of light. I turned
|
||||
back to those school days for aid. But in all the nasal harmonies
|
||||
we whined forth from those hard benches I could not recall one that
|
||||
treated of the voice of agglomerated mankind. In other words, of the composite vocal message of massed humanity. In other words, of the Voice of a Big City. Now, the individual voice is not lacking. We can understand the
|
||||
song of the poet, the ripple of the brook, the meaning of the man
|
||||
who wants $5 until …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Finding the voice of a big city, I sought in vain among school chants.</p><label><input type="radio" name="p0" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggles to understand the overwhelming complexity of a city's collective voice, seeking clarity in a world filled with individual sounds but no unified message.</p><label><input type="radio" name="p0" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p0" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="1">
|
||||
<header><span class="n">2</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: I want to know what the city says, but someone thinks it repeats what someone says.</summary>
|
||||
<p class="scene">"Tell me," I said, stammeringly, for I have no voice of my own, "what
|
||||
does this big--er--enormous--er--whopping city say? It must have a
|
||||
voice of some kind. Does it ever speak to you? How do you interpret
|
||||
its meaning? It is a tremendous mass, but it must have a key." "Like a Saratoga trunk?" asked Aurelia. "No," said I. "Please do not refer to the lid. I have a fancy that
|
||||
every city has a voice. Each one has something to say to the one who
|
||||
can hear it. What does the big one say to you?" "All cities," said Aurelia, judicially, "say the same thing. When
|
||||
they get through saying it there is an echo from Philadelphia. So,
|
||||
they are unanimous." "Here are 4,000,000 people," said I, scholastically, "compressed upon
|
||||
an island, which is mostly lamb surrounded by Wall Street water. The
|
||||
conjunction of so many units into so small a space must result in an
|
||||
identity--or, or rather a homogeneity that finds its oral expression
|
||||
through a common channel. It is, as you might say, a consensus of
|
||||
translation, concentrating in a crystallized, general idea which
|
||||
reveals itself in what may be termed the Voice of the City. Can you
|
||||
tell me what it is?" Aurelia smiled wonderfully. She sat on the high stoop. A …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>I want to know the city's voice, but it echoes like someone.</p><label><input type="radio" name="p1" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>The person is trying to understand the city's hidden message, hoping to find a unified voice that reveals its true nature.</p><label><input type="radio" name="p1" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p1" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="2">
|
||||
<header><span class="n">3</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: I must find the Voice of New someone, but first I seek out someone, the best <mark>bartender</mark>, to learn the city's unique ways.</summary>
|
||||
<p class="scene">"I must go and find out," I said, "what is the Voice of this City.
|
||||
Other cities have voices. It is an assignment. I must have it. New
|
||||
York," I continued, in a rising tone, "had better not hand me a cigar
|
||||
and say: 'Old man, I can't talk for publication.' No other city acts
|
||||
in that way. Chicago says, unhesitatingly, 'I will;' I Philadelphia
|
||||
says, 'I should;' New Orleans says, 'I used to;' Louisville says,
|
||||
'Don't care if I do;' St. Louis says, 'Excuse me;' Pittsburg says,
|
||||
'Smoke up.' Now, New York--" Aurelia smiled. "Very well," said I, "I must go elsewhere and find out." I went into a palace, tile-floored, cherub-ceilinged and square with
|
||||
the cop. I put my foot on the brass rail and said to Billy Magnus,
|
||||
the best bartender in the diocese: "Billy, you've lived in New York a long time--what kind of a
|
||||
song-and-dance does this old town give you? What I mean is, doesn't
|
||||
the gab of it seem to kind of bunch up and slide over the bar to you
|
||||
in a sort of amalgamated tip that hits off the burg in a kind of an
|
||||
epigram with a dash of bitters and a slice of--" "Excuse me a minute," said Billy, "somebody's punching the button at
|
||||
the side door." He went away; came back with an empty tin bucket; aga…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>I must find the unique voice of New someone, proving it doesn't treat me like a cigar.</p><label><input type="radio" name="p2" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man determined to uncover the unique voice of a city faces the challenge of distinguishing its true character from the distractions and distractions of daily life.</p><label><input type="radio" name="p2" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p2" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="3">
|
||||
<header><span class="n">4</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: The <mark>cop</mark>, tasked with maintaining order, confronts a young man who questions the <mark>cop</mark>'s role and civic duty, leading to a conversation about the city's unrest and the <mark>cop</mark>'s mundane nightly duties.</summary>
|
||||
<p class="scene">I walked up to Broadway. I saw a cop on the corner. The cops take
|
||||
kids up, women across, and men in. I went up to him. "If I'm not exceeding the spiel limit," I said, "let me ask you. You
|
||||
see New York during its vocative hours. It is the function of you and
|
||||
your brother cops to preserve the acoustics of the city. There must
|
||||
be a civic voice that is intelligible to you. At night during your
|
||||
lonely rounds you must have heard it. What is the epitome of its
|
||||
turmoil and shouting? What does the city say to you?" "Friend," said the policeman, spinning his club, "it don't say
|
||||
nothing. I get my orders from the man higher up. Say, I guess you're
|
||||
all right. Stand here for a few minutes and keep an eye open for the
|
||||
roundsman." The cop melted into the darkness of the side street. In ten minutes
|
||||
he had returned. "Married last Tuesday," he said, half gruffly. "You know how they
|
||||
are. She comes to that corner at nine every night for a--comes to say
|
||||
'hello!' I generally manage to be there. Say, what was it you asked
|
||||
me a bit ago--what's doing in the city? Oh, there's a roof-garden or
|
||||
two just opened, twelve blocks up." I crossed a crow's-foot of street-car tracks, and skirted the edge
|
||||
of an umbrageo…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>I want to know the city's cry, but the <mark>cop</mark> only follows orders.</p><label><input type="radio" name="p3" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man tries to uncover the city's hidden voice through a <mark>cop</mark>, only to find that the <mark>cop</mark>'s personal life and indifference to the city's soul leave him with no real answers.</p><label><input type="radio" name="p3" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p3" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="4">
|
||||
<header><span class="n">5</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: Bill, in the role of someone, seeks a poetic description of New someone’s soul from someone, someone, someone, May, and someone, hinting at a symposium to capture the city's multifaceted voice.</summary>
|
||||
<p class="scene">"Bill," said I (in the magazine he is Cleon), "give me a lift. I am
|
||||
on an assignment to find out the Voice of the city. You see, it's a
|
||||
special order. Ordinarily a symposium comprising the views of Henry
|
||||
Clews, John L. Sullivan, Edwin Markham, May Irwin and Charles Schwab
|
||||
would be about all. But this is a different matter. We want a broad,
|
||||
poetic, mystic vocalization of the city's soul and meaning. You are
|
||||
the very chap to give me a hint. Some years ago a man got at the
|
||||
Niagara Falls and gave us its pitch. The note was about two feet
|
||||
below the lowest G on the piano. Now, you can't put New York into a
|
||||
note unless it's better indorsed than that. But give me an idea of
|
||||
what it would say if it should speak. It is bound to be a mighty and
|
||||
far-reaching utterance. To arrive at it we must take the tremendous
|
||||
crash of the chords of the day's traffic, the laughter and music
|
||||
of the night, the solemn tones of Dr. Parkhurst, the rag-time, the
|
||||
weeping, the stealthy hum of cab-wheels, the shout of the press
|
||||
agent, the tinkle of fountains on the roof gardens, the hullabaloo
|
||||
of the strawberry vender and the covers of _Everybody's Magazine_,
|
||||
the whispers of the lovers in the parks--all these sounds …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Bill, find the essence of New someone’s chaos and harmony.</p><label><input type="radio" name="p4" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man seeks to capture the essence of a city's soul through a unique blend of its sounds, hoping to create a single, powerful vocalization that represents its spirit.</p><label><input type="radio" name="p4" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p4" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="5">
|
||||
<header><span class="n">6</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: The <mark>poet</mark> seeks advice from someone about his failed ties and unfulfilled prophecy, while a newsboy offers practical advice about buying a birthday present for someone.</summary>
|
||||
<p class="scene">"Do you remember," asked the poet, with a chuckle, "that California
|
||||
girl we met at Stiver's studio last week? Well, I'm on my way to see
|
||||
her. She repeated that poem of mine, 'The Tribute of Spring,' word
|
||||
for word. She's the smartest proposition in this town just at
|
||||
present. Say, how does this confounded tie look? I spoiled four
|
||||
before I got one to set right." "And the Voice that I asked you about?" I inquired. "Oh, she doesn't sing," said Cleon. "But you ought to hear her recite
|
||||
my 'Angel of the Inshore Wind.'" I passed on. I cornered a newsboy and he flashed at me prophetic pink
|
||||
papers that outstripped the news by two revolutions of the clock's
|
||||
longest hand. "Son," I said, while I pretended to chase coins in my penny pocket,
|
||||
"doesn't it sometimes seem to you as if the city ought to be able to
|
||||
talk? All these ups and downs and funny business and queer things
|
||||
happening every day--what would it say, do you think, if it could
|
||||
speak?" "Quit yer kiddin'," said the boy. "Wot paper yer want? I got no time
|
||||
to waste. It's Mag's birthday, and I want thirty cents to git her a
|
||||
present." Here was no interpreter of the city's mouthpiece. I bought a paper,
|
||||
and consigned its undeclared treaties, i…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The <mark>poet</mark> seeks a hit with his poem, fearing rejection.</p><label><input type="radio" name="p5" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man seeks understanding from a city that refuses to speak, desperate to uncover a hidden truth that eludes everyone else.</p><label><input type="radio" name="p5" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p5" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="6">
|
||||
<header><span class="n">7</span> <b>THE VOICE OF THE CITY</b></header>
|
||||
<details><summary>Scene summary: Someone waits on her porch as night deepens, while her companion remains silent, their hands finally meeting in a moment of unspoken understanding.</summary>
|
||||
<p class="scene">Aurelia was still on the stoop. The moon was higher and the ivy
|
||||
shadows were deeper. I sat at her side and we watched a little cloud
|
||||
tilt at the drifting moon and go asunder quite pale and discomfited. And then, wonder of wonders and delight of delights! our hands
|
||||
somehow touched, and our fingers closed together and did not part. After half an hour Aurelia said, with that smile of hers: "Do you know, you haven't spoken a word since you came back!" "That," said I, nodding wisely, "is the Voice of the City." II</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone waits alone, wanting her companion to speak.</p><label><input type="radio" name="p6" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weight of the city's presence.</p><label><input type="radio" name="p6" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p6" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="7">
|
||||
<header><span class="n">8</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone, a <mark>clerk</mark> at Insurance, seeks to understand life through poverty, love, and war, finding deeper meaning in urban experiences.</summary>
|
||||
<p class="scene">There is a saying that no man has tasted the full flavour of life
|
||||
until he has known poverty, love and war. The justness of this
|
||||
reflection commends it to the lover of condensed philosophy. The
|
||||
three conditions embrace about all there is in life worth knowing. A
|
||||
surface thinker might deem that wealth should be added to the list.
|
||||
Not so. When a poor man finds a long-hidden quarter-dollar that has
|
||||
slipped through a rip into his vest lining, he sounds the pleasure of
|
||||
life with a deeper plummet than any millionaire can hope to cast. It seems that the wise executive power that rules life has thought
|
||||
best to drill man in these three conditions; and none may escape all
|
||||
three. In rural places the terms do not mean so much. Poverty is less
|
||||
pinching; love is temperate; war shrinks to contests about boundary
|
||||
lines and the neighbors' hens. It is in the cities that our epigram
|
||||
gains in truth and vigor; and it has remained for one John Hopkins to
|
||||
crowd the experience into a rather small space of time. The Hopkins flat was like a thousand others. There was a rubber plant
|
||||
in one window; a flea-bitten terrier sat in the other, wondering when
|
||||
he was to have his day. John Hopkins was like a thousand …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone finds deepest life joys in a hidden quarter, not wealth.</p><label><input type="radio" name="p7" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone must navigate the harsh realities of poverty, love, and war all at once in the crowded, unforgiving city.</p><label><input type="radio" name="p7" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p7" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="8">
|
||||
<header><span class="n">9</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone navigated the mundane routines and sudden upheavals of city life, from thrift <mark>store</mark> sales to <mark>street</mark> encounters with danger.</summary>
|
||||
<p class="scene">Mrs. Hopkins was like a thousand others. The auriferous tooth, the
|
||||
sedentary disposition, the Sunday afternoon wanderlust, the draught
|
||||
upon the delicatessen store for home-made comforts, the furor for
|
||||
department store marked-down sales, the feeling of superiority to
|
||||
the lady in the third-floor front who wore genuine ostrich tips and
|
||||
had two names over her bell, the mucilaginous hours during which
|
||||
she remained glued to the window sill, the vigilant avoidance of
|
||||
the instalment man, the tireless patronage of the acoustics of the
|
||||
dumb-waiter shaft--all the attributes of the Gotham flat-dweller were
|
||||
hers. One moment yet of sententiousness and the story moves. In the Big City large and sudden things happen. You round a corner
|
||||
and thrust the rib of your umbrella into the eye of your old friend
|
||||
from Kootenai Falls. You stroll out to pluck a Sweet William in
|
||||
the park--and lo! bandits attack you--you are ambulanced to the
|
||||
hospital--you marry your nurse; are divorced--get squeezed while
|
||||
short on U. P. S. and D. O. W. N. S.--stand in the bread line--marry
|
||||
an heiress, take out your laundry and pay your club dues--seemingly
|
||||
all in the wink of an eye. You travel the streets, and a finger
|
||||
beckons …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone struggles to balance her desires and fears in the fast-paced city.</p><label><input type="radio" name="p8" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone must navigate the chaotic unpredictability of city life while clinging to the comfort of her familiar routines.</p><label><input type="radio" name="p8" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p8" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="9">
|
||||
<header><span class="n">10</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone sat in his <mark>flat</mark>, seeking conversation to spice up his life, while someone complained about the smells from across the hall.</summary>
|
||||
<p class="scene">John Hopkins sat, after a compressed dinner, in his glove-fitting
|
||||
straight-front flat. He sat upon a hornblende couch and gazed, with
|
||||
satiated eyes, at Art Brought Home to the People in the shape of "The
|
||||
Storm" tacked against the wall. Mrs. Hopkins discoursed droningly
|
||||
of the dinner smells from the flat across the hall. The flea-bitten
|
||||
terrier gave Hopkins a look of disgust, and showed a man-hating
|
||||
tooth. Here was neither poverty, love, nor war; but upon such barren stems
|
||||
may be grafted those essentials of a complete life. John Hopkins sought to inject a few raisins of conversation into
|
||||
the tasteless dough of existence. "Putting a new elevator in at the
|
||||
office," he said, discarding the nominative noun, "and the boss has
|
||||
turned out his whiskers." "You don't mean it!" commented Mrs. Hopkins. "Mr. Whipples," continued John, "wore his new spring suit down
|
||||
to-day. I liked it fine It's a gray with--" He stopped, suddenly
|
||||
stricken by a need that made itself known to him. "I believe I'll
|
||||
walk down to the corner and get a five-cent cigar," he concluded. John Hopkins took his hat and picked his way down the musty halls and
|
||||
stairs of the flat-house. The evening air was mild, and the streets s…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone sought to inject conversation into his tasteless existence by buying a cigar.</p><label><input type="radio" name="p9" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone longs for connection but is trapped in a life of monotony and shallow conversation.</p><label><input type="radio" name="p9" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p9" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="10">
|
||||
<header><span class="n">11</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone, seeking groceries, confronts someone over payment, escalating into a physical altercation in the <mark>store</mark>.</summary>
|
||||
<p class="scene">Hopkins, unknown in the store, entered and called genially for his
|
||||
"bunch of spinach, car-fare grade." This imputation deepened the
|
||||
pessimism of Freshmayer; but he set out a brand that came perilously
|
||||
near to filling the order. Hopkins bit off the roots of his purchase,
|
||||
and lighted up at the swinging gas jet. Feeling in his pockets to
|
||||
make payment, he found not a penny there. "Say, my friend," he explained, frankly, "I've come out without any
|
||||
change. Hand you that nickel first time I pass." Joy surged in Freshmayer's heart. Here was corroboration of his
|
||||
belief that the world was rotten and man a peripatetic evil. Without
|
||||
a word he rounded the end of his counter and made earnest onslaught
|
||||
upon his customer. Hopkins was no man to serve as a punching-bag for
|
||||
a pessimistic tobacconist. He quickly bestowed upon Freshmayer a
|
||||
colorado-maduro eye in return for the ardent kick that he received
|
||||
from that dealer in goods for cash only. The impetus of the enemy's attack forced the Hopkins line back to
|
||||
the sidewalk. There the conflict raged; the pacific wooden Indian,
|
||||
with his carven smile, was overturned, and those of the street who
|
||||
delighted in carnage pressed round to view the zealous joust.</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone buys spinach, finds no cash, challenges tobacconist someone.</p><label><input type="radio" name="p10" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confrontation with a <mark>shopkeeper</mark> who sees his actions as proof of humanity's corruption.</p><label><input type="radio" name="p10" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p10" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="11">
|
||||
<header><span class="n">12</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone, a rebus worker, eludes a <mark>policeman</mark> by running and jumping into a speeding car driven by a masked <mark>chauffeur</mark>, unaware of his passenger's indifference.</summary>
|
||||
<p class="scene">But then came the inevitable cop and imminent inconvenience for both
|
||||
the attacker and attacked. John Hopkins was a peaceful citizen, who
|
||||
worked at rebuses of nights in a flat, but he was not without the
|
||||
fundamental spirit of resistance that comes with the battle-rage.
|
||||
He knocked the policeman into a grocer's sidewalk display of goods
|
||||
and gave Freshmayer a punch that caused him temporarily to regret
|
||||
that he had not made it a rule to extend a five-cent line of credit
|
||||
to certain customers. Then Hopkins took spiritedly to his heels down
|
||||
the sidewalk, closely followed by the cigar-dealer and the policeman,
|
||||
whose uniform testified to the reason in the grocer's sign that read:
|
||||
"Eggs cheaper than anywhere else in the city." As Hopkins ran he became aware of a big, low, red, racing automobile
|
||||
that kept abreast of him in the street. This auto steered in to the
|
||||
side of the sidewalk, and the man guiding it motioned to Hopkins
|
||||
to jump into it. He did so without slackening his speed, and fell
|
||||
into the turkey-red upholstered seat beside the chauffeur. The big
|
||||
machine, with a diminuendo cough, flew away like an albatross down
|
||||
the avenue into which the street emptied. The driver of the auto sped hi…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone sought revenge, but found himself pursued by law enforcers and a mysterious <mark>chauffeur</mark> in a racing car.</p><label><input type="radio" name="p11" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but must decide whether to trust his mysterious rescuer.</p><label><input type="radio" name="p11" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p11" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="12">
|
||||
<header><span class="n">13</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone, seeking revenge for mistreatment by a man who has insulted the lady, agrees to help despite someone’s initial reluctance.</summary>
|
||||
<p class="scene">"Come quick. The lady, she will explain. It is the great honor you
|
||||
will have, monsieur. Ah, that milady could call upon Armand to do
|
||||
this thing! But, no, I am only one chauffeur." With vehement gestures the chauffeur conducted Hopkins into the
|
||||
house. He was ushered into a small but luxurious reception chamber. A
|
||||
lady, young, and possessing the beauty of visions, rose from a chair.
|
||||
In her eyes smouldered a becoming anger. Her high-arched, threadlike
|
||||
brows were ruffled into a delicious frown. "Milady," said the chauffeur, bowing low, "I have the honor to
|
||||
relate to you that I went to the house of Monsieur Long and found
|
||||
him to be not at home. As I came back I see this gentleman in
|
||||
combat against--how you say--greatest odds. He is fighting with
|
||||
five--ten--thirty men--gendarmes, _aussi_. Yes, milady, he what you
|
||||
call 'swat' one--three--eight policemans. If that Monsieur Long is
|
||||
out I say to myself this gentleman he will serve milady so well, and
|
||||
I bring him here." "Very well, Armand," said the lady, "you may go." She turned to
|
||||
Hopkins. "I sent my chauffeur," she said, "to bring my cousin, Walter Long.
|
||||
There is a man in this house who has treated me with insult and
|
||||
abuse. I have complain…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone wants Milady's assistance against unseen attackers, while Milady seeks a brave companion.</p><label><input type="radio" name="p12" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with courage and discretion.</p><label><input type="radio" name="p12" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p12" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="13">
|
||||
<header><span class="n">14</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone, smug after a romantic encounter, confronts a young man who had been flirting with a lady he desires.</summary>
|
||||
<p class="scene">John Hopkins thrust the remains of his cigar into his coat pocket.
|
||||
He looked upon this winning creature and felt his first thrill of
|
||||
romance. It was a knightly love, and contained no disloyalty to the
|
||||
flat with the flea-bitten terrier and the lady of his choice. He had
|
||||
married her after a picnic of the Lady Label Stickers' Union, Lodge
|
||||
No. 2, on a dare and a bet of new hats and chowder all around with
|
||||
his friend, Billy McManus. This angel who was begging him to come
|
||||
to her rescue was something too heavenly for chowder, and as for
|
||||
hats--golden, jewelled crowns for her! "Say," said John Hopkins, "just show me the guy that you've got the
|
||||
grouch at. I've neglected my talents as a scrapper heretofore, but
|
||||
this is my busy night." "He is in there," said the lady, pointing to a closed door. "Come.
|
||||
Are you sure that you do not falter or fear?" "Me?" said John Hopkins. "Just give me one of those roses in the
|
||||
bunch you are wearing, will you?" The lady gave him a red, red rose. John Hopkins kissed it, stuffed it
|
||||
into his vest pocket, opened the door and walked into the room. It
|
||||
was a handsome library, softly but brightly lighted. A young man was
|
||||
there, reading. "Books on etiquette is what you …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone seeks a romantic encounter, only to be kidnapped by a mysterious lady.</p><label><input type="radio" name="p13" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety to do so.</p><label><input type="radio" name="p13" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p13" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="14">
|
||||
<header><span class="n">15</span> <b>THE COMPLETE LIFE OF JOHN HOPKINS</b></header>
|
||||
<details><summary>Scene summary: Someone and someone argue over someone, a St. Bernard dog, while someone watches his philandering <mark>wife</mark> and <mark>daughter</mark>.</summary>
|
||||
<p class="scene">"Bess," he said calmly, "I wish you would quit reading historical
|
||||
novels. How in the world did that fellow get in here?" "Armand brought him," said the young lady. "I think you are awfully
|
||||
mean not to let me have that St. Bernard. I sent Armand for Walter. I
|
||||
was so angry with you." "Be sensible, Bess," said the young man, taking her arm. "That dog
|
||||
isn't safe. He has bitten two or three people around the kennels.
|
||||
Come now, let's go tell auntie we are in good humor again." Arm in arm, they moved away. John Hopkins walked to his flat. The janitor's five-year-old daughter
|
||||
was playing on the steps. Hopkins gave her a nice, red rose and
|
||||
walked upstairs. Mrs. Hopkins was philandering with curl-papers. "Get your cigar?" she asked, disinterestedly. "Sure," said Hopkins, "and I knocked around a while outside. It's a
|
||||
nice night." He sat upon the hornblende sofa, took out the stump of his cigar,
|
||||
lighted it, and gazed at the graceful figures in "The Storm" on the
|
||||
opposite wall. "I was telling you," said he, "about Mr. Whipple's suit. It's a gray,
|
||||
with an invisible check, and it looks fine." III</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Bess wants her St. Bernard, but someone wants her to stop reading dangerous novels.</p><label><input type="radio" name="p14" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circle.</p><label><input type="radio" name="p14" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p14" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="15">
|
||||
<header><span class="n">16</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, a 19-year-old <mark>saleslady</mark> in men's gloves, learns much about customers and human nature in the Biggest <mark>Store</mark>, while keeping her own secrets.</summary>
|
||||
<p class="scene">There, were 3,000 girls in the Biggest Store. Masie was one of them.
|
||||
She was eighteen and a saleslady in the gents' gloves. Here she
|
||||
became versed in two varieties of human beings--the kind of gents who
|
||||
buy their gloves in department stores and the kind of women who buy
|
||||
gloves for unfortunate gents. Besides this wide knowledge of the
|
||||
human species, Masie had acquired other information. She had listened
|
||||
to the promulgated wisdom of the 2,999 other girls and had stored it
|
||||
in a brain that was as secretive and wary as that of a Maltese cat.
|
||||
Perhaps nature, foreseeing that she would lack wise counsellors, had
|
||||
mingled the saving ingredient of shrewdness along with her beauty, as
|
||||
she has endowed the silver fox of the priceless fur above the other
|
||||
animals with cunning. For Masie was beautiful. She was a deep-tinted blonde, with the calm
|
||||
poise of a lady who cooks butter cakes in a window. She stood behind
|
||||
her counter in the Biggest Store; and as you closed your hand over
|
||||
the tape-line for your glove measure you thought of Hebe; and as you
|
||||
looked again you wondered how she had come by Minerva's eyes. When the floorwalker was not looking Masie chewed tutti frutti; when
|
||||
he was looking she gaze…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone, a young <mark>saleslady</mark>, seeks to navigate the complexities of human nature while hiding her true identity.</p><label><input type="radio" name="p15" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone, trapped in a monotonous job she despises, longs for a life that offers more than just the dull routine of selling gloves.</p><label><input type="radio" name="p15" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p15" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="16">
|
||||
<header><span class="n">17</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, a wealthy <mark>visitor</mark>, hesitates as floorwalker someone confronts him with a <mark>shopgirl</mark> smile, unaware of the flirtatious nature of his profession.</summary>
|
||||
<p class="scene">That is the shopgirl smile, and I enjoin you to shun it unless you
|
||||
are well fortified with callosity of the heart, caramels and a
|
||||
congeniality for the capers of Cupid. This smile belonged to Masie's
|
||||
recreation hours and not to the store; but the floorwalker must have
|
||||
his own. He is the Shylock of the stores. When he comes nosing around
|
||||
the bridge of his nose is a toll-bridge. It is goo-goo eyes or "git"
|
||||
when he looks toward a pretty girl. Of course not all floorwalkers
|
||||
are thus. Only a few days ago the papers printed news of one over
|
||||
eighty years of age. One day Irving Carter, painter, millionaire, traveller, poet,
|
||||
automobilist, happened to enter the Biggest Store. It is due to him
|
||||
to add that his visit was not voluntary. Filial duty took him by the
|
||||
collar and dragged him inside, while his mother philandered among the
|
||||
bronze and terra-cotta statuettes. Carter strolled across to the glove counter in order to shoot a
|
||||
few minutes on the wing. His need for gloves was genuine; he had
|
||||
forgotten to bring a pair with him. But his action hardly calls for
|
||||
apology, because he had never heard of glove-counter flirtations. As he neared the vicinity of his fate he hesitated, suddenly
|
||||
conscious o…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The <mark>shopgirl</mark> someone confronts the floorwalker someone with her flirtatious gaze, testing his resolve.</p><label><input type="radio" name="p16" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man finds himself trapped in an awkward encounter with a <mark>shopgirl</mark> whose icy beauty challenges his composure, forcing him to navigate the delicate line between professional decorum and unexpected attraction.</p><label><input type="radio" name="p16" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p16" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="17">
|
||||
<header><span class="n">18</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, <mark>millionaire</mark>, felt both flattered and awkward as he encountered someone, a <mark>shopgirl</mark>, in a glove <mark>shop</mark>, realizing he had no social means to propose to her.</summary>
|
||||
<p class="scene">And then Irving Carter, painter, millionaire, etc., felt a warm flush
|
||||
rise to his aristocratically pale face. But not from diffidence. The
|
||||
blush was intellectual in origin. He knew in a moment that he stood
|
||||
in the ranks of the ready-made youths who wooed the giggling girls at
|
||||
other counters. Himself leaned against the oaken trysting place of
|
||||
a cockney Cupid with a desire in his heart for the favor of a glove
|
||||
salesgirl. He was no more than Bill and Jack and Mickey. And then he
|
||||
felt a sudden tolerance for them, and an elating, courageous contempt
|
||||
for the conventions upon which he had fed, and an unhesitating
|
||||
determination to have this perfect creature for his own. When the gloves were paid for and wrapped Carter lingered for a
|
||||
moment. The dimples at the corners of Masie's damask mouth deepened.
|
||||
All gentlemen who bought gloves lingered in just that way. She curved
|
||||
an arm, showing like Psyche's through her shirt-waist sleeve, and
|
||||
rested an elbow upon the show-case edge. Carter had never before encountered a situation of which he had not
|
||||
been perfect master. But now he stood far more awkward than Bill
|
||||
or Jack or Mickey. He had no chance of meeting this beautiful girl
|
||||
socially. His mind …</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone desires to charm a <mark>shopgirl</mark> despite feeling out of place.</p><label><input type="radio" name="p17" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggling with his pride and social conventions finds himself awkwardly drawn to a <mark>shopgirl</mark>, determined to win her favor despite his usual confidence.</p><label><input type="radio" name="p17" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p17" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="18">
|
||||
<header><span class="n">19</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, a lady in her late twenties, diplomatically turns down a man's romantic overtures while subtly arranging a clandestine meeting with him.</summary>
|
||||
<p class="scene">"Will you please pardon me," he said, "if I seem too bold; but I
|
||||
earnestly hope you will allow me the pleasure of seeing you again.
|
||||
There is my name; I assure you that it is with the greatest respect
|
||||
that I ask the favor of becoming one of your fr--acquaintances. May
|
||||
I not hope for the privilege?" Masie knew men--especially men who buy gloves. Without hesitation she
|
||||
looked him frankly and smilingly in the eyes, and said: "Sure. I guess you're all right. I don't usually go out with strange
|
||||
gentlemen, though. It ain't quite ladylike. When should you want to
|
||||
see me again?" "As soon as I may," said Carter. "If you would allow me to call at
|
||||
your home, I--" Masie laughed musically. "Oh, gee, no!" she said, emphatically. "If
|
||||
you could see our flat once! There's five of us in three rooms. I'd
|
||||
just like to see ma's face if I was to bring a gentleman friend
|
||||
there!" "Anywhere, then," said the enamored Carter, "that will be convenient
|
||||
to you." "Say," suggested Masie, with a bright-idea look in her peach-blow
|
||||
face; "I guess Thursday night will about suit me. Suppose you come to
|
||||
the corner of Eighth Avenue and Forty-eighth Street at 7:30. I live
|
||||
right near the corner. But I've got to be back hom…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Willie nervously asked for her company, hoping she'd agree to a date.</p><label><input type="radio" name="p18" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to explore new possibilities without disappointing her family or losing her sense of self.</p><label><input type="radio" name="p18" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p18" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="19">
|
||||
<header><span class="n">20</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, a young man, eagerly awaits a date with a <mark>street</mark>-corner <mark>shopgirl</mark>, while his <mark>mother</mark> chauffeurs him to a fancy <mark>restaurant</mark>.</summary>
|
||||
<p class="scene">"Permission to call!" echoed small eyes, with a snigger. "Did he say
|
||||
anything about dinner in the Waldorf and a spin in his auto
|
||||
afterward?" "Oh, cheese it!" said Masie, wearily. "You've been used to swell
|
||||
things, I don't think. You've had a swelled head ever since that
|
||||
hose-cart driver took you out to a chop suey joint. No, he never
|
||||
mentioned the Waldorf; but there's a Fifth Avenue address on his
|
||||
card, and if he buys the supper you can bet your life there won't be
|
||||
no pigtail on the waiter what takes the order." As Carter glided away from the Biggest Store with his mother in his
|
||||
electric runabout, he bit his lip with a dull pain at his heart.
|
||||
He knew that love had come to him for the first time in all the
|
||||
twenty-nine years of his life. And that the object of it should make
|
||||
so readily an appointment with him at a street corner, though it was
|
||||
a step toward his desires, tortured him with misgivings. Carter did not know the shopgirl. He did not know that her home is
|
||||
often either a scarcely habitable tiny room or a domicile filled to
|
||||
overflowing with kith and kin. The street-corner is her parlor, the
|
||||
park is her drawing-room; the avenue is her garden walk; yet for the
|
||||
most part she is a…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone seeks love but fears rejection, while someone uses him to prove her worth.</p><label><input type="radio" name="p19" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life struggles to navigate the uncertain path of love.</p><label><input type="radio" name="p19" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p19" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="20">
|
||||
<header><span class="n">21</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, a <mark>suitor</mark>, pressures someone into marriage, emphasizing his social superiority and wealth, while someone questions his sincerity and social status.</summary>
|
||||
<p class="scene">"Masie," said Carter, earnestly, "you surely know that I love you. I
|
||||
ask you sincerely to marry me. You know me well enough by this time
|
||||
to have no doubts of me. I want you, and I must have you. I care
|
||||
nothing for the difference in our stations." "What is the difference?" asked Masie, curiously. "Well, there isn't any," said Carter, quickly, "except in the minds
|
||||
of foolish people. It is in my power to give you a life of luxury. My
|
||||
social position is beyond dispute, and my means are ample." "They all say that," remarked Masie. "It's the kid they all give you.
|
||||
I suppose you really work in a delicatessen or follow the races. I
|
||||
ain't as green as I look." "I can furnish you all the proofs you want," said Carter, gently.
|
||||
"And I want you, Masie. I loved you the first day I saw you." "They all do," said Masie, with an amused laugh, "to hear 'em talk.
|
||||
If I could meet a man that got stuck on me the third time he'd seen
|
||||
me I think I'd get mashed on him." "Please don't say such things," pleaded Carter. "Listen to me, dear.
|
||||
Ever since I first looked into your eyes you have been the only woman
|
||||
in the world for me." "Oh, ain't you the kidder!" smiled Masie. "How many other girls did
|
||||
you ever tell…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone faces someone’s marriage proposal, wanting to see through his sincerity.</p><label><input type="radio" name="p20" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his words and mocks his desperate, insincere pursuit.</p><label><input type="radio" name="p20" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p20" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="21">
|
||||
<header><span class="n">22</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone persistently courted someone, a <mark>shopgirl</mark>, offering her a life of leisure in exotic locations to break her from her mundane existence.</summary>
|
||||
<p class="scene">But Carter persisted. And at length he reached the flimsy, fluttering
|
||||
little soul of the shopgirl that existed somewhere deep down in her
|
||||
lovely bosom. His words penetrated the heart whose very lightness was
|
||||
its safest armor. She looked up at him with eyes that saw. And a warm
|
||||
glow visited her cool cheeks. Tremblingly, awfully, her moth wings
|
||||
closed, and she seemed about to settle upon the flower of love. Some
|
||||
faint glimmer of life and its possibilities on the other side of her
|
||||
glove counter dawned upon her. Carter felt the change and crowded the
|
||||
opportunity. "Marry me, Masie," he whispered softly, "and we will go away from
|
||||
this ugly city to beautiful ones. We will forget work and business,
|
||||
and life will be one long holiday. I know where I should take you--I
|
||||
have been there often. Just think of a shore where summer is eternal,
|
||||
where the waves are always rippling on the lovely beach and the
|
||||
people are happy and free as children. We will sail to those shores
|
||||
and remain there as long as you please. In one of those far-away
|
||||
cities there are grand and lovely palaces and towers full of
|
||||
beautiful pictures and statues. The streets of the city are water,
|
||||
and one travels about in--" "I know,…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone looked up at someone, her eyes alight with a flicker of possibility.</p><label><input type="radio" name="p21" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain journey with him.</p><label><input type="radio" name="p21" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p21" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="22">
|
||||
<header><span class="n">23</span> <b>A LICKPENNY LOVER</b></header>
|
||||
<details><summary>Scene summary: Someone, cool in his authority, insists they leave while someone’s uncertain nature holds sway, hinting at her potential marriage proposal to someone’s friend.</summary>
|
||||
<p class="scene">"I think we had better be going home," she said, coolly. "It's
|
||||
getting late." Carter humored her. He had come to know her varying, thistle-down
|
||||
moods, and that it was useless to combat them. But he felt a certain
|
||||
happy triumph. He had held for a moment, though but by a silken
|
||||
thread, the soul of his wild Psyche, and hope was stronger within
|
||||
him. Once she had folded her wings and her cool hand had closed about
|
||||
his own. At the Biggest Store the next day Masie's chum, Lulu, waylaid her in
|
||||
an angle of the counter. "How are you and your swell friend making it? she asked. "Oh, him?" said Masie, patting her side curls. "He ain't in it any
|
||||
more. Say, Lu, what do you think that fellow wanted me to do?" "Go on the stage?" guessed Lulu, breathlessly. "Nit; he's too cheap a guy for that. He wanted me to marry him and go
|
||||
down to Coney Island for a wedding tour!" IV</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>She wants to end their secret affair, but he feels a hidden joy.</p><label><input type="radio" name="p22" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connection without giving in to the possibility of love.</p><label><input type="radio" name="p22" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p22" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="23">
|
||||
<header><span class="n">24</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: Big someone coveted social acceptance from the titled nobility of someone’s sport world.</summary>
|
||||
<p class="scene">Big Jim Dougherty was a sport. He belonged to that race of men.
|
||||
In Manhattan it is a distinct race. They are the Caribs of the
|
||||
North--strong, artful, self-sufficient, clannish, honorable within
|
||||
the laws of their race, holding in lenient contempt neighboring
|
||||
tribes who bow to the measure of Society's tapeline. I refer, of
|
||||
course, to the titled nobility of sportdom. There is a class which
|
||||
bears as a qualifying adjective the substantive belonging to a wind
|
||||
instrument made of a cheap and base metal. But the tin mines of
|
||||
Cornwall never produced the material for manufacturing descriptive
|
||||
nomenclature for "Big Jim" Dougherty. The habitat of the sport is the lobby or the outside corner of
|
||||
certain hotels and combination restaurants and cafés. They are mostly
|
||||
men of different sizes, running from small to large; but they are
|
||||
unanimous in the possession of a recently shaven, blue-black cheek
|
||||
and chin and dark overcoats (in season) with black velvet collars. Of the domestic life of the sport little is known. It has been said
|
||||
that Cupid and Hymen sometimes take a hand in the game and copper the
|
||||
queen of hearts to lose. Daring theorists have averred--not content
|
||||
with simply saying--that a sport o…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Big someone sought acceptance in someone’s titled nobility of sportdom.</p><label><input type="radio" name="p23" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Big someone must navigate the rigid codes of his underground world while seeking a way to break free and claim a life beyond the shadows.</p><label><input type="radio" name="p23" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p23" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="24">
|
||||
<header><span class="n">25</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: Big someone" attends social gatherings with his fellow someone, avoiding his <mark>wife</mark> at home.</summary>
|
||||
<p class="scene">But mostly the sport is Oriental. He believes his women-folk should
|
||||
not be too patent. Somewhere behind grilles or flower-ornamented fire
|
||||
escapes they await him. There, no doubt, they tread on rugs from
|
||||
Teheran and are diverted by the bulbul and play upon the dulcimer and
|
||||
feed upon sweetmeats. But away from his home the sport is an integer.
|
||||
He does not, as men of other races in Manhattan do, become the convoy
|
||||
in his unoccupied hours of fluttering laces and high heels that tick
|
||||
off delectably the happy seconds of the evening parade. He herds with
|
||||
his own race at corners, and delivers a commentary in his Carib lingo
|
||||
upon the passing show. "Big Jim" Dougherty had a wife, but he did not wear a button portrait
|
||||
of her upon his lapel. He had a home in one of those brown-stone,
|
||||
iron-railed streets on the west side that look like a recently
|
||||
excavated bowling alley of Pompeii. To this home of his Mr. Dougherty repaired each night when the hour
|
||||
was so late as to promise no further diversion in the arch domains
|
||||
of sport. By that time the occupant of the monogamistic harem would
|
||||
be in dreamland, the bulbul silenced and the hour propitious for
|
||||
slumber. "Big Jim" always arose at twelve, meridian,…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>He seeks a private life away from his public Oriental sport, yet feels trapped in public life.</p><label><input type="radio" name="p24" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggles to balance his cultural identity and personal life while navigating the expectations of his community and the quiet routine of his domestic existence.</p><label><input type="radio" name="p24" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p24" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="25">
|
||||
<header><span class="n">26</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: Big someone" someone reluctantly agreed to go out for <mark>dinner</mark> after his <mark>wife</mark> insisted, challenging his usual reluctance to leave the <mark>house</mark>.</summary>
|
||||
<p class="scene">"Big Jim" Dougherty even listened to this conversation of hers
|
||||
sometimes. He knew that she would have a nice dinner ready for him
|
||||
every evening at seven when he came for it. She sometimes went to
|
||||
matinées, and she had a talking machine with six dozen records. Once
|
||||
when her Uncle Amos blew in on a wind from up-state, she went with
|
||||
him to the Eden Musée. Surely these things were diversions enough for
|
||||
any woman. One afternoon Mr. Dougherty finished his breakfast, put on his hat
|
||||
and got away fairly for the door. When his hand was on the knob be
|
||||
heard his wife's voice. "Jim," she said, firmly, "I wish you would take me out to dinner this
|
||||
evening. It has been three years since you have been outside the door
|
||||
with me." "Big Jim" was astounded. She had never asked anything like this
|
||||
before. It had the flavour of a totally new proposition. But he was a
|
||||
game sport. "All right," he said. "You be ready when I come at seven. None of
|
||||
this 'wait two minutes till I primp an hour or two' kind of business,
|
||||
now, Dele." "I'll be ready," said his wife, calmly. At seven she descended the stone steps in the Pompeian bowling alley
|
||||
at the side of "Big Jim" Dougherty. She wore a dinner gown made of
|
||||
a stuff t…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Big someone" expected her to ask for a <mark>dinner</mark> date, not insist on one.</p><label><input type="radio" name="p25" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsider the balance between habit and connection.</p><label><input type="radio" name="p25" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p25" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="26">
|
||||
<header><span class="n">27</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: Big someone, unsure of his new <mark>wife</mark>'s identity, decided to flaunt her at his usual haunts to impress his social circle.</summary>
|
||||
<p class="scene">"Big Jim" Dougherty was troubled. There was a being at his side whom
|
||||
he did not know. He thought of the sober-hued plumage that this bird
|
||||
of paradise was accustomed to wear in her cage, and this winged
|
||||
revelation puzzled him. In some way she reminded him of the Delia
|
||||
Cullen that he had married four years before. Shyly and rather
|
||||
awkwardly he stalked at her right hand. "After dinner I'll take you back home, Dele," said Mr. Dougherty,
|
||||
"and then I'll drop back up to Seltzer's with the boys. You can have
|
||||
swell chuck to-night if you want it. I made a winning on Anaconda
|
||||
yesterday; so you can go as far as you like." Mr. Dougherty had intended to make the outing with his unwonted wife
|
||||
an inconspicuous one. Uxoriousness was a weakness that the precepts
|
||||
of the Caribs did not countenance. If any of his friends of the
|
||||
track, the billiard cloth or the square circle had wives they had
|
||||
never complained of the fact in public. There were a number of table
|
||||
d'hôte places on the cross streets near the broad and shining way;
|
||||
and to one of these he had purposed to escort her, so that the bushel
|
||||
might not be removed from the light of his domesticity. But while on the way Mr. Dougherty altered those inte…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Big someone wanted to hide his <mark>wife</mark>'s presence, but a mysterious bird-like being at her side made him change plans.</p><label><input type="radio" name="p26" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggling to reconcile his desire to display his <mark>wife</mark> publicly with his fear of judgment finds himself torn between pride and secrecy.</p><label><input type="radio" name="p26" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p26" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="27">
|
||||
<header><span class="n">28</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: Seltzer's tribal gentlemen watch as someone and his <mark>wife</mark> someone enter, impressed but wary, while the guests enjoy a lively meal.</summary>
|
||||
<p class="scene">The congregation of smooth-faced tribal gentlemen were on watch at
|
||||
Seltzer's. As Mr. Dougherty and his reorganized Delia passed they
|
||||
stared, momentarily petrified, and then removed their hats--a
|
||||
performance as unusual to them as was the astonishing innovation
|
||||
presented to their gaze by "Big Jim". On the latter gentleman's
|
||||
impassive face there appeared a slight flicker of triumph--a faint
|
||||
flicker, no more to be observed than the expression called there by
|
||||
the draft of little casino to a four-card spade flush. Hoogley's was animated. Electric lights shone as, indeed, they were
|
||||
expected to do. And the napery, the glassware and the flowers also
|
||||
meritoriously performed the spectacular duties required of them. The
|
||||
guests were numerous, well-dressed and gay. A waiter--not necessarily obsequious--conducted "Big Jim" Dougherty
|
||||
and his wife to a table. "Play that menu straight across for what you like, Dele," said "Big
|
||||
Jim." "It's you for a trough of the gilded oats to-night. It strikes
|
||||
me that maybe we've been sticking too fast to home fodder." "Big Jim's" wife gave her order. He looked at her with respect. She
|
||||
had mentioned truffles; and he had not known that she knew what
|
||||
truffles were. F…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Big someone seeks to impress his <mark>wife</mark> at a fancy <mark>restaurant</mark>, hoping her beauty and charm will win him praise.</p><label><input type="radio" name="p27" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggles to reconcile his pride with the growing awareness of his <mark>wife</mark>'s hidden talents and the shame of having underestimated her.</p><label><input type="radio" name="p27" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p27" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="28">
|
||||
<header><span class="n">29</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The Honorable someone, seeking to impress, engages the reluctant someone in a lively conversation, transforming her into the center of attention as other politicians and acquaintances arrive.</summary>
|
||||
<p class="scene">But when the Honorable Patrick Corrigan, leader in Dougherty's
|
||||
district and a friend of his, saw them and came over to the table,
|
||||
matters got to the three-quarter stretch. The Honorable Patrick was a
|
||||
gallant man, both in deeds and words. As for the Blarney stone, his
|
||||
previous actions toward it must have been pronounced. Heavy damages
|
||||
for breach of promise could surely have been obtained had the Blarney
|
||||
stone seen fit to sue the Honorable Patrick. "Jimmy, old man!" he called; he clapped Dougherty on the back; he
|
||||
shone like a midday sun upon Delia. "Honorable Mr. Corrigan--Mrs. Dougherty," said "Big Jim." The Honorable Patrick became a fountain of entertainment and
|
||||
admiration. The waiter had to fetch a third chair for him; he made
|
||||
another at the table, and the wineglasses were refilled. "You selfish old rascal!" he exclaimed, shaking an arch finger at
|
||||
"Big Jim," "to have kept Mrs. Dougherty a secret from us." And then "Big Jim" Dougherty, who was no talker, sat dumb, and saw
|
||||
the wife who had dined every evening for three years at home, blossom
|
||||
like a fairy flower. Quick, witty, charming, full of light and ready
|
||||
talk, she received the experienced attack of the Honorable Patrick on
|
||||
the…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The honorable someone seeks to expose a secret and win someone’s heart, while "Big someone" struggles to keep up.</p><label><input type="radio" name="p28" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggles to reconcile his quiet life with the sudden, overwhelming attention his <mark>wife</mark> receives, threatening to expose the hidden depths of their marriage.</p><label><input type="radio" name="p28" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p28" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="29">
|
||||
<header><span class="n">30</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The Honorable someone, seeking to impress someone, attempts to seduce her, leading to a confrontation and a decision to take her to a pub instead.</summary>
|
||||
<p class="scene">The dinner came to an end. The Honorable Patrick reached for Mrs.
|
||||
Dougherty's cloak; but that was a matter of action instead of words,
|
||||
and Dougherty's big hand got it first by two seconds. While the farewells were being said at the door the Honorable Patrick
|
||||
smote Dougherty mightily between the shoulders. "Jimmy, me boy," he declared, in a giant whisper, "the madam is a
|
||||
jewel of the first water. Ye're a lucky dog." "Big Jim" walked homeward with his wife. She seemed quite as
|
||||
pleased with the lights and show windows in the streets as with the
|
||||
admiration of the men in Hoogley's. As they passed Seltzer's they
|
||||
heard the sound of many voices in the café. The boys would be
|
||||
starting the drinks around now and discussing past performances. At the door of their home Delia paused. The pleasure of the outing
|
||||
radiated softly from her countenance. She could not hope for Jim of
|
||||
evenings, but the glory of this one would lighten her lonely hours
|
||||
for a long time. "Thank you for taking me out, Jim," she said, gratefully. "You'll be
|
||||
going back up to Seltzer's now, of course." "To ---- with Seltzer's," said "Big Jim," emphatically. "And d----
|
||||
Pat Corrigan! Does he think I haven't got any eyes?" And the…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone seeks to impress someone with a bold gesture, but is thwarted by her quicker action.</p><label><input type="radio" name="p29" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man struggles to maintain his pride and dignity after a public embarrassment, while trying to preserve his relationship and quiet his <mark>wife</mark>'s growing happiness.</p><label><input type="radio" name="p29" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p29" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="30">
|
||||
<header><span class="n">31</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The <mark>bride</mark> desires a peach from her distant <mark>lover</mark>, while he holds power over her marriage and her desires.</summary>
|
||||
<p class="scene">The bride sat in the rocker with her feet resting upon the world. She
|
||||
was wrapt in rosy dreams and a kimono of the same hue. She wondered
|
||||
what the people in Greenland and Tasmania and Beloochistan were
|
||||
saying one to another about her marriage to Kid McGarry. Not that it
|
||||
made any difference. There was no welter-weight from London to the
|
||||
Southern Cross that could stand up four hours--no; four rounds--with
|
||||
her bridegroom. And he had been hers for three weeks; and the crook
|
||||
of her little finger could sway him more than the fist of any
|
||||
142-pounder in the world. Love, when it is ours, is the other name for self-abnegation and
|
||||
sacrifice. When it belongs to people across the airshaft it means
|
||||
arrogance and self-conceit. The bride crossed her oxfords and looked thoughtfully at the
|
||||
distemper Cupids on the ceiling. "Precious," said she, with the air of Cleopatra asking Antony for
|
||||
Rome done up in tissue paper and delivered at residence, "I think
|
||||
I would like a peach." Kid McGarry arose and put on his coat and hat. He was serious,
|
||||
shaven, sentimental, and spry. "All right," said he, as coolly as though he were only agreeing to
|
||||
sign articles to fight the champion of England. "I'll step down and
|
||||
…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The <mark>bride</mark> desires her <mark>husband</mark>'s affection, yet fears losing him to distant admirers.</p><label><input type="radio" name="p30" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A woman trapped in a love that bends her will longs for a simple peach, while her powerful but obedient <mark>husband</mark> struggles to fulfill her wish in a world that resists giving way.</p><label><input type="radio" name="p30" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p30" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="31">
|
||||
<header><span class="n">32</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The Kid seeks a peach for a <mark>lover</mark>, vying with a fruit vendor and a reluctant someone, who ultimately agrees to find oranges instead.</summary>
|
||||
<p class="scene">"Gotta da peach?" asked the Kid in the tongue of Dante, the lover of
|
||||
lovers. "Ah, no,--" sighed the vender. "Not for one mont com-a da peach. Too
|
||||
soon. Gotta da nice-a orange. Like-a da orange?" Scornful, the Kid pursued his quest. He entered the all-night
|
||||
chop-house, café, and bowling-alley of his friend and admirer, Justus
|
||||
O'Callahan. The O'Callahan was about in his institution, looking for
|
||||
leaks. "I want it straight," said the Kid to him. "The old woman has got a
|
||||
hunch that she wants a peach. Now, if you've got a peach, Cal, get it
|
||||
out quick. I want it and others like it if you've got 'em in plural
|
||||
quantities." "The house is yours," said O'Callahan. "But there's no peach in it.
|
||||
It's too soon. I don't suppose you could even find 'em at one of the
|
||||
Broadway joints. That's too bad. When a lady fixes her mouth for a
|
||||
certain kind of fruit nothing else won't do. It's too late now to
|
||||
find any of the first-class fruiterers open. But if you think the
|
||||
missis would like some nice oranges I've just got a box of fine ones
|
||||
in that she might--" "Much obliged, Cal. It's a peach proposition right from the ring of
|
||||
the gong. I'll try further." The time was nearly midnight as the Kid walked down the…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The Kid seeks a peach but finds oranges instead, rushing to a fruiterer just as the lights go out.</p><label><input type="radio" name="p31" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>The Kid needs to find a peach for a woman who insists on it, but every vendor says it's too soon and offers only oranges instead.</p><label><input type="radio" name="p31" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p31" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="32">
|
||||
<header><span class="n">33</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The Kid seeks employment, while the <mark>Captain</mark> of Police considers helping him infiltrate gambling den someone’s, despite previous conflicts.</summary>
|
||||
<p class="scene">"Peaches?" said he, with extreme deliberation. "Well, no, Sir. Not for three or four weeks yet. I haven't any idea
|
||||
where you might find some. There may be a few in town from under
|
||||
the glass, but they'd be hard to locate. Maybe at one of the more
|
||||
expensive hotels--some place where there's plenty of money to waste.
|
||||
I've got some very fine oranges, though--from a shipload that came in
|
||||
to-day." The Kid lingered on the corner for a moment, and then set out briskly
|
||||
toward a pair of green lights that flanked the steps of a building
|
||||
down a dark side street. "Captain around anywhere?" he asked of the desk sergeant of the
|
||||
police station. At that moment the captain came briskly forward from the rear. He was
|
||||
in plain clothes and had a busy air. "Hello, Kid," he said to the pugilist. "Thought you were
|
||||
bridal-touring? "Got back yesterday. I'm a solid citizen now. Think I'll take an
|
||||
interest in municipal doings. How would it suit you to get into
|
||||
Denver Dick's place to-night, Cap? "Past performances," said the captain, twisting his moustache.
|
||||
"Denver was closed up two months ago." "Correct," said the Kid. "Rafferty chased him out of the Forty-third.
|
||||
He's running in your precinct now, and his game'…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Kid seeks to infiltrate someone’s gambling den but finds it closed, needing help from the <mark>captain</mark>.</p><label><input type="radio" name="p32" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>The Kid needs to convince the <mark>captain</mark> to raid a gambling den controlled by an old enemy, but he must do so quickly before the enemy discovers his betrayal.</p><label><input type="radio" name="p32" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p32" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="33">
|
||||
<header><span class="n">34</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The <mark>captain</mark> and his dozen men, led by the Kid, raided a dark building, encountering a heated game of chance and a <mark>rival</mark> gang, before capturing the premises.</summary>
|
||||
<p class="scene">Before ten minutes had sped the captain with a dozen men stole with
|
||||
their guide into the hallway of a dark and virtuous-looking building
|
||||
in which many businesses were conducted by day. "Third floor, rear," said the Kid, softly. "I'll lead the way." Two axemen faced the door that he pointed out to them. "It seems all quiet," said the captain, doubtfully. "Are you sure
|
||||
your tip is straight?" "Cut away!" said the Kid. "It's on me if it ain't." The axes crashed through the as yet unprotected door. A blaze of
|
||||
light from within poured through the smashed panels. The door fell,
|
||||
and the raiders sprang into the room with their guns handy. The big room was furnished with the gaudy magnificence dear to Denver
|
||||
Dick's western ideas. Various well-patronized games were in progress.
|
||||
About fifty men who were in the room rushed upon the police in a
|
||||
grand break for personal liberty. The plain-clothes men had to do a
|
||||
little club-swinging. More than half the patrons escaped. Denver Dick had graced his game with his own presence that night.
|
||||
He led the rush that was intended to sweep away the smaller body of
|
||||
raiders, But when he saw the Kid his manner became personal. Being
|
||||
in the heavyweight class he ca…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The <mark>captain</mark> seeks to lead a raid, but doubts success as the Kid's plan unfolds.</p><label><input type="radio" name="p33" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man must prove his worth in a dangerous confrontation to claim a prize he's risking everything for.</p><label><input type="radio" name="p33" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p33" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="34">
|
||||
<header><span class="n">35</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The Kid demands control of a lavish <mark>dinner</mark> <mark>party</mark>, suspects theft, and retrieves the last peach, threatening violence if it happens again.</summary>
|
||||
<p class="scene">Here was a long table set with choicest chinaware and silver, and
|
||||
lavishly furnished with food of that expensive and spectacular sort
|
||||
of which the devotees of sport are supposed to be fond. Here again
|
||||
was to be perceived the liberal and florid taste of the gentleman
|
||||
with the urban cognomenal prefix. A No. 10 patent leather shoe protruded a few of its inches outside
|
||||
the tablecloth along the floor. The Kid seized this and plucked forth
|
||||
a black man in a white tie and the garb of a servitor. "Get up!" commanded the Kid. "Are you in charge of this free lunch?" "Yes, sah, I was. Has they done pinched us ag'in, boss?" "Looks that way. Listen to me. Are there any peaches in this layout?
|
||||
If there ain't I'll have to throw up the sponge." "There was three dozen, sah, when the game opened this evenin'; but
|
||||
I reckon the gentlemen done eat 'em all up. If you'd like to eat a
|
||||
fust-rate orange, sah, I kin find you some." "Get busy," ordered the Kid, sternly, "and move whatever peach crop
|
||||
you've got quick or there'll be trouble. If anybody oranges me again
|
||||
to-night, I'll knock his face off." The raid on Denver Dick's high-priced and prodigal luncheon revealed
|
||||
one lone, last peach that had escaped th…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The Kid seeks a last peach at a lavish <mark>dinner</mark>, fearing theft, while authorities arrive.</p><label><input type="radio" name="p34" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man desperate to find a peach before it's too late races against time and rivals to secure his prize before it vanishes entirely.</p><label><input type="radio" name="p34" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p34" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="35">
|
||||
<header><span class="n">36</span> <b>DOUGHERTY'S EYE-OPENER</b></header>
|
||||
<details><summary>Scene summary: The Knight returns to someone with a stolen peach for his <mark>bride</mark>, who prefers an orange.</summary>
|
||||
<p class="scene">His heart was light as he went. So rode the knights back to Camelot
|
||||
after perils and high deeds done for their ladies fair. The Kid's
|
||||
lady had commanded him and he had obeyed. True, it was but a peach
|
||||
that she had craved; but it had been no small deed to glean a peach
|
||||
at midnight from that wintry city where yet the February snows lay
|
||||
like iron. She had asked for a peach; she was his bride; in his
|
||||
pocket the peach was warming in his hand that held it for fear that
|
||||
it might fall out and be lost. On the way the Kid turned in at an all-night drug store and said to
|
||||
the spectacled clerk: "Say, sport, I wish you'd size up this rib of mine and see if it's
|
||||
broke. I was in a little scrap and bumped down a flight or two of
|
||||
stairs." The druggist made an examination. "It isn't broken," was his
|
||||
diagnosis, "but you have a bruise there that looks like you'd fallen
|
||||
off the Flatiron twice." "That's all right," said the Kid. "Let's have your clothesbrush,
|
||||
please." The bride waited in the rosy glow of the pink lamp shade. The
|
||||
miracles were not all passed away. By breathing a desire for some
|
||||
slight thing--a flower, a pomegranate, a--oh, yes, a peach--she could
|
||||
send forth her man into the night, into th…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>The Kid seeks to please his <mark>bride</mark> by delivering a requested peach, despite its trivial nature.</p><label><input type="radio" name="p35" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>He risks danger to fetch a trivial gift for his <mark>bride</mark>, only to find her preference has changed.</p><label><input type="radio" name="p35" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p35" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="36">
|
||||
<header><span class="n">37</span> <b>THE HARBINGER</b></header>
|
||||
<details><summary>Scene summary: The city <mark>editor</mark> reads news from across the country while the farmer sees only winter on his fields, unaware that someone’s search for someone signals the arrival of spring.</summary>
|
||||
<p class="scene">Long before the springtide is felt in the dull bosom of the yokel
|
||||
does the city man know that the grass-green goddess is upon her
|
||||
throne. He sits at his breakfast eggs and toast, begirt by stone
|
||||
walls, opens his morning paper and sees journalism leave vernalism
|
||||
at the post. For, whereas, spring's couriers were once the evidence of our finer
|
||||
senses, now the Associated Press does the trick. The warble of the first robin in Hackensack, the stirring of the
|
||||
maple sap in Bennington, the budding of the pussy willows along Main
|
||||
Street in Syracuse, the first chirp of the bluebird, the swan song of
|
||||
the Blue Point, the annual tornado in St. Louis, the plaint of the
|
||||
peach pessimist from Pompton, N. J., the regular visit of the tame
|
||||
wild goose with a broken leg to the pond near Bilgewater Junction,
|
||||
the base attempt of the Drug Trust to boost the price of quinine
|
||||
foiled in the House by Congressman Jinks, the first tall poplar
|
||||
struck by lightning and the usual stunned picknickers who had taken
|
||||
refuge, the first crack of the ice jam in the Allegheny River, the
|
||||
finding of a violet in its mossy bed by the correspondent at Round
|
||||
Corners--these are the advance signs of the burgeoning season that
|
||||
are w…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Stuck in stone walls, city man craves nature's first signs to confirm spring's arrival.</p><label><input type="radio" name="p36" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man trapped in the monotony of city life longs for the genuine signs of spring, but finds only artificial reports replacing nature's quiet signals.</p><label><input type="radio" name="p36" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p36" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="37">
|
||||
<header><span class="n">38</span> <b>THE HARBINGER</b></header>
|
||||
<details><summary>Scene summary: Someone, desperate for his <mark>wife</mark>'s dollar, conspires with someone and someone to steal it from their married friends in Union <mark>Square</mark>.</summary>
|
||||
<p class="scene">Ere the first violet blew, Mr. Peters, Mr. Ragsdale and Mr. Kidd sat
|
||||
together on a bench in Union Square and conspired. Mr. Peters was the
|
||||
D'Artagnan of the loafers there. He was the dingiest, the laziest,
|
||||
the sorriest brown blot against the green background of any bench in
|
||||
the park. But just then he was the most important of the trio. Mr. Peters had a wife. This had not heretofore affected his standing
|
||||
with Ragsy and Kidd. But to-day it invested him with a peculiar
|
||||
interest. His friends, having escaped matrimony, had shown a
|
||||
disposition to deride Mr. Peters for his venture on that troubled
|
||||
sea. But at last they had been forced to acknowledge that either
|
||||
he had been gifted with a large foresight or that he was one of
|
||||
Fortune's lucky sons. For, Mrs. Peters had a dollar. A whole dollar bill, good and
|
||||
receivable by the Government for customs, taxes and all public
|
||||
dues. How to get possession of that dollar was the question up for
|
||||
discussion by the three musty musketeers. "How do you know it was a dollar?" asked Ragsy, the immensity of the
|
||||
sum inclining him to scepticism. "The coalman seen her have it," said Mr. Peters. "She went out
|
||||
and done some washing yesterday. And look what she gi…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone wants the dollar his <mark>wife</mark> has, but fears losing face among friends.</p><label><input type="radio" name="p37" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Three men plot to steal a dollar from someone’s <mark>wife</mark>, who unknowingly possesses it, forcing him to confront his friends' greed and protect her from their violent intentions.</p><label><input type="radio" name="p37" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p37" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="38">
|
||||
<header><span class="n">39</span> <b>THE HARBINGER</b></header>
|
||||
<details><summary>Scene summary: Someone threatens to leave someone to seek payment from their adulterous <mark>daughter</mark>-in-law someone.</summary>
|
||||
<p class="scene">"Maguire," said Ragsy, pointedly, "has got his bock beer sign out. If
|
||||
we had a dollar we could--" "Hush up!" said Mr. Peters, licking his lips. "We got to get that
|
||||
case note somehow, boys. Ain't what's a man's wife's his? Leave it to
|
||||
me. I'll go over to the house and get it. Wait here for me." "I've seen 'em give up quick, and tell you where it's hid if you kick
|
||||
'em in the ribs," said Kidd. "No man would kick a woman," said Peters, virtuously. "A little
|
||||
choking--just a touch on the windpipe--that gets away with 'em--and
|
||||
no marks left. Wait for me. I'll bring back that dollar, boys." High up in a tenement-house between Second Avenue and the river lived
|
||||
the Peterses in a back room so gloomy that the landlord blushed to
|
||||
take the rent for it. Mrs. Peters worked at sundry times, doing odd
|
||||
jobs of scrubbing and washing. Mr. Peters had a pure, unbroken record
|
||||
of five years without having earned a penny. And yet they clung
|
||||
together, sharing each other's hatred and misery, being creatures
|
||||
of habit. Of habit, the power that keeps the earth from flying to
|
||||
pieces; though there is some silly theory of gravitation. Mrs. Peters reposed her 200 pounds on the safer of the two chairs and
|
||||
gazed stoli…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone wants the dollar, but someone plans to get the case note instead.</p><label><input type="radio" name="p38" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>Someone must steal a case note from his <mark>wife</mark>'s hidden stash to buy a dollar for beer, but he refuses to harm her despite his desperation.</p><label><input type="radio" name="p38" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p38" value="n"> neither is a predicament</label>
|
||||
</section><section data-i="39">
|
||||
<header><span class="n">40</span> <b>THE HARBINGER</b></header>
|
||||
<details><summary>Scene summary: Someone calculated someone’s distance, eyeing her as a potential victim for his planned blackmail.</summary>
|
||||
<p class="scene">Mr. Peters's eye calculated the distance between them. By taking her
|
||||
by surprise it might be possible to spring upon her, overthrow her,
|
||||
and apply the throttling tactics of which he had boasted to his
|
||||
waiting comrades. True, it had been only a boast; never yet had he
|
||||
dared to lay violent hands upon her; but with the thoughts of the
|
||||
delicious, cool bock or Culmbacher bracing his nerves, he was near
|
||||
to upsetting his own theories of the treatment due by a gentleman
|
||||
to a lady. But, with his loafer's love for the more artistic and
|
||||
less strenuous way, he chose diplomacy first, the high card in the
|
||||
game--the assumed attitude of success already attained. "You have a dollar," he said, loftily, but significantly in the tone
|
||||
that goes with the lighting of a cigar--when the properties are at
|
||||
hand. "I have," said Mrs. Peters, producing the bill from her bosom and
|
||||
crackling it, teasingly. "I am offered a position in a--in a tea store," said Mr. Peters. "I
|
||||
am to begin work to-morrow. But it will be necessary for me to buy a
|
||||
pair of--" "You are a liar," said Mrs. Peters, reinterring the note. "No tea
|
||||
store, nor no A B C store, nor no junk shop would have you. I rubbed
|
||||
the skin off both me hands wa…</p></details>
|
||||
<div class="pair"><div class="pred"><span class="who">qwen2.5:3b-instruct</span><p>Someone schemes to charm someone away from a tea job, while she resists with a tough stance.</p><label><input type="radio" name="p39" value="0"> better</label></div><div class="pred"><span class="who">qwen3:14b</span><p>A man trying to manipulate a woman with false promises faces her sharp rejection and must find another way to achieve his goal.</p><label><input type="radio" name="p39" value="1"> better</label></div></div>
|
||||
<label class="both"><input type="radio" name="p39" value="n"> neither is a predicament</label>
|
||||
</section>
|
||||
</main>
|
||||
<script>
|
||||
const KEY='stp-pilot:'+["qwen2.5:3b-instruct", "qwen3:14b"].join('|'), labels=["qwen2.5:3b-instruct", "qwen3:14b"];
|
||||
let saved={}; try{saved=JSON.parse(localStorage.getItem(KEY)||'{}')}catch(e){}
|
||||
for(const [k,v] of Object.entries(saved)){const el=document.querySelector(`input[name="${k}"][value="${v}"]`); if(el) el.checked=true}
|
||||
function tally(){
|
||||
const c={0:0,1:0,n:0}; let done=0;
|
||||
document.querySelectorAll('input[type=radio]:checked').forEach(r=>{c[r.value]++;done++});
|
||||
document.getElementById('tally').textContent=`Picked ${done} of 40: ${labels[0]} ${c[0]}, ${labels[1]} ${c[1]}, neither ${c.n}`;
|
||||
}
|
||||
document.addEventListener('change',e=>{if(e.target.type!=='radio')return; saved[e.target.name]=e.target.value;
|
||||
try{localStorage.setItem(KEY,JSON.stringify(saved))}catch(err){} tally()});
|
||||
tally();
|
||||
</script>
|
||||
@@ -0,0 +1,246 @@
|
||||
{
|
||||
"model": "qwen3:14b",
|
||||
"prompt": "You describe the predicament of the main person in a passage of fiction, in ONE sentence.\n\nA predicament is what that person is up against and what they want out of it: the kind of trouble or opportunity that could happen to anyone, in any line of work, in any town.\n\nRules:\n- Exactly one sentence, at most 20 words.\n- Leave out everything that ties it to this story: no names, no jobs or professions, no places, no businesses, and no objects that only belong to this story.\n- Say what the person is up against, and what they want.\n- Do not describe the writing, the mood, or who is telling the story.\n- Output only the sentence.",
|
||||
"summaries": [
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"summary": "A man struggles to understand the overwhelming complexity of a city's collective voice, seeking clarity in a world filled with individual sounds but no unified message.",
|
||||
"model_output": "A man struggles to understand the overwhelming complexity of a city's collective voice, seeking clarity in a world filled with individual sounds but no unified message."
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"summary": "The person is trying to understand the city's hidden message, hoping to find a unified voice that reveals its true nature.",
|
||||
"model_output": "The person is trying to understand the city's hidden message, hoping to find a unified voice that reveals its true nature."
|
||||
},
|
||||
{
|
||||
"chunk": 2,
|
||||
"story": 0,
|
||||
"summary": "A man determined to uncover the unique voice of a city faces the challenge of distinguishing its true character from the distractions and distractions of daily life.",
|
||||
"model_output": "A man determined to uncover the unique voice of a city faces the challenge of distinguishing its true character from the distractions and distractions of daily life."
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"summary": "A man tries to uncover the city's hidden voice through a cop, only to find that the cop's personal life and indifference to the city's soul leave him with no real answers.",
|
||||
"model_output": "A man tries to uncover the city's hidden voice through a cop, only to find that the cop's personal life and indifference to the city's soul leave him with no real answers."
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"summary": "A man seeks to capture the essence of a city's soul through a unique blend of its sounds, hoping to create a single, powerful vocalization that represents its spirit.",
|
||||
"model_output": "A man seeks to capture the essence of a city's soul through a unique blend of its sounds, hoping to create a single, powerful vocalization that represents its spirit."
|
||||
},
|
||||
{
|
||||
"chunk": 5,
|
||||
"story": 0,
|
||||
"summary": "A man seeks understanding from a city that refuses to speak, desperate to uncover a hidden truth that eludes everyone else.",
|
||||
"model_output": "A man seeks understanding from a city that refuses to speak, desperate to uncover a hidden truth that eludes everyone else."
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"summary": "Aurelia and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weight of the city's presence.",
|
||||
"model_output": "Aurelia and the narrator sit in silence, caught between the quiet intimacy of the moment and the unspoken weight of the city's presence."
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins must navigate the harsh realities of poverty, love, and war all at once in the crowded, unforgiving city.",
|
||||
"model_output": "John Hopkins must navigate the harsh realities of poverty, love, and war all at once in the crowded, unforgiving city."
|
||||
},
|
||||
{
|
||||
"chunk": 8,
|
||||
"story": 1,
|
||||
"summary": "Mrs. Hopkins must navigate the chaotic unpredictability of city life while clinging to the comfort of her familiar routines.",
|
||||
"model_output": "Mrs. Hopkins must navigate the chaotic unpredictability of city life while clinging to the comfort of her familiar routines."
|
||||
},
|
||||
{
|
||||
"chunk": 9,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins longs for connection but is trapped in a life of monotony and shallow conversation.",
|
||||
"model_output": "John Hopkins longs for connection but is trapped in a life of monotony and shallow conversation."
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 1,
|
||||
"summary": "A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confrontation with a shopkeeper who sees his actions as proof of humanity's corruption.",
|
||||
"model_output": "A man tries to pay for a purchase with a promise of future money, but his dishonesty sparks a violent confrontation with a shopkeeper who sees his actions as proof of humanity's corruption."
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 1,
|
||||
"summary": "A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but must decide whether to trust his mysterious rescuer.",
|
||||
"model_output": "A man flees from police pursuit, seeking refuge in an unknown vehicle that offers him a chance to escape, but must decide whether to trust his mysterious rescuer."
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 1,
|
||||
"summary": "A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with courage and discretion.",
|
||||
"model_output": "A man finds himself unexpectedly drawn into a personal conflict he did not ask for, yet must navigate with courage and discretion."
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety to do so.",
|
||||
"model_output": "John Hopkins must confront a man who has wronged a woman he now feels compelled to protect, risking his own safety to do so."
|
||||
},
|
||||
{
|
||||
"chunk": 14,
|
||||
"story": 1,
|
||||
"summary": "A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circle.",
|
||||
"model_output": "A man struggles to reconcile his growing affection for a woman with the rigid expectations of his social circle."
|
||||
},
|
||||
{
|
||||
"chunk": 15,
|
||||
"story": 2,
|
||||
"summary": "Masie, trapped in a monotonous job she despises, longs for a life that offers more than just the dull routine of selling gloves.",
|
||||
"model_output": "Masie, trapped in a monotonous job she despises, longs for a life that offers more than just the dull routine of selling gloves."
|
||||
},
|
||||
{
|
||||
"chunk": 16,
|
||||
"story": 2,
|
||||
"summary": "A man finds himself trapped in an awkward encounter with a shopgirl whose icy beauty challenges his composure, forcing him to navigate the delicate line between professional decorum and unexpected attraction.",
|
||||
"model_output": "A man finds himself trapped in an awkward encounter with a shopgirl whose icy beauty challenges his composure, forcing him to navigate the delicate line between professional decorum and unexpected attraction."
|
||||
},
|
||||
{
|
||||
"chunk": 17,
|
||||
"story": 2,
|
||||
"summary": "A man struggling with his pride and social conventions finds himself awkwardly drawn to a shopgirl, determined to win her favor despite his usual confidence.",
|
||||
"model_output": "A man struggling with his pride and social conventions finds himself awkwardly drawn to a shopgirl, determined to win her favor despite his usual confidence."
|
||||
},
|
||||
{
|
||||
"chunk": 18,
|
||||
"story": 2,
|
||||
"summary": "A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to explore new possibilities without disappointing her family or losing her sense of self.",
|
||||
"model_output": "A young woman balances between curiosity and caution as she agrees to meet a man she barely knows, hoping to explore new possibilities without disappointing her family or losing her sense of self."
|
||||
},
|
||||
{
|
||||
"chunk": 19,
|
||||
"story": 2,
|
||||
"summary": "A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life struggles to navigate the uncertain path of love.",
|
||||
"model_output": "A man torn between his growing feelings for a woman he barely knows and his fear of losing control of his life struggles to navigate the uncertain path of love."
|
||||
},
|
||||
{
|
||||
"chunk": 20,
|
||||
"story": 2,
|
||||
"summary": "A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his words and mocks his desperate, insincere pursuit.",
|
||||
"model_output": "A man insists a woman marry him, claiming his wealth and status make them equal, but she sees through his words and mocks his desperate, insincere pursuit."
|
||||
},
|
||||
{
|
||||
"chunk": 21,
|
||||
"story": 2,
|
||||
"summary": "A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain journey with him.",
|
||||
"model_output": "A man tries to convince a hesitant woman to leave her mundane life and embark on an extravagant, uncertain journey with him."
|
||||
},
|
||||
{
|
||||
"chunk": 22,
|
||||
"story": 2,
|
||||
"summary": "A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connection without giving in to the possibility of love.",
|
||||
"model_output": "A woman hides her growing feelings for a man while pretending to be uninterested, hoping to keep their connection without giving in to the possibility of love."
|
||||
},
|
||||
{
|
||||
"chunk": 23,
|
||||
"story": 3,
|
||||
"summary": "Big Jim Dougherty must navigate the rigid codes of his underground world while seeking a way to break free and claim a life beyond the shadows.",
|
||||
"model_output": "Big Jim Dougherty must navigate the rigid codes of his underground world while seeking a way to break free and claim a life beyond the shadows."
|
||||
},
|
||||
{
|
||||
"chunk": 24,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to balance his cultural identity and personal life while navigating the expectations of his community and the quiet routine of his domestic existence.",
|
||||
"model_output": "A man struggles to balance his cultural identity and personal life while navigating the expectations of his community and the quiet routine of his domestic existence."
|
||||
},
|
||||
{
|
||||
"chunk": 25,
|
||||
"story": 3,
|
||||
"summary": "A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsider the balance between habit and connection.",
|
||||
"model_output": "A man accustomed to routine is confronted with an unexpected request for companionship, forcing him to reconsider the balance between habit and connection."
|
||||
},
|
||||
{
|
||||
"chunk": 26,
|
||||
"story": 3,
|
||||
"summary": "A man struggling to reconcile his desire to display his wife publicly with his fear of judgment finds himself torn between pride and secrecy.",
|
||||
"model_output": "A man struggling to reconcile his desire to display his wife publicly with his fear of judgment finds himself torn between pride and secrecy."
|
||||
},
|
||||
{
|
||||
"chunk": 27,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to reconcile his pride with the growing awareness of his wife's hidden talents and the shame of having underestimated her.",
|
||||
"model_output": "A man struggles to reconcile his pride with the growing awareness of his wife's hidden talents and the shame of having underestimated her."
|
||||
},
|
||||
{
|
||||
"chunk": 28,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to reconcile his quiet life with the sudden, overwhelming attention his wife receives, threatening to expose the hidden depths of their marriage.",
|
||||
"model_output": "A man struggles to reconcile his quiet life with the sudden, overwhelming attention his wife receives, threatening to expose the hidden depths of their marriage."
|
||||
},
|
||||
{
|
||||
"chunk": 29,
|
||||
"story": 3,
|
||||
"summary": "A man struggles to maintain his pride and dignity after a public embarrassment, while trying to preserve his relationship and quiet his wife's growing happiness.",
|
||||
"model_output": "A man struggles to maintain his pride and dignity after a public embarrassment, while trying to preserve his relationship and quiet his wife's growing happiness."
|
||||
},
|
||||
{
|
||||
"chunk": 30,
|
||||
"story": 3,
|
||||
"summary": "A woman trapped in a love that bends her will longs for a simple peach, while her powerful but obedient husband struggles to fulfill her wish in a world that resists giving way.",
|
||||
"model_output": "A woman trapped in a love that bends her will longs for a simple peach, while her powerful but obedient husband struggles to fulfill her wish in a world that resists giving way."
|
||||
},
|
||||
{
|
||||
"chunk": 31,
|
||||
"story": 3,
|
||||
"summary": "The Kid needs to find a peach for a woman who insists on it, but every vendor says it's too soon and offers only oranges instead.",
|
||||
"model_output": "The Kid needs to find a peach for a woman who insists on it, but every vendor says it's too soon and offers only oranges instead."
|
||||
},
|
||||
{
|
||||
"chunk": 32,
|
||||
"story": 3,
|
||||
"summary": "The Kid needs to convince the captain to raid a gambling den controlled by an old enemy, but he must do so quickly before the enemy discovers his betrayal.",
|
||||
"model_output": "The Kid needs to convince the captain to raid a gambling den controlled by an old enemy, but he must do so quickly before the enemy discovers his betrayal."
|
||||
},
|
||||
{
|
||||
"chunk": 33,
|
||||
"story": 3,
|
||||
"summary": "A man must prove his worth in a dangerous confrontation to claim a prize he's risking everything for.",
|
||||
"model_output": "A man must prove his worth in a dangerous confrontation to claim a prize he's risking everything for."
|
||||
},
|
||||
{
|
||||
"chunk": 34,
|
||||
"story": 3,
|
||||
"summary": "A man desperate to find a peach before it's too late races against time and rivals to secure his prize before it vanishes entirely.",
|
||||
"model_output": "A man desperate to find a peach before it's too late races against time and rivals to secure his prize before it vanishes entirely."
|
||||
},
|
||||
{
|
||||
"chunk": 35,
|
||||
"story": 3,
|
||||
"summary": "He risks danger to fetch a trivial gift for his bride, only to find her preference has changed.",
|
||||
"model_output": "He risks danger to fetch a trivial gift for his bride, only to find her preference has changed."
|
||||
},
|
||||
{
|
||||
"chunk": 36,
|
||||
"story": 4,
|
||||
"summary": "A man trapped in the monotony of city life longs for the genuine signs of spring, but finds only artificial reports replacing nature's quiet signals.",
|
||||
"model_output": "A man trapped in the monotony of city life longs for the genuine signs of spring, but finds only artificial reports replacing nature's quiet signals."
|
||||
},
|
||||
{
|
||||
"chunk": 37,
|
||||
"story": 4,
|
||||
"summary": "Three men plot to steal a dollar from Mr. Peters's wife, who unknowingly possesses it, forcing him to confront his friends' greed and protect her from their violent intentions.",
|
||||
"model_output": "Three men plot to steal a dollar from Mr. Peters's wife, who unknowingly possesses it, forcing him to confront his friends' greed and protect her from their violent intentions."
|
||||
},
|
||||
{
|
||||
"chunk": 38,
|
||||
"story": 4,
|
||||
"summary": "Mr. Peters must steal a case note from his wife's hidden stash to buy a dollar for beer, but he refuses to harm her despite his desperation.",
|
||||
"model_output": "Mr. Peters must steal a case note from his wife's hidden stash to buy a dollar for beer, but he refuses to harm her despite his desperation."
|
||||
},
|
||||
{
|
||||
"chunk": 39,
|
||||
"story": 4,
|
||||
"summary": "A man trying to manipulate a woman with false promises faces her sharp rejection and must find another way to achieve his goal.",
|
||||
"model_output": "A man trying to manipulate a woman with false promises faces her sharp rejection and must find another way to achieve his goal."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
{
|
||||
"model": "qwen2.5:3b-instruct",
|
||||
"prompt": "You describe the predicament of the main person in a passage of fiction, in ONE sentence.\n\nA predicament is what that person is up against and what they want out of it: the kind of trouble or opportunity that could happen to anyone, in any line of work, in any town.\n\nRules:\n- Exactly one sentence, at most 20 words.\n- Leave out everything that ties it to this story: no names, no jobs or professions, no places, no businesses, and no objects that only belong to this story.\n- Say what the person is up against, and what they want.\n- Do not describe the writing, the mood, or who is telling the story.\n- Output only the sentence.",
|
||||
"summaries": [
|
||||
{
|
||||
"chunk": 0,
|
||||
"story": 0,
|
||||
"summary": "Finding the voice of a big city, I sought in vain among school chants.",
|
||||
"model_output": "Finding the voice of a big city, I sought in vain among school chants."
|
||||
},
|
||||
{
|
||||
"chunk": 1,
|
||||
"story": 0,
|
||||
"summary": "I want to know the city's voice, but it echoes like Philadelphia.",
|
||||
"model_output": "I want to know the city's voice, but it echoes like Philadelphia."
|
||||
},
|
||||
{
|
||||
"chunk": 2,
|
||||
"story": 0,
|
||||
"summary": "I must find the unique voice of New York, proving it doesn't treat me like a cigar.",
|
||||
"model_output": "I must find the unique voice of New York, proving it doesn't treat me like a cigar."
|
||||
},
|
||||
{
|
||||
"chunk": 3,
|
||||
"story": 0,
|
||||
"summary": "I want to know the city's cry, but the cop only follows orders.",
|
||||
"model_output": "I want to know the city's cry, but the cop only follows orders."
|
||||
},
|
||||
{
|
||||
"chunk": 4,
|
||||
"story": 0,
|
||||
"summary": "Bill, find the essence of New York's chaos and harmony.",
|
||||
"model_output": "Bill, find the essence of New York's chaos and harmony."
|
||||
},
|
||||
{
|
||||
"chunk": 5,
|
||||
"story": 0,
|
||||
"summary": "The poet seeks a hit with his poem, fearing rejection.",
|
||||
"model_output": "The poet seeks a hit with his poem, fearing rejection."
|
||||
},
|
||||
{
|
||||
"chunk": 6,
|
||||
"story": 0,
|
||||
"summary": "Aurelia waits alone, wanting her companion to speak.",
|
||||
"model_output": "Aurelia waits alone, wanting her companion to speak."
|
||||
},
|
||||
{
|
||||
"chunk": 7,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins finds deepest life joys in a hidden quarter, not wealth.",
|
||||
"model_output": "John Hopkins finds deepest life joys in a hidden quarter, not wealth."
|
||||
},
|
||||
{
|
||||
"chunk": 8,
|
||||
"story": 1,
|
||||
"summary": "Mrs. Hopkins struggles to balance her desires and fears in the fast-paced city.",
|
||||
"model_output": "Mrs. Hopkins struggles to balance her desires and fears in the fast-paced city."
|
||||
},
|
||||
{
|
||||
"chunk": 9,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins sought to inject conversation into his tasteless existence by buying a cigar.",
|
||||
"model_output": "John Hopkins sought to inject conversation into his tasteless existence by buying a cigar."
|
||||
},
|
||||
{
|
||||
"chunk": 10,
|
||||
"story": 1,
|
||||
"summary": "Hopkins buys spinach, finds no cash, challenges tobacconist Freshmayer.",
|
||||
"model_output": "Hopkins buys spinach, finds no cash, challenges tobacconist Freshmayer."
|
||||
},
|
||||
{
|
||||
"chunk": 11,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins sought revenge, but found himself pursued by law enforcers and a mysterious chauffeur in a racing car.",
|
||||
"model_output": "John Hopkins sought revenge, but found himself pursued by law enforcers and a mysterious chauffeur in a racing car."
|
||||
},
|
||||
{
|
||||
"chunk": 12,
|
||||
"story": 1,
|
||||
"summary": "Hopkins wants Milady's assistance against unseen attackers, while Milady seeks a brave companion.",
|
||||
"model_output": "Hopkins wants Milady's assistance against unseen attackers, while Milady seeks a brave companion."
|
||||
},
|
||||
{
|
||||
"chunk": 13,
|
||||
"story": 1,
|
||||
"summary": "John Hopkins seeks a romantic encounter, only to be kidnapped by a mysterious lady.",
|
||||
"model_output": "John Hopkins seeks a romantic encounter, only to be kidnapped by a mysterious lady."
|
||||
},
|
||||
{
|
||||
"chunk": 14,
|
||||
"story": 1,
|
||||
"summary": "Bess wants her St. Bernard, but John wants her to stop reading dangerous novels.",
|
||||
"model_output": "Bess wants her St. Bernard, but John wants her to stop reading dangerous novels."
|
||||
},
|
||||
{
|
||||
"chunk": 15,
|
||||
"story": 2,
|
||||
"summary": "Masie, a young saleslady, seeks to navigate the complexities of human nature while hiding her true identity.",
|
||||
"model_output": "Masie, a young saleslady, seeks to navigate the complexities of human nature while hiding her true identity."
|
||||
},
|
||||
{
|
||||
"chunk": 16,
|
||||
"story": 2,
|
||||
"summary": "The shopgirl Masie confronts the floorwalker Irving Carter with her flirtatious gaze, testing his resolve.",
|
||||
"model_output": "The shopgirl Masie confronts the floorwalker Irving Carter with her flirtatious gaze, testing his resolve."
|
||||
},
|
||||
{
|
||||
"chunk": 17,
|
||||
"story": 2,
|
||||
"summary": "Irving Carter desires to charm a shopgirl despite feeling out of place.",
|
||||
"model_output": "Irving Carter desires to charm a shopgirl despite feeling out of place."
|
||||
},
|
||||
{
|
||||
"chunk": 18,
|
||||
"story": 2,
|
||||
"summary": "Willie nervously asked for her company, hoping she'd agree to a date.",
|
||||
"model_output": "Willie nervously asked for her company, hoping she'd agree to a date."
|
||||
},
|
||||
{
|
||||
"chunk": 19,
|
||||
"story": 2,
|
||||
"summary": "Carter seeks love but fears rejection, while Masie uses him to prove her worth.",
|
||||
"model_output": "Carter seeks love but fears rejection, while Masie uses him to prove her worth."
|
||||
},
|
||||
{
|
||||
"chunk": 20,
|
||||
"story": 2,
|
||||
"summary": "Masie faces Carter's marriage proposal, wanting to see through his sincerity.",
|
||||
"model_output": "Masie faces Carter's marriage proposal, wanting to see through his sincerity."
|
||||
},
|
||||
{
|
||||
"chunk": 21,
|
||||
"story": 2,
|
||||
"summary": "Masie looked up at Carter, her eyes alight with a flicker of possibility.",
|
||||
"model_output": "Masie looked up at Carter, her eyes alight with a flicker of possibility."
|
||||
},
|
||||
{
|
||||
"chunk": 22,
|
||||
"story": 2,
|
||||
"summary": "She wants to end their secret affair, but he feels a hidden joy.",
|
||||
"model_output": "She wants to end their secret affair, but he feels a hidden joy."
|
||||
},
|
||||
{
|
||||
"chunk": 23,
|
||||
"story": 3,
|
||||
"summary": "Big Jim Dougherty sought acceptance in Manhattan's titled nobility of sportdom.",
|
||||
"model_output": "Big Jim Dougherty sought acceptance in Manhattan's titled nobility of sportdom."
|
||||
},
|
||||
{
|
||||
"chunk": 24,
|
||||
"story": 3,
|
||||
"summary": "He seeks a private life away from his public Oriental sport, yet feels trapped in public life.",
|
||||
"model_output": "He seeks a private life away from his public Oriental sport, yet feels trapped in public life."
|
||||
},
|
||||
{
|
||||
"chunk": 25,
|
||||
"story": 3,
|
||||
"summary": "Big Jim\" expected her to ask for a dinner date, not insist on one.",
|
||||
"model_output": "\"Big Jim\" expected her to ask for a dinner date, not insist on one."
|
||||
},
|
||||
{
|
||||
"chunk": 26,
|
||||
"story": 3,
|
||||
"summary": "Big Jim Dougherty wanted to hide his wife's presence, but a mysterious bird-like being at her side made him change plans.",
|
||||
"model_output": "Big Jim Dougherty wanted to hide his wife's presence, but a mysterious bird-like being at her side made him change plans."
|
||||
},
|
||||
{
|
||||
"chunk": 27,
|
||||
"story": 3,
|
||||
"summary": "Big Jim seeks to impress his wife at a fancy restaurant, hoping her beauty and charm will win him praise.",
|
||||
"model_output": "Big Jim seeks to impress his wife at a fancy restaurant, hoping her beauty and charm will win him praise."
|
||||
},
|
||||
{
|
||||
"chunk": 28,
|
||||
"story": 3,
|
||||
"summary": "The honorable Patrick Corrigan seeks to expose a secret and win Mrs. Dougherty's heart, while \"Big Jim\" struggles to keep up.",
|
||||
"model_output": "The honorable Patrick Corrigan seeks to expose a secret and win Mrs. Dougherty's heart, while \"Big Jim\" struggles to keep up."
|
||||
},
|
||||
{
|
||||
"chunk": 29,
|
||||
"story": 3,
|
||||
"summary": "Patrick seeks to impress Mrs. Dougherty with a bold gesture, but is thwarted by her quicker action.",
|
||||
"model_output": "Patrick seeks to impress Mrs. Dougherty with a bold gesture, but is thwarted by her quicker action."
|
||||
},
|
||||
{
|
||||
"chunk": 30,
|
||||
"story": 3,
|
||||
"summary": "The bride desires her husband's affection, yet fears losing him to distant admirers.",
|
||||
"model_output": "The bride desires her husband's affection, yet fears losing him to distant admirers."
|
||||
},
|
||||
{
|
||||
"chunk": 31,
|
||||
"story": 3,
|
||||
"summary": "The Kid seeks a peach but finds oranges instead, rushing to a fruiterer just as the lights go out.",
|
||||
"model_output": "The Kid seeks a peach but finds oranges instead, rushing to a fruiterer just as the lights go out."
|
||||
},
|
||||
{
|
||||
"chunk": 32,
|
||||
"story": 3,
|
||||
"summary": "Kid seeks to infiltrate Denver Dick's gambling den but finds it closed, needing help from the captain.",
|
||||
"model_output": "Kid seeks to infiltrate Denver Dick's gambling den but finds it closed, needing help from the captain."
|
||||
},
|
||||
{
|
||||
"chunk": 33,
|
||||
"story": 3,
|
||||
"summary": "The captain seeks to lead a raid, but doubts success as the Kid's plan unfolds.",
|
||||
"model_output": "The captain seeks to lead a raid, but doubts success as the Kid's plan unfolds."
|
||||
},
|
||||
{
|
||||
"chunk": 34,
|
||||
"story": 3,
|
||||
"summary": "The Kid seeks a last peach at a lavish dinner, fearing theft, while authorities arrive.",
|
||||
"model_output": "The Kid seeks a last peach at a lavish dinner, fearing theft, while authorities arrive."
|
||||
},
|
||||
{
|
||||
"chunk": 35,
|
||||
"story": 3,
|
||||
"summary": "The Kid seeks to please his bride by delivering a requested peach, despite its trivial nature.",
|
||||
"model_output": "The Kid seeks to please his bride by delivering a requested peach, despite its trivial nature."
|
||||
},
|
||||
{
|
||||
"chunk": 36,
|
||||
"story": 4,
|
||||
"summary": "Stuck in stone walls, city man craves nature's first signs to confirm spring's arrival.",
|
||||
"model_output": "Stuck in stone walls, city man craves nature's first signs to confirm spring's arrival."
|
||||
},
|
||||
{
|
||||
"chunk": 37,
|
||||
"story": 4,
|
||||
"summary": "Mr. Peters wants the dollar his wife has, but fears losing face among friends.",
|
||||
"model_output": "Mr. Peters wants the dollar his wife has, but fears losing face among friends."
|
||||
},
|
||||
{
|
||||
"chunk": 38,
|
||||
"story": 4,
|
||||
"summary": "Maguire wants the dollar, but Mr. Peters plans to get the case note instead.",
|
||||
"model_output": "Maguire wants the dollar, but Mr. Peters plans to get the case note instead."
|
||||
},
|
||||
{
|
||||
"chunk": 39,
|
||||
"story": 4,
|
||||
"summary": "Mr. Peters schemes to charm Mrs. Peters away from a tea job, while she resists with a tough stance.",
|
||||
"model_output": "Mr. Peters schemes to charm Mrs. Peters away from a tea job, while she resists with a tough stance."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
"""Side-by-side page for reading the predicament pilot: the scene, its summary, and what each model made of it.
|
||||
|
||||
python3 strip_names.py pilot-predicaments-3b.json pilot-3b-clean.json
|
||||
python3 strip_names.py pilot-predicaments-14b.json pilot-14b-clean.json
|
||||
python3 pilot_page.py pilot-3b-clean.json pilot-14b-clean.json > pilot-compare.html
|
||||
|
||||
Takes strip_names.py's output, so the page shows what the full run would actually embed. (strip_names
|
||||
runs as a script on import, so it is not imported here.) Job, relationship and place words are
|
||||
highlighted. A pick per scene is kept in the browser only; the tally at the top is what to report
|
||||
back. No inference.
|
||||
"""
|
||||
import collections, html, json, pathlib, re, sys
|
||||
from topic_words import TOPIC_WORDS
|
||||
|
||||
def clean(text):
|
||||
return text # inputs are already name-stripped
|
||||
|
||||
a_path, b_path = sys.argv[1], sys.argv[2]
|
||||
cols = [json.loads(pathlib.Path(p).read_text(encoding='utf-8')) for p in (a_path, b_path)]
|
||||
labels = [c['model'] for c in cols]
|
||||
chunks = json.loads(pathlib.Path('chunks.json').read_text(encoding='utf-8'))
|
||||
summary = {x['chunk']: x['summary'] for x in
|
||||
json.loads(pathlib.Path('summaries_clean.json').read_text(encoding='utf-8'))['summaries']}
|
||||
|
||||
|
||||
def marked(text):
|
||||
out = []
|
||||
for part in re.split(r"([A-Za-zé]+)", text):
|
||||
esc = html.escape(part)
|
||||
out.append(f'<mark>{esc}</mark>' if part.lower() in TOPIC_WORDS else esc)
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def leaks(text):
|
||||
return len(set(re.findall(r"[a-zé]+", text.lower())) & TOPIC_WORDS)
|
||||
|
||||
|
||||
def opening(text):
|
||||
return ' '.join(text.split()[:3])
|
||||
|
||||
|
||||
rows, stats = [], []
|
||||
for col in cols:
|
||||
texts = [clean(x['summary']) for x in col['summaries']]
|
||||
stats.append({
|
||||
'leaky': sum(leaks(t) > 0 for t in texts),
|
||||
'city': sum('city' in t.lower() for t in texts),
|
||||
'words': sum(len(t.split()) for t in texts) / len(texts),
|
||||
'openings': collections.Counter(opening(t) for t in texts).most_common(3),
|
||||
})
|
||||
n = min(len(c['summaries']) for c in cols)
|
||||
for i in range(n):
|
||||
x = cols[0]['summaries'][i]
|
||||
ch = chunks[x['chunk']]
|
||||
cells = ''.join(
|
||||
f'<div class="pred"><span class="who">{html.escape(labels[j])}</span>'
|
||||
f'<p>{marked(clean(cols[j]["summaries"][i]["summary"]))}</p>'
|
||||
f'<label><input type="radio" name="p{i}" value="{j}"> better</label></div>'
|
||||
for j in range(2))
|
||||
rows.append(f'''<section data-i="{i}">
|
||||
<header><span class="n">{i + 1}</span> <b>{html.escape(ch["title"])}</b></header>
|
||||
<details><summary>Scene summary: {marked(summary.get(x["chunk"], ""))}</summary>
|
||||
<p class="scene">{html.escape(ch["text"][:1200])}{"…" if len(ch["text"]) > 1200 else ""}</p></details>
|
||||
<div class="pair">{cells}</div>
|
||||
<label class="both"><input type="radio" name="p{i}" value="n"> neither is a predicament</label>
|
||||
</section>''')
|
||||
|
||||
stat_rows = ''.join(
|
||||
f'<tr><td>{html.escape(labels[j])}</td><td>{s["leaky"]} of {n}</td><td>{s["city"]}</td>'
|
||||
f'<td>{s["words"]:.0f}</td><td>{"; ".join(f"“{html.escape(o)}…” ×{c}" for o, c in s["openings"])}</td></tr>'
|
||||
for j, s in enumerate(stats))
|
||||
|
||||
print(f'''<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Predicament pilot</title>
|
||||
<style>
|
||||
:root{{--bg:#f7f6f2;--card:#fff;--ink:#222;--dim:#666;--line:#ddd;--mark:#ffe08a;--pick:#e3f1e6}}
|
||||
@media (prefers-color-scheme:dark){{:root{{--bg:#1b1b1d;--card:#252528;--ink:#e6e6e6;--dim:#999;--line:#3a3a3e;--mark:#6b5415;--pick:#23402b}}}}
|
||||
body{{background:var(--bg);color:var(--ink);font:15px/1.5 system-ui,sans-serif;margin:0;padding:24px 16px}}
|
||||
main{{max-width:980px;margin:auto}} h1{{margin:0 0 4px}} .dim{{color:var(--dim)}}
|
||||
table{{border-collapse:collapse;margin:12px 0;font-size:14px}} td,th{{border-bottom:1px solid var(--line);padding:4px 10px;text-align:left}}
|
||||
.wrap{{overflow-x:auto}}
|
||||
#tally{{position:sticky;top:0;background:var(--bg);padding:8px 0;border-bottom:1px solid var(--line);z-index:1}}
|
||||
section{{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:12px 14px;margin:14px 0}}
|
||||
header .n{{color:var(--dim);margin-right:4px}} summary{{cursor:pointer;color:var(--dim);margin:6px 0}}
|
||||
.scene{{white-space:pre-wrap;font-size:13px;color:var(--dim)}}
|
||||
.pair{{display:grid;grid-template-columns:1fr 1fr;gap:10px}} @media (max-width:640px){{.pair{{grid-template-columns:1fr}}}}
|
||||
.pred{{border:1px solid var(--line);border-radius:6px;padding:8px 10px}} .pred p{{margin:4px 0 8px}}
|
||||
.pred:has(input:checked){{background:var(--pick)}} .who{{font-size:12px;color:var(--dim)}}
|
||||
mark{{background:var(--mark);color:inherit;border-radius:2px}} .both{{display:block;margin-top:8px;font-size:13px;color:var(--dim)}}
|
||||
</style>
|
||||
<main>
|
||||
<h1>Predicament pilot</h1>
|
||||
<p class="dim">The first {n} scenes, each re-described by two models as the main person's predicament. Names are
|
||||
already removed as the full run would. <mark>Highlighted</mark> words are jobs, relationships and places, the
|
||||
words the old groups formed around. Pick the better sentence in each scene, or "neither". The column order
|
||||
is fixed: {html.escape(labels[0])} on the left.</p>
|
||||
<p class="dim">What to look for: does the sentence say what the person is up against and what they want? Could
|
||||
it happen to anyone, anywhere? Would two scenes with the same predicament end up with similar sentences?</p>
|
||||
<div class="wrap"><table><tr><th>Model</th><th>Has a highlighted word</th><th>Says "city"</th><th>Words, mean</th><th>Most common openings</th></tr>{stat_rows}</table></div>
|
||||
<div id="tally"></div>
|
||||
{"".join(rows)}
|
||||
</main>
|
||||
<script>
|
||||
const KEY='stp-pilot:'+{json.dumps(labels)}.join('|'), labels={json.dumps(labels)};
|
||||
let saved={{}}; try{{saved=JSON.parse(localStorage.getItem(KEY)||'{{}}')}}catch(e){{}}
|
||||
for(const [k,v] of Object.entries(saved)){{const el=document.querySelector(`input[name="${{k}}"][value="${{v}}"]`); if(el) el.checked=true}}
|
||||
function tally(){{
|
||||
const c={{0:0,1:0,n:0}}; let done=0;
|
||||
document.querySelectorAll('input[type=radio]:checked').forEach(r=>{{c[r.value]++;done++}});
|
||||
document.getElementById('tally').textContent=`Picked ${{done}} of {n}: ${{labels[0]}} ${{c[0]}}, ${{labels[1]}} ${{c[1]}}, neither ${{c.n}}`;
|
||||
}}
|
||||
document.addEventListener('change',e=>{{if(e.target.type!=='radio')return; saved[e.target.name]=e.target.value;
|
||||
try{{localStorage.setItem(KEY,JSON.stringify(saved))}}catch(err){{}} tally()}});
|
||||
tally();
|
||||
</script>''')
|
||||
@@ -0,0 +1,731 @@
|
||||
{
|
||||
"R001": {
|
||||
"members": [
|
||||
57,
|
||||
58,
|
||||
61,
|
||||
63,
|
||||
75,
|
||||
279,
|
||||
281,
|
||||
326,
|
||||
358,
|
||||
359,
|
||||
364,
|
||||
435,
|
||||
640
|
||||
],
|
||||
"methods": [
|
||||
"B",
|
||||
"M1"
|
||||
]
|
||||
},
|
||||
"R002": {
|
||||
"members": [
|
||||
5,
|
||||
78,
|
||||
107,
|
||||
128,
|
||||
130,
|
||||
131,
|
||||
153,
|
||||
178,
|
||||
247,
|
||||
277,
|
||||
334,
|
||||
335,
|
||||
342,
|
||||
344,
|
||||
345,
|
||||
420,
|
||||
443,
|
||||
445,
|
||||
446,
|
||||
447,
|
||||
449,
|
||||
515,
|
||||
519,
|
||||
555,
|
||||
561,
|
||||
613,
|
||||
775,
|
||||
818,
|
||||
824,
|
||||
832
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R003": {
|
||||
"members": [
|
||||
22,
|
||||
41,
|
||||
76,
|
||||
80,
|
||||
98,
|
||||
128,
|
||||
130,
|
||||
131,
|
||||
161,
|
||||
163,
|
||||
200,
|
||||
261,
|
||||
340,
|
||||
357,
|
||||
363,
|
||||
397,
|
||||
407,
|
||||
448,
|
||||
478,
|
||||
498,
|
||||
519,
|
||||
599,
|
||||
662,
|
||||
663,
|
||||
693,
|
||||
701,
|
||||
708,
|
||||
709,
|
||||
769,
|
||||
788
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R004": {
|
||||
"members": [
|
||||
3,
|
||||
101,
|
||||
130,
|
||||
131,
|
||||
243,
|
||||
272,
|
||||
407,
|
||||
408,
|
||||
409,
|
||||
411,
|
||||
444,
|
||||
476,
|
||||
510,
|
||||
513,
|
||||
515,
|
||||
597,
|
||||
599,
|
||||
600,
|
||||
662,
|
||||
693,
|
||||
694,
|
||||
695,
|
||||
696,
|
||||
698,
|
||||
701,
|
||||
762,
|
||||
771,
|
||||
830
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R005": {
|
||||
"members": [
|
||||
52,
|
||||
126,
|
||||
144,
|
||||
160,
|
||||
166,
|
||||
169,
|
||||
208,
|
||||
257,
|
||||
258,
|
||||
303,
|
||||
356,
|
||||
431,
|
||||
480,
|
||||
517,
|
||||
573,
|
||||
588,
|
||||
606,
|
||||
608,
|
||||
648,
|
||||
651,
|
||||
658,
|
||||
691,
|
||||
706,
|
||||
730,
|
||||
750,
|
||||
751,
|
||||
805,
|
||||
807,
|
||||
809,
|
||||
816
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R006": {
|
||||
"members": [
|
||||
38,
|
||||
41,
|
||||
66,
|
||||
69,
|
||||
121,
|
||||
127,
|
||||
131,
|
||||
149,
|
||||
163,
|
||||
187,
|
||||
198,
|
||||
227,
|
||||
283,
|
||||
293,
|
||||
322,
|
||||
391,
|
||||
475,
|
||||
483,
|
||||
488,
|
||||
526,
|
||||
536,
|
||||
550,
|
||||
590,
|
||||
614,
|
||||
721,
|
||||
756,
|
||||
757,
|
||||
758,
|
||||
759,
|
||||
760,
|
||||
761,
|
||||
762,
|
||||
763,
|
||||
764,
|
||||
786,
|
||||
804
|
||||
],
|
||||
"methods": [
|
||||
"B",
|
||||
"M1",
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R007": {
|
||||
"members": [
|
||||
39,
|
||||
62,
|
||||
93,
|
||||
125,
|
||||
148,
|
||||
172,
|
||||
175,
|
||||
178,
|
||||
199,
|
||||
201,
|
||||
215,
|
||||
222,
|
||||
224,
|
||||
251,
|
||||
254,
|
||||
264,
|
||||
265,
|
||||
267,
|
||||
306,
|
||||
323,
|
||||
328,
|
||||
352,
|
||||
361,
|
||||
367,
|
||||
479,
|
||||
490,
|
||||
500,
|
||||
511,
|
||||
521,
|
||||
533,
|
||||
534,
|
||||
543,
|
||||
589,
|
||||
662,
|
||||
680,
|
||||
799,
|
||||
803,
|
||||
823
|
||||
],
|
||||
"methods": [
|
||||
"B",
|
||||
"M1",
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R008": {
|
||||
"members": [
|
||||
17,
|
||||
19,
|
||||
20,
|
||||
29,
|
||||
45,
|
||||
46,
|
||||
49,
|
||||
60,
|
||||
108,
|
||||
159,
|
||||
170,
|
||||
178,
|
||||
185,
|
||||
221,
|
||||
278,
|
||||
333,
|
||||
342,
|
||||
343,
|
||||
369,
|
||||
372,
|
||||
522,
|
||||
538,
|
||||
579,
|
||||
586,
|
||||
587,
|
||||
611,
|
||||
635,
|
||||
639,
|
||||
692,
|
||||
799
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R009": {
|
||||
"members": [
|
||||
42,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
61,
|
||||
63,
|
||||
75,
|
||||
76,
|
||||
175,
|
||||
265,
|
||||
267,
|
||||
275,
|
||||
279,
|
||||
281,
|
||||
326,
|
||||
352,
|
||||
358,
|
||||
359,
|
||||
364,
|
||||
435,
|
||||
479,
|
||||
500,
|
||||
519,
|
||||
540,
|
||||
543,
|
||||
640,
|
||||
662,
|
||||
680,
|
||||
739,
|
||||
799
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R010": {
|
||||
"members": [
|
||||
18,
|
||||
25,
|
||||
26,
|
||||
55,
|
||||
77,
|
||||
107,
|
||||
126,
|
||||
167,
|
||||
186,
|
||||
285,
|
||||
289,
|
||||
392,
|
||||
403,
|
||||
419,
|
||||
421,
|
||||
424,
|
||||
427,
|
||||
482,
|
||||
492,
|
||||
493,
|
||||
506,
|
||||
532,
|
||||
569,
|
||||
596,
|
||||
604,
|
||||
605,
|
||||
606,
|
||||
608,
|
||||
627,
|
||||
651,
|
||||
690,
|
||||
691,
|
||||
704,
|
||||
705,
|
||||
755,
|
||||
801,
|
||||
802
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R011": {
|
||||
"members": [
|
||||
20,
|
||||
65,
|
||||
125,
|
||||
135,
|
||||
198,
|
||||
221,
|
||||
381,
|
||||
459,
|
||||
477,
|
||||
488,
|
||||
639,
|
||||
642,
|
||||
692,
|
||||
799
|
||||
],
|
||||
"methods": [
|
||||
"M2"
|
||||
]
|
||||
},
|
||||
"R012": {
|
||||
"members": [
|
||||
27,
|
||||
47,
|
||||
144,
|
||||
168,
|
||||
216,
|
||||
308,
|
||||
311,
|
||||
355,
|
||||
416,
|
||||
422,
|
||||
559,
|
||||
619,
|
||||
648,
|
||||
649,
|
||||
660,
|
||||
665,
|
||||
675,
|
||||
774,
|
||||
805,
|
||||
808,
|
||||
809,
|
||||
836
|
||||
],
|
||||
"methods": [
|
||||
"B",
|
||||
"M1"
|
||||
]
|
||||
},
|
||||
"R013": {
|
||||
"members": [
|
||||
13,
|
||||
22,
|
||||
41,
|
||||
95,
|
||||
106,
|
||||
115,
|
||||
117,
|
||||
119,
|
||||
128,
|
||||
129,
|
||||
130,
|
||||
131,
|
||||
163,
|
||||
227,
|
||||
231,
|
||||
271,
|
||||
336,
|
||||
451,
|
||||
464,
|
||||
519,
|
||||
599,
|
||||
662,
|
||||
663,
|
||||
693,
|
||||
701,
|
||||
710,
|
||||
740,
|
||||
762,
|
||||
765,
|
||||
790
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R014": {
|
||||
"members": [
|
||||
29,
|
||||
150,
|
||||
229,
|
||||
252,
|
||||
272,
|
||||
275,
|
||||
276,
|
||||
309,
|
||||
324,
|
||||
349,
|
||||
355,
|
||||
398,
|
||||
399,
|
||||
450,
|
||||
504,
|
||||
631,
|
||||
656,
|
||||
657,
|
||||
659,
|
||||
661,
|
||||
662,
|
||||
693,
|
||||
695,
|
||||
728,
|
||||
731,
|
||||
749,
|
||||
754,
|
||||
768,
|
||||
772,
|
||||
825
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R015": {
|
||||
"members": [
|
||||
41,
|
||||
72,
|
||||
106,
|
||||
131,
|
||||
148,
|
||||
149,
|
||||
205,
|
||||
293,
|
||||
301,
|
||||
302,
|
||||
304,
|
||||
305,
|
||||
411,
|
||||
590,
|
||||
663
|
||||
],
|
||||
"methods": [
|
||||
"M2"
|
||||
]
|
||||
},
|
||||
"R016": {
|
||||
"members": [
|
||||
7,
|
||||
8,
|
||||
17,
|
||||
21,
|
||||
124,
|
||||
171,
|
||||
282,
|
||||
414,
|
||||
547,
|
||||
554,
|
||||
570,
|
||||
602,
|
||||
638,
|
||||
641,
|
||||
727,
|
||||
746,
|
||||
834
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R017": {
|
||||
"members": [
|
||||
10,
|
||||
12,
|
||||
59,
|
||||
62,
|
||||
150,
|
||||
175,
|
||||
244,
|
||||
245,
|
||||
252,
|
||||
275,
|
||||
276,
|
||||
312,
|
||||
324,
|
||||
352,
|
||||
355,
|
||||
374,
|
||||
378,
|
||||
415,
|
||||
422,
|
||||
479,
|
||||
520,
|
||||
543,
|
||||
662,
|
||||
680,
|
||||
684,
|
||||
717,
|
||||
739,
|
||||
825,
|
||||
826,
|
||||
833
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R018": {
|
||||
"members": [
|
||||
13,
|
||||
16,
|
||||
20,
|
||||
28,
|
||||
29,
|
||||
44,
|
||||
65,
|
||||
67,
|
||||
87,
|
||||
91,
|
||||
92,
|
||||
94,
|
||||
135,
|
||||
136,
|
||||
141,
|
||||
146,
|
||||
169,
|
||||
170,
|
||||
250,
|
||||
266,
|
||||
332,
|
||||
366,
|
||||
368,
|
||||
377,
|
||||
494,
|
||||
495,
|
||||
509,
|
||||
573,
|
||||
607,
|
||||
628,
|
||||
629,
|
||||
630,
|
||||
636,
|
||||
692,
|
||||
748,
|
||||
785,
|
||||
796,
|
||||
817,
|
||||
819
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R019": {
|
||||
"members": [
|
||||
27,
|
||||
47,
|
||||
144,
|
||||
159,
|
||||
168,
|
||||
216,
|
||||
275,
|
||||
308,
|
||||
311,
|
||||
355,
|
||||
416,
|
||||
422,
|
||||
520,
|
||||
559,
|
||||
605,
|
||||
606,
|
||||
608,
|
||||
619,
|
||||
648,
|
||||
649,
|
||||
651,
|
||||
660,
|
||||
665,
|
||||
675,
|
||||
691,
|
||||
774,
|
||||
805,
|
||||
808,
|
||||
809,
|
||||
836
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
},
|
||||
"R020": {
|
||||
"members": [
|
||||
12,
|
||||
38,
|
||||
62,
|
||||
121,
|
||||
306,
|
||||
324,
|
||||
354,
|
||||
375,
|
||||
410,
|
||||
448,
|
||||
519,
|
||||
615,
|
||||
662,
|
||||
680,
|
||||
703,
|
||||
739,
|
||||
823
|
||||
],
|
||||
"methods": [
|
||||
"M2"
|
||||
]
|
||||
},
|
||||
"R021": {
|
||||
"members": [
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
29,
|
||||
53,
|
||||
54,
|
||||
60,
|
||||
173,
|
||||
174,
|
||||
175,
|
||||
178,
|
||||
230,
|
||||
367,
|
||||
406,
|
||||
479,
|
||||
489,
|
||||
491,
|
||||
540,
|
||||
608,
|
||||
634,
|
||||
639,
|
||||
643,
|
||||
644,
|
||||
662,
|
||||
692,
|
||||
710,
|
||||
734,
|
||||
739,
|
||||
792,
|
||||
799
|
||||
],
|
||||
"methods": [
|
||||
"M3"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"B": {
|
||||
"accepted": 4,
|
||||
"precision1plus": 1.0,
|
||||
"precision2": 0.0,
|
||||
"label2": 0,
|
||||
"distinct_situations": 4,
|
||||
"repeated_names": 0,
|
||||
"coverage": 0.13007159904534607,
|
||||
"scenes_in_several": 0
|
||||
},
|
||||
"M1": {
|
||||
"accepted": 4,
|
||||
"precision1plus": 1.0,
|
||||
"precision2": 0.0,
|
||||
"label2": 0,
|
||||
"distinct_situations": 4,
|
||||
"repeated_names": 0,
|
||||
"coverage": 0.13007159904534607,
|
||||
"scenes_in_several": 0
|
||||
},
|
||||
"M2": {
|
||||
"accepted": 3,
|
||||
"precision1plus": 1.0,
|
||||
"precision2": 1.0,
|
||||
"label2": 3,
|
||||
"distinct_situations": 3,
|
||||
"repeated_names": 0,
|
||||
"coverage": 0.05489260143198091,
|
||||
"scenes_in_several": 0
|
||||
},
|
||||
"M3": {
|
||||
"accepted": 16,
|
||||
"precision1plus": 0.6875,
|
||||
"precision2": 0.0,
|
||||
"label2": 0,
|
||||
"distinct_situations": 10,
|
||||
"repeated_names": 1,
|
||||
"coverage": 0.45465393794749404,
|
||||
"scenes_in_several": 68
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
seed 13: 42 candidates; B accepts 4
|
||||
M1: pool 729, k=36; accepts 4 (0 new)
|
||||
M2: accepts 3
|
||||
M3: accepts 16 (12 recovered by pruning); scenes in >1 cluster: 68
|
||||
blind sheet: 21 distinct clusters, 576 lines
|
||||
|
||||
real 0m54.699s
|
||||
user 0m39.193s
|
||||
sys 0m14.866s
|
||||
exit 0
|
||||
@@ -0,0 +1,170 @@
|
||||
"""Recall recovery: baseline gate and three methods on the seed-13 partition (SELECTION.md).
|
||||
|
||||
python3 recover.py
|
||||
|
||||
Writes:
|
||||
recover-map.json sheet id -> member chunks and which methods produced it (do not read before labelling)
|
||||
sheet-recover.txt blind sheet: member summaries only, shuffled
|
||||
labels-recover.json empty label template (only if absent)
|
||||
Prints counts per method only.
|
||||
"""
|
||||
import collections, json, math, pathlib, random, re, sys
|
||||
|
||||
SEED, K = 13, 60
|
||||
MIN_GATE_SIZE, GROW_CAP = 13, 30
|
||||
|
||||
sys.argv = ['measure.py', 'summary_embeddings.json']
|
||||
exec(pathlib.Path('measure.py').read_text(encoding='utf-8').split("print(f'{path.name}")[0])
|
||||
# defined: chunks, V, n, dim, story, dot, norm, kmeans (on V), describe, MIN_SIZE, MIN_STORIES, MAX_DOMINANT
|
||||
|
||||
frozen = json.loads(pathlib.Path('rule-frozen.json').read_text())
|
||||
CUT = {s: t for s, _, t in frozen['terms']}
|
||||
S1_CUT, S6_CUT = CUT['S1'], CUT['S6']
|
||||
|
||||
summaries = json.loads(pathlib.Path('summaries_clean.json').read_text(encoding='utf-8'))['summaries']
|
||||
text = {x['chunk']: x['summary'] for x in summaries}
|
||||
|
||||
# Identical to signals.py.
|
||||
STOP = set("""a an the and or but of to in on at for with by from as into over under about after before
|
||||
while during than then that this these those it its his her hers him he she they them their theirs we our
|
||||
you your i me my is are was were be been being has have had do does did not no nor so such who whom whose
|
||||
which what when where why how all any each both either neither one two some other others another more most
|
||||
less least very just only also even yet still now up down out off away back again once own same too can
|
||||
could will would shall should may might must upon despite toward towards between among amid amidst
|
||||
someone someone's someone’s""".split())
|
||||
docs = [set(re.findall(r"[a-z][a-z'’]+", text[i].lower())) - STOP for i in range(n)]
|
||||
df = collections.Counter(w for d in docs for w in d)
|
||||
|
||||
def s1(m):
|
||||
tot, cnt = 0.0, 0
|
||||
for x in range(len(m)):
|
||||
for y in range(x + 1, len(m)):
|
||||
if story[m[x]] != story[m[y]]:
|
||||
tot += dot(V[m[x]], V[m[y]]); cnt += 1
|
||||
return tot / cnt if cnt else 0.0
|
||||
|
||||
def s6(m):
|
||||
size = len(m)
|
||||
cw = collections.Counter(w for i in m for w in docs[i])
|
||||
best = None
|
||||
for w, c in cw.items():
|
||||
if c < 2: continue
|
||||
score = math.log((c + .5) / (size - c + .5)) - math.log((df[w] + .5) / (n - df[w] + .5))
|
||||
if best is None or score > best[0]: best = (score, c)
|
||||
return best[1] / size if best else 0.0
|
||||
|
||||
def eligible(m):
|
||||
size, nst, dom, _ = describe(m)
|
||||
return size >= MIN_SIZE and nst >= MIN_STORIES and dom <= MAX_DOMINANT
|
||||
|
||||
def passes(m):
|
||||
return eligible(m) and s1(m) >= S1_CUT and s6(m) <= S6_CUT
|
||||
|
||||
# Guard against drift from signals.py: recompute S1/S6 for the dev candidates.
|
||||
dev_sig = json.loads(pathlib.Path('signals-dev.json').read_text())
|
||||
dev_cand = json.loads(pathlib.Path('candidates-dev.json').read_text())
|
||||
for cid, m in dev_cand.items():
|
||||
assert abs(s1(m) - dev_sig[cid]['S1']) < 1e-3 and abs(s6(m) - dev_sig[cid]['S6']) < 1e-3, cid
|
||||
|
||||
def kmeans_on(vecs, k, seed, iters=15):
|
||||
"""Same algorithm as measure.kmeans, over arbitrary unit vectors; returns index groups."""
|
||||
m, d = len(vecs), len(vecs[0])
|
||||
rnd = random.Random(seed)
|
||||
cent = [vecs[i] for i in rnd.sample(range(m), k)]
|
||||
assign = [-1] * m
|
||||
for _ in range(iters):
|
||||
moved = 0
|
||||
for i, v in enumerate(vecs):
|
||||
best, bs = 0, -2.0
|
||||
for j, c in enumerate(cent):
|
||||
s = sum(a * b for a, b in zip(v, c))
|
||||
if s > bs: bs, best = s, j
|
||||
if assign[i] != best: assign[i] = best; moved += 1
|
||||
if moved == 0: break
|
||||
groups = collections.defaultdict(list)
|
||||
for i, j in enumerate(assign): groups[j].append(i)
|
||||
for j, mem in groups.items():
|
||||
cent[j] = norm([sum(vecs[i][x] for i in mem) / len(mem) for x in range(d)])
|
||||
groups = collections.defaultdict(list)
|
||||
for i, j in enumerate(assign): groups[j].append(i)
|
||||
return list(groups.values())
|
||||
|
||||
def centroid(m):
|
||||
return norm([sum(V[i][x] for i in m) / len(m) for x in range(dim)])
|
||||
|
||||
# Baseline
|
||||
partition = [sorted(c) for c in kmeans(K, SEED)]
|
||||
candidates = [c for c in partition if eligible(c)]
|
||||
B = [c for c in candidates if passes(c)]
|
||||
print(f'seed {SEED}: {len(candidates)} candidates; B accepts {len(B)}', flush=True)
|
||||
|
||||
# M1: re-cluster everything outside B's accepted clusters
|
||||
in_B = {i for c in B for i in c}
|
||||
pool = [i for i in range(n) if i not in in_B]
|
||||
k1 = max(2, round(len(pool) / 20))
|
||||
M1 = [list(c) for c in B]
|
||||
for g in kmeans_on([V[i] for i in pool], k1, SEED):
|
||||
m = sorted(pool[x] for x in g)
|
||||
if passes(m): M1.append(m)
|
||||
print(f'M1: pool {len(pool)}, k={k1}; accepts {len(M1)} ({len(M1) - len(B)} new)', flush=True)
|
||||
|
||||
# M2: consensus clustering over co-association rows
|
||||
co = json.loads(pathlib.Path('coassoc.json').read_text())
|
||||
rows = [norm([float(x) for x in co[i]]) for i in range(n)]
|
||||
M2 = [sorted(g) for g in kmeans_on(rows, K, SEED) if passes(sorted(g))]
|
||||
print(f'M2: accepts {len(M2)}', flush=True)
|
||||
|
||||
# M3: prune failing candidates to a passing core, then grow every passing cluster
|
||||
def prune(m):
|
||||
m = list(m)
|
||||
while not passes(m):
|
||||
if len(m) <= MIN_GATE_SIZE: return None
|
||||
def cross_mean(x):
|
||||
others = [y for y in m if story[y] != story[x]]
|
||||
return sum(dot(V[x], V[y]) for y in others) / len(others) if others else -1.0
|
||||
m.remove(min(m, key=cross_mean))
|
||||
return m
|
||||
|
||||
def grow(m):
|
||||
m = list(m)
|
||||
while len(m) < GROW_CAP:
|
||||
cen = centroid(m)
|
||||
members = set(m)
|
||||
j = max((i for i in range(n) if i not in members), key=lambda i: dot(V[i], cen))
|
||||
if not passes(m + [j]): break
|
||||
m.append(j)
|
||||
return sorted(m)
|
||||
|
||||
M3, pruned_ok = [], 0
|
||||
for c in candidates:
|
||||
core = c if passes(c) else prune(c)
|
||||
if core is None: continue
|
||||
if core is not c: pruned_ok += 1
|
||||
M3.append(grow(core))
|
||||
cover3 = collections.Counter(i for c in M3 for i in c)
|
||||
print(f'M3: accepts {len(M3)} ({pruned_ok} recovered by pruning); scenes in >1 cluster: {sum(v > 1 for v in cover3.values())}', flush=True)
|
||||
|
||||
# Pool for blind labelling
|
||||
methods = {'B': B, 'M1': M1, 'M2': M2, 'M3': M3}
|
||||
union = {}
|
||||
for name, clusters in methods.items():
|
||||
for c in clusters:
|
||||
union.setdefault(tuple(sorted(c)), []).append(name)
|
||||
keys = list(union)
|
||||
rnd = random.Random(1307)
|
||||
rnd.shuffle(keys)
|
||||
mapping = {f'R{i + 1:03d}': {'members': list(k), 'methods': union[k]} for i, k in enumerate(keys)}
|
||||
pathlib.Path('recover-map.json').write_text(json.dumps(mapping, indent=1))
|
||||
|
||||
lines = []
|
||||
for rid, entry in mapping.items():
|
||||
shown = entry['members'][:]
|
||||
rnd.shuffle(shown)
|
||||
lines.append(f'=== {rid}')
|
||||
lines.extend(f' - {text[i]}' for i in shown)
|
||||
lines.append('')
|
||||
pathlib.Path('sheet-recover.txt').write_text('\n'.join(lines), encoding='utf-8')
|
||||
lab = pathlib.Path('labels-recover.json')
|
||||
if not lab.exists():
|
||||
lab.write_text(json.dumps({rid: {'label': None, 'name': ''} for rid in mapping}, indent=1))
|
||||
print(f'blind sheet: {len(mapping)} distinct clusters, {sum(len(e["members"]) for e in mapping.values())} lines')
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Re-describe each scene as the predicament its main person is in, with nothing tying it to the story.
|
||||
|
||||
The first human review found the summary groups were topics (artists, police, courtship, hotels)
|
||||
because summaries keep jobs and places, and those dominate the embeddings. The unit a pack needs is
|
||||
a predicament: what someone is up against and what they want out of it (SELECTION.md, "Predicaments").
|
||||
This rewrites the scene's own prose into that, leaving out whatever makes a topic.
|
||||
|
||||
STP_OLLAMA=http://host:11434 STP_CHAT_MODEL=qwen3:14b python3 redescribe.py --limit 40 --out pilot-predicaments-14b.json
|
||||
STP_OLLAMA=http://host:11434 python3 redescribe.py # every scene -> predicaments.json
|
||||
|
||||
The output has the same layout as summaries.json ({"summaries": [{"chunk", "story", "summary"}]}),
|
||||
so strip_names.py and embed.py read it unchanged. One request at a time; progress is saved after
|
||||
every scene, so an interrupted run resumes.
|
||||
"""
|
||||
import argparse, json, os, pathlib, re, time
|
||||
import ollama
|
||||
from topic_words import TOPIC_WORDS
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--limit', type=int, default=None)
|
||||
ap.add_argument('--out', default='predicaments.json')
|
||||
args = ap.parse_args()
|
||||
MODEL = os.environ.get('STP_CHAT_MODEL', 'qwen2.5:3b-instruct')
|
||||
|
||||
# Fixed before any call (SELECTION.md). No example of the output vocabulary: the 3B model copied
|
||||
# the examples it was given when summarising.
|
||||
SYSTEM = """You describe the predicament of the main person in a passage of fiction, in ONE sentence.
|
||||
|
||||
A predicament is what that person is up against and what they want out of it: the kind of trouble or opportunity that could happen to anyone, in any line of work, in any town.
|
||||
|
||||
Rules:
|
||||
- Exactly one sentence, at most 20 words.
|
||||
- Leave out everything that ties it to this story: no names, no jobs or professions, no places, no businesses, and no objects that only belong to this story.
|
||||
- Say what the person is up against, and what they want.
|
||||
- Do not describe the writing, the mood, or who is telling the story.
|
||||
- Output only the sentence."""
|
||||
|
||||
# A full stop after these is not the end of a sentence; nor is one after a single capital (an initial).
|
||||
END = re.compile(r'(?<!\bMr)(?<!\bMrs)(?<!\bMs)(?<!\bDr)(?<!\bSt)(?<!\bNo)(?<![\s.][A-Z])[.!?](?:\s|$)')
|
||||
|
||||
|
||||
def first_sentence(text):
|
||||
text = re.sub(r'<think>.*?</think>', '', text, flags=re.S)
|
||||
text = re.sub(r'\s+', ' ', text).strip().strip('"')
|
||||
m = END.search(text)
|
||||
return text[:m.end()].strip() if m else text
|
||||
|
||||
|
||||
chunks = json.loads(pathlib.Path('chunks.json').read_text(encoding='utf-8'))
|
||||
if args.limit:
|
||||
chunks = chunks[:args.limit]
|
||||
out = pathlib.Path(args.out)
|
||||
state = json.loads(out.read_text()) if out.exists() else {'model': MODEL, 'prompt': SYSTEM, 'summaries': []}
|
||||
if state['model'] != MODEL or state['prompt'] != SYSTEM:
|
||||
raise SystemExit(f'{out} was made with a different model or prompt; choose another --out')
|
||||
done = state['summaries']
|
||||
print(f'{len(chunks)} scenes, resuming at {len(done)}, model {MODEL}', flush=True)
|
||||
|
||||
t0 = time.time()
|
||||
for i in range(len(done), len(chunks)):
|
||||
think = False if MODEL.startswith('qwen3') else None
|
||||
text, _, _ = ollama.chat(MODEL, SYSTEM, chunks[i]['text'], num_ctx=2048, num_predict=80, think=think)
|
||||
done.append({'chunk': i, 'story': chunks[i]['story'], 'summary': first_sentence(text), 'model_output': text})
|
||||
out.write_text(json.dumps(state, indent=1, ensure_ascii=False), encoding='utf-8')
|
||||
if (i + 1) % 10 == 0 or i + 1 == len(chunks):
|
||||
print(f' {i + 1}/{len(chunks)} {time.time() - t0:.0f}s', flush=True)
|
||||
|
||||
leaks = [x for x in done if set(re.findall(r"[a-zé]+", x['summary'].lower())) & TOPIC_WORDS]
|
||||
print(f'done: {len(done)} predicaments, {time.time() - t0:.0f}s this session; '
|
||||
f'{len(leaks)} still mention a job, relationship or place word')
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Build a review page: the groups gate G2 accepts, plus the nearest misses.
|
||||
|
||||
python3 review.py # k = 60, seed 24 -> review/k60-s24/
|
||||
python3 review.py --k 60 --seed 31 --borderline 8
|
||||
|
||||
Writes into the run directory:
|
||||
REVIEW.html open it in a browser: the whole review happens there
|
||||
candidates.json what was found, for apply_review.py to check the saved review against
|
||||
|
||||
When the review is saved from the page:
|
||||
python3 apply_review.py review/k60-s24 -> situations.json
|
||||
|
||||
No inference: this uses the summaries and embeddings already on disk.
|
||||
"""
|
||||
import argparse, datetime, hashlib, json, pathlib
|
||||
import gate2 as g
|
||||
import review_format as rf
|
||||
import review_page
|
||||
|
||||
|
||||
def gate_failures(sig, gate):
|
||||
out = []
|
||||
if gate.get('S1') is not None and sig['S1'] < gate['S1']:
|
||||
out.append(f"cohesion S1 {sig['S1']:.3f}, needs {gate['S1']:.3f}")
|
||||
if gate.get('Z1') is not None and sig['Z1'] < gate['Z1']:
|
||||
out.append(f"cohesion against same-size random sets Z1 {sig['Z1']:.1f}, needs {gate['Z1']}")
|
||||
if gate.get('MMIN') is not None and sig['MMIN'] < gate['MMIN']:
|
||||
out.append(f"weakest scene MMIN {sig['MMIN']:.3f}, needs {gate['MMIN']:.3f}")
|
||||
if gate.get('W') is not None and sig['W'] >= gate['W']:
|
||||
out.append(f"the word \"{sig['W_word']}\" is in {sig['W']:.0%} of summaries, limit {gate['W']:.0%}")
|
||||
return out
|
||||
|
||||
|
||||
def shortfall(sig, gate):
|
||||
"""How far a rejected group is from passing; smaller is closer."""
|
||||
s = 0.0
|
||||
if gate.get('S1') is not None: s += max(0.0, gate['S1'] - sig['S1']) / 0.01
|
||||
if gate.get('Z1') is not None: s += max(0.0, gate['Z1'] - sig['Z1'])
|
||||
if gate.get('W') is not None and sig['W'] >= gate['W']: s += 10 * (sig['W'] - gate['W'] + 0.01)
|
||||
return s
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
ap.add_argument('--k', type=int, default=60, help='number of k-means clusters (default 60)')
|
||||
ap.add_argument('--seed', type=int, default=24, help='k-means seed (default 24)')
|
||||
ap.add_argument('--borderline', type=int, default=8, help='near misses offered as optional extras (default 8)')
|
||||
ap.add_argument('--out', help='run directory (default review/k<K>-s<SEED>)')
|
||||
args = ap.parse_args()
|
||||
|
||||
run_id = f'k{args.k}-s{args.seed}'
|
||||
out = pathlib.Path(args.out or f'review/{run_id}')
|
||||
|
||||
gate = json.loads(pathlib.Path('gate2-frozen.json').read_text())
|
||||
found = [c for c in g.kmeans(args.k, args.seed) if g.eligible(c)]
|
||||
scored = [(c, g.signals(c)) for c in found]
|
||||
accepted = sorted([cs for cs in scored if g.g2_passes(cs[1], gate)], key=lambda cs: -cs[1]['S1'])
|
||||
rejected = sorted([cs for cs in scored if not g.g2_passes(cs[1], gate)], key=lambda cs: shortfall(cs[1], gate))
|
||||
borderline = rejected[:args.borderline]
|
||||
|
||||
listed = [(f'C{i + 1:02d}', 'accepted', c, s) for i, (c, s) in enumerate(accepted)]
|
||||
listed += [(f'B{i + 1:02d}', 'borderline', c, s) for i, (c, s) in enumerate(borderline)]
|
||||
|
||||
candidates = {}
|
||||
for cid, kind, members, sig in listed:
|
||||
member_rows = [{'member': rf.member_id(cid, n + 1), 'chunk': chunk, 'story': g.chunks[chunk]['story'],
|
||||
'title': g.chunks[chunk]['title'].title(), 'summary': g.text[chunk]}
|
||||
for n, chunk in enumerate(sorted(members))]
|
||||
candidates[cid] = {
|
||||
'kind': kind,
|
||||
'stats': {'scenes': sig['size'], 'stories': sig['stories'], 'largest_story_share': round(sig['dominant'], 3),
|
||||
'S1': round(sig['S1'], 4), 'Z1': round(sig['Z1'], 2), 'W': round(sig['W'], 3), 'W_word': sig['W_word']},
|
||||
'gate_failures': gate_failures(sig, gate),
|
||||
'role_hints': review_page.role_hints(member_rows),
|
||||
'members': member_rows,
|
||||
}
|
||||
# Ties a saved review to exactly these groups, so progress for a different set of groups
|
||||
# that happens to share the run name is never loaded into this page.
|
||||
fingerprint = hashlib.sha1(json.dumps({cid: [m['chunk'] for m in c['members']] for cid, c in candidates.items()},
|
||||
sort_keys=True).encode()).hexdigest()[:12]
|
||||
document = {
|
||||
'schema_version': rf.SCHEMA_VERSION,
|
||||
'run': {'id': run_id, 'k': args.k, 'seed': args.seed, 'fingerprint': fingerprint,
|
||||
'created': datetime.datetime.now().astimezone().isoformat(timespec='seconds'),
|
||||
'gate': {'name': 'G2', **gate}, 'scenes': g.n, 'stories': len(set(g.story)),
|
||||
'candidates_found': len(found), 'accepted': len(accepted), 'borderline': len(borderline)},
|
||||
'candidates': candidates,
|
||||
}
|
||||
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
(out / 'candidates.json').write_text(json.dumps(document, indent=1, ensure_ascii=False), encoding='utf-8')
|
||||
(out / 'REVIEW.html').write_text(review_page.render_page(document), encoding='utf-8')
|
||||
|
||||
print(f'{run_id}: {len(found)} candidate groups, G2 accepts {len(accepted)}, {len(borderline)} optional extras')
|
||||
print(f'open this in a browser: {(out / "REVIEW.html").resolve()}')
|
||||
print(f'when the review is saved: python3 apply_review.py {out}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user