You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
8.6 KiB
271 lines
8.6 KiB
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") |
|
}
|
|
|