Skip to content
health-record-assistant logo

Health Record Assistant

health-record-assistant

Connect to and analyze a user's health records from their patient portal via SMART on FHIR. Use when the user wants to review medical records, understand lab results, see medications, analyze health trends, identify care gaps, or answer questions about personal health data.

SKILL.md

Full skill instructions

Health Record Assistant

Fetch and analyze electronic health records from patient portals using SMART on FHIR.

When to Use

  • User asks about their health records, medical history, or test results
  • User wants to understand medications, conditions, or treatments
  • User asks about lab trends or health metrics over time
  • User wants to identify care gaps or preventive care needs
  • User wants summaries of visits or clinical notes

Analysis Philosophy

Unless the user specifically asks for a live app or artifact, you should:

  1. Download data into your computational environment and analyze it manually
  2. Inspect structured data by writing and running code to process FHIR resources
  3. Read clinical notes in full where relevant - grep through attachments, identify important notes, read them completely
  4. Use your judgment to evaluate what's clinically significant, iterate on your analysis, and refine your understanding
  5. Synthesize thoughtful answers based on your exploration of the data

This approach is important because:

  • You can see intermediate results, catch errors, and improve your analysis
  • You can apply clinical reasoning as you explore, not just execute blind code
  • You can identify which notes are worth reading fully vs. skimming
  • Complex health questions often require iterative investigation

If the user wants a live artifact/​app, pre-processing is still valuable:

  • Do your exploratory analysis first
  • Identify the key data points and insights
  • Then build the artifact with pre-processed results or focused queries
  • This avoids shipping analysis code you can't see or debug

How to Connect

Helper scripts are provided in scripts/ to simplify the workflow.

Prerequisites: These scripts require Bun to be installed:

curl -fsSL https://bun.sh/​install | bash

Step 1: Create a Session

bun scripts/​create-session.ts

Output:

{
  "sessionId": "abc123...",
  "userUrl": "https://health-skillz.exe.xyz/​connect/​abc123...",
  "pollUrl": "https://health-skillz.exe.xyz/​api/​poll/​abc123...",
  "privateKeyJwk": { "kty": "EC", "crv": "P-256", "d": "...", ... }
}

Save the privateKeyJwk - you'll need it to decrypt the data.

Step 2: Show the User a Link

Present userUrl to the user as a clickable link:

To access your health records, please click this link:

Connect Your Health Records

You'll sign into your patient portal (like Epic MyChart), and your records will be securely transferred for analysis.

🔒 Your data is end-to-end encrypted - only this conversation can decrypt it.

Step 3: Finalize and Decrypt

Once the user has connected their provider(s) and clicked "Done - Send to AI":

bun scripts/​finalize-session.ts <sessionId> '<privateKeyJwk>' ./​health-data

This script:

  1. Polls until data is ready (outputs JSON status lines while waiting)
  2. Decrypts each provider's data
  3. Writes one JSON file per provider:

Example output:

{"status":"polling","sessionId":"abc123..."}
{"status":"waiting","sessionStatus":"collecting","providerCount":1,"attempt":1}
{"status":"ready","providerCount":1}
{"status":"decrypting"}
{"status":"wrote_file","file":"./​health-data/​unitypoint-health.json","provider":"UnityPoint Health","resources":277,"attachments":82}
{"status":"done","files":["./​health-data/​unitypoint-health.json"]}

Result:

health-data/
  unitypoint-health.json
  mayo-clinic.json

Each file contains a single provider's data:

interface ProviderData {
  name: string;
  fhirBaseUrl: string;
  connectedAt: string;
  fhir: {
    Patient?: Patient[];
    Condition?: Condition[];
    Observation?: Observation[];
    MedicationRequest?: MedicationRequest[];
    Procedure?: Procedure[];
    Immunization?: Immunization[];
    AllergyIntolerance?: AllergyIntolerance[];
    Encounter?: Encounter[];
    DiagnosticReport?: DiagnosticReport[];
    DocumentReference?: DocumentReference[];
    CareTeam?: CareTeam[];
    Goal?: Goal[];
  };
  attachments: Attachment[];
}

interface Attachment {
  resourceType: string;      // "DocumentReference" or "DiagnosticReport"
  resourceId: string;        // FHIR resource ID this attachment came from
  contentType: string;       // MIME type: "text/​html", "text/​rtf", "application/​xml", etc.
  contentPlaintext: string | null;  // Extracted plain text (for text formats)
  contentBase64: string | null;     // Raw content, base64 encoded
}

Each provider is a separate slice - no merging, preserves data provenance.

Working with FHIR Data

Available Resource Types

data.fhir.Patient           // Demographics (name, DOB, contact)
data.fhir.Condition         // Diagnoses and health problems
data.fhir.MedicationRequest // Prescribed medications
data.fhir.Observation       // Lab results, vital signs
data.fhir.Procedure         // Surgeries and procedures
data.fhir.Immunization      // Vaccination records
data.fhir.AllergyIntolerance// Allergies and reactions
data.fhir.Encounter         // Healthcare visits
data.fhir.DocumentReference // Clinical documents
data.fhir.DiagnosticReport  // Lab panels, imaging reports

Example: Get Lab Results by LOINC Code

function getLabsByLoinc(loincCode) {
  return data.fhir.Observation?.filter(obs =>
    obs.code?.coding?.some(c => c.code === loincCode)
  ).map(obs => ({
    value: obs.valueQuantity?.value,
    unit: obs.valueQuantity?.unit,
    date: obs.effectiveDateTime,
    flag: obs.interpretation?.[0]?.coding?.[0]?.code // H, L, N
  })).sort((a, b) => new Date(b.date) - new Date(a.date));
}

