Skip to content

Automated bill audit orchestration

BillAuditEngine.AuditBill is the single entry point for the automated bill-audit workflow. It retrieves a bill and its prior audit (if any), validates the bill and its status, generates or refreshes audit-specific data by enriching every service line with claim, provider, and national-drug-code context, delegates to the bill-edit, service-edit, and MCO-determination operations, invokes the rule-based audit engine, and finally saves the result unless running in read-only mode.

Field Value
Operation ID OP-AA-01
Trigger Caller invokes BillAuditEngine.AuditBill(inBillID, ForceCompleteReAudit, ReadOnlyMode)
Inputs BillID (Decimal), ForceCompleteReAudit (Boolean, default False), ReadOnlyMode (Boolean, default False), IDataPortalInfo supplied at engine construction
Outputs AuditResults enum value in AuditResult; explanatory text in AuditResultMessage; populated clsBillAudit exposed as ProcessedAuditData; persisted CBP_BILL/audit data unless ReadOnlyMode is set; timing/diagnostic entries in TrailMessages
Primary components BillAuditEngine.AuditBill, BillAuditEngine.RetrieveBill, BillAuditEngine.GenerateAuditData, BillAuditEngine.SaveAuditData, BillAuditEngine.GetProvider, BillAuditEngine.GetClaim, BillAuditEngine.GetNationalDrugCode
Entry point Business\SAIF.Business.Claim.Bill.AutomatedAudit\BillAuditEngine.vb

Steps

  1. Reset run state. Clear the audit trail and broken-rules collection, and record the requested ReadOnlyMode and ForceCompleteReAudit flags. Start an audit-trail group tagged "Audit Bill (<BillID>)" and log the mode flags and portal ID.
  2. Initialize result state. Set AuditResult = Success, clear AuditResultMessage, clear any prior AuditExceptions, and clear ProcessedAuditData.
  3. Retrieve the bill. RetrieveBill(inBillID) calls clsBillAudit.GetBillAudit(DPI, inBillID).
  4. Sub-step: GetBillAudit issues IDataPortalInfo.DataAccessLayer.RetrieveObject against a criteria keyed by BillID and returns the hydrated clsBillAudit, which lazily exposes Claim, BillingProvider, MCOInformation, History, and PharmacyEligibilityRange.

