Clinical Sync Batch Engine: first commit

This commit is contained in:
voltsrage
2026-06-23 03:02:30 +08:00
parent 90b8baa2a1
commit c9994b1ba2
60 changed files with 3547 additions and 31 deletions
@@ -63,6 +63,8 @@ public class ClinicalAlertConfiguration : IEntityTypeConfiguration<ClinicalAlert
builder.Property(a => a.AcknowledgedAt).HasColumnName("acknowledged_at");
builder.Property(a => a.AcknowledgedBy).HasColumnName("acknowledged_by").HasMaxLength(200);
builder.Property(a => a.ResolvedAt).HasColumnName("resolved_at");
builder.Property(a => a.ClientAlertId).HasColumnName("client_alert_id");
builder.Property(a => a.SyncedFromGateway).HasColumnName("synced_from_gateway").HasDefaultValue(false);
builder.Property(a => a.TriggeredAt).HasColumnName("triggered_at").HasDefaultValueSql("NOW()");
builder.HasOne(a => a.Encounter)
@@ -76,5 +78,8 @@ public class ClinicalAlertConfiguration : IEntityTypeConfiguration<ClinicalAlert
.HasFilter("status = 'OPEN'");
builder.HasIndex(a => new { a.EncounterId, a.AlertType, a.ObservationCode })
.HasFilter("status IN ('OPEN', 'ESCALATED')");
builder.HasIndex(a => a.ClientAlertId)
.IsUnique()
.HasFilter("client_alert_id IS NOT NULL");
}
}
@@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class ClinicalSiteConfiguration : IEntityTypeConfiguration<ClinicalSite>
{
public void Configure(EntityTypeBuilder<ClinicalSite> builder)
{
builder.ToTable("clinical_sites");
builder.HasKey(s => s.Id);
builder.Property(s => s.Id).HasColumnName("id").HasDefaultValueSql("gen_random_uuid()");
builder.Property(s => s.SiteCode).HasColumnName("site_code").HasMaxLength(32).IsRequired();
builder.Property(s => s.Name).HasColumnName("name").HasMaxLength(200).IsRequired();
builder.Property(s => s.Address).HasColumnName("address");
builder.Property(s => s.Active).HasColumnName("active").HasDefaultValue(true);
builder.Property(s => s.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("NOW()");
builder.HasIndex(s => s.SiteCode).IsUnique();
}
}
@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class ClinicalSyncBatchConfiguration : IEntityTypeConfiguration<ClinicalSyncBatch>
{
public void Configure(EntityTypeBuilder<ClinicalSyncBatch> builder)
{
builder.ToTable("clinical_sync_batches", t =>
{
t.HasCheckConstraint("chk_clinical_sync_batches_status",
"status IN ('RECEIVED','PROCESSING','APPLIED','CONFLICT','REJECTED')");
});
builder.HasKey(b => b.Id);
builder.Property(b => b.Id).HasColumnName("id").HasDefaultValueSql("gen_random_uuid()");
builder.Property(b => b.GatewayId).HasColumnName("gateway_id");
builder.Property(b => b.SiteId).HasColumnName("site_id");
builder.Property(b => b.BatchReference).HasColumnName("batch_reference");
builder.Property(b => b.Status)
.HasColumnName("status")
.HasMaxLength(16)
.HasConversion(v => v.ToDbString(), v => ClinicalSyncBatchStatusExtensions.FromDbString(v))
.HasDefaultValueSql("'RECEIVED'");
builder.Property(b => b.Payload).HasColumnName("payload").HasColumnType("jsonb");
builder.Property(b => b.SubmittedAt).HasColumnName("submitted_at").HasDefaultValueSql("NOW()");
builder.Property(b => b.ProcessedAt).HasColumnName("processed_at");
builder.HasOne(b => b.Gateway)
.WithMany()
.HasForeignKey(b => b.GatewayId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne<ClinicalSite>()
.WithMany()
.HasForeignKey(b => b.SiteId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(b => b.BatchReference).IsUnique();
builder.HasIndex(b => new { b.GatewayId, b.SubmittedAt });
}
}
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class ClinicalSyncConflictConfiguration : IEntityTypeConfiguration<ClinicalSyncConflict>
{
public void Configure(EntityTypeBuilder<ClinicalSyncConflict> builder)
{
builder.ToTable("clinical_sync_conflicts");
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).HasColumnName("id").HasDefaultValueSql("gen_random_uuid()");
builder.Property(c => c.BatchId).HasColumnName("batch_id");
builder.Property(c => c.ClientRef).HasColumnName("client_ref");
builder.Property(c => c.ItemType).HasColumnName("item_type").HasMaxLength(16);
builder.Property(c => c.ConflictReason).HasColumnName("conflict_reason").HasMaxLength(500);
builder.Property(c => c.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("NOW()");
builder.HasOne(c => c.Batch)
.WithMany(b => b.Conflicts)
.HasForeignKey(c => c.BatchId)
.OnDelete(DeleteBehavior.Restrict);
}
}
@@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class WardGatewayConfiguration : IEntityTypeConfiguration<WardGateway>
{
public void Configure(EntityTypeBuilder<WardGateway> builder)
{
builder.ToTable("ward_gateways", t =>
{
t.HasCheckConstraint("chk_ward_gateways_status",
"status IN ('ONLINE','DEGRADED','OFFLINE')");
});
builder.HasKey(g => g.Id);
builder.Property(g => g.Id).HasColumnName("id").HasDefaultValueSql("gen_random_uuid()");
builder.Property(g => g.SiteId).HasColumnName("site_id");
builder.Property(g => g.GatewayCode).HasColumnName("gateway_code").HasMaxLength(64).IsRequired();
builder.Property(g => g.Department).HasColumnName("department").HasMaxLength(100).IsRequired();
builder.Property(g => g.Status)
.HasColumnName("status")
.HasMaxLength(16)
.HasConversion(v => v.ToDbString(), v => GatewayStatusExtensions.FromDbString(v))
.HasDefaultValueSql("'OFFLINE'");
builder.Property(g => g.ReportedBufferDepth).HasColumnName("reported_buffer_depth").HasDefaultValue(0);
builder.Property(g => g.LastHeartbeatAt).HasColumnName("last_heartbeat_at");
builder.Property(g => g.LastSyncAt).HasColumnName("last_sync_at");
builder.Property(g => g.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("NOW()");
builder.HasOne(g => g.Site)
.WithMany(s => s.Gateways)
.HasForeignKey(g => g.SiteId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(g => new { g.SiteId, g.GatewayCode }).IsUnique();
builder.HasIndex(g => new { g.SiteId, g.Department });
builder.HasIndex(g => g.Status).HasFilter("status != 'ONLINE'");
}
}