Living Review Updater
Keep your systematic review up to date — without re-screening thousands of records by hand, and without leaving the screening tool you already use.
Maintaining a living systematic review means periodically re-running your searches and screening every new result. That screening burden is the #1 reason living reviews get abandoned (Cochrane, 2025 found only about half of a cohort of living reviews were ever updated). Existing automation either locks you inside a commercial platform (Nested Knowledge, DistillerSR) or makes you do the search-and-re-import by hand (ASReview). This tool fills the gap: it re-runs your searches, removes what you've already seen, and uses your own prior include/exclude decisions to put the most likely-relevant new studies at the top of the pile — then hands them back to the screening tool you already use.
Status: early development (v0.2.0, pre-release). The full pipeline runs end to end — searches, dedupe, ranking, digest and ASReview re-import — and has been exercised against the live PubMed and Europe PMC APIs. The core relevance mechanic is validated on a temporal (original→update) backtest (see below). Still missing before production use: validation with a real review team on their own review.
Install
pip install living-review-updater
ASReview user? Install with the integration extra to get the asreview livingreview subcommand and direct .asreview project-file support (see Use with ASReview):
pip install 'living-review-updater[asreview]'
Requires Python 3.10+. To try the latest unreleased changes, install from source:
pip install git+https://github.com/mattebso/living-review-updater
Does the core idea actually work?
Yes — measured on 26 real systematic reviews (the public SYNERGY dataset). Training a classifier on a review's prior screening decisions and ranking the records published after a cutoff date — a true simulation of an update, where the "new batch" is temporally later, not a random sample:
| Median across the reviews | |
|---|---|
| Screening needed to catch 95% of new relevant studies | ~24% of the new records |
| Screening work saved at 95% recall (WSS@95) | ~71% |
In plain terms: instead of reading every new search result, a team could typically read the top quarter and still catch ~95% of what matters. The spread across reviews is wide: in some reviews the ranking saved >90% of the work; in the worst it saved almost none. That is exactly why every run calibrates against your review's own decisions and reports what it can't promise, rather than quoting a global average at you.
(A conventional random-split benchmark on the same data gives a rosier median of ~84% — that figure matches the published literature on these datasets but flatters the real update use case, so we don't lead with it.) Full methods and honest caveats: eval/backtest3-temporal-RESULTS.md and eval/backtest-RESULTS.md.
Design principles (safety first)
Getting this wrong could hide a relevant study and bias a medical or policy conclusion. So:
- Rank and flag — never auto-exclude. The tool orders records by likely relevance; a human always makes the include/exclude call and stays accountable.
- Recall-first, and honest about it. Every run reports how much you'd need to screen to hit 95% / 99% / 100% recall on that batch, so you decide where to stop. It never claims to have found everything.
- Human-in-the-loop is mandatory, per the Cochrane Handbook (Ch. 22) and the RAISE / PRISMA-trAIce guidance on AI in evidence synthesis.
- Keep your existing tool. Output re-imports into ASReview; the tool does not replace your screening workflow.
- Open and self-hostable. Your search corpus and decisions never leave your machine. Free public APIs only; no always-on server needed.
How it works
-
Describe your review once in a small config file — your saved search per database, plus a CSV export of your prior screening decisions. Start from
examples/review.example.yaml. -
Run it:
living-review run --config review.yaml
Each run re-runs the searches (PubMed + Europe PMC), fetching only what is new since that source's last successful run → removes records already in your corpus or already screened → ranks the rest with a classifier trained on your prior decisions → writes a Markdown digest and a ranked RIS file.
-
Read the digest, then re-import
new_candidates.risinto ASReview and screen top-down. -
Run it on a cron or a scheduled GitHub Action.
Use with ASReview
If you screen in ASReview LAB, the optional integration removes the export/import friction. Install the extra (pip install 'living-review-updater[asreview]', ASReview LAB ≥ 3) and you get:
- A subcommand on the
asreviewCLI.asreview livingreview run --config review.yamlruns exactly the same pipeline asliving-review run— it just lives where your other ASReview commands live. - Direct
.asreviewproject-file support. Pointprior_decisionsin your config at your exported.asreviewfile (or an unpacked project directory) instead of a CSV — the tool reads your labeled include/exclude decisions from the project itself. Unlabeled and pending records are ignored, same as blank rows in a CSV.
# review.yaml
prior_decisions: "my-review.asreview" # instead of a CSV export
The update loop then looks like: screen in ASReview → asreview livingreview run → re-import the ranked new_candidates.ris into a new ASReview screening round → repeat. Built and tested against ASReview LAB 3.x; the plain CSV path never requires ASReview to be installed.
What a run produces
digests/<date>-update.md— how many records each source returned, how many survived dedupe, the calibration table (how far down the ranking you must screen for 95% / 99% / 100% recall, estimated by cross-validation on your own prior decisions), and the full ranked list. If you have few included records the digest says so plainly and tells you not to use the table as a stopping rule.new_candidates.ris— the same candidates in ranked order, for re-import into ASReview or any reference manager.corpus.sqlite— the local ledger of everything seen, plus each source's incremental cursor. Nothing leaves your machine.
Prior decisions format
A CSV with title, abstract, and a label column (included, label_included, or included_final), where 1 = included and 0 = excluded. Rows labelled -1 or left blank are treated as never screened and ignored. This is what ASReview exports. With the [asreview] extra installed, prior_decisions may instead point directly at a .asreview project file or unpacked project directory.
License
MIT — see LICENSE. Built in the open as a public good; contributions welcome.
Metadata
Release files for living-review-updater 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| living_review_updater-0.2.0.tar.gz | 31.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| living_review_updater-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 58.1 kB
Release files / living_review_updater-0.2.0.tar.gz
| Download URL | living_review_updater-0.2.0.tar.gz |
|---|---|
| Size | 31.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4b78586f5e08ecb30051dd8d31ef1146e5b34acc2b6b7b1fe36f0be13417d6e7
|
|
BLAKE2b-256 checksum How to use checksums |
bc3a054c9e6feabc5b534f185348662d37f2515619b488157d05920a264125ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / living_review_updater-0.2.0-py3-none-any.whl
| Download URL | living_review_updater-0.2.0-py3-none-any.whl |
|---|---|
| Size | 26.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
080c96b3a0111c2d5ff7ab71c6b8fe1ccb0f2c6a807f565bf656820f6e852b63
|
|
BLAKE2b-256 checksum How to use checksums |
a63d40ea7f86208935a1b932c97329d569093bcd8c14a0e4e7489de8a55c61bc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|