Corpus campaigns¶
A campaign turns a research topic or a seed DOI file into a reproducible list of scientific works. Discovery is deliberately separate from ingestion: you can inspect the candidates and resume network work before anything lands in the corpus.
Discover from a topic¶
Identify every API request with a monitored contact address:
uv run sci-rag campaign discover \
--topic "rice straw valorization" \
--name rice-straw \
--mailto you@example.org \
--max-results 100
Topic discovery searches OpenAlex and follows cursor pagination. Casual use
works without a key. For a larger API budget, set OPENALEX_API_KEY in the
environment. The kit never prints that key and never writes it to campaign
state.
Discover from DOI seeds¶
Put one bare DOI, DOI URL, or doi: value on each line. The reader skips
blank lines and lines beginning with #.
Then run:
uv run sci-rag campaign discover \
--doi-file seed-dois.txt \
--name seed-review \
--mailto you@example.org
DOI-file discovery normalizes and deduplicates the values, then retrieves bibliographic metadata from Crossref. The report counts invalid lines and malformed upstream records rather than letting them disappear.
Resume behavior¶
The default state path is data/campaigns/<name>/state.jsonl. The run appends
each completed DOI step and flushes it to disk. Repeating the command skips
DOI records already present in state. If a process dies during the final write,
the next run ignores only the truncated tail and safely continues appending.
OpenAlex and Crossref calls are rate limited and identify the contact address in both the query and User-Agent. The client retries HTTP 429 and server errors with bounded backoff. Exhausted retries fail visibly; they never become an empty success report.
Discovery metadata is not proof that a document may be redistributed. Resolve rights without downloading first:
uv run sci-rag campaign build \
--topic "rice straw valorization" \
--name rice-straw \
--mailto you@example.org \
--max-results 20 \
--dry-run
The dry run queries Unpaywall for each DOI, prints direct-PDF counts and the
license-class distribution, and writes only resumable state. It does not
create pdfs/ or corpus.jsonl.
Fail-closed rights mapping¶
Availability and redistribution rights are different signals. Unpaywall marking a work green or gold is not enough on its own. A work earns an open license class only when its selected location also carries an explicit, recognized license:
| Explicit location license | Corpus class |
|---|---|
| CC0 or public-domain mark | public |
| CC BY family | open_commercial |
| CC BY-NC family | open_noncommercial |
Missing, implied-oa, publisher-specific, or unrecognized |
unknown |
The mapping never infers a license from oa_status, a reachable URL, or a PDF
response. unknown is the intentional safe result when rights are unclear.
Download and ingest¶
After reviewing the dry-run distribution, repeat the command without
--dry-run. The builder fetches only Unpaywall's direct url_for_pdf for a
record marked open access. It never visits a landing page to scrape through a
paywall.
Each response must declare application/pdf, stay within --max-pdf-mb, and
begin with a PDF signature. The builder writes files through a temporary path
and renames them only after validation. After an interruption it reuses files
it has already verified rather than downloading them again.
Successful downloads produce data/campaigns/<name>/corpus.jsonl, in the same
format sci-rag ingest reads:
Every manifest row retains the normalized DOI, bibliographic metadata,
fail-closed license_class, and the exact Unpaywall license signal in
license_source.
Screen a discovered campaign¶
Write the review protocol as plain text. State the inclusion and exclusion criteria precisely enough that another reviewer could apply them without outside context:
Include field studies of rice-straw conversion with a measured material yield.
Exclude reviews, simulations without experimental validation, and studies of
other feedstocks.
Screen the abstracts already retained in campaign state:
uv run sci-rag campaign screen \
--name rice-straw \
--criteria-file screening-criteria.txt \
--confidence-threshold 0.8
The model receives abstracts in bounded batches and must return one strict
include or exclude decision, confidence, and reason per work. The command
does not trust that output blindly:
- confidence below the threshold becomes
review; - a missing abstract becomes
reviewwithout a model call; - malformed JSON, missing rows, duplicate indexes, and provider failures make
the affected batch
review; - no failure path silently excludes a work.
Decisions append to state.jsonl. screening-report.json records the current
protocol, its SHA-256 digest, the confidence floor, every per-work reason,
failure counts, and the current PRISMA-aligned totals. PRISMA is the
Preferred Reporting Items for Systematic Reviews and Meta-Analyses. It is
the reporting standard systematic reviews are held to, so aligning with it
means the counts are the ones a reviewer expects. Repeating the same
protocol resumes without calling the model again. Changing the criteria
or confidence floor starts a new set of decisions while preserving the old
append-only history.
The screening report begins at the deduplicated campaign-state boundary.
identified, screened, and the sum of included, excluded, and
awaiting_review therefore reconcile against the unique discovered works.
campaign discover already removed and reported the upstream duplicates, so
duplicates_removed is zero at this boundary. Exclusions also include a
reason breakdown rather than only an aggregate count.
Review uncertain rows¶
Walk the queue interactively:
For each row, choose include, exclude, or skip, then record a reason.
Human decisions append after the model suggestion instead of overwriting it,
and the report regenerates from the latest decision under that protocol.
Skipping leaves the row in awaiting_review, so the totals continue to
reconcile without pretending the campaign is complete.