Spaces:
Sleeping
Sleeping
File size: 11,502 Bytes
b92b744 285b041 b92b744 285b041 b92b744 285b041 b92b744 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | # Public S3 ingestion
OpenAlex literature comes exclusively from the anonymous public bucket
`s3://openalex/data/parquet/works/`. No OpenAlex API key, AWS account or signed URL
is used. ClinicalTrials.gov still uses its separate, keyless API. The snapshot
contains bibliographic metadata, citation references and available abstracts;
it is not a collection of full-text PDFs. The normalizer keeps each work's PubMed
Central ID when present; the API uses it at query time to read open-access full text
from Europe PMC (see [fulltext.py](../fulltext.py)). No full text is stored by ingestion.
Run these commands from `backend/` **on the batch host**, such as the sponsor
EC2 machine. Python reads selected Parquet columns over HTTPS, retains matching
rows there, computes embeddings there, and writes to Elasticsearch. Serving
FastAPI there also puts query embedding inference there. OpenAI handles PICO
parsing and bounded evidence extraction through the existing API integration.
The browser only sends queries and receives results; it never scans S3.
## Scope and planning
The default `hypertension-kidney` profile includes the **union** of those fields:
hypertension topics, the Nephrology subfield, additional renal topics, and
matching terms in titles, reconstructed abstracts and topic names. Exact rules
and the registry query are versioned in [scope.py](scope.py). All years and work
types are retained, including records without abstracts. Nonprimary works are
retained for discovery but excluded from primary-study statistical pools.
Missing abstracts do not imply missing results.
The opt-in `hypertension-kidney-pubmed` profile is the same topical scope restricted
to PubMed-indexed works (a PMID in `ids`, or `pubmed` in `indexed_in`). Update-date
partitions differ enormously in quality: in a 2026-09-19 check, a `2025-10` part had
0.0% PMIDs and 4.9% DOIs, a `2026-01` part 11.5% and 99.6%, and the identifier-poor
parts contain many records whose title and abstract belong to different works.
Registry linking matches on PMID and full text needs a PMCID, so a corpus built from
such parts cannot link. `--largest-first` does not avoid them; it selects by bytes.
The profile is recorded in checkpoints and provenance as its own scope. The Parquet
snapshot carries no `pmcid` in `ids`; the normalizer recovers it from PMC location URLs.
Completion means all matches under those rules in the supplied snapshot.
Incomplete metadata, terminology and source coverage prevent a promise of every
relevant study worldwide. The snapshot is a dated release, not a live feed.
```sh
.venv/bin/python -m app.bootstrap --plan
```
Planning is the default when neither `--plan` nor `--run` is supplied. It reads
only the manifest and prints its hash, date, part count, physical byte budget
and target index. On 2026-09-19 the public works manifest described **2,446 parts,
724,970,323,127 bytes, dated 2026-06-26**.
Partitions are update dates, **not topics**. Broad topic coverage requires
scanning all parts. Column projection can reduce traffic; the physical size
budget is conservative, not a measurement of transferred bytes or required
output disk space. The 5 GB default refuses a full scan instead of silently
importing a partial corpus.
## Full build on remote compute
Install dependencies using the root [setup guide](../../../README.md). Configure
Elasticsearch and models in the remote host's `backend/.env`. For an auditable
fresh corpus, choose a dedicated index such as
`ELASTIC_INDEX=studies-hypertension-kidney-s3`. Existing indexes are preserved and
updated by ID; importing does not delete unrelated older records.
Choose an existing persistent mount with room for the retained JSONL and index.
Inspect storage first: the sponsor machine's root disk may be small. The code
does not format or mount disks. After inspecting the plan, an example run on an
adequately provisioned batch host is:
```sh
.venv/bin/python -m app.bootstrap --run \
--data-dir /mnt/research/nullmap/2026-06-26 \
--max-snapshot-bytes 750000000000
```
Replace that directory with the actual persistent mount. Set the budget from
the printed manifest and host capacity. Repeat the same command and directory
to resume. There is no default year or record cap. The registry condition query
includes all statuses and study types and exhausts its cursor. Snapshot records
are normalized, weakly classified, embedded, linked across batches and indexed.
Bootstrap performs no paid LLM labeling or extraction.
For a bounded smoke test:
```sh
.venv/bin/python -m app.bootstrap --plan --max-files 1
.venv/bin/python -m app.bootstrap --run --max-files 1 --max-records 100 \
--registry-limit 20 --data-dir data/s3-smoke
```
If the selected part exceeds 5 GB, choose an explicit budget after inspecting
its size. A part may contain no scope matches. `--max-files`, `--max-records` and
`--registry-limit` are explicit partial runs. Use a separate directory for the
full run: changing selected files invalidates the scan checkpoint.
To continue after a `--max-files N` run without rescanning, start a new directory
with `--skip-files N` (optionally with another `--max-files` to work in chunks).
Provenance keeps the fingerprint of the whole release. A run that skips parts
never sets `complete_snapshot_scope=true`, even when every part has been covered
across runs; coverage is then the union of the per-directory checkpoints.
```sh
.venv/bin/python -m app.bootstrap --run --skip-registry --skip-files 550 --max-files 30 \
--max-snapshot-bytes 40000000000 --data-dir data/research-s3-continue/p0550-0579
```
## Checkpoints and coverage
Keep each output and its sidecars together:
| Artifact | Purpose |
| --- | --- |
| `manifest.json` | Pinned release metadata and part sizes |
| `openalex.jsonl` | Matched source records and snapshot provenance |
| `openalex.jsonl.checkpoint.json` | Committed bytes, part, within-part progress and coverage |
| `ctgov.jsonl` and checkpoint | Registry records, query signature and paging cursor |
| `*.materialize.json` | Indexed offset, counts, model and target-index signature |
| `corpus-summary.json` | Stage, declared scope, source completion and index health |
Writes flush before checkpoints advance. Restart rolls back uncommitted JSONL
bytes and repeats uncommitted index batches with idempotent IDs. Source, scope,
model and target-index signatures prevent incompatible resumes. Capped source
files may grow; materialization resumes at its committed offset. A new snapshot
release requires a new directory. A remote manifest change detected at scan
completion invalidates the completion claim.
`complete_snapshot_scope=true` means every part in the supplied manifest was
exhausted. A subset manifest establishes coverage only of that subset. The corpus
summary is complete only when the snapshot and registry query both finish.
Limits, interruptions and skipped sources remain explicit. Low disk space stops
the scan with its checkpoint preserved; it does not provision storage.
The importer uses bounded batches and an index-backed linker. It neither loads
the whole corpus into RAM nor duplicates all vectors into JSONL. Registry facts
override linked paper numbers; canonical links prevent double counting.
Metadata-only records are embedded using their available title/text and marked
as abstract unavailable in search results.
## Reference expansion without API calls
Search expands review citations using **already indexed** works and their stored
vectors, reporting missing references. There are no live OpenAlex lookups.
For missing references, collect IDs and run a separate S3 pass into the same
Elasticsearch index using a separate output directory:
```sh
.venv/bin/python -m app.ingest reference-ids \
--input /mnt/research/nullmap/2026-06-26/openalex.jsonl \
--output /mnt/research/nullmap/missing-references.txt
.venv/bin/python -m app.bootstrap --run --skip-registry \
--work-ids /mnt/research/nullmap/missing-references.txt \
--data-dir /mnt/research/nullmap/reference-backfill \
--max-snapshot-bytes 750000000000
```
Skip the backfill if the ID file is empty. `--work-ids` overrides topic filtering
so differently worded references can be recovered. This requires another scan:
S3 has no work-ID search endpoint. Only works in the snapshot can be recovered.
Backfills keep separate provenance and do not claim completion of the main build.
## Individual stages and model choices
`python -m app.ingest --help` exposes snapshot scans, registry fetching,
normalization, classification, embedding, indexing, reference IDs, TEI full-text
extraction, labeling, classifier training and the `pagerank` pass, which
recomputes within-corpus citation PageRank (references outside the index are
dropped) and bulk-updates the `pagerank`/`authority` rank-feature fields used
by retrieval. For example:
```sh
.venv/bin/python -m app.ingest snapshot --plan
.venv/bin/python -m app.ingest snapshot --input /data/sample.parquet \
--profile hypertension-kidney --output data/sample.jsonl --resume
.venv/bin/python -m app.ingest fetch ctgov --query 'kidney' --filter '' \
--all --output data/ctgov.jsonl --resume
.venv/bin/python -m app.ingest tei --input /data/tei/ --output data/tei.jsonl
```
Manifests use `{ "files": [{ "url": "…", "size_bytes": 123 }] }`; the public
`meta.content_length` form is supported. Local fixtures are supported for tests.
Remote paths accept only unsigned public OpenAlex works URLs. DuckDB installs
its `httpfs` extension under the output directory on first use.
The `tei` stage reads GROBID-style TEI XML full text (files or directories of
`*.xml`) and extracts null-hypothesis evidence with the deterministic, auditable
heuristic in [tei.py](tei.py): a dictionary of statistical terms (confidence
interval, p-value, correlation, effect size, sample size) locates candidate
sentences; the surrounding paragraph is scanned for null confirmations
("no significant difference", non-significant p-values, and confidence intervals
that straddle the null value of 0 for differences/correlations or 1 for
ratios); and the associated numbers are parsed from the body text, tables and
appendix. Each row carries the study fields consumed by `statistics.py` and
`classifier.py` (`estimate`, `ci_low`/`ci_high`, `p_value`, `effect_type`, `n`,
`result_label`, `null_score`, `evidence_span`) plus a `tei_findings` list and a
`null_findings` subset, each keeping the exact source sentence. Scores are
heuristic, never calibrated probabilities. OpenAlex itself ships bibliographic
Parquet, not TEI; point this stage at a full-text TEI corpus keyed by the same
work identifiers.
The legacy standalone `link` command loads its input into memory and is suitable
for small slices. Bootstrap uses the bounded index-backed path. For lexical-only
operation use `--skip-embeddings` and serve with `EMBEDDINGS_ENABLED=false`.
Keep indexing and query embedding models identical.
The default classifier remains the auditable phrase heuristic. The earlier small
LLM-labeled pilot failed evaluation and was not promoted. Labeling and model
training remain explicit experiments; see the historical
[validation record](../../../docs/VALIDATION.md) and
[registry fixture provenance](../../tests/fixtures/README.md).
Sources: [OpenAlex snapshot documentation](https://help.openalex.org/access/snapshot/)
and the [sponsor's OpenAlex README](https://voloridge-hack-mit-2026.s3.us-east-1.amazonaws.com/src/openalex/README.md).
|