| name | openevidence-data-handling |
| description | 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> |
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)
- Names
- Geographic data (smaller than state)
- Dates (except year) related to individual
- Phone numbers
- Fax numbers
- Email addresses
- Social Security numbers
- Medical record numbers
- Health plan beneficiary numbers
- Account numbers
- Certificate/license numbers
- Vehicle identifiers and serial numbers
- Device identifiers and serial numbers
- Web URLs
- IP addresses
- Biometric identifiers
- Full-face photographs
- Any other unique identifying characteristic
Instructions
Step 1: PHI Detection and Removal
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.(text)) {
detectedPatterns.(pattern);
regex. = ;
sanitized = sanitized.(regex, );
}
}
{
: detectedPatterns. > ,
detectedPatterns,
: sanitized,
};
}
(): {
result = (text);
(result.) {
.(, result.);
}
result.;
}
Step 2: Patient Context De-identification
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;
}
function getAgeRange(age?: number): string | undefined {
if (!age) return undefined;
if (age < 1) return 'infant';
if (age < 5) return 'toddler';
if (age < ) ;
(age < ) ;
(age < ) ;
(age < ) ;
(age < ) ;
(age < ) ;
;
}
: <, > = {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
};
(): [] | {
(!conditions) ;
categories = <>();
( condition conditions) {
category = [condition.()];
(category) {
categories.(category);
} {
categories.();
}
}
.(categories);
}
: <, > = {
: ,
: ,
: ,
: ,
: ,
};
(): [] | {
(!medications) ;
classes = <>();
( med medications) {
drugClass = [med.()];
(drugClass) {
classes.(drugClass);
} {
classes.();
}
}
.(classes);
}
(): {
{
: context. ? (context.) : ,
: context.,
: (context.),
: (context.),
: context. ? context.. > : ,
};
}
Step 3: Encrypted Storage
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) {
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();
iv.() + authTag.() + encrypted;
}
(: ): {
iv = .(ciphertext.(, * ), );
authTag = .(
ciphertext.( * , * + * ),
);
encrypted = ciphertext.( * + * );
decipher = crypto.(, ., iv);
decipher.(authTag);
decrypted = decipher.(encrypted, , );
decrypted += decipher.();
decrypted;
}
}
{
: ;
: ;
() {
. = redis;
. = (encryptionKey);
}
(: , : , : ): <> {
encrypted = ..(.(value));
..(key, ttlSeconds, encrypted);
}
get<T>(: ): <T | > {
encrypted = ..(key);
(!encrypted) ;
{
decrypted = ..(encrypted);
.(decrypted) T;
} (error) {
.();
..(key);
;
}
}
}
Step 4: Data Retention Policies
interface RetentionPolicy {
dataType: string;
retentionDays: number;
archiveAfterDays?: number;
encryptionRequired: boolean;
}
const HIPAA_RETENTION_POLICIES: RetentionPolicy[] = [
{
dataType: 'audit_logs',
retentionDays: 2190,
archiveAfterDays: 365,
encryptionRequired: true,
},
{
dataType: 'query_cache',
retentionDays: 1,
encryptionRequired: true,
},
{
dataType: 'deepconsult_reports',
retentionDays: 365,
archiveAfterDays: 90,
encryptionRequired: true,
},
{
dataType: 'error_logs',
retentionDays: 90,
encryptionRequired: false,
},
];
export class DataRetentionManager {
() {}
(): <> {
: = {
: (),
: [],
};
( policy ) {
result = .(policy);
report..(result);
}
report;
}
(: ): <> {
cutoffDate = ();
cutoffDate.(cutoffDate.() - policy.);
deletedCount = ..(
policy.,
cutoffDate
);
archivedCount = ;
(policy.) {
archiveCutoff = ();
archiveCutoff.(archiveCutoff.() - policy.);
toArchive = ..(policy., archiveCutoff);
(toArchive. > ) {
..(policy., toArchive);
archivedCount = toArchive.;
}
}
{
: policy.,
deletedCount,
archivedCount,
cutoffDate,
};
}
}
(): <> {
manager = (db, archiveStorage);
report = manager.();
.(, .(report));
totalDeleted = report..( sum + a., );
(totalDeleted > ) {
();
}
}
Step 5: Audit Trail for PHI Access
interface PHIAccessLog {
timestamp: Date;
userId: string;
userRole: string;
action: 'query' | 'view_result' | 'export' | 'deepconsult';
resourceType: string;
resourceId: string;
accessReason?: string;
ipAddress: string;
userAgent: string;
}
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<PHIAccessLog, 'timestamp'>): <> {
: = {
...entry,
: (),
};
encryptedLog = {
...log,
: ..(log.),
: ..(log.),
};
...({ : encryptedLog });
}
(
: {
?: ;
?: ;
?: ;
?: ;
},
: { : ; : }
): <{ : []; : }> {
result = ...({
: {
...(filters. && { : filters. }),
...(filters. && { : { : filters. } }),
...(filters. && { : { : filters. } }),
...(filters. && { : filters. }),
},
: (pagination. - ) * pagination.,
: pagination.,
: { : },
});
decryptedLogs = result.( ({
...log,
: ..(log.),
: ..(log.),
}));
total = ...({ : filters });
{ : 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
Resources
Next Steps
For enterprise access control, see openevidence-enterprise-rbac.