feat(prisma): palliative-rfi PRISMA project — PubMed search, criteria, local-LLM screening fix (refs #642-#646, P42)
Mirrors the skin-subs (P29) systematic-review pipeline for the CY2027
NPRM's Community-Based Palliative Care RFI (CMS-1848-P, 91 FR
43949-43950, comments close 2026-09-14):
- dev/scripts/search_pubmed_palliative_rfi.py: five E-utilities
searches, one per RFI part (a-e), relevance-sorted with per-domain
caps (the palliative literature dwarfs skin subs; caps are printed
and disclosed, not silent). 1,227 retrieved / 1,189 unique stored to
bib with module:palliative-rfi + rfi:{a-e} tags.
- prisma.project: palliative-rfi STARTERS entry (criteria keyed to the
five RFI questions, RFI-mapped extraction template, exclusion-reason
codebook) + legacy cohort tag mapping.
- prisma.screen: long bulleted system prompts make qwen-family models
served through Ollama return an empty response (no text, no tool
call — bisected against qwen2.5:14b). System message is now a single
sentence and the guidance bullets moved into the cached first user
message, which every provider handles; also added a no-abstract
rule. Needed because screening runs on the local server GPU
(qwen2.5:14b-ctx8k) — the Anthropic key has no API credit and this
work is directed to local inference.
This commit is contained in:
265
dev/scripts/search_pubmed_palliative_rfi.py
Normal file
265
dev/scripts/search_pubmed_palliative_rfi.py
Normal file
@@ -0,0 +1,265 @@
|
||||
"""PubMed systematic literature search for the palliative-care RFI (P42).
|
||||
|
||||
Queries NCBI E-utilities across five domains, one per part of the
|
||||
CY2027 PFS NPRM's Community-Based Palliative Care RFI (CMS-1848-P,
|
||||
91 FR 43949-43950):
|
||||
|
||||
a. fwa_hospice_palliative — fraud/waste/abuse focus areas
|
||||
b. eligibility_prognosis — life-expectancy / prognosis gating
|
||||
c. eligibility_function — ADL impact + caregiver strain criteria
|
||||
d. care_management_delivery — community-based delivery models, teams
|
||||
e. quality_measures — palliative quality measurement
|
||||
|
||||
Reuses the E-utilities plumbing from search_pubmed_skin_subs.py (P29);
|
||||
adds relevance-sorted, per-domain-capped esearch because the palliative
|
||||
literature is far larger than the skin-substitute corpus — caps keep the
|
||||
identified set screenable within the comment window (closes 2026-09-14).
|
||||
|
||||
Results land in bib.sqlite via bib.store.Store with tags:
|
||||
module:palliative-rfi, source:pubmed, rfi:{a|b|c|d|e},
|
||||
type:{rct|review|meta-analysis|...}
|
||||
|
||||
Usage:
|
||||
uv run python dev/scripts/search_pubmed_palliative_rfi.py --dry-run
|
||||
uv run python dev/scripts/search_pubmed_palliative_rfi.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from search_pubmed_skin_subs import ( # noqa: E402
|
||||
EUTILS_BASE,
|
||||
RATE_LIMIT,
|
||||
Article,
|
||||
_params,
|
||||
deduplicate,
|
||||
efetch_articles,
|
||||
)
|
||||
|
||||
from bib.item import Source # noqa: E402
|
||||
from bib.store import Store # noqa: E402
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Search strategies — one domain per RFI part (91 FR 43949-43950)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Common anchor: the RFI's subject population/service.
|
||||
_ANCHOR = (
|
||||
'("palliative care"[MeSH] OR "palliative"[tiab] '
|
||||
'OR "serious illness"[tiab] OR "seriously ill"[tiab] '
|
||||
'OR "hospice care"[MeSH] OR "supportive care"[tiab])'
|
||||
)
|
||||
|
||||
_Q_FWA = (
|
||||
'("hospice care"[MeSH] OR "hospice"[tiab] OR "palliative"[tiab]) '
|
||||
'AND ("fraud"[MeSH] OR "fraud"[tiab] OR "program integrity"[tiab] '
|
||||
'OR "improper payment"[tiab] OR "improper payments"[tiab] '
|
||||
'OR "overutilization"[tiab] OR "upcoding"[tiab] OR "kickback"[tiab] '
|
||||
'OR "billing patterns"[tiab] OR "enforcement"[tiab] '
|
||||
'OR "medically unnecessary"[tiab] OR "for-profit ownership"[tiab] '
|
||||
'OR "live discharge"[tiab])'
|
||||
)
|
||||
|
||||
_Q_PROGNOSIS = (
|
||||
f"{_ANCHOR} "
|
||||
'AND ("life expectancy"[tiab] OR "terminal prognosis"[tiab] '
|
||||
'OR "prognostication"[tiab] OR "prognostic criteria"[tiab] '
|
||||
'OR "hospice eligibility"[tiab] OR "6-month prognosis"[tiab] '
|
||||
'OR "survival prediction"[tiab]) '
|
||||
'AND ("eligibility"[tiab] OR "identification"[tiab] '
|
||||
'OR "criteria"[tiab] OR "certification"[tiab] OR "accuracy"[tiab] '
|
||||
'OR "medicare"[tiab])'
|
||||
)
|
||||
|
||||
_Q_FUNCTION = (
|
||||
f"{_ANCHOR} "
|
||||
'AND ("activities of daily living"[MeSH] OR "functional status"[tiab] '
|
||||
'OR "functional decline"[tiab] OR "functional impairment"[tiab] '
|
||||
'OR "caregiver burden"[tiab] OR "caregiver strain"[tiab] '
|
||||
'OR "caregiver strain"[MeSH:noexp]) '
|
||||
'AND ("eligibility"[tiab] OR "identification"[tiab] '
|
||||
'OR "identifying"[tiab] OR "criteria"[tiab] OR "definition"[tiab] '
|
||||
'OR "screening"[tiab] OR "medicare"[tiab])'
|
||||
)
|
||||
|
||||
_Q_DELIVERY = (
|
||||
f"{_ANCHOR} "
|
||||
'AND ("community-based"[tiab] OR "home-based"[tiab] '
|
||||
'OR "home visit"[tiab] OR "home visits"[tiab] '
|
||||
'OR "interdisciplinary"[tiab] OR "care management"[tiab] '
|
||||
'OR "care coordination"[tiab] OR "outpatient palliative"[tiab] '
|
||||
'OR "ambulatory palliative"[tiab]) '
|
||||
'AND ("medicare"[tiab] OR "randomized controlled trial"[pt] '
|
||||
'OR "systematic review"[pt] OR "meta-analysis"[pt] '
|
||||
'OR "hospitalization"[tiab] OR "utilization"[tiab] '
|
||||
'OR "cost"[tiab] OR "costs"[tiab])'
|
||||
)
|
||||
|
||||
_Q_QUALITY = (
|
||||
f"{_ANCHOR} "
|
||||
'AND ("quality indicators, health care"[MeSH] '
|
||||
'OR "quality measure"[tiab] OR "quality measures"[tiab] '
|
||||
'OR "quality indicator"[tiab] OR "quality indicators"[tiab] '
|
||||
'OR "feeling heard and understood"[tiab] '
|
||||
'OR "patient-reported outcome"[tiab] '
|
||||
'OR "patient reported outcome"[tiab] OR "quality reporting"[tiab] '
|
||||
'OR "value pathway"[tiab] OR "MIPS"[tiab])'
|
||||
)
|
||||
|
||||
# Per-domain caps (relevance-sorted): the palliative literature dwarfs
|
||||
# the skin-subs corpus, and every identified record costs a stage-2 LLM
|
||||
# screening call. Caps are printed in the run log and disclosed in the
|
||||
# notebook's PRISMA narrative — capped identification is a stated
|
||||
# limitation, not silent truncation.
|
||||
SEARCHES: dict[str, dict] = {
|
||||
"fwa_hospice_palliative": {"query": _Q_FWA, "tags": ["rfi:a"], "cap": 250},
|
||||
"eligibility_prognosis": {"query": _Q_PROGNOSIS, "tags": ["rfi:b"], "cap": 250},
|
||||
"eligibility_function": {"query": _Q_FUNCTION, "tags": ["rfi:c"], "cap": 250},
|
||||
"care_management_delivery": {"query": _Q_DELIVERY, "tags": ["rfi:d"], "cap": 300},
|
||||
"quality_measures": {"query": _Q_QUALITY, "tags": ["rfi:e"], "cap": 250},
|
||||
}
|
||||
|
||||
MODULE_TAG = "module:palliative-rfi"
|
||||
|
||||
|
||||
def esearch_relevance(query: str, retmax: int) -> tuple[list[str], int]:
|
||||
"""esearch with sort=relevance so per-domain caps keep the best hits."""
|
||||
params = _params(
|
||||
db="pubmed",
|
||||
term=query,
|
||||
retmax=str(retmax),
|
||||
retmode="json",
|
||||
sort="relevance",
|
||||
usehistory="n",
|
||||
)
|
||||
resp = httpx.get(f"{EUTILS_BASE}/esearch.fcgi", params=params, timeout=30)
|
||||
resp.raise_for_status()
|
||||
result = resp.json().get("esearchresult", {})
|
||||
ids = result.get("idlist", [])
|
||||
count = int(result.get("count", 0))
|
||||
print(f" esearch: {count} total results, retrieved {len(ids)} PMIDs (cap {retmax})")
|
||||
return ids, count
|
||||
|
||||
|
||||
def article_to_source(article: Article, domain_tags: list[str]) -> Source:
|
||||
"""Convert an Article to a bib Source item (palliative-rfi tagging)."""
|
||||
tags = [MODULE_TAG, "source:pubmed"]
|
||||
if article.year:
|
||||
tags.append(f"year:{article.year}")
|
||||
type_tag = article.infer_type_tag()
|
||||
if type_tag:
|
||||
tags.append(type_tag)
|
||||
tags.extend(domain_tags)
|
||||
|
||||
author_str = "; ".join(article.authors[:10])
|
||||
if len(article.authors) > 10:
|
||||
author_str += f" (+{len(article.authors) - 10} more)"
|
||||
|
||||
extra_parts = [f"PMID: {article.pmid}"]
|
||||
if article.doi:
|
||||
extra_parts.append(f"DOI: {article.doi}")
|
||||
if author_str:
|
||||
extra_parts.append(f"Authors: {author_str}")
|
||||
if article.journal:
|
||||
extra_parts.append(f"Journal: {article.journal}")
|
||||
if article.pub_types:
|
||||
extra_parts.append(f"PubTypes: {'; '.join(article.pub_types)}")
|
||||
if article.mesh_terms:
|
||||
extra_parts.append(f"MeSH: {'; '.join(article.mesh_terms[:15])}")
|
||||
|
||||
return Source(
|
||||
title=article.title,
|
||||
url=article.url,
|
||||
date_published=article.year,
|
||||
institution=article.journal,
|
||||
abstract=article.abstract,
|
||||
doc_type="journal-article",
|
||||
tags=tags,
|
||||
extra="\n".join(extra_parts),
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="PubMed palliative-care RFI search")
|
||||
parser.add_argument(
|
||||
"--dry-run", action="store_true", help="esearch counts only, no efetch/store"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 70)
|
||||
print("PubMed Systematic Search: Community-Based Palliative Care RFI (P42)")
|
||||
print(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
print("=" * 70)
|
||||
|
||||
all_articles: dict[str, list[Article]] = {}
|
||||
all_pmids: dict[str, list[str]] = {}
|
||||
domain_totals: dict[str, int] = {}
|
||||
|
||||
for domain, cfg in SEARCHES.items():
|
||||
print(f"\n--- Domain: {domain} (tags {cfg['tags']}) ---")
|
||||
print(f" Query: {cfg['query'][:100]}...")
|
||||
time.sleep(RATE_LIMIT)
|
||||
pmids, total = esearch_relevance(cfg["query"], retmax=cfg["cap"])
|
||||
all_pmids[domain] = pmids
|
||||
domain_totals[domain] = total
|
||||
if args.dry_run or not pmids:
|
||||
all_articles[domain] = []
|
||||
continue
|
||||
all_articles[domain] = efetch_articles(pmids)
|
||||
print(f" Fetched {len(all_articles[domain])} articles")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("PRISMA Flow — Identification (relevance-capped)")
|
||||
print("=" * 70)
|
||||
total_identified = sum(len(v) for v in all_pmids.values())
|
||||
for domain, pmids in all_pmids.items():
|
||||
print(
|
||||
f" {domain:28s}: {len(pmids):>5} retrieved "
|
||||
f"(of {domain_totals[domain]} matching)"
|
||||
)
|
||||
print(f" {'TOTAL retrieved':28s}: {total_identified:>5}")
|
||||
|
||||
if args.dry_run:
|
||||
print("\n[DRY RUN] Skipping efetch and bib.sqlite write")
|
||||
return
|
||||
|
||||
merged = deduplicate(all_articles)
|
||||
print(f"\n Duplicates removed: {total_identified - len(merged):>5}")
|
||||
print(f" Unique articles: {len(merged):>5}")
|
||||
|
||||
pmid_domains: dict[str, list[str]] = {}
|
||||
for domain, articles in all_articles.items():
|
||||
for art in articles:
|
||||
for t in SEARCHES[domain]["tags"]:
|
||||
pmid_domains.setdefault(art.pmid, [])
|
||||
if t not in pmid_domains[art.pmid]:
|
||||
pmid_domains[art.pmid].append(t)
|
||||
|
||||
print(f"\nStoring {len(merged)} articles in bib.sqlite ...")
|
||||
store = Store()
|
||||
created = updated = 0
|
||||
for pmid, article in sorted(merged.items()):
|
||||
source = article_to_source(article, pmid_domains.get(pmid, []))
|
||||
con = store._con()
|
||||
existing = con.execute(
|
||||
"SELECT key FROM items WHERE url = ?", (source.url,)
|
||||
).fetchone()
|
||||
if existing:
|
||||
updated += 1
|
||||
else:
|
||||
created += 1
|
||||
store.upsert(source)
|
||||
print(f" Created: {created}")
|
||||
print(f" Updated: {updated}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -190,12 +190,190 @@ Included by default; excluded only with code:
|
||||
"""
|
||||
|
||||
|
||||
_STARTER_CRITERIA_PALLIATIVE_RFI = """\
|
||||
# Community-Based Palliative Care RFI — Inclusion / Exclusion Criteria
|
||||
|
||||
## Review question
|
||||
|
||||
What evidence can inform CMS's Request for Information on
|
||||
Community-Based Palliative Care in the CY2027 PFS NPRM (CMS-1848-P,
|
||||
91 FR 43949-43950)? The RFI asks five things, and a record is
|
||||
included when it provides usable evidence for at least one:
|
||||
|
||||
- **(a)** where fraud, waste, and abuse concentrate in
|
||||
community-based palliative care and adjacent hospice programs;
|
||||
- **(b)** whether eligibility for a serious-illness service should be
|
||||
gated on certified life expectancy, and what prognostic interval
|
||||
the evidence supports;
|
||||
- **(c)** whether eligibility is better defined by impact on daily
|
||||
function (ADLs) and/or caregiver strain, and with what instruments;
|
||||
- **(d)** which service elements of community-based palliative care
|
||||
management (interdisciplinary teams, home visits, 24/7 access,
|
||||
care plans, post-discharge follow-up) drive outcomes;
|
||||
- **(e)** how quality of palliative care management should be
|
||||
measured (including CBE 3665, "feeling heard and understood").
|
||||
|
||||
## Population
|
||||
|
||||
Adults with serious illness — palliative care, hospice, or
|
||||
supportive-care populations. U.S. Medicare-relevant settings
|
||||
preferred; non-U.S. evidence is includable when the finding
|
||||
transfers (instruments, service elements, prognostic accuracy).
|
||||
Exclude pediatric-only populations.
|
||||
|
||||
## Topic scope
|
||||
|
||||
Include: service-delivery models (community-based, home-based,
|
||||
outpatient/ambulatory palliative care), eligibility/identification
|
||||
criteria and instruments, prognostication accuracy, caregiver strain
|
||||
measurement, care-management service elements, quality measures and
|
||||
patient-reported outcomes, utilization/cost/hospitalization outcomes,
|
||||
billing-integrity and program-integrity evidence.
|
||||
|
||||
Exclude: pure symptom-pharmacology efficacy trials (drug/dose
|
||||
comparisons) with no service-delivery, eligibility, or quality
|
||||
measurement angle; inpatient-only intervention studies with no
|
||||
community/outpatient transferability; disease-specific prognostic
|
||||
biomarker papers with no service-eligibility framing.
|
||||
|
||||
## Study designs
|
||||
|
||||
Include: RCT, quasi-experimental, prospective/retrospective cohort
|
||||
with comparator, validation studies of instruments or prognostic
|
||||
criteria, systematic review, meta-analysis, economic evaluation,
|
||||
health-services/claims analyses, mixed-methods evaluations of
|
||||
delivery models.
|
||||
|
||||
Exclude: case reports, case series without comparator, narrative
|
||||
reviews, editorials, letters, commentaries, conference abstracts
|
||||
without full data.
|
||||
|
||||
## Time / language
|
||||
|
||||
Any publication year (CMMI's synthesis window is 2012-2021; older
|
||||
foundational instrument papers still qualify). English, or
|
||||
non-English with available English abstract.
|
||||
|
||||
## Policy / grey sub-domain
|
||||
|
||||
Docs tagged with any of ``source:oig``, ``source:gao``, ``source:medpac``,
|
||||
``source:court``, ``source:cms-rule``, ``source:mac-lcd`` are reference
|
||||
material and bypass clinical screening. Flag with
|
||||
``screen:reason:off-topic`` only if unrelated to palliative or
|
||||
hospice care.
|
||||
"""
|
||||
|
||||
|
||||
_STARTER_EXTRACTION_PALLIATIVE_RFI = """\
|
||||
# Data extraction template — palliative-rfi
|
||||
|
||||
The LLM will fill this form for every included study. Fields absent
|
||||
from the paper should be returned as empty strings rather than
|
||||
omitted.
|
||||
|
||||
## Study metadata
|
||||
- **authors** — lead + et al.
|
||||
- **year** — publication year (YYYY)
|
||||
- **country** — setting of the study
|
||||
- **funding** — industry / public / mixed / unreported
|
||||
- **design** — RCT / quasi-experimental / cohort / validation /
|
||||
systematic review / meta-analysis / economic / claims analysis
|
||||
|
||||
## RFI mapping
|
||||
- **rfi_parts** — which RFI questions the study informs (a-e,
|
||||
comma-separated)
|
||||
- **medicare_relevance** — direct (Medicare data/population) /
|
||||
transferable / weak
|
||||
|
||||
## Population & setting
|
||||
- **n_total** — total enrolled or analyzed
|
||||
- **population** — serious-illness definition used
|
||||
- **setting** — home / outpatient clinic / community / mixed /
|
||||
inpatient
|
||||
|
||||
## Eligibility evidence (parts b-c)
|
||||
- **eligibility_basis** — prognosis / function-ADL / caregiver-strain /
|
||||
diagnosis-count / combination
|
||||
- **instrument** — named instrument or criteria set
|
||||
- **prognostic_interval** — life-expectancy interval studied
|
||||
- **instrument_performance** — accuracy / discrimination as reported
|
||||
|
||||
## Delivery-model evidence (part d)
|
||||
- **service_elements** — team composition, home visits, 24/7 access,
|
||||
care planning, follow-up cadence as reported
|
||||
- **outcomes** — utilization, hospitalization, cost, place of death,
|
||||
patient/caregiver-reported outcomes
|
||||
- **effect_size** — OR / RR / HR / mean diff with 95% CI
|
||||
- **p_value**
|
||||
|
||||
## Quality-measure evidence (part e)
|
||||
- **measures_used** — named quality measures / PROMs (note CBE 3665)
|
||||
- **measure_findings**
|
||||
|
||||
## Integrity evidence (part a)
|
||||
- **fwa_findings** — billing-pattern, enforcement, or integrity
|
||||
findings
|
||||
|
||||
## Risk of bias
|
||||
- **overall** — low / some concerns / high
|
||||
- **rob_notes**
|
||||
"""
|
||||
|
||||
|
||||
_STARTER_REASONS_PALLIATIVE_RFI = """\
|
||||
# Exclusion reason codebook — palliative-rfi
|
||||
|
||||
Each code applies to a single PRISMA stage. When the LLM decides to
|
||||
exclude a record it must return one of these codes plus a one-sentence
|
||||
rationale.
|
||||
|
||||
## Stage 2 — title / abstract screening
|
||||
|
||||
- `off-topic` — unrelated to palliative, hospice, or serious-illness
|
||||
care.
|
||||
- `wrong-population` — pediatric-only, or not a serious-illness /
|
||||
palliative / hospice population.
|
||||
- `symptom-pharmacology` — drug/dose efficacy study with no
|
||||
service-delivery, eligibility, or quality-measurement angle.
|
||||
- `inpatient-only` — exclusively inpatient/ICU intervention with no
|
||||
community, home, or outpatient transferability.
|
||||
- `no-rfi-question` — on-topic but informs none of RFI parts a-e.
|
||||
- `case-report` — case report / case series without comparator.
|
||||
- `editorial` — editorial, commentary, narrative review, letter.
|
||||
- `conference-abstract` — abstract only, no full data.
|
||||
- `not-english` — not English and no English abstract available.
|
||||
- `duplicate` — duplicate publication (same data, different venue).
|
||||
- `retracted` — paper has been retracted.
|
||||
|
||||
## Stage 3 — full-text eligibility
|
||||
|
||||
Everything in Stage 2, plus:
|
||||
|
||||
- `no-usable-evidence` — full text reports no findings usable for any
|
||||
RFI question.
|
||||
- `wrong-outcome` — outcomes unrelated to eligibility, delivery,
|
||||
quality, cost/utilization, or integrity.
|
||||
- `unavailable-fulltext` — full text cannot be retrieved.
|
||||
|
||||
## Policy / grey sub-domain (OIG, GAO, MedPAC, court, CMS rules)
|
||||
|
||||
Included by default; excluded only with code:
|
||||
|
||||
- `off-topic` — unrelated to palliative or hospice care.
|
||||
"""
|
||||
|
||||
|
||||
STARTERS = {
|
||||
"skin-subs": (
|
||||
_STARTER_CRITERIA_SKIN_SUBS,
|
||||
_STARTER_EXTRACTION,
|
||||
_STARTER_REASONS,
|
||||
),
|
||||
"palliative-rfi": (
|
||||
_STARTER_CRITERIA_PALLIATIVE_RFI,
|
||||
_STARTER_EXTRACTION_PALLIATIVE_RFI,
|
||||
_STARTER_REASONS_PALLIATIVE_RFI,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +382,7 @@ STARTERS = {
|
||||
# onto them so the screening queue has something to chew on.
|
||||
_LEGACY_COHORT_TAGS: dict[str, str] = {
|
||||
"skin-subs": "module:skin-subs",
|
||||
"palliative-rfi": "module:palliative-rfi",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,16 +32,27 @@ if TYPE_CHECKING:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
_SYSTEM = """\
|
||||
You are a systematic-review assistant running PRISMA screening. Be
|
||||
rigorous, conservative, and explicit. Cite the specific inclusion or
|
||||
exclusion criterion that drives each decision.
|
||||
# Kept to a single sentence deliberately: long, bulleted system
|
||||
# prompts make qwen-family models served through Ollama's chat
|
||||
# template emit an empty response (no text, no tool call) — the
|
||||
# guidance bullets live in the first user message instead, which
|
||||
# every provider handles.
|
||||
_SYSTEM = (
|
||||
"You are a systematic-review assistant running PRISMA screening. "
|
||||
"Record each decision by calling the record_screening_decision tool."
|
||||
)
|
||||
|
||||
For each item, call the `record_screening_decision` tool exactly once.
|
||||
Do not emit prose outside the tool call.
|
||||
_GUIDANCE = """\
|
||||
## Screening rules
|
||||
|
||||
- Be rigorous, conservative, and explicit; cite the specific criterion
|
||||
that drives each decision in the rationale field.
|
||||
- Call the `record_screening_decision` tool exactly once per item.
|
||||
- Favor `uncertain` when the abstract is ambiguous on population,
|
||||
design, or outcomes — the full-text stage will resolve it.
|
||||
design, or outcomes — the full-text stage will resolve it. If no
|
||||
abstract is available, judge from the title and default to
|
||||
`uncertain` unless the title alone cleanly matches an exclusion
|
||||
code.
|
||||
- Use `exclude` only when a reason code cleanly applies.
|
||||
- Reason codes must come from the codebook; do not invent codes.
|
||||
- Themes are optional; 0-4 short kebab-case slugs.
|
||||
@@ -56,7 +67,8 @@ def _build_call(project: Project, item_markdown: str) -> LLMCall:
|
||||
LLMMessage(
|
||||
role="user",
|
||||
content=(
|
||||
"## Inclusion / exclusion criteria\n\n"
|
||||
_GUIDANCE
|
||||
+ "\n## Inclusion / exclusion criteria\n\n"
|
||||
+ project.criteria
|
||||
+ "\n\n## Exclusion reason codebook\n\n"
|
||||
+ project.reasons
|
||||
|
||||
Reference in New Issue
Block a user