Skip to content

LEGLINK-788: Improve Generated Patient Storage and Uploaded Patient Processing. - #1798

Open
nvmLantana wants to merge 6 commits into
devfrom
nm/LEGLINK-788_GeneratedPatientStorage
Open

LEGLINK-788: Improve Generated Patient Storage and Uploaded Patient Processing.#1798
nvmLantana wants to merge 6 commits into
devfrom
nm/LEGLINK-788_GeneratedPatientStorage

Conversation

@nvmLantana

@nvmLantana nvmLantana commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🛠️ Description of Changes

Generated patient bundles are now stored in ABS to avoid cosmosdb size limits
Generated patient bundles now use a caching/versioning system to reduce repetitive bundle saves (Each scenario will maintain a versioned cache of the generated data that is then uniquified per run, and the run is tied to that cache version).

Made Expunging patients and uploading their data to the fhir server for large patient bundle uploads when creating a scenario a background process that is monitored to avoid timeouts.

🧪 Testing Performed

Please describe the testing that was performed on the changes included in this PR.

🧑‍🔬 Unit Testing

  • I have written or updated unit tests to cover my changes
  • Coverage: 8.2%

📓 Documentation Updated

Please update any relevant sections in the project documentation that were impacted by the changes in the PR.

Summary by CodeRabbit

  • New Features
    • Patient replacement now runs asynchronously with progress tracking and a status endpoint.
    • Generated patient templates are cached and reused across runs for more consistent results.
    • Imported patient bundles are resolved automatically during execution.
  • Bug Fixes
    • Added validation to detect missing imported bundle content before processing.
    • Improved confirmation that patient data has been fully deleted before replaying bundles.
  • Improvements
    • Run summaries now include generated-template version details for improved traceability.

…e process uploaded patient files to avoid timeouts.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 787d4fac-b78c-4321-b44c-78543f2564b5

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds durable generated-patient template caching and version tracking, external imported-bundle resolution, asynchronous patient replacement with status polling, and related persistence and runtime updates.

Changes

Generated template caching

