---
title: Business rules
summary: Business rules enforced during nightly premium audit batch processing.
status: draft
owner: Premium Audit Team
last_reviewed: 2026-08-14
---

# Business rules

`SAIF.PremiumAudit.Batch` recalculates workers' compensation earned premium from
completed payroll audits and posts the resulting adjustment to the policy system.
The rules below govern which audits are eligible for a nightly run, what payroll
data is acceptable, which rate is applied, when an adjustment is material enough
to post, and how a rejected audit is recorded so that underwriting can correct it.
Payroll-level data rules are delegated to `SAIF.Framework.Validation` and keep that
library's `VAL-` identifiers; all other rules are enforced by the batch executable.

---

## BR-001 Audited payroll must use a recognised class code

| Field | Value |
| --- | --- |
| **Statement** | Every payroll line on an audit must carry a payroll class code that exists in the rating tables and is approved for the policy's state of coverage; an unrecognised code cannot be rated and makes the whole audit unpostable. |
| **Trigger** | Evaluated for each `dbo.PolicyAuditDetail` row at step 2 of the processing loop, before any premium is recalculated. |
| **Enforcement** | Delegated to `SAIF.Framework.Validation` rule VAL-032, applied by `ClassCodeValidator.Validate` against the `dbo.ClassCodeValidation` table. |
| **Outcome** | The audit is marked `Rejected`, the per-audit transaction rolls back, a `dbo.PolicyAuditReject` row records rule VAL-032, and the failure is written to File2 with the policy number and `PolicyAuditId`. |
| **Source** | src/SAIF.Framework.Validation/ClassCodeValidator.vb:64 |

---

## BR-002 Audited payroll amounts must be non-negative and within class limits

| Field | Value |
| --- | --- |
| **Statement** | Reported payroll for a class code must be zero or greater and must not exceed the maximum annual payroll configured for that class code, because a negative or implausible figure would produce a premium adjustment that cannot be collected or refunded. |
| **Trigger** | Evaluated for each `dbo.PolicyAuditDetail` row at step 2, immediately after the class code check in BR-001 passes. |
| **Enforcement** | Delegated to `SAIF.Framework.Validation` rule VAL-037, applied by `ClassCodeValidator.Validate` using the payroll limits held in `dbo.ClassCodeValidation`. |
| **Outcome** | The audit is marked `Rejected`, the transaction rolls back, a `dbo.PolicyAuditReject` row records rule VAL-037, and no `dbo.PremiumTransaction` row is written. |
| **Source** | src/SAIF.Framework.Validation/ClassCodeValidator.vb:118 |

---

## BR-003 Only completed audits on or before the run's audit date are eligible

| Field | Value |
| --- | --- |
| **Statement** | A nightly run may process only those policy audits that underwriting has marked complete with a completion date on or before the supplied audit date and that have not already been posted, so that an audit is never rated twice and an in-flight audit is never rated early. |
| **Trigger** | Evaluated once per run during step 1, when eligible audits are retrieved. |
| **Enforcement** | `dbo.usp_GetPendingAudits` filters `dbo.PolicyAudit` on `Status = 'Pending'` and `CompletedDate <= @AuditDate`, ordering by `PolicyAuditId`; `AuditProcessor.RetrievePendingAudits` passes the validated `AuditDate` and `BatchSize` arguments. |
| **Outcome** | Ineligible audits are simply not returned and remain available to a later run; the count of audits read is written to the File0 run totals. |
| **Source** | src/SAIF.PremiumAudit.Batch/AuditProcessor.vb:142 |

---

## BR-004 Premium is recalculated at the rate in effect on the policy effective date

| Field | Value |
| --- | --- |
| **Statement** | Earned premium must be recalculated using the payroll class code rate that was in effect on the policy effective date, not the rate in effect on the day the batch runs, so that an audit adjustment is consistent with the rates the policy was originally rated with. |
| **Trigger** | Evaluated for every payroll line of an audit that passed validation, at step 3 of the processing loop. |
| **Enforcement** | `AuditProcessor.ResolveRate` selects the `dbo.PayrollClassCode` row whose effective and expiration dates bracket the policy effective date from the rate table loaded during `PreProcess`. |
| **Outcome** | The recalculated premium is compared to the estimated premium and the difference becomes the adjustment amount; when no rate row covers the policy effective date the audit is treated as a per-record failure, rolls back, and is logged to File2. |
| **Source** | src/SAIF.PremiumAudit.Batch/AuditProcessor.vb:268 |

---

