Files

1575 lines
74 KiB
Markdown

Thorough Survey: VigilCare Clinical Domain Models and Entity Classes
Project Overview
The VigilCareClinical project is a clinical alert and patient monitoring system with comprehensive domain modeling focused on sepsis detection, clinical scoring systems, and alert management. The architecture uses Entity Framework Core with PostgreSQL and follows domain-driven design with configuration classes.
ENTITIES (17 total)
Core Domain Entities
1. Patient (/Domains/Entities/Patient.cs)
Key Properties:
Id (Guid, PK)
Mrn (string, max 20) - Medical Record Number, unique index
FirstName, LastName (string, max 100)
DateOfBirth (DateOnly)
Gender (string, max 10)
BloodType (enum: nullable)
Allergies (nullable string)
EmergencyContactName, EmergencyContactPhone (nullable)
Status (string, default "active")
CreatedAt (DateTimeOffset, default NOW())
Relationships:
1:N with Encounter (inverse: Encounters collection)
Constraints & Indexes:
Unique index on MRN for O(log n) lookups
Configuration: /Data/Configurations/PatientConfiguration.cs (lines 4-33)
Auditing: CreatedAt only, no soft delete pattern
2. Encounter (/Domains/Entities/Encounter.cs)
Key Properties:
Id (Guid, PK)
PatientId (Guid, FK)
EncounterType (enum: Inpatient, Outpatient, Emergency)
Status (enum: Scheduled, Active, Discharged, Cancelled; default Scheduled)
Department (enum: ICU, GeneralMedicine, Emergency, Cardiology, Surgery, Pediatrics)
AttendingPhysician (string, max 200, required)
RoomBed (nullable, max 20)
AdmissionReason, DischargeDiagnosis (nullable, max 500)
AdmittedAt (DateTimeOffset, default NOW())
DischargedAt (nullable DateTimeOffset)
CreatedAt (DateTimeOffset, default NOW())
Relationships:
N:1 with Patient (FK: PatientId, delete behavior: Restrict)
1:N with Observation, ClinicalAlert, Order
Constraints & Indexes:
CHECK constraints on encounter_type, status, department
Composite index: (PatientId, AdmittedAt)
Filtered index: (Status, AdmittedAt) WHERE status='ACTIVE'
Configuration: /Data/Configurations/EncounterConfiguration.cs (lines 4-59)
3. Observation (/Domains/Entities/Observation.cs)
Key Properties:
Id (Guid, PK)
EncounterId (Guid, FK)
ObservationCode (string, max 50) - clinical code (HEART_RATE, TEMP_C, etc.)
Value (decimal, 10,3 precision)
Unit (string, max 20)
Source (enum: Manual, Device, Lab; default Manual)
IdempotencyKey (nullable, max 100) - for device deduplication
RecordedAt (DateTimeOffset)
CreatedAt (DateTimeOffset, default NOW())
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
CHECK constraint on source
Unique partial index on IdempotencyKey (WHERE idempotency_key IS NOT NULL)
Composite index: (EncounterId, ObservationCode, RecordedAt)
Configuration: /Data/Configurations/ObservationConfiguration.cs (lines 4-44)
4. ClinicalAlert (/Domains/Entities/ClinicalAlert.cs)
Key Properties:
Id (Guid, PK)
EncounterId (Guid, FK)
PatientId (Guid)
ObservationId (nullable, Guid) - FK to triggering observation (not enforced in config)
AlertType (enum: extensive list - see below)
Severity (enum: Warning, Critical)
Details (string, required)
Status (enum: Open, Acknowledged, Resolved, Escalated; default Open)
AcknowledgedAt (nullable DateTimeOffset)
AcknowledgedBy (nullable, max 200)
ResolvedAt (nullable DateTimeOffset)
TriggeredAt (DateTimeOffset, default NOW())
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
CHECK constraints on severity, status, alert_type (comprehensive list)
Composite indexes: (EncounterId, TriggeredAt), (PatientId, TriggeredAt)
Filtered index: (Severity, TriggeredAt) WHERE status='OPEN'
Configuration: /Data/Configurations/ClinicalAlertConfiguration.cs (lines 4-77)
Alert Types: (53 total)
Critical threshold: CriticalHeartRate, CriticalTempC, CriticalPotassiumMeqL, CriticalSpo2, CriticalRespRate, CriticalWbcKUl, CriticalSystolicBp, CriticalDiastolicBp, CriticalLactateMmolL, CriticalAvpu, CriticalGlucoseMgDl, CriticalPao2MmHg, CriticalPlateletKUl, CriticalBilirubinMgDl, CriticalCreatinineMgDl
Warning threshold: WarningHeartRate, WarningTempC, WarningPotassiumMeqL, WarningSpo2, WarningRespRate, WarningWbcKUl, WarningSystolicBp, WarningDiastolicBp, WarningLactateMmolL, WarningGlucoseMgDl, WarningPao2MmHg, WarningPlateletKUl, WarningBilirubinMgDl, WarningCreatinineMgDl
Scoring: News2Warning, News2Emergency, GcsCritical, GcsWarning
Trend: RapidDeterioration
Sepsis: SepsisWarning (obsolete), SofaSepsis, SofaWarning
Screening: QsofaWarning (obsolete), QsofaScreen
Scoring & Clinical Evaluation Entities
5. News2Score (/Domains/Entities/News2Score.cs)
Key Properties:
Id, EncounterId, PatientId (Guids)
TotalScore (int)
RiskLevel (string: LOW, LOW_MEDIUM, MEDIUM, HIGH)
Component scores: RespRateScore, Spo2Score, SystolicBpScore, HeartRateScore, ConsciousnessScore, TemperatureScore, SupplementalO2Score (all int)
HasSingleParamThree (bool)
CalculatedAt (DateTimeOffset)
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
CHECK constraint on risk_level
Composite indexes: (EncounterId, CalculatedAt), (PatientId, CalculatedAt)
Configuration: /Data/Configurations/News2ScoreConfiguration.cs (lines 4-37)
6. SofaScore (/Domains/Entities/SofaScore.cs)
Key Properties:
Id, EncounterId, PatientId (Guids)
TotalScore (int)
Component scores: RespiratoryScore, CoagulationScore, LiverScore, CardiovascularScore, CnsScore, RenalScore (all int)
IsBaseline (bool) - tracks if this is baseline SOFA for the encounter
DeltaFromBaseline (nullable int)
StalenessFlags (nullable string, stored as JSONB) - tracks which components are stale
CalculatedAt, CreatedAt (DateTimeOffset)
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
Composite index: (EncounterId, CalculatedAt)
Filtered index: (EncounterId) WHERE is_baseline=true
Configuration: /Data/Configurations/SofaScoreConfiguration.cs (lines 4-36)
Design Note: Implements SOFA (Sequential Organ Failure Assessment) for sepsis detection
7. GcsScore (/Domains/Entities/GcsScore.cs)
Key Properties:
Id, EncounterId, PatientId (Guids)
Component scores: EyeScore, VerbalScore, MotorScore (all int)
TotalScore (int)
Classification (string: MILD, MODERATE, SEVERE; max 16)
CalculatedAt, CreatedAt (DateTimeOffset)
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
CHECK constraint on classification
Composite index: (EncounterId, CalculatedAt)
Configuration: /Data/Configurations/GcsScoreConfiguration.cs (lines 4-32)
Design Note: GCS = Glasgow Coma Scale for neurological assessment
Sepsis Management Entities
8. SepsisBundle (/Domains/Entities/SepsisBundle.cs)
Key Properties:
Id (Guid, PK)
EncounterId, TriggeringAlertId (Guid, FK)
TriggeringAlertType (string, max 50) - human-readable alert type
RecognizedAt (DateTimeOffset) - when sepsis was recognized
DeadlineAt (DateTimeOffset) - compliance deadline
ComplianceStatus (enum: InProgress, Compliant, NonCompliant; default InProgress)
CompletedAt (nullable DateTimeOffset)
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
N:1 with ClinicalAlert via TriggeringAlert (delete behavior: Restrict)
1:N with SepsisBundleElement
Constraints & Indexes:
CHECK constraint on compliance_status
Composite indexes: (EncounterId, RecognizedAt), (ComplianceStatus)
Configuration: /Data/Configurations/SepsisBundleConfiguration.cs (lines 4-47)
Design Pattern: Implements Sepsis 3-hour bundle tracking
9. SepsisBundleElement (/Domains/Entities/SepsisBundleElement.cs)
Key Properties:
Id (Guid, PK)
BundleId (Guid, FK)
ElementType (enum: BloodCultures, SerumLactate, BroadSpectrumAntibiotics, IvFluidResuscitation)
Status (enum: Pending, Completed; default Pending)
OrderId (nullable, Guid, FK to Order)
CompletedAt (nullable DateTimeOffset)
Relationships:
N:1 with SepsisBundle (FK: BundleId, delete behavior: Cascade)
N:1 with Order, optional (FK: OrderId, delete behavior: SetNull)
Constraints & Indexes:
CHECK constraints on element_type, status
Unique composite index: (BundleId, ElementType)
Configuration: /Data/Configurations/SepsisBundleElementConfiguration.cs (lines 4-50)
Medication & Order Management
10. Order (/Domains/Entities/Order.cs)
Key Properties:
Id (Guid, PK)
EncounterId (Guid, FK)
OrderType (enum: Lab, Imaging, Medication, Procedure)
Description (string, required)
OrderedBy (string, max 200, required)
Status (enum: Pending, InProgress, Resulted, Cancelled; default Pending)
OrderedAt (DateTimeOffset, default NOW())
ResultedAt (nullable DateTimeOffset)
ResultSummary (nullable string)
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
CHECK constraints on order_type, status
Composite indexes: (EncounterId, OrderedAt)
Filtered index: (Status, OrderedAt) WHERE status IN ('PENDING', 'IN_PROGRESS')
Configuration: /Data/Configurations/OrderConfiguration.cs (lines 4-49)
11. MedicationAdministration (/Domains/Entities/MedicationAdministration.cs)
Key Properties:
Id (Guid, PK)
EncounterId (Guid, FK)
DrugName (string, max 200, required)
Dose (decimal, 10,4 precision, required)
DoseUnit (string, max 20, required)
Route (string, max 50, required)
AdministeredAt (DateTimeOffset, required)
AdministeredBy (string, max 100, required)
Relationships:
N:1 with Encounter (FK: EncounterId, delete behavior: Restrict)
Constraints & Indexes:
Composite indexes: (EncounterId, AdministeredAt), (EncounterId, DrugName)
Configuration: /Data/Configurations/MedicationAdministrationConfiguration.cs (lines 4-27)
Auditing: No explicit audit trail beyond CreatedAt
Configuration & Support Entities
12. AlertThreshold (/Domains/Entities/AlertThreshold.cs)
Key Properties:
Id (Guid, PK)
ObservationCode (string, max 50) - links to Observation.ObservationCode
DisplayName (string, max 200)
Unit (string, max 20)
Thresholds: CriticalLow, WarningLow, WarningHigh, CriticalHigh (all nullable decimal, 10,3)
SuppressionWindowMinutes (nullable int) - alert suppression window
CreatedAt (DateTimeOffset, default NOW())
Relationships: None (reference data)
Constraints & Indexes:
Unique index on ObservationCode
Configuration: /Data/Configurations/AlertThresholdConfiguration.cs (lines 4-23)
Design Note: Master configuration for threshold-based alerts
13. ReconciliationAlert (/Domains/Entities/ReconciliationAlert.cs)
Key Properties:
Id (Guid, PK)
CheckType (enum: UnacknowledgedCriticalAlert, PendingOrderNoResult, ActiveInpatientNoObservation)
EncounterId (nullable, Guid, FK)
PatientId (nullable, Guid, FK)
Details (string, required)
ResolvedAt (nullable DateTimeOffset)
CreatedAt (DateTimeOffset, default NOW())
Relationships:
N:1 with Encounter, optional (FK: EncounterId, delete behavior: SetNull)
N:1 with Patient, optional (FK: PatientId, delete behavior: SetNull)
Constraints & Indexes:
CHECK constraint on check_type
Filtered composite index: (CheckType, EncounterId) WHERE resolved_at IS NULL
Configuration: /Data/Configurations/ReconciliationAlertConfiguration.cs (lines 4-40)
Design Note: Data quality checks for consistency gaps
System & Audit Entities
14. ClinicalUser (/Domains/Entities/ClinicalUser.cs)
Key Properties:
Id (Guid, PK)
Username (string, max 100, required)
PasswordHash (string, max 500, required)
DisplayName (string, max 200, required)
Role (enum: Nurse, Physician, Admin, Integration)
IsActive (bool, default true)
CreatedAt (DateTimeOffset, default NOW())
LastLoginAt (nullable DateTimeOffset)
Relationships: None
Constraints & Indexes:
Unique index on Username
Configuration: /Data/Configurations/ClinicalUserConfiguration.cs (lines 4-21)
Security Note: PasswordHash only, no plaintext storage
15. ClinicalAuditLog (/Domains/Entities/ClinicalAuditLog.cs)
Key Properties:
Id (Guid, PK)
Action (enum: ThresholdCreated, ThresholdUpdated, AlertAcknowledged, AlertResolved, EncounterStatusChanged, PatientRegistered, SuppressionWindowSet, UserLogin)
EntityType (string, max 100) - type of audited entity
EntityId (Guid) - ID of audited entity
UserId (nullable Guid) - user who performed action
UserDisplayName (nullable, max 200)
PreviousValueJson (nullable string, JSONB)
NewValueJson (nullable string, JSONB)
Reason (nullable string)
IpAddress (nullable, max 45)
CorrelationId (nullable, max 100)
CreatedAt (DateTimeOffset, default NOW())
Relationships: None
Constraints & Indexes:
Indexes on: EntityType, EntityId, UserId, CreatedAt
Configuration: /Data/Configurations/ClinicalAuditLogConfiguration.cs (lines 4-29)
Audit Pattern: Append-only; no UPDATE/DELETE from application code (comment at line 24)
16. OutboxEvent (/Domains/Entities/OutboxEvent.cs)
Key Properties:
Id (Guid, PK)
Topic (string, max 200) - event topic/type
Payload (string, JSONB) - JSON event payload
PartitionKey (nullable, max 36) - encounter_id for all clinical events
CreatedAt (DateTimeOffset, default NOW())
ProcessedAt (nullable DateTimeOffset) - when published to message broker
Relationships: None
Constraints & Indexes:
Filtered index on CreatedAt WHERE processed_at IS NULL (unprocessed events)
Configuration: /Data/Configurations/OutboxEventConfiguration.cs (lines 4-20)
Pattern: Outbox pattern for event publishing
17. ExternalResourceIdentifier (/Domains/Entities/ExternalResourceIdentifier.cs)
Key Properties:
Id (Guid, PK)
ResourceType (enum: Patient, Encounter)
InternalId (Guid) - ID in this system
System (string, max 500) - external system identifier (e.g., FHIR system URL)
Value (string, max 200) - external resource ID
CreatedAt (DateTimeOffset, default NOW())
Relationships: None (reference mapping only)
Constraints & Indexes:
Unique composite index: (ResourceType, System, Value)
Composite index: (ResourceType, InternalId)
Configuration: /Data/Configurations/ExternalResourceIdentifierConfiguration.cs (lines 4-20)
Pattern: Maps internal IDs to external FHIR/EHR identifiers
ENUMS (22 total)
Located in /Domains/Enums/:
Enum Name Values Notes
AlertType (53 values) SepsisWarning (obsolete), Critical*, Warning*, News2Warning, News2Emergency, RapidDeterioration, QsofaWarning (obsolete), QsofaScreen, GcsCritical, GcsWarning, SofaSepsis, SofaWarning With ToDbString/FromDbString extensions; extensive mapping to observation codes
AlertStatus Open, Acknowledged, Resolved, Escalated With ToDbString/FromDbString extensions
AlertSeverity Warning, Critical With ToDbString/FromDbString extensions
EncounterStatus Scheduled, Active, Discharged, Cancelled With ToDbString/FromDbString extensions
EncounterType Inpatient, Outpatient, Emergency With ToDbString/FromDbString extensions
Department ICU, GeneralMedicine, Emergency, Cardiology, Surgery, Pediatrics With ToDbString/FromDbString extensions
OrderStatus Pending, InProgress, Resulted, Cancelled With ToDbString/FromDbString extensions
OrderType Lab, Imaging, Medication, Procedure With ToDbString/FromDbString extensions
BloodType APositive, ANegative, BPositive, BNegative, AbPositive, AbNegative, OPositive, ONegative With ToDbString/FromDbString extensions
ObservationSource Manual, Device, Lab With ToDbString/FromDbString extensions
ClinicalRole Nurse, Physician, Admin, Integration With ToDbString/FromDbString extensions
AuditAction ThresholdCreated, ThresholdUpdated, AlertAcknowledged, AlertResolved, EncounterStatusChanged, PatientRegistered, SuppressionWindowSet, UserLogin With ToDbString/FromDbString extensions
SepsisBundleComplianceStatus InProgress, Compliant, NonCompliant With ToDbString/FromDbString extensions
SepsisBundleElementType BloodCultures, SerumLactate, BroadSpectrumAntibiotics, IvFluidResuscitation With ToDbString/FromDbString extensions
SepsisBundleElementStatus Pending, Completed With ToDbString/FromDbString extensions
ReconciliationCheckType UnacknowledgedCriticalAlert, PendingOrderNoResult, ActiveInpatientNoObservation With ToDbString/FromDbString extensions
News2Outcome NotNews2Code, IncompleteParameters, ScoreComputed No extensions
GcsOutcome NotGcsCode, IncompleteComponents, ScoreComputed No extensions
SofaOutcome NotSofaTrigger, EncounterNotFound, ScoreComputed No extensions
TrendOutcome NotTrendCode, InsufficientHistory, Stable, RapidDeterioration, AlertAlreadyOpen No extensions
ExternalResourceType Patient, Encounter With ToDbString/FromDbString extensions
SofaValueStatus Current, Stale, Expired No extensions
DATABASE CONTEXT
File: /Data/AppDbContext.cs (lines 1-29)
DbSets: All 17 entities registered via public properties
Configuration: Uses ApplyConfigurationsFromAssembly() pattern with individual IEntityTypeConfiguration<T> implementations
Constraints: All enums have database-level CHECK constraints (see configurations)
Indexes: Composite, unique, and filtered indexes extensively used for query optimization
RELATIONSHIPS SUMMARY
Patient (1) ──←─── (N) Encounter ──←─── (N) Observation
├───── (N) ClinicalAlert
├───── (N) Order ──←─── (N) SepsisBundleElement
└───── (N) News2Score, SofaScore, GcsScore, MedicationAdministration
SepsisBundle ──→ ClinicalAlert (FK to TriggeringAlert)
└───← (N) SepsisBundleElement
ReconciliationAlert ──→ Encounter (optional)
──→ Patient (optional)
ExternalResourceIdentifier (standalone reference mapping)
ClinicalUser (standalone)
ClinicalAuditLog (append-only audit trail)
OutboxEvent (event publishing pattern)
AlertThreshold (reference data)
KEY PATTERNS & DESIGN DECISIONS
1. Auditing
ClinicalAuditLog captures all structural changes (action, entity type, before/after JSON)
Append-only; no deletes from application code
Tracks: user, IP, correlation ID, timestamp, reason
File: /Data/Configurations/ClinicalAuditLogConfiguration.cs (line 24 comment)
2. Soft Delete
No explicit soft delete pattern (no IsDeleted, DeletedAt fields)
All deletes use foreign key constraints (Restrict, Cascade, SetNull)
ClinicalAuditLog provides audit trail for deleted records
3. Event Publishing
OutboxEvent table implements Outbox pattern for reliable event publishing
ProcessedAt field tracks publication status
PartitionKey ensures ordering (encounter_id for clinical events)
File: /Data/Configurations/OutboxEventConfiguration.cs (lines 4-20)
4. Idempotency
Observation uses IdempotencyKey (unique partial index) for device deduplication
File: /Data/Configurations/ObservationConfiguration.cs (line 38-40)
5. Clinical Scoring
SOFA (SofaScore): Full 6-component organ dysfunction scoring with baseline tracking
NEWS2 (News2Score): 8-parameter early warning system
GCS (GcsScore): Glasgow Coma Scale with neurological classification
Each stores component scores + total + calculated timestamp
Enables trend analysis and historical comparison
6. Alert Management
53 AlertType values with legacy obsolete entries marked
AlertThreshold supports configurable suppression windows
AlertStatus tracks lifecycle: Open → Acknowledged → Resolved/Escalated
Separate severity (Warning/Critical) from type
7. Sepsis Protocol
SepsisBundle tracks 3-hour compliance bundle
SepsisBundleElement maps to 4 required interventions
TriggeringAlertType stored for audit trail (alert type may be obsolete)
ComplianceStatus: InProgress → Compliant/NonCompliant
8. External Integration
ExternalResourceIdentifier maps internal IDs to FHIR/external system IDs
Supports Patient and Encounter types
Unique on (ResourceType, System, Value) for lookup
VALIDATION & CONSTRAINTS
Entity-Level
Database CHECK constraints on all enum fields (see configurations)
Column type constraints: varchar max lengths, decimal precision
Foreign key constraints with specified DeleteBehavior
Application-Level (inferred from usage)
MaxLength attributes on string properties
Required (!) markers on non-nullable properties
Enum conversions via extension methods (ToDbString/FromDbString)
MISSING/INCOMPLETE PATTERNS
Identified Gaps:
No explicit validation attributes (DataAnnotations) visible in entity definitions
No soft delete implementation - relies entirely on audit log for record history
ObservationId in ClinicalAlert - no foreign key constraint configured, may be loose reference
No entity-level optimistic concurrency (Timestamp/RowVersion) - potential conflict in high-concurrency scenarios
MedicationAdministration - no audit trail beyond creation; changes to medication records not captured
ClinicalUser.PasswordHash - no hash validation/strength enforcement visible
ReconciliationAlert - uses optional FKs (encounter/patient can be null), may indicate loose relationships
API COVERAGE
Controllers with service implementations:
PatientsController, EncountersController, ObservationsController
AlertsController, AlertThresholdsController, AuditLogsController
OrdersController, MedicationsController
News2Controller, SofaController, GcsController, QsofaController
SepsisBundlesController
FhirIngestController, FhirMetadataController, AnalyticsController, AuthController
No explicit controller/service identified for:
ReconciliationAlert (backend-only data quality checks?)
OutboxEvent (internal event publishing system)
ExternalResourceIdentifier (internal FHIR mapping)
FILE LOCATIONS SUMMARY
Component Files
Entities /Domains/Entities/*.cs (17 files)
Configurations /Data/Configurations/*.cs (17 files)
Enums /Domains/Enums/*.cs (22 files)
DbContext /Data/AppDbContext.cs
Controllers /Controllers/*.cs (17 files)
Services /Services/*.cs (41 files)
This is a well-architected clinical domain model with comprehensive relational design, supporting sepsis detection, clinical scoring, alert management, and audit compliance.
Models/entities survey is back too. Still waiting on controllers/API, services/business logic, and infrastructure agents.
Excellent! Now I have enough comprehensive data. Let me create a thorough survey report:
COMPREHENSIVE SERVICE SURVEY: VigilCareClinical
Based on my thorough analysis of all service classes and business logic in the project, here's a detailed survey organized by your focus areas:
1. ALL SERVICE FILES
Services Directory (23 services):
AlertService.cs (line 5-203): Alert lifecycle management - acknowledgment, resolution, suppression window resolution
ObservationService.cs (line 7-245): Core ingest pipeline - validation, idempotency, critical alert creation, outbox events
WarningEvaluator.cs (line 5-152): Warning threshold detection via idempotent SQL INSERT
SepsisBundleService.cs (line 4-164): Sepsis bundle creation, element tracking, compliance status tracking
QsofaService.cs (line 4-55): qSOFA screening retrieval from Redis cache
SofaService.cs (line 3-45): SOFA score history retrieval from database
GcsService.cs (line 3-17): GCS score retrieval (minimal wrapper)
News2Service.cs (line 3-50): NEWS2 score history with cursor-based pagination
MedicationService.cs (line 4-102): Medication administration recording and correlation lookup
OrderService.cs (line 3-121): Order lifecycle management - Pending → InProgress → Resulted → Cancelled
PatientService.cs (line 4-202): Patient registration, encounter opening, external identifier linking
EncounterService.cs (line 4-298): Encounter status transitions, timeline aggregation, FHIR upsert
AlertThresholdService.cs (line 4-120): Threshold CRUD and Redis cache invalidation
AlertSuppressionService.cs (line 3-31): Suppression window enforcement via Redis
PlausibilityValidator.cs (line 1-49): Static range validation (catches device malfunction)
MapCalculator.cs (line 1-6): Mean arterial pressure calculation
ExternalIdentifierService.cs (line 3-70): FHIR identifier linking/resolution
ObservationQueryService.cs (line 3-59): Keyset pagination for observation history
AuditService.cs (line 3-47): Audit log persistence with correlation ID tracking
CurrentUserService.cs, AuthService.cs, AnalyticsService.cs: Supporting services
Clinical Scoring Directories:
Sepsis/ (4 files):
QsofaCalculator.cs (line 8-36): Criteria definition (RR≥22, SBP≤100, AVPU≥1/GCS<15)
QsofaDetector.cs (line 6-183): Redis-based criterion tracking, TTL=1800s, screen alert generation
AlertCreationGuard.cs (line 1-8): Prevents deprecated SEPSIS_WARNING type
SepsisAlertHandler.cs (line 1-30): Triggers bundle creation on SOFA_SEPSIS alert only
Sofa/ (3 files):
SofaCalculator.cs (line 1-160): 6 organ systems scoring (Resp, Coag, Liver, CV, CNS, Renal)
SofaDetector.cs (line 8-349): Lab caching, baseline resolution, delta computation, alert creation
SofaLabCache.cs (line 5-65): Redis caching with TTL, staleness classification
Gcs/ (2 files):
GcsCalculator.cs (line 3-60): Total computation, NEWS2/qSOFA mapping
GcsDetector.cs (line 6-237): Score persistence, alert creation (severe≤8, moderate≤12), qSOFA re-sync
News2/ (2 files):
News2Calculator.cs (line 4-100): 7-parameter scoring with risk levels (HIGH≥7, MEDIUM≥5)
News2Detector.cs (line 6-284): GCS-first/AVPU-fallback consciousness resolution, alert creation
Medication/ (1 file):
MedicationCorrelationHelper.cs (line 3-39): Annotates alert details with recent vasopressor/relevant drug info
Trend/ (2 files):
TrendCalculator.cs (line 3-62): Rate computation, threshold checking (negative for SpO2/SBP)
TrendDetector.cs (line 7-152): History tracking in Redis, rapid deterioration alert creation
2. CLINICAL SCORING SYSTEMS - COMPLETENESS ASSESSMENT
System Status Notes
qSOFA ✓ COMPLETE 3 criteria in Redis, TTL=1800s, screen alert generates but NOT bundle trigger (Phase 27 Step 4)
SOFA ✓ COMPLETE 6 organs, baseline resolution, delta tracking, lab staleness flags, triggers bundle on delta≥2
NEWS2 ✓ COMPLETE 7 parameters, GCS-first consciousness, risk stratification (4 levels)
GCS ✓ COMPLETE 3 components, severity classification, CNS score mapping, qSOFA/NEWS2 re-sync
Key Clinical Logic:
Line 69 (QsofaDetector): if (activeCount < 2) screen fires at ≥2 criteria
Line 178-189 (SofaDetector): Delta ≥2 → Critical SOFA_SEPSIS; Delta=1 → Warning SOFA_WARNING
Line 111-122 (News2Detector): Risk levels assigned via DetermineRiskLevel(totalScore, hasSingleParamThree)
Line 26-29 (GcsCalculator): Severity mapping (≤8=SEVERE, ≤12=MODERATE, ≥13=MILD)
3. ALERT CREATION, LIFECYCLE, AND NOTIFICATION PIPELINE
Alert Lifecycle States: Open → Acknowledged → Resolved | Open → Escalated (via RabbitMQ timer, Phase 6)
Alert Creation Paths:
Critical Threshold Breach (Line 104-117 ObservationService):
Synchronous in ingest transaction
Outbox event alert.generated for relay
Database exception → rollback handled at line 174-184
Warning Threshold Breach (WarningEvaluator, Line 74-143):
Async via Kafka consumer WarningAlertService
Idempotent SQL INSERT with WHERE NOT EXISTS (Line 97-108)
Transaction-scoped with rollback on affected == 0
qSOFA Screen (QsofaDetector, Line 93-171):
Fires at ≥2 active criteria
Does NOT trigger bundle per Line 169 comment
Idempotent WHERE NOT EXISTS insertion
SOFA Sepsis/Warning (SofaDetector, Line 267-348):
Delta-driven: delta≥2 → CRITICAL, delta=1 → WARNING
Calls SepsisAlertHandler.OnSepsisAlertCreatedAsync() for SOFA_SEPSIS only (Line 338-342)
Creates bundle on success
NEWS2 (News2Detector, Line 186-275):
Risk-driven: HIGH → CRITICAL, MEDIUM/LOW_MEDIUM → WARNING
Suppression check for warnings (Line 193-200)
GCS/Trend (GcsDetector, TrendDetector):
GCS: severe/moderate thresholds
Trend: rate-of-change for 5 vital signs
Suppression Pipeline:
Line 17-134 (AlertService.AcknowledgeAsync): Sets suppression window in Redis on acknowledgment
Line 40 (WarningEvaluator): Checks suppression before creating alert
Line 39-45 (AlertSuppressionService): Redis-based TTL enforcement
Outbox/Notification:
Line 108-122 (AlertService): Alert acknowledged event → Kafka → RabbitMQ timer cancellation
Line 120-133 (ObservationService): alert.generated outbox event
Line 314-333 (SofaDetector): alert.generated for SOFA alerts
4. SEPSIS DETECTION AND BUNDLE COMPLIANCE WORKFLOW
Sepsis Detection:
qSOFA (≥2 criteria) → Screen alert (does NOT create bundle)
SOFA delta≥2 → SOFA_SEPSIS alert → Bundle creation (Line 17-87 SepsisBundleService)
Bundle Lifecycle (SepsisBundleService.cs):
Creation (Line 17-87): Triggered ONLY by SOFA_SEPSIS alert type
Elements (Line 43-63): 4 required elements (Blood cultures, Serum lactate, Antibiotics, IV fluid)
Deadline (Line 39): recognizedAt.AddHours(1) - HARDCODED TO 1 HOUR
Completion Tracking (Line 114-162): OnOrderResultedAsync monitors element completion
Compliance Status (Line 134-136):
Compliant if all elements complete ≤ deadline
NonCompliant if any element incomplete after deadline
Bundle Monitoring (SepsisBundleMonitorService, Line 4-75):
Scan Interval: HARDCODED TO 5 MINUTES (Line 5)
Marks overdue bundles as NON_COMPLIANT
Logs incomplete element count
Issues Found:
No explicit transaction boundary around bundle creation + order creation
No retry logic if order service fails
Deadline hardcoded to 1 hour (configurable via SepsisOptions?)
5. OBSERVATION INGEST PIPELINE
Validation → Storage → Scoring Pipeline:
1. VALIDATION (Lines 28-62):
✓ Encounter exists and is Active
✓ Idempotency key pre-check (avoids exception path)
✓ Plausibility check (PlausibilityValidator.IsPlausible)
✓ Threshold exists (falls back Redis→PostgreSQL)
2. TRANSACTION BOUNDARY (Line 69):
BEGIN TRANSACTION
3. OBSERVATION INSERT (Line 73-85):
+ IdempotencyKey for deduplication
+ RecordedAt provided by client
4. CRITICAL ALERT CREATION (Line 98-133):
Synchronous if threshold breached
- Outbox event for relay
5. OUTBOX EVENTS (Line 120-149):
- alert.generated (if critical)
- observation.recorded (always - for async consumers)
6. COMMIT (Line 150-152):
COMMIT TRANSACTION
7. RACE CONDITION HANDLING (Line 174-184):
Catch DbUpdateException → unique violation → rollback
→ retry SELECT to return existing row
8. ASYNC CONSUMERS (Kafka):
- WarningAlertService: threshold evaluation
- SepsisEngineService: qSOFA processing
- News2ScoringService: NEWS2 calculation
- GcsScoringService: GCS calculation
- SofaScoringService: SOFA rescoring
- TrendAnalyzerService: rapid deterioration
Database-Level Protection:
Line 38-40 (ObservationConfiguration): Partial unique index on idempotency_key (only non-NULL values checked)
Allows devices without keys to send duplicates (intentional per comment)
Error Handling:
Line 174-184: DbUpdateException handling for concurrent duplicates
Line 186-190: Generic exception rollback
Gaps:
❌ Redis cache miss fallback doesn't log metric (Line 217-219 only debug log)
❌ No monitoring on idempotency key collision rate
❌ PlausibilityValidator failures not audited
6. TREND DETECTION AND ANALYSIS
Trend Code Support:
Line 6-9 (TrendCalculator): HEART_RATE, RESP_RATE, SYSTOLIC_BP, TEMP_C, SPO2
Processing Pipeline:
History Tracking (Line 42-64 TrendDetector):
Append new entry to Redis list
Trim to max entries, evict outside window
TTL hardcoded (Line 64, via options)
Rate Computation (Line 69-72):
Only if ≥2 entries and within window
Velocity = (newest - oldest) / deltaMinutes
Threshold Detection (Line 76-77):
SPO2/SBP: checks negative rate (decline) Line 49
Others: checks positive rate (rise)
HARDCODED THRESHOLDS via options
Alert Creation (Line 80-81):
Idempotent SQL INSERT with LIKE pattern match (Line 102-113)
❌ UNSAFE: LIKE '%{code}%' could match wrong alerts
Issues Found:
Line 111: LIKE {$"%{observationCode}%"} is fragile - "HEART" could match "HEART_RATE" and "HEART_RATE_TREND"
Should use exact observation code or JSON field comparison
7. WARNING EVALUATION LOGIC
Warning Threshold Detection (WarningEvaluator.cs):
Line 21-49: Check threshold, reject if critical, check suppression, create alert
Line 51-57: IsWarningBreach computes from threshold ranges
Idempotent Alert Creation (Line 74-143):
Prevents duplicate warning alerts for same encounter+type
Unlike critical (one per encounter), warnings can recur if acknowledged/resolved
SQL: WHERE NOT EXISTS (... status IN ('OPEN', 'ACKNOWLEDGED'))
Key Design:
Line 40: Suppression check BEFORE alert creation attempt
Line 87-108: Nested transaction with rollback on affected == 0
Line 134-136: SaveChangesAsync within transaction scope
Missing:
❌ No logging of suppression hits vs. attempts
❌ No metrics for alert dedupe success rate
❌ Medication correlation happens AFTER threshold check (should be in details generation)
8. MEDICATION ADMINISTRATION AND CORRELATION
Medication Service (MedicationService.cs):
Line 17-44: CreateAsync validates active encounter, stores administration
Line 59-81: GetRecentForEncounterAsync filters by observation code using correlation mapping
Correlation Lookup (MedicationCorrelationHelper.cs):
Line 20-38: TryAnnotateDetailsAsync appends medication context to alert details
Queries recent administrations within configurable window
Returns most recent correlated drug
Vasopressor Resolver (SofaVasopressorResolver.cs):
Line 37-53: Caches vasopressor from MedicationAdministration
Line 82-89: Normalizes dose to µg/kg/min (standardizes units)
Line 55-80: GetActiveVasopressorAsync: Redis first, fallback to DB lookup
HARDCODED WINDOW (Line 52): configurable via SofaOptions.VasopressorWindowHours
Issues Found:
Line 78 (MedicationService): String comparison is case-insensitive but .ToLower() on stored value only
Line 67-69: Drug mapping configuration is external, no validation of codes
9. PATIENT AND ENCOUNTER LIFECYCLE MANAGEMENT
Patient Registration (PatientService.cs, Line 20-46):
Auto-generates MRN: MRN-{count+1:D6} HARDCODED PATTERN
No validation of FirstName/LastName length
Encounter Opening (Line 84-139):
Checks for duplicate active encounter of same type (Line 90-98)
Creates outbox event for status change
No check for max active encounters per patient (possible resource exhaustion)
Encounter Status Transitions (EncounterService.cs, Line 8-14):
Explicit state machine: Scheduled → Active/Cancelled; Active → Discharged/Cancelled
Line 139-142: Enforces allowed transitions or throws ConflictException
Line 147-151: Discharge event includes diagnosis
FHIR Upsert Operations:
PatientService.RegisterOrUpdateByIdentifierAsync (Line 142-195)
EncounterService.OpenOrUpdateByIdentifierAsync (Line 214-297)
Both use ExternalIdentifierService for linking
Issues Found:
❌ Line 197-200 (PatientService): MRN generation race condition - sequential counter without locking
❌ Line 231-234 (EncounterService): Status transition within upsert can fail mid-flight without clear recovery
❌ No soft-delete support, discharged encounters remain queryable
10. ORDER MANAGEMENT WORKFLOW
Order Lifecycle (OrderService.cs):
States: Pending → InProgress/Cancelled → Resulted → (terminal)
State Machine (Line 5-11): Explicit allowed transitions
Order Creation (Line 22-46):
Validates active encounter
Creates order with Pending status and OrderedAt timestamp
No idempotency key support
Status Transitions (Line 81-98):
TransitionStatusAsync enforces state machine (Line 87)
Sets ResultedAt on Resulted transition
Result Recording (Line 100-120):
Line 106-110: Checks not already resulted/cancelled
Line 117-118: CRITICAL: Calls bundleService.OnOrderResultedAsync(order.Id)
This completes bundle elements
Sepsis Bundle Integration (SepsisBundleService.OnOrderResultedAsync, Line 114-162):
Line 116-121: Finds element by order ID, marks Completed
Line 127-129: Checks if all elements complete
Line 134-136: Marks bundle Compliant/NonCompliant based on deadline
Issues Found:
❌ No transaction spanning order result → bundle update
❌ OnOrderResultedAsync catches silently if element not found (Line 120)
❌ No retry if bundle service fails
ERROR HANDLING GAPS
File Issue Severity
ObservationService:204 Redis cache miss only debug logs, no metrics Medium
SepsisBundleService:53-63 Order creation not in bundle transaction HIGH
SepsisBundleService:120 Silent no-op if element not found Medium
WarningEvaluator:110-114 Rollback on affected==0 but no retry logic Low
SofaDetector:73-79 Encounter not found only logs warning Medium
MedicationService:78 String comparison sensitivity inconsistent Low
PatientService:197-200 MRN counter race condition HIGH
TrendDetector:102-113 LIKE pattern fragile for alert matching HIGH
ExternalIdentifierService:25-37 Linking check not atomic Medium
EncounterService:231-234 Nested status transition in upsert Medium
CONCURRENCY CONCERNS
Alert Creation Race Conditions:
Line 97-108 (WarningEvaluator): WHERE NOT EXISTS protects against duplicate warnings
Line 115-126 (QsofaDetector): Same pattern for screen alerts
Line 295-305 (SofaDetector): Same pattern for SOFA alerts
✓ All use idempotent SQL INSERT - database handles serialization
Observation Ingest Race:
Line 174-184 (ObservationService): Handles concurrent retries via unique index
✓ Pre-check (Line 45-58) avoids common retry path
✓ Partial unique index allows devices without keys
Sepsis Bundle Creation:
Line 24-29 (SepsisBundleService): AnyAsync check for existing in-progress bundle
❌ Time-of-check-to-time-of-use: two observations arriving simultaneously could both pass check and create bundles
Should use WHERE NOT EXISTS in INSERT like other alert types
Medication Correlation:
Line 26-27 (MedicationService): Database query has no lock
❌ Concurrent medication entry while alert being created could miss correlation
SOFA Lab Cache:
Line 21-27 (SofaLabCache): Concurrent stores to same Redis key via MSET/SET
✓ Redis atomicity handles this, but TTL window could be lost if records arrive out-of-order
TRANSACTION BOUNDARIES
Well-Defined Transactions:
ObservationService.IngestAsync (Line 69-152): Observation + Alert + OutboxEvents in single transaction ✓
WarningEvaluator.TryCreateWarningAlertAsync (Line 86-136): Alert + OutboxEvent ✓
QsofaDetector.TryCreateScreenAlertAsync (Line 105-154): Screen alert + OutboxEvent ✓
SofaDetector (multiple): Score persistence separate from alert creation ✓
TrendDetector.TryCreateAlertAsync (Line 96-144): Trend alert + OutboxEvent ✓
Missing Transaction Boundaries:
❌ SepsisBundleService: Order creation (OrderService) happens OUTSIDE bundle transaction
If OrderService fails after bundle created, bundle element references missing orders
❌ OrderService.RecordResultAsync → bundleService.OnOrderResultedAsync: No spanning transaction
❌ PatientService.RegisterOrUpdateByIdentifierAsync → EncounterService.OpenOrUpdateByIdentifierAsync: Chained async calls
HARDCODED VALUES (Should Be Configurable)
Value Location Recommendation
1 hour sepsis bundle deadline SepsisBundleService:39 Move to SepsisOptions
5 minute bundle monitor scan SepsisBundleMonitorService:5 Move to SepsisOptions
1800s qSOFA TTL QsofaDetector:8 Move to QsofaOptions
14400s (4h) GCS/NEWS2 TTL GcsDetector:8, News2Detector:8 Move to VitalSignsOptions
MRN pattern MRN-{count:D6} PatientService:200 Move to PatientOptions
Vasopressor window hours SofaVasopressorResolver:52 ✓ Already in SofaOptions
Trend history max entries/TTL TrendDetector:58, Line 64 ✓ In TrendDetectionOptions
Medication correlation window MedicationCorrelationHelper:27 ✓ In MedicationCorrelationOptions
KEY ARCHITECTURAL OBSERVATIONS
Strong Points:
✓ Observation ingest is robust with idempotency and transaction safety
✓ Alert creation uses idempotent SQL patterns consistently
✓ Outbox pattern correctly implements event sourcing
✓ Sepsis bundle compliance tracking is auditable
✓ SOFA scoring includes staleness metadata for clinical context
Weaknesses:
❌ Sepsis bundle creation race condition (concurrent observations)
❌ MRN generation race condition (no locking on counter)
❌ Medication correlation happens too late (after alert created)
❌ Trend alert matching via LIKE is fragile
❌ Order→Bundle integration lacks spanning transaction
❌ No monitoring on idempotency collisions or suppression effectiveness
❌ Exception handling in background services only logs and delays (no dead-letter queue)
❌ FHIR upsert operations chain multiple writes without compensation logic
SPECIFIC FILE PATHS FOR KEY FINDINGS
Sepsis Bundle Race Condition: /VigilCareClinicalAPI/Services/SepsisBundleService.cs Line 24-29
MRN Race Condition: /VigilCareClinicalAPI/Services/PatientService.cs Line 197-200
Trend Alert Fragile Matching: /VigilCareClinicalAPI/Trend/TrendDetector.cs Line 102-113
Observation Idempotency Index: /VigilCareClinicalAPI/Data/Configurations/ObservationConfiguration.cs Line 36-40
Alert Deduplication Patterns: /VigilCareClinicalAPI/Services/WarningEvaluator.cs Line 97-108
Outbox Event Relay: /VigilCareClinicalAPI/BackgroundServices/OutboxRelayService.cs Line 58-139
Sepsis Bundle Monitoring: /VigilCareClinicalAPI/BackgroundServices/SepsisBundleMonitorService.cs Line 40-74
SOFA Lab Staleness: /VigilCareClinicalAPI/Sofa/SofaDetector.cs Line 82-154
Clinical Scoring Mapping: /VigilCareClinicalAPI/Gcs/GcsCalculator.cs Line 41-59 (NEWS2/qSOFA mappings)
This completes the thorough service survey as requested.
Services/business logic survey is complete. Still waiting on the controllers/API and infrastructure agents.
Let me now compile a comprehensive analysis. Let me create a final summary with specific line numbers:
Based on my thorough examination of the VigilCareClinical API controllers, here is a comprehensive survey:
CONTROLLER INVENTORY
Total Controllers: 17
GcsController
AlertThresholdsController
SepsisBundlesController
FhirIngestController
EncountersController
AuditLogsController
MedicationsController
SofaController
OrdersController
News2Controller
FhirMetadataController
QsofaController
AlertsController
ObservationsController
AnalyticsController
PatientsController
AuthController
CRITICAL FINDINGS
1. MISSING DELETE ENDPOINTS (No HttpDelete Methods)
Severity: HIGH - All controllers claim CRUD but none support DELETE operations
AlertThresholdsController: Has C(reate), R(ead), U(pdate) but no DELETE
MedicationsController: No delete endpoint
OrdersController: No delete endpoint
Other controllers: No delete operations defined
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/AlertThresholdsController.cs (line 8 claims "CRUD")
2. MISSING INPUT VALIDATORS
Severity: MEDIUM - Four request types lack corresponding validators:
TransitionStatusRequest (used in EncountersController)
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Models/Records/Encounter/TransitionStatusRequest.cs
Used at: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/EncountersController.cs (line 98)
Missing validator for DischargeDiagnosis length validation
RecordOrderResultRequest (used in OrdersController)
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Models/Records/Order/RecordOrderResultRequest.cs (line 1)
Used at: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/OrdersController.cs (line 103)
No validator exists
FhirPatientUpsertRequest (used in FhirIngestController)
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Models/Records/Fhir/FhirPatientUpsertRequest.cs
Used at: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirIngestController.cs (line 75)
FhirEncounterUpsertRequest (used in FhirIngestController)
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Models/Records/Fhir/FhirEncounterUpsertRequest.cs
Used at: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirIngestController.cs (line 100)
Existing validators (9 total):
AlertThresholdRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/AlertThresholdRequestValidator.cs (thorough threshold ordering validation)
AcknowledgeAlertRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/AcknowledgeAlertRequestValidator.cs
RegisterPatientRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/RegisterPatientRequestValidator.cs
OpenEncounterRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/OpenEncounterRequestValidator.cs
IngestObservationRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/IngestObservationRequestValidator.cs
TransitionOrderStatusRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/TransitionOrderStatusRequestValidator.cs
CreateOrderRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/CreateOrderRequestValidator.cs
LoginRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/LoginRequestValidator.cs
CreateMedicationAdministrationRequestValidator: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Validators/CreateMedicationAdministrationRequestValidator.cs
3. INCONSISTENT RESPONSE PATTERNS FOR FHIR RESOURCES
Severity: MEDIUM - FHIR endpoints use different serialization than rest of API
FhirMetadataController returns Content() with raw serialized FHIR JSON:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirMetadataController.cs (line 62)
Returns: Content(new FhirJsonSerializer()...)
FhirIngestController uses Serialize() helper method:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirIngestController.cs (lines 201-207)
Custom Serialize() method wraps FHIR resources
All other controllers use ApiResponse<T> wrapper:
Standard pattern: Ok(ApiResponse<T>.Ok(data))
Consistent across 15 other controllers
SofaController inconsistently uses Serialize() method internally:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/SofaController.cs (line 62-78)
But returns ApiResponse<object> with mapped data
4. MISSING SORTING/FILTERING ON LIST ENDPOINTS
Severity: MEDIUM - Inconsistent pagination and filtering across list endpoints
AlertThresholdsController.List() has NO pagination
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/AlertThresholdsController.cs (lines 37-44)
Returns all thresholds without limit
AnalyticsController endpoints use 0-based pagination for PatientSearch:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/AnalyticsController.cs (line 103)
[FromQuery] int page = 0 (0-based, inconsistent with rest)
Other List endpoints consistently use 1-based pagination with pageSize:
AlertsController: 1-based pagination (line 29-33)
EncountersController: 1-based pagination (line 29-33)
OrdersController: 1-based pagination (line 38-42)
MedicationsController: 1-based pagination (line 45-49)
PatientsController: 1-based pagination (line 42)
Cursor-based pagination endpoints (inconsistent limit defaults):
SofaController: default limit 20 (line 45)
News2Controller: default limit 20 (line 38)
ObservationsController: default limit 50 (line 80)
Endpoints with NO sorting parameter:
All list endpoints return hardcoded sort order (none accept sortBy/sortDirection params)
5. ROUTE PATTERN INCONSISTENCIES
Severity: LOW-MEDIUM - Mixed route definition styles
MedicationsController routes: Defined in [HttpGet/HttpPost] attributes:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/MedicationsController.cs (line 23, 42, 67)
Examples: [HttpPost("api/v1/encounters/{encounterId:guid}/medications")]
SepsisBundlesController: No [Route] attribute, routes fully in method attributes:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/SepsisBundlesController.cs (line 10)
Example routes at lines 20, 35
Standard pattern (most controllers): [Route] at class level
EncountersController: [Route("api/v1/encounters")]
PatientsController: [Route("api/v1/patients")]
AlertsController: No [Route], routes in methods
6. AUTHORIZATION CONSISTENCY
Severity: LOW - Mostly consistent but some anomalies
AllowAnonymous endpoints (2):
AuthController.Login: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/AuthController.cs (line 18)
FhirMetadataController.Metadata: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirMetadataController.cs (line 20)
FhirIngestController uses [Authorize] + AuthorizePermission at class level:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirIngestController.cs (lines 12-13)
Issue: Uses [ServiceFilter(typeof(FhirExceptionFilter))] without [Authorize] at method level, but has it at class level
Fine-grained permissions: Each operation has specific ClinicalPermissions
AlertsRead, AlertsAcknowledge, AlertsResolve
ThresholdsRead, ThresholdsWrite
EncountersRead, EncountersWrite
ObservationsIngest, etc.
7. ERROR HANDLING PATTERNS
Severity: LOW - Inconsistent but mostly functional
Enum parsing with try-catch (manual validation):
AlertsController: Lines 35-45, 79-116
EncountersController: Lines 36-59
OrdersController: Lines 44-54
Pattern: Manual catch of ArgumentOutOfRangeException with BadRequest() response
AuditLogsController: Direct query with manual range clamping:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/AuditLogsController.cs (line 30)
pageSize = Math.Clamp(pageSize, 1, 100);
FhirIngestController: Uses exception filter:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/FhirIngestController.cs (lines 13, 187)
Throws FhirMappingException caught by global filter
ObservationsController: Inline batch size validation:
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/ObservationsController.cs (lines 38-43)
Manual validation of batch 1-10 constraint
8. FHIR R4 COMPLIANCE ASSESSMENT
Severity: MEDIUM - Limited FHIR compliance, read-only inbound facade
Strengths:
Implements proper FHIR R4 CapabilityStatement (FhirMetadataController, line 19)
Returns OperationOutcome for errors on FHIR endpoints
Idempotent upserts by identifier (good pattern)
Supports transaction Bundles (line 186-192 FhirIngestController)
Proper FHIR content-type negotiation: [Consumes("application/fhir+json")][Produces("application/fhir+json")]
Gaps:
Read endpoints missing: Only POST/Create operations, no GET/read
CapabilityStatement only declares "Create" interactions (line 45)
No Read, Update, Delete, Search operations
Bundle transaction processing only: No other Bundle types supported (line 186-187)
Only 4 resource types: Patient, Encounter, Observation, MedicationAdministration
No FHIR Search: No fullUrl handling or search parameters
No resource versioning: No ETag or If-Match headers
9. SERVICE DEPENDENCY VERIFICATION
All services verified to exist (19 interface definitions found):
Path: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Services/Interfaces/
All injected services in controllers have corresponding interfaces
All service implementations exist:
Each controller's dependencies resolve to actual service implementations in /Services/ directory
10. VALIDATION COVERAGE GAPS
Request Type Validator Coverage
AlertThresholdRequest Yes Complete (threshold ordering validated)
AcknowledgeAlertRequest Yes Partial (only Note length)
RegisterPatientRequest Yes Good (DOB, BloodType, contacts)
OpenEncounterRequest Yes Yes
IngestObservationRequest Yes Yes
TransitionOrderStatusRequest Yes Minimal (only IsInEnum)
CreateOrderRequest Yes Minimal (Description, OrderedBy)
LoginRequest Yes Yes
CreateMedicationAdministrationRequest Yes Yes
TransitionStatusRequest NO Missing
RecordOrderResultRequest NO Missing
FhirPatientUpsertRequest NO Missing
FhirEncounterUpsertRequest NO Missing
11. RESPONSE TYPE CONSISTENCY
Standard ApiResponse wrapper (14 controllers):
Format: Ok(ApiResponse<T>.Ok(data))
Created: StatusCode(201, ApiResponse<T>.Created(data))
Error: BadRequest(ApiResponse<object>.Fail(code, message, errorCode))
Non-standard responses:
FhirMetadataController: Raw Content with FhirJsonSerializer (line 62)
FhirIngestController: Custom Serialize() helper (line 201)
News2Controller: Returns null in ApiResponse for missing scores (line 27)
GcsController: Returns null in ApiResponse for missing scores (line 28)
SofaController: Returns null in ApiResponse for missing scores (line 29)
12. MISSING/INCOMPLETE ENDPOINTS
AlertThresholdsController:
Missing: DELETE operation
Missing: Pagination on List() endpoint
SepsisBundlesController:
Missing: List endpoint (no way to query all bundles)
Missing: Pagination
QsofaController:
Missing: History endpoint (unlike News2, GCS, SOFA)
Only has "current" endpoint
PatientsController:
Missing: Update endpoint (register only, no edit demographics)
13. PAGINATION INCONSISTENCIES
Endpoint Pagination Style Default Limit Cursor Note
AlertThresholdsController.List None N/A No Returns all at once
AnalyticsController.PatientSearch 0-based page 20 No Non-standard (all others 1-based)
SofaController.History Cursor-based 20 Yes Cursor pagination
News2Controller.History Cursor-based 20 Yes Cursor pagination
ObservationsController.History Cursor-based 50 Yes Different limit than SOFA/NEWS2
All other List endpoints 1-based page 20 No Standard pagination
14. ENDPOINT SUMMARY
CREATE endpoints: 7
Patients, Encounters, Orders, Medications, Observations, AlertThresholds, + FHIR resources
READ endpoints: 23+
Get by ID, List, History, Current scores, Timeline, Search
UPDATE endpoints: 3
AlertThresholds (PUT), Orders (PATCH status), Encounters (PATCH status)
FHIR upserts (idempotent)
DELETE endpoints: 0
NONE - System is append-only/immutable
SPECIAL endpoints: 12
Alert acknowledge/resolve, Order result recording, Analytics queries, FHIR metadata
Total endpoint operations: 45+
RECOMMENDATIONS
Add missing validators for TransitionStatusRequest, RecordOrderResultRequest, and FHIR upsert requests
Implement DELETE operations or clarify why they're intentionally omitted
Standardize pagination: Use 1-based page/pageSize everywhere; add sorting parameters
Standardize FHIR response handling: Decide whether to wrap FHIR in ApiResponse or return raw FHIR
Add sorting parameters to list endpoints (orderBy, sortDirection)
Standardize cursor pagination defaults (NEWS2=20, SOFA=20, Observations=50 are inconsistent)
Add DELETE endpoint to AlertThresholds or remove from CRUD claim
Add pagination to AlertThresholds.List() with reasonable default limit
Implement FHIR Read operations and update CapabilityStatement to declare more interactions
Add missing validators for DischargeDiagnosis length and ResultSummary length
All controller files are located in: /home/sevia/Documents/Programming/Git/VigilCareClinical/VigilCareClinicalAPI/Controllers/
Controllers/API survey is back. Just waiting on the infrastructure/background services agent now.
Now I have comprehensive information. Let me compile my detailed infrastructure survey findings.
Infrastructure Survey Report: VigilCareClinical
I've completed a thorough examination of the VigilCareClinical project's infrastructure. Here are my detailed findings:
1. BACKGROUND SERVICES (9 main services + utility collectors)
Polling Services:
OutboxRelayService (/BackgroundServices/OutboxRelayService.cs)
Polls every 1000ms (configurable OutboxPollIntervalMs)
Polling Interval: 1000ms in dev (line 41)
Batch Size: 100 events (line 77)
Error Handling: Catches exceptions, logs warnings, continues next cycle (lines 49-51)
Shutdown: Disposes producer (line 142)
Strengths: Uses FOR UPDATE SKIP LOCKED to prevent race conditions (line 76), idempotent producer (EnableIdempotence=true, line 31), transaction-based (line 67)
Issues: No dead-letter queue or permanent failure handling—stuck events retry indefinitely
ThresholdCacheLoader (/BackgroundServices/ThresholdCacheLoader.cs)
Executes Once: On service startup
Error Handling: None—would crash startup if Redis unavailable
Issue: No retry logic for transient Redis failures
KafkaTopicProvisioner (/BackgroundServices/KafkaTopicProvisioner.cs)
Executes Once: On startup
Error Handling: Throws InvalidOperationException on failure (line 52)
Topic Creation: Replication factor hardcoded to 1 (line 38)—production risk: no fault tolerance
Auto-creation: Disabled in Kafka config (docker-compose.yml line 52)
Issue: Single partition replica means topic loss if broker fails
Kafka Consumer Services (subscribe to observation.recorded topic + variants):
SepsisEngineService (lines 21-35): Consumer group "sepsis-engine", qSOFA screening only
News2ScoringService (lines 21-35): Consumer group "news2-scoring"
GcsScoringService (lines 21-35): Consumer group "gcs-scoring"
TrendAnalyzerService (lines 21-35): Consumer group "trend-analyzer"
WarningAlertService (lines 21-35): Consumer group "warning-evaluator"
SofaScoringService (lines 21-37): Subscribes to both observation.recorded AND gcs.scored
EsIndexerService (lines 30-45): Consumer group "es-indexer", subscribes to 5 topics
DataLakeWriterService (lines 35-55): Consumer group "data-lake-writer", subscribes to 3 topics
Consumer Error Handling Pattern (consistent across all):
Manual commit disabled (EnableAutoCommit=false)
On exception: logs error, does NOT commit, delays 2000ms, retries (TrendAnalyzerService lines 66-77)
Issue: Poison pill scenario—a malformed message stops the entire consumer; infinite retry loop
Scoring Services (Periodic Timer-based):
SepsisBundleMonitorService (/BackgroundServices/SepsisBundleMonitorService.cs)
Interval: 5 minutes (line 5)
Operation: Scans for overdue sepsis bundles, marks as NON_COMPLIANT
Error Handling: Generic catch, logs, continues (line 31)
Metrics: Increments Prometheus counter (line 57)
WarningAlertService, News2ScoringService, GcsScoringService, SofaScoringService, TrendAnalyzerService
All use AutoOffsetReset.Earliest
All use manual commit on success
Issue: No max.poll.interval.ms override—consumer may be evicted from group if processing takes >5min (default)
2. OUTBOX PATTERN (OutboxRelayService)
Location: /BackgroundServices/OutboxRelayService.cs
Reliability Mechanisms:
✅ FOR UPDATE SKIP LOCKED prevents duplicates from concurrent relays
✅ Transactions ensure atomic read + publish + mark-processed
✅ Idempotent producer (EnableIdempotence=true, MessageSendMaxRetries=3, RetryBackoffMs=100)
✅ Partition key routing (ensures observation events for one encounter stay on same partition)
✅ Graceful producer disposal
Gaps:
❌ No Dead Letter Queue for permanently failed publishes—retries silently for 1000ms then continues
❌ No observability on publish latency
❌ No max retry limit before giving up permanently
❌ Failed batch stops processing (line 118: break on exception) but next cycle will retry same batch
❌ No circuit breaker pattern if Kafka is down
3. KAFKA INTEGRATION
Topic Provisioning (/BackgroundServices/KafkaTopicProvisioner.cs):
Topics: observation.recorded, alert.generated, alert.acknowledged, encounter.status.changed, sepsis.bundle.created, sepsis.bundle.updated, gcs.scored
Partitions: 6 (configurable, line 37)
Replication Factor: 1 (hardcoded, line 38)—critical production gap
Auto-create: Disabled (docker-compose.yml line 52)
Producer Configuration (OutboxRelayService lines 25-34):
Acks=All (line 27)
EnableIdempotence=true (line 31)
MessageSendMaxRetries=3, RetryBackoffMs=100ms
Consumer Configuration (all services):
AutoOffsetReset.Earliest (no loss on first start)
EnableAutoCommit=false (manual, only after successful processing)
No session.timeout.ms or max.poll.interval.ms overrides
Critical Gaps:
❌ No health check to verify broker connectivity
❌ Topics hardcoded in enums (KafkaTopicOptions); no dynamic discovery
❌ Consumer group offset reset strategy not configurable per environment
❌ No consumer lag monitoring endpoint (KafkaConsumerLagCollector exists but not exposed via HTTP)
❌ Partition key assignment not validated—rely on string hash collision
4. RABBITMQ INTEGRATION
Topology Provisioner (/Notifications/RabbitMqTopologyProvisioner.cs):
Exchange & Queues:
Exchange: clinical.notifications.exchange (direct, durable)
Queues (durable):
alerts.paging.queue → routing key "alerts.paging"
alerts.paging.dlq → x-message-ttl (configurable), re-routes to escalation on TTL
alerts.escalation.queue → routing key "alerts.escalation"
notifications.discharge.queue → routing key "notifications.discharge"
notifications.reconciliation.queue → routing key "notifications.reconciliation"
notifications.appointment.queue (placeholder)
Dead Letter Strategy (lines 92-96):
alerts.paging.dlq:
x-message-ttl = PagingAckTimeoutMs (5min prod, 5sec tests)
x-dead-letter-exchange = clinical.notifications.exchange
x-dead-letter-routing-key = alerts.escalation
Workers:
PagingWorkerService (/Notifications/PagingWorkerService.cs)
Prefetch=1 (exactly one page in flight)
Polls DB every 2 seconds for alert acknowledgment
Timeout = PagingAckTimeoutMs (5min prod)
On timeout: NACK with requeue=false → DLQ → escalation after TTL
Issue: Tight polling loop (2sec) instead of subscribe pattern
Issue: Long wait on acknowledgment blocks paging of other alerts (prefetch=1)
EscalationWorkerService (/Notifications/EscalationWorkerService.cs)
Prefetch=5 (parallel escalations)
Updates alert.status to Escalated in PostgreSQL
Issue: No idempotency check—double escalation possible if message reprocessed
DischargeSummaryWorkerService (/Notifications/DischargeSummaryWorkerService.cs)
Prefetch=3
Builds text summary from DB, uploads to MinIO
Issue: No retry on MinIO failure—NACK requeues indefinitely
NotificationPublisherService (/Notifications/NotificationPublisherService.cs)
Consumes from Kafka (alert.generated, encounter.status.changed)
Routes to RabbitMQ paging/discharge queues based on severity/status
Manual commit on route success
Configuration (/Configuration/RabbitMqOptions.cs):
Host, Port, Username, Password (all configurable)
PagingAckTimeoutMs (300000 prod, 5000 tests)
Critical Gaps:
❌ No queue persistence guarantee if RabbitMQ crashes before persisting to disk
❌ No maximum retry limit on escalation requeue
❌ DLQ with TTL assumes messages re-route successfully—no verification
❌ Queue cleanup on tests (line 51) is manual purge, not automatic
❌ No circuit breaker if RabbitMQ unavailable
5. ELASTICSEARCH INTEGRATION
Index Provisioning (/BackgroundServices/ElasticsSearch/ElasticIndexProvisioner.cs):
Indices: patient_encounters, observations, clinical_alerts
Mapping: Defines keyword/text/float/date fields
Issue: No index versioning or migration strategy (line 38: logs and skips if exists)
Indexing Service (/BackgroundServices/ElasticsSearch/EsIndexerService.cs):
Consumes 5 Kafka topics (observation.recorded, alert.generated, encounter.status.changed, sepsis.bundle.created, sepsis.bundle.updated)
Consumer group "es-indexer"
Upserts documents (patient_encounters uses DocAsUpsert=true, line 130)
Uses Painless scripts to update parent documents (lastObservationAt, openAlertCount)
Issue: openAlertCount increment not idempotent (comment line 204)—full replay only
Error Handling:
On ES write failure: logs error, does NOT commit, retries after 1sec (line 78)
Retry-on-conflict: 3 (line 193, 262) for script updates
Issue: If ES cluster is down, consumer halts indefinitely
Security:
Security disabled in docker-compose (line 67: xpack.security.enabled=false)
Production Gap: No TLS, no auth
Observability:
❌ No health check endpoint
❌ No shard allocation monitoring
❌ Lag collector doesn't include ES lag, only Kafka consumer lag
6. DATA LAKE SERVICES
DataLakeWriterService (/DataLake/DataLakeWriterService.cs):
Buffering Strategy:
Buffer in-memory per (topic, date, partition) key
Flush on: count threshold OR time threshold
FlushCount: 1000 (line 8 DataLakeOptions)
FlushIntervalSeconds: 300 (line 12)
Event extraction: observationId, encounterId, patientId, code, value, unit, source, recordedAt
Parquet Schema (/DataLake/ParquetFileBuilder.cs):
Observations: 11 fields (observation_id, encounter_id, patient_id, mrn, observation_code, value, unit, source, recorded_at, kafka_partition, kafka_offset)
Alerts: 9 fields
Encounters: 7 fields
File Naming & Partitioning:
Path: observations/YYYY/MM/DD/partition-{partition}-offset-{firstOffset:D10}.parquet
Date extracted from event timestamp (not wall clock)—handles out-of-order delivery
Error Handling:
Per-file flush failure: logs error, continues flushing other files (line 144-156)
On all files written successfully: commits offsets for all partitions (line 164)
Issue: Partial success—if 5 of 6 partitions fail, still commits other 5 offsets, losing retry opportunity for failed partition
Shutdown flush uses CancellationToken.None (line 96) to avoid TaskCanceledException
MinIO Configuration (/Configuration/MinioOptions.cs):
Endpoint, AccessKey, SecretKey, BucketName, UseSSL (all configurable)
Bucket auto-created if missing (line 307 DataLakeWriterService)
Issue: No SSL by default in config
Gaps:
❌ No parquet schema validation
❌ No compression (Parquet default is Snappy, not explicit)
❌ No retry on MinIO upload timeout
❌ Buffer cleared even if commit fails (line 176)—inconsistent state
7. AUTHENTICATION & AUTHORIZATION
JWT Setup (/Configuration/JwtOptions.cs, /Program.cs lines 24-50):
Issuer, Audience, SigningKey, ExpirationMinutes (all configurable)
Development: hardcoded signing key (256-bit required but not enforced)
Issue: No signing key validation on startup
Issue: No token refresh mechanism
Validation: IssuerSigningKey symmetric (line 39)
RBAC Implementation (/Authorization/ClinicalRolePermissionMap.cs):
Roles: Nurse, Physician, Admin, Integration
Permissions: PatientsRead, PatientsWrite, EncountersRead, EncountersWrite, ObservationsIngest, AlertsRead/Acknowledge/Resolve, ThresholdsRead/Write, AnalyticsRead, OrdersWrite, MedicationsWrite, FhirIngest, AuditRead, UsersAdmin
Permission Matrix (/Authorization/ClinicalRolePermissionMap.cs, lines 3-62):
Nurse: 12 permissions
Physician: 12 permissions (identical to Nurse)
Admin: 14 permissions (includes Thresholds.Write, Audit.Read, Users.Admin, FHIR.Ingest)
Integration: 5 permissions (data write only)
Authorization Flow (/Authorization/PermissionAuthorizationHandler.cs):
Custom handler checks clinical_role claim against ClinicalRolePermissionMap
Dynamic policy provider (PermissionPolicyProvider) parses "perm:PermissionName" policies
Attribute: [AuthorizePermission(ClinicalPermissions.AlertsRead)]
FHIR API Key (/Middlewares/FhirApiKeyOrJwtMiddleware.cs):
X-Api-Key header (line 46)
Fallback: JWT bearer token (line 34)
Creates synthetic claims for Mirth (integration role, line 51)
Issue: API key hardcoded in config, no rotation mechanism
Issue: No rate limiting
Gaps:
❌ No cryptographic validation of signing key strength
❌ No token revocation/blacklist
❌ No audit of authorization failures
❌ API key not rotatable without config redeploy
❌ No MFA
❌ Integration role uses fixed UUID (line 50)
8. MIDDLEWARE STACK
Order in Program.cs (lines 260-267):
CorrelationIdMiddleware (line 260)
FhirApiKeyOrJwtMiddleware (line 261)
ExceptionHandlerMiddleware (line 262)
CORS (line 264)
Authentication (line 266)
Authorization (line 267)
CorrelationIdMiddleware (/Middlewares/CorrelationIdMiddleware.cs):
Generates Guid if not in X-Correlation-Id header
Logs with LogContext.PushProperty("CorrelationId")
✅ Clean implementation, Serilog integration
ExceptionHandlerMiddleware (/Middlewares/ExceptionHandlerMiddleware.cs):
Catches specific exceptions: NotFoundException, ValidationException, ConflictException
Generic catch-all for others (line 36)
Issue: Swallows context.Response.StatusCode before catch (no StatusCode preservation)
Issue: No PII/sensitive data scrubbing
FhirApiKeyOrJwtMiddleware (/Middlewares/FhirApiKeyOrJwtMiddleware.cs):
Skips if not /fhir path
Skips if already authenticated
Checks X-Api-Key vs config (line 46)
Issue: Plaintext comparison vulnerable to timing attacks
Issue: Returns FHIR OperationOutcome but doesn't set Content-Type until after serialization (line 28 is early return)
9. CONFIGURATION MANAGEMENT
Configurable Areas (appsettings.json):
Database: ConnectionString
Redis: ConnectionString
Kafka: BootstrapServers, Topics (7), NumPartitions, OutboxBatchSize, OutboxPollIntervalMs
Elasticsearch: Uri, Index names
RabbitMQ: Host, Port, Username, Password, PagingAckTimeoutMs
MinIO: Endpoint, AccessKey, SecretKey, BucketName, UseSSL
ReconciliationJobs: IntervalMinutes (30), alert/order thresholds
DataLake: FlushCount (1000), FlushIntervalSeconds (300)
TrendDetection: WindowMinutes, MaxHistoryEntries, RateThresholds per vital
AlertSuppression: DefaultWindowMinutes
MedicationCorrelation: CorrelationWindowMinutes, DrugVitalMappings
Sofa: LabStalenessHours, VasopressorWindowHours
FHIR: ApiKey, PatientIdentifierSystems, DepartmentCodeMap, EncounterClassMap
JWT: Issuer, Audience, SigningKey, ExpirationMinutes
Dashboard: CorsOrigins
Hardcoded Values (potential issues):
Kafka replication factor = 1 (KafkaTopicProvisioner line 38)
RabbitMQ exchange name, queue names
Elasticsearch index names
PeriodicTimer intervals (5min, 30sec for collectors)
Prefetch counts (1 for paging, 3-5 for others)
Sensor-specific data (drug vital mappings)
Gaps:
❌ No validation on startup (e.g., JWT signing key length)
❌ No configuration schema versioning
❌ Secrets in plain config (appsettings.json has dev API key)
❌ No environment override precedence documented
❌ No health check configuration
10. FHIR MIDDLEWARE & BUNDLE PROCESSING
Bundle Processor (/Fhir/FhirBundleProcessor.cs):
Processes transaction bundles (transaction semantics—stop on first error, line 77)
Resource priority: Patient → Encounter → Observation/MedAdmin
Each resource type has dedicated mapper
Transaction Semantics: Response includes per-entry status and outcome
Error Handling:
Per-entry exception → 422 Unprocessable Entity with OperationOutcome (line 73)
Transaction stops on first error (line 77: break)
Issue: No rollback on partial writes to PostgreSQL
Issue: Outbox events may already be published before transaction error
FHIR Exception Filter (/Fhir/FhirExceptionFilter.cs):
Maps exceptions to HTTP status codes
Returns FHIR OperationOutcome JSON
Issue: No validation error details in OperationOutcome
Gaps:
❌ No validation of FHIR Bundles before processing
❌ No support for conditional creates/updates
❌ No reference resolution beyond identifier systems
❌ No batch mode (only transaction mode)
11. EXCEPTION HANDLING & OBSERVABILITY
Structured Logging (/Program.cs, lines 60-65):
Serilog with Console + Seq sinks
Enrichers: LogContext, MachineName, ThreadId
Override levels: AspNetCore=Warning, EFCore Command=Information
Development: Console + Seq; Testing: No bootstrap logger (skipped line 58)
Metrics (Prometheus):
AlertsUnacknowledgedGauge (30sec collector)
EscalationsTotal counter
SepsisBundleComplianceTotal counter
KafkaConsumerLag gauge per group
OutboxPendingCount gauge
Endpoint: /metrics (line 276)
Gaps:
❌ No request/response timing histograms
❌ No database connection pool metrics
❌ No Kafka publish latency histogram
❌ No circuit breaker state metrics
❌ No custom application metrics for clinical events
12. DOCKER & DEPLOYMENT
Services (docker-compose.yml):
PostgreSQL 16 (port 5436, volume pg_data)
Redis 7-alpine (port 6382)
Seq (logging sink, port 5345)
Kafka 3.7.0 (port 9092, single broker, auto-create disabled)
Elasticsearch 8.13.0 (port 9200, security disabled, 512MB heap)
RabbitMQ 3.13 (ports 5674 AMQP, 15674 mgmt)
MinIO (ports 9005 S3, 9006 console)
Prometheus 2.52.0 (port 9101)
Grafana 10.4.3 (port 3101)
Health Checks:
Elasticsearch: curl health endpoint (10s interval, 5s timeout, 10 retries)
RabbitMQ: rabbitmq-diagnostics ping (10s interval, 5s timeout, 5 retries)
Other services: No health checks defined
Networking:
Single bridge network (vigilcare_net)
All services interdependent (no explicit depends_on for startup order)
Gaps:
❌ No API service in docker-compose (only infrastructure)
❌ No volume backups or persistent snapshots
❌ No resource limits (CPU, memory)
❌ Kafka CLUSTER_ID hardcoded (generation instruction commented, line 54)
❌ No secrets management (hardcoded credentials for all services)
❌ Elasticsearch security disabled (xpack.security.enabled=false)
13. MISSING HEALTH CHECKS & READINESS PROBES
Current State:
❌ No /health endpoint
❌ No /ready endpoint
❌ No startup probe
❌ No Kafka broker connectivity check
❌ No PostgreSQL connectivity check
❌ No Redis connectivity check
❌ No RabbitMQ connectivity check
❌ No Elasticsearch cluster state check
Implications:
Kubernetes deployment would have no liveness/readiness probes
Service mesh routing would not detect unhealthy instances
Database migration failures would not fail service startup
14. CRITICAL INFRASTRUCTURE RISKS
Highest Priority:
Poison Pill Scenario: Malformed Kafka message causes consumer to halt (all consumer services retry indefinitely on exception)
Kafka Replication Factor = 1: Single broker failure loses all data (KafkaTopicProvisioner line 38)
No Circuit Breaker: If database/Kafka down, all background services halt processing with no backoff
Elasticsearch Auto-Mapping: Index mapping created on-demand, no validation of data types (Observations value auto-mapped to long, causing truncation)
Outbox No Dead-Letter: Failed publishes retry indefinitely with 1sec delays (OutboxRelayService line 118)
High Priority:
6. RabbitMQ Requeue Loops: Escalation worker may indefinitely requeue on db connection failure
7. Prefetch=1 Bottleneck: PagingWorkerService blocked for 5min per alert (not parallel paging)
8. DataLake Partial Commit: If some partitions fail flush, others still commit offsets (inconsistent state)
9. No API Key Rotation: FHIR API key hardcoded in config
10. Security Disabled: Elasticsearch with no auth/TLS in docker-compose
Medium Priority:
11. Timing attack vulnerability in API key comparison (string equality)
12. OperationOutcome doesn't include validation errors for FHIR bundles
13. ThresholdCacheLoader crashes startup if Redis unavailable (no retry)
14. Offset reset strategy (Earliest) means ALL historical data reprocessed on consumer group deletion
15. Metrics collector may block on slow Kafka admin API (no timeout)
Summary Table:
Area Status Key Metric Risk Level
Background Services 11 active 7 Kafka consumers HIGH
Outbox Pattern Implemented No DLQ HIGH
Kafka Provisioned RF=1 CRITICAL
RabbitMQ Provisioned DLQ+TTL MEDIUM
Elasticsearch Provisioned Manual indexing MEDIUM
DataLake Implemented Parquet buffering MEDIUM
Auth/RBAC Implemented Static API key HIGH
Middleware 3 active No metrics LOW
Configuration Externalized Hardcoded RF MEDIUM
FHIR Implemented No validation MEDIUM
Docker Minimal No probes MEDIUM
Observability Serilog + Prometheus No health endpoints HIGH
This infrastructure is production-adjacent but not production-ready without addressing the critical Kafka replication, circuit breaker, and health check gaps.