alterlab-pdf-extract — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited alterlab-pdf-extract (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The free analog of Elicit's "columns" feature. Point it at a stack of PDFs, declare the columns you care about, and get back a tidy evidence table — one row per paper, one column per question — as CSV or a Markdown table. Built for data extraction across a corpus: literature screening, methods comparison, PICO/sample-size pulls, and review evidence sheets.
Every input is normalized to clean Markdown via Microsoft MarkItDown (see the alterlab-markitdown skill) before any column is extracted, so PDF, DOCX, PPTX, HTML, and the other MarkItDown formats all work.
outcome, effect size, limitations).
This skill operates on documents you already have and produces a structured table, not prose — use it after you have the papers.
| Scenario | Use Instead |
|---|---|
| Searching databases to find papers, or writing the narrative synthesis | alterlab-literature-review |
| Interrogating one PDF in depth (every section, figure, table, appendix) | alterlab-pdf-explore |
| Just converting a document to Markdown | alterlab-markitdown |
| Structured experimental data for papers you do not have on disk | alterlab-bgpt-search |
| Managing the PDFs and their references in a Zotero library | alterlab-pyzotero |
SCRIPT=skills/research-tools/alterlab-pdf-extract/scripts/extract_to_table.py
# Heuristic backend: use 'label:question' so the question carries the words the
# papers actually use — overlap is lexical, so a bare label like "Sample size"
# rarely matches "we enrolled 240 participants" (see Backends below).
uv run python "$SCRIPT" papers/*.pdf \
--column "Sample size:how many participants were enrolled" \
--column "Main finding:primary result reported" \
--column "Limitations:study limitations weaknesses" --format md
# Columns with explicit extraction questions, CSV to a file:
uv run python "$SCRIPT" a.pdf b.pdf \
--column "n:how many participants were enrolled" \
--column "effect:what was the main reported effect size" \
--format csv -o evidence.csv
# Precise LLM-backed extraction (needs OPENROUTER_API_KEY) — label-only columns
# are fine here, since the model reads the paper rather than matching keywords:
uv run python "$SCRIPT" lit/*.pdf \
--columns "Population" "Intervention" "Outcome" --backend llmA column is a label plus the question that drives extraction. Two ways to declare:
| Spec form | Meaning |
|---|---|
"Sample size" | label == question; keywords come from the label |
"n:how many participants" | label n, question how many participants |
Use --columns for a space-separated list of simple labels, and repeat --column for specs that need an explicit question. Both combine; duplicate labels are rejected.
literal keyword overlap with the column spec and emits the best evidence snippet. Deterministic — good for a first pass, screening, and reproducible runs. Returns an empty cell when no sentence shares a keyword. Gotcha: matching is lexical, not semantic. A generic label like "Sample size" tokenizes to sample, size — which never appears in "we enrolled 240 participants", so the cell comes back empty. For the heuristic backend, phrase columns as Label:question where the question uses the words the papers actually use (participants, enrolled, cohort, n=). Quantitative questions also get a small ranking bonus for sentences containing digits when the question itself contains a digit or %. When you only have generic labels, use the llm backend instead.
column questions to an OpenAI-compatible endpoint for a concise per-column answer (N/A when the paper is silent). With OPENROUTER_API_KEY set, requests go to OpenRouter; otherwise OPENAI_API_KEY is sent to OPENAI_BASE_URL (or the OpenAI default) — each key only ever goes to its own endpoint. The model ID follows the ALTERLAB_MODEL convention (skills/core/shared/model_env.md): $ALTERLAB_MODEL, else the dated default; override per-run with --model (on a non-OpenRouter endpoint, pass a model ID it serves). Spot-check LLM answers against the PDFs before they enter a review, since a confident wrong number looks the same as a right one in the table.
--format md → GitHub-flavored Markdown table (pipes in cell text are escaped).--format csv → CSV via csv.DictWriter.-o FILE writes to disk (UTF-8); otherwise the table goes to stdout.The first column is always Source (the input filename); a file that fails conversion yields an ERROR row so the table stays aligned.
alterlab-pdf-extract sits downstream of paper discovery and upstream of synthesis:
alterlab-literature-review, search backends).alterlab-literature-review, alterlab-paper-writer.scripts/extract_to_table.py — the CLI. --help lists every flag; --columns /--column define the table; --backend, --format, --model, -o control extraction and output.
Part of the AlterLab Academic Skills suite.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.