Skip to content

feat: dev infrastructure, mock removal, k8s standardization#527

Merged
aurelianware merged 6 commits intomainfrom
claude/dev-infrastructure-and-mock-removal
Mar 20, 2026
Merged

feat: dev infrastructure, mock removal, k8s standardization#527
aurelianware merged 6 commits intomainfrom
claude/dev-infrastructure-and-mock-removal

Conversation

@aurelianware
Copy link
Owner

Summary

  • Local dev stack: docker-compose.development.yml with all 11 backend services + MongoDB + Redis + seed data container. .env.example for credentials. appsettings.Development.json pointing portal to localhost ports.
  • Seed scripts: seed-demo-users.js (11 users, 7 departments) and rewritten seed-demo-data.js (525 documents across 12 collections with full cross-referential integrity — every claim references real members/providers, accumulators derived from actual paid amounts)
  • Mock data removal: Deleted 3,102 lines of mock fallbacks from ServiceImplementations.cs. Services now throw ServiceUnavailableException instead of returning fake data. 6 portal pages updated with dismissible MudAlert error handling.
  • K8s manifests: All 10 core services standardized (ACR image, /health/live+/ready probes, consistent resource limits). New payment-service manifest. Portal ConfigMap updated with all service URLs. Deploy script with build/push/apply/seed/verify.

Test plan

  • docker compose -f docker-compose.development.yml up -d — all services healthy
  • docker compose exec mongodb mongosh cloudhealthoffice --eval 'db.Claims.countDocuments()' returns 200
  • Portal connects to local services via appsettings.Development.json
  • Stop a backend service → portal shows MudAlert error, not fake data
  • kubectl apply all manifests — YAML validates, deployments roll out
  • scripts/deploy/deploy-core-services.sh --skip-build applies manifests successfully
  • Portal build: 0 errors (dotnet build passes)

🤖 Generated with Claude Code

aurelianware and others added 3 commits March 20, 2026 02:40
- docker-compose.development.yml: full 11-service backend + MongoDB/Redis + seed container
- .env.example: configurable credentials for local dev
- appsettings.Development.json: portal config pointing to localhost docker-compose ports
- seed-demo-users.js: 11 tenant users across 7 departments
- seed-demo-data.js: rewritten with 525 documents across 12 collections (Members, Claims,
  Providers, BenefitPlans, Sponsors, Authorizations, WorkQueueItems, Payments, Accumulators,
  Appeals, Correspondence, EnrollmentFiles) with full cross-referential integrity
- README.md: added Full Development Stack section

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all mock data fallbacks in ServiceImplementations.cs with
ServiceUnavailableException. When a backend service is down, users
now see a clear error message instead of fake data pretending to be real.

- ServiceImplementations.cs: 5,418 → 2,316 lines (-3,102 lines)
  Deleted all GetMock* methods across 18 service classes
  Changed catch(Exception) → catch(HttpRequestException) + throw
  Mutation methods that silently swallowed errors now properly throw
- ServiceUnavailableException.cs: new exception with ServiceName property
- 6 portal pages updated with MudAlert error handling for ServiceUnavailableException
- appsettings.json: added env var override documentation comments

Exceptions kept:
- MetricsService: mocks retained (marked TEMPORARY) until Prometheus is wired
- OperatingModeService: default fallback intentional (missing config = Replace mode)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Standardize all 10 core service k8s manifests with consistent configuration:
- ACR image: choacrhy6h2vdulfru6.azurecr.io/cloudhealthoffice-*
- Health probes: /health/live (liveness) + /health/ready (readiness)
- Resources: 100m/128Mi requests, 500m/512Mi limits
- MongoDB from shared mongodb-secret, database name from ConfigMap
- 1 replica per service