Origin: clsBillAudit extends clsBill and its persistence surface (Save, OnSave, OnSaveComplete, BeforeUpdate, GenerateStatusRecord) is implemented by Bill CRUD and Audit and audited services in SAIF.Business.Claim.Bill. - If ReadOnlyMode is requested, call Bill.SetReadOnlyMode() immediately after retrieval. 4. Validate the bill. If Bill.IsValid is False: set AuditResult = InvalidBill (BR-001), copy Bill.ErrorMessages into AuditResultMessage, propagate the bill's broken rules onto the engine, clone the bill into ProcessedAuditData, and return True — no data is saved. 5. Validate bill status. Unless ForceCompleteReAudit is set, require Bill.CurrentBillStatus to be Entered, Reviewed, Audited, or Approved (BR-002). - On any other status: set AuditResult = InvalidStatus, message "Incorrect status for audit: <status>", clone the bill, and return True. 6. Generate/enrich audit data (GenerateAuditData). 1. If the bill is already audited and this is not a forced re-audit, clear the prior audit's messages so stale messages are not shown alongside the new pass. 2. Decide whether the previous audit is eligible for deletion and regeneration: - Not eligible (already audited, status is not Entered, and not forced): keep the existing audit data unchanged. - Eligible (forced re-audit on an already-audited bill, status Entered, or no prior audit): unless read-only, delete the prior audit (DeletePreviousAudit — deletes Bill.Audit and saves the deletion via SaveChildObject; returns False and aborts on error) and call Bill.GenerateAudit() to build a fresh audit header and audited-service rows.

     > **Origin**: `GenerateAudit`/`OnGenerateAudit`/`OnGenerateAuditService` are implemented on `clsBillAudit`, which overrides base behavior from `clsBill`/`clsAudit` in [Audit and audited services](../../saif-business-claim-bill/operations/audit-and-audited-services.md) (`SAIF.Business.Claim.Bill`). The override sets `Audit.AuditReceiptDate`, `Audit.PrintEobFlag` (based on a hard-coded non-EOB provider tax-ID list), and applies interpreter-service (`D0004`/`D0006`) minimum-quantity and 15-minute-rounding normalization to each newly generated audited service.
  - On `GenerateAudit()` failure: return `False` (propagates to step 7 below).
  1. If data generation succeeded so far, enrich the bill:

    • If Bill.ClaimID is populated, retrieve claim context via GetClaim(ClaimID).

      • Sub-step: GetClaim calls clsClaimData.GetData(DPI, ClaimID); if the returned claim IsReplaced, it re-retrieves using Claim.ReplacedClaimID and adds an informational message "Claim <old> has been merged to <new>.".

      Origin: Claim retrieval and the merge/replacement chain are implemented by Retrieve claim summary data in SAIF.Business.View.Claim. - If Bill.ProviderLocationID is populated, retrieve billing-provider context via GetProvider(ProviderLocationID), caching results per provider ID for the run.

      Origin: Provider location retrieval is implemented by Retrieve provider location in SAIF.BOF.Business.View.Provider. - For each billed service, build a clsBillAuditService wrapper (GetAuditService locates or generates the matching audited-service row) and enrich it: - Resolve servicing/prescribing provider location via GetProvider when a non-zero location ID is present. - For pharmacy-type services, resolve the national drug record via GetNationalDrugCode, trying the existing NationalDrugCode first and falling back to ServiceCode for new records.

      Origin: National drug code retrieval and price history are implemented by National drug validation in SAIF.Business.Claim.Bill. - For InpatientHospital bills, force each service's ServiceDate/ServiceEndDate to the bill's statement from/thru dates when those are populated. - For non-InpatientHospital bills with at least one service, recompute the bill's StatementFromDate/StatementThruDate as the min/max of the audited services' dates. 4. Return True if enrichment completed; False propagates to the caller as a generation failure. 7. Handle generation failure. If step 6 returns False: set AuditResult = FailAuditGeneration, message "Failed to generate audit data.", clone the bill, and return False (this is one of the three cases where AuditBill reports failure to the caller). 8. Guard against an empty bill. If Bill.Services.Count = 0, call BillAuditor.GenerateError(Bill, "Bill data - no services to audit") (an informational audit message, not a fatal error at this point). 9. Run bill edit validation. Delegate to Bill edit validation via BillEditor.EditBill(Bill) (BR-003). - On failure: set AuditResult = FailBillEdits, message from BillEditor.ErrorMessages, and remove any audited-service rows that were newly generated for this run (IsNew) so they are not persisted with an invalid bill. 10. Run service edit and adjustment per service, when bill edits pass. For each item in Bill.ServiceData, delegate to Service edit and adjustment: - Call ServiceEditor.EditService(Bill, Service); a False result marks PassServiceEdits = False for this run. - If the service IsAuditable (ReauditCode = "R"): remove any prior adjustments for that service, then call ServiceEditor.AdjustService(Bill, Service); a False result also marks PassServiceEdits = False. - Propagate broken rules from ServiceEditor to the engine after each service. - After all services: sum Bill.Audit.TotalAllowedAmount and TotalMcoAdminFeeAmount across Bill.ServiceData (BR-004). 11. Determine whether there is anything to audit. If Bill.ServiceData.ForReAudit.Count > 0: - If service edits failed: set AuditResult = FailServiceEdits. - Otherwise: - Unless Bill.BillType = WorkerReimbursement, delegate to MCO determination via MCOManager.DetermineMCO(Bill). - Delegate to Bill audit rule execution via BillAuditor.AuditBill(Bill). - On failure: set AuditResult = FailAuditCheck, message from BillAuditor.ErrorMessages. 12. Propagate remaining broken rules. Copy broken rules from BillEditor and BillAuditor onto the engine's rule collection. 13. Save the result (SaveAuditData). - If ReadOnlyMode: call Bill.GenerateStatusRecord() only (builds an in-memory AUD status row and routing target) and log "Process Only Mode - Data Not Saved!" — no database write occurs. - Otherwise, if Bill.IsDirty or AuditedServiceCount = 0, call Bill.Save().

    Origin: Save()/OnSave()/OnSaveComplete()/BeforeUpdate() cascade the bill header (CBP_BILL via an inline update binding :BillTotalAmount, :ClaimID, :ClaimNumber, :ClaimCheckValue, :StatementFromDate, :StatementThruDate, :DRGCode, :UpdateUser, :UpdateDate, :BillID* against CBP_BILL) together with Audit, StatusHistory, and Diagnosis child collections, implemented by Bill CRUD and Audit and audited services in SAIF.Business.Claim.Bill. Before saving, OnSave() resets any non-blank ReauditCode back to a single blank so re-audited services are not repeatedly re-flagged on the next run. - On save failure: set AuditResult = FailAuditSave, message from Bill.ErrorMessages (one of the three cases where AuditBill reports failure to the caller). 14. Clone and return. Store the final bill state in ProcessedAuditData (CloneBill). The whole method body is wrapped in a Try/Catch; any unhandled exception sets AuditResult = EnvironmentError, captures the exception in AuditExceptions, and is followed by the Finally block stopping the audit-trail group. The method returns False only for EnvironmentError, FailAuditGeneration, or FailAuditSave; all other outcomes (including InvalidBill, InvalidStatus, FailBillEdits, FailServiceEdits, and FailAuditCheck) return True.*