Layer / File(s) Summary
Template generation and persistence
DotNet/Automation/Generation/*, DotNet/Automation.UI/Services/Persistence/MongoGeneratedPatientTemplateCache.cs, DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs
The generation pipeline reads and stores deterministic templates, restores run-specific identifiers, and records version bindings. MongoDB and Blob Storage persist template payloads and metadata.
Run traceability
DotNet/Automation.Link/Models/AutomationRunSummary.cs, DotNet/Automation.UI/Services/AutomationRunManager.cs, DotNet/Automation.UI/Services/MutableRunState.cs, DotNet/Automation.UI/Services/Persistence/MongoDocuments.cs, DotNet/Automation.UI/Services/Persistence/MongoSnapshotStore.cs
Run state, documents, and summaries store generated-template version, scenario, and hash metadata.

Imported bundle execution

Layer / File(s) Summary
External bundle migration and resolution
DotNet/Automation.UI/Services/Persistence/PatientBundleExternalizationMigrationService.cs, DotNet/Automation.UI/Services/Persistence/ImportedBundleExecutionResolver.cs, DotNet/Automation.UI/Services/Persistence/MongoScenarioStore.cs, DotNet/Automation.UI/Services/RunExecutor.cs
Scenario reads preserve bundle references. Startup migration externalizes inline payloads. Run execution resolves referenced content before processing.
Service registration and validation
DotNet/Automation.UI/Program.cs, DotNet/Automation.UI/Controllers/ScenariosController.cs, DotNet/Automation/Generation/ImportedPatientInput.cs
The new services are registered, and imported bundle validation rejects missing content.

Asynchronous patient replacement

Layer / File(s) Summary
Replacement workflow
DotNet/Automation.UI/Services/PatientReplacementManager.cs, DotNet/Automation/FhirDataLoader.cs, DotNet/Automation.UI/Controllers/ScenariosController.cs
Patient replacement now queues purge and sequential replay work, waits for deletion, and exposes operation status.
Client polling
DotNet/Automation.UI/Views/Shared/_ScenarioEditorModal.cshtml
The scenario editor polls the status endpoint and reports progress, failures, and deletion results.

Runtime and controller maintenance

Layer / File(s) Summary
Controller and model cleanup
DotNet/Automation.UI/Controllers/*, DotNet/Automation.UI/Models/IdRequest.cs, DotNet/Automation.UI/Automation.UI.csproj
Shared IdRequest usage replaces nested request models. Active-run aggregation and sort handling are centralized. Project item exclusions are removed.
Runtime consistency
DotNet/Automation.UI/Services/ApiHealth/ApiHealthExecutionRunManager.cs, DotNet/Automation.UI/Services/RunSnapshotOrchestrator.cs, DotNet/Automation.UI/Services/RunExecutor.cs
Completion timestamps use one captured value, poller cleanup awaits disposal, and nullable normalization values use empty strings.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RunExecutor
  participant FhirGenerationPipeline
  participant MongoGeneratedPatientTemplateCache
  participant GeneratedTemplateCacheVersionStore
  participant MongoSnapshotStore
  RunExecutor->>FhirGenerationPipeline: Generate and upload with cache
  FhirGenerationPipeline->>MongoGeneratedPatientTemplateCache: Get or store template
  FhirGenerationPipeline-->>RunExecutor: Return generated template keys
  RunExecutor->>GeneratedTemplateCacheVersionStore: Bind keys to run version
  RunExecutor->>MongoSnapshotStore: Persist cache metadata
Loading
sequenceDiagram
  participant ScenarioEditor
  participant ScenariosController
  participant PatientReplacementManager
  participant FhirDataLoader
  ScenarioEditor->>ScenariosController: Start replacement
  ScenariosController->>PatientReplacementManager: Queue purge and replay
  PatientReplacementManager->>FhirDataLoader: Expunge and verify deletion
  PatientReplacementManager-->>ScenariosController: Return operation status URL
  ScenarioEditor->>ScenariosController: Poll status
  ScenariosController-->>ScenarioEditor: Return progress and outcome
Loading

Possibly related PRs

Suggested labels: config-change, db-change

Suggested reviewers: smailliwcs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main changes to generated patient storage and uploaded patient processing.
Description check ✅ Passed The description covers the main changes and includes all template sections, but testing and documentation details remain as placeholders.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch nm/LEGLINK-788_GeneratedPatientStorage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (11)
DotNet/Automation/Generation/FhirGenerationPipeline.cs (2)

809-823: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the self-referencing assembly name from the dependency list.

assembly is the Automation assembly itself. GetReferencedAssemblies() never returns the declaring assembly, so the "Automation" entry never matches and contributes nothing. The pipeline assembly identity is already covered by GetAssemblyIdentityHash(assembly) on Line 826.

♻️ Proposed cleanup
         var dependencyNames = new[]
         {
-            "Automation",
             "Hl7.Fhir.Base",
             "Hl7.Fhir.Support"
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation/Generation/FhirGenerationPipeline.cs` around lines 809 -
823, Remove the "Automation" entry from the dependencyNames array in
ComputeGeneratorDependencyFingerprint; retain only the externally referenced
dependency names, since the pipeline assembly is already included through
GetAssemblyIdentityHash(assembly).

336-369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add XUnit tests for the cache-hit and cache-miss branches.

This new if/else changes how patient entries are produced. Add focused unit tests for both branches with a mocked IGeneratedPatientTemplateCache (Moq): one test that asserts a store call on a miss, and one test that asserts the cached template run tag is materialized to the current run tag on a hit. Do not use network calls in these tests.

Based on learnings from the path instructions: "If/Else or Switch/Case blocks are introduced or modified — ensure each branch has a corresponding unit test" and "No network activity (HTTP calls, sockets, etc.) should appear in unit tests. Recommend using mocks (via Moq) for any external communication."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation/Generation/FhirGenerationPipeline.cs` around lines 336 -
369, Add focused XUnit tests covering both branches around the
generated-template cache flow, using a Moq mock of
IGeneratedPatientTemplateCache and no network calls. Verify the cache-miss test
invokes StoreAsync, and verify the cache-hit test replaces the cached template
run tag with the current run tag in the materialized bundle output.

Source: Path instructions

DotNet/Automation.UI/Services/Persistence/MongoGeneratedPatientTemplateCache.cs (1)

81-90: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reduce the per-store round trips to blob storage.

StoreAsync runs once per generated patient. Each call issues CreateIfNotExistsAsync, ExistsAsync, UploadAsync, and SetHttpHeadersAsync. Two of these are avoidable:

  • Create the container once (lazily, or in the same place the imported-bundle content store creates it) instead of on every store.
  • Set the content type in the upload call instead of a second SetHttpHeadersAsync request.
♻️ Proposed change for the upload call
-            using var stream = new MemoryStream(bytes, writable: false);
-            await blob.UploadAsync(stream, overwrite: true, cancellationToken: ct);
-            await blob.SetHttpHeadersAsync(new BlobHttpHeaders { ContentType = "application/json" }, cancellationToken: ct);
+            using var stream = new MemoryStream(bytes, writable: false);
+            await blob.UploadAsync(
+                stream,
+                new BlobUploadOptions { HttpHeaders = new BlobHttpHeaders { ContentType = "application/json" } },
+                cancellationToken: ct);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DotNet/Automation.UI/Services/Persistence/MongoGeneratedPatientTemplateCache.cs`
around lines 81 - 90, Update StoreAsync to avoid per-call container creation by
ensuring _container is created once through lazy initialization or the existing
imported-bundle container setup. Configure the JSON content type in
BlobUploadOptions during UploadAsync, then remove the separate
SetHttpHeadersAsync request while preserving the existing existence check and
cancellation behavior.
DotNet/Automation.UI/Services/AutomationRunManager.cs (1)

28-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider grouping the RunExecutor dependencies.

The constructor now takes 14 parameters and hand-builds RunExecutor from 12 of them. Each new execution dependency requires a change in two places. Register RunExecutor in the container and inject it, or pass a single options/dependencies record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation.UI/Services/AutomationRunManager.cs` around lines 28 - 66,
Refactor AutomationRunManager to avoid manually constructing RunExecutor with
its execution dependencies. Register RunExecutor in the dependency-injection
container and inject it into the AutomationRunManager constructor, then retain
the injected instance; alternatively, introduce a single dependencies/options
record and pass that record to RunExecutor.
DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs (1)

97-106: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

BuildScenarioKey can split one scenario across two keys.

The key falls back from scenarioId to a trimmed scenarioName, then to a template-set hash. A scenario that is run once by ID and once by name produces two independent version sequences for the same logical scenario. The name branch is also case-sensitive and free-form.

If that is intentional, add a short comment that states the precedence and its consequence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs`
around lines 97 - 106, Clarify the intended key precedence in BuildScenarioKey
by adding a short comment explaining that scenarioId takes priority over
scenarioName and templateKeys, and that using different identity inputs creates
separate version sequences. Preserve the current case-sensitive, trimmed-name
behavior unless the implementation is being changed intentionally.
DotNet/ServiceTests/UnitTests/AutomationUI/ImportedBundleBlobMigrationServiceTests.cs (2)

13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the test class and file to match the renamed service.

The service is now PatientBundleExternalizationMigrationService, but the class is still ImportedBundleBlobMigrationServiceTests and the file is still ImportedBundleBlobMigrationServiceTests.cs. Rename both to PatientBundleExternalizationMigrationServiceTests so the test type tracks the type under test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DotNet/ServiceTests/UnitTests/AutomationUI/ImportedBundleBlobMigrationServiceTests.cs`
at line 13, Rename the test file and the ImportedBundleBlobMigrationServiceTests
class to PatientBundleExternalizationMigrationServiceTests, keeping the existing
test contents unchanged so the test type matches the renamed service.

191-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add branch coverage for the new externalization logic.

ExternalizeBundleJsonAsync introduces new branches in PatientBundleExternalizationMigrationService: blank input, root JsonArray shape, root JsonObject with importedPatientBundles, unsupported shape, per-entry blank bundleJson, and the zero-migrated result. None of these have tests.

CreateService already mocks IMongoCollection and IImportedBundleContentStore with Moq, so the shape-detection branches can be covered with small focused tests and no network activity.

Do you want me to generate these XUnit tests?

Based on path instructions: "If/Else or Switch/Case blocks are introduced or modified — ensure each branch has a corresponding unit test" and "Large unit tests should be avoided; keeping unit tests small and focused on targeted business logic".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DotNet/ServiceTests/UnitTests/AutomationUI/ImportedBundleBlobMigrationServiceTests.cs`
around lines 191 - 212, Add focused xUnit tests for each branch introduced by
ExternalizeBundleJsonAsync in PatientBundleExternalizationMigrationService:
blank input, root JsonArray, root JsonObject containing importedPatientBundles,
unsupported root shape, blank per-entry bundleJson, and the zero-migrated
result. Reuse CreateService and its existing Moq dependencies, keep tests small,
and avoid network activity by mocking the content store and database
interactions as needed.

Source: Path instructions

DotNet/Automation.UI/Services/Persistence/ImportedBundleExecutionResolver.cs (1)

26-48: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Confirm the resolver stays within Cosmos DB for MongoDB RU limits for large scenarios.

ResolveAsync issues one Find with $in over all bundle ids, then one blob read per bundle in a sequential loop. The query shape is compatible with Cosmos DB for MongoDB RU. The sequential blob reads add latency proportional to the bundle count on the run path.

Consider bounded parallelism for _contentStore.ReadAsync when a scenario carries many imported bundles.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation.UI/Services/Persistence/ImportedBundleExecutionResolver.cs`
around lines 26 - 48, Update ResolveAsync’s content-loading loop to read
imported bundle content with bounded parallelism instead of sequentially
awaiting each _contentStore.ReadAsync call. Preserve the existing validation
errors, cancellation token usage, contentById population, and single MongoDB $in
query; limit concurrent reads to avoid unbounded load.

Source: Coding guidelines

DotNet/Automation.UI/Services/RunExecutor.cs (2)

362-379: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make the cache-version binding best-effort.

BindRunAsync runs after generation and upload complete. It writes traceability metadata only. If the store throws, the exception propagates to the handler at line 894, the run is marked Failed, and the completed generation work is discarded.

This file already treats non-essential persistence as best-effort. Lines 664-672 and 678-686 wrap snapshot writes in try/catch and emit a warning. Apply the same pattern here.

♻️ Proposed refactor to isolate the binding failure
-                var cacheBinding = await _generatedTemplateVersionStore.BindRunAsync(
-                    state.RunId,
-                    state.ScenarioId,
-                    state.RunNameOverride,
-                    pipelineResult.GeneratedTemplateKeys,
-                    state.RunCancellation.Token);
-                if (cacheBinding != null)
-                {
-                    lock (state.Sync)
-                    {
-                        state.GeneratedTemplateCacheVersionId = cacheBinding.VersionId;
-                        state.GeneratedTemplateCacheVersionNumber = cacheBinding.VersionNumber;
-                        state.GeneratedTemplateCacheScenarioKey = cacheBinding.ScenarioKey;
-                        state.GeneratedTemplateSetHash = cacheBinding.TemplateSetHash;
-                    }
-
-                    output.WriteLine($"[cache-version] Bound run to {cacheBinding.ScenarioKey} v{cacheBinding.VersionNumber} ({cacheBinding.VersionId}).");
-                }
+                try
+                {
+                    var cacheBinding = await _generatedTemplateVersionStore.BindRunAsync(
+                        state.RunId,
+                        state.ScenarioId,
+                        state.RunNameOverride,
+                        pipelineResult.GeneratedTemplateKeys,
+                        state.RunCancellation.Token);
+                    if (cacheBinding != null)
+                    {
+                        lock (state.Sync)
+                        {
+                            state.GeneratedTemplateCacheVersionId = cacheBinding.VersionId;
+                            state.GeneratedTemplateCacheVersionNumber = cacheBinding.VersionNumber;
+                            state.GeneratedTemplateCacheScenarioKey = cacheBinding.ScenarioKey;
+                            state.GeneratedTemplateSetHash = cacheBinding.TemplateSetHash;
+                        }
+
+                        output.WriteLine($"[cache-version] Bound run to {cacheBinding.ScenarioKey} v{cacheBinding.VersionNumber} ({cacheBinding.VersionId}).");
+                    }
+                }
+                catch (Exception bindEx) when (bindEx is not OperationCanceledException)
+                {
+                    output.WriteLine($"[WARN] Failed to bind generated-template cache version: {bindEx.Message}");
+                }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation.UI/Services/RunExecutor.cs` around lines 362 - 379, Wrap
the BindRunAsync call and its cache metadata updates in a try/catch so binding
failures do not propagate to the run handler or change a completed run to
Failed. Preserve successful state assignments and output, and emit a warning
through the existing logging pattern used for non-essential snapshot
persistence.

368-379: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add XUnit coverage for both branches of the cache-binding condition.

Line 368 introduces a new conditional. Add small XUnit tests that cover a non-null cacheBinding (state fields are populated) and a null cacheBinding (state fields stay unset). Mock GeneratedTemplateCacheVersionStore with Moq so no network activity occurs.

Do you want me to generate these tests?

Based on path instructions: "If/Else or Switch/Case blocks are introduced or modified — ensure each branch has a corresponding unit test" and "No network activity (HTTP calls, sockets, etc.) should appear in unit tests. Recommend using mocks (via Moq) for any external communication."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation.UI/Services/RunExecutor.cs` around lines 368 - 379, Add
focused XUnit coverage for the cacheBinding conditional in the relevant
RunExecutor tests: mock GeneratedTemplateCacheVersionStore with Moq, then verify
the non-null branch populates all GeneratedTemplateCache* state fields and the
null branch leaves them unset. Keep the tests isolated from network or other
external activity.

Source: Path instructions

DotNet/Automation.UI/Services/PatientReplacementManager.cs (1)

41-44: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Consider sharing a FhirDataLoader factory instead of constructing one per operation.

ExecuteAsync builds a new FhirDataLoader for every replacement. FhirDataLoader creates its own RestClient and authenticates in its constructor (FhirDataLoader.cs lines 35-44). ScenariosController repeats the same construction at lines 431 and 644. Each instance holds a separate connection pool.

A shared factory or a typed client registration would reuse connections and centralize the authentication configuration. This matches the repository guideline that prefers typed HttpClient clients with HttpClientFactory. Treat this as a follow-up, because FhirDataLoader uses RestSharp rather than HttpClient today.

As per coding guidelines: "Prefer typed HttpClient clients with HttpClientFactory and enable header propagation (e.g., Authorization)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNet/Automation.UI/Services/PatientReplacementManager.cs` around lines 41 -
44, The FhirDataLoader is constructed per operation, preventing reuse of its
RestClient and duplicating authentication setup. Introduce a shared
FhirDataLoader factory or typed client registration, then update ExecuteAsync
and the construction sites in ScenariosController to obtain instances through it
while centralizing FHIR configuration and preserving Authorization header
propagation.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DotNet/Automation.UI/Controllers/ScenariosController.cs`:
- Around line 360-362: Update the operation start call in the controller to pass
the trimmed patient identifier, matching the identifier used for
resourcesToDelete and BuildReplayBundles. Keep patientIdForLog limited to
sanitized logging purposes and do not use it as the operational id passed to
Start.

In `@DotNet/Automation.UI/Models/IdRequest.cs`:
- Around line 3-6: Update IdRequest validation and its consumers to reject
invalid ModelState or Guid.Empty identifiers with HTTP 400 before invoking any
store or persistence calls. Add shared validation around IdRequest so all
request handlers use the same checks, while preserving normal processing for
valid IDs.

In `@DotNet/Automation.UI/Program.cs`:
- Line 313: Update PatientReplacementManager to expire completed and failed
operations instead of retaining them for the process lifetime. Add a
terminal-state timestamp and periodic cleanup, or use an equivalent
bounded-cache strategy, while preserving active-operation tracking and status
retrieval until expiry.

In
`@DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs`:
- Around line 59-77: Update MongoIndexManager to register
automation_generated_template_versions with the required indexes: a composite
{ScenarioKey: 1, VersionNumber: -1} index supporting the filtered descending
queries, and a {ScenarioKey: 1, TemplateSetHash: 1} index supporting the version
lookup. Ensure these indexes are provisioned for the new collection alongside
its existing index configuration.
- Around line 74-94: Add a unique MongoDB index for the ScenarioKey and
TemplateSetHash fields during the GeneratedTemplateCacheVersionStore
initialization/setup. Update BindRunAsync to catch duplicate-key insertion
failures, reread the existing matching document, and return its binding so
concurrent runs share the first writer’s version; retain the existing
latest-version allocation for new template hashes.

In
`@DotNet/Automation.UI/Services/Persistence/ImportedBundleExecutionResolver.cs`:
- Around line 50-62: Update ResolveAsync to accept inputs without
UploadedBundleId when their inline BundleJson is present. Remove the
unconditional unresolved-input failure and resolve each input by preferring
contentById for a valid uploaded-bundle reference, otherwise using the input’s
inline BundleJson; throw only when both are unavailable, while preserving the
existing missing-content error context.

In
`@DotNet/Automation.UI/Services/Persistence/MongoGeneratedPatientTemplateCache.cs`:
- Around line 42-45: Update the blob-root construction in
MongoGeneratedPatientTemplateCache so generated templates never derive their
prefix from ImportedBundleBlobStorageSettings.BlobRoot. Use a dedicated
template-root setting, or the container root, and retain the
generated-patient-templates suffix without nesting under
automation/imported-bundles.
- Around line 48-102: Update GeneratedPatientTemplateCache.GetAsync and
StoreAsync to inject and use a logger, catching non-cancellation exceptions from
MongoDB, blob, and JSON operations; GetAsync must log and return null, while
StoreAsync must log and return without throwing. Preserve cancellation
propagation by rethrowing cancellation exceptions, and keep StoreAsync’s
existing argument validation exceptions outside the catch scope.

In `@DotNet/Automation.UI/Services/Persistence/MongoSnapshotStore.cs`:
- Around line 97-101: Update UpsertRunSummaryAsync so the four
generated-template cache fields—GeneratedTemplateCacheVersionId,
GeneratedTemplateCacheVersionNumber, GeneratedTemplateCacheScenarioKey, and
GeneratedTemplateSetHash—are preserved when an incoming summary does not provide
them, while still allowing explicitly deleted fields to remain deleted. Ensure
fresh DashboardSeedService summaries cannot overwrite existing cache bindings
with empty values, and retain the existing behavior for populated cache fields.

In
`@DotNet/Automation.UI/Services/Persistence/PatientBundleExternalizationMigrationService.cs`:
- Around line 68-106: Update MigrateEmbeddedPayloadsAsync to process both
scenario and run-input documents in MigrationBatchSize batches instead of
calling ToListAsync without limits. Project only the fields used by each pass,
and apply the existing InterBatchPause between batches, reusing the batching
pattern from MigrateInlinePayloadsToAbsAsync while preserving the current
updates and migration counts.
- Around line 113-124: Update ExternalizeBundleJsonAsync to catch JsonNode.Parse
failures and treat malformed JSON as unprocessable by returning the original
json with no bundles and zero externalized items, matching
RemapUploadedBundleIdsInJson’s skip behavior. Ensure malformed payloads do not
propagate exceptions into StartAsync.

In `@DotNet/Automation.UI/Services/RunSnapshotOrchestrator.cs`:
- Around line 254-271: Update DisposeUnregisteredPollerAsync to catch unexpected
exceptions from await task in addition to OperationCanceledException, log the
fault through the existing service logger, and preserve the finally block so
scope and cts are always disposed.

In `@DotNet/Automation.UI/Views/Shared/_ScenarioEditorModal.cshtml`:
- Around line 610-612: Update the importedUploadStatus span in the scenario
editor modal to include role="status" and aria-live="polite", preserving its
existing content and behavior so setImportedUploadStatus updates are announced
to assistive technology.
- Around line 602-624: Update waitForPatientReplacement to enforce a finite
polling deadline or attempt limit, throwing a clear timeout error when the
replacement does not reach a terminal status. Ensure the upload handler’s
existing error flow can surface that timeout and release the busy state, and
stop further polling when the scenario editor modal is closed by checking the
modal’s active/open state before each request and delay.

In `@DotNet/Automation/FhirDataLoader.cs`:
- Around line 106-135: Add one propagated operation deadline across
DotNet/Automation/FhirDataLoader.cs lines 106-135,
DotNet/Automation.UI/Services/PatientReplacementManager.cs lines 13-25, and
DotNet/Automation.UI/Views/Shared/_ScenarioEditorModal.cshtml lines 602-624:
update WaitForPatientDeletionAsync to accept a timeout, use a
bounded/increasing-delay loop like WaitForServerAsync, and throw
TimeoutException; have PatientReplacementManager.Start create the timeout CTS,
pass its token through ExecuteAsync, DeleteResourcesWithExpungeAsync, and
WaitForPatientDeletionAsync, translate OperationCanceledException to
operation.Fail, and evict completed operations after a retention period; add a
maximum and cancellation-on-close to the scenario editor’s polling loop so it
stops with a timeout instead of running forever.

In `@DotNet/Automation/Generation/FhirGenerationPipeline.cs`:
- Around line 878-884: Update ReplaceRunTag and the cache path around
GenerateAndUploadAsync to validate run tags before template replacement. Add or
reuse IsSafeRunTagForTemplateCache to accept only the expected generated run-tag
shape, and skip caching by setting generatedTemplateCache to null when
ids.RunTag is unsafe; preserve replacement behavior for validated tags.

---

Nitpick comments:
In `@DotNet/Automation.UI/Services/AutomationRunManager.cs`:
- Around line 28-66: Refactor AutomationRunManager to avoid manually
constructing RunExecutor with its execution dependencies. Register RunExecutor
in the dependency-injection container and inject it into the
AutomationRunManager constructor, then retain the injected instance;
alternatively, introduce a single dependencies/options record and pass that
record to RunExecutor.

In `@DotNet/Automation.UI/Services/PatientReplacementManager.cs`:
- Around line 41-44: The FhirDataLoader is constructed per operation, preventing
reuse of its RestClient and duplicating authentication setup. Introduce a shared
FhirDataLoader factory or typed client registration, then update ExecuteAsync
and the construction sites in ScenariosController to obtain instances through it
while centralizing FHIR configuration and preserving Authorization header
propagation.

In
`@DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs`:
- Around line 97-106: Clarify the intended key precedence in BuildScenarioKey by
adding a short comment explaining that scenarioId takes priority over
scenarioName and templateKeys, and that using different identity inputs creates
separate version sequences. Preserve the current case-sensitive, trimmed-name
behavior unless the implementation is being changed intentionally.

In
`@DotNet/Automation.UI/Services/Persistence/ImportedBundleExecutionResolver.cs`:
- Around line 26-48: Update ResolveAsync’s content-loading loop to read imported
bundle content with bounded parallelism instead of sequentially awaiting each
_contentStore.ReadAsync call. Preserve the existing validation errors,
cancellation token usage, contentById population, and single MongoDB $in query;
limit concurrent reads to avoid unbounded load.

In
`@DotNet/Automation.UI/Services/Persistence/MongoGeneratedPatientTemplateCache.cs`:
- Around line 81-90: Update StoreAsync to avoid per-call container creation by
ensuring _container is created once through lazy initialization or the existing
imported-bundle container setup. Configure the JSON content type in
BlobUploadOptions during UploadAsync, then remove the separate
SetHttpHeadersAsync request while preserving the existing existence check and
cancellation behavior.

In `@DotNet/Automation.UI/Services/RunExecutor.cs`:
- Around line 362-379: Wrap the BindRunAsync call and its cache metadata updates
in a try/catch so binding failures do not propagate to the run handler or change
a completed run to Failed. Preserve successful state assignments and output, and
emit a warning through the existing logging pattern used for non-essential
snapshot persistence.
- Around line 368-379: Add focused XUnit coverage for the cacheBinding
conditional in the relevant RunExecutor tests: mock
GeneratedTemplateCacheVersionStore with Moq, then verify the non-null branch
populates all GeneratedTemplateCache* state fields and the null branch leaves
them unset. Keep the tests isolated from network or other external activity.

In `@DotNet/Automation/Generation/FhirGenerationPipeline.cs`:
- Around line 809-823: Remove the "Automation" entry from the dependencyNames
array in ComputeGeneratorDependencyFingerprint; retain only the externally
referenced dependency names, since the pipeline assembly is already included
through GetAssemblyIdentityHash(assembly).
- Around line 336-369: Add focused XUnit tests covering both branches around the
generated-template cache flow, using a Moq mock of
IGeneratedPatientTemplateCache and no network calls. Verify the cache-miss test
invokes StoreAsync, and verify the cache-hit test replaces the cached template
run tag with the current run tag in the materialized bundle output.

In
`@DotNet/ServiceTests/UnitTests/AutomationUI/ImportedBundleBlobMigrationServiceTests.cs`:
- Line 13: Rename the test file and the ImportedBundleBlobMigrationServiceTests
class to PatientBundleExternalizationMigrationServiceTests, keeping the existing
test contents unchanged so the test type matches the renamed service.
- Around line 191-212: Add focused xUnit tests for each branch introduced by
ExternalizeBundleJsonAsync in PatientBundleExternalizationMigrationService:
blank input, root JsonArray, root JsonObject containing importedPatientBundles,
unsupported root shape, blank per-entry bundleJson, and the zero-migrated
result. Reuse CreateService and its existing Moq dependencies, keep tests small,
and avoid network activity by mocking the content store and database
interactions as needed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ff1c8cd-8b2f-4265-a1a4-ef797edf44eb

📥 Commits

Reviewing files that changed from the base of the PR and between ec45dd0 and 69ed426.

📒 Files selected for processing (36)
  • .github/copilot-instructions.md
  • DotNet/Automation.Link/Models/AutomationRunSummary.cs
  • DotNet/Automation.UI/Automation.UI.csproj
  • DotNet/Automation.UI/Controllers/NormalizationsController.cs
  • DotNet/Automation.UI/Controllers/OrganizationResourceMapsController.cs
  • DotNet/Automation.UI/Controllers/QueryPlansController.cs
  • DotNet/Automation.UI/Controllers/RunsController.cs
  • DotNet/Automation.UI/Controllers/ScenariosController.cs
  • DotNet/Automation.UI/Models/IdRequest.cs
  • DotNet/Automation.UI/Program.cs
  • DotNet/Automation.UI/Services/ApiHealth/ApiHealthExecutionRunManager.cs
  • DotNet/Automation.UI/Services/ApiHealth/TestSuites/IServiceTestSuite.cs
  • DotNet/Automation.UI/Services/AutomationRunManager.cs
  • DotNet/Automation.UI/Services/MutableRunState.cs
  • DotNet/Automation.UI/Services/PatientReplacementManager.cs
  • DotNet/Automation.UI/Services/Persistence/ApiHealthRunDocument.cs
  • DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs
  • DotNet/Automation.UI/Services/Persistence/ImportedBundleExecutionResolver.cs
  • DotNet/Automation.UI/Services/Persistence/MongoApiHealthRunStore.cs
  • DotNet/Automation.UI/Services/Persistence/MongoDocuments.cs
  • DotNet/Automation.UI/Services/Persistence/MongoGeneratedPatientTemplateCache.cs
  • DotNet/Automation.UI/Services/Persistence/MongoScenarioStore.cs
  • DotNet/Automation.UI/Services/Persistence/MongoSnapshotStore.cs
  • DotNet/Automation.UI/Services/Persistence/PatientBundleExternalizationMigrationService.cs
  • DotNet/Automation.UI/Services/Persistence/TestScenarioDocument.cs
  • DotNet/Automation.UI/Services/QueryPlanTemplateSeedService.cs
  • DotNet/Automation.UI/Services/RunExecutor.cs
  • DotNet/Automation.UI/Services/RunHub.cs
  • DotNet/Automation.UI/Services/RunSnapshotOrchestrator.cs
  • DotNet/Automation.UI/Views/Shared/_ScenarioEditorModal.cshtml
  • DotNet/Automation/FhirDataLoader.cs
  • DotNet/Automation/Generation/FhirBundleGenerator.cs
  • DotNet/Automation/Generation/FhirGenerationPipeline.cs
  • DotNet/Automation/Generation/IGeneratedPatientTemplateCache.cs
  • DotNet/Automation/Generation/ImportedPatientInput.cs
  • DotNet/ServiceTests/UnitTests/AutomationUI/ImportedBundleBlobMigrationServiceTests.cs
💤 Files with no reviewable changes (9)
  • DotNet/Automation.UI/Services/Persistence/ApiHealthRunDocument.cs
  • DotNet/Automation.UI/Services/Persistence/MongoApiHealthRunStore.cs
  • DotNet/Automation.UI/Controllers/OrganizationResourceMapsController.cs
  • DotNet/Automation.UI/Controllers/QueryPlansController.cs
  • DotNet/Automation.UI/Controllers/NormalizationsController.cs
  • DotNet/Automation.UI/Automation.UI.csproj
  • DotNet/Automation.UI/Services/ApiHealth/TestSuites/IServiceTestSuite.cs
  • DotNet/Automation.UI/Services/RunHub.cs
  • DotNet/Automation.UI/Services/QueryPlanTemplateSeedService.cs

Comment thread DotNet/Automation.UI/Controllers/ScenariosController.cs Outdated
Comment thread DotNet/Automation.UI/Models/IdRequest.cs
Comment thread DotNet/Automation.UI/Program.cs
Comment thread DotNet/Automation.UI/Services/Persistence/GeneratedTemplateCacheVersionStore.cs Outdated
Comment thread DotNet/Automation.UI/Services/RunSnapshotOrchestrator.cs
Comment thread DotNet/Automation.UI/Views/Shared/_ScenarioEditorModal.cshtml
Comment thread DotNet/Automation.UI/Views/Shared/_ScenarioEditorModal.cshtml Outdated
Comment thread DotNet/Automation/FhirDataLoader.cs
Comment thread DotNet/Automation/Generation/FhirGenerationPipeline.cs
@nvmLantana
nvmLantana requested review from a team as code owners August 7, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant