| name | snf-review |
| description | End-to-end Skilled Nursing Facility records review, from a folder of facility PDFs to cited, tagged findings. START HERE for any nursing-home or SNF chart review — runs the whole pipeline — file inventory, page-level document mapping and OCR/readability triage, packetization of large mixed scanned charts, parallel reviewer dispatch, and aggregation into Findings / HotItems / Timeline / Gaps roll-ups plus machine-readable JSON. Use when the user says "review my mother's nursing home records", "go through the chart the facility sent", "we got 800 pages back from the HIPAA request", "review the SNF records", "tag the findings in this chart", "run the full records review", or "/snf-review" — or has any facility record production too large to read page by page. For an inventory-only or map-only pass, use snf-inventory or snf-document-map instead. |
SNF Review (Full Pipeline)
Orchestrate the complete adversarial review workflow defined by the snf-records-review skill. Produce per-packet findings files and the four roll-up documents.
Read ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/SKILL.md, ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/RUBRIC.md, and ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/REVIEWER_PROMPT.md before starting so the rubric is loaded and the reviewer prompt is available.
Inputs to collect from the user (via AskUserQuestion if not provided)
- Patient context — name, DOB, facility name, HIPAA response date. Used to populate
{{PATIENT_NAME}}, {{FACILITY_NAME}}, {{DROP_DATE}} placeholders in the reviewer prompt.
- Case background — 3-8 sentences of the situation, in the user's own words. Goes into
{{CASE_BACKGROUND}}. Ask for whichever of these apply: what the payer or facility did and when, which appeals or disputes are already open and at what level, whether the discharge destination is contested, and what the family disputes in the facility's account. Keep the user's phrasing; do not substitute an example case's posture for theirs.
- PDF directory — path to the records PDFs. OCR'd PDFs are preferred, but mixed scanned/image-only pages are expected and must be triaged.
- Stay window —
--stay-start and --stay-end.
- Output root — where to write inventory, parts, findings, and roll-ups. Default: sibling directory named
<pdf-dir-name>-review/.
- Prefix — short label for roll-up filenames (e.g.,
FacilityShortName, SpringfieldSNF).
- Case context ledger — optional path to
case-context/; if present, read case_profile.md, deadlines.md, and open_questions.md before filling {{CASE_BACKGROUND}}.
- Source records request — optional path to the HIPAA/document request letter, email, Markdown/text, or PDF. If supplied, pass it to the document-map step so the workflow reports requested-vs-received deltas.
- Dispatch mode — default local chat/sub-agent dispatch. Optional: Copilot CLI plan-only dispatch, Copilot CLI run, or Copilot CLI
/fleet handoff.
Execution
Run the pipeline step by step. Mark progress with TodoWrite.
Step 1 — Inventory
Invoke the snf-inventory skill (or run the script directly):
python3 ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/scripts/inventory.py \
--in-dir "<pdf-dir>" --out-dir "<out>/inventory" \
--stay-start <date> --stay-end <date>
Step 2 — Build the document map and packet plan
Invoke /snf-document-map (or run the script directly):
python3 ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/scripts/build_document_map.py \
--in-dir "<pdf-dir>" --out-dir "<out>/document-map" \
--stay-start <date> --stay-end <date>
If a source records request is supplied, append --request-file "<request-file>". The resulting document_map.md includes a Records request comparison section that separates requested items that appear present from requested items that are possible, not_found, or indeterminate_ocr.
Read <out>/document-map/document_map.md before reviewer dispatch. If it shows indeterminate_ocr for load-bearing expected documents, decide with the user whether to remediate OCR first or proceed with that limitation clearly marked in reviewer prompts and final gaps.
The document-map script uses the local PDF toolkit when present: pdftotext -layout for extraction, pdfinfo for metadata, pdfimages -list for scanned-page hints, and pdffonts for text-layer hints. In diagnostic comparison mode, if these tools disagree with pypdf, prefer the strongest source but preserve the extraction method in the map.
By default, the mapper now prefers pdftotext -layout when it is available and skips per-page pypdf extraction whenever Poppler returns non-empty page text. Add --prefer-pdftotext=false only for diagnostic both-engine comparison.
Time-bounded sandbox execution
If the agent environment has a short per-command timeout, treat inventory and document-map generation as resumable per-file work rather than one giant run:
- Create
<out>/per-file/<pdf-stem>/ for each source PDF and run the scripts against a temp input directory containing only a symlink to that PDF.
- Write logs to
<out>/logs/inventory.<pdf-stem>.log and <out>/logs/document-map.<pdf-stem>.log so the caller can poll progress without rerunning extraction.
- After each successful per-file run, write a
<out>/per-file/<pdf-stem>/DONE.json sentinel containing source filename, SHA-256, page count, script name, output paths, and completion timestamp. On resume, skip matching DONE entries.
- For one very large PDF, split first with
qpdf --split-pages (or another deterministic splitter), then map the chunks while maintaining a manifest from derived chunk pages back to original PDF/page citations.
- If the sandbox still cannot finish a chunk, dispatch that one step to a sub-agent/tool with a longer execution budget and keep outputs inside the same private case output root.
Do not quote PHI from logs in chat summaries. Report counts, filenames, and statuses unless the user explicitly asks for quoted content.
Step 3 — Materialize review packets
Preferred for large or disorganized charts: use the document-map packet plan. Packet materialization uses qpdf when available and falls back to pdfseparate + pdfunite.
python3 ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/scripts/materialize_packet_plan.py \
--packet-plan "<out>/document-map/review_packet_plan.json" \
--source-dir "<pdf-dir>" \
--out-dir "<out>/review-packets"
Use --dry-run first if the corpus is very large or if you need to inspect packet boundaries before writing derived PDFs.
Fallback only when the document-map plan is unavailable: split large charts using inferred sections.
For any PDF in the inventory with >200 pages, split it:
python3 ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/scripts/split_by_sections.py \
--pdf "<large-pdf>" \
--inventory-json "<out>/inventory/<large-pdf-stem>.json" \
--out-dir "<out>/chart-parts" \
--min-pages 30 --max-pages 60
Requires qpdf in PATH. Install with the platform package manager if missing (brew install qpdf on macOS, apt-get install -y qpdf on Debian/Ubuntu).
Step 4 — Dispatch reviewers in parallel
Build the final review list from <out>/document-map/review_packet_plan.json and, if materialized, <out>/review-packets/MANIFEST.json. For each packet, spawn a sub-agent via the Agent tool (subagent_type: general-purpose) with a filled-in copy of ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/REVIEWER_PROMPT.md. Dispatch them concurrently — send one message with multiple Agent tool calls, not one at a time.
Each reviewer's prompt MUST include:
- The exact filled reviewer prompt (read
${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/REVIEWER_PROMPT.md and substitute placeholders).
- Path to the rubric:
${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/RUBRIC.md.
- Path to the findings template:
${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/FINDINGS_TEMPLATE.md.
- Path to the document map:
<out>/document-map/document_map.json.
- Packet plan/manifest entry showing original source PDF/page range and OCR profile.
- Output path:
<out>/findings/<packet-stem>.md.
Reviewer prompts must explicitly distinguish true gaps from OCR-indeterminate gaps. If a packet is mostly image-only, the reviewer should create a gap/remediation note rather than guessing.
Optional Copilot CLI dispatch
If the user wants to offload packet review to GitHub Copilot CLI, read ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/COPILOT_CLI_DISPATCH.md and run ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/scripts/plan_copilot_dispatch.py after packet materialization. Start in plan-only mode; it creates prompt files, a dispatch manifest, a run script, and a /fleet prompt under <out>/copilot-dispatch/ without launching jobs.
Only launch Copilot CLI jobs when the user explicitly approves --run. Use low bounded concurrency first (--max-concurrent 1 or 2). Prefer the default reviewer-tools permission mode; use allow-all / --yolo only with explicit user approval. Keep Copilot session logs and prompt files in the private case output root and out of git.
Step 5 — Aggregate
Once all reviewer findings files exist:
python3 ${CLAUDE_PLUGIN_ROOT}/skills/snf-records-review/scripts/aggregate_findings.py \
--findings-dir "<out>/findings" \
--out-dir "<out>" \
--prefix "<prefix>"
Produces <prefix>_Findings.md, <prefix>_HotItems.md, <prefix>_Timeline.md, <prefix>_Gaps.md.
Also produces <prefix>_Findings.json and <prefix>_Summary.json for dashboards, MCP ingestion, spreadsheets, or later case-context enrichment.
Step 6 — Report and follow-up routing
Summarize for the user in plain language:
- Counts by tag (APPEAL / LTC-DISPUTE / MEDICAID / QUALITY / TIMELINE).
- Document-map coverage: expected documents present / possible / not_found / indeterminate_ocr.
- Requested-vs-received delta when a source records request was supplied.
- OCR/readability limitations by PDF/page range.
- Top 5 hot items with dates and one-line rationale (from
HotItems.md).
- Records gaps that should drive a follow-up HIPAA records request (feed
hipaa-records-request skill), separated from OCR remediation items.
- Suggested next steps: use
case-context-ledger to add value-3 findings and unresolved gaps, run /hipaa-records-request for narrow follow-up requests, run /snf-appeal-supplement for the appeal draft, build a dashboard, or add an admin-interaction-log entry if anything new turned up.
Guardrails
- This is records-review orchestration and evidence triage, not medical advice or legal advice.
- All PHI stays in the output directory. Do not quote patient identifiers verbatim in the chat report.
- If a sub-agent returns with no findings or fails, surface that packet in the report rather than silently proceeding.
- If a record type is not detected but OCR is weak, call it
indeterminate_ocr, not missing.
- Confirm destination paths with the user before writing outside the selected workspace.
- If the stay window (
--stay-start / --stay-end) isn't known, ask — it drives the in-stay vs. out-of-window flag that matters for appeal narrative.