Skip to content

About

Source-available dental practice management platform built with .NET Blazor Server & microservices. Patient scheduling, 837D claims, eligibility (270/271), ERA/835, and multi-tenant SaaS. Business Source License 1.1.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Cloud Dental Office

Modern SaaS Practice Management Platform for Dental Providers

A cloud-native, microservices-based dental practice management system built from the ground up with .NET 8, Blazor Server and deep payer interoperability.

License: BSL 1.1 .NET Blazor Server Architecture


Architecture

Cloud Dental Office uses a microservices architecture with each bounded context deployed as an independent service:

┌─────────────────────────────────────────────────────────────┐
│                    Blazor Server Portal                      │
│         (MudBlazor · Dark Theme · Real-time · AI Vision)     │
└──────────────────────────┬──────────────────────────────────┘
                           │
                    ┌──────┴──────┐
                    │ API Gateway │  ← YARP Reverse Proxy
                    │   :5200     │
                    └──────┬──────┘
         ┌─────────┬───────┼───────┬──────────┬──────────┬──────────┐
         │         │       │       │          │          │          │
    ┌────┴───┐ ┌───┴────┐ ┌┴─────┐ ┌┴────────┐ ┌┴──────┐ ┌┴─────┐ ┌┴─────────┐
    │Patient │ │Schedule│ │Claims│ │Eligiblty│ │  ERA  │ │ Auth │ │Rx (EPCS) │
    │Service │ │Service │ │Svc   │ │Service  │ │Service│ │  Svc │ │ :5107    │
    │ :5101  │ │ :5102  │ │:5103 │ │ :5104   │ │ :5105 │ │:5106 │ └─────┬────┘
    └────┬───┘ └───┬────┘ └┬─────┘ └┬────────┘ └┬──────┘ └┬─────┘       │
         │         │       │        │           │         │             │
         │         │       │        │           │     ┌───┴──────┐      │
         │         │       │        │           │     │AI Vision │      │
         │         │       │        │           │     │ Service  │      │
         │         │       │        │           │     │ :5108    │      │
         │         │       │        │           │     └────┬─────┘      │
         │         │       │        │           │          │            │
         └─────────┴───────┴────────┴───────────┴──────────┴────────────┘
                        PostgreSQL (per-service DB)
              ┌──────────────────────┴────────────────────┐
              │  privaseeAI Edge Devices (IP Cameras,     │
              │  Tablets, Raspberry Pi) + Azure AI Vision │
              └───────────────────────────────────────────┘

Services

Service Port Description
Portal 5000 Blazor Server UI — dashboard, patient management, claims, scheduling, e-prescribing, AI vision
API Gateway 5200 YARP reverse proxy routing to all backend services
PatientService 5101 Not deployed. Patients live in the Portal database; Zocdoc patient resolution uses the Portal's internal-only port 5091. Kept in the repo pending retirement.
SchedulingService 5102 Appointments, operatory management, provider calendars
ClaimsService 5103 Claim lifecycle (draft → submit → adjudicate), 837D generation
EligibilityService 5104 Real-time 270/271 eligibility verification
EraService 5105 835 ERA file processing, claim matching, auto-posting
AuthService 5106 Tenant-aware identity, membership-derived roles, password verification, and short-lived JWT issuance
PrescriptionService 5107 e-Prescribing with DoseSpot integration, EPCS compliance, Surescripts certified
VisionService 5108 AI vision platform — privaseeAI integration, insurance card OCR (Azure AI Vision), narcotics cabinet monitoring, consent recording, clinical note generation
IntakeService 5109 Isolated public intake. Publishes validated booking requests and stores only minimized, durable integration-inbox records; it has no read access to patient/clinical systems.

Shared Libraries

  • CloudDentalOffice.Contracts — DTOs, integration events, and API contracts shared across services
  • CloudDentalOffice.EdiCommon — Clean-room X12 EDI parser and generators (837D, 270/271, 835)
  • CloudDentalOffice.Messaging — Service Bus event-publishing abstraction (IEventPublisher)

