
Implementation Guide: Draft prior authorization letters for insurance carriers
Step-by-step implementation guide for deploying AI to draft prior authorization letters for insurance carriers for Healthcare clients.
Hardware Procurement
HIPAA-Compliant Business Workstation
$950 per unit MSP cost / $1,300 suggested resale
Staff workstations for PA workflow. Windows 11 Pro required for BitLocker full-disk encryption. 16GB RAM supports browser-based SaaS tools, EHR client, and AI assistant concurrently. One per PA-handling staff member plus one clinician review station.
Privacy Screen Filter 24-inch
$45 per unit MSP cost / $75 suggested resale
HIPAA physical safeguard. Prevents shoulder-surfing of PHI displayed during PA letter drafting. Required for any workstation in a shared or semi-public area of the practice.
Next-Generation Firewall
$900 hardware + $450/year UTM subscription MSP cost / $1,400 hardware + $700/year suggested resale
NGFW with IDS/IPS, TLS inspection, application control, and VLAN support. Segments clinical network (PHI traffic to Azure OpenAI, EHR systems) from guest Wi-Fi and IoT devices. Required for HIPAA technical safeguard compliance.
Managed Wi-Fi Access Point (VLAN-capable)
$150 per unit MSP cost / $250 suggested resale
Enterprise-grade Wi-Fi with VLAN support to separate clinical, administrative, and guest networks. Two APs cover a typical 2,000–5,000 sq ft practice. Managed via UniFi Network Controller hosted on-site or in UniFi Cloud.
HIPAA-Compliant Backup Appliance
$199/month MSP cost / $350/month suggested resale
Image-based backup with instant virtualization for disaster recovery. Backs up EHR server, audit logs, PA letter archives, and prompt template databases. HIPAA-compliant cloud replication included. Essential for HIPAA contingency planning requirements.
UPS Battery Backup
$650 MSP cost / $900 suggested resale
Protects firewall, switch, and backup appliance from power events. SmartConnect enables remote monitoring via APC cloud portal. Ensures PA workflow continuity during power fluctuations common in older medical office buildings.
Software Procurement
Doximity GPT (DoxGPT)
$0/month
Phase 1 quick-win tool. HIPAA-compliant AI writing assistant purpose-built for clinicians. Drafts prior authorization letters, appeal letters, and clinical correspondence. Zero data retention policy. Supports PHI in prompts. Available to all verified U.S. physicians, NPs, PAs, pharmacists, podiatrists, CRNAs, and medical students.
CoverMyMeds Electronic Prior Authorization
$0/month
Phase 1 pharmacy/medication PA platform. Largest ePA network — processed 43M+ prior authorizations in Q1 2025. Integrated with 350+ EHRs and 500+ health plans. Handles medication/prescription prior authorizations electronically. Not suitable for medical/procedural PAs.
Azure OpenAI Service (GPT-5.4)
$2.50/million input tokens + $10/million output tokens for GPT-5.4; ~$0.02 per PA letter; estimated $10–$50/month for typical practice volume of 200–500 letters
Phase 3 core LLM engine for custom PA letter generation. HIPAA-compliant under Microsoft BAA. Deployed within Azure subscription with private endpoints, RBAC, and audit logging. GPT-5.4 provides best quality for medical letter generation; GPT-5.4 mini at $0.15/$0.60 per million tokens available as cost-optimized fallback.
Microsoft Azure Subscription (HIPAA-eligible services)
$200–$500/month for App Service, Cosmos DB, Key Vault, and networking
Phase 3 hosting platform for custom PA letter generation tool. Includes Azure App Service (B2 tier for web app), Azure Cosmos DB (serverless for audit logs and templates), Azure Key Vault (API key and encryption key management), and Azure Virtual Network with private endpoints. All services covered under Microsoft HIPAA BAA.
Microsoft 365 Business Premium
$22/user/month MSP cost / $30/user/month suggested resale
Foundation identity and productivity platform. Provides Microsoft Entra ID P1 (Conditional Access + MFA), Microsoft Defender for Endpoint P1 (EDR), BitLocker management via Intune, and Exchange Online with data loss prevention policies. Required for centralized identity management across all AI tools.
SentinelOne Singularity Complete
$5/endpoint/month MSP cost / $10/endpoint/month suggested resale
AI-powered EDR for all workstations and servers. Provides real-time threat detection, automated response, and forensic investigation capabilities. Required for HIPAA Security Rule audit defensibility. Deploys via MSP multi-tenant console.
Duo Security MFA
$3/user/month MSP cost / $7/user/month suggested resale
Multi-factor authentication for all PHI-accessing accounts. Integrates with Microsoft Entra ID, EHR web portals, Azure Portal, and custom applications. Required HIPAA technical safeguard. Alternative: use Entra ID P1 MFA if already included in M365 Business Premium.
DNSFilter
$1.15/user/month MSP cost / $3.50/user/month suggested resale
DNS-layer security filtering to block malicious domains, prevent data exfiltration, and enforce acceptable use policies. Lightweight defense-in-depth layer that protects against phishing and C2 callbacks targeting PA workflow credentials.
Linear Health (if athenahealth EHR)
$500–$1,500/month estimated per practice
Phase 2 option for practices on athenahealth. Purpose-built referral and PA automation platform with native Athena integration. Most practices go live in 4 weeks. Automates end-to-end referral coordination from fax receipt through booked appointment using AI agents.
Tandem AI (for specialty practices)
$500–$2,000/month estimated per practice
Phase 2 option for specialty practices (dermatology, rheumatology, oncology). Automates medication prior authorizations and form-based authorization submissions. Portal-based workflow with AI-assisted clinical documentation.
Overjet (dental practices)
$300–$1,000/month estimated per practice
Dental-specific AI platform. Automates insurance verification across 300+ payers — verifies entire day's patient list in under 5 seconds. AI-powered analysis of dental radiographs supports clinical documentation for PA justification.
Prerequisites
- Active EHR/PM system with API access enabled (FHIR R4 preferred). Supported systems: athenahealth, eClinicalWorks, NextGen, DrChrono, Epic (medical); Open Dental, Dentrix Ascend, Eaglesoft (dental). Verify API licensing with EHR vendor before project start.
- Stable internet connection: minimum 50 Mbps symmetrical, 100+ Mbps recommended. SaaS-dependent workflow requires high reliability — consider redundant ISP or cellular failover for practices with single-provider internet.
- All clinicians who will review PA letters must have verified Doximity accounts (free). Verification requires active NPI number and U.S. medical license. Allow 1–5 business days for new account verification.
- Signed HIPAA Business Associate Agreements (BAAs) with all technology vendors that will handle PHI: Microsoft (Azure/M365), OpenAI (if using direct API), SaaS PA platform vendor, backup vendor (Datto/Axcient), and the MSP itself must have a BAA with the practice.
- Windows 11 Pro on all workstations (required for BitLocker management via Intune). Verify all existing machines meet Windows 11 hardware requirements (TPM 2.0, Secure Boot, 4GB+ RAM, 64GB+ storage).
- Designated Clinical Champion: One provider at the practice who will own template review, workflow validation, and serve as the escalation point for AI output quality issues. This person must commit 2–4 hours/week during implementation.
- Practice must have a current HIPAA Risk Assessment on file (updated within the last 12 months). If not, conduct one before implementation — this is a CMS audit requirement and the foundation for all technical safeguard decisions.
- Admin credentials for EHR/PM system, domain controller (if applicable), Microsoft 365 tenant, and firewall. Collect and verify all credentials during the pre-implementation discovery call.
- Existing or new Microsoft 365 Business Premium tenant with Microsoft Entra ID configured. If practice uses Google Workspace, plan for Entra ID overlay or migration before Phase 3.
- Practice leadership must approve an AI Acceptable Use Policy that mandates human review of all AI-generated PA letters before submission. Draft policy provided in client handoff documentation.
Installation Steps
...
Step 1: Conduct Site Assessment and HIPAA Gap Analysis
Before any technical work begins, perform a comprehensive site assessment of the practice environment. Document current network topology, EHR system and version, number of PA-handling staff, monthly PA volume by payer, current PA workflow (manual vs. partially electronic), and existing security controls. Cross-reference findings against HIPAA Security Rule requirements to identify gaps that must be remediated before deploying AI tools that will process PHI.
Use a standardized assessment template. Key items to document: (1) EHR vendor, version, and API availability; (2) current PA volume and top 5 payers; (3) average time per PA letter currently; (4) network diagram with IP ranges; (5) existing firewall, AV, and backup solutions; (6) BAA inventory. This assessment typically takes 2–4 hours on-site. Schedule during a low-patient-volume period.
Step 2: Deploy and Configure HIPAA-Compliant Network Infrastructure
Install the FortiGate 60F firewall, replacing or supplementing the existing edge device. Configure network segmentation with at minimum three VLANs: Clinical (VLAN 10, for EHR workstations and PA workflow), Administrative (VLAN 20, for billing and front-desk non-PHI tasks), and Guest (VLAN 30, for patient Wi-Fi). Enable FortiGuard UTM bundle including IPS, application control, web filtering, and antivirus. Configure TLS 1.2+ inspection for outbound HTTPS traffic on the Clinical VLAN. Deploy UniFi U6 Pro access points and assign SSIDs to appropriate VLANs.
# Create VLANs and enable UTM features on Clinical VLAN outbound policy
# FortiGate CLI - Create VLANs
config system interface
edit "clinical-vlan"
set vdom "root"
set ip 10.10.10.1 255.255.255.0
set allowaccess ping https ssh
set interface "internal"
set vlanid 10
next
edit "admin-vlan"
set vdom "root"
set ip 10.10.20.1 255.255.255.0
set allowaccess ping https ssh
set interface "internal"
set vlanid 20
next
edit "guest-vlan"
set vdom "root"
set ip 10.10.30.1 255.255.255.0
set allowaccess ping
set interface "internal"
set vlanid 30
next
end
# Enable UTM features on Clinical VLAN outbound policy
config firewall policy
edit 1
set name "clinical-to-internet"
set srcintf "clinical-vlan"
set dstintf "wan1"
set srcaddr "all"
set dstaddr "all"
set action accept
set schedule "always"
set service "ALL"
set utm-status enable
set av-profile "default"
set ips-sensor "default"
set application-list "default"
set ssl-ssh-profile "deep-inspection"
set logtraffic all
set nat enable
next
endEnsure the FortiGate firmware is updated to the latest stable release before configuration. Register the device on FortiCloud for centralized MSP management. The deep-inspection SSL profile requires installing the FortiGate CA certificate on all clinical workstations — push via Intune GPO. Guest VLAN should have bandwidth throttling (10 Mbps) and no access to internal subnets.
Step 3: Configure Microsoft 365 Business Premium and Entra ID
Set up or verify the Microsoft 365 Business Premium tenant. Configure Microsoft Entra ID with Conditional Access policies requiring MFA for all users, blocking legacy authentication protocols, and requiring compliant devices for access to PHI-related applications. Enable Microsoft Intune for device management. Create security groups: PA-Staff, PA-Clinicians, IT-Admins. Configure BitLocker encryption policy via Intune to enforce full-disk encryption on all enrolled Windows devices.
# Connect to Microsoft Graph and configure basic policies
# PowerShell - Connect to Microsoft Graph and configure basic policies
Install-Module Microsoft.Graph -Scope CurrentUser
Connect-MgGraph -Scopes 'Policy.ReadWrite.ConditionalAccess','DeviceManagementConfiguration.ReadWrite.All'
# Create Security Groups
New-MgGroup -DisplayName 'PA-Staff' -MailEnabled:$false -MailNickname 'pa-staff' -SecurityEnabled:$true
New-MgGroup -DisplayName 'PA-Clinicians' -MailEnabled:$false -MailNickname 'pa-clinicians' -SecurityEnabled:$true
New-MgGroup -DisplayName 'IT-Admins' -MailEnabled:$false -MailNickname 'it-admins' -SecurityEnabled:$true
# Intune BitLocker Policy (via Endpoint Security in Intune portal)
# Navigate to: Endpoint security > Disk encryption > Create policy
# Platform: Windows 10 and later
# Profile: BitLocker
# Settings:
# Require device encryption: Yes
# BitLocker OS drive policy: Require
# Startup authentication: Require TPM + PIN
# Recovery key rotation: Enabled
# Encryption method: XTS-AES 256-bitIf the practice already has Microsoft 365, verify the plan level supports Conditional Access (Business Premium, E3, or E5). If on Business Basic or Standard, an upgrade is required. Enroll all existing workstations in Intune before proceeding. Conditional Access policies should be tested in Report-Only mode for 48 hours before enforcement to avoid locking out staff.
Step 4: Deploy Endpoint Security Stack
Install SentinelOne Singularity Complete on all workstations and any on-premise servers (EHR server, file server). Configure the SentinelOne management console with a site for this practice under the MSP's multi-tenant account. Set threat response to 'Protect' mode (auto-quarantine). Deploy DNSFilter agent on all endpoints and configure the practice's DNS policy to block malware, phishing, botnet C2, and unapproved AI tools (block consumer ChatGPT, Gemini, Claude web interfaces to prevent staff from using non-BAA-covered AI with PHI). Install 3M privacy screens on all PA workflow workstations.
# SentinelOne deployment via Intune
# 1. Download SentinelOne MSI installer from Management Console > Sentinels > Packages
# 2. Upload MSI to Intune: Apps > Windows > Add > Line-of-business app
# 3. Assign to 'All Devices' group
# 4. Command-line arguments for silent install:
SentinelOneInstaller.msi /q SITE_TOKEN=<your_site_token>
# DNSFilter deployment via Intune
# 1. Download DNSFilter agent MSI from dnsfitler.com/dashboard/deployment
# 2. Upload to Intune as LOB app
# 3. Silent install:
msiexec /i DNSFilter_Agent.msi /qn ORGANIZATION_ID=<your_org_id>
# Verify deployments
# SentinelOne: Check Management Console > Sentinels for all endpoints reporting
# DNSFilter: Check Dashboard > Roaming Clients for all endpoints reportingCRITICAL: Add the following domains to DNSFilter's BLOCK list to prevent staff from using non-HIPAA-compliant AI tools with PHI: chat.openai.com, gemini.google.com, claude.ai, bard.google.com, copilot.microsoft.com (unless using M365 Copilot with BAA). Add the following to the ALLOW list: doximity.com, *.openai.azure.com (for Phase 3 custom tool). Document the block policy in the practice's AI Acceptable Use Policy.
Step 5: Phase 1 Deployment — Doximity GPT for Clinician PA Letter Drafting
Set up verified Doximity accounts for all eligible clinicians at the practice. For each clinician, verify their Doximity profile includes current NPI number, practice address, and specialty. Enable the Doximity GPT (DoxGPT) feature in their account settings. Create a shared document (stored in HIPAA-compliant SharePoint) containing standardized prompt templates for the practice's most common PA letter scenarios. Conduct a 60-minute training session with all clinicians on using DoxGPT effectively for PA letters.
Doximity verification typically takes 1–5 business days for new accounts. Start this process at the beginning of the project. DoxGPT is HIPAA-compliant with zero data retention — PHI can be included in prompts. However, advise clinicians to use only the minimum necessary PHI. For dental practices where providers may not have Doximity accounts (dentists are not currently eligible for Doximity), skip to Phase 2 or Phase 3 approaches.
Step 6: Phase 1 Deployment — CoverMyMeds Integration for Pharmacy PAs
Enable CoverMyMeds electronic prior authorization (ePA) within the practice's EHR system. Most major EHRs (athenahealth, eClinicalWorks, DrChrono, NextGen, Epic) have native CoverMyMeds integration available. Navigate to the EHR's integration marketplace or module settings to activate CoverMyMeds. Register the practice on CoverMyMeds if no account exists. Map provider NPIs and practice TIN to the CoverMyMeds account. Test the workflow with a sample medication PA request.
- athenahealth: Navigate to Admin > Clinical > eRx Settings → Enable CoverMyMeds ePA integration → Map each provider's NPI
- eClinicalWorks: Navigate to Admin > eRx Configurations → Enable electronic Prior Authorization → CoverMyMeds will activate automatically for eRx-enabled providers
- DrChrono: Navigate to Account > Settings > Integrations → Enable CoverMyMeds → Follow OAuth authorization flow
- Open Dental (dental): CoverMyMeds does not natively integrate with dental PM systems — Use CoverMyMeds web portal directly at covermymeds.com
CoverMyMeds is free for providers — it is funded by pharmacies and payers. It handles medication/prescription PAs only, NOT medical/procedural PAs (e.g., imaging, surgeries, DME). For medical PAs, proceed to Phase 2 (SaaS platform) or Phase 3 (custom build). Allow 1–2 weeks for CoverMyMeds activation and payer network verification.
Step 7: Phase 2 Deployment — Select and Onboard Turnkey PA SaaS Platform
Based on the practice's EHR, specialty, and PA volume, select and deploy one of the recommended turnkey SaaS platforms. For athenahealth practices: Linear Health. For specialty medical practices: Tandem AI or Ethermed. For dental practices: Overjet for insurance verification plus custom letter tool (Phase 3). Engage the vendor's onboarding team, provide EHR API credentials, and configure the integration. Map the practice's top payers and most common PA procedure/medication codes. Configure the workflow: (1) PA need identified → (2) platform pulls patient data from EHR → (3) AI generates draft letter → (4) clinician reviews in platform → (5) submission to payer via fax/portal.
Vendor selection decision matrix: (1) If on athenahealth → Linear Health (purpose-built, 4-week go-live). (2) If specialty medical practice → Tandem AI (strong medication PA automation). (3) If multi-specialty or procedure-heavy → Ethermed (broad medical PA coverage). (4) If dental → Overjet + Phase 3 custom tool. Always require a signed BAA from the SaaS vendor before providing any EHR API credentials. Verify the vendor's SOC 2 Type II report and HIPAA compliance documentation.
Step 8: Phase 3 Setup — Provision Azure Environment for Custom AI Build
Create a dedicated Azure subscription for the practice (or a shared MSP subscription with resource group isolation). Sign the Microsoft HIPAA BAA via the Azure portal Trust Center. Deploy the foundational Azure resources: Resource Group, Virtual Network with private endpoints, Azure Key Vault, Azure Cosmos DB (serverless, for audit logs and prompt templates), and Azure App Service (B2 tier). Apply for Azure OpenAI Service access if not already approved. Once approved, deploy a GPT-5.4 model instance in the Azure OpenAI resource within a HIPAA-eligible region (East US or West US 2).
# Azure CLI - Provision core infrastructure
az login
az account set --subscription "<subscription-id>"
# Create Resource Group
az group create --name rg-pa-lettergen --location eastus
# Create Virtual Network with subnet for private endpoints
az network vnet create --resource-group rg-pa-lettergen --name vnet-pa --address-prefix 10.0.0.0/16 --subnet-name snet-private-endpoints --subnet-prefix 10.0.1.0/24
# Create Azure Key Vault
az keyvault create --resource-group rg-pa-lettergen --name kv-pa-lettergen --location eastus --sku standard --enable-purge-protection true --enable-soft-delete true
# Create Cosmos DB account (serverless)
az cosmosdb create --resource-group rg-pa-lettergen --name cosmos-pa-lettergen --kind GlobalDocumentDB --capabilities EnableServerless --default-consistency-level Session --locations regionName=eastus
# Create Cosmos DB database and containers
az cosmosdb sql database create --resource-group rg-pa-lettergen --account-name cosmos-pa-lettergen --name pa-letter-db
az cosmosdb sql container create --resource-group rg-pa-lettergen --account-name cosmos-pa-lettergen --database-name pa-letter-db --name audit-logs --partition-key-path /practiceId
az cosmosdb sql container create --resource-group rg-pa-lettergen --account-name cosmos-pa-lettergen --database-name pa-letter-db --name prompt-templates --partition-key-path /templateCategory
# Create Azure OpenAI resource
az cognitiveservices account create --resource-group rg-pa-lettergen --name aoai-pa-lettergen --kind OpenAI --sku S0 --location eastus --custom-domain aoai-pa-lettergen
# Deploy GPT-5.4 model
az cognitiveservices account deployment create --resource-group rg-pa-lettergen --name aoai-pa-lettergen --deployment-name gpt-5.4 --model-name gpt-5.4 --model-version 2024-08-06 --model-format OpenAI --sku-name Standard --sku-capacity 30
# Create App Service Plan and Web App
az appservice plan create --resource-group rg-pa-lettergen --name asp-pa-lettergen --sku B2 --is-linux
az webapp create --resource-group rg-pa-lettergen --plan asp-pa-lettergen --name app-pa-lettergen --runtime "PYTHON:3.11"
# Store Azure OpenAI key in Key Vault
AOAI_KEY=$(az cognitiveservices account keys list --resource-group rg-pa-lettergen --name aoai-pa-lettergen --query key1 -o tsv)
az keyvault secret set --vault-name kv-pa-lettergen --name aoai-api-key --value $AOAI_KEYAzure OpenAI access requires an application — apply at https://aka.ms/oai/access. Approval typically takes 1–5 business days for healthcare use cases. Ensure the subscription has the Microsoft HIPAA BAA signed BEFORE deploying any resources — navigate to Azure Portal > Trust Center > Regulatory Compliance > HIPAA BAA. Deploy all resources in the same region (East US recommended) to minimize latency and avoid cross-region data transfer.
Step 9: Phase 3 Build — Deploy Custom PA Letter Generation Application
Deploy the custom PA letter generation web application to Azure App Service. The application is a Python Flask/FastAPI web app that: (1) Accepts structured patient data input from staff (or pulls from EHR via FHIR API), (2) Selects the appropriate prompt template based on payer and procedure type, (3) Calls Azure OpenAI GPT-5.4 to generate the PA letter draft, (4) Presents the draft to a clinician for review and editing, (5) Logs all activity to Cosmos DB for HIPAA audit trail, (6) Exports the final approved letter as PDF for fax/portal submission. See the custom_ai_components section for complete prompt templates and application code.
# Clone the application repository (MSP internal repo)
git clone https://github.com/<msp-org>/pa-letter-generator.git
cd pa-letter-generator
# Create Python virtual environment
python3.11 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# requirements.txt contents:
# fastapi==0.115.0
# uvicorn==0.30.0
# openai==1.52.0
# azure-identity==1.18.0
# azure-cosmos==4.7.0
# azure-keyvault-secrets==4.8.0
# python-multipart==0.0.9
# jinja2==3.1.4
# weasyprint==62.0
# python-dotenv==1.0.1
# pydantic==2.9.0
# Configure environment variables (stored in App Service Configuration)
az webapp config appsettings set --resource-group rg-pa-lettergen --name app-pa-lettergen --settings \
AZURE_OPENAI_ENDPOINT=https://aoai-pa-lettergen.openai.azure.com/ \
AZURE_OPENAI_DEPLOYMENT=gpt-5.4 \
AZURE_OPENAI_API_VERSION=2024-08-06 \
COSMOS_ENDPOINT=https://cosmos-pa-lettergen.documents.azure.com:443/ \
COSMOS_DATABASE=pa-letter-db \
KEY_VAULT_URL=https://kv-pa-lettergen.vault.azure.net/ \
PRACTICE_NAME="Example Medical Associates" \
REQUIRE_CLINICIAN_APPROVAL=true
# Deploy to Azure App Service
az webapp deployment source config-zip --resource-group rg-pa-lettergen --name app-pa-lettergen --src app.zip
# Enable managed identity for Key Vault access
az webapp identity assign --resource-group rg-pa-lettergen --name app-pa-lettergen
PRINCIPAL_ID=$(az webapp identity show --resource-group rg-pa-lettergen --name app-pa-lettergen --query principalId -o tsv)
az keyvault set-policy --name kv-pa-lettergen --object-id $PRINCIPAL_ID --secret-permissions get list
# Configure custom domain and SSL (optional but recommended)
# az webapp config hostname add --resource-group rg-pa-lettergen --webapp-name app-pa-lettergen --hostname pa.example-practice.com
# az webapp config ssl bind ...The application MUST enforce clinician approval — the REQUIRE_CLINICIAN_APPROVAL flag should never be set to false in production. Use Azure Managed Identity instead of hardcoded API keys wherever possible. The App Service should have IP restrictions configured to allow access only from the practice's public IP and the MSP's management IPs. Enable App Service authentication via Entra ID to require Azure AD login for all users.
Step 10: Phase 3 Build — Configure EHR FHIR Integration for Auto-Population
For practices with FHIR-capable EHRs, configure the PA letter generator to pull patient demographic data, diagnoses (ICD-10 codes), procedures (CPT codes), medications, and relevant clinical notes directly from the EHR via FHIR R4 APIs. This eliminates manual data entry and reduces errors. Register the PA letter generator as a SMART on FHIR application in the EHR's app marketplace. Configure OAuth 2.0 authorization flow so staff can pull patient data with a single click.
# Example FHIR data pull (Python - using requests library)
# This code is integrated into the main application
import requests
def get_patient_data_for_pa(fhir_base_url, patient_id, access_token):
headers = {
'Authorization': f'Bearer {access_token}',
'Accept': 'application/fhir+json'
}
# Get patient demographics
patient = requests.get(f'{fhir_base_url}/Patient/{patient_id}', headers=headers).json()
# Get active conditions (diagnoses)
conditions = requests.get(
f'{fhir_base_url}/Condition?patient={patient_id}&clinical-status=active',
headers=headers
).json()
# Get recent medications
medications = requests.get(
f'{fhir_base_url}/MedicationRequest?patient={patient_id}&status=active',
headers=headers
).json()
# Get relevant procedures/service requests pending PA
service_requests = requests.get(
f'{fhir_base_url}/ServiceRequest?patient={patient_id}&status=active',
headers=headers
).json()
# Get recent clinical notes (last 90 days)
from datetime import datetime, timedelta
date_90_days_ago = (datetime.now() - timedelta(days=90)).strftime('%Y-%m-%d')
notes = requests.get(
f'{fhir_base_url}/DocumentReference?patient={patient_id}&date=ge{date_90_days_ago}&type=http://loinc.org|11506-3',
headers=headers
).json()
return {
'patient': patient,
'conditions': conditions,
'medications': medications,
'service_requests': service_requests,
'clinical_notes': notes
}FHIR integration complexity varies dramatically by EHR. athenahealth and Epic have mature FHIR APIs with good documentation. eClinicalWorks and NextGen support is improving but may require vendor support tickets. Dental PM systems (Dentrix, Eaglesoft) generally do NOT support FHIR — for dental practices, use manual data entry or file-based export from Open Dental's open-source database. FHIR integration is the highest-risk step in Phase 3; allocate 2–4 weeks and budget for EHR vendor consultation fees.
Step 11: Configure Audit Logging and Compliance Documentation
Implement comprehensive audit logging that captures every interaction with the PA letter generator. Every AI-generated letter must be logged with: timestamp, requesting user, reviewing clinician, patient identifier (MRN, not SSN), input data summary, AI model version used, generated output hash, clinician edits (diff), approval timestamp, and submission method. Store logs in Azure Cosmos DB with a 7-year retention policy (HIPAA requirement: 6 years minimum; 7 years provides buffer). Generate monthly compliance reports showing letter volume, average review time, edit rates, and any anomalies.
# Cosmos DB audit log document structure (Python dataclass)
from dataclasses import dataclass, asdict
from datetime import datetime
import hashlib
import json
@dataclass
class PAAuditLog:
id: str # UUID
practice_id: str # Practice identifier
timestamp_created: str # ISO 8601 UTC
timestamp_approved: str # ISO 8601 UTC (null until approved)
requesting_user: str # Staff member email
reviewing_clinician: str # Clinician email/NPI
patient_mrn: str # Medical Record Number (not SSN)
payer_name: str # Insurance carrier
pa_type: str # medication | procedure | imaging | dme
procedure_codes: list # CPT codes
diagnosis_codes: list # ICD-10 codes
prompt_template_id: str # Template version used
model_deployment: str # e.g., gpt-5.4
input_token_count: int # Tokens sent to model
output_token_count: int # Tokens received
generated_output_hash: str # SHA-256 of raw AI output
clinician_edits_made: bool # Did clinician modify the draft?
edit_diff_hash: str # SHA-256 of final vs. original
approval_status: str # pending | approved | rejected
submission_method: str # fax | portal | electronic
letter_pdf_hash: str # SHA-256 of final PDF
# Write to Cosmos DB
from azure.cosmos import CosmosClient
def log_pa_event(audit_log: PAAuditLog):
client = CosmosClient(COSMOS_ENDPOINT, COSMOS_KEY)
db = client.get_database_client('pa-letter-db')
container = db.get_container_client('audit-logs')
container.create_item(body=asdict(audit_log))NEVER log the full text of the PA letter in the audit database — this creates an additional PHI repository that increases breach surface. Instead, store SHA-256 hashes and keep the actual letters in the practice's EHR document management system or a separate encrypted Azure Blob Storage container with strict access controls. Set Cosmos DB TTL to -1 (no auto-deletion) and implement a manual purge process after 7 years. Enable Cosmos DB diagnostic logging to Azure Monitor for infrastructure-level audit trail.
Step 12: Conduct Staff Training and Go-Live
Conduct two training sessions: (1) Technical training for PA staff (90 minutes) covering the PA letter workflow, data entry, template selection, and submission process. (2) Clinical review training for providers (60 minutes) covering AI output review best practices, hallucination detection, edit workflows, and approval process. Create laminated quick-reference cards for each workstation. Go live with a 2-week pilot period where AI-generated letters are created in parallel with the existing manual process to validate quality and catch issues.
Training should include live demonstrations with de-identified patient data. Key training topics: (1) How to select the correct prompt template for each payer/procedure combination. (2) What hallucination looks like in a PA letter (fabricated study citations, incorrect drug dosages, made-up policy numbers). (3) How to edit and approve letters in the system. (4) When to escalate to the MSP (system errors, unexpected outputs, new payer requirements). (5) AI Acceptable Use Policy review and sign-off. All staff must sign the AI Acceptable Use Policy before gaining system access. Keep sign-off records for HIPAA documentation.
Custom AI Components
PA Letter Generation System Prompt
Type: prompt The core system prompt that instructs GPT-5.4 on how to generate prior authorization letters. This prompt establishes the AI's role, output format, clinical accuracy requirements, and compliance guardrails. It is stored in Azure Cosmos DB as a versioned template and retrieved at runtime by the application.
Implementation:
PA Letter Generation System Prompt (version 1.0)
Payer-Specific Prompt Templates
Type: prompt A library of payer-specific prompt template extensions that augment the system prompt with carrier-specific requirements, known approval criteria, and formatting preferences. These are stored in Cosmos DB and selected based on the payer identified in the PA request. MSP maintains and updates these templates as payer requirements change.
Implementation:
UnitedHealthcare (UHC) — tmpl-uhc-medical-v1
Aetna — tmpl-aetna-medical-v1
Cigna — tmpl-cigna-medical-v1
BlueCross BlueShield (Generic) — tmpl-bcbs-medical-v1
Medicare Advantage — tmpl-medicare-adv-v1
Dental Insurance (Generic) — tmpl-dental-generic-v1
PA Letter Generation API Endpoint
Type: integration The core FastAPI application endpoint that orchestrates the PA letter generation workflow. Accepts structured patient and PA request data, retrieves the appropriate prompt template, calls Azure OpenAI, and returns the draft letter for clinician review. Includes audit logging and input validation.
Implementation:
# main.py - FastAPI Application for PA Letter Generation
from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.responses import HTMLResponse
from fastapi.security import HTTPBearer
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
import hashlib
import uuid
import os
from openai import AzureOpenAI
from azure.cosmos import CosmosClient
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential
app = FastAPI(title='PA Letter Generator', version='1.0.0')
# Configuration from environment
AOAI_ENDPOINT = os.environ['AZURE_OPENAI_ENDPOINT']
AOAI_DEPLOYMENT = os.environ['AZURE_OPENAI_DEPLOYMENT']
AOAI_API_VERSION = os.environ['AZURE_OPENAI_API_VERSION']
COSMOS_ENDPOINT = os.environ['COSMOS_ENDPOINT']
KEY_VAULT_URL = os.environ['KEY_VAULT_URL']
# Initialize clients with Managed Identity
credential = DefaultAzureCredential()
kv_client = SecretClient(vault_url=KEY_VAULT_URL, credential=credential)
aoai_key = kv_client.get_secret('aoai-api-key').value
oai_client = AzureOpenAI(
azure_endpoint=AOAI_ENDPOINT,
api_key=aoai_key,
api_version=AOAI_API_VERSION
)
cosmos_client = CosmosClient(COSMOS_ENDPOINT, credential=credential)
db = cosmos_client.get_database_client('pa-letter-db')
audit_container = db.get_container_client('audit-logs')
template_container = db.get_container_client('prompt-templates')
# Pydantic models
class PARequest(BaseModel):
patient_name: str
patient_dob: str
patient_mrn: str
member_id: str
group_number: Optional[str] = None
payer_name: str
payer_address: Optional[str] = None
provider_name: str
provider_npi: str
provider_specialty: str
practice_name: str
practice_address: str
practice_phone: str
practice_fax: str
diagnosis_codes: List[str] = Field(..., description='ICD-10 codes with descriptions, e.g. ["M54.5 - Low back pain"]')
procedure_codes: List[str] = Field(..., description='CPT/CDT codes with descriptions, e.g. ["72148 - MRI lumbar spine without contrast"]')
clinical_history: str = Field(..., description='Relevant clinical history and exam findings')
failed_treatments: Optional[str] = Field(None, description='Prior treatments attempted and outcomes')
medical_necessity: str = Field(..., description='Clinical rationale for the requested service')
urgency: Optional[str] = Field(None, description='If urgent, explain clinical urgency')
additional_context: Optional[str] = Field(None, description='Any additional payer-specific info')
pa_type: str = Field(..., description='medication | procedure | imaging | dme | dental')
class PAResponse(BaseModel):
request_id: str
draft_letter: str
model_used: str
input_tokens: int
output_tokens: int
generated_at: str
approval_required: bool = True
warnings: List[str]
def get_system_prompt():
return """You are a medical prior authorization letter drafting assistant working within a HIPAA-compliant healthcare environment. Your role is to generate professional, clinically appropriate prior authorization request letters addressed to insurance carriers on behalf of the requesting provider.
CRITICAL RULES:
1. ONLY use the clinical information explicitly provided in the user message. NEVER fabricate, assume, or hallucinate any clinical details, study citations, drug dosages, policy numbers, or patient information.
2. If information needed for a complete letter is missing from the input, clearly mark it with [MISSING: description of needed information] so the reviewing clinician can fill it in.
3. NEVER invent or cite specific clinical studies, journal articles, or guideline references unless they are provided verbatim in the input. Instead, use phrases like "per current evidence-based guidelines" or "consistent with standard of care for this condition."
4. Every letter MUST include the following disclaimer at the bottom: "This letter was drafted with AI assistance and has been reviewed and approved by the undersigned provider."
5. Format the letter as a formal business letter with: date, provider letterhead placeholder, payer name and address, RE line with patient name and member ID, salutation, body paragraphs, and provider signature block.
LETTER STRUCTURE:
- Paragraph 1: Provider introduction, patient identification, and specific authorization request (procedure/medication/service with CPT/HCPCS/CDT codes)
- Paragraph 2: Clinical history and diagnosis (ICD-10 codes with descriptions)
- Paragraph 3: Medical necessity justification
- Paragraph 4: Treatment plan and expected outcomes
- Paragraph 5: Urgency statement (if applicable) and request for expedited review
- Closing: Provider contact information for peer-to-peer review, signature block
TONE: Professional, clinical, assertive but respectful.
OUTPUT FORMAT: Plain text formatted as a letter. Do not use markdown."""
def get_payer_template(payer_name: str, pa_type: str) -> str:
query = f"SELECT * FROM c WHERE CONTAINS(LOWER(c.payer), LOWER('{payer_name}')) AND c.category = '{pa_type}'"
templates = list(template_container.query_items(query=query, enable_cross_partition_query=True))
if templates:
return templates[0].get('additional_instructions', '')
return ''
def build_user_message(req: PARequest, payer_template: str) -> str:
msg = f"""Generate a prior authorization request letter with the following information:
PATIENT INFORMATION:
- Name: {req.patient_name}
- Date of Birth: {req.patient_dob}
- MRN: {req.patient_mrn}
- Member ID: {req.member_id}
- Group Number: {req.group_number or '[MISSING: Group Number]'}
INSURANCE CARRIER:
- Payer: {req.payer_name}
- Address: {req.payer_address or '[MISSING: Payer Address]'}
REQUESTING PROVIDER:
- Name: {req.provider_name}
- NPI: {req.provider_npi}
- Specialty: {req.provider_specialty}
- Practice: {req.practice_name}
- Address: {req.practice_address}
- Phone: {req.practice_phone}
- Fax: {req.practice_fax}
DIAGNOSES:
{chr(10).join('- ' + d for d in req.diagnosis_codes)}
REQUESTED SERVICES:
{chr(10).join('- ' + p for p in req.procedure_codes)}
CLINICAL HISTORY:
{req.clinical_history}
"""
if req.failed_treatments:
msg += f"\nPRIOR TREATMENTS ATTEMPTED:\n{req.failed_treatments}\n"
msg += f"\nMEDICAL NECESSITY JUSTIFICATION:\n{req.medical_necessity}\n"
if req.urgency:
msg += f"\nURGENCY:\n{req.urgency}\n"
if req.additional_context:
msg += f"\nADDITIONAL CONTEXT:\n{req.additional_context}\n"
if payer_template:
msg += f"\nPAYER-SPECIFIC REQUIREMENTS:\n{payer_template}\n"
return msg
@app.post('/api/generate-pa-letter', response_model=PAResponse)
async def generate_pa_letter(req: PARequest, request: Request):
request_id = str(uuid.uuid4())
warnings = []
# Get payer-specific template
payer_template = get_payer_template(req.payer_name, req.pa_type)
if not payer_template:
warnings.append(f'No payer-specific template found for {req.payer_name}. Using generic format.')
# Build messages
system_prompt = get_system_prompt()
user_message = build_user_message(req, payer_template)
# Call Azure OpenAI
response = oai_client.chat.completions.create(
model=AOAI_DEPLOYMENT,
messages=[
{'role': 'system', 'content': system_prompt},
{'role': 'user', 'content': user_message}
],
temperature=0.3,
max_tokens=3000,
top_p=0.95
)
draft_letter = response.choices[0].message.content
input_tokens = response.usage.prompt_tokens
output_tokens = response.usage.completion_tokens
# Check for hallucination markers
if '[MISSING:' in draft_letter:
warnings.append('Letter contains [MISSING] placeholders that require clinician input.')
# Log to Cosmos DB
audit_entry = {
'id': request_id,
'practiceId': req.practice_name,
'timestamp_created': datetime.utcnow().isoformat(),
'timestamp_approved': None,
'requesting_user': request.headers.get('X-User-Email', 'unknown'),
'reviewing_clinician': None,
'patient_mrn': req.patient_mrn,
'payer_name': req.payer_name,
'pa_type': req.pa_type,
'procedure_codes': req.procedure_codes,
'diagnosis_codes': req.diagnosis_codes,
'prompt_template_id': f'system-v1.0 + {req.payer_name}-{req.pa_type}',
'model_deployment': AOAI_DEPLOYMENT,
'input_token_count': input_tokens,
'output_token_count': output_tokens,
'generated_output_hash': hashlib.sha256(draft_letter.encode()).hexdigest(),
'clinician_edits_made': False,
'edit_diff_hash': None,
'approval_status': 'pending',
'submission_method': None,
'letter_pdf_hash': None
}
audit_container.create_item(body=audit_entry)
return PAResponse(
request_id=request_id,
draft_letter=draft_letter,
model_used=AOAI_DEPLOYMENT,
input_tokens=input_tokens,
output_tokens=output_tokens,
generated_at=datetime.utcnow().isoformat(),
approval_required=True,
warnings=warnings
)
@app.post('/api/approve-pa-letter/{request_id}')
async def approve_pa_letter(request_id: str, clinician_email: str, final_letter_text: str, submission_method: str, request: Request):
# Retrieve audit log
audit_entry = audit_container.read_item(item=request_id, partition_key=request.headers.get('X-Practice-Name', ''))
# Update with approval
original_hash = audit_entry['generated_output_hash']
final_hash = hashlib.sha256(final_letter_text.encode()).hexdigest()
audit_entry['timestamp_approved'] = datetime.utcnow().isoformat()
audit_entry['reviewing_clinician'] = clinician_email
audit_entry['clinician_edits_made'] = (original_hash != final_hash)
audit_entry['edit_diff_hash'] = final_hash
audit_entry['approval_status'] = 'approved'
audit_entry['submission_method'] = submission_method
audit_container.replace_item(item=request_id, body=audit_entry)
return {'status': 'approved', 'request_id': request_id}
@app.get('/health')
async def health_check():
return {'status': 'healthy', 'timestamp': datetime.utcnow().isoformat()}PA Letter Generation System Prompt
Clinician Review Web Interface
Type: workflow A simple, clean web interface that PA staff use to input request data and clinicians use to review, edit, and approve AI-generated letters. Built with Jinja2 templates served by the FastAPI backend. Enforces the human-in-the-loop workflow.
Implementation
PA Request Form (Staff-Facing)
- Form fields map 1:1 to the PARequest Pydantic model
- Dropdown selectors for: Payer (populated from Cosmos DB template list), PA Type (medication/procedure/imaging/dme/dental), Provider (populated from practice roster)
- Free-text fields for: clinical history, failed treatments, medical necessity, urgency, additional context
- ICD-10 and CPT/CDT code lookup with autocomplete (use a client-side library like icd10-autocomplete or embed NLM API lookup)
- 'Generate Draft' button submits to /api/generate-pa-letter
- Requires Entra ID authentication; logs staff user identity
Clinician Review View (Provider-Facing)
- Displays the AI-generated letter in an editable rich-text area
- Side panel shows: patient summary, original input data, any [MISSING] warnings highlighted in yellow
- RED BANNER at top: 'AI-GENERATED DRAFT — CLINICIAN REVIEW REQUIRED BEFORE SUBMISSION'
- 'Approve and Generate PDF' button — disabled until all checklist items are checked
- 'Reject and Return to Staff' button with required rejection reason
- Submits to /api/approve-pa-letter/{request_id}
Audit Dashboard (MSP/Admin-Facing)
- Table of all PA letters generated, with columns: Date, Patient MRN, Payer, PA Type, Status (pending/approved/rejected), Review Time, Edits Made
- Filters by date range, payer, status, provider
- Monthly summary: total letters, approval rate, average review time, edit rate, cost (token usage)
- Export to CSV for compliance reporting
Implementation Notes
- Use Jinja2 templates with Bootstrap 5 for responsive layout
- All pages require Entra ID authentication via Azure App Service Easy Auth
- Session timeout: 15 minutes of inactivity (HIPAA requirement)
- All form submissions use CSRF protection tokens
- PDF generation uses WeasyPrint library to convert approved letter to PDF with practice letterhead template
- PDF is downloaded to clinician's machine for faxing or uploaded to payer portal; NOT stored in the web app (stored in EHR document management)
FHIR Patient Data Connector
Type: integration Middleware component that connects to the practice's EHR via FHIR R4 APIs to auto-populate PA request forms with patient demographics, diagnoses, medications, and recent clinical notes. Implements SMART on FHIR OAuth 2.0 authorization.
Implementation:
# fhir_connector.py
import requests
from typing import Optional, Dict, Any
from datetime import datetime, timedelta
import logging
logger = logging.getLogger(__name__)
class FHIRConnector:
def __init__(self, fhir_base_url: str, client_id: str, client_secret: str, token_url: str):
self.fhir_base_url = fhir_base_url.rstrip('/')
self.client_id = client_id
self.client_secret = client_secret
self.token_url = token_url
self._access_token = None
self._token_expiry = None
def _get_token(self) -> str:
if self._access_token and self._token_expiry and datetime.utcnow() < self._token_expiry:
return self._access_token
response = requests.post(self.token_url, data={
'grant_type': 'client_credentials',
'client_id': self.client_id,
'client_secret': self.client_secret,
'scope': f'{self.fhir_base_url}/.default'
})
response.raise_for_status()
token_data = response.json()
self._access_token = token_data['access_token']
self._token_expiry = datetime.utcnow() + timedelta(seconds=token_data.get('expires_in', 3600) - 60)
return self._access_token
def _headers(self) -> Dict[str, str]:
return {
'Authorization': f'Bearer {self._get_token()}',
'Accept': 'application/fhir+json'
}
def get_patient(self, patient_id: str) -> Dict[str, Any]:
resp = requests.get(f'{self.fhir_base_url}/Patient/{patient_id}', headers=self._headers())
resp.raise_for_status()
patient = resp.json()
name = patient.get('name', [{}])[0]
return {
'name': f"{name.get('given', [''])[0]} {name.get('family', '')}",
'dob': patient.get('birthDate', ''),
'mrn': next((i['value'] for i in patient.get('identifier', []) if i.get('type', {}).get('coding', [{}])[0].get('code') == 'MR'), ''),
'member_id': next((i['value'] for i in patient.get('identifier', []) if 'insurance' in i.get('system', '').lower()), ''),
'gender': patient.get('gender', ''),
'address': self._format_address(patient.get('address', [{}])[0]) if patient.get('address') else ''
}
def get_active_conditions(self, patient_id: str) -> list:
resp = requests.get(
f'{self.fhir_base_url}/Condition?patient={patient_id}&clinical-status=active',
headers=self._headers()
)
resp.raise_for_status()
bundle = resp.json()
conditions = []
for entry in bundle.get('entry', []):
resource = entry.get('resource', {})
coding = resource.get('code', {}).get('coding', [{}])[0]
conditions.append({
'code': coding.get('code', ''),
'display': coding.get('display', ''),
'system': coding.get('system', ''),
'onset': resource.get('onsetDateTime', '')
})
return conditions
def get_active_medications(self, patient_id: str) -> list:
resp = requests.get(
f'{self.fhir_base_url}/MedicationRequest?patient={patient_id}&status=active',
headers=self._headers()
)
resp.raise_for_status()
bundle = resp.json()
medications = []
for entry in bundle.get('entry', []):
resource = entry.get('resource', {})
med_coding = resource.get('medicationCodeableConcept', {}).get('coding', [{}])[0]
medications.append({
'name': med_coding.get('display', ''),
'code': med_coding.get('code', ''),
'dosage': resource.get('dosageInstruction', [{}])[0].get('text', '') if resource.get('dosageInstruction') else '',
'authored': resource.get('authoredOn', '')
})
return medications
def get_pending_service_requests(self, patient_id: str) -> list:
resp = requests.get(
f'{self.fhir_base_url}/ServiceRequest?patient={patient_id}&status=active',
headers=self._headers()
)
resp.raise_for_status()
bundle = resp.json()
requests_list = []
for entry in bundle.get('entry', []):
resource = entry.get('resource', {})
coding = resource.get('code', {}).get('coding', [{}])[0]
requests_list.append({
'code': coding.get('code', ''),
'display': coding.get('display', ''),
'reason': resource.get('reasonCode', [{}])[0].get('text', '') if resource.get('reasonCode') else '',
'authored': resource.get('authoredOn', '')
})
return requests_list
def get_recent_notes(self, patient_id: str, days: int = 90) -> list:
date_cutoff = (datetime.utcnow() - timedelta(days=days)).strftime('%Y-%m-%d')
resp = requests.get(
f'{self.fhir_base_url}/DocumentReference?patient={patient_id}&date=ge{date_cutoff}',
headers=self._headers()
)
resp.raise_for_status()
bundle = resp.json()
notes = []
for entry in bundle.get('entry', []):
resource = entry.get('resource', {})
for content in resource.get('content', []):
attachment = content.get('attachment', {})
if attachment.get('contentType') == 'text/plain' and attachment.get('data'):
import base64
notes.append({
'date': resource.get('date', ''),
'type': resource.get('type', {}).get('text', 'Clinical Note'),
'text': base64.b64decode(attachment['data']).decode('utf-8')
})
return notes
def build_pa_context(self, patient_id: str) -> Dict[str, Any]:
"""Aggregates all patient data needed for a PA letter into a single context object."""
return {
'patient': self.get_patient(patient_id),
'conditions': self.get_active_conditions(patient_id),
'medications': self.get_active_medications(patient_id),
'service_requests': self.get_pending_service_requests(patient_id),
'recent_notes': self.get_recent_notes(patient_id)
}
@staticmethod
def _format_address(addr: dict) -> str:
lines = addr.get('line', [])
city = addr.get('city', '')
state = addr.get('state', '')
postal = addr.get('postalCode', '')
return f"{', '.join(lines)}, {city}, {state} {postal}".strip(', ')PDF Letter Generator with Practice Letterhead
Type: skill Converts approved PA letter text into a professional PDF document with the practice's letterhead, formatted for faxing or upload to payer portals. Uses WeasyPrint for HTML-to-PDF conversion.
Implementation:
# pdf_generator.py
from weasyprint import HTML
from datetime import datetime
import os
import hashlib
def generate_pa_letter_pdf(
letter_text: str,
practice_name: str,
practice_address: str,
practice_phone: str,
practice_fax: str,
practice_logo_path: str = None
) -> tuple:
"""
Generates a PDF from the approved PA letter text.
Returns (pdf_bytes, sha256_hash).
"""
logo_html = ''
if practice_logo_path and os.path.exists(practice_logo_path):
import base64
with open(practice_logo_path, 'rb') as f:
logo_b64 = base64.b64encode(f.read()).decode()
logo_html = f'<img src="data:image/png;base64,{logo_b64}" style="max-height:80px; margin-bottom:10px;">'
# Convert plain text letter to HTML paragraphs
letter_paragraphs = letter_text.strip().split('\n\n')
letter_html_body = '\n'.join(f'<p>{para.replace(chr(10), "<br>")}</p>' for para in letter_paragraphs)
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
@page {{
size: letter;
margin: 1in;
}}
body {{
font-family: 'Times New Roman', Times, serif;
font-size: 12pt;
line-height: 1.5;
color: #000000;
}}
.letterhead {{
text-align: center;
border-bottom: 2px solid #003366;
padding-bottom: 15px;
margin-bottom: 25px;
}}
.letterhead h1 {{
font-size: 18pt;
color: #003366;
margin: 5px 0;
}}
.letterhead p {{
font-size: 10pt;
color: #555;
margin: 2px 0;
}}
.letter-body p {{
margin-bottom: 12px;
text-align: justify;
}}
.disclaimer {{
font-size: 9pt;
color: #777;
border-top: 1px solid #ccc;
padding-top: 10px;
margin-top: 30px;
}}
.generated-info {{
font-size: 8pt;
color: #999;
text-align: right;
margin-top: 20px;
}}
</style>
</head>
<body>
<div class="letterhead">
{logo_html}
<h1>{practice_name}</h1>
<p>{practice_address}</p>
<p>Phone: {practice_phone} | Fax: {practice_fax}</p>
</div>
<div class="letter-body">
{letter_html_body}
</div>
<div class="disclaimer">
This letter was drafted with AI assistance and has been reviewed and approved by the undersigned provider.
</div>
<div class="generated-info">
Generated: {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')} | Document ID: {{request_id}}
</div>
</body>
</html>
"""
pdf_bytes = HTML(string=html_content).write_pdf()
pdf_hash = hashlib.sha256(pdf_bytes).hexdigest()
return pdf_bytes, pdf_hashTesting & Validation
- NETWORK SECURITY TEST: From a workstation on the Clinical VLAN, attempt to access chat.openai.com and claude.ai — both should be blocked by DNSFilter. Verify by checking DNSFilter dashboard for blocked query logs. Then access doximity.com and *.openai.azure.com — both should resolve successfully.
- ENCRYPTION VERIFICATION: On each enrolled workstation, open PowerShell as admin and run 'manage-bde -status C:' — verify BitLocker status shows 'Protection On' and 'Encryption Method: XTS-AES 256'. Check that all workstations appear as 'Compliant' in the Intune device compliance dashboard.
- MFA ENFORCEMENT TEST: Attempt to log into Microsoft 365 and the PA letter generator web app from a new device/browser without completing MFA — access should be denied. Verify Conditional Access policy is in 'Enforced' mode (not Report-Only) in Entra ID portal.
- DOXIMITY GPT FUNCTIONAL TEST: Have a clinician log into DoxGPT and generate a sample PA letter using de-identified test patient data. Verify the output is formatted as a professional letter, contains no hallucinated references, and includes appropriate clinical language. Time the process — target is under 3 minutes for draft generation.
- COVERMYMEDS INTEGRATION TEST: From within the EHR, initiate an ePA request for a common medication (e.g., a biologic requiring PA). Verify the request flows through to the CoverMyMeds portal, the correct payer is identified, and the ePA form is pre-populated with patient and medication data from the EHR.
- AZURE OPENAI API TEST: Using curl or Postman, send a test request to the deployed GPT-5.4 endpoint with a sample PA letter prompt (using de-identified data). Verify: (1) Response returns within 10 seconds, (2) HTTP 200 status, (3) Response content is a properly formatted PA letter, (4) No error messages or rate limiting.
- CUSTOM APP END-TO-END TEST: Access the PA letter generator web app via browser. Log in with Entra ID credentials. Complete the PA request form with test data for a common scenario (e.g., MRI lumbar spine for chronic low back pain, UnitedHealthcare payer). Submit and verify: (1) Draft letter is generated within 15 seconds, (2) Letter includes correct patient demographics, diagnosis codes, and procedure codes, (3) Payer-specific template instructions are reflected in the output, (4) [MISSING] placeholders appear for any omitted fields, (5) Audit log entry appears in Cosmos DB.
- CLINICIAN REVIEW WORKFLOW TEST: Generate a PA letter draft, then log in as a clinician user. Verify: (1) The approval checklist is displayed and all items must be checked, (2) The 'Approve' button is disabled until all checklist items are checked, (3) Edits to the letter text are preserved, (4) After approval, the audit log is updated with clinician email, approval timestamp, and edit hash, (5) PDF download generates a properly formatted document with practice letterhead.
- HALLUCINATION DETECTION TEST: Submit a PA request with minimal clinical information and verify the AI output contains [MISSING] placeholders rather than fabricated details. Specifically test: (1) Submit without clinical history — letter should contain [MISSING: clinical history], (2) Submit for an unusual procedure — letter should NOT contain fabricated study citations, (3) Submit with a fictional payer name — system should warn 'No payer-specific template found' and use generic format.
- HIPAA AUDIT LOG COMPLETENESS TEST: Generate 5 PA letters through the system. Query Cosmos DB and verify each letter has a complete audit record with all required fields populated: request_id, practice_id, timestamps, user identities, patient MRN, payer, codes, model deployment, token counts, output hash, and approval status. Export the audit data and verify it can be formatted into a compliance report.
- FHIR INTEGRATION TEST (if applicable): From the PA letter generator, use the patient lookup feature to pull data from the EHR via FHIR. Verify: (1) Patient demographics are correctly retrieved, (2) Active diagnoses (ICD-10 codes) are listed, (3) Current medications are populated, (4) Pending service requests appear, (5) All data auto-populates the PA request form correctly. Test with 3 different patients to verify consistency.
- BACKUP AND RECOVERY TEST: Verify the Datto SIRIS backup includes the EHR server and any local configuration files. Initiate a test restore of a single file from the latest backup. Confirm backup encryption is enabled and verify backup reports are being sent to the MSP monitoring dashboard. Document the RPO (Recovery Point Objective) and RTO (Recovery Time Objective) achieved.
curl -X POST https://aoai-pa-lettergen.openai.azure.com/openai/deployments/gpt-5.4/chat/completions?api-version=2024-08-06 -H 'Content-Type: application/json' -H 'api-key: <key>' -d '{"messages":[{"role":"system","content":"You are a PA letter assistant."},{"role":"user","content":"Draft a PA letter for a knee MRI."}],"temperature":0.3}'Client Handoff
CLIENT HANDOFF DOCUMENTATION AND TRAINING AGENDA:
Maintenance
1. Monitoring Cadence
- Daily: Automated monitoring of Azure App Service health, API response times, and error rates via Azure Monitor alerts. Alert threshold: >5% error rate or >30-second response time triggers MSP notification.
- Weekly: Review SentinelOne threat dashboard for the practice's endpoints. Review DNSFilter logs for any attempted access to blocked AI tools (potential policy violations). Check Datto backup success/failure reports.
- Monthly: Generate and review PA letter audit report from Cosmos DB. Review metrics: total letters generated, approval rate, average review time, edit rate, token usage/cost. Share summary report with practice manager. Review Azure cost report and optimize if needed.
- Quarterly: Conduct HIPAA Security Rule compliance spot-check. Verify all BAAs are current. Test backup restore procedure. Review and update payer-specific prompt templates based on staff feedback and payer policy changes. Conduct 30-minute refresher training if usage patterns indicate need.
- Annually: Full HIPAA Risk Assessment update. Review all vendor contracts and BAAs for renewal. Assess Azure OpenAI model updates (new GPT versions) and test before upgrading. Review CMS-0057-F compliance timeline and plan for FHIR PA API integration by January 2027 deadline.
2. Update Schedule
- Azure OpenAI model versions: Test new model versions (e.g., GPT-5.4 updates) in a staging deployment before updating production. Allow 2-week testing period with side-by-side comparison of output quality. Update quarterly or when significant model improvements are released.
- Prompt templates: Review and update payer-specific templates when: (a) a payer changes their PA requirements or medical policies, (b) staff report consistent issues with a specific payer's letters, (c) new payers are added to the practice's panel. Template updates should be versioned in Cosmos DB with change logs.
- Application code: Apply security patches within 72 hours of release. Feature updates deployed quarterly with practice approval. All deployments use Blue/Green deployment slots in Azure App Service for zero-downtime updates.
- Security stack: SentinelOne, DNSFilter, and FortiGate signature updates are automatic. Firmware updates for FortiGate applied quarterly during maintenance windows (Saturday 6-8 AM). Windows updates managed via Intune with 7-day deferral for testing.
3. SLA Considerations
- Response time — Critical issues (system down, PHI exposure): 1-hour response, 4-hour resolution target.
- Response time — High (workflow disruption): 4-hour response, 1 business day resolution.
- Response time — Medium (template updates, minor bugs): 1 business day response, 1 week resolution.
- Response time — Low (feature requests, optimization): 1 week response, scheduled for next quarterly update.
- Availability target: 99.5% uptime for the PA letter generator during business hours (M-F 7AM-7PM local time). Azure App Service SLA is 99.95%; the bottleneck is typically the practice's internet connection.
- Data retention: Audit logs retained for 7 years per HIPAA. Cosmos DB serverless pricing means storage costs scale with volume (~$0.25/GB/month). At ~1KB per audit log entry and 500 letters/month, annual storage is negligible (<$1/year).
4. Escalation Paths
- Level 1 (Practice Staff): Check quick reference guide, verify internet connectivity, try clearing browser cache, check Azure status page (status.azure.com).
- Level 2 (MSP Help Desk): Remote troubleshooting, App Service restart, DNS/firewall rule verification, Entra ID access issues.
- Level 3 (MSP Engineering): Azure resource configuration, FHIR integration issues, prompt template engineering, code deployments.
- Level 4 (Vendor Escalation): Azure OpenAI service outages (Microsoft Support), EHR API issues (EHR vendor support), SaaS platform issues (vendor support team).
5. CMS-0057-F Compliance Roadmap
Alternatives
...
DoxGPT Only (Zero-Cost Quick Start)
Deploy Doximity GPT as the sole PA letter drafting tool. Each clinician uses their verified Doximity account to access DoxGPT, pastes patient information into the prompt, and receives a draft PA letter. No custom infrastructure, no API costs, no application development. MSP role is limited to HIPAA stack deployment (firewall, EDR, MFA, backup) and staff training.
Turnkey SaaS Platform Only (Tandem AI / Linear Health / Ethermed)
Skip the custom Azure OpenAI build entirely and rely on a purpose-built PA automation SaaS platform. The vendor handles all AI model hosting, EHR integration, payer connectivity, and compliance. MSP role is vendor selection, onboarding coordination, HIPAA stack management, and ongoing vendor relationship management.
Microsoft 365 Copilot with HIPAA Controls
If the practice is already on Microsoft 365 E3/E5 with Copilot licenses, use Microsoft 365 Copilot as the AI letter drafting engine. Staff draft PA letters in Word using Copilot, with custom prompt templates stored in SharePoint. Copilot is covered under the Microsoft HIPAA BAA when used within the M365 enterprise environment.
Open-Source LLM Self-Hosted (Llama 3 / Mistral on Azure)
Instead of using Azure OpenAI (GPT-5.4), deploy an open-source LLM (Meta Llama 3 70B or Mistral Large) on Azure Virtual Machines with GPU (NC-series). This eliminates per-token API costs and keeps all data processing within the MSP-controlled Azure environment with no third-party AI vendor dependency.
Hybrid Approach: Voice AI (Simbie AI / Asepha AI) + Letter Generation
Deploy a voice AI platform like Simbie AI or Asepha AI that automates the phone-based PA workflow (calling payers for benefit verification, status checks, peer-to-peer scheduling) alongside the letter generation system. This addresses both the written and verbal components of the PA process.
Want early access to the full toolkit?