New: payment-service manifest (was the only one missing)
Relocated: member-service and coverage-service from infrastructure/k8s/ to src/services/*/k8s/
Updated: portal ConfigMap with PaymentService, TenantService, BillingService, RfaiService URLs

scripts/deploy/deploy-core-services.sh: build, push, apply, wait, seed, verify health
Supports --skip-build and --skip-seed flags.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 20, 2026 09:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR modernizes local development and Kubernetes deployment for Cloud Health Office by standardizing service manifests, adding a full local docker-compose stack with deterministic seed data, and updating the portal to surface real backend connectivity errors after mock fallbacks are removed.

Changes:

  • Adds a full local development stack (docker-compose.development.yml, .env.example) plus deterministic MongoDB seed scripts for demo users and cross-referenced operational data.
  • Standardizes Kubernetes manifests for multiple backend services (MongoDB config, health probes, ACR images, resource settings) and introduces a new payment-service manifest.
  • Updates the portal with new service URL configuration and adds ServiceUnavailableException handling surfaced via MudAlert on several pages.

Reviewed changes

Copilot reviewed 25 out of 27 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/services/tenant-service/k8s/tenant-service-deployment.yaml Moves tenant-service manifest toward standardized probes/config and MongoDB settings.
src/services/provider-service/k8s/provider-service-deployment.yaml Replaces CosmosDB config with MongoDB wiring and standard probes/resources.
src/services/payment-service/k8s/payment-service-deployment.yaml Adds a new k8s deployment/service for payment-service.
src/services/member-service/k8s/member-service-deployment.yaml Adds a new k8s deployment/service for member-service.
src/services/enrollment-import-service/k8s/enrollment-import-service-deployment.yaml Refactors manifest to standard layout and MongoDB settings; reorders Service/Deployment.
src/services/eligibility-service/k8s/eligibility-service-deployment.yaml Shifts eligibility-service manifest from Cosmos-oriented config to MongoDB + standard probes.
src/services/coverage-service/k8s/coverage-service-deployment.yaml Adds a new k8s deployment/service for coverage-service.
src/services/claims-service/k8s/claims-service-deployment.yaml Updates claims-service manifest to MongoDB/Redis secrets + standard probes/resources.
src/services/benefit-plan-service/k8s/benefit-plan-service-deployment.yaml Updates benefit-plan-service manifest to MongoDB/Redis + standard probes/resources.
src/services/authorization-service/k8s/authorization-service-deployment.yaml Updates authorization-service manifest to MongoDB + standard probes/resources.
src/portal/CloudHealthOffice.Portal/k8s/portal-deployment.yaml Adds portal ConfigMap entries/env vars for additional service URLs.
src/portal/CloudHealthOffice.Portal/appsettings.json Adds inline documentation comments and expands service URL configuration keys.
src/portal/CloudHealthOffice.Portal/appsettings.Development.json Adds a localhost-focused dev configuration for portal + local compose ports.
src/portal/CloudHealthOffice.Portal/Services/ServiceUnavailableException.cs Introduces a typed exception for backend transport failures.
src/portal/CloudHealthOffice.Portal/Pages/Providers.razor Displays backend unavailability via dismissible MudAlert.
src/portal/CloudHealthOffice.Portal/Pages/Members.razor Displays backend unavailability via dismissible MudAlert.
src/portal/CloudHealthOffice.Portal/Pages/Dashboard.razor Displays backend unavailability via dismissible MudAlert during dashboard loads.
src/portal/CloudHealthOffice.Portal/Pages/Claims.razor Displays backend unavailability via dismissible MudAlert for claims searches.
src/portal/CloudHealthOffice.Portal/Pages/BenefitPlans.razor Displays backend unavailability via dismissible MudAlert during plan loading.
src/portal/CloudHealthOffice.Portal/Pages/Authorizations.razor Displays backend unavailability via dismissible MudAlert for authorization searches.
scripts/setup/seed-demo-users.js Adds deterministic tenant user seed data for demos.
scripts/setup/seed-demo-data.js Rewrites demo data seeding with deterministic PRNG and expanded operational dataset.
scripts/deploy/deploy-core-services.sh Adds an end-to-end build/push/apply/seed/verify deployment helper for AKS.
docker-compose.development.yml Adds a full local dev stack (services + MongoDB + Redis + seed).
README.md Documents bringing up the full local development stack.
.env.example Provides example env vars for local compose credentials/ports.
Comments suppressed due to low confidence (3)

src/portal/CloudHealthOffice.Portal/Pages/Claims.razor:467

  • _serviceError is set when a backend is unreachable, but it’s not cleared on subsequent successful searches or when ClearSearch() resets the form. This can leave a stale MudAlert visible even after _searchResult is populated. Clear _serviceError at the start of PerformSearch() and in ClearSearch().
    private async Task PerformSearch()
    {
        _showValidationMessage = false;

        // Validate at least one search criterion
        if (string.IsNullOrWhiteSpace(_searchRequest.ClaimNumber)
            && _selectedMember == null
            && _selectedProvider == null
            && string.IsNullOrWhiteSpace(_searchRequest.ClaimType)
            && string.IsNullOrWhiteSpace(_searchRequest.Status)
            && _serviceDateRange?.Start == null)
        {
            _showValidationMessage = true;
            return;
        }

        _loading = true;
        _hasSearched = true;
        _currentPage = 1;

        try
        {
            // Populate member and provider IDs from selected items
            if (_selectedMember != null)
            {
                _searchRequest.MemberId = _selectedMember.MemberId;
            }

            if (_selectedProvider != null)
            {
                _searchRequest.ProviderId = _selectedProvider.ProviderId;
            }

            // Populate date range
            if (_serviceDateRange?.Start.HasValue == true)
            {
                _searchRequest.ServiceDateFrom = _serviceDateRange.Start.Value;
                _searchRequest.ServiceDateTo = _serviceDateRange.End ?? DateTime.Now;
            }

            _searchRequest.PageNumber = _currentPage;
            _searchResult = await ClaimsService.SearchClaimsAsync(_searchRequest);
        }
        catch (ServiceUnavailableException ex)
        {
            _serviceError = ex.Message;
            _searchResult = null;
        }
        catch (Exception ex)
        {
            Snackbar.Add($"Error searching claims: {ex.Message}", Severity.Error);
            _searchResult = null;
        }
        finally
        {
            _loading = false;
            StateHasChanged();
        }
    }

    private void ClearSearch()
    {
        _searchRequest = new()
        {
            PageSize = 25,
            SortBy = "SubmittedDate",
            SortOrder = "Descending"
        };
        _selectedMember = null;
        _selectedProvider = null;
        _serviceDateRange = null;
        _searchResult = null;
        _hasSearched = false;
        _currentPage = 1;
        _showValidationMessage = false;
    }

src/portal/CloudHealthOffice.Portal/Pages/Authorizations.razor:277

  • _serviceError is set when the authorization service is unavailable but never cleared on a later successful search or when clearing inputs, so a stale error MudAlert can persist. Clear _serviceError at the start of SearchAuthorizations() and in ClearSearch().
    private async Task SearchAuthorizations()
    {
        if (!HasCriteria)
        {
            _showValidationMessage = true;
            return;
        }

        _showValidationMessage = false;
        _loading = true;
        StateHasChanged();

        try
        {
            _allAuthorizations = await AuthorizationService.GetAuthorizationsAsync();
            ApplyFilters();
            _hasSearched = true;
        }
        catch (ServiceUnavailableException ex)
        {
            _serviceError = ex.Message;
            _allAuthorizations = new();
            _filteredAuthorizations = new();
            _hasSearched = true;
        }
        catch
        {
            _allAuthorizations = new();
            _filteredAuthorizations = new();
            _hasSearched = true;
        }
        finally
        {
            _loading = false;
            StateHasChanged();
        }

src/portal/CloudHealthOffice.Portal/Pages/Members.razor:217

  • _serviceError is set on ServiceUnavailableException but never cleared on success or when the user clicks Clear, so the error alert can persist across successful searches. Clear _serviceError when starting SearchMembers() and in ClearSearch().
    private async Task SearchMembers()
    {
        if (!HasCriteria)
        {
            _showValidationMessage = true;
            return;
        }

        _showValidationMessage = false;
        _loading = true;
        StateHasChanged();

        try
        {
            // Pass the most specific criterion to the service (ID > name > DOB)
            var term = !string.IsNullOrWhiteSpace(_memberIdSearch) ? _memberIdSearch
                     : !string.IsNullOrWhiteSpace(_lastNameSearch) ? _lastNameSearch
                     : _dobSearch;

            _members = await MemberService.SearchMembersAsync(term);
            _hasSearched = true;
        }
        catch (ServiceUnavailableException ex)
        {
            _serviceError = ex.Message;
            _members = new();
            _hasSearched = true;
        }
        catch
        {
            _members = new();
            _hasSearched = true;
        }
        finally
        {
            _loading = false;
            StateHasChanged();
        }
    }

    private void ClearSearch()
    {
        _memberIdSearch = string.Empty;
        _lastNameSearch = string.Empty;
        _dobSearch = string.Empty;
        _members = new();
        _hasSearched = false;
        _showValidationMessage = false;
    }

Comment on lines 198 to 217
private async Task LoadPlans()
{
_isLoading = true;
_allPlans = await BenefitPlanService.SearchBenefitPlansAsync();
ApplyFilters();
try
{
_allPlans = await BenefitPlanService.SearchBenefitPlansAsync();
ApplyFilters();
}
catch (ServiceUnavailableException ex)
{
_serviceError = ex.Message;
_allPlans = new();
_filteredPlans = new();
}
catch
{
_allPlans = new();
_filteredPlans = new();
}
_isLoading = false;
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

_serviceError is set in the catch, but LoadPlans() never clears it when a later load succeeds, so the error alert can remain visible even while showing data. Clear _serviceError before the try block (or after a successful load).

Copilot uses AI. Check for mistakes.
Comment on lines 427 to +469
try
{
metrics = await MetricsService.GetDashboardMetricsAsync();
_chartData = new double[] { metrics.ApprovedClaims, metrics.DeniedClaims, metrics.PendingClaims };
}
catch (ServiceUnavailableException ex)
{
_serviceError = ex.Message;
_metricsError = true;
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to load dashboard metrics");
_metricsError = true;
}

try
{
recentClaims = await ClaimsService.GetRecentClaimsAsync(10);
}
catch (ServiceUnavailableException ex)
{
_serviceError = ex.Message;
recentClaims = new();
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to load recent claims");
recentClaims = new();
}

try
{
// TODO: Add a server-side "recent authorizations" endpoint with limit/sort
// to avoid fetching the full list just for the dashboard's 5 most recent.
var allAuths = await AuthorizationService.GetAuthorizationsAsync();
recentAuthorizations = allAuths.OrderByDescending(a => a.RequestDate).Take(5).ToList();
}
catch (ServiceUnavailableException ex)
{
_serviceError = ex.Message;
recentAuthorizations = new();
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

_serviceError is assigned when any of the backend calls fail, but it’s never cleared if later calls succeed (or on page reload attempts), so the error alert can remain visible alongside healthy data. Clear _serviceError before the sequence of service calls, and consider whether you want one shared message or per-call messages (currently later failures overwrite earlier ones).

Copilot uses AI. Check for mistakes.
Comment on lines +659 to +662
renderingProviderNPI: provNpi,
renderingProviderName: provName,
facilityNPI: null,
facilityName: null,
facilityNPI: claimType === "Institutional" ? providerNpiByIdx[pick([16, 17, 18])] : null,
facilityName: claimType === "Institutional" ? providerNameByIdx[pick([16, 17, 18])] : null,
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

For institutional claims, facilityNPI and facilityName are chosen via two separate random pick([16,17,18]) calls. This can generate claims where the facility name doesn’t match the facility NPI, breaking the “cross-referential integrity” guarantee. Pick the facility index once and use it for both fields.

Copilot uses AI. Check for mistakes.
volumes:
- ./scripts/setup:/scripts:ro
command: >
mongosh mongodb://admin:securepassword123@mongodb:27017/cloudhealthoffice?authSource=admin
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The seed container uses a hard-coded MongoDB username/password in the connection string (admin:securepassword123) instead of the same ${MONGO_USER} / ${MONGO_PASSWORD} variables used elsewhere in the compose file. This makes .env overrides ineffective and increases the chance of committing/using the wrong credentials. Use the compose env vars in the connection string (and consider using mongodb service name + db from ${MONGO_DATABASE}).

Suggested change
mongosh mongodb://admin:securepassword123@mongodb:27017/cloudhealthoffice?authSource=admin
mongosh mongodb://${MONGO_USER:-admin}:${MONGO_PASSWORD:-securepassword123}@mongodb:27017/${MONGO_DATABASE:-cloudhealthoffice}?authSource=admin

Copilot uses AI. Check for mistakes.
Services__BenefitPlanService: "http://benefit-plan-service.cloudhealthoffice/api"
Services__PaymentService: "http://payment-service.cloudhealthoffice/api"
Services__TenantService: "http://tenant-service.cloudhealthoffice/api"
Services__BillingService: "http://payment-service.cloudhealthoffice/api"
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

Services__BillingService is configured to point at payment-service, but the portal’s premium billing client (IPremiumBillingService / PremiumBillingService) calls billing-cycle endpoints that payment-service does not expose. This will cause runtime 404s on /premium-billing and related features. Point Services__BillingService at the actual billing backend (and ensure that service is deployed), or remove/disable the premium billing feature in this environment so it can’t be selected.

Suggested change
Services__BillingService: "http://payment-service.cloudhealthoffice/api"
Services__BillingService: "http://billing-service.cloudhealthoffice/api"

Copilot uses AI. Check for mistakes.
"EligibilityService": "http://localhost:5007/api",
"TenantService": "http://localhost:5008/api",
"CoverageService": "http://localhost:5009/api",
"BillingService": "http://localhost:5006/api",
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

BillingService is set to http://localhost:5006/api (payment-service). The portal’s premium billing client uses billing-cycle endpoints that payment-service doesn’t provide, so local /premium-billing will fail even when payment-service is healthy. Update this to the correct billing service URL, or omit the setting until the billing service is part of the dev stack.

Suggested change
"BillingService": "http://localhost:5006/api",

Copilot uses AI. Check for mistakes.
Comment on lines 286 to +302
_showValidationMessage = false;
_loading = true;
StateHasChanged();

try
{
// Pass NPI as search term if filled, otherwise name
var searchTerm = !string.IsNullOrWhiteSpace(_npiSearch) ? _npiSearch : _nameSearch;
_providers = await ProviderService.SearchProvidersAsync(_selectedSpecialty, _selectedNetworkStatus, searchTerm);
_hasSearched = true;
}
catch (ServiceUnavailableException ex)
{
_serviceError = ex.Message;
_providers = new();
_hasSearched = true;
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

_serviceError is never cleared on a successful search (or when starting a new search), so an old error MudAlert can remain visible even after subsequent calls succeed unless the user manually closes it. Clear _serviceError at the start of SearchProviders() and in ClearSearch() when resetting the page state.

Copilot uses AI. Check for mistakes.
- Change @demo.local to @demo.test in seed-demo-users.js (PHI scan
  excludes .test TLD but not .local)
- Rewrite 5 test files to expect ServiceUnavailableException instead of
  removed mock fallback data
- Add calculation-engines.svg and services-overview.svg diagrams
- Reference new SVG diagrams in README.md
- Clear _serviceError before service calls in BenefitPlans.razor,
  Dashboard.razor, and Providers.razor so stale errors don't persist
  after successful loads
- Fix facility NPI/name mismatch in seed-demo-data.js by using single
  pick() call for both fields
- Use MONGO_USER/MONGO_PASSWORD env vars in docker-compose seed
  container instead of hardcoded credentials
- Point BillingService to billing-service (not payment-service) in
  portal-deployment.yaml and appsettings.Development.json
Add ServiceUnavailableException tests for all previously untested
HTTP-based portal services:
- ClaimsService, MemberService, EdiOperationsService
- PaymentRunService, PremiumBillingService, BenefitPlanService
- AuthorizationService, AttachmentService (with ITokenAcquisition mocks)
- ProviderService, ReportingService, EligibilityService
- CoverageService, SponsorService, ReferenceDataService
- WorkflowService (no ILogger, Argo Workflows)

Portal test count: 62 → 161 (99 new tests)
@github-actions
Copy link

Code Coverage

Package Line Rate Branch Rate Health
CloudHealthOffice.Portal 13% 3%
CloudHealthOffice.Portal 13% 3%
Summary 13% (2498 / 18662) 3% (174 / 5968)

@aurelianware aurelianware merged commit e1c5430 into main Mar 20, 2026
58 checks passed
@aurelianware aurelianware deleted the claude/dev-infrastructure-and-mock-removal branch March 20, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants