LogVisitChangeEventsBatch
Overview
LogVisitChangeEventsBatchTask detects and logs changes between visits in the batch table (VisitBatch) and their corresponding production versions (Visit). It compares key visit properties, charges, quotes, insurance, and custom properties to identify what changed, then creates VisitChangeEvent records in VisitChangeEventBatch with detailed reasons for each change.
Use this task when: You need to track which visits have changed before determining GFE compliance, auditing visit modifications, or triggering downstream processes that depend on specific types of visit changes.
Don't use this task when: You only need to sync batch data to production (use SyncBatchTask), or you're working with entirely new visits that have no production counterparts (all new visits are automatically flagged as VisitChange.Initial).
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| None | - | - | - | This task has no configurable parameters. It automatically compares all visits in VisitBatch against their corresponding records in the Visit table. |
Execution Flow
flowchart TD
A[Start LogVisitChangeEventsBatch] --> B[Clear VisitChangeEventBatch table]
B --> C[Load all visits from VisitBatch]
C --> D[Load all existing visits from Visit table that exist in batch]
D --> E[Load custom properties for batch visits]
E --> F[Load custom properties for existing visits]
F --> G[Load all quotes from QuoteBatch]
G --> H[Load all quotes from Quote table for batch visits]
H --> I[Join quotes to their respective visits]
I --> J[Create visit mapping dictionary by Key]
J --> K[For each batch visit]
K --> L{Visit exists in production?}
L -->|No| M[Create VisitChangeEvent with Initial reason]
L -->|Yes| N[Compare visit properties and quotes]
N --> O[Check for property changes]
O --> P[Check: Charges, Insurance, Deposit, Patient Amount, Estimate, Facility, Provider, Status, Scheduled Time, Visit Type]
P --> Q[Check: Auto Benefits Status, Custom Properties]
Q --> R[Check: Quote changes - New/Deleted/Modified]
R --> S{Any changes detected?}
S -->|Yes| T[Create VisitChangeEvent with specific reasons]
S -->|No| U[Skip - no event created]
M --> V[Add event to list]
T --> V
U --> W{More visits?}
V --> W
W -->|Yes| K
W -->|No| X[Assign entity keys to all events]
X --> Y[Bulk save all VisitChangeEvents to VisitChangeEventBatch]
Y --> Z[End]
style B fill:#ffe1e1
style N fill:#fff4e1
style X fill:#fff4e1
style Y fill:#e1f5ff
Processing Algorithm
The task uses a property-by-property comparison algorithm to detect changes:
Change Detection Logic
For each visit in VisitBatch:
Check if visit exists in production (
Visittable)- If not found: Create event with
VisitChange.Initialreason - If found: Proceed to detailed comparison
- If not found: Create event with
Compare basic visit properties:
Chargescollection →VisitChange.ChargesChangedInsuranceKey→VisitChange.InsuranceChangedDepositAmount→VisitChange.DepositAmountChangedAmountPatient→VisitChange.PatientAmountChangedAmountAllowed→VisitChange.EstimateChangedFacilityKey→VisitChange.FacilityChangedProviderID→VisitChange.ProviderChangedStatus→VisitChange.StatusChangedScheduledTime→VisitChange.ScheduledTimeChangedVisitType→VisitChange.VisitTypeChangedBenefits.AutoBenefitsStatuses→VisitChange.VisitAutoBenefitsStatusChanged
Compare custom properties:
- Count mismatch OR any property key/value difference →
VisitChange.CustomPropertiesChanged
- Count mismatch OR any property key/value difference →
Compare quotes:
- New quotes added →
VisitChange.NewQuote - Quotes removed →
VisitChange.QuoteDeleted - Quote benefits status changed →
VisitChange.QuoteAutoBenefitsStatusChanged
- New quotes added →
Create event only if changes detected:
- Events with zero reasons are filtered out
- Only visits with actual changes get
VisitChangeEventrecords
Example Execution
For a visit where insurance and provider changed:
Batch Visit V-1001:
InsuranceKey: "INS-BCBS"
ProviderID: "PRV-102"
AmountPatient: $850.00
Production Visit V-1001:
InsuranceKey: "INS-AETNA" (different)
ProviderID: "PRV-101" (different)
AmountPatient: $850.00 (same)
Comparison Result:
✓ InsuranceKey changed: INS-AETNA → INS-BCBS
✓ ProviderID changed: PRV-101 → PRV-102
✗ AmountPatient unchanged: $850.00
Created VisitChangeEvent:
Key: "VC-0001" (auto-generated)
Visit: "V-1001"
Reasons: [InsuranceChanged, ProviderChanged]
Saved to: VisitChangeEventBatch table
Usage Examples
Example 1: Basic task execution
var task = new LogVisitChangeEventsBatchTask();
// Task automatically compares VisitBatch vs Visit table
// No parameters needed
Example 2: Use before GFE compliance checking
// Common pattern: Log changes first, then check GFE compliance
var job = new Job
{
Key = "JOB-GFE-COMPLIANCE",
DisplayName = "Check GFE Compliance with Change Tracking",
Tasks = new List<Task>
{
// First, log what changed
new LogVisitChangeEventsBatchTask(),
// Then, check GFE compliance (uses change events to determine resends)
new CheckVisitGFEComplianceBatchTask()
}
};
Example 3: Audit visit changes in data pipeline
// Track changes during batch processing workflow
var job = new Job
{
Tasks = new List<Task>
{
// Load visits into batch
new LoadVisitsTask
{
VisitQuery = "SELECT * FROM ExternalVisits WHERE ModifiedDate > @LastRun"
},
// Process visits and calculate estimates
new BatchProcessAccountDataTask
{
Facilities = new List<string> { "FAC-001" },
Insurances = new List<string>() // All insurances
},
// Log what changed for audit trail
new LogVisitChangeEventsBatchTask(),
// Sync to production
new SyncBatchTask()
}
};
Example 4: Query change events after execution
// After running the task, query the events
var task = new LogVisitChangeEventsBatchTask();
task.Execute(customerDataStore);
// Query the logged events
var visitChangeEventRepo = new VisitChangeEventRepo(customerDataStore);
var visit = visitRepo.GetByKey("V-1001");
List<VisitChangeEvent> events = visitChangeEventRepo.GetVisitChangeEventsBatchForVisit(visit);
foreach (var evt in events)
{
Console.WriteLine($"Visit {evt.Visit} changed:");
foreach (var reason in evt.Reasons)
{
Console.WriteLine($" - {reason}");
}
}
Related Tasks Comparison
| Task | Purpose | Input → Output | Tracks Changes | Creates Events |
|---|---|---|---|---|
| LogVisitChangeEventsBatch | Log visit changes between batch and production | VisitBatch vs Visit → VisitChangeEventBatch | Yes (property-by-property) | Yes (batch table) |
CheckVisitGFEComplianceBatch |
Determine GFE compliance and resend requirements | VisitBatch + VisitChangeEventBatch → VisitGFEComplianceBatch | No (uses change events) | No (compliance records) |
SyncBatch |
Promote batch data to production | Batch tables → Production tables | No | No |
AutoSendLettersTask |
Generate and send patient letters | VisitBatch → Letters/Emails | No | Yes (send events) |
LoadVisitsTask |
Load visits from external sources | SQL queries → VisitBatch | No | No |
Key Differentiators:
- LogVisitChangeEventsBatch is the only task that compares batch vs production and creates detailed change events
- CheckVisitGFEComplianceBatch depends on LogVisitChangeEventsBatch output to determine if GFE resends are required
- SyncBatch moves data but doesn't track what changed
- Use LogVisitChangeEventsBatch before CheckVisitGFEComplianceBatch if you need to track what changed for GFE resend determination
Performance Considerations
Database Impact
Tables Read:
VisitBatch(all rows)Visit(filtered by keys in VisitBatch)QuoteBatch(all rows)Quote(filtered by visit keys in VisitBatch)CustomPropertyMapBatch(for visit entities)CustomPropertyMap(for visit entities)
Tables Written:
VisitChangeEventBatch(cleared then bulk inserted)
Query Patterns:
- Bulk loads all batch data into memory
- Single bulk save operation for all change events
- No row-by-row processing
Optimization Tips
| Scenario | Recommendation | Rationale |
|---|---|---|
| Large batch sizes (10K+ visits) | Run during off-hours | Task loads all visits and quotes into memory for comparison |
| Frequent unchanged visits | Task is still efficient | Only creates events for visits with actual changes |
| Chained with GFE compliance | Always run LogVisitChangeEventsBatch first | CheckVisitGFEComplianceBatch depends on VisitChangeEventBatch table |
| Audit trail requirements | Query VisitChangeEventBatch after task | Events are cleared on each run, so save to audit table if needed |
Memory Considerations
- All batch visits, quotes, and custom properties loaded into memory
- All production visits, quotes, and custom properties loaded into memory
- For 10,000 visits with average 3 quotes each, expect ~50MB memory usage
- Not suitable for extremely large batch sizes (100K+ visits) without pagination
Error Handling
The task uses a fail-fast approach with no error aggregation:
// If any repository operation fails, the entire task fails
// No try-catch blocks around individual visit comparisons
// All database operations use standard ADO.NET exception propagation
Important Behaviors:
- Database Connection Failures: Task immediately throws exception and exits
- Missing Visit in Production: Treated as a new visit (
VisitChange.Initialreason) - Missing Quotes: Empty quote collections are compared without error
- Missing Custom Properties: Empty custom property collections are compared without error
- Null Property Values: Standard null-safe comparison using
!=operator - Collection Comparisons: Uses
SequenceEqualfor charges andContentsMatchfor benefits
No Partial Results: If the task fails midway through:
VisitChangeEventBatchtable was cleared at start (no old data remains)- No new events are saved (bulk save happens at end)
- You must re-run the task to get change events
Common Pitfalls
| Issue | Problem | Solution |
|---|---|---|
| Events cleared on each run | VisitChangeEventBatch is truncated at task start, losing previous change history | Save events to a permanent audit table if you need historical change tracking beyond the current batch |
| IsResendRequired not triggering | CheckVisitGFEComplianceBatchTask returns false for IsResendRequired even though visit changed | Run LogVisitChangeEventsBatchTask BEFORE CheckVisitGFEComplianceBatchTask in your job task list |
| Change events for unchanged visits | Seeing VisitChangeEvents when you expect no changes | Check for subtle differences: decimal precision in amounts, date/time precision, quote ordering, or benefits collection differences |
| Missing change events | Expected VisitChangeEvents but none were created | Verify visits exist in both VisitBatch and Visit tables. Only existing visits with differences create events (new visits get "Initial" reason) |
| Memory issues with large batches | Task crashes or runs slowly with 50K+ visits | Split large batches into smaller chunks, or run during low-memory periods. Consider pagination if processing extremely large visit sets |
| Custom property changes not detected | CustomPropertiesChanged reason not appearing despite property changes | Verify custom properties are in CustomPropertyMapBatch and CustomPropertyMap tables. Task only compares properties that are actually stored in these tables |
| Quote changes not detected | NewQuote or QuoteDeleted reasons not appearing | Ensure quotes are in QuoteBatch and Quote tables. Task compares quotes by Key, so quote keys must match for change detection |
Code References
- Task Definition:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchTask.cs:8 - Task Field Definition:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchTask.cs:33 - Execution Entry Point:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchTask.cs:15 - Executor Implementation:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchExecutor.cs:18 - Main Execute Logic:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchExecutor.cs:41 - Visit Mapping Logic:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchExecutor.cs:73 - Change Event Creation:
MDClarityCore/Backend/Visits/LogVisitChangeEventsBatchExecutor.cs:91 - Change Detection Logic:
MDClarityCore/Backend/Scheduling/VisitChangeEventLogger.cs:71 - VisitChange Enum:
MDClarityCore/Models/Entities/VisitChangeEvent.cs:8 - VisitChangeEvent Entity:
MDClarityCore/Models/Entities/VisitChangeEvent.cs:34 - Integration Tests:
MDClarityCore/MDClarityTest/Tests/IntegrationTests/Backend/LogVisitChangeEventBatchTests.cs:16