πŸ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

openevidence-data-handling

Implement HIPAA-compliant PHI data handling for OpenEvidence integrations. Use when implementing data protection, configuring retention policies, or ensuring compliance for clinical AI data flows. Trigger with phrases like "openevidence phi", "openevidence data", "openevidence hipaa data", "clinical data handling", "patient data protection". allowed-tools: Read, Write, Edit version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

openevidence-pack

Claude Code skill pack for OpenEvidence medical AI (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the openevidence-pack plugin:

/plugin install openevidence-pack@claude-code-plugins-plus

Click to copy

Instructions

# OpenEvidence Data Handling ## Overview Implement HIPAA-compliant Protected Health Information (PHI) handling for OpenEvidence clinical AI integrations. ## Prerequisites - Signed BAA with OpenEvidence - Understanding of HIPAA regulations - Data classification policy - Encryption infrastructure ## HIPAA Data Categories | Category | Examples | Handling | |----------|----------|----------| | PHI Identifiers | Name, DOB, SSN, MRN | Never send to OpenEvidence | | Clinical Data | Conditions, medications | May send de-identified | | Query Results | Answers, citations | Cache with encryption, audit access | | Audit Logs | User actions, timestamps | Retain 6 years, encrypt | ## 18 HIPAA Identifiers (Never Send to OpenEvidence) 1. Names 2. Geographic data (smaller than state) 3. Dates (except year) related to individual 4. Phone numbers 5. Fax numbers 6. Email addresses 7. Social Security numbers 8. Medical record numbers 9. Health plan beneficiary numbers 10. Account numbers 11. Certificate/license numbers 12. Vehicle identifiers and serial numbers 13. Device identifiers and serial numbers 14. Web URLs 15. IP addresses 16. Biometric identifiers 17. Full-face photographs 18. Any other unique identifying characteristic ## Instructions ### Step 1: PHI Detection and Removal ```typescript // src/compliance/phi-detector.ts // Patterns for common PHI const PHI_PATTERNS = { ssn: /\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b/g, mrn: /\b(MRN|Medical Record)[\s:#]*\d{6,12}\b/gi, phone: /\b(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g, email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, dob: /\b(DOB|Date of Birth|Born)[\s:]*\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}\b/gi, date: /\b\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}\b/g, name: /\b(Mr\.|Mrs\.|Ms\.|Dr\.|Patient)\s+[A-Z][a-z]+\s+[A-Z][a-z]+\b/g, address: /\b\d{1,5}\s+[A-Z][a-z]+\s+(Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd)\b/gi, }; export interface PHIDetectionResult { containsPHI: boolean; detectedPatterns: string[]; sanitizedText: string; } export function detectPHI(text: string): PHIDetectionResult { const detectedPatterns: string[] = []; let sanitized = text; for (const [pattern, regex] of Object.entries(PHI_PATTERNS)) { if (regex.test(text)) { detectedPatterns.push(pattern); // Reset regex lastIndex for global patterns regex.lastIndex = 0; sanitized = sanitized.replace(regex, `[${pattern.toUpperCase()}_REDACTED]`); } } return { containsPHI: detectedPatterns.length > 0, detectedPatterns, sanitizedText: sanitized, }; } export function sanitizeForOpenEvidence(text: string): string { const result = detectPHI(text); if (result.containsPHI) { console.warn('[PHI] Detected PHI in query, sanitizing:', result.detectedPatterns); } return result.sanitizedText; } ``` ### Step 2: Patient Context De-identification ```typescript // src/compliance/deidentify.ts interface IdentifiedPatientContext { patientId?: string; name?: string; dateOfBirth?: Date; age?: number; sex?: 'male' | 'female' | 'other'; conditions?: string[]; medications?: string[]; allergies?: string[]; } interface DeidentifiedPatientContext { ageRange?: string; sex?: string; conditionCategories?: string[]; medicationClasses?: string[]; hasAllergies?: boolean; } // Age ranges for de-identification function getAgeRange(age?: number): string | undefined { if (!age) return undefined; if (age < 1) return 'infant'; if (age < 5) return 'toddler'; if (age < 13) return 'child'; if (age < 18) return 'adolescent'; if (age < 30) return 'young-adult'; if (age < 50) return 'adult'; if (age < 65) return 'middle-aged'; if (age < 80) return 'elderly'; return 'elderly-80+'; } // Condition category mapping const CONDITION_CATEGORIES: Record = { // Cardiovascular 'hypertension': 'cardiovascular', 'coronary artery disease': 'cardiovascular', 'heart failure': 'cardiovascular', 'atrial fibrillation': 'cardiovascular', // Metabolic 'diabetes': 'metabolic', 'type 2 diabetes': 'metabolic', 'hyperlipidemia': 'metabolic', 'obesity': 'metabolic', // Respiratory 'asthma': 'respiratory', 'copd': 'respiratory', 'pneumonia': 'respiratory', // Mental Health 'depression': 'mental-health', 'anxiety': 'mental-health', // Add more as needed }; function categorizeConditions(conditions?: string[]): string[] | undefined { if (!conditions) return undefined; const categories = new Set(); for (const condition of conditions) { const category = CONDITION_CATEGORIES[condition.toLowerCase()]; if (category) { categories.add(category); } else { categories.add('other'); } } return Array.from(categories); } // Drug class mapping const DRUG_CLASSES: Record = { 'metformin': 'antidiabetic', 'lisinopril': 'ace-inhibitor', 'atorvastatin': 'statin', 'amlodipine': 'calcium-channel-blocker', 'metoprolol': 'beta-blocker', // Add more as needed }; function classifyMedications(medications?: string[]): string[] | undefined { if (!medications) return undefined; const classes = new Set(); for (const med of medications) { const drugClass = DRUG_CLASSES[med.toLowerCase()]; if (drugClass) { classes.add(drugClass); } else { classes.add('other-medication'); } } return Array.from(classes); } export function deidentifyPatientContext( context: IdentifiedPatientContext ): DeidentifiedPatientContext { return { ageRange: context.age ? getAgeRange(context.age) : undefined, sex: context.sex, conditionCategories: categorizeConditions(context.conditions), medicationClasses: classifyMedications(context.medications), hasAllergies: context.allergies ? context.allergies.length > 0 : undefined, }; } ``` ### Step 3: Encrypted Storage ```typescript // src/compliance/encryption.ts import crypto from 'crypto'; const ALGORITHM = 'aes-256-gcm'; const IV_LENGTH = 16; const AUTH_TAG_LENGTH = 16; export class EncryptionService { private key: Buffer; constructor(encryptionKey: string) { // Key should be 32 bytes for AES-256 this.key = crypto.scryptSync(encryptionKey, 'salt', 32); } encrypt(plaintext: string): string { const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv(ALGORITHM, this.key, iv); let encrypted = cipher.update(plaintext, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); // Return IV + AuthTag + Encrypted data return iv.toString('hex') + authTag.toString('hex') + encrypted; } decrypt(ciphertext: string): string { const iv = Buffer.from(ciphertext.slice(0, IV_LENGTH * 2), 'hex'); const authTag = Buffer.from( ciphertext.slice(IV_LENGTH * 2, IV_LENGTH * 2 + AUTH_TAG_LENGTH * 2), 'hex' ); const encrypted = ciphertext.slice(IV_LENGTH * 2 + AUTH_TAG_LENGTH * 2); const decipher = crypto.createDecipheriv(ALGORITHM, this.key, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } } // Usage for caching OpenEvidence responses export class EncryptedCacheStore { private encryption: EncryptionService; private redis: Redis; constructor(redis: Redis, encryptionKey: string) { this.redis = redis; this.encryption = new EncryptionService(encryptionKey); } async set(key: string, value: any, ttlSeconds: number): Promise { const encrypted = this.encryption.encrypt(JSON.stringify(value)); await this.redis.setex(key, ttlSeconds, encrypted); } async get(key: string): Promise { const encrypted = await this.redis.get(key); if (!encrypted) return null; try { const decrypted = this.encryption.decrypt(encrypted); return JSON.parse(decrypted) as T; } catch (error) { console.error('Decryption failed, cache entry may be corrupted'); await this.redis.del(key); return null; } } } ``` ### Step 4: Data Retention Policies ```typescript // src/compliance/retention.ts interface RetentionPolicy { dataType: string; retentionDays: number; archiveAfterDays?: number; encryptionRequired: boolean; } const HIPAA_RETENTION_POLICIES: RetentionPolicy[] = [ { dataType: 'audit_logs', retentionDays: 2190, // 6 years (HIPAA minimum) archiveAfterDays: 365, // Archive after 1 year encryptionRequired: true, }, { dataType: 'query_cache', retentionDays: 1, // 24 hours max encryptionRequired: true, }, { dataType: 'deepconsult_reports', retentionDays: 365, // 1 year archiveAfterDays: 90, encryptionRequired: true, }, { dataType: 'error_logs', retentionDays: 90, // 90 days encryptionRequired: false, // No PHI in error logs }, ]; export class DataRetentionManager { constructor(private db: Database, private archive: ArchiveStorage) {} async enforceRetentionPolicies(): Promise { const report: RetentionReport = { timestamp: new Date(), actions: [], }; for (const policy of HIPAA_RETENTION_POLICIES) { const result = await this.enforcePolicy(policy); report.actions.push(result); } return report; } private async enforcePolicy(policy: RetentionPolicy): Promise { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - policy.retentionDays); // Delete expired data const deletedCount = await this.db.deleteOlderThan( policy.dataType, cutoffDate ); // Archive if applicable let archivedCount = 0; if (policy.archiveAfterDays) { const archiveCutoff = new Date(); archiveCutoff.setDate(archiveCutoff.getDate() - policy.archiveAfterDays); const toArchive = await this.db.findOlderThan(policy.dataType, archiveCutoff); if (toArchive.length > 0) { await this.archive.store(policy.dataType, toArchive); archivedCount = toArchive.length; } } return { dataType: policy.dataType, deletedCount, archivedCount, cutoffDate, }; } } // Run daily via cron job export async function runDailyRetentionJob(): Promise { const manager = new DataRetentionManager(db, archiveStorage); const report = await manager.enforceRetentionPolicies(); console.log('[Retention] Daily job completed:', JSON.stringify(report)); // Alert if significant data deleted const totalDeleted = report.actions.reduce((sum, a) => sum + a.deletedCount, 0); if (totalDeleted > 10000) { await alertOps(`Data retention: ${totalDeleted} records deleted`); } } ``` ### Step 5: Audit Trail for PHI Access ```typescript // src/compliance/audit-trail.ts interface PHIAccessLog { timestamp: Date; userId: string; userRole: string; action: 'query' | 'view_result' | 'export' | 'deepconsult'; resourceType: string; resourceId: string; accessReason?: string; ipAddress: string; userAgent: string; // Never log: actual query content, patient identifiers, clinical data } export class PHIAuditLogger { private db: Database; private encryption: EncryptionService; constructor(db: Database, encryptionKey: string) { this.db = db; this.encryption = new EncryptionService(encryptionKey); } async logAccess(entry: Omit): Promise { const log: PHIAccessLog = { ...entry, timestamp: new Date(), }; // Encrypt sensitive fields const encryptedLog = { ...log, ipAddress: this.encryption.encrypt(log.ipAddress), userAgent: this.encryption.encrypt(log.userAgent), }; await this.db.auditLogs.create({ data: encryptedLog }); } async queryLogs( filters: { userId?: string; startDate?: Date; endDate?: Date; action?: string; }, pagination: { page: number; pageSize: number } ): Promise<{ logs: PHIAccessLog[]; total: number }> { // Require authorization to query audit logs const result = await this.db.auditLogs.findMany({ where: { ...(filters.userId && { userId: filters.userId }), ...(filters.startDate && { timestamp: { gte: filters.startDate } }), ...(filters.endDate && { timestamp: { lte: filters.endDate } }), ...(filters.action && { action: filters.action }), }, skip: (pagination.page - 1) * pagination.pageSize, take: pagination.pageSize, orderBy: { timestamp: 'desc' }, }); // Decrypt sensitive fields for authorized viewer const decryptedLogs = result.map(log => ({ ...log, ipAddress: this.encryption.decrypt(log.ipAddress), userAgent: this.encryption.decrypt(log.userAgent), })); const total = await this.db.auditLogs.count({ where: filters }); return { logs: decryptedLogs, total }; } } ``` ## Data Flow Diagram (HIPAA Compliant) ``` Clinical User β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PHI Detection │──── Block if PHI detected β”‚ & Sanitization β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ (De-identified query) β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Audit Logger │───▢│ Encrypted β”‚ β”‚ (Access Log) β”‚ β”‚ Audit Storage β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ OpenEvidence │───▢│ Encrypted β”‚ β”‚ API Call β”‚ β”‚ Cache β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Response to β”‚ β”‚ Clinical User β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Output - PHI detection and sanitization - Patient context de-identification - Encrypted storage for cached data - HIPAA-compliant retention policies - Comprehensive audit trail ## Data Handling Checklist - [ ] PHI patterns defined and detected - [ ] Patient context de-identified before queries - [ ] All cached data encrypted (AES-256) - [ ] Audit logging for all PHI access - [ ] 6-year retention for audit logs - [ ] Daily retention job running - [ ] Encryption keys managed securely - [ ] BAA signed with OpenEvidence ## Resources - [HIPAA Privacy Rule](https://www.hhs.gov/hipaa/for-professionals/privacy/index.html) - [HIPAA Security Rule](https://www.hhs.gov/hipaa/for-professionals/security/index.html) - [OpenEvidence Security](https://www.openevidence.com/security) ## Next Steps For enterprise access control, see `openevidence-enterprise-rbac`.

Skill file: plugins/saas-packs/openevidence-pack/skills/openevidence-data-handling/SKILL.md