npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@meddevkit/chunker

v0.3.1

Published

PHI-aware medical text chunker for RAG applications

Readme

MedDevKit

Live demo → Paste a clinical note and compare MedDevKit's chunks with a generic chunker side by side.

MedDevKit is an open-source medical NLP toolkit that provides PHI-aware text chunking for RAG applications, real-time detection of vital signs, clinical sections, negations, and protected health information (PHI) in clinical documentation. Designed for healthcare AI applications with ~50ms latency and no network calls, it enables safe processing of medical text while preserving clinical context boundaries.

Why MedDevKit?

Generic RAG chunkers fail on medical text:

  • They split vital signs: "BP: 120" | "/80 mmHg"
  • They ignore clinical sections
  • They miss PHI risks

MedDevKit was built for healthcare.

Quick Start

npm install @meddevkit/chunker
import { MedicalChunker } from '@meddevkit/chunker';

const chunker = new MedicalChunker({
  preserveVitals: true,      // Don't split "BP: 120/80"
  phiDetection: 'basic',     // Flag potential PHI
  maxTokens: 512
});

const result = chunker.chunk(clinicalNote);
// { chunks: MedicalChunk[], metadata: ProcessingMetadata }

Features

Vital Signs Preservation

Never splits measurements like blood pressure, heart rate, or O2 saturation:

// Input: "BP: 120/80 mmHg, HR 72 bpm"
// Will NOT be split into separate chunks

PHI Detection

Flags potential Protected Health Information:

const result = chunker.chunk(noteWithPhi);

if (result.metadata.containsPhi) {
  console.log('Warning: PHI detected');
  result.chunks.forEach(chunk => {
    chunk.phiMarkers.forEach(marker => {
      console.log(`${marker.type} at position ${marker.startOffset}`);
    });
  });
}

Clinical Section Awareness

Recognizes and respects standard clinical note sections:

  • Chief Complaint (CC)
  • History of Present Illness (HPI)
  • Physical Examination (PE)
  • Assessment & Plan (A/P)
  • Medications, Allergies, and more

Token-Aware Chunking

Respects LLM context limits while preserving medical integrity:

const chunker = new MedicalChunker({
  maxTokens: 512,      // Maximum tokens per chunk
  minTokens: 50,       // Minimum tokens per chunk
  overlapTokens: 50,   // Context overlap between chunks
});

Each chunk after the first is prefixed with up to overlapTokens of the text that precedes it. The overlap is a contiguous slice of the normalized text that starts on a word boundary (a sentence or line start when one is available) and never inside a vital sign, medication, or PHI span. Overlap is added on top of maxTokens, so a chunk's tokenCount may exceed maxTokens by up to overlapTokens. PHI markers and all other detections in the overlap are reported on the chunk, and every offset is relative to chunk.text.

Negation Detection

Optionally detect negated medical findings:

const chunker = new MedicalChunker({
  detectNegations: true
});

// "Patient denies chest pain" -> { negatedText: "chest pain", type: "denied" }

Medication, Temporal & Severity Detection

Medication mentions (drug + dose) are detected and preserved by default, so "Metoprolol 25 mg PO BID" is never split across chunks. Temporal expressions and severity modifiers are opt-in:

const chunker = new MedicalChunker({
  preserveMedications: true, // default
  detectTemporal: true,
  detectSeverity: true,
});

const { chunks } = chunker.chunk(
  'Severe chest pain for 3 days. Started metoprolol 25 mg PO BID.'
);

chunks[0].metadata.medications;
// [{ name: 'metoprolol', dose: 25, unit: 'mg', route: 'oral', frequency: 'BID', ... }]
chunks[0].metadata.temporal;
// [{ type: 'duration', raw: 'for 3 days', value: 3, unit: 'day', ... }]
chunks[0].metadata.severity;
// [{ level: 'severe', term: 'Severe', modifiedText: 'chest pain for 3 days', ... }]

The detectors are also available standalone: detectMedications(), detectTemporal() and detectSeverity(). Medication detection is pattern-based (no drug lexicon) and ignores vitals and lab values such as "weight 80 kg" or "glucose 180 mg/dL".

Plugin System

Extend MedDevKit with custom pattern detection via the plugin architecture:

import { MedicalChunker } from '@meddevkit/chunker';
import type { PatternPlugin, MedDevKitContext } from '@meddevkit/chunker';

const myPlugin: PatternPlugin = {
  id: '@myorg/medication-plugin',
  name: 'Medication Pattern Plugin',
  version: '1.0.0',
  minContextVersion: '1.0.0',

  detectPatterns(text: string, ctx: MedDevKitContext) {
    const matches = [];
    const regex = /(\w+)\s+(\d+)\s*(mg|mcg|ml)/gi;
    let match;

    while ((match = regex.exec(text)) !== null) {
      matches.push({
        patternId: 'medication-dose',
        patternName: 'Medication Dosage',
        pluginId: '@myorg/medication-plugin',
        startOffset: match.index,
        endOffset: match.index + match[0].length,
        raw: match[0],
        confidence: 0.9,
        isProtected: true, // Prevents splitting on this pattern
        category: 'medication',
        data: {
          medication: match[1],
          dose: parseInt(match[2]),
          unit: match[3],
        },
      });
    }
    return matches;
  },
};

const chunker = new MedicalChunker({
  plugins: [myPlugin],
  includePluginPatterns: true,
});

const result = chunker.chunk('Patient taking aspirin 81 mg daily.');
// result.metadata.pluginsApplied: ['@myorg/medication-plugin']
// result.chunks[0].metadata.pluginPatterns: { '@myorg/medication-plugin': [...] }

Plugins can be published to npm as @meddevkit/plugin-* packages for community sharing.

Plugin Lifecycle Hooks:

  • onRegister — Called when plugin is registered
  • onDocumentStart — Called before processing begins
  • onPatternsCollected — Modify all collected patterns
  • onChunkCreated — Modify individual chunks
  • onDocumentEnd — Called after processing completes
  • onUnload — Cleanup when plugin is removed

Clinical NLP providers (MedicalAnalyzer)

MedicalAnalyzer combines the local detectors with a clinical NLP provider (Azure Text Analytics for health, or your own) and returns provider-neutral entities you can feed into the chunker:

  • Local detection runs first: PHI, sections, vitals, medications, negation.
  • PHI policy is applied before any text leaves the process ('mask' by default, same-length so offsets stay valid).
  • Provider: the first available adapter in providers order is called, with a timeout. If all fail, you get a degraded local-only result.
  • Merge: the provider owns diagnoses, symptoms, procedures and codes; local owns PHI, sections, parsed vitals and dose/route/frequency. Negation is 'either' by default, and disagreements are flagged.
  • Chunk with entities: entities are protected from splitting and attached to chunk metadata.
import { MedicalChunker, MedicalAnalyzer } from '@meddevkit/chunker';
import { AzureHealthAdapter } from '@meddevkit/adapter-azure';

const analyzer = new MedicalAnalyzer({
  providers: [
    new AzureHealthAdapter({
      endpoint: process.env.AZURE_LANGUAGE_ENDPOINT!,
      apiKey: process.env.AZURE_LANGUAGE_KEY,
    }),
  ],
});

const analysis = await analyzer.analyze(note);
const result = new MedicalChunker().chunk(analysis.text, { entities: analysis.entities });

To add another provider, implement the MedicalNlpAdapter interface and pass it in providers.

Try it in the live demo's Analyzer view: local detectors merged with Azure Text Analytics for health, replayed from recordings or called live with your own endpoint and key. To record the sample notes, run pnpm build and then pnpm --filter @meddevkit/demo record:azure with AZURE_LANGUAGE_ENDPOINT and AZURE_LANGUAGE_KEY set.

Note: PHI masking is regex-based and best-effort. It is not HIPAA de-identification; only send clinical text to a provider under an agreement (such as a BAA) that permits it.

API Reference

MedicalChunker

interface MedicalChunkerConfig {
  preserveVitals?: boolean;      // Default: true
  phiDetection?: 'none' | 'basic' | 'enhanced';  // Default: 'basic'
  maxTokens?: number;            // Default: 512
  minTokens?: number;            // Default: 50
  overlapTokens?: number;        // Default: 50
  preserveSections?: boolean;    // Default: true
  preserveSentences?: boolean;   // Default: true
  detectNegations?: boolean;     // Default: false
  preserveMedications?: boolean; // Default: true (never split "Metoprolol 25 mg PO BID")
  detectTemporal?: boolean;      // Default: false
  detectSeverity?: boolean;      // Default: false
  customPreservePatterns?: RegExp[];

  // Plugin System
  plugins?: PatternPlugin[];     // Custom pattern plugins
  includePluginPatterns?: boolean;  // Attach patterns to chunks (Default: true)
  strictPluginCompatibility?: boolean;  // Throw on version mismatch (Default: false)
}

PatternPlugin

interface PatternPlugin {
  readonly id: string;           // Unique identifier (e.g., '@myorg/plugin-name')
  readonly name: string;         // Human-readable name
  readonly version: string;      // Plugin version (semver)
  readonly minContextVersion: string;  // Minimum MedDevKit Context version

  // Pattern Detection
  detectPatterns?(text: string, ctx: MedDevKitContext): PatternMatch[];
  getProtectedRanges?(text: string, ctx: MedDevKitContext): ProtectedRange[];
  annotateChunk?(chunk: MedicalChunk, ctx: MedDevKitContext): ChunkAnnotations;

  // Lifecycle Hooks
  onRegister?(ctx: MedDevKitContext): void | Promise<void>;
  onDocumentStart?(document: { text: string }, ctx: MedDevKitContext): void;
  onPatternsCollected?(patterns: PatternMatch[], ctx: MedDevKitContext): PatternMatch[];
  onChunkCreated?(chunk: MedicalChunk, ctx: MedDevKitContext): MedicalChunk;
  onDocumentEnd?(result: ChunkingResult, ctx: MedDevKitContext): void;
  onUnload?(): void;
}

MedDevKitContext

The context object passed to plugins with utilities and version information:

interface MedDevKitContext {
  readonly brandVersion: string;    // "MedDevKit Context 1.0"
  readonly contextVersion: string;  // "1.0.0" (semver)
  readonly text: string;            // Document text
  readonly features: ContextFeatures;

  // Utilities
  estimateTokens(text: string): number;
  estimateCharsForTokens(tokens: number): number;
  matchPattern(pattern: RegExp): Array<{ match: RegExpExecArray; start: number; end: number }>;
  hasFeature(feature: keyof ContextFeatures): boolean;
  addProtectedRange(start: number, end: number): void;
}

ChunkingResult

interface ChunkingResult {
  chunks: MedicalChunk[];
  metadata: ProcessingMetadata;
}

interface MedicalChunk {
  text: string;
  id: string;
  index: number;
  hasPhi: boolean;
  phiMarkers: PhiMarker[];
  boundaries: ChunkBoundary;
  metadata: ChunkMetadata;
}

interface ChunkBoundary {
  start: number;          // Core (non-overlap) range start in the normalized text
  end: number;            // Core range end (exclusive)
  overlapStart?: number;  // Where chunk.text begins when overlap was prepended
  splitReason: SplitReason;
}

Offsets in phiMarkers and metadata (vitals, negations, medications, temporal, severity, plugin patterns) are relative to chunk.text, so chunk.text.slice(m.startOffset, m.endOffset) returns the matched text.

Production Ready?

This toolkit provides basic patterns ideal for demos and development.

For production healthcare deployments requiring:

  • HIPAA-compliant PHI handling
  • Enhanced pattern detection (50+ patterns)
  • BAA coverage
  • Enterprise support

Contact us about MedDevKit Cloud or consulting services.

Packages

| Package | Description | Status | |---------|-------------|--------| | @meddevkit/chunker | PHI-aware medical text chunker with plugin system | Available | | @meddevkit/adapter-azure | Azure Text Analytics for health provider adapter | Available | | @meddevkit/terminology | Medical term normalizer | Coming soon | | @meddevkit/retrieval | Cascading medical retrieval | Coming soon |

Community plugins follow the @meddevkit/plugin-* naming convention.

Development

# Clone the repository
git clone https://github.com/meddevkit/meddevkit.git
cd meddevkit

# Install dependencies
pnpm install

# Build all packages
pnpm build

# Run tests
pnpm test

# Development mode
pnpm dev

Contributing

We welcome contributions! Please open an issue or pull request on GitHub.

License

MIT - See LICENSE


Built with expertise from production healthcare AI systems. Powered by MedDevKit Context 1.0.