Public Website Booking Intake

Practice websites (e.g. 3rd Set Smiles) submit appointment requests through a dedicated public service with no read access to patient, clinical, or scheduling databases. It accepts only the minimum information necessary for appointment intake.

flowchart LR
    Website[Practice website] --> Intake[IntakeService]
    Intake --> Bus[Service Bus]
    Bus --> Request[BookingRequest]
    Request --> Review[Staff review]
    Review --> Patient[Match or create patient]
    Patient --> Approval[Approve & Schedule]
    Approval --> Appointment[Appointment]
Loading

BookingRequest is the visitor's scheduling intent. Appointment is a practice-confirmed event created only after staff review and approval.

IntakeService (the only internet-facing component) exposes:

POST /api/public/booking-requests
  • Disabled by default — returns 404 unless PublicBooking:Enabled is true.
  • Requires an API key — Authorization: Bearer <key> or X-Api-Key: <key>, compared in constant time. Missing/wrong key → 401.
  • Rate limited — fixed window, 5 requests/minute per client IP. Forwarded addresses are honored only from explicitly configured TrustedProxies.
  • No database access. It validates the request and publishes a BookingRequestedEvent to Service Bus, then returns 202 Accepted ({ status, eventId }). It cannot read appointments or PHI.

Request body (PublicBookingRequest):

{
  "name": "Jane Doe",           // required
  "phone": "480-555-0100",      // required
  "email": "jane@example.com",  // optional
  "patientRelationship": "Existing", // New, Existing, Unknown; missing -> Unknown
  "preferredStart": "2026-08-20T21:00:00Z", // required, UTC ISO-8601 (or offset), must be future
  "durationMinutes": 60,        // optional preference
  "reason": "New patient exam", // optional
  "message": "Second choice: Friday" // optional
}

preferredStart must carry a timezone (UTC Z or an explicit offset); a value with no timezone is rejected.

Public availability API (vendor-neutral)

The same internet-facing IntakeService publishes bookable availability to external scheduling partners through a versioned, vendor-neutral contract. Cloud Dental is the single system of record; partners read availability, they never see the underlying calendar or any PHI.

GET /api/public/v1/availability        # JSON: practice time zone + bookable slots
GET /api/public/v1/availability.ics     # iCalendar feed of the same free time
  • Same enable switch, API key, and per-IP rate limit as booking (disabled → 404, missing/wrong key → 401).
  • Query by public codes (providerId, locationId, appointmentTypeId — slugs, not database IDs) plus from/to; patientRelationship defaults to New. The date range is capped at the edge and clamped by the practice's booking horizon.
  • The response echoes the requested codes, states the practice timeZone (e.g. America/Phoenix), and returns zone-offset start/end plus durationMinutes. Each slot carries an opaque, encrypted availabilityToken.
  • Only free/bookable time is exposed. A booked or blocked period simply yields no slot. See Public availability API.

Every calculation flows through the one canonical ISchedulingAvailabilityService in SchedulingService; there is no parallel availability engine. A future Zocdoc or Google adapter plugs in as a channel adapter over the same engine.

Booking revalidates the slot. Submitting a booking with a slot's availabilityToken re-runs the engine server-side; if the slot was consumed in the meantime the API returns 409 Conflict and the caller reloads availability. Previously loaded availability is never treated as authoritative.

SchedulingService runs a BookingRequestConsumer (a BackgroundService) that subscribes to the topic and creates a durable BookingRequest:

  • No placeholder patient or provider is required.
  • (TenantId, EventId) uniqueness prevents duplicates during Service Bus redelivery.
  • Malformed events are dead-lettered and processing failures remain eligible for retry.
  • Staff use Appointment Requests to match/create the patient, review the provider and actual slot, approve and schedule, reject, or request follow-up.

Failure behavior (the website keeps working)

