Files

344 lines
19 KiB
C#

using System.Text.Json;
using Microsoft.EntityFrameworkCore;
public static class DataSeeder
{
// Deterministic IDs for referencing seeded entities in tests and scripts
private static readonly Guid IntakeClerk1Id = Guid.Parse("a1000000-0000-0000-0000-000000000001");
private static readonly Guid IntakeClerk2Id = Guid.Parse("a1000000-0000-0000-0000-000000000002");
private static readonly Guid EntryClerk1Id = Guid.Parse("a2000000-0000-0000-0000-000000000001");
private static readonly Guid EntryClerk2Id = Guid.Parse("a2000000-0000-0000-0000-000000000002");
private static readonly Guid Verifier1Id = Guid.Parse("a3000000-0000-0000-0000-000000000001");
private static readonly Guid Verifier2Id = Guid.Parse("a3000000-0000-0000-0000-000000000002");
private static readonly Guid Approver1Id = Guid.Parse("a4000000-0000-0000-0000-000000000001");
private static readonly Guid Approver2Id = Guid.Parse("a4000000-0000-0000-0000-000000000002");
private static readonly Guid Clinician1Id = Guid.Parse("a5000000-0000-0000-0000-000000000001");
private static readonly Guid Clinician2Id = Guid.Parse("a5000000-0000-0000-0000-000000000002");
private static readonly Guid Admin1Id = Guid.Parse("a6000000-0000-0000-0000-000000000001");
private static readonly Guid Admin2Id = Guid.Parse("a6000000-0000-0000-0000-000000000002");
// Patient IDs
private static readonly Guid Patient1Id = Guid.Parse("b1000000-0000-0000-0000-000000000001");
private static readonly Guid Patient2Id = Guid.Parse("b1000000-0000-0000-0000-000000000002");
private static readonly Guid Patient3Id = Guid.Parse("b1000000-0000-0000-0000-000000000003");
private static readonly Guid Patient4Id = Guid.Parse("b1000000-0000-0000-0000-000000000004");
private static readonly Guid Patient5Id = Guid.Parse("b1000000-0000-0000-0000-000000000005");
public static async Task SeedAsync(AppDbContext db)
{
if (await db.Users.AnyAsync()) return;
// ── Users (2 per role) ────────────────────────────────────────────────
var users = new[]
{
CreateUser(IntakeClerk1Id, "intake1", "Intake Clerk 1", UserRole.IntakeClerk),
CreateUser(IntakeClerk2Id, "intake2", "Intake Clerk 2", UserRole.IntakeClerk),
CreateUser(EntryClerk1Id, "entry1", "Entry Clerk 1", UserRole.DataEntryClerk),
CreateUser(EntryClerk2Id, "entry2", "Entry Clerk 2", UserRole.DataEntryClerk),
CreateUser(Verifier1Id, "verifier1", "Verifier 1", UserRole.Verifier),
CreateUser(Verifier2Id, "verifier2", "Verifier 2", UserRole.Verifier),
CreateUser(Approver1Id, "approver1", "Clinical Approver 1", UserRole.ClinicalApprover),
CreateUser(Approver2Id, "approver2", "Clinical Approver 2", UserRole.ClinicalApprover),
CreateUser(Clinician1Id, "clinician1", "Dr. Tanaka", UserRole.Clinician),
CreateUser(Clinician2Id, "clinician2", "Dr. Chen", UserRole.Clinician),
CreateUser(Admin1Id, "admin1", "Administrator 1", UserRole.Administrator),
CreateUser(Admin2Id, "admin2", "Administrator 2", UserRole.Administrator),
};
db.Users.AddRange(users);
// ── Batches across all types, tracks, and statuses ────────────────────
var now = DateTimeOffset.UtcNow;
// Batch 1: VITALS_SHEET / BACKFILL / PROMOTED (full lifecycle complete)
var batch1Id = Guid.Parse("c1000000-0000-0000-0000-000000000001");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch1Id,
Status = BatchStatus.Promoted,
BatchType = BatchType.VitalsSheet,
Track = BatchTrack.Backfill,
PatientId = Patient1Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000001/abc123.pdf",
DocumentSha256 = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
EnableRetroactiveAlerts = false,
EnteredByUserId = EntryClerk1Id,
VerifiedByUserId = Verifier1Id,
ApprovedByUserId = Approver1Id,
PromotedAt = now.AddHours(-12),
CreatedAt = now.AddDays(-3),
UpdatedAt = now.AddHours(-12),
});
db.DraftPatients.Add(new DraftPatient
{
Id = Guid.NewGuid(), BatchId = batch1Id,
FullName = "Maria Santos", DateOfBirth = new DateOnly(1978, 3, 15),
Sex = "female", BloodType = BloodType.APos,
EmergencyContact = "Juan Santos - 555-0101",
NoKnownAllergies = false,
AllergiesJson = "[\"Penicillin\", \"Sulfa drugs\"]",
CreatedAt = now.AddDays(-3), UpdatedAt = now.AddDays(-2),
});
db.DraftEncounters.Add(new DraftEncounter
{
Id = Guid.NewGuid(), BatchId = batch1Id,
AdmissionDate = now.AddDays(-5),
Department = Department.InternalMedicine, RoomBed = "2A-04",
AdmissionReason = "Pneumonia with elevated WBC",
Status = "active",
CreatedAt = now.AddDays(-3), UpdatedAt = now.AddDays(-2),
});
db.DraftObservations.AddRange(
CreateObservation(batch1Id, "HEART_RATE", 88m, "bpm", now.AddDays(-5)),
CreateObservation(batch1Id, "TEMP_C", 38.7m, "C", now.AddDays(-5)),
CreateObservation(batch1Id, "BP_SYSTOLIC", 128m, "mmHg", now.AddDays(-5)),
CreateObservation(batch1Id, "BP_DIASTOLIC", 82m, "mmHg", now.AddDays(-5)),
CreateObservation(batch1Id, "RESP_RATE", 22m, "breaths/min", now.AddDays(-5)),
CreateObservation(batch1Id, "SPO2", 94m, "%", now.AddDays(-5)),
CreateObservation(batch1Id, "WBC_K_UL", 14.2m, "K/uL", now.AddDays(-5))
);
// Batch 2: PATIENT_REGISTRATION / BACKFILL / PENDING_VERIFICATION
var batch2Id = Guid.Parse("c1000000-0000-0000-0000-000000000002");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch2Id,
Status = BatchStatus.PendingVerification,
BatchType = BatchType.PatientRegistration,
Track = BatchTrack.Backfill,
PatientId = Patient2Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000002/def456.pdf",
DocumentSha256 = "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3",
EnteredByUserId = EntryClerk2Id,
CreatedAt = now.AddDays(-1),
UpdatedAt = now.AddHours(-6),
});
db.DraftPatients.Add(new DraftPatient
{
Id = Guid.NewGuid(), BatchId = batch2Id,
FullName = "Kenji Nakamura", DateOfBirth = new DateOnly(1952, 11, 8),
Sex = "male", BloodType = BloodType.ONeg,
EmergencyContact = "Yuki Nakamura - 555-0202",
NoKnownAllergies = true,
CreatedAt = now.AddDays(-1), UpdatedAt = now.AddHours(-6),
});
// Batch 3: LAB_RESULTS / BACKFILL / REJECTED (awaiting re-entry)
var batch3Id = Guid.Parse("c1000000-0000-0000-0000-000000000003");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch3Id,
Status = BatchStatus.Rejected,
BatchType = BatchType.LabResults,
Track = BatchTrack.Backfill,
PatientId = Patient3Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000003/ghi789.pdf",
DocumentSha256 = "c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
EnteredByUserId = EntryClerk1Id,
VerifiedByUserId = Verifier2Id,
RejectionReason = "Potassium value unclear on scan - decimal position ambiguous (5.2 vs 52). Please re-examine source document.",
CreatedAt = now.AddDays(-2),
UpdatedAt = now.AddHours(-18),
});
db.DraftPatients.Add(new DraftPatient
{
Id = Guid.NewGuid(), BatchId = batch3Id,
FullName = "Leilani Tupou", DateOfBirth = new DateOnly(1990, 7, 22),
Sex = "female", BloodType = BloodType.BPos,
CreatedAt = now.AddDays(-2), UpdatedAt = now.AddHours(-20),
});
db.DraftObservations.AddRange(
CreateObservation(batch3Id, "POTASSIUM_MEQ_L", 52m, "mEq/L", now.AddDays(-4)),
CreateObservation(batch3Id, "GLUCOSE_MG_DL", 110m, "mg/dL", now.AddDays(-4)),
CreateObservation(batch3Id, "LACTATE_MMOL_L", 1.8m, "mmol/L", now.AddDays(-4))
);
// Batch 4: ENCOUNTER_SUMMARY / BACKFILL / IN_ENTRY
var batch4Id = Guid.Parse("c1000000-0000-0000-0000-000000000004");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch4Id,
Status = BatchStatus.InEntry,
BatchType = BatchType.EncounterSummary,
Track = BatchTrack.Backfill,
PatientId = Patient1Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000004/jkl012.pdf",
DocumentSha256 = "d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5",
EnteredByUserId = EntryClerk2Id,
CreatedAt = now.AddHours(-4),
UpdatedAt = now.AddHours(-2),
});
// Batch 5: ALLERGY_UPDATE / BACKFILL / AWAITING_CLINICAL_APPROVAL
var batch5Id = Guid.Parse("c1000000-0000-0000-0000-000000000005");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch5Id,
Status = BatchStatus.AwaitingClinicalApproval,
BatchType = BatchType.AllergyUpdate,
Track = BatchTrack.Backfill,
PatientId = Patient4Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000005/mno345.pdf",
DocumentSha256 = "e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6",
EnteredByUserId = EntryClerk1Id,
VerifiedByUserId = Verifier1Id,
CreatedAt = now.AddDays(-1),
UpdatedAt = now.AddHours(-3),
});
db.DraftPatients.Add(new DraftPatient
{
Id = Guid.NewGuid(), BatchId = batch5Id,
FullName = "Anh Tran", DateOfBirth = new DateOnly(1985, 1, 30),
Sex = "female",
AllergiesJson = "[\"Codeine\", \"Iodine contrast\", \"Latex\"]",
NoKnownAllergies = false,
CreatedAt = now.AddDays(-1), UpdatedAt = now.AddHours(-5),
});
// Batch 6: VITALS_SHEET / LIVE_CAPTURE / PROMOTED (Track B)
var batch6Id = Guid.Parse("c1000000-0000-0000-0000-000000000006");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch6Id,
Status = BatchStatus.Promoted,
BatchType = BatchType.VitalsSheet,
Track = BatchTrack.LiveCapture,
PatientId = Patient5Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000006/pqr678.pdf",
DocumentSha256 = "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1",
EnteredByUserId = Clinician1Id,
ClinicianAttestation = true,
PromotedAt = now.AddHours(-1),
CreatedAt = now.AddHours(-2),
UpdatedAt = now.AddHours(-1),
});
db.DraftObservations.AddRange(
CreateObservation(batch6Id, "HEART_RATE", 72m, "bpm", now.AddHours(-2)),
CreateObservation(batch6Id, "TEMP_C", 36.8m, "C", now.AddHours(-2)),
CreateObservation(batch6Id, "BP_SYSTOLIC", 120m, "mmHg", now.AddHours(-2)),
CreateObservation(batch6Id, "BP_DIASTOLIC", 78m, "mmHg", now.AddHours(-2)),
CreateObservation(batch6Id, "SPO2", 98m, "%", now.AddHours(-2))
);
// Batch 7: MEDICATION_LIST / BACKFILL / UPLOADED (fresh, unassigned)
var batch7Id = Guid.Parse("c1000000-0000-0000-0000-000000000007");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch7Id,
Status = BatchStatus.Uploaded,
BatchType = BatchType.MedicationList,
Track = BatchTrack.Backfill,
PatientId = Patient2Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000007/stu901.pdf",
DocumentSha256 = "a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8",
CreatedAt = now.AddMinutes(-30),
UpdatedAt = now.AddMinutes(-30),
});
// Batch 8: MIXED / BACKFILL / VERIFIED
var batch8Id = Guid.Parse("c1000000-0000-0000-0000-000000000008");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch8Id,
Status = BatchStatus.Verified,
BatchType = BatchType.Mixed,
Track = BatchTrack.Backfill,
PatientId = Patient3Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000008/vwx234.pdf",
DocumentSha256 = "b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9",
EnteredByUserId = EntryClerk2Id,
VerifiedByUserId = Verifier1Id,
CreatedAt = now.AddDays(-1),
UpdatedAt = now.AddHours(-8),
});
// Batch 9: VITALS_SHEET / BACKFILL / APPROVED (waiting for promotion — tests retry worker)
var batch9Id = Guid.Parse("c1000000-0000-0000-0000-000000000009");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch9Id,
Status = BatchStatus.Approved,
BatchType = BatchType.VitalsSheet,
Track = BatchTrack.Backfill,
PatientId = Patient4Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000009/yza567.pdf",
DocumentSha256 = "c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0",
EnteredByUserId = EntryClerk1Id,
VerifiedByUserId = Verifier2Id,
ApprovedByUserId = Approver1Id,
CreatedAt = now.AddDays(-1),
UpdatedAt = now.AddMinutes(-45),
});
db.DraftObservations.AddRange(
CreateObservation(batch9Id, "HEART_RATE", 92m, "bpm", now.AddDays(-3)),
CreateObservation(batch9Id, "TEMP_C", 37.2m, "C", now.AddDays(-3)),
CreateObservation(batch9Id, "RESP_RATE", 18m, "breaths/min", now.AddDays(-3)),
CreateObservation(batch9Id, "SPO2", 96m, "%", now.AddDays(-3))
);
// Batch 10: Correction batch (supersedes batch 1)
var batch10Id = Guid.Parse("c1000000-0000-0000-0000-000000000010");
db.DigitizationBatches.Add(new DigitizationBatch
{
Id = batch10Id,
Status = BatchStatus.PendingVerification,
BatchType = BatchType.VitalsSheet,
Track = BatchTrack.Backfill,
PatientId = Patient1Id,
SupersedesBatchId = batch1Id,
DocumentRef = "scans/2026/06/c1000000-0000-0000-0000-000000000010/correction.pdf",
DocumentSha256 = "d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1f2a7b8c9d0e1",
EnteredByUserId = EntryClerk2Id,
CreatedAt = now.AddHours(-2),
UpdatedAt = now.AddHours(-1),
});
db.DraftObservations.AddRange(
CreateObservation(batch10Id, "HEART_RATE", 88m, "bpm", now.AddDays(-5)),
CreateObservation(batch10Id, "TEMP_C", 38.7m, "C", now.AddDays(-5)),
CreateObservation(batch10Id, "SPO2", 95m, "%", now.AddDays(-5)) // corrected from 94
);
// ── Audit events for completed batches ─────────────────────────────────
db.DigitizationEvents.AddRange(
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch1Id, EventType = DigitizationEventType.Uploaded, ActorUserId = IntakeClerk1Id, OccurredAt = now.AddDays(-3) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch1Id, EventType = DigitizationEventType.EntryStarted, ActorUserId = EntryClerk1Id, OccurredAt = now.AddDays(-2).AddHours(-6) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch1Id, EventType = DigitizationEventType.SubmittedForVerification, ActorUserId = EntryClerk1Id, OccurredAt = now.AddDays(-2) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch1Id, EventType = DigitizationEventType.Verified, ActorUserId = Verifier1Id, OccurredAt = now.AddDays(-1) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch1Id, EventType = DigitizationEventType.Approved, ActorUserId = Approver1Id, OccurredAt = now.AddHours(-14) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch1Id, EventType = DigitizationEventType.Promoted, ActorUserId = Approver1Id, OccurredAt = now.AddHours(-12) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch3Id, EventType = DigitizationEventType.Uploaded, ActorUserId = IntakeClerk2Id, OccurredAt = now.AddDays(-2) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch3Id, EventType = DigitizationEventType.EntryStarted, ActorUserId = EntryClerk1Id, OccurredAt = now.AddDays(-2).AddHours(-3) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch3Id, EventType = DigitizationEventType.SubmittedForVerification, ActorUserId = EntryClerk1Id, OccurredAt = now.AddHours(-20) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch3Id, EventType = DigitizationEventType.Rejected, ActorUserId = Verifier2Id, OccurredAt = now.AddHours(-18),
MetadataJson = JsonSerializer.Serialize(new { rejectionReason = "Potassium value unclear on scan - decimal position ambiguous" }) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch6Id, EventType = DigitizationEventType.Uploaded, ActorUserId = Clinician1Id, OccurredAt = now.AddHours(-2) },
new DigitizationEvent { Id = Guid.NewGuid(), BatchId = batch6Id, EventType = DigitizationEventType.Promoted, ActorUserId = Clinician1Id, OccurredAt = now.AddHours(-1) }
);
await db.SaveChangesAsync();
}
private static User CreateUser(Guid id, string username, string fullName, UserRole role) => new()
{
Id = id,
Username = username,
PasswordHash = BCrypt.Net.BCrypt.HashPassword("password"),
FullName = fullName,
Role = role,
IsActive = true,
CreatedAt = DateTimeOffset.UtcNow
};
private static DraftObservation CreateObservation(
Guid batchId, string code, decimal value, string unit, DateTimeOffset recordedAt) => new()
{
Id = Guid.NewGuid(),
BatchId = batchId,
ObservationCode = code,
Value = value,
Unit = unit,
RecordedAt = recordedAt,
CreatedAt = DateTimeOffset.UtcNow,
};
}