272 lines
8.6 KiB
Plaintext
272 lines
8.6 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
previewFeatures = ["postgresqlExtensions"]
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
extensions = [vector, uuidOssp(map: "uuid-ossp")]
|
|
}
|
|
|
|
// =========================================================================
|
|
// AUTH / RBAC
|
|
// =========================================================================
|
|
|
|
enum Role {
|
|
MANAGER
|
|
SENIOR_ADMIN
|
|
SECURITY
|
|
SYSADMIN
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
email String @unique
|
|
fullName String @map("full_name")
|
|
passwordHash String @map("password_hash")
|
|
role Role
|
|
isActive Boolean @default(true) @map("is_active")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
refreshTokens RefreshToken[]
|
|
consentActions PatientConsent[]
|
|
biometryAccessLog BiometryAccessLog[]
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model RefreshToken {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
userId String @map("user_id") @db.Uuid
|
|
tokenHash String @unique @map("token_hash")
|
|
expiresAt DateTime @map("expires_at")
|
|
revokedAt DateTime? @map("revoked_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@map("refresh_tokens")
|
|
}
|
|
|
|
// =========================================================================
|
|
// DOMAIN: PATIENT / CONSENT
|
|
// =========================================================================
|
|
|
|
model Patient {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
polimedPatientId String? @unique @map("polimed_patient_id")
|
|
fullName String? @map("full_name")
|
|
consentReceivedAt DateTime? @map("consent_received_at")
|
|
consentRevokedAt DateTime? @map("consent_revoked_at")
|
|
pendingDeletionAt DateTime? @map("pending_deletion_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
consents PatientConsent[]
|
|
tracks Track[]
|
|
visits Visit[]
|
|
consentRevocationJobs ConsentRevocationJob[]
|
|
|
|
@@map("patients")
|
|
}
|
|
|
|
enum ConsentAction {
|
|
GRANTED
|
|
REVOKED
|
|
}
|
|
|
|
model PatientConsent {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
patientId String @map("patient_id") @db.Uuid
|
|
action ConsentAction
|
|
paperRef String? @map("paper_ref")
|
|
actorUserId String @map("actor_user_id") @db.Uuid
|
|
occurredAt DateTime @default(now()) @map("occurred_at")
|
|
|
|
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
|
|
actor User @relation(fields: [actorUserId], references: [id])
|
|
|
|
@@index([patientId])
|
|
@@map("patient_consents")
|
|
}
|
|
|
|
// =========================================================================
|
|
// CAMERAS / ZONES
|
|
// =========================================================================
|
|
|
|
enum ZoneCode {
|
|
A
|
|
B
|
|
C
|
|
D
|
|
E
|
|
}
|
|
|
|
model Zone {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
code ZoneCode @unique
|
|
name String
|
|
|
|
cameras Camera[]
|
|
trackEvents TrackEvent[]
|
|
|
|
@@map("zones")
|
|
}
|
|
|
|
model Camera {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
name String @unique
|
|
rtspUrl String? @map("rtsp_url")
|
|
zoneId String @map("zone_id") @db.Uuid
|
|
|
|
zone Zone @relation(fields: [zoneId], references: [id])
|
|
trackEvents TrackEvent[]
|
|
faceEmbeddings FaceEmbedding[]
|
|
|
|
@@map("cameras")
|
|
}
|
|
|
|
// =========================================================================
|
|
// TRACKS / EVENTS / EMBEDDINGS
|
|
// =========================================================================
|
|
|
|
enum TrackStatus {
|
|
UNMATCHED
|
|
MATCHED
|
|
ANONYMIZED
|
|
}
|
|
|
|
model Track {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
patientId String? @map("patient_id") @db.Uuid
|
|
status TrackStatus @default(UNMATCHED)
|
|
firstSeenAt DateTime @map("first_seen_at")
|
|
lastSeenAt DateTime @map("last_seen_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
patient Patient? @relation(fields: [patientId], references: [id])
|
|
events TrackEvent[]
|
|
faceEmbeddings FaceEmbedding[]
|
|
|
|
@@index([status, firstSeenAt])
|
|
@@index([patientId])
|
|
@@map("tracks")
|
|
}
|
|
|
|
enum TrackEventType {
|
|
arrived
|
|
waiting
|
|
service_started
|
|
service_ended
|
|
left_without_service
|
|
}
|
|
|
|
model TrackEvent {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
trackId String @map("track_id") @db.Uuid
|
|
type TrackEventType
|
|
cameraId String @map("camera_id") @db.Uuid
|
|
zoneId String @map("zone_id") @db.Uuid
|
|
occurredAt DateTime @map("occurred_at")
|
|
evidenceKey String? @map("evidence_key")
|
|
// [x1, y1, x2, y2] нормализовано 0..1 — bbox распознанного лица на кадре.
|
|
faceBbox Json? @map("face_bbox") @db.JsonB
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
track Track @relation(fields: [trackId], references: [id], onDelete: Cascade)
|
|
camera Camera @relation(fields: [cameraId], references: [id])
|
|
zone Zone @relation(fields: [zoneId], references: [id])
|
|
|
|
@@index([trackId, occurredAt])
|
|
@@map("track_events")
|
|
}
|
|
|
|
// 512-d face embedding (InsightFace buffalo_l).
|
|
// face-service пишет и читает напрямую через psycopg2 + pgvector.
|
|
model FaceEmbedding {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
embedding Unsupported("vector(512)")
|
|
patientId String? @map("patient_id") @db.Uuid
|
|
trackId String? @map("track_id") @db.Uuid
|
|
cameraId String @map("camera_id") @db.Uuid
|
|
quality Float @default(0)
|
|
capturedAt DateTime @map("captured_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
track Track? @relation(fields: [trackId], references: [id], onDelete: SetNull)
|
|
camera Camera @relation(fields: [cameraId], references: [id])
|
|
|
|
@@index([trackId])
|
|
@@index([patientId])
|
|
@@index([capturedAt])
|
|
@@map("face_embeddings")
|
|
}
|
|
|
|
// =========================================================================
|
|
// VISITS
|
|
// =========================================================================
|
|
|
|
model Visit {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
patientId String @map("patient_id") @db.Uuid
|
|
polimedAppointmentId String? @map("polimed_appointment_id")
|
|
arrivedAt DateTime @map("arrived_at")
|
|
serviceStartedAt DateTime? @map("service_started_at")
|
|
serviceEndedAt DateTime? @map("service_ended_at")
|
|
leftWithoutService Boolean @default(false) @map("left_without_service")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([patientId, arrivedAt])
|
|
@@map("visits")
|
|
}
|
|
|
|
// =========================================================================
|
|
// AUDIT
|
|
// =========================================================================
|
|
|
|
model BiometryAccessLog {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
actorUserId String? @map("actor_user_id") @db.Uuid
|
|
subjectPatientId String? @map("subject_patient_id") @db.Uuid
|
|
action String
|
|
requestPath String? @map("request_path")
|
|
occurredAt DateTime @default(now()) @map("occurred_at")
|
|
|
|
actor User? @relation(fields: [actorUserId], references: [id])
|
|
|
|
@@index([occurredAt])
|
|
@@index([subjectPatientId])
|
|
@@map("biometry_access_log")
|
|
}
|
|
|
|
// =========================================================================
|
|
// CONSENT REVOCATION JOBS (24h delete)
|
|
// =========================================================================
|
|
|
|
enum ConsentRevocationStatus {
|
|
PENDING
|
|
DONE
|
|
}
|
|
|
|
model ConsentRevocationJob {
|
|
id String @id @default(uuid()) @db.Uuid
|
|
patientId String @map("patient_id") @db.Uuid
|
|
revokedAt DateTime @map("revoked_at")
|
|
scheduledFor DateTime @map("scheduled_for")
|
|
status ConsentRevocationStatus @default(PENDING)
|
|
completedAt DateTime? @map("completed_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
patient Patient @relation(fields: [patientId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([status, scheduledFor])
|
|
@@map("consent_revocation_jobs")
|
|
}
|