Skip to content

Data Model + PHI Classification

This document classifies every Prisma model across all services into PHI, non-PHI, or metadata categories. Encryption status and retention behaviour are noted for each. All model names and field references are verified against the service schemas in services/*/prisma/schema.prisma.

The cross-service entity relationship diagram below shows how models relate at the case level.

erDiagram
  Patient ||--o{ Case : "has"
  Patient ||--o{ LegalHold : "protected by"
  Case ||--o{ SkinFinding : "contains"
  Case ||--o{ Image : "captures"
  SkinFinding ||--o{ Diagnosis : "yields"
  Case ||--o{ AiReview : "triages via"
  Case ||--o{ Review : "human-reviewed in"
  Case ||--o{ WorkflowInstance : "orchestrated by"
  AiReview ||--o{ AiReviewResult : "produces"
  AiReview ||--o{ AiReviewLesion : "annotates"
  Review ||--o{ ReviewClaim : "claimed via"
  Review ||--o{ ReviewAuditLog : "audited via"
  WorkflowInstance ||--o{ WorkflowStep : "drives"
  WorkflowInstance ||--o{ WorkflowEvent : "logs"
  WorkflowInstance ||--o{ WorkflowIntervention : "records"
  Patient ||--o{ ConsentRecord : "grants"
  Organisation ||--o{ Patient : "owns"
  Organisation ||--o{ AuditLog : "records"
  Organisation ||--o{ RetentionPolicy : "governs"
  Patient {
    string id PK
    string organisationId FK
    string givenName "encrypted"
    string familyName "encrypted"
    string dob "encrypted"
    string encryptedDek "wrapped DEK"
  }
  Case {
    string id PK
    string patientId FK
    string organisationId FK
    string productId FK
    string status
  }
  AiReview {
    string id PK
    string caseId FK
    string orgId FK
    string status
  }
  Review {
    string id PK
    string caseId FK
    string orgId FK
    string tier
    string status
  }
  LegalHold {
    string id PK
    string patientId FK
    string organisationId FK
    string reason
    datetime releasedAt
  }
  RetentionPolicy {
    string id PK
    string organisationId FK
    string entityType
    int retentionDays
    string action
  }

Classification Table

clinical-api service

Source: services/clinical-api/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
Organisation No None Permanent (tenant root)
Product No None Permanent
Patient Yes givenName, familyName, dob, sexAtBirth, genderIdentity, postalCode, email, phone all stored as @db.Text and encrypted via per-patient DEK. Hashes of email, dob, and postalCode stored separately for de-duplication. encryptedDek holds the wrapped key. Per-product RetentionPolicy; erasure via DEK nulling (ErasureService)
PatientIdentifier Yes value stored as @db.Text (encrypted). valueHash for lookups. Per-product policy
PatientMergeCandidate Yes References patient IDs; candidateRankedJson / matchFeaturesJson may contain identifiers Per-product policy
PatientMergeHistory Yes absorbedSnapshotJson contains a snapshot of patient data at merge time. actorSnapshot records who performed the merge. Per-product policy
Case Yes clinicalContextJson (free-text clinical context), createdByActor (actor snapshot) stored as @db.Text Per-product policy
SkinFinding Yes clinicalNotes (@db.Text), createdByActor (@db.Text). Body-site coordinates are non-encrypted floats. Per-product policy
LesionExtension Indirect Physical measurements (floats). No direct identifiers. Linked to SkinFinding. Via SkinFinding
Diagnosis Yes freeText and notes are @db.Text fields containing clinical decisions. actorSnapshot records the clinician. Per-product policy
DiagnosisCodeMapping No Lookup table — code system mappings only, no patient data Permanent
MedicationStatement Yes medicationFreeText, dosageText, reasonFreeText, notes all @db.Text; prescriberActorSnapshot Per-product policy
MedicationStatementHistory Yes changedByActor actor snapshot; previous/new dosage text Per-product policy
Image Yes Image bytes stored in S3 (s3Bucket/s3Key). S3 server-side encryption applies. uploadedByActor (@db.Text). Per-product policy; S3 objects deleted on erasure (ErasureService)
ImageDerivative Yes Derived image stored in S3. Via parent Image
FindingImage Yes Join table linking findings to images; bounding box coordinates. Via parent records
HistologyReport Yes resultSummaryCiphertext explicitly named as ciphertext (@db.Text). Report files stored in S3. reportingLabSnapshotJson. Per-product policy; S3 objects deleted on erasure
ReportFile Yes File bytes in S3. uploadedByActor (@db.Text). Via HistologyReport; S3 deleted on erasure
Event Indirect Event payload (@db.Text) may reference resource IDs; payload format depends on event type. No automated trim in v1
WebhookSubscription No Contains URL and HMAC secret only. Org-controlled
WebhookDelivery Indirect References event IDs. No patient data directly. No automated trim in v1
AuditLog Yes actorSnapshot (@db.Text) contains actor identity including clinician details. metadata may reference patient/case IDs. Append-only; no automated trim in v1
RetentionPolicy No Configuration table — no patient data Permanent
LegalHold Yes References patientId directly. reason field. Active until releasedAt is set

ai-review service

Source: services/ai-review/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
AiReview Yes caseId and orgId link to patient data. Status and DERM metadata stored in plain text. Per-product policy
AiReviewImage Yes clinicalImageId links to patient image. dermImageHash is a hash only. Via AiReview
AiReviewResult Yes rawCiphertext / rawIv / rawAuthTag columns — the raw DERM API response is stored encrypted in binary columns (AES-256-GCM at application layer). analysisResult, analysisStatus stored in plain text as summary codes. Per-product policy
AiReviewLesion Yes suspectedDiagnosis* ICD-10 / SNOMED codes and terms; referralRecommendation* and interpretationGuidance in plain text. Linked to patient via aiReviewId. Via AiReview

orchestrator service

Source: services/orchestrator/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
WorkflowDefinition No Workflow logic/configuration. No patient data. Permanent
WorkflowDefinitionRevision No Diff of workflow definition changes. Permanent
WorkflowInstance Yes caseId links to patient record. context (JSON) may contain case-level data. cancelledBy references a user ID. Per-product policy (no automated trim in v1)
WorkflowStep Yes input/output JSON may contain case context. correlationId links to events. Via WorkflowInstance
WorkflowEvent Yes envelope JSON contains event payload which may reference case/patient IDs. No automated trim in v1
WorkflowIntervention Yes beforeState/afterState JSON; performedBy user reference. Audit record of manual workflow actions. Via WorkflowInstance
ReasonCode No Reference data only. Permanent

human-review service

Source: services/human-review/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
Reviewer No Reviewer credentials (licence number, jurisdiction). Not patient data. Permanent
Review Yes caseId links to patient. contextSnapshot (JSON) contains a snapshot of the case at review time, potentially including clinical data. notes field. Per-product policy (no automated trim in v1)
ReviewClaim Yes Links reviewer to case via reviewId. declineNote may contain clinical reasoning. Via Review
ReviewAuditLog Yes reviewerId, action, and metadata JSON. Audit record of human review actions. Append-only; no automated trim in v1

Source: services/consent/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
ConsentType No Configuration / consent definition. Permanent
ConsentTextVersion No Consent text versions. Permanent
ConsentRecord Yes patientId links to patient. actorSnapshot records who captured consent. capturedAt timestamp. Per-org policy; no automated trim in v1

notifications service

Source: services/notifications/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
Template No Email/Slack template content. phi boolean flag on each template indicates whether rendering will include PHI. Permanent
NotificationSubscription No Event routing rules. Permanent
Notification Conditional phi = true rows contain PHI in variables (LongText). recipientRef and resolvedAddress contain email addresses or user IDs. No automated trim in v1
NotificationPreference No Opt-out preferences by user + category + channel. Permanent
DeliveryAttempt No Provider request/response JSON. No patient data expected. No automated trim in v1
EventInbox No Tracks processed event IDs. No automated trim in v1
SuppressionList No Email/Slack addresses suppressed from delivery. Permanent

auth service

Source: services/auth/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
SigningKey No RSA key material for JWT signing. privateKey stored at rest — protect via DB access controls. Rotated; old keys archived (status="rotated")
ApiClient No OAuth client configuration. authConfigJson may contain external IdP details. Permanent until deleted
ApiClientSecret No Secret stored as a hash (secretHash). Raw secret never persisted. Per-client lifecycle
RegisteredScope No Scope catalogue. Permanent

user-management service

Source: services/user-management/prisma/schema.prisma

Model PHI? Encrypted fields Retention behaviour
User No (PII) email (plain text), displayName. Not clinical PHI but personal data under GDPR. Per-org policy
PractitionerProfile No (PII) Professional ID and speciality. Per-org policy
PatientAccount Yes clinicalPatientId links to the clinical-api Patient record. Bridges authentication identity to clinical identity. Per-org policy
OrgMembership No Role assignments. Permanent
Role / RolePermission / RegisteredPermission / ProductAccess No RBAC configuration. Permanent

Summary

  • PHI-bearing services: clinical-api, ai-review, orchestrator (case context), human-review (case snapshot), consent, notifications (conditional).
  • Encryption: PHI fields in clinical-api are encrypted with per-patient DEKs (AES-256-GCM). AI review raw results are encrypted in binary columns. Other services store patient references (IDs) in plain text — the data they hold is de-identified at the field level when PHI content is not required.
  • No automated retention trim is shipped in v1. Retention policies and legal holds are configurable via the RetentionPolicy and LegalHold models, and the erasure endpoint exists, but no nightly cron job runs automatically. See Retention + GDPR Erasure for detail.