hypgraph 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hypgraph/__init__.py +28 -0
- hypgraph/__main__.py +3 -0
- hypgraph/bundle.py +216 -0
- hypgraph/checks.py +140 -0
- hypgraph/cli.py +445 -0
- hypgraph/demos/__init__.py +3 -0
- hypgraph/demos/broad_street.py +283 -0
- hypgraph/derive.py +390 -0
- hypgraph/graph.py +1002 -0
- hypgraph/mcp.py +461 -0
- hypgraph/plan.py +328 -0
- hypgraph/policy.py +51 -0
- hypgraph/schema.py +141 -0
- hypgraph/server.py +202 -0
- hypgraph/skill/SKILL.md +94 -0
- hypgraph/store.py +139 -0
- hypgraph/ui/__init__.py +7 -0
- hypgraph/ui/atlas.css +219 -0
- hypgraph/ui/atlas.js +1052 -0
- hypgraph/ui/hypgraph.svg +10 -0
- hypgraph/ui/index.html +30 -0
- hypgraph/ui/vendor/LICENSES.md +10 -0
- hypgraph/ui/vendor/cose-base.js +3214 -0
- hypgraph/ui/vendor/cytoscape-fcose.js +1549 -0
- hypgraph/ui/vendor/cytoscape.min.js +32 -0
- hypgraph/ui/vendor/layout-base.js +5230 -0
- hypgraph-0.1.0.dist-info/METADATA +140 -0
- hypgraph-0.1.0.dist-info/RECORD +31 -0
- hypgraph-0.1.0.dist-info/WHEEL +4 -0
- hypgraph-0.1.0.dist-info/entry_points.txt +2 -0
- hypgraph-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Broad Street, 1854 — a teaching reconstruction of how cholera was shown to travel in water.
|
|
3
|
+
|
|
4
|
+
The roles are simplified and the record is kept as if the people involved had kept it at the time. The figures are
|
|
5
|
+
the ones John Snow published (On the Mode of Communication of Cholera, 2nd ed., 1855): the 535 inmates of the Poland
|
|
6
|
+
Street workhouse, five of whom died, and his comparison of the two water companies (Table IX). The point is the method:
|
|
7
|
+
|
|
8
|
+
- two competing hypotheses, each with predictions — water-borne (Snow) and elevation / bad air (Farr);
|
|
9
|
+
- an experiment that tells them apart, with what each hypothesis expects written down *before* the result;
|
|
10
|
+
- a skeptic (the Rev. Henry Whitehead) who objects, runs his own inquiry on new data, and ends up replicating it;
|
|
11
|
+
- a decision (taking the handle off the pump) pinned to the version of the claim it rested on, flagged REVISIT when
|
|
12
|
+
that claim is later revised;
|
|
13
|
+
- and three overclaims the record refuses — what students (and agents) are tempted to write.
|
|
14
|
+
|
|
15
|
+
from hypgraph.demos import broad_street
|
|
16
|
+
g, inv, story = broad_street.build()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
|
|
23
|
+
from ..graph import Graph, GraphError
|
|
24
|
+
from ..policy import Policy
|
|
25
|
+
|
|
26
|
+
TITLE = "How does cholera spread? Broad Street, 1854"
|
|
27
|
+
|
|
28
|
+
TABLE_IX_CSV = """company,houses,cholera_deaths,deaths_per_10000_houses
|
|
29
|
+
Southwark and Vauxhall,40046,1263,315
|
|
30
|
+
Lambeth,26107,98,37
|
|
31
|
+
Rest of London,256423,1422,59
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DemoClock:
|
|
36
|
+
"""The demo's clock: it stands at whatever date the story has reached."""
|
|
37
|
+
def __init__(self, at: str = "1854-09-01T09:00:00+00:00"):
|
|
38
|
+
self.at = at
|
|
39
|
+
|
|
40
|
+
def __call__(self) -> str:
|
|
41
|
+
return self.at
|
|
42
|
+
|
|
43
|
+
def set(self, day: str, time: str = "10:00:00") -> None:
|
|
44
|
+
self.at = f"{day}T{time}+00:00"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build(g: Graph | None = None, path: str | None = None) -> tuple[Graph, str, list[dict]]:
|
|
48
|
+
"""Build the investigation. Returns (graph, investigation id, story) — the story is every step in order, with the
|
|
49
|
+
refusals the record made along the way."""
|
|
50
|
+
clock = DemoClock()
|
|
51
|
+
if g is None:
|
|
52
|
+
g = Graph(path, policy=Policy(reviewers={"whitehead"}), clock=clock)
|
|
53
|
+
else:
|
|
54
|
+
g.clock = clock
|
|
55
|
+
story: list[dict] = []
|
|
56
|
+
|
|
57
|
+
def say(day: str, who: str, what: str, node: dict | None = None, **kw) -> None:
|
|
58
|
+
story.append({"at": day, "actor": who, "text": what, **({"node": node["id"], "code": node["code"]}
|
|
59
|
+
if node else {}), **kw})
|
|
60
|
+
|
|
61
|
+
def refused(day: str, who: str, what: str, attempt) -> None:
|
|
62
|
+
try:
|
|
63
|
+
attempt()
|
|
64
|
+
except GraphError as e:
|
|
65
|
+
story.append({"at": day, "actor": who, "text": what, "refused": str(e)})
|
|
66
|
+
return
|
|
67
|
+
raise AssertionError(f"the demo expected a refusal: {what}")
|
|
68
|
+
|
|
69
|
+
snow, farr, whitehead, guardians, you = (g.author(x) for x in ("snow", "farr", "whitehead", "guardians", "you"))
|
|
70
|
+
|
|
71
|
+
# ── 1 September 1854: the question and the two hypotheses ─────────
|
|
72
|
+
clock.set("1854-09-01")
|
|
73
|
+
inv = snow.investigation(TITLE, "Find out how cholera passes from person to person — through the air of a "
|
|
74
|
+
"district, or through water contaminated by the sick.",
|
|
75
|
+
scope="London, the 1854 epidemic: the Golden Square (Soho) outbreak and the south "
|
|
76
|
+
"London water supply.",
|
|
77
|
+
success_criteria="An observation that one hypothesis predicts and the other forbids.",
|
|
78
|
+
reviewers=["whitehead"])["id"]
|
|
79
|
+
say("1854-09-01", "snow", "Opens the investigation.", g.get(inv))
|
|
80
|
+
q = snow.add("question", inv, text="How is cholera communicated?",
|
|
81
|
+
why="If it travels in water, cleaning the air does nothing; if in air, the water is no help.")
|
|
82
|
+
h1 = snow.add("hypothesis", inv, links=[{"rel": "answers", "to": q["id"]}],
|
|
83
|
+
statement="Cholera is spread by swallowing something from the evacuations of the sick, most "
|
|
84
|
+
"widely through drinking water contaminated with sewage.",
|
|
85
|
+
predictions=["Deaths follow the water supply, not the air: neighbours on different water die at "
|
|
86
|
+
"different rates.", "People near a contaminated source who drink other water are "
|
|
87
|
+
"spared."])
|
|
88
|
+
say("1854-09-01", "snow", "Hypothesis H1: cholera travels in contaminated water.", h1)
|
|
89
|
+
clock.set("1854-09-01", "11:00:00")
|
|
90
|
+
h2 = farr.add("hypothesis", inv, links=[{"rel": "answers", "to": q["id"]},
|
|
91
|
+
{"rel": "alternative_to", "to": h1["id"]}],
|
|
92
|
+
statement="Cholera is caused by a poison in the air (miasma) of low-lying, crowded districts, so "
|
|
93
|
+
"mortality falls as elevation rises.",
|
|
94
|
+
predictions=["Mortality depends on the district's elevation and air, whatever water people drink."])
|
|
95
|
+
say("1854-09-01", "farr", "Hypothesis H2: a poison in the air of low ground — the elevation law.", h2)
|
|
96
|
+
rf = farr.add("result", inv, kind="observed", outcome="positive",
|
|
97
|
+
summary="Across London's registration districts in the 1849 epidemic, cholera mortality fell "
|
|
98
|
+
"steadily as elevation above the Thames rose.",
|
|
99
|
+
limitations="Low districts also drew their water from the most polluted reaches of the Thames.",
|
|
100
|
+
links=[{"rel": "supports", "to": h2["id"],
|
|
101
|
+
"why": "Farr's elevation law is what a poison in low-lying air predicts."}])
|
|
102
|
+
say("1854-09-01", "farr", "Farr's 1849 returns: mortality falls with elevation. It supports H2 — "
|
|
103
|
+
"but note the limitation he didn't control for.", rf)
|
|
104
|
+
|
|
105
|
+
# ── the Golden Square outbreak ───────────────────────────────────
|
|
106
|
+
clock.set("1854-09-03")
|
|
107
|
+
x2 = snow.add("experiment", inv, method="house-to-house inquiry",
|
|
108
|
+
aim="Find what the victims of the Golden Square outbreak had in common.",
|
|
109
|
+
protocol="Take every cholera death in the district from the General Register Office returns, "
|
|
110
|
+
"visit each house, ask where the household drew its water, and mark each death on a "
|
|
111
|
+
"street map with the pumps.",
|
|
112
|
+
controls="Groups living among the deaths who drew water elsewhere — the Poland Street workhouse "
|
|
113
|
+
"has its own well.",
|
|
114
|
+
success_criteria="The deaths either cluster on one water source or they don't.",
|
|
115
|
+
links=[{"rel": "tests", "to": h1["id"], "data": {"expected":
|
|
116
|
+
"Deaths cluster on the Broad Street pump; neighbours on other water are spared."}},
|
|
117
|
+
{"rel": "tests", "to": h2["id"], "data": {"expected":
|
|
118
|
+
"Deaths spread over the district's low, crowded streets whatever the water."}}])
|
|
119
|
+
say("1854-09-03", "snow", "Plans the inquiry and writes down, before looking, what each hypothesis expects.", x2)
|
|
120
|
+
clock.set("1854-09-06")
|
|
121
|
+
ra = snow.add("run", inv, kind="agent", state="succeeded",
|
|
122
|
+
name="House-to-house inquiry, Golden Square district, September 1854",
|
|
123
|
+
note="Snow's own inquiry, reported by him (an agent-reported run, in the record's terms).",
|
|
124
|
+
links=[{"rel": "executes", "to": x2["id"]}])
|
|
125
|
+
rs_a = snow.add("result", inv, kind="observed", outcome="positive",
|
|
126
|
+
summary="Of 535 inmates of the Poland Street workhouse, which had its own well, 5 died of "
|
|
127
|
+
"cholera, though the houses around it were struck hard.",
|
|
128
|
+
values={"workhouse": {"k": 5, "n": 535}},
|
|
129
|
+
links=[{"rel": "produced", "from": ra["id"]}])
|
|
130
|
+
rs_map = snow.add("result", inv, kind="observed", outcome="positive",
|
|
131
|
+
summary="Nearly every death was in a house nearer the Broad Street pump than any other, or of "
|
|
132
|
+
"someone known to drink from it.",
|
|
133
|
+
links=[{"rel": "produced", "from": ra["id"]}])
|
|
134
|
+
rs_h = snow.add("result", inv, kind="observed", outcome="positive",
|
|
135
|
+
summary="A widow in Hampstead, far from Soho, who had Broad Street water carried to her because "
|
|
136
|
+
"she liked its taste, died of cholera; so did her niece who drank it with her.",
|
|
137
|
+
links=[{"rel": "produced", "from": ra["id"]}])
|
|
138
|
+
say("1854-09-06", "snow", "Results: the workhouse with its own well (5 of 535 died); the map; the Hampstead widow.",
|
|
139
|
+
rs_a)
|
|
140
|
+
|
|
141
|
+
# ── 7 September: the claim, and what the record refuses ──────────
|
|
142
|
+
clock.set("1854-09-07")
|
|
143
|
+
support = [{"rel": "supports", "from": r["id"], "why": w} for r, w in (
|
|
144
|
+
(rs_a, "Neighbours on other water were spared, as H1 predicts."),
|
|
145
|
+
(rs_map, "The deaths cluster on one water source."),
|
|
146
|
+
(rs_h, "A death far away, linked only by the water."))]
|
|
147
|
+
refused("1854-09-07", "you", "Tries to write: \"Every death was near the pump.\"", lambda: you.add(
|
|
148
|
+
"claim", inv, statement="Every death in the outbreak was near the Broad Street pump.",
|
|
149
|
+
scope="The Golden Square outbreak of September 1854.", links=support))
|
|
150
|
+
cl1 = snow.add("claim", inv, links=support, measurements=[
|
|
151
|
+
{"name": "workhouse deaths", "result": rs_a["id"], "key": "workhouse", "k": 5, "n": 535}],
|
|
152
|
+
statement="The Golden Square outbreak was spread by water from the Broad Street pump: those who drank it "
|
|
153
|
+
"died, while 5 of the 535 workhouse inmates, who had their own well, died.",
|
|
154
|
+
scope="The Golden Square (Soho) outbreak of late August and early September 1854, from Snow's house-to-house "
|
|
155
|
+
"inquiry and the General Register Office death returns.")
|
|
156
|
+
say("1854-09-07", "snow", "Claim CL1: the outbreak was spread by the Broad Street pump — numbers bound to RS1.", cl1)
|
|
157
|
+
whitehead.review(cl1["id"], "objection", method="Read Snow's account against what I know of my parishioners.",
|
|
158
|
+
notes="The Broad Street water is thought the best in the parish. I expect a careful house-to-house "
|
|
159
|
+
"inquiry to show the deaths followed the district's air, not one pump.")
|
|
160
|
+
say("1854-09-07", "whitehead", "The curate of St Luke's objects — and sets out to prove Snow wrong.", g.get(cl1["id"]))
|
|
161
|
+
|
|
162
|
+
clock.set("1854-09-08")
|
|
163
|
+
d1 = guardians.add("decision", inv, links=[{"rel": "based_on", "to": cl1["id"]}],
|
|
164
|
+
choice="Remove the handle of the Broad Street pump.",
|
|
165
|
+
alternatives=["Wait for the official inquiry.", "Clean the streets and sewers (the miasma "
|
|
166
|
+
"remedy)."],
|
|
167
|
+
reasons="Snow's inquiry ties the deaths to the pump's water, and taking off the handle costs "
|
|
168
|
+
"little if he is wrong.",
|
|
169
|
+
revisit_if="The deaths turn out not to follow the pump.", meeting="Board of Guardians, "
|
|
170
|
+
"St James's parish, 7 September 1854")
|
|
171
|
+
say("1854-09-08", "guardians", "Decision D1: take the handle off the pump — pinned to CL1 as it stood.", d1)
|
|
172
|
+
refused("1854-09-08", "you", "Tries to write: \"Removing the handle ended the epidemic\", with nothing behind it.",
|
|
173
|
+
lambda: you.add("claim", inv, statement="Removing the pump handle ended the epidemic."))
|
|
174
|
+
|
|
175
|
+
# ── the grand experiment: two water companies, one street ────────
|
|
176
|
+
clock.set("1854-11-20")
|
|
177
|
+
data = snow.add("artifact", inv, sha256=hashlib.sha256(TABLE_IX_CSV.encode()).hexdigest(),
|
|
178
|
+
path="data/snow-1855-table-ix.csv", size=len(TABLE_IX_CSV.encode()), format="csv")
|
|
179
|
+
x1 = snow.add("experiment", inv, method="comparison by water company",
|
|
180
|
+
aim="Compare cholera deaths in houses supplied by the two companies that share the same streets of "
|
|
181
|
+
"south London.",
|
|
182
|
+
protocol="For the first seven weeks of the 1854 epidemic, find which company supplied each house "
|
|
183
|
+
"where a cholera death was registered, and divide by the houses each company supplies.",
|
|
184
|
+
controls="The two companies' pipes run down the same streets: the houses share elevation, air, "
|
|
185
|
+
"poverty and drains, and differ in their water. Southwark and Vauxhall draws from the "
|
|
186
|
+
"Thames below the sewer outfalls; Lambeth moved its intake upstream in 1852.",
|
|
187
|
+
success_criteria="A large difference in deaths per house between the companies, or none.",
|
|
188
|
+
links=[{"rel": "tests", "to": h1["id"], "data": {"expected":
|
|
189
|
+
"Southwark and Vauxhall houses die at many times the Lambeth rate."}},
|
|
190
|
+
{"rel": "tests", "to": h2["id"], "data": {"expected":
|
|
191
|
+
"Houses side by side die at the same rate whichever company supplies them."}}])
|
|
192
|
+
say("1854-11-20", "snow", "Plans the grand experiment: neighbours, same air, different water.", x1)
|
|
193
|
+
rb = snow.add("run", inv, kind="agent", state="succeeded",
|
|
194
|
+
name="Deaths by water company, first seven weeks of the 1854 epidemic",
|
|
195
|
+
links=[{"rel": "executes", "to": x1["id"]}, {"rel": "uses", "to": data["id"], "data": {"role": "data"}}])
|
|
196
|
+
rs_b = snow.add("result", inv, kind="computed", outcome="positive",
|
|
197
|
+
summary="Southwark and Vauxhall houses: 1,263 deaths in 40,046 houses, 315 per 10,000. Lambeth: 98 "
|
|
198
|
+
"in 26,107, 37 per 10,000. The rest of London: 59 per 10,000.",
|
|
199
|
+
values={"southwark_vauxhall": {"k": 1263, "n": 40046}, "lambeth": {"k": 98, "n": 26107},
|
|
200
|
+
"rest_of_london": {"k": 1422, "n": 256423},
|
|
201
|
+
"sv_per_10000": {"value": 315}, "lambeth_per_10000": {"value": 37},
|
|
202
|
+
"rest_per_10000": {"value": 59}, "ratio": {"value": 8.5}},
|
|
203
|
+
links=[{"rel": "produced", "from": rb["id"]},
|
|
204
|
+
{"rel": "supports", "to": h1["id"], "why": "Neighbours on different water died at very "
|
|
205
|
+
"different rates, as H1 predicts."},
|
|
206
|
+
{"rel": "challenges", "to": h2["id"], "why": "Houses on the same streets, at the same "
|
|
207
|
+
"elevation and in the same air, died at 8.5 times the rate when their water came from "
|
|
208
|
+
"Southwark and Vauxhall. Elevation can't produce that."}])
|
|
209
|
+
say("1854-11-20", "snow", "Result RS: 315 against 37 deaths per 10,000 houses. It supports H1 and challenges H2.",
|
|
210
|
+
rs_b)
|
|
211
|
+
refused("1854-11-20", "you", "Tries to file it as an exclusion: \"This rules out the miasma theory.\"",
|
|
212
|
+
lambda: you.add("claim", inv, kind="exclusion", statement="The water-company comparison rules out the "
|
|
213
|
+
"miasma theory.", scope="South London, the 1854 epidemic.", links=[
|
|
214
|
+
{"rel": "supports", "from": rs_b["id"], "why": "the comparison is the evidence"}]))
|
|
215
|
+
cl2 = snow.add("claim", inv, links=[{"rel": "supports", "from": rs_b["id"],
|
|
216
|
+
"why": "The tally by company is the measurement this claim states."},
|
|
217
|
+
{"rel": "requires", "to": cl1["id"]}],
|
|
218
|
+
measurements=[{"name": "S&V rate", "result": rs_b["id"], "key": "sv_per_10000", "value": 315},
|
|
219
|
+
{"name": "Lambeth rate", "result": rs_b["id"], "key": "lambeth_per_10000", "value": 37},
|
|
220
|
+
{"name": "ratio", "result": rs_b["id"], "key": "ratio", "value": 8.5}],
|
|
221
|
+
statement="Houses supplied by the Southwark and Vauxhall Company had 315 cholera deaths per 10,000 "
|
|
222
|
+
"houses against 37 for Lambeth — 8.5 times the rate, in the same streets.",
|
|
223
|
+
scope="South London, the first seven weeks of the 1854 epidemic, deaths per 10,000 houses by the "
|
|
224
|
+
"company supplying each house.",
|
|
225
|
+
limitations="Which company supplied a house was found by asking, and for some by testing the "
|
|
226
|
+
"water's salt; a few houses may be assigned to the wrong company.")
|
|
227
|
+
say("1854-11-20", "snow", "Claim CL2: 8.5 times the rate in the same streets — every number bound to the result.",
|
|
228
|
+
cl2)
|
|
229
|
+
|
|
230
|
+
# ── 1855: the skeptic's own inquiry ──────────────────────────────
|
|
231
|
+
clock.set("1855-04-15")
|
|
232
|
+
rw = whitehead.add("run", inv, kind="agent", state="succeeded",
|
|
233
|
+
name="Second house-to-house inquiry of Broad Street, for the parish's Cholera Inquiry Committee",
|
|
234
|
+
note="A new inquiry by someone who expected the opposite answer, house by house, including "
|
|
235
|
+
"those who had fled the district.",
|
|
236
|
+
links=[{"rel": "replicates", "to": ra["id"], "data": {"differs": "new_data"}},
|
|
237
|
+
{"rel": "executes", "to": x2["id"]}])
|
|
238
|
+
rs_w = whitehead.add("result", inv, kind="observed", outcome="positive",
|
|
239
|
+
summary="Those who drank the pump water died at far higher rates than those who didn't. The "
|
|
240
|
+
"first case was an infant at 40 Broad Street whose nappies were rinsed into a "
|
|
241
|
+
"cesspool a few feet from the well.",
|
|
242
|
+
links=[{"rel": "produced", "from": rw["id"]},
|
|
243
|
+
{"rel": "supports", "to": cl1["id"],
|
|
244
|
+
"why": "An independent inquiry, on new data, by a skeptic, finds the same pattern "
|
|
245
|
+
"and the source of the contamination."}])
|
|
246
|
+
say("1855-04-15", "whitehead", "Whitehead's own inquiry, on new data, finds the pattern — and the first case.",
|
|
247
|
+
rs_w)
|
|
248
|
+
whitehead.review(cl1["id"], "resolved", method="My own house-to-house inquiry.",
|
|
249
|
+
notes="My objection doesn't survive my own data.")
|
|
250
|
+
whitehead.review(cl1["id"], "replicated", method="Independent house-to-house inquiry of the same outbreak.",
|
|
251
|
+
notes="Reproduced on new data: the deaths follow the pump, and the well was contaminated "
|
|
252
|
+
"from the cesspool at number 40.")
|
|
253
|
+
say("1855-04-15", "whitehead", "Resolves his own objection and records CL1 as replicated.", g.get(cl1["id"]))
|
|
254
|
+
|
|
255
|
+
# ── a revision: the claim changes, the decision is flagged ───────
|
|
256
|
+
clock.set("1855-06-01")
|
|
257
|
+
snow.revise(cl1["id"], reason="State a limit Snow himself noted: the outbreak was already falling.",
|
|
258
|
+
limitations="The outbreak was already declining when the handle was removed, partly because so many "
|
|
259
|
+
"had fled; the fall in deaths afterwards doesn't by itself show the pump was the cause.")
|
|
260
|
+
say("1855-06-01", "snow", "Revises CL1 to add a limitation. Whitehead's check was on v1, so it is stale now; and "
|
|
261
|
+
"D1 rested on v1, so it is flagged REVISIT.", g.get(cl1["id"]))
|
|
262
|
+
clock.set("1855-06-10")
|
|
263
|
+
whitehead.review(cl1["id"], "checked", method="Read v2 against my inquiry.",
|
|
264
|
+
notes="The added limitation is right and doesn't change the finding.")
|
|
265
|
+
say("1855-06-10", "whitehead", "Checks v2. CL1 is checked again; D1 still says REVISIT until the guardians look.",
|
|
266
|
+
g.get(cl1["id"]))
|
|
267
|
+
|
|
268
|
+
# ── what a student would do next ─────────────────────────────────
|
|
269
|
+
clock.set("2026-10-24", "12:00:00")
|
|
270
|
+
x3 = you.add("experiment", inv, method="comparison by water company, another epidemic",
|
|
271
|
+
aim="Repeat the water-company comparison in a different epidemic, to see whether it generalises.",
|
|
272
|
+
protocol="Take a later epidemic's death returns by household water supply and compute deaths per "
|
|
273
|
+
"10,000 houses for each supplier in the same streets.",
|
|
274
|
+
success_criteria="The same large gap between suppliers, or not.",
|
|
275
|
+
power_check="Plant a known two-fold difference in rates into the 1854 tallies and check the "
|
|
276
|
+
"comparison finds it at the new epidemic's size.",
|
|
277
|
+
cost_minutes=600,
|
|
278
|
+
links=[{"rel": "tests", "to": h1["id"], "data": {"expected": "Contaminated supply dies faster."}},
|
|
279
|
+
{"rel": "tests", "to": h2["id"], "data": {"expected": "No difference between suppliers."}},
|
|
280
|
+
{"rel": "motivated_by", "to": cl2["id"]}])
|
|
281
|
+
say("2026-10-24", "you", "Plans the follow-up the record ranks first: it separates H1 from H2, and can show its "
|
|
282
|
+
"own power.", x3)
|
|
283
|
+
return g, inv, story
|