// Common LOINC codes:
// 4548-4  = Hemoglobin A1c
// 2345-7  = Glucose
// 2093-3  = Total Cholesterol
// 2085-9  = HDL Cholesterol
// 13457-7 = LDL Cholesterol
// 2160-0  = Creatinine
// 8480-6  = Systolic Blood Pressure
// 8462-4  = Diastolic Blood Pressure
// 718-7   = Hemoglobin
// 39156-5 = BMI

Example: List Active Medications

const activeMeds = data.fhir.MedicationRequest
  ?.filter(m => m.status === 'active')
  .map(m => ({
    name: m.medicationCodeableConcept?.coding?.[0]?.display,
    dosage: m.dosageInstruction?.[0]?.text,
    prescribedDate: m.authoredOn
  }));

Example: Get Active Conditions

const conditions = data.fhir.Condition
  ?.filter(c => c.clinicalStatus?.coding?.[0]?.code === 'active')
  .map(c => ({
    name: c.code?.coding?.[0]?.display,
    onsetDate: c.onsetDateTime
  }));

Understanding Attachments

The attachments array contains clinical documents extracted from DocumentReference and DiagnosticReport resources. Each attachment has:

  • contentPlaintext: Extracted readable text (for HTML, RTF, XML, plain text formats)
  • contentBase64: Raw file content, base64 encoded (always present)
  • contentType: MIME type like text/​html, text/​rtf, application/​xml

Common patterns from Epic:

  • Most DocumentReferences have 2 attachments: one text/​html and one text/​rtf (same content, different formats)
  • RTF files contain Epic-specific markup that gets stripped during plaintext extraction
  • All attachments are fetched (no artificial limits)

For analysis, use contentPlaintext - it's clean and searchable. The contentBase64 is available if you need the original format.

Example: Search Clinical Notes

The attachments array contains extracted text from clinical documents:

function searchNotes(searchTerm) {
  return data.attachments?.filter(att =>
    att.contentPlaintext?.toLowerCase().includes(searchTerm.toLowerCase())
  ).map(att => {
    const text = att.contentPlaintext || '';
    const idx = text.toLowerCase().indexOf(searchTerm.toLowerCase());
    const start = Math.max(0, idx - 150);
    const end = Math.min(text.length, idx + searchTerm.length + 150);
    return {
      context: text.substring(start, end),
      docType: att.resourceType
    };
  });
}

// Example: Find mentions of diabetes
const diabetesNotes = searchNotes('diabetes');

Example: Check for Care Gaps

function checkCareGaps(patientAge) {
  const gaps = [];
  const now = new Date();
  
  // Colonoscopy (age 45+, every 10 years)
  if (patientAge >= 45) {
    const colonoscopy = data.fhir.Procedure?.find(p =>
      p.code?.coding?.[0]?.display?.toLowerCase().includes('colonoscopy')
    );
    const lastDate = colonoscopy ? new Date(colonoscopy.performedDateTime) : null;
    const yearsSince = lastDate ? (now - lastDate) / (365 * 24 * 60 * 60 * 1000) : Infinity;
    if (yearsSince > 10) {
      gaps.push('Colonoscopy may be due (last: ' + (lastDate?.toLocaleDateString() || 'never') + ')');
    }
  }
  
  // Annual flu shot
  const fluShot = data.fhir.Immunization?.find(i =>
    i.vaccineCode?.coding?.[0]?.display?.toLowerCase().includes('influenza') &&
    new Date(i.occurrenceDateTime).getFullYear() === now.getFullYear()
  );
  if (!fluShot) {
    gaps.push('Annual flu shot may be due');
  }
  
  return gaps;
}

Example: Analyze Lab Trends

function analyzeTrend(loincCode, testName) {
  const values = getLabsByLoinc(loincCode);
  if (values.length < 2) return `${testName}: Insufficient data for trend`;
  
  const recent = values[0];
  const previous = values[1];
  const change = ((recent.value - previous.value) / previous.value * 100).toFixed(1);
  
  let trend = 'stable';
  if (change > 5) trend = `increased ${change}%`;
  if (change < -5) trend = `decreased ${Math.abs(change)}%`;
  
  return `${testName}: ${recent.value} ${recent.unit} (${trend} from ${previous.value})`;
}

// Example
analyzeTrend('4548-4', 'A1c');

Combining Structured + Unstructured Data

The power is combining FHIR resources with clinical note text:

// 1. Check if patient has diabetes diagnosis
const hasDiabetes = data.fhir.Condition?.some(c =>
  c.code?.coding?.[0]?.display?.toLowerCase().includes('diabetes')
);

// 2. Get A1c trend
const a1cValues = getLabsByLoinc('4548-4');

// 3. Find related medications
const diabetesMeds = data.fhir.MedicationRequest?.filter(m =>
  ['metformin', 'insulin', 'glipizide', 'januvia'].some(drug =>
    m.medicationCodeableConcept?.coding?.[0]?.display?.toLowerCase().includes(drug)
  )
);

// 4. Search notes for management discussions
const managementNotes = searchNotes('diabetes');

// Now provide comprehensive diabetes analysis

Important Guidelines

  1. Be empathetic - Health data is personal. Be supportive and clear.
  2. Not medical advice - Always remind users to discuss findings with their healthcare provider.
  3. Use plain language - Translate medical jargon into understandable terms.
  4. Respect privacy - Data is temporary and session-based.

Testing

For testing with Epic's sandbox:

  • Username: fhircamila
  • Password: epicepic1

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View