The topic decouples the website from the scheduler:

  • SchedulingService down / deploying: IntakeService still returns 202 and the event waits durably in the scheduling subscription until the consumer comes back and processes it. No booking is lost and the website is unaffected.
  • Service Bus unreachable, or not configured: IntakeService returns 503 (rather than a false 202), so the calling website falls back to its own delivery path (e.g. email).
  • IntakeService has an isolated inbox database and no access to the scheduling database, so a scheduling-database outage does not affect durable webhook intake.

Configuration

IntakeService (PublicBooking + ServiceBus sections):

Key Purpose
PublicBooking:Enabled Master switch (default false).
PublicBooking:ApiKey / PublicBooking:TenantId Backwards-compatible single-practice credential mapping.
PublicBooking:Clients:{index}:ApiKey / TenantId Credential-to-tenant mappings for multiple practices. Store these in a secret provider.
TrustedProxies Proxy IPs allowed to supply forwarded client headers.
ConnectionStrings:IntakeDb Isolated durable inbox database (PostgreSQL in production).
IntegrationInbox:* Dispatcher batch, lease, bounded retry, and tenant admin-client settings.
ServiceBus:ConnectionString Service Bus namespace connection string. Empty → website booking returns 503; verified Zocdoc webhooks remain in the durable inbox for retry.
ServiceBus:BookingTopic Topic to publish to (default booking-requests).

SchedulingService (Jwt, InternalApi + ServiceBus sections):

Key Purpose
Jwt:Key / Issuer / Audience Validates short-lived Portal staff/admin bearer tokens. Missing keys fail closed.
InternalApi:PublicIntakeClients:{index}:ApiKey / TenantId Tenant-bound IntakeService credential for public availability operations.
ServiceBus:ConnectionString Same namespace as IntakeService. Empty → the consumer stays idle.
ServiceBus:BookingTopic / ServiceBus:BookingSubscription Defaults booking-requests / scheduling.

Environment-variable form uses double underscores, e.g. PublicBooking__ApiKey, ServiceBus__ConnectionString. You must create the topic and the scheduling subscription in your Service Bus namespace.

Upgrade note

The service creates the new BookingRequests table and indexes on startup for SQLite, PostgreSQL, and SQL Server. The earlier prototype stored scheduling patient/provider identifiers as GUID placeholders; this workflow aligns them with the integer identifiers exposed by the current PatientService/Portal. Before upgrading a non-empty scheduling database created by the earlier public booking prototype, archive any Requested placeholder appointments and rebuild or explicitly migrate the Appointments.PatientId and ProviderId columns. Fresh local/Kubernetes databases require no manual step.

Deployment boundary (important)

Expose only IntakeService to the internet (TLS + the API key at the edge). Keep api-gateway and every PHI-bearing service — including SchedulingService and its authenticated appointment APIs — on the private network. IntakeService has only its isolated durable inbox database and cannot read clinical, patient, or scheduling records. Visitor-submitted contact and visit information must nevertheless be handled as sensitive healthcare information.

3rd Set Smiles is the first reference integration, but no practice name or internal patient/provider/location identifier is coupled to this workflow.

SchedulingService appointment and booking-request APIs always require a signed tenant-bearing staff token; there is no insecure read toggle. CORS is not required—the website calls IntakeService server-to-server.


Quick Start

Docker Compose (recommended)

git clone https://github.com/aurelianware/clouddentaloffice.git
cd clouddentaloffice
docker-compose up -d

Portal: http://localhost:5000 API Gateway: http://localhost:5200 Swagger (per service): http://localhost:510x/swagger

Compose gives the Portal and SchedulingService a shared development-only JWT signing key; set CDO_JWT_KEY to override it. Outside Development the Portal refuses to start unless Jwt__Key is a unique secret of at least 32 bytes.

Local Development

# Prerequisites: .NET 8 SDK, PostgreSQL (or use SQLite default)

git clone https://github.com/aurelianware/clouddentaloffice.git
cd clouddentaloffice

# Restore, build, and run all deterministic tests
dotnet restore CloudDentalOffice.sln
dotnet build CloudDentalOffice.sln --no-restore
dotnet test CloudDentalOffice.sln --no-restore --no-build