## BR-005 An audit posts at most one premium adjustment transaction

| Field | Value |
| --- | --- |
| **Statement** | Posting an audit must produce exactly one premium adjustment transaction and must move the audit out of `Pending` in the same action, so that a re-run or a restart can never bill a policyholder twice for the same audit. |
| **Trigger** | Evaluated at step 3, when the recalculated premium is written back to the policy system. |
| **Enforcement** | `AuditProcessor.PostAdjustment` inserts the `dbo.PremiumTransaction` row and updates `dbo.PolicyAudit.Status`, `PostedDate`, and `PostedBy` inside the single SQL Server transaction opened for that audit. |
| **Outcome** | The transaction commits and the audit becomes `Posted`, so BR-003 excludes it from every later run; if either statement fails the transaction rolls back and the audit stays `Pending` with no transaction row. |
| **Source** | src/SAIF.PremiumAudit.Batch/AuditProcessor.vb:341 |

---

## BR-006 Immaterial adjustments are recorded but not billed

| Field | Value |
| --- | --- |
| **Statement** | An audit whose premium difference is smaller than the materiality threshold is closed without a billable adjustment, because the cost of billing and collecting a trivial amount exceeds the amount itself. |
| **Trigger** | Evaluated at step 3 for every audit that produced a recalculated premium, after the difference to the estimated premium has been computed. |
| **Enforcement** | `AuditProcessor.PostAdjustment` compares the absolute adjustment amount to the `MaterialityThreshold` setting in `App.config` (default 5.00) and writes the `dbo.PremiumTransaction` row with a zero amount and a `NoChange` transaction type when the difference falls below it. |
| **Outcome** | The audit is marked `Posted` and appears on the File1 success report with a zero adjustment, so the run is auditable without generating a billing item. |
| **Source** | src/SAIF.PremiumAudit.Batch/AuditProcessor.vb:377 |

---

## BR-007 Rejected audits stay correctable

| Field | Value |
| --- | --- |
| **Statement** | An audit that fails validation must remain in `Pending` status with the failing rule identifier recorded against it, so that underwriting can correct the payroll detail and have the audit rated on a later run without manual re-entry. |
| **Trigger** | Evaluated whenever BR-001 or BR-002 records a blocking failure for an audit. |
| **Enforcement** | `AuditProcessor.RecordRejection` writes a `dbo.PolicyAuditReject` row carrying the `PolicyAuditId` and the failing `VAL-` rule identifier, then rolls back the per-audit transaction so no status change survives. |
| **Outcome** | The audit is picked up again by `dbo.usp_GetPendingAudits` on the next run, and the rejection appears on the File2 error report for the underwriting correction queue. |
| **Source** | src/SAIF.PremiumAudit.Batch/AuditProcessor.vb:412 |

---

## BR-008 A run stops rather than post against systemically bad data

| Field | Value |
| --- | --- |
| **Statement** | When per-audit failures exceed the configured tolerance the run must stop instead of continuing to post, because a failure rate that high indicates a bad upstream extract rather than isolated data entry errors. |
| **Trigger** | Evaluated after each per-audit failure is counted during the processing loop. |
| **Enforcement** | `AuditProcessor.RunAppLogic` compares the running failure count to the `ErrorThreshold` setting in `App.config` (default 25) and ends the loop when it is exceeded. |
| **Outcome** | Run totals and the last checkpoint `PolicyAuditId` are written to File0, the on-call notification is sent to `NotificationRecipients`, and the program exits with return code 5; audits already committed remain `Posted` and the remainder stay `Pending`. |
| **Source** | src/SAIF.PremiumAudit.Batch/AuditProcessor.vb:198 |

---

## Notes
- Rules BR-001 and BR-002 are enforced by `SAIF.Framework.Validation` and keep
  that library's `VAL-` identifiers; changing their thresholds is a change to
  `dbo.ClassCodeValidation` data, not to this application.
- `MaterialityThreshold` and `ErrorThreshold` are configuration values, so BR-006
  and BR-008 can be retuned per environment without a code change.

## Related documentation
- [Overview](overview.md)
- Operations and processes
- [Batch lifecycle](batch-lifecycle.md)
- [Data model](data-model.md)
- SAIF.Framework.Validation business rules

## Sources
- src/SAIF.PremiumAudit.Batch/Program.vb
- src/SAIF.PremiumAudit.Batch/AuditProcessor.vb
- src/SAIF.PremiumAudit.Batch/App.config
- src/SAIF.Framework.Validation/ClassCodeValidator.vb
