Replace the single-pass, exact-match-only audit with the two-pass model from
imageright_claim_review_audit_methodology.md, applied across all 10 SHAPE
checklist items:
- TaskAudit.status (3-state) -> TaskAuditClassification (7-state): PASS,
PASS_WITH_NAMING_EXCEPTION, MANUAL_REVIEW, FAIL_MISSING, FAIL_LATE,
FAIL_WRONG_ARTIFACT, NOT_APPLICABLE. New columns for confidence score,
evidence bucket, naming-exception/manual-review flags, msg subject/sent
date, attachment filenames, a human-readable evidence summary, and the
full scoring signal trail (matched_signals).
- New audit-scoring.ts: weighted metadata + content scoring per the
methodology's table, generalized to use each spec item's own
keywords/docTypes rather than a hardcoded claim-specific list.
- New content-inspector.ts: downloads and parses `.msg` (subject, sender,
sent date, body, attachments via @kenjiuno/msgreader), `.xlsx` (sheet
names/cell text via xlsx), and `.pdf` (text-layer only via pdf-parse,
no OCR) — recursing one level into email attachments.
- ImageRightClient: new getPageImageContent() using the v2 REST API
(`/api/v2/pages/{pageId}/images/{imageId}/{version}`) — the only endpoint
that returns raw file bytes; v1 only exposes JSON metadata. Verified live
against a real .msg (correct OLE2 signature, exact byte-size match).
- audit-matching.ts: matchesKeywords is now plural/singular-tolerant
(normalizeForMatch) — fixes false negatives like "Open Claims Review" not
matching keyword "CLAIM REVIEW". Added docTypesToExtensions, mapping the
spec's generic doc_types labels to real file extensions (evidence shows
these labels describe file format, not ImageRight's document-type
taxonomy).
- audit-engine.ts: auditSpecItem() replaces findBestMatch() — scores every
in-window candidate, falls back to strict exact-match (fast path, no
content download) when possible, otherwise deep-inspects the top
candidate and only confirms PASS_WITH_NAMING_EXCEPTION when content
positively confirms (not merely "score didn't decrease"). Also fixes a
real accuracy bug: unscoped whole-file document search silently truncates
at ~1000 docs on high-volume accounts (verified live) — ALL_TIME spec
items now iterate every folder instead (findDocumentsSafe).
- UI/API updated for the new classification taxonomy and evidence fields.
Verified end-to-end live against the methodology doc's own worked example
(American Marine Express, Inc., IR document 12884779): real API calls, real
.msg download/parse (found both a signature image and the actual Excel
attachment), correctly classified PASS_WITH_NAMING_EXCEPTION.
68 new/updated tests covering scoring, content parsing (real xlsx bytes;
mocked msgreader/pdf-parse), fuzzy keyword matching, and all 6+1
classification outcomes via auditSpecItem with a fake ImageRight client.
Known follow-up (not resolved here): this client's stored Client.renewalDate
(2026-10-07) and PolicyGroup renewal date (2027-03-02) don't match the
2026-04-04 renewal date used in the methodology doc's own example — worth
reconciling separately, since it determines which target-date windows the
live app actually computes for this client.
580 lines
21 KiB
Text
580 lines
21 KiB
Text
// This is your Prisma schema file,
|
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
}
|
|
|
|
// ============================================
|
|
// User Management & Authentication
|
|
// ============================================
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
entraOid String? @unique @map("entra_oid")
|
|
email String @unique
|
|
displayName String? @map("display_name")
|
|
department String?
|
|
jobTitle String? @map("job_title")
|
|
photoUrl String? @map("photo_url")
|
|
office String?
|
|
isActive Boolean @default(true) @map("is_active")
|
|
lastLoginAt DateTime? @map("last_login_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
userRoles UserRole[]
|
|
createdTasks Task[] @relation("TaskCreatedBy")
|
|
completedTasks Task[] @relation("TaskCompletedBy")
|
|
taskAssignments TaskAssignment[]
|
|
taskNotes TaskNote[] @relation("TaskNoteAuthor")
|
|
createdTemplates TaskTemplate[]
|
|
syncLogs SyncLog[]
|
|
auditLogs AuditLog[]
|
|
notifications Notification[]
|
|
notificationPrefs NotificationPreference[]
|
|
createdPolicyGroups PolicyGroup[]
|
|
advocateClients Client[] @relation("ClientAdvocate")
|
|
clientMemberships ClientMember[]
|
|
shapeImportRuns ShapeImportRun[]
|
|
taskAudits TaskAudit[]
|
|
|
|
@@map("users")
|
|
}
|
|
|
|
model Role {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
permissions Json @default("{}")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
userRoles UserRole[]
|
|
entraGroupMappings EntraGroupRoleMapping[]
|
|
|
|
@@map("roles")
|
|
}
|
|
|
|
model UserRole {
|
|
id String @id @default(cuid())
|
|
userId String @map("user_id")
|
|
roleId String @map("role_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, roleId])
|
|
@@map("user_roles")
|
|
}
|
|
|
|
model EntraGroupRoleMapping {
|
|
id String @id @default(cuid())
|
|
entraGroupId String @map("entra_group_id")
|
|
roleId String @map("role_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([entraGroupId, roleId])
|
|
@@map("entra_group_role_mappings")
|
|
}
|
|
|
|
// ============================================
|
|
// Client & Policy Management
|
|
// ============================================
|
|
|
|
model Designation {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
description String?
|
|
color String
|
|
rules String?
|
|
displayOrder Int @map("display_order")
|
|
isActive Boolean @default(true) @map("is_active")
|
|
afwAnotId String? @unique @map("afw_anot_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
clientsDesignation1 Client[] @relation("ClientDesignation1")
|
|
clientsDesignation2 Client[] @relation("ClientDesignation2")
|
|
taskTemplates TaskTemplate[]
|
|
|
|
@@map("designations")
|
|
}
|
|
|
|
model Client {
|
|
id String @id @default(cuid())
|
|
amsCustomerId String @unique @map("ams_customer_id")
|
|
/// AFW's short numeric customer/account number (AFW_Customer.CustNo) — distinct from
|
|
/// amsCustomerId (AFW's internal GUID). This is what ImageRight's FileNumberPart1 expects.
|
|
amsCustomerNumber Int? @map("ams_customer_number")
|
|
name String
|
|
addressLine1 String? @map("address_line1")
|
|
addressLine2 String? @map("address_line2")
|
|
city String?
|
|
state String?
|
|
zipCode String? @map("zip_code")
|
|
phone String?
|
|
email String?
|
|
producerCode String? @map("producer_code")
|
|
amsCreatedAt DateTime? @map("ams_created_at")
|
|
amsModifiedAt DateTime? @map("ams_modified_at")
|
|
designationId String? @map("designation_id")
|
|
designation2Id String? @map("designation2_id")
|
|
claimsAdvocateId String? @map("claims_advocate_id")
|
|
parentClientId String? @map("parent_client_id")
|
|
notes String? @db.Text
|
|
customFields Json @default("{}") @map("custom_fields")
|
|
renewalDate DateTime? @map("renewal_date")
|
|
setupCompletedAt DateTime? @map("setup_completed_at")
|
|
lastSyncedAt DateTime? @map("last_synced_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
designation Designation? @relation("ClientDesignation1", fields: [designationId], references: [id])
|
|
designation2 Designation? @relation("ClientDesignation2", fields: [designation2Id], references: [id])
|
|
claimsAdvocate User? @relation("ClientAdvocate", fields: [claimsAdvocateId], references: [id])
|
|
parentClient Client? @relation("ClientParent", fields: [parentClientId], references: [id])
|
|
subsidiaries Client[] @relation("ClientParent")
|
|
policies Policy[]
|
|
tasks Task[]
|
|
policyGroups PolicyGroup[]
|
|
members ClientMember[]
|
|
contacts ClientContact[]
|
|
taskAudits TaskAudit[]
|
|
|
|
@@index([name])
|
|
@@index([designationId])
|
|
@@index([designation2Id])
|
|
@@index([claimsAdvocateId])
|
|
@@index([parentClientId])
|
|
@@map("clients")
|
|
}
|
|
|
|
model ClientContact {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
label String
|
|
name String
|
|
title String?
|
|
phone String?
|
|
mobilePhone String? @map("mobile_phone")
|
|
email String?
|
|
notes String? @db.Text
|
|
source String @default("manual")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clientId, name, label, source], name: "client_contact_dedup")
|
|
@@index([clientId])
|
|
@@map("client_contacts")
|
|
}
|
|
|
|
model ClientMember {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
userId String @map("user_id")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([clientId, userId])
|
|
@@index([clientId])
|
|
@@index([userId])
|
|
@@map("client_members")
|
|
}
|
|
|
|
model PolicyGroup {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
name String
|
|
renewalDate DateTime @map("renewal_date")
|
|
notes String? @db.Text
|
|
createdBy String? @map("created_by")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
creator User? @relation(fields: [createdBy], references: [id])
|
|
policies Policy[]
|
|
tasks Task[]
|
|
|
|
@@index([clientId])
|
|
@@index([renewalDate])
|
|
@@map("policy_groups")
|
|
}
|
|
|
|
model Policy {
|
|
id String @id @default(cuid())
|
|
amsPolicyId String @unique @map("ams_policy_id")
|
|
clientId String @map("client_id")
|
|
policyGroupId String? @map("policy_group_id")
|
|
policyNumber String? @map("policy_number")
|
|
policyType String? @map("policy_type")
|
|
effectiveDate DateTime? @map("effective_date")
|
|
expirationDate DateTime @map("expiration_date")
|
|
carrierName String? @map("carrier_name")
|
|
writingCompanyName String? @map("writing_company_name")
|
|
premiumAmount Decimal? @map("premium_amount") @db.Decimal(12, 2)
|
|
status String?
|
|
billMethod String? @map("bill_method")
|
|
businessType String? @map("business_type")
|
|
department String?
|
|
executiveName String? @map("executive_name")
|
|
csrName String? @map("csr_name")
|
|
additionalRep1 String? @map("additional_rep1")
|
|
additionalRep2 String? @map("additional_rep2")
|
|
additionalExec1 String? @map("additional_exec1")
|
|
additionalExec2 String? @map("additional_exec2")
|
|
amsCreatedAt DateTime? @map("ams_created_at")
|
|
amsModifiedAt DateTime? @map("ams_modified_at")
|
|
lastSyncedAt DateTime? @map("last_synced_at")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
policyGroup PolicyGroup? @relation(fields: [policyGroupId], references: [id])
|
|
tasks Task[]
|
|
|
|
@@index([clientId])
|
|
@@index([policyGroupId])
|
|
@@index([expirationDate])
|
|
@@index([department])
|
|
@@map("policies")
|
|
}
|
|
|
|
// ============================================
|
|
// Task Management
|
|
// ============================================
|
|
|
|
enum TaskStatus {
|
|
NOT_STARTED
|
|
IN_PROGRESS
|
|
COMPLETED
|
|
BLOCKED
|
|
NA
|
|
CANCELLED
|
|
}
|
|
|
|
enum TaskPriority {
|
|
LOW
|
|
MEDIUM
|
|
HIGH
|
|
URGENT
|
|
}
|
|
|
|
enum TaskTiming {
|
|
PRE_RENEWAL
|
|
POST_RENEWAL
|
|
}
|
|
|
|
enum TaskLevel {
|
|
POLICY
|
|
RENEWAL_GROUP
|
|
BOTH
|
|
CLIENT
|
|
}
|
|
|
|
enum DepartmentType {
|
|
PERSONAL_LINES
|
|
COMMERCIAL_LINES
|
|
CLAIMS
|
|
BENEFITS
|
|
OTHER
|
|
}
|
|
|
|
/// Two-pass audit classification, per imageright_claim_review_audit_methodology.md.
|
|
/// Separates substantive filing compliance from filename/naming-convention compliance.
|
|
enum TaskAuditClassification {
|
|
PASS
|
|
PASS_WITH_NAMING_EXCEPTION
|
|
MANUAL_REVIEW
|
|
FAIL_MISSING
|
|
FAIL_LATE
|
|
FAIL_WRONG_ARTIFACT
|
|
NOT_APPLICABLE
|
|
}
|
|
|
|
model TaskTemplate {
|
|
id String @id @default(cuid())
|
|
name String
|
|
description String? @db.Text
|
|
department DepartmentType
|
|
timing TaskTiming
|
|
daysOffset Int @map("days_offset")
|
|
defaultPriority TaskPriority @map("default_priority")
|
|
taskGroup String? @map("task_group")
|
|
isActive Boolean @default(true) @map("is_active")
|
|
displayOrder Int? @map("display_order")
|
|
level TaskLevel @default(BOTH)
|
|
designationId String? @map("designation_id")
|
|
policyTypeFilter String? @map("policy_type_filter")
|
|
createdBy String? @map("created_by")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
designation Designation? @relation(fields: [designationId], references: [id])
|
|
creator User? @relation(fields: [createdBy], references: [id])
|
|
tasks Task[]
|
|
|
|
@@index([designationId])
|
|
@@map("task_templates")
|
|
}
|
|
|
|
model Task {
|
|
id String @id @default(cuid())
|
|
title String
|
|
description String? @db.Text
|
|
department DepartmentType
|
|
timing TaskTiming
|
|
daysOffset Int @map("days_offset")
|
|
dueDate DateTime @map("due_date")
|
|
status TaskStatus @default(NOT_STARTED)
|
|
priority TaskPriority
|
|
clientId String @map("client_id")
|
|
policyId String? @map("policy_id")
|
|
policyGroupId String? @map("policy_group_id")
|
|
templateId String? @map("template_id")
|
|
createdBy String? @map("created_by")
|
|
completedAt DateTime? @map("completed_at")
|
|
completedBy String? @map("completed_by")
|
|
notes String? @db.Text
|
|
isAdHoc Boolean @default(false) @map("is_ad_hoc")
|
|
naReason String? @map("na_reason") @db.Text
|
|
cancelledReason String? @map("cancelled_reason") @db.Text
|
|
taskGroup String? @map("task_group")
|
|
imageRightFiled Boolean? @map("image_right_filed")
|
|
reminderDate DateTime? @map("reminder_date")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
policy Policy? @relation(fields: [policyId], references: [id])
|
|
policyGroup PolicyGroup? @relation(fields: [policyGroupId], references: [id])
|
|
template TaskTemplate? @relation(fields: [templateId], references: [id])
|
|
creator User? @relation("TaskCreatedBy", fields: [createdBy], references: [id])
|
|
completer User? @relation("TaskCompletedBy", fields: [completedBy], references: [id])
|
|
assignments TaskAssignment[]
|
|
taskNotes TaskNote[]
|
|
taskAudits TaskAudit[]
|
|
|
|
@@index([clientId])
|
|
@@index([policyId])
|
|
@@index([policyGroupId])
|
|
@@index([status])
|
|
@@index([dueDate])
|
|
@@index([department])
|
|
@@map("tasks")
|
|
}
|
|
|
|
model TaskAssignment {
|
|
id String @id @default(cuid())
|
|
taskId String @map("task_id")
|
|
userId String @map("user_id")
|
|
assignedAt DateTime @default(now()) @map("assigned_at")
|
|
|
|
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([taskId, userId])
|
|
@@map("task_assignments")
|
|
}
|
|
|
|
/// Result of checking one SHAPE_IR_Filing_Audit_Spec.md checklist item against
|
|
/// ImageRight for a client. Each audit run inserts new rows (history preserved);
|
|
/// the latest row per (clientId, specItemKey) is the current status. Optionally
|
|
/// linked to an existing generated Task, but not required — a spec item with no
|
|
/// matching Task is still auditable and surfaces as a gap.
|
|
model TaskAudit {
|
|
id String @id @default(cuid())
|
|
clientId String @map("client_id")
|
|
taskId String? @map("task_id")
|
|
specItemKey String @map("spec_item_key")
|
|
targetDate DateTime @map("target_date")
|
|
classification TaskAuditClassification
|
|
/// 0-100 metadata confidence score (audit-scoring.ts); null when short-circuited (e.g. no file found).
|
|
confidenceScore Int? @map("confidence_score")
|
|
/// "high" | "medium" | "low" | "none" — bucketed from confidenceScore.
|
|
evidenceConfidence String? @map("evidence_confidence")
|
|
/// Did the matched artifact's own filename/type match the spec's expected pattern exactly?
|
|
filenamePatternCompliant Boolean? @map("filename_pattern_compliant")
|
|
/// True when classification is PASS_WITH_NAMING_EXCEPTION.
|
|
namingException Boolean? @map("naming_exception")
|
|
manualReviewRequired Boolean? @map("manual_review_required")
|
|
matchedDocId String? @map("matched_doc_id")
|
|
matchedDocName String? @map("matched_doc_name")
|
|
matchedDocDate DateTime? @map("matched_doc_date")
|
|
/// Subject of the .msg, when the matched artifact (or its container) was an email.
|
|
msgSubject String? @map("msg_subject")
|
|
msgSentDate DateTime? @map("msg_sent_date")
|
|
/// Filenames of attachments found inside a matched .msg container.
|
|
attachmentFilenames String[] @map("attachment_filenames")
|
|
/// Human-readable summary of what content evidence was found and why (for the audit report).
|
|
contentEvidenceSummary String? @map("content_evidence_summary") @db.Text
|
|
/// Ordered list of {signal, score, detail} scoring reasons, for the audit trail.
|
|
matchedSignals Json? @map("matched_signals")
|
|
folderChecked String? @map("folder_checked")
|
|
errorMessage String? @map("error_message") @db.Text
|
|
runBy String? @map("run_by")
|
|
runAt DateTime @default(now()) @map("run_at")
|
|
|
|
client Client @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
|
task Task? @relation(fields: [taskId], references: [id], onDelete: SetNull)
|
|
runByUser User? @relation(fields: [runBy], references: [id])
|
|
|
|
@@index([clientId])
|
|
@@index([specItemKey])
|
|
@@index([clientId, specItemKey, runAt])
|
|
@@map("task_audits")
|
|
}
|
|
|
|
model TaskNote {
|
|
id String @id @default(cuid())
|
|
taskId String @map("task_id")
|
|
userId String @map("user_id")
|
|
content String @db.Text
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
|
user User @relation("TaskNoteAuthor", fields: [userId], references: [id])
|
|
|
|
@@index([taskId])
|
|
@@map("task_notes")
|
|
}
|
|
|
|
// ============================================
|
|
// Sync & System Management
|
|
// ============================================
|
|
|
|
model SyncLog {
|
|
id String @id @default(cuid())
|
|
syncType String @map("sync_type")
|
|
startedAt DateTime @default(now()) @map("started_at")
|
|
completedAt DateTime? @map("completed_at")
|
|
status String
|
|
rowsProcessed Int @default(0) @map("rows_processed")
|
|
rowsInserted Int @default(0) @map("rows_inserted")
|
|
rowsUpdated Int @default(0) @map("rows_updated")
|
|
errorMessage String? @map("error_message") @db.Text
|
|
triggeredBy String? @map("triggered_by")
|
|
|
|
user User? @relation(fields: [triggeredBy], references: [id])
|
|
|
|
@@index([syncType])
|
|
@@index([startedAt])
|
|
@@map("sync_logs")
|
|
}
|
|
|
|
model SyncConfig {
|
|
id String @id @default(cuid())
|
|
key String @unique
|
|
value String @db.Text
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
@@map("sync_config")
|
|
}
|
|
|
|
model AppSetting {
|
|
key String @id
|
|
value String @db.Text
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
@@map("app_settings")
|
|
}
|
|
|
|
model AuditLog {
|
|
id String @id @default(cuid())
|
|
userId String? @map("user_id")
|
|
action String
|
|
entityType String @map("entity_type")
|
|
entityId String? @map("entity_id")
|
|
oldValues Json? @map("old_values")
|
|
newValues Json? @map("new_values")
|
|
ipAddress String? @map("ip_address")
|
|
userAgent String? @map("user_agent") @db.Text
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User? @relation(fields: [userId], references: [id])
|
|
|
|
@@index([userId])
|
|
@@index([entityType])
|
|
@@index([createdAt])
|
|
@@map("audit_logs")
|
|
}
|
|
|
|
// ============================================
|
|
// Notifications
|
|
// ============================================
|
|
|
|
model Notification {
|
|
id String @id @default(cuid())
|
|
userId String @map("user_id")
|
|
type String
|
|
title String
|
|
message String @db.Text
|
|
relatedEntityType String? @map("related_entity_type")
|
|
relatedEntityId String? @map("related_entity_id")
|
|
isRead Boolean @default(false) @map("is_read")
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, isRead])
|
|
@@index([createdAt])
|
|
@@map("notifications")
|
|
}
|
|
|
|
model ShapeImportRun {
|
|
id String @id @default(cuid())
|
|
startedAt DateTime @default(now()) @map("started_at")
|
|
completedAt DateTime? @map("completed_at")
|
|
status String // pending | running | completed | failed
|
|
dryRun Boolean @default(true) @map("dry_run")
|
|
driveId String @map("drive_id")
|
|
folderPath String @default("Claims/SHAPE Accounts") @map("folder_path")
|
|
triggeredBy String? @map("triggered_by")
|
|
stats Json?
|
|
|
|
user User? @relation(fields: [triggeredBy], references: [id])
|
|
logLines ShapeImportLog[]
|
|
|
|
@@index([startedAt])
|
|
@@map("shape_import_runs")
|
|
}
|
|
|
|
model ShapeImportLog {
|
|
id String @id @default(cuid())
|
|
runId String @map("run_id")
|
|
seq Int
|
|
line String @db.Text
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
|
|
run ShapeImportRun @relation(fields: [runId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([runId, seq])
|
|
@@map("shape_import_logs")
|
|
}
|
|
|
|
model NotificationPreference {
|
|
id String @id @default(cuid())
|
|
userId String @map("user_id")
|
|
notificationType String @map("notification_type")
|
|
inApp Boolean @default(true) @map("in_app")
|
|
email Boolean @default(false)
|
|
teams Boolean @default(false)
|
|
createdAt DateTime @default(now()) @map("created_at")
|
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, notificationType])
|
|
@@map("notification_preferences")
|
|
}
|