# Run individual services
dotnet run --project src/Services/ClaimsService
dotnet run --project src/Services/ApiGateway
dotnet run --project src/CloudDentalOffice.Portal

Each service defaults to SQLite for local dev — no database setup required.

Continuous integration

.github/workflows/ci.yml runs for pull requests and pushes to main using the .NET 8 SDK selected by Directory.Build.props and the .NET 8 deployment Dockerfiles. It restores and builds the complete solution, then runs every test project included in CloudDentalOffice.sln:

  • CloudDentalOffice.Portal.Tests
  • SchedulingService.Tests
  • IntakeService.Tests

Test output is written as TRX and uploaded for 14 days, including on failure. Configure the following checks as required status checks for main branch protection:

CI / Build
CI / Tests

Azure deployment runs only after the CI workflow succeeds for the exact main commit. Manually dispatched Azure and DOKS deployments invoke the same reusable validation workflow before any registry login, image build, push, or deployment step.

The Zocdoc.IntegrationTests project is deliberately not in the normal solution. It contacts Zocdoc's external sandbox and requires partner-issued credentials. Run it locally as documented in src/Services/Zocdoc.IntegrationTests/README.md, or manually dispatch Zocdoc Sandbox Certification. Store ZOCDOC_SANDBOX_CLIENT_ID and ZOCDOC_SANDBOX_CLIENT_SECRET in the protected zocdoc-sandbox GitHub environment. The workflow fails with a configuration message when either secret is absent and never runs for pull requests.

Local Kubernetes

CloudDentalOffice runs in its own clouddental namespace. The local overlay uses portable Kubernetes resources, an in-cluster PostgreSQL instance, and local images; it does not depend on Azure or DigitalOcean services.

Prerequisites: Docker, kubectl, and a running local cluster. The helper recognizes a kind cluster named docker-desktop by default and loads images into it. The helper generates the local database password and JWT signing key directly in the cluster; these credentials are not stored in the repository.

./scripts/deploy-local-k8s.sh
kubectl port-forward -n clouddental service/portal 5000:5000

Then open http://localhost:5000 and sign in with demo@clouddentaloffice.com / Password123!.

Useful overrides:

# Re-apply manifests without rebuilding images
SKIP_BUILD=true ./scripts/deploy-local-k8s.sh

# Use another kind cluster name
KIND_CLUSTER_NAME=my-cluster ./scripts/deploy-local-k8s.sh

The manifests are under infrastructure/k8s/local. The reusable application manifests remain under infrastructure/k8s/clouddental for cloud deployments.

CloudHealthOffice claim flow

The local portal configures the demo payer to submit to CloudHealthOffice. Staff follow the claim in CloudDentalOffice through the claim intelligence API — timeline, lifecycle status, patient responsibility, and posted financials — without seeing X12 transactions or clearinghouse vendors.

CloudDentalOffice (clouddental)
  -> submit claim to CloudHealthOffice
  -> GET /api/claims/{claimId}/intelligence
  -> practice-facing lifecycle (status, timeline, patient responsibility)
  -> post billed / insurance / contractual amounts to the patient ledger

Configure CloudHealthOffice__BaseUrl, CloudHealthOffice__IntelligencePath (default /api/claims/{claimId}/intelligence), and CloudHealthOffice__Enabled. The browser never calls CloudHealthOffice. Override CloudHealthOffice__BaseUrl in the portal deployment when the payer platform uses a different namespace or an external URL.


Claims and payer interoperability

Cloud Dental Office is the practice surface. Cloud Health Office owns payer connectivity and claim intelligence. Staff see lifecycle status, a timeline, patient responsibility, and posted financials — not X12 transactions or clearinghouse vendors.

Capability Surface Status
Claim submission CloudHealthOffice ✅ Implemented
Claim lifecycle (status, timeline, patient responsibility) Claim Intelligence API ✅ Implemented
Posted financials Patient ledger from remittance summary ✅ Implemented
Eligibility Payer transaction router 🔧 In progress
Predetermination / Advanced EOB Reserved 📋 Planned

