feature: Schema, Migrations, Core CRUD, and Redis Threshold Cache
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
public class AlertThreshold
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string ObservationCode { get; set; } = null!;
|
||||
public string DisplayName { get; set; } = null!;
|
||||
public string Unit { get; set; } = null!;
|
||||
public decimal? CriticalLow { get; set; }
|
||||
public decimal? WarningLow { get; set; }
|
||||
public decimal? WarningHigh { get; set; }
|
||||
public decimal? CriticalHigh { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
public class ClinicalAlert
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EncounterId { get; set; }
|
||||
public Guid PatientId { get; set; }
|
||||
public Guid? ObservationId { get; set; }
|
||||
public AlertType AlertType { get; set; }
|
||||
public AlertSeverity Severity { get; set; }
|
||||
public string Details { get; set; } = null!;
|
||||
public AlertStatus Status { get; set; } = AlertStatus.Open;
|
||||
public DateTimeOffset? AcknowledgedAt { get; set; }
|
||||
public string? AcknowledgedBy { get; set; }
|
||||
public DateTimeOffset? ResolvedAt { get; set; }
|
||||
public DateTimeOffset TriggeredAt { get; set; }
|
||||
|
||||
public Encounter Encounter { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
public class Encounter
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid PatientId { get; set; }
|
||||
public EncounterType EncounterType { get; set; }
|
||||
public EncounterStatus Status { get; set; }
|
||||
public string Department { get; set; } = null!;
|
||||
public string AttendingPhysician { get; set; } = null!;
|
||||
public DateTimeOffset AdmittedAt { get; set; }
|
||||
public DateTimeOffset? DischargedAt { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
public Patient Patient { get; set; } = null!;
|
||||
public ICollection<Observation> Observations { get; set; } = new List<Observation>();
|
||||
public ICollection<ClinicalAlert> Alerts { get; set; } = new List<ClinicalAlert>();
|
||||
public ICollection<Order> Orders { get; set; } = new List<Order>();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
public class Observation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EncounterId { get; set; }
|
||||
public string ObservationCode { get; set; } = null!;
|
||||
public decimal Value { get; set; }
|
||||
public string Unit { get; set; } = null!;
|
||||
public ObservationSource Source { get; set; } = ObservationSource.Manual;
|
||||
public string? IdempotencyKey { get; set; }
|
||||
public DateTimeOffset RecordedAt { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
public Encounter Encounter { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
public class Order
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid EncounterId { get; set; }
|
||||
public OrderType OrderType { get; set; }
|
||||
public string Description { get; set; } = null!;
|
||||
public string OrderedBy { get; set; } = null!;
|
||||
public string Status { get; set; } = "pending";
|
||||
public DateTimeOffset OrderedAt { get; set; }
|
||||
public DateTimeOffset? ResultedAt { get; set; }
|
||||
|
||||
public Encounter Encounter { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
public class OutboxEvent
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Topic { get; set; } = null!;
|
||||
public string Payload { get; set; } = null!; // JSON string
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset? ProcessedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
public class Patient
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Mrn { get; set; } = null!;
|
||||
public string FirstName { get; set; } = null!;
|
||||
public string LastName { get; set; } = null!;
|
||||
public DateOnly DateOfBirth { get; set; }
|
||||
public string Gender { get; set; } = null!;
|
||||
public string Status { get; set; } = "active";
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
public ICollection<Encounter> Encounters { get; set; } = new List<Encounter>();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class ReconciliationAlert
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public ReconciliationCheckType CheckType { get; set; }
|
||||
public Guid? EncounterId { get; set; }
|
||||
public Guid? PatientId { get; set; }
|
||||
public string Details { get; set; } = null!;
|
||||
public DateTimeOffset? ResolvedAt { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
public enum AlertSeverity { Warning, Critical }
|
||||
|
||||
public static class AlertSeverityExtensions
|
||||
{
|
||||
public static string ToDbString(this AlertSeverity s) => s switch
|
||||
{
|
||||
AlertSeverity.Warning => "WARNING",
|
||||
AlertSeverity.Critical => "CRITICAL",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(s))
|
||||
};
|
||||
|
||||
public static AlertSeverity FromDbString(string v) => v switch
|
||||
{
|
||||
"WARNING" => AlertSeverity.Warning,
|
||||
"CRITICAL" => AlertSeverity.Critical,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown alert severity: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
public enum AlertStatus { Open, Acknowledged, Resolved, Escalated }
|
||||
|
||||
public static class AlertStatusExtensions
|
||||
{
|
||||
public static string ToDbString(this AlertStatus s) => s switch
|
||||
{
|
||||
AlertStatus.Open => "OPEN",
|
||||
AlertStatus.Acknowledged => "ACKNOWLEDGED",
|
||||
AlertStatus.Resolved => "RESOLVED",
|
||||
AlertStatus.Escalated => "ESCALATED",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(s))
|
||||
};
|
||||
|
||||
public static AlertStatus FromDbString(string v) => v switch
|
||||
{
|
||||
"OPEN" => AlertStatus.Open,
|
||||
"ACKNOWLEDGED" => AlertStatus.Acknowledged,
|
||||
"RESOLVED" => AlertStatus.Resolved,
|
||||
"ESCALATED" => AlertStatus.Escalated,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown alert status: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
public enum AlertType
|
||||
{
|
||||
SepsisWarning,
|
||||
CriticalHeartRate,
|
||||
CriticalTempC,
|
||||
CriticalPotassiumMeqL,
|
||||
CriticalSpo2,
|
||||
CriticalRespRate,
|
||||
CriticalWbcKUl
|
||||
}
|
||||
|
||||
public static class AlertTypeExtensions
|
||||
{
|
||||
public static string ToDbString(this AlertType t) => t switch
|
||||
{
|
||||
AlertType.SepsisWarning => "SEPSIS_WARNING",
|
||||
AlertType.CriticalHeartRate => "CRITICAL_HEART_RATE",
|
||||
AlertType.CriticalTempC => "CRITICAL_TEMP_C",
|
||||
AlertType.CriticalPotassiumMeqL => "CRITICAL_POTASSIUM_MEQ_L",
|
||||
AlertType.CriticalSpo2 => "CRITICAL_SPO2",
|
||||
AlertType.CriticalRespRate => "CRITICAL_RESP_RATE",
|
||||
AlertType.CriticalWbcKUl => "CRITICAL_WBC_K_UL",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(t))
|
||||
};
|
||||
|
||||
public static AlertType FromDbString(string v) => v switch
|
||||
{
|
||||
"SEPSIS_WARNING" => AlertType.SepsisWarning,
|
||||
"CRITICAL_HEART_RATE" => AlertType.CriticalHeartRate,
|
||||
"CRITICAL_TEMP_C" => AlertType.CriticalTempC,
|
||||
"CRITICAL_POTASSIUM_MEQ_L"=> AlertType.CriticalPotassiumMeqL,
|
||||
"CRITICAL_SPO2" => AlertType.CriticalSpo2,
|
||||
"CRITICAL_RESP_RATE" => AlertType.CriticalRespRate,
|
||||
"CRITICAL_WBC_K_UL" => AlertType.CriticalWbcKUl,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown alert type: '{v}'")
|
||||
};
|
||||
|
||||
// Threshold alerts are derived from observation codes in alert_thresholds — not free-form strings.
|
||||
public static AlertType CriticalFor(string observationCode) => observationCode switch
|
||||
{
|
||||
"HEART_RATE" => AlertType.CriticalHeartRate,
|
||||
"TEMP_C" => AlertType.CriticalTempC,
|
||||
"POTASSIUM_MEQ_L" => AlertType.CriticalPotassiumMeqL,
|
||||
"SPO2" => AlertType.CriticalSpo2,
|
||||
"RESP_RATE" => AlertType.CriticalRespRate,
|
||||
"WBC_K_UL" => AlertType.CriticalWbcKUl,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(observationCode), $"No critical alert type for observation code '{observationCode}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
public enum EncounterStatus { Scheduled, Active, Discharged, Cancelled }
|
||||
|
||||
public static class EncounterStatusExtensions
|
||||
{
|
||||
public static string ToDbString(this EncounterStatus s) => s switch
|
||||
{
|
||||
EncounterStatus.Scheduled => "SCHEDULED",
|
||||
EncounterStatus.Active => "ACTIVE",
|
||||
EncounterStatus.Discharged => "DISCHARGED",
|
||||
EncounterStatus.Cancelled => "CANCELLED",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(s))
|
||||
};
|
||||
|
||||
public static EncounterStatus FromDbString(string v) => v switch
|
||||
{
|
||||
"SCHEDULED" => EncounterStatus.Scheduled,
|
||||
"ACTIVE" => EncounterStatus.Active,
|
||||
"DISCHARGED" => EncounterStatus.Discharged,
|
||||
"CANCELLED" => EncounterStatus.Cancelled,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown encounter status: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
public enum EncounterType { Inpatient, Outpatient, Emergency }
|
||||
|
||||
public static class EncounterTypeExtensions
|
||||
{
|
||||
public static string ToDbString(this EncounterType t) => t switch
|
||||
{
|
||||
EncounterType.Inpatient => "INPATIENT",
|
||||
EncounterType.Outpatient => "OUTPATIENT",
|
||||
EncounterType.Emergency => "EMERGENCY",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(t))
|
||||
};
|
||||
|
||||
public static EncounterType FromDbString(string v) => v switch
|
||||
{
|
||||
"INPATIENT" => EncounterType.Inpatient,
|
||||
"OUTPATIENT" => EncounterType.Outpatient,
|
||||
"EMERGENCY" => EncounterType.Emergency,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown encounter type: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
public enum ObservationSource { Manual, Device, Lab }
|
||||
|
||||
public static class ObservationSourceExtensions
|
||||
{
|
||||
public static string ToDbString(this ObservationSource s) => s switch
|
||||
{
|
||||
ObservationSource.Manual => "MANUAL",
|
||||
ObservationSource.Device => "DEVICE",
|
||||
ObservationSource.Lab => "LAB",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(s))
|
||||
};
|
||||
|
||||
public static ObservationSource FromDbString(string v) => v switch
|
||||
{
|
||||
"MANUAL" => ObservationSource.Manual,
|
||||
"DEVICE" => ObservationSource.Device,
|
||||
"LAB" => ObservationSource.Lab,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown observation source: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
public enum OrderType { Lab, Imaging, Medication, Procedure }
|
||||
|
||||
public static class OrderTypeExtensions
|
||||
{
|
||||
public static string ToDbString(this OrderType t) => t switch
|
||||
{
|
||||
OrderType.Lab => "LAB",
|
||||
OrderType.Imaging => "IMAGING",
|
||||
OrderType.Medication => "MEDICATION",
|
||||
OrderType.Procedure => "PROCEDURE",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(t))
|
||||
};
|
||||
|
||||
public static OrderType FromDbString(string v) => v switch
|
||||
{
|
||||
"LAB" => OrderType.Lab,
|
||||
"IMAGING" => OrderType.Imaging,
|
||||
"MEDICATION" => OrderType.Medication,
|
||||
"PROCEDURE" => OrderType.Procedure,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown order type: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
public enum ReconciliationCheckType
|
||||
{
|
||||
UnacknowledgedCriticalAlert,
|
||||
PendingOrderNoResult,
|
||||
ActiveInpatientNoObservation
|
||||
}
|
||||
|
||||
public static class ReconciliationCheckTypeExtensions
|
||||
{
|
||||
public static string ToDbString(this ReconciliationCheckType t) => t switch
|
||||
{
|
||||
ReconciliationCheckType.UnacknowledgedCriticalAlert => "UNACKNOWLEDGED_CRITICAL_ALERT",
|
||||
ReconciliationCheckType.PendingOrderNoResult => "PENDING_ORDER_NO_RESULT",
|
||||
ReconciliationCheckType.ActiveInpatientNoObservation => "ACTIVE_INPATIENT_NO_OBSERVATION",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(t))
|
||||
};
|
||||
|
||||
public static ReconciliationCheckType FromDbString(string v) => v switch
|
||||
{
|
||||
"UNACKNOWLEDGED_CRITICAL_ALERT" => ReconciliationCheckType.UnacknowledgedCriticalAlert,
|
||||
"PENDING_ORDER_NO_RESULT" => ReconciliationCheckType.PendingOrderNoResult,
|
||||
"ACTIVE_INPATIENT_NO_OBSERVATION" => ReconciliationCheckType.ActiveInpatientNoObservation,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(v), $"Unknown reconciliation check type: '{v}'")
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public sealed class ObservationSourceJsonConverter : JsonConverter<ObservationSource>
|
||||
{
|
||||
public override ObservationSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> ObservationSourceExtensions.FromDbString(reader.GetString()!);
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ObservationSource value, JsonSerializerOptions options)
|
||||
=> writer.WriteStringValue(value.ToDbString());
|
||||
}
|
||||
Reference in New Issue
Block a user