← Back to AAI for MerusCase

Read Upload

The universal document reader. Download any upload on any case by ID, read it natively (Claude vision for PDFs and images, in-process conversion for Office formats), summarize what it is, extract deadlines and action items, and clean up the file. One command, no extraction libraries, no third-party tools, no persistence after the read.

The upload IDs, filenames, and document content in this page are fictitious demonstration data. Your install runs against your real Merus uploads.

On this page

What it is

At the prompt:

aaicase> read upload [UPLOAD-ID]
aaicase> read the QME report on Doe
aaicase> what's in upload [UPLOAD-ID]

The skill downloads the document, identifies the file type, picks the right read strategy, reads it through Claude natively, and produces a structured summary: document type, applicant name, ADJ#, date, content summary, any deadlines, any discrepancies with the case data, and any follow-up actions.

One step from the attorney’s point of view: “read the defense letter” → download, read, summarize in a single round trip.

Identifying the upload (ID or description)

Two input modes:

For finding uploads on a case, the skill uses /activities/index/[CASE-ID], NOT /uploads/index. The global uploads index is capped at ~1,900 recent files and misses older uploads on long-running cases.

This is one of those Merus quirks worth knowing: the activities endpoint with the case ID as path parameter returns every activity (and therefore every upload reference) on the case, no matter how old. The global uploads index has a recency cap that makes it unsuitable for finding documents on cases more than a few months old.

PDF reads — no text extraction, ever

AAI does not use pdftotext, pdf-parse, pdftoppm, mupdf, tesseract, or any text-extraction layer. Every PDF is read through Claude’s vision capability — the model sees the actual pages.

This matters because the documents this skill is most often run against — QME reports, UR/IMR decisions, subpoenas, defense letters, medical records — are exactly the kinds of PDFs that text extraction breaks on:

Native vision reads them like a person reads them. Text extraction returns garbage from any of them — or worse, returns partial garbage that looks plausible and quietly omits critical content. The skill never takes that risk.

This is also a CI-guarded rule in the codebase: any commit that re-introduces a text-extraction dependency on PDFs fails the build.

Large PDFs — page-windowed reading

For PDFs over 50 pages, the skill plans the read in windows rather than asking Claude to read 200 pages in one shot. The window strategy:

Page countStrategy
1–50Single Read call covering the whole document.
51–20050-page windows: pages: "1-50", then pages: "51-100", etc.
200+75-page windows, with a TOC scan first to identify which ranges matter.

The windows are read sequentially in the same session — not dispatched to sub-agents, not split into separate PDFs with pdfseparate. The Read tool’s pages parameter handles the slicing internally; the skill just plans the sequence and walks it.

Why sequential and not parallel: the answer being assembled depends on what was read before. A QME report’s pages 30–40 only make sense in context of pages 1–10. The sequential read lets each subsequent window’s extraction benefit from what was already understood.

DOCX and XLSX — in-process conversion

Word and Excel files need conversion before they can be read — Claude has direct vision for PDFs but not for binary Office formats. The skill uses bin/merus-convert.mjs, an in-process Node helper that:

The conversion output lives in the same per-run temp dir as the downloaded source. Both files are deleted at the end of the read.

Images, RTF, legacy formats

FormatStrategy
JPEG, PNG, TIFF, WebP, GIFRead directly via Claude vision. Same as PDF — native, no conversion.
RTFConvert to plain text via the same in-process helper.
EML (email)Parse RFC 5322 headers + body in-process; read as text.
.doc, .xls (legacy)Reported to the user as “needs conversion” — no native Node parser for these formats. Recommend re-saving as .docx/.xlsx.

For everything else (encrypted PDFs, password-protected files, unknown binary formats), the skill stops and tells the attorney what it found. It doesn’t guess at content.

What gets extracted

After reading, the skill produces a structured summary:

If any of these surface a real issue (wrong applicant, missed deadline), the skill offers to create a task on the case rather than just reporting.

Template selection by document type

The skill keeps a library of extraction templates in skills/templates/ — specialized prompts for each document type that produce structured output tuned to the document’s schema:

TemplateUsed for
qme.mdQME and AME reports — WPI percentages, P&S date, apportionment
subpoena.mdSDTs and Notice to Consumer (CCP 1985.3)
ur-denial.mdUtilization Review decisions from carriers/UR vendors
imr-decision.mdIndependent Medical Review determinations from Maximus
medical-records.mdPTP visit notes, hospital records, imaging reports
generic.mdAnything not matching a specialized template

The skill picks the template from what the cover page says, not from the filename. UR (carrier-side) and IMR (Maximus) decisions look superficially similar but have different deadlines (LC 4610 vs. LC 4610.6) — the right template is critical for the extracted deadline to be correct.

Security boundary — per-run temp dir, no cache

Every read creates a fresh temp directory:

TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/aai-doc-XXXXXX") && chmod 700 "$TMPDIR"

The download lands inside that directory. Any conversion outputs land in the same directory. At the end of the read, the directory is deleted unconditionally:

rm -rf "$TMPDIR"

No caching, no retention. Each read re-downloads from Merus, even if the same upload was read minutes ago. The reasoning is the same as audit case’s no-cache rule: a cached document is a stale document, and stale documents lie about what they currently say.

The Merus token is never exposed via the command line — the skill uses merus-fetch --download, which keeps the token in headers; never via curl, which would expose the token in ps output. This matters on multi-user machines.

Example output

READ — Upload #[ID]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

File:        2026-04-22-qme-report-sample.pdf
Type:        PDF (12 pages, 2.4 MB)
Downloaded:  /tmp/aai-doc-XYZ123/doc.pdf
Read plan:   single window (≤50 pp)
Template:    qme.md

EXTRACTED:

  Document type:    QME Report (LC 4060)
  Applicant:        Doe, Jane
  ADJ:              [ADJ-#]
  Document date:    2026-04-22
  QME physician:    Dr. Sample, M.D. — Orthopedic Surgery

  Key findings:
    • P&S declared as of 2026-04-15
    • WPI: 12% lumbar spine
    • WPI: 5% right knee
    • No apportionment to non-industrial causes
    • Work restrictions: no lifting > 25 lbs, no repetitive bending
    • Future medical: ongoing PT + ortho consults

  Deadlines triggered:
    ⚠ QME objection: 8 CCR 31.5(a) — 30 days from report date
       Deadline: 2026-05-22 (26 days remaining)

  Follow-up actions:
    1. File objection if rating is disputed (deadline above)
    2. PD payment must initiate within 14 days of P&S (LC 4650(b))
       — check benefits skill to verify
    3. Update case comments with P&S date

  Discrepancies with case data:
    ✓ Applicant matches Case [CASE-ID]
    ✓ ADJ matches case injury record
    ⚠ Body parts: report addresses lumbar + right knee, but case
       application lists only lumbar. Consider amended application.

NEXT STEPS:
  1. Run "deadlines on [CASE-ID]" to confirm the QME 30-day objection date
  2. Want to create a task to file objection? (yes/no)
  3. Run "audit on [CASE-ID]" to surface PD payment activities and
     cross-reference against the QME WPI rating

When to run it

Troubleshooting

“Upload not found”

The upload ID doesn’t exist, was deleted, or you don’t have permission. Verify the ID via the case’s activity feed (timeline on [case] shows upload IDs).

“Encrypted or password-protected PDF”

The skill stops — AAI doesn’t attempt password cracking. Get an unencrypted copy from the source (carrier, defense, opposing counsel) and re-upload.

“File appears to be image-based with no OCR layer”

For pure scanned PDFs, Claude vision still reads the visual content — the “no OCR layer” warning means text extraction would fail, but the vision read should work normally. Continue past the warning.

The extraction missed something important

If the document is over 50 pages and the key content is past the first window, the skill may have stopped at the first window’s extraction. Ask for the specific page range: read upload [ID] pages 51-100.

“Legacy .doc / .xls file”

The pure-Node converters don’t handle the binary .doc/.xls formats. Re-save as .docx/.xlsx in Word/Excel and re-upload.

Template picked seems wrong

The skill chose based on cover-page content. If it picked generic.md when the document is clearly a QME, the cover page may not have used standard QME language. Run the read again with an explicit template hint, or use audit [case] for case-level context that helps template selection.

Temp file didn’t get deleted

The skill cleans up with rm -rf at the end of every run. If you see a leftover /tmp/aai-doc-* directory, the skill crashed mid-run. The directory is owner-locked (chmod 700), so it’s readable only by you; delete it manually.

Part of AAI for MerusCase — code-guarded AI case intelligence for California Workers’ Comp attorneys.