Skip to content

Commit 4939724

Browse files
authored
Merge pull request #16 from ScienceLiveHub/feat/build-chain-draft
Add build_chain_draft.py — produce the pre-filled chain-draft.json
2 parents 108093c + 27452c9 commit 4939724

7 files changed

Lines changed: 1694 additions & 24 deletions

File tree

docs/chain-draft-contract.md

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,53 @@ or `01_pcc` (`PCC_RESEARCH_QUESTION`); the optional `07_research_software`
8787
(`RESEARCH_SOFTWARE`) and `08_synthesis` (`RESEARCH_SYNTHESIS`) steps append when
8888
applicable.
8989

90+
## Repeatable and complex fields — array-shaped, form-field names
91+
92+
A few template fields are **repeatable groups** (an "add another" list) or custom
93+
widgets, and for these the component's form-field name is **not** the template
94+
placeholder name and the value is **not** a flat string. `prefill` must use the
95+
form-field name and the exact shape below (verified against the platform's
96+
`create/components/templates/*.tsx`). The producer hard-codes these; the wizard
97+
stays a pass-through.
98+
99+
| Step | Form field (prefill key) | Shape | Notes |
100+
|---|---|---|---|
101+
| `06_citation` | `st02` | `[{ cites, cited }]` | **required ≥1.** `cites` = a CiTO relation URI, `cited` = the cited work. This replaces flat `cites`/`cited`. |
102+
| `08_synthesis` | `sources` | `[{ source }]` | required ≥1 |
103+
| `08_synthesis` | `topicSelection` | `[{ uri, label }]` | required ≥1 |
104+
| `02_aida` | `st3` / `st4` | `[{ dataset }]` / `[{ publication }]` | optional |
105+
| `02_aida` | `topic` | `[{ uri, label }]` | optional |
106+
| `04_study` | `keywordSelection` | `[{ uri, label }]` | optional |
107+
| `04_study` | `disciplineSelection` | `{ uri, label }` | optional — a single object, **not** an array |
108+
| `07_research_software` | `datasets` / `researchOutputs` | `["url", …]` | optional — plain-string arrays |
109+
110+
Two runtime notes for the wizard: **date** fields (`05_outcome.date`,
111+
`08_synthesis.date`) want a JS `Date` — the wizard converts a `YYYY-MM-DD` prefill
112+
string to a `Date` before passing it on; and the `minItems: 1` groups above must
113+
always carry at least one entry or the form won't submit.
114+
115+
## Judgment fields are pre-filled, not left blank
116+
117+
The `restricted_choice` dropdowns (claim type, study type, validation status,
118+
confidence, CiTO relation) are **decisions the agent already made during the
119+
replication**, recorded in the drafts — not things to leave to a form default
120+
(which can be wrong). The producer reads the agent's ticked option from the draft
121+
and puts it in `prefill` as an editable suggestion, **and** keeps the field in
122+
`manual` so the wizard flags it "confirm" rather than "you choose". The CiTO
123+
relation is derived from the validation status (Validated→`confirms`,
124+
PartiallySupported→`qualifies`, Contradicted→`disputes`, …).
125+
126+
The **URI-suffix id** of each step (`claim`, `study`, `outcome`, …), if the draft
127+
gives none, is suggested as `<org>-<repo>-<step>` (from `CITATION.cff`'s
128+
`repository-code`), editable.
129+
130+
**Wikidata concept fields** (`04_study.keywordSelection`/`disciplineSelection`,
131+
`02_aida.topic`, `08_synthesis.topicSelection`) need a `{uri, label}` — but the
132+
draft records only plain labels. The producer resolves each label to a Wikidata
133+
QID at build time (one `wbsearchentities` call per label; this is the producer's
134+
only network use, and it degrades to leaving the field empty if Wikidata is
135+
unreachable). `disciplineSelection` is a single object; the rest are arrays.
136+
90137
## Carry-forward topology
91138

92139
Each step's published URI fills one field of the next step. These edges are fixed
@@ -103,6 +150,29 @@ for a FORRT chain and are declared in `carry_forward` so the wizard is generic:
103150
The wizard fills the carry-forward field from its captured URI; it does **not**
104151
appear in the producer's `prefill` (the URI does not exist until publish time).
105152

153+
### Optional side-branches (07 / 08) — multiple, non-adjacent, shaped edges
154+
155+
The two optional layers don't continue the linear chain — they link **back** to
156+
earlier steps, and a step can have **several** incoming edges. So a step may be
157+
the `into` of more than one edge, the `from` may be any earlier step (not just the
158+
immediately-preceding one), and the target field may be an array rather than a
159+
scalar. Two optional keys on an edge describe the target shape:
160+
161+
| From (published) | Into | Field | `mode` / `itemKey` | Injected as |
162+
|---|---|---|---|---|
163+
| `03_claim` | `07_research_software` | `project` | — (scalar) | `"<uri>"` |
164+
| `05_outcome` | `07_research_software` | `researchOutputs` | `mode: "uriList"` | `["<uri>"]` (appended) |
165+
| `05_outcome` | `08_synthesis` | `sources` | `mode: "uriObjectList"`, `itemKey: "source"` | `[{ "source": "<uri>" }]` (appended) |
166+
167+
- **No `mode`** → scalar string (the linear edges above, and `07.project`).
168+
- **`mode: "uriList"`** → append the URI to an array-of-strings field.
169+
- **`mode: "uriObjectList"`** (+ `itemKey`) → append `{ [itemKey]: uri }` to an
170+
array-of-objects field.
171+
172+
These edges are emitted only when **both** ends are present in the chain (the
173+
producer appends 07/08 only when their drafts have content). As with the linear
174+
edges, the carried field is absent from the step's `prefill`.
175+
106176
### Known friction (for the wizard implementer)
107177

108178
Most back-reference fields are plain text inputs (`02_aida.project`,
@@ -121,7 +191,8 @@ fields.
121191
"chain_shape": "paper-rooted", // "paper-rooted" | "pico" | "pcc"
122192
"source": {
123193
"repository": "https://github.com/OWNER/REPO",
124-
"commit": "<sha>" // the repo state the values were drawn from
194+
"commit": "<sha>", // the repo state the values were drawn from
195+
"figure": "figures/main_result.png" // optional; absent when the repo has none
125196
},
126197
"steps": [
127198
{
@@ -147,7 +218,11 @@ fields.
147218
{ "from": "02_aida", "into": "03_claim", "field": "aida" },
148219
{ "from": "03_claim", "into": "04_study", "field": "claim" },
149220
{ "from": "04_study", "into": "05_outcome", "field": "study" },
150-
{ "from": "05_outcome", "into": "06_citation", "field": "work" }
221+
{ "from": "05_outcome", "into": "06_citation", "field": "work" },
222+
// optional side-branches — only when 07/08 are in the chain (see above):
223+
{ "from": "03_claim", "into": "07_research_software", "field": "project" },
224+
{ "from": "05_outcome", "into": "07_research_software", "field": "researchOutputs", "mode": "uriList" },
225+
{ "from": "05_outcome", "into": "08_synthesis", "field": "sources", "mode": "uriObjectList", "itemKey": "source" }
151226
]
152227
}
153228
```
@@ -166,6 +241,31 @@ Rules:
166241
- **Determinism:** the producer does not stamp a timestamp (so regenerating on an
167242
unchanged repo yields an identical file); `source.commit` records the state.
168243

244+
## The headline figure
245+
246+
`source.figure` is the repo-relative path to the one image that represents the
247+
replication. It is **not** published in any nanopublication and the wizard does
248+
not consume it — it is recorded because the producer is where a missing figure
249+
can still be noticed and fixed.
250+
251+
The story page the platform generates from a published chain finds the figure by
252+
resolving the chain's Zenodo DOI to its GitHub repo and looking in `figures/`.
253+
So the figure reaches the blog by *being committed at that path*, not by being
254+
declared here. The producer applies the same rule the platform does:
255+
256+
- only `figures/` is scanned — never `results/`, which collects run artefacts;
257+
- among several images, a name matching `main`, `result`, `headline` or `hero`
258+
wins; otherwise the alphabetically first, so the pick never varies by machine;
259+
- `.png`, `.jpg`, `.jpeg`, `.webp`, `.svg` count as images.
260+
261+
When nothing is found the producer says so on stderr. The failure this catches is
262+
a figure written to a **git-ignored** path: it exists on the machine that ran the
263+
experiment, the author sees it locally, and the published story page has no image.
264+
265+
Because the choice is inferred from filenames rather than stated by the author,
266+
it is a sensible default and not a permanent contract — a future FORRT template
267+
field would let the author name the figure explicitly and have it signed.
268+
169269
## Value sources (what the producer fills from where)
170270

171271
| Field(s) | Source |

figures/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# figures/
2+
3+
Commit the image that represents your replication here.
4+
5+
One of these images becomes the illustration on your replication's **story page**
6+
— the readable, blog-style page the Science Live platform generates from your
7+
published FORRT chain. Which one is picked:
8+
9+
- a filename containing `main`, `result`, `headline` or `hero` wins;
10+
- otherwise the alphabetically first image in this directory;
11+
- `.png`, `.jpg`, `.jpeg`, `.webp` and `.svg` all count.
12+
13+
So `main_result.png` is the safe name when you have several.
14+
15+
## It has to be committed
16+
17+
This is the part that catches people out. Analysis scripts usually write their
18+
plots to `results/`, which this repo git-ignores along with the other run
19+
artefacts — so the figure exists on the machine that ran the experiment, the
20+
author sees it every day, and the published story page has no image at all.
21+
22+
`results/` is for run output. `figures/` is for the curated figure you want
23+
people to see. Copy or save it here, and commit it.
24+
25+
`scripts/build_chain_draft.py` prints a warning when this directory holds no
26+
image, so you find out while you can still fix it rather than after publishing.
27+
28+
## What makes a good one
29+
30+
The story page is read by people deciding whether your replication is worth
31+
their attention, so the figure should answer the question the replication asked
32+
— typically your result against the original's, with the uncertainty that
33+
decides whether they agree. A figure that shows only your own numbers makes the
34+
reader do the comparison themselves.

0 commit comments

Comments
 (0)