Process flow

flowchart TD
    Start([AuditBill invoked]) --> ResetState[Reset trail and rule state]
    ResetState --> Retrieve[RetrieveBill: clsBillAudit.GetBillAudit]
    Retrieve --> Valid{Bill.IsValid?}
    Valid -->|No| InvalidBill([AuditResult = InvalidBill;<br>return True, no save])
    Valid -->|Yes| StatusCheck{Status eligible<br>or ForceCompleteReAudit?}
    StatusCheck -->|No| InvalidStatus([AuditResult = InvalidStatus;<br>return True])
    StatusCheck -->|Yes| Generate[GenerateAuditData:<br>delete/regenerate audit,<br>enrich claim/provider/NDC]
    Generate -->|Failed| FailGen([AuditResult = FailAuditGeneration;<br>return False])
    Generate -->|Ok| BillEdits[Bill edit validation]
    BillEdits -->|Fatal error| FailBillEdits([AuditResult = FailBillEdits;<br>discard new audit rows])
    BillEdits -->|Ok| ServiceLoop[Service edit and adjustment<br>per service line]
    ServiceLoop --> ForAudit{ForReAudit.Count > 0?}
    ForAudit -->|No| Save
    ForAudit -->|Yes, failed| FailServiceEdits([AuditResult = FailServiceEdits])
    ForAudit -->|Yes, passed| MCO{BillType =<br>WorkerReimbursement?}
    MCO -->|No| MCODetermine[MCO determination]
    MCO -->|Yes| AuditRun
    MCODetermine --> AuditRun[Bill audit rule execution]
    AuditRun -->|Failed| FailAuditCheck([AuditResult = FailAuditCheck])
    AuditRun -->|Ok| Save[SaveAuditData]
    FailServiceEdits --> Save
    FailAuditCheck --> Save
    FailBillEdits --> Save
    Save -->|ReadOnly| StatusOnly([GenerateStatusRecord only;<br>no DB write])
    Save -->|Failed| FailSave([AuditResult = FailAuditSave;<br>return False])
    Save -->|Ok| Clone[Clone bill into ProcessedAuditData]
    StatusOnly --> Clone
    Clone --> Done([Return True/False<br>per AuditResult])

Failure and edge-case behavior

  • An invalid bill or ineligible status is not treated as a hard failure: AuditBill returns True, and callers must check AuditResult to detect these conditions.
  • Any exception anywhere in the pipeline is caught, recorded in AuditExceptions, and reported as EnvironmentError; the audit-trail group is always stopped via Finally regardless of outcome.
  • If GenerateAuditData fails partway through deleting the previous audit, the method returns False immediately without attempting to regenerate — a prior audit may already be deleted with no replacement saved.*
  • ReadOnlyMode never calls Bill.Save(); only an in-memory AUD status record is produced, so no ReadOnlyMode run can persist changes even if later logic assumes a save occurred.
  • Only EnvironmentError, FailAuditGeneration, and FailAuditSave cause AuditBill to return False; all business-rule failures return True, which can be surprising for callers that treat the boolean return as a simple success/failure flag.

Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts.

Sources

  • Business\SAIF.Business.Claim.Bill.AutomatedAudit\BillAuditEngine.vb
  • Business\SAIF.Business.Claim.Bill.AutomatedAudit\clsBillAudit.vb
  • Business\SAIF.Business.Claim.Bill.AutomatedAudit\clsBillAuditService.vb
  • Business\SAIF.Business.Claim.Bill.AutomatedAudit\AuditUtilities\AuditTrailManager.vb