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 |
consent service¶
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
RetentionPolicyandLegalHoldmodels, and the erasure endpoint exists, but no nightly cron job runs automatically. See Retention + GDPR Erasure for detail.