AI Vision Platform

Cloud Dental Office integrates with privaseeAI edge devices to provide intelligent vision capabilities for dental practices:

Features

Feature Description Status
Insurance Card OCR Automatic extraction of member ID, payer info, group numbers from insurance cards using Azure AI Vision ✅ Implemented
Narcotics Cabinet Monitoring Real-time detection and compliance tracking for controlled substance access with badge verification ✅ Implemented
Patient Consent Recording Video-verified consent capture with detection of patient, provider, and consent forms ✅ Implemented
Clinical Note Generation AI-assisted procedure documentation from instrument detection and procedure observations ✅ Implemented
Real-time Detection SignalR hub for live camera feeds and event streaming ✅ Implemented
Device Management Multi-device registration and monitoring (IP cameras, tablets, Raspberry Pi, mobile devices) ✅ Implemented

Detection Classes

The VisionService supports detection of:

  • Generic objects: Person, Document, Cell Phone, Backpack, Handbag (COCO-SSD)
  • Dental instruments: Handpiece, Mirror, Explorer, Forceps, Elevator, Scaler/Curette, Syringes, Suture Kit, Cotton Roll, Gauze, Impression Tray, Crown/Bridge, Dental Dam
  • Documents: Insurance cards, consent forms, ID documents, prescription pads
  • Security: Cabinet door status, badge scanning, medication vials, narcotics safe monitoring

Architecture

┌───────────────────────────────────────────────────────────────┐
│                     privaseeAI Edge Devices                    │
│  (IP Cameras, Tablets, Raspberry Pi, Mobile Devices)          │
│                  ↓ Detection Events (HTTPS)                   │
└───────────────────────────────────────────────────────────────┘
                              ↓
┌───────────────────────────────────────────────────────────────┐
│                      VisionService (:5108)                     │
│  ┌─────────────────┐  ┌────────────────┐  ┌────────────────┐ │
│  │ Ingest Detections│→│Context Correlate│→│ Event Storage │ │
│  │  (REST API)      │  │   (Appt/Pt)     │  │  (PostgreSQL) │ │
│  └─────────────────┘  └────────────────┘  └────────────────┘ │
│                              ↓                                 │
│  ┌─────────────────┐  ┌────────────────┐  ┌────────────────┐ │
│  │ SignalR Hub      │  │  Azure AI      │  │ Alert Engine  │ │
│  │ (Real-time)      │  │  Vision OCR    │  │ (Compliance)  │ │
│  └─────────────────┘  └────────────────┘  └────────────────┘ │
└───────────────────────────────────────────────────────────────┘
                              ↓
┌───────────────────────────────────────────────────────────────┐
│                   Portal UI — AI Vision Pages                  │
│  • Vision Dashboard  • Device Management  • Events & Alerts    │
│  • Insurance Scans   • Consent Recording  • Cabinet Access     │
│  • Clinical Notes                                              │
└───────────────────────────────────────────────────────────────┘

Configuration

The VisionService supports two modes:

  • Development/Mock: Uses mock OCR and correlation providers for testing without external dependencies
  • Production: Integrates with Azure AI Vision API and live correlation engine

Configure via appsettings.json:

{
  "OcrProvider": "AzureAiVision",  // or "Mock"
  "CorrelationProvider": "Live",    // or "Mock"
  "AzureAiVision": {
    "Endpoint": "https://yourresource.cognitiveservices.azure.com/",
    "ApiKey": "your-api-key"
  }
}

Project Structure

clouddentaloffice/
├── CloudDentalOffice.sln          # Solution file
├── Directory.Build.props          # Shared build settings
├── docker-compose.yml             # Full stack orchestration
├── src/
│   ├── CloudDentalOffice.Portal/  # Blazor Server UI (your existing Portal)
│   ├── CloudDentalOffice.Portal.Tests/
│   ├── Services/
│   │   ├── ApiGateway/            # YARP reverse proxy
│   │   ├── PatientService/        # Patient bounded context
│   │   ├── SchedulingService/     # Scheduling bounded context
│   │   ├── ClaimsService/         # Claims bounded context
│   │   ├── EligibilityService/    # 270/271 bounded context
│   │   ├── EraService/            # 835 bounded context
│   │   ├── AuthService/           # Identity bounded context
│   │   ├── PrescriptionService/   # e-Prescribing (DoseSpot, EPCS)
│   │   └── VisionService/         # AI Vision (privaseeAI, Azure AI)
│   └── Shared/
│       ├── CloudDentalOffice.Contracts/   # Shared DTOs & events
│       └── CloudDentalOffice.EdiCommon/   # X12 parser & generators
├── infrastructure/
│   ├── docker/                    # Per-service Dockerfiles
│   ├── k8s/                       # Kubernetes manifests
│   └── azure/                     # Azure Bicep/ARM templates
├── scripts/
│   └── seeds/                     # Database init & seed data
└── docs/                          # Architecture & API documentation

Technology Stack

  • .NET 8 — All services and portal
  • Blazor Server with MudBlazor — Responsive UI with dark theme
  • Entity Framework Core — Multi-provider (PostgreSQL, SQL Server, SQLite)
  • YARP — API Gateway / reverse proxy
  • Docker + Kubernetes — Container orchestration
  • Azure — Cloud deployment (Bicep IaC templates)
  • JWT / OpenID Connect — Authentication and multi-tenant identity
  • SSH.NET — SFTP for clearinghouse file exchange
  • DoseSpot — Electronic prescription (eRx) and EPCS integration

Roadmap

  • Microservices architecture with per-service databases
  • API Gateway with YARP
  • Patient, Scheduling, Claims, Eligibility, ERA, Auth services
  • e-Prescribing service with DoseSpot integration (EPCS, Surescripts)
  • AI Vision service — privaseeAI integration for dental practice automation
  • Insurance card OCR with Azure AI Vision
  • Narcotics cabinet monitoring and compliance tracking
  • Video consent recording and verification
  • Clinical note generation from procedure observations
  • Clean-room X12 837D claim generator
  • Docker Compose full-stack deployment
  • Blazor Portal integration with microservices via API Gateway
  • Kubernetes deployment manifests (DOKS)
  • CI/CD with GitHub Actions
  • Send 837D Claim transactions to payer SFTP
  • Full 270/271 real-time eligibility checks
  • 835 ERA auto-posting & reconciliation
  • 276/277 claim status polling
  • 278 prior authorization
  • Multi-location / DSO support
  • Azure AD B2C / OpenID Connect auth
  • Kubernetes Helm charts
  • Availity Integration
  • Change Healthcare Integration

Related Projects

  • Cloud Health Office — Payer-side EDI platform (X12, FHIR R4, CMS-0057-F compliance)
  • Together, Cloud Dental Office + Cloud Health Office provide complete provider ↔ payer interoperability

License

Cloud Dental Office is source-available under the Business Source License 1.1 (BSL). Copyright 2026 Aurelianware, Inc.

What dental practices can do for free

  • Run Cloud Dental Office in production to manage your own dental practice, or practices under common ownership or control with yours.
  • Self-host it on your own infrastructure.
  • Read, copy, modify and build on the source code, and use it freely for development, testing and evaluation.

What requires a commercial license

  • Offering Cloud Dental Office, or anything derived from it, to other parties on a hosted, managed or embedded basis as a commercial product or service that competes with Aurelianware's paid versions of Cloud Dental Office.

Change date

Each version converts to the Apache License, Version 2.0 four years after it is first publicly released.

Earlier code

Code at or before the git tag apache-2.0-final (commit 2c92270) was released under the Apache License 2.0 and remains available under it.

See LICENSING.md for more detail. For other licensing arrangements, contact licensing@cloudhealthoffice.com.

This summary is for convenience only; the LICENSE file is the governing text.

About

Source-available dental practice management platform built with .NET Blazor Server & microservices. Patient scheduling, 837D claims, eligibility (270/271), ERA/835, and multi-tenant SaaS. Business Source License 1.1.

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages