# Medical Records

> File a medical After Visit Summary into the Obsidian vault as a structured, queryable record. Extracts vitals and visit metadata from the document text, writes a dated note with flat Number frontmatter properties (so Obsidian Bases and Dataview can trend vitals across visits), archives and embeds the source scan, and slots the note into the Medical Vitals base. Usually invoked by the scan-documen…

- **Type:** Skill
- **Install:** `agentstack add skill-pfeff-claude-skills-medical-records`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [pfeff](https://agentstack.voostack.com/s/pfeff)
- **Installs:** 0
- **Category:** [Productivity](https://agentstack.voostack.com/c/productivity)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [pfeff](https://github.com/pfeff)
- **Source:** https://github.com/pfeff/claude-skills/tree/main/skills/medical-records

## Install

```sh
agentstack add skill-pfeff-claude-skills-medical-records
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# medical-records — file an After Visit Summary

A handler skill: turns the text of a medical After Visit Summary into a structured vault record. Consumes OCR'd text (from `scan-document`, or produced here if invoked directly) and the source file; produces a dated note + archived scan.

## When this fires

- Invoked by `scan-document` for class `medical.after-visit-summary`.
- Direct: "file this doctor visit", "capture this after-visit summary", "log my visit vitals".

## Trigger signals (how scan-document recognizes this)

This handler owns its own recognition vocabulary — `scan-document`'s router does not hardcode these; it reads this section to decide whether a scan belongs here. A document is an After Visit Summary when the recognized text shows several of:

- Header/title text: "After Visit Summary"
- An MRN (medical record number)
- Vitals (blood pressure, weight, pulse, temperature, BMI, oxygen saturation, etc.)
- A provider/clinic/institution name
- A follow-up date or discharge instructions

One signal alone may be coincidental (e.g. a phone number that looks like an MRN); several together are a confident match. Ambiguous cases should be confirmed with the user rather than guessed.

## Inputs

- Path to the source scan (PDF/image).
- The recognized text. If not supplied (direct invocation), OCR it first: `pdftotext -layout "" -` fast path (only if the `pdftotext` binary is present — it is not part of stock macOS; treat "command not found" as "skip to Vision," not an error), else `swift /scripts/ocr.swift ""`.

**Command safety:** the file path is untrusted input (user- or filesystem-derived). Always pass it as a single quoted argument to any shell command — never interpolate it unquoted — and reject/validate it if it contains shell metacharacters (`; | & $ \` ( )  newline`) before invocation.

## Steps

1. **Extract** these fields from the text (leave a field out if genuinely absent — do not invent):
   - Visit metadata: `visit_date` (the appointment date), provider/institution, department, department phone, MRN, reason(s) for visit, referral(s) + expiry, follow-up date, medications.
   - Vitals: blood pressure (systolic + diastolic separately), weight, BMI, height, pulse, temperature, oxygen saturation.

2. **Resolve the vault** via the `obsidian-notes` host-config (`~/.claude/hosts/.md`). Bail clearly if none is configured.

3. **Path safety:** `slug` and `visit_date` are both derived from OCR'd text and are therefore untrusted — sanitize both before using them to build any `Attachments/` or `Notes/` path. Constrain `slug` to `[a-z0-9-]` (lowercase, strip/replace anything else, collapse repeats). Validate `visit_date` as strict ISO `YYYY-MM-DD`; if it doesn't match, treat it as unrecoverable and fall through to the "ambiguous visit date" edge case rather than using the raw value in a path. Never write an unvalidated `slug` or `visit_date` into a filesystem path — this blocks path traversal (`../`, absolute paths, embedded separators) riding in through OCR text.

4. **Archive the scan:** copy the source file into `/Attachments/` named `-.` (e.g. `2026-06-03-osu-sleep-avs.pdf`), using the sanitized values from step 3. The attachment is a source artifact — name it by the document's own date for findability. Verify the filename is unique in the vault (the embed resolves by filename).

5. **YAML safety:** `title`, `provider`, `department`, `department_phone`, each `visit_reason` item, `referral`, and `medications` are all derived from OCR'd text and are therefore untrusted — never write any of them into frontmatter unquoted. Emit every OCR-derived scalar as a double-quoted YAML string with internal `"` escaped as `\"` and `\` escaped as `\\` (or as a YAML block scalar for long/multi-line values). A stray `:`, a leading `-`, an embedded newline, or a line that merely resembles `---` or `key: value` in the source text can otherwise break the YAML frontmatter block or inject extra properties — and medical text is full of colons (times, ratios, dosages). This applies to every scalar in the schema below sourced from OCR text, not just the ones named here.

6. **Write the note** at `/Notes///-.md` (sanitized `slug`):
   - **Filename prefix = today's write date**, NOT the visit date. The prefix is a de-dupe / folder-partition key; the visit date lives in frontmatter as `visit_date`.
   - Frontmatter — the schema (see below). **Vitals are flat, top-level, Number-typed properties.** This is deliberate and load-bearing (see "Why this schema").
   - Body: visit header, a human-readable vitals table, plan & follow-up, a condensed instructions summary (keep patient-specific guidance; the full text lives in the embedded PDF), and the embedded source (`![[]]`).

7. **Report** where the note and attachment landed, and note that it will appear in the Medical Vitals base.

## Frontmatter schema

**Every OCR-derived scalar below (`title`, `provider`, `department`, `department_phone`, `visit_reason` items, `referral`, `medications`) MUST be written as a quoted, YAML-safe string — see "YAML safety" in Steps above. Do not write raw OCR text into frontmatter.**

```yaml
---
title:  — After Visit Summary
type: medical-visit
visit_date: 2026-06-03            # semantic date (Date type)
tags:
  - medical/visit
  -                    # e.g. sleep-medicine
provider: 
department: 
department_phone: 
mrn: ""                      # quoted — keep as Text, not a number
visit_reason:
  - 
referral:      # omit if none
follow_up: 2026-12-03             # omit if none
medications: 
source_pdf: "[[]]"
captured: 2026-07-13              # write date; matches filename prefix
# --- vitals: flat Number properties → Bases columns / Dataview trending ---
bp_systolic: 122
bp_diastolic: 68
weight_lb: 188
bmi: 26.98
height_in: 70
pulse_bpm: 77
temp_f: 97.3
spo2_pct: 99
---
```

## Why this schema (do not "improve" it back to the obvious-but-wrong forms)

Verified against Obsidian's official Properties and Bases docs:

- **Vitals are flat top-level properties, not a nested `vitals:` object.** Obsidian's Bases model is literally "each row is a file, each column is a property of that file," and Bases/Dataview read **frontmatter only, never the note body**. Nested properties are *not supported* in the Properties UI. So flat frontmatter is the correct — and only — form that serves both Bases and Dataview.
- **Blood pressure is split into two Numbers** (`bp_systolic`/`bp_diastolic`), not a single `122/68` string. Obsidian has 6 property types (Text, List, Number, Checkbox, Date, Date&time) and no unit type; a `122/68` value is Text and can't be trended. Split → both Numbers → aggregatable.
- **Numbers are bare** (no quotes, no units in the value) so they type as Number. Units go in the key (`weight_lb`, `temp_f`) since there is no unit type. `mrn` is the exception — quote it so it stays Text.
- **No `vital_` prefix.** `type: medical-visit` already scopes these; clean names make better Base columns.

## The Medical Vitals base

Records feed `/Areas/Health/Medical Vitals.base` — a table filtered to `type == "medical-visit"`, sorted by `visit_date`, with the vitals as columns and a `bp` formula (`bp_systolic + "/" + bp_diastolic`) for display. It exists after the first record; adding visits just adds rows. If it is missing, recreate it with that shape.

## Edge cases

- **Missing vitals:** omit absent properties rather than writing nulls or zeros — a zero would corrupt trend averages.
- **Ambiguous visit date:** if the appointment date isn't clearly recoverable, ask (AskUserQuestion) rather than guessing; it is the semantic key.
- **Non-AVS medical doc** (lab result, imaging, immunization): out of scope for v1 — say so and fall back to the router's generic capture. Those get their own handlers later.
- **Height unchanged across visits:** fine to carry it each time; it makes each record self-contained.
- **Sensitive content:** these records carry MRN and vitals — private medical data by nature. Capture into the local vault only; never send content (including the file, extracted text, or any field) to an external service without explicit operator say-so.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [pfeff](https://github.com/pfeff)
- **Source:** [pfeff/claude-skills](https://github.com/pfeff/claude-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-pfeff-claude-skills-medical-records
- Seller: https://agentstack.voostack.com/s/pfeff
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
