PR 54 Prose Coordinates - neurostuff/ns-pond-ingestion-workflow GitHub Wiki
#54 Coordinates in prose
Opened 2026-10-06. Frozen once merged. The system as it stands is described in Design.
Some papers report their peaks only in the text — "greater activation in the
left amygdala (x = −22, y = −4, z = −18; t = 4.1)" — and many report a few
there besides their tables: a correlation computed at a peak, a seed, an ROI
follow-up. The pipeline read tables only. This change gives prose the same
two steps tables have, an extraction and a model call, in three stages that
run when prose_model is set.
download → extract → passages → metadata → triage → analyses → prose → resolve → space → upload → sync
(tables) (prose) (tables) (prose)
The stages, and why there
passages is prose's extraction, as extract is the tables': no model.
It requires download, nothing later. A paper reporting its peaks only in
its text often has no table that parsed, or none at all, so neither a
successful extraction nor table triage is asked for. It reads the article's
own download — best first, pubget's JATS, Elsevier's XML, ACE's HTML, a PDF's
text layer — keeps the Methods and Results with every figure legend (the
whole text when no section is recognised), leaves table markup out (the
tables are the table path's), and keeps the passages the detector finds.
A cheap test on the raw file runs before anything is parsed. Tables and tags are stripped by regex and the detector is put to the text around every match of its patterns; a no is final. PDFs skip it, since their bytes are not text.
metadata runs after both extractions and before both models. It also
takes an article with no extraction whose passages hold a coordinate — the
providers are asked by identifier alone — and the prose model reads each
passage with the article's title and abstract: the fine-tune was trained
with them on every passage.
prose is the model call, one per passage. Its fingerprint includes the
metadata it read with, so an article read before its metadata arrived is
read again once it does. An article with no passage gets an empty result
without a call, so resolve can pass its tables through.
resolve is a merge that costs nothing; kept apart from the model, its rule
can be revised and re-run without a call. It reads prose and, beside it,
analyses when the article has one, because requires names one parent.
It writes the analyses payload plus one more collection, prose, so space
reads it unchanged. An article with no extraction takes its space from the
same Methods and Results.
Switching it on does not re-upload the corpus. Where the prose adds
nothing, resolve records the analyses fingerprint it passed through and
space keys its own on that, so space and upload stay fresh for every
article already processed from its tables.
The only settings are prose_model and, when it has its own server,
prose_api_base. The context the model reads, the passage cap and the
sections kept were settled by the experiments below.
A file the detector cannot finish in a minute fails its article rather than holding its batch: one Elsevier file whose stripped tags left 500 spaces after a bare "x" held an overlapping-quantifier pattern for minutes before that was fixed.
Finding the passages
The detector began as the 2026-10-02 experiment's, and was measured on the 575 NeuroMetaBench gold peaks written in prose, in the text the stage reads.
| recall of prose gold | |
|---|---|
| experiment detector, extraction text | 44.5% |
| loose triplets beside a cue, extraction text | 72.9% |
| the same, download's Methods and Results | 83.5% |
| lists in one bracket, abbreviated regions, "n.s." | 92.2% |
The loose pattern takes a triplet with no closing bracket after z — "(−8, −96, 2, Z = 9.15)", "(Left: −30, 22, −8, max z: 3.11 …)" — beside a coordinate cue: a statistic, "peak", a Brodmann area, an ROI. Reading the download rather than the extraction's text gained the peaks in figure legends and in Elsevier tables the text had flattened into paragraphs. The last row reads every item of a list in one bracket ("[−24, −96, 9; 42, −81, −6]", "(−30, −81, 30 and 36, −75, 33)"), stops "n.s." ending a sentence, counts MPFC or STS as anatomy, and keeps an uncued triplet in a sentence that reports others.
Of the 45 peaks still missed, 23 are in the download only inside a table, 11 outside the Methods and Results, and the rest are one-offs: "(2, y = 37, z = −8)", "(ROI-5, 63, −9, −6; …)", positive triplets with no coordinate word.
The detector refuses the shapes that recur and are not coordinates — consecutive numbers, citation-shaped brackets, thousands separators — and leaves the rest to the model.
Reading them
Base NuExtract3 with an instruction, an object-form template with a role per point, and constrained decoding. Test sets: 78 hand-labelled passages, 111 NeuroMetaBench passages with hand-checked labels, 300 synthetic passages built from the failures below.
Base NuExtract3, and a fine-tune of it, with an instruction, an object-form template with a role per point (result, roi, seed, target, prior_study, figure, other), and constrained decoding. Test sets: 78 hand-labelled passages, 111 NeuroMetaBench passages with hand-checked labels, 300 synthetic passages built from the failures the gold set showed.
Names. An analysis is named for the contrast, effect or model its coordinates are a result of — "patients < controls, covarying for IQ", "positive correlation with craving", "PPI with amygdala seed: threat > safe" — never a heading, method or region. The labels were redone to this convention for this change. Base NuExtract3 under the earlier prompt named analyses after section headings ("Whole-brain analyses"); judged by an LLM against the labels, names of result points:
| gold exactly | gold +partly | NMB exactly | NMB +partly | |
|---|---|---|---|---|
| earlier prompt | 14% | 44% | 30% | 63% |
| naming prompt (N5) | 31–33% | 72–81% | 50–59% | 86–87% |
| fine-tune, same input | 65% | 93% | 74% | 91% |
What moved the base model: naming rules (most of it), concrete forms rather than "A > B" placeholders (the model copied the placeholder), letting a peak sit under several contrasts, and worked examples. A section heading or the sentences around the passage add a few points; both together no more.
Constrained decoding was not enough on its own. The worked examples made the base model loop, repeating whole analyses until the token budget cut the JSON off. The schema now requires every key and caps analyses, points and name length just above the most any label holds, so a loop ends as valid JSON, and exact repeats are dropped after.
The fine-tune is v21's recipe unchanged — LoRA on attention, MLP and the linear-attention mixers, A_log, dt_bias, conv1d and the norms trained directly — on 5,875 passages (875 real: luna-labelled silver and NMB; 5,000 synthetic), each with a random choice of heading and surrounding context. Two epochs, 1.5 h on four 8 GB cards. Against the naming prompt on the same input:
| coordinate precision | grouping precision | statistic type | role | |
|---|---|---|---|---|
| gold, prompt | 77–81% | 42–46% | 93–94% | 91% |
| gold, fine-tune | 99% | 81% | 96% | 92% |
| NMB, prompt | 83–89% | 76–83% | 91% | 92–93% |
| NMB, fine-tune | 97% | 97% | 98% | 90% |
Recall is 96–98% for both. The prompt's lower precision is repeats: counted once per coordinate it is 96–98%. Role is where neither improves.
Passages without coordinates. 336 of the fine-tune's training passages (5.7%) have an empty answer. On 869 corpus passages luna found no coordinate in — the second round's training data, unseen by the first — the fine-tune answers nothing for 863 and gives a result point for 4: eight points, each made from numbers that are not a coordinate, "(BA 9, 47)" as (47, 47, 47), "t(14) = 2.96" as (2.96, 2.96, 2.96). A point must now be written in the passage as x, y and z in that order, at most two numbers apart, which drops seven of the eight and 3 of 14,265 labelled points — a PDF's "227" for −27, and two silver labels not in their passage.
End to end
The whole path — detector, model, resolve against the paper's own table
analyses — over the 67 NeuroMetaBench papers whose prose passages are in the
test split (the fine-tune trained on the other papers' passages):
| gold peaks held | statistic type / value on added points | |
|---|---|---|
| tables alone | 675 of 1,380 (48.9%) | |
| tables and prose, base | 794 (57.5%) | 81.9% / 86.2% |
| tables and prose, fine-tune | 796 (57.7%) | 94.4% / 89.9% |
| the same, read from the download | 797 (57.8%) | 95.7% / 94.7% |
Over all 154 papers, 222 of the 266 prose gold peaks no table holds are added; 25 are missed by the detector and 19 are read under a non-result role (an ROI or crosshair the paper defined from its own result) and so dropped. The models recover the same gold; they differ in what else they upload.
In the wild, on 150 random corpus articles holding a passage — none of
them in any training set — every point resolve would upload, judged with
its passage:
| real results | name right | |
|---|---|---|
| base, articles with table analyses | 76% | 72% |
| base, articles without | 93% | 73% |
| fine-tune, with | 98% | 100% |
| fine-tune, without | 99% | 96% |
28 of the 87 articles without any table analysis gain results.
A second round did not help. Adding 2,400 corpus passages labelled by luna (names unreviewed) and 1,500 synthetic passages for the first round's failures — "lower in A than B" read as A > B, a null reverse contrast taken as the name, "-6–8 22" — fixed those failures on their synthetic test and raised the statistic accuracy (98.8% / 97.5% end to end), but merged covariate-adjusted repeats into one analysis (gold grouping precision 81% → 68%) and slipped in the wild (95% real, 92–94% names). A third round with the synthetic passages but none of the corpus ones fixed the same failures and kept the statistics (97.7% / 96.6%), but lost grouping precision as well (gold 74%, NMB 91%) and was a little behind in the wild (96–99% real, 91–95% names) — so it is the synthetic set, most likely the passages teaching "one analysis, though shown separately per condition", that teaches the model to merge. The first round's model stays the recommendation.
Resolving against the tables
A prose result within 1 mm of one of the article's table peaks is usually the text restating it — but not always. Of 277 hand-labelled prose results on a table peak, a judge reading both called 85 a different analysis at that peak: a correlation, a conjunction, an ROI follow-up. Keeping a prose result when it names an analysis computed at a peak, and no table analysis there does, keeps 46 of those 85 and re-keeps 20 of the 192 restatements — 79% agreement. A coordinate the prose reports under two contrasts stays under both.
Results, ROIs, seeds and stimulation targets are uploaded; other studies'
peaks, display locations and anything else are counted in the summary and
dropped. Each prose analysis holds one role, recorded in its metadata — which
upload sends with the analysis, since a Neurostore point has no field for it —
and seed points are flagged is_seed. An ROI or seed at one of the article's
own table peaks is that result reused, already uploaded from the table, and is
not uploaded again.
The corpus, and the upload
Run on 2026-10-07 over every downloaded article, with the round-one fine-tune.
| articles | |
|---|---|
| downloaded | 631,451 |
| ruled out by the raw filter, before parsing | 543,610 (86%) |
| with a passage | 37,674 (78,691 passages) |
| gaining prose points after resolve | 22,153 |
| of those, never uploaded before | 11,810 (69,610 points) |
| of those, already uploaded | 10,343 (47,222 points) |
Of 3,000 articles the filter ruled out, read anyway, five produced a passage and none of those was a coordinate (a voxel size, scan parameters, bacterial counts); it ruled out none of the 575 NeuroMetaBench gold prose peaks.
A new article gains a median of 4 coordinates (mean 5.9; 90% gain 13 or fewer) in a median of 2 analyses; an uploaded one 3 (mean 4.6). Of what resolve keeps, 86,677 points are results, 21,591 ROIs, 7,576 seeds and 1,029 targets; 26,310 more were restatements of a table peak and 7,005 other studies' peaks or display locations.
Luna judged every point resolve would upload from 150 new and 150 uploaded articles, none in any training set: 97.1% and 96.5% are real coordinates, names right for 90% and 89% (6–7% more partly); 149 and 148 of the articles hold at least one real point. Weakest: seeds in articles with no table, the role right for 72%.
The model read took four hours on four 8 GB cards. When the stages were
split, passages was run over the corpus in the catalog (an hour; identical
output) and the read migrated in as prose artifacts wherever the stage
would have read the same thing — the same passages, and the metadata the
model saw unchanged since — 35,948 articles. The 1,726 that had gained
metadata since, or had a failed passage, were read again.
Uploaded to Neurostore the same day, as the nuextract-v21 version:
11,809 new articles and 10,343 existing ones gaining prose analyses — 115,570
analyses in all, tables included. One failed: a new base study whose author
list is longer than Neurostore's authors index allows. Ten canary articles,
then forty more at random, read back from the API match the catalog's
analyses and points exactly; prose analyses carry source: prose and their
role in their metadata. Two traps on the way: 314 of the existing articles
had upload rows migrated with an empty fingerprint, which reads as fresh
under any version, so the run needed --refresh upload; and the API serves a
cached study for a while after an update, so read-back needs a cache-busting
query.
Synced to the ns-pond tree the same day: 22,152 articles; pmids.tsv
went from 39,249 to 51,415 rows with none lost (19 changed their source
column to the PDF extraction triage now judges). 130 articles failed the
first pass, having no extraction — 124 publisher pages ACE could not
identify, which the generic reader behind passages had read — so
passages now keeps the text it read, as extract does, and sync writes such
an article from it: its download, its text, no tables. In stage1, which
pondie reads, a prose analysis now carries source: prose and its role. 150
sampled articles match the catalog by content.
Reading extract's text instead of the download was measured and rejected: 75.5% of the gold prose peaks against 92.2%, mostly because extract writes each table into the text as flattened rows of numbers.
Meta-analyses stored as meta
A meta-analysis reports peaks pooled from other papers; counted beside group
studies, it counts them twice. Neurostore's level says which an article is
(group or meta), but upload set every article to group, and reset an
existing base study to it. Upload now reads the title
(services/study_level.py): a meta-analysis, systematic or umbrella review,
meta-regression, or an ALE or SDM analysis is meta; a primary study that
took its regions from one ("meta-analytically defined", "brain regions based
on a meta-analysis in patients with T2DM: a resting-state fMRI study") is
not; anything else keeps the level it has.
The same rule was applied to Neurostore on 2026-10-07: of 65,220 base
studies, 1,890 are titled as meta-analyses, 83 of them already meta; 1,807
base studies and 3,898 study versions (every source: 1,351 nuextract-v21,
1,173 llm, 299 neurosynth, 227 neuroquery, …) were set to meta, the old
levels saved beside the run. A version counted only when its title is its
base study's with more after it; 14 versions titled as another paper's
meta-analysis were left, as versions linked to the wrong base study.
Found, not fixed
Twenty-four points per analysis is a cap the model hits. The schema caps an analysis at 24 points, just above the largest label. 19 of 88,957 analyses, in 14 articles, end at exactly 24 — one passage of a twin study lists 71 triplets — so their remaining points and their own contrasts are lost. Splitting long passages before the model would fix it.
An author list too long for Neurostore's index fails the upload of a new
base study (ix_base_studies_authors, 2,704 bytes); upload should truncate
it.
"From a prior study" is a role, and should be a flag beside it: a paper can quote another study's result, or take another study's peak as its own ROI or seed. 12% of ROI and 7% of seed points come from other papers and are uploaded as this study's. Issue #55.
A seed or ROI is uploaded as an analysis like any other. Its role is in the analysis metadata only; a reader that ignores the metadata takes a seed for a peak.
The resolve rule is a heuristic, at 79% agreement with a judge. A model call does not beat it: given the passage, the prose analysis and the table analyses at the peak, base NuExtract3 agrees 71% (it keeps nearly every overlap) and the fine-tune 75%.
The fine-tuned weights are on beast only
(/data/alejandro/jk-prose-coords/ft/merged-n5-final), not published.