---
title: SAIF.Services operations and processes
summary: Business operations and processes implemented by this library, organized by functional area.
status: draft
owner: SAIF Doc Gen
last_reviewed: 2026-09-24
library: SAIF.Services
reference_type: package
---

# SAIF.Services operations and processes

This page catalogues the business operations implemented by SAIF.Services, organized by the same functional areas used for [Business rules](business-rules.md): Claims (bills, EBilling, EDI, ICD-10 and payments), Claims (authority, information, tasks, notes, RTW, security and investigations), Legal, Policies and certificates, and Collections/Agencies/Groups/Imaging/Utilities. Each H3 heading is a service class; bullets describe its key operations, the validators and rules they enforce, the entities/tables they touch through `GetRepository<T>()` or a query provider, and any delegation to SAIF.Domain or an external system (Guidewire ClaimCenter/PolicyCenter, OnBase/ImagingClient, PDF rendering, CleanAddress, Simple Certified Mail, Google Maps, email/fax). SAIF.Services is entirely NHibernate ORM based; there is no application-layer raw SQL, so this page does not cite `sql/*` documents directly (see [SQL catalog](sql/index.md) for the ORM-mapping catalog). Business-rule citations link to the corresponding heading in [Business rules](business-rules.md); where the source material referenced a rule number that has no matching heading in that catalogue (chiefly some higher-numbered `SVC-BR-MSC-*` and `SVC-BR-COL-*` sub-items rolled up into a single catalogued entry), the behavior is described in prose without a bracketed rule citation rather than linking to a non-existent anchor.

## Claims: bills, EBilling, EDI, ICD-10 codes, and payments

This area covers `Claims/Bills/**`, `Claims/EBilling/**`, `Edi/**`, `Claims/ICDCodeService/**`, `Claims/Payments/**` and `Utilities/Edi/**` (paths relative to `Services/SAIF.Services/`). It implements medical/legal bill entry, audit, approval, payment and regeneration; electronic-billing (837/835/999) file processing; imaging and document management for EBilling batches; EDI override and bill-report services; ICD-10 code maintenance; and Guidewire check/recovery payment creation. Rule citations in this area use the [`SVC-BR-BIL-*`](business-rules.md#bills) prefix.

### BillService

`Claims/Bills/BillService.cs` (`IBillService`).

- **CanBillBeNonChargeable(billId)** loads the SAIF.Domain `Bill`, checks that it is reviewed, the billing provider type is IME, and the audit is a chargeable cost ([SVC-BR-BIL-001](business-rules.md#svc-br-bil-001-can-be-non-chargeable-only-if-status-is-reviewed%2C-billing-provider-is-ime%2C-and-audit-ischargeablecost)); if the first WCD "Closed" status date is on or before the statement-from date it returns true ([SVC-BR-BIL-002](<business-rules.md#svc-br-bil-002-true-if-the-first-wcd-'closed'-(denial)-date-%E2%89%A4-statementfromdate>)); otherwise it checks conditions for a "Denied…" decision date on or before the statement-from date ([SVC-BR-BIL-003](business-rules.md#svc-br-bil-003-true-if-any-'denied'-condition-decisiondate-%E2%89%A4-statementfromdate)).
- **GetBillAmounts(claimId, paymentCategory, billTypes)** joins `CbpBill` and `CbpAuditedService` via the query provider and sums `AllowedAmount`, returning 0 for null ([SVC-BR-BIL-004](business-rules.md#svc-br-bil-004-amount-%3D-sum-of-allowedamount-over-audited-services-for-claim%2Fstatus-list%2Fpayment-category%3B-null-%E2%86%92-0)).
- **ApproveBill(req)** runs `ApproveBillRequestValidator` ([SVC-BR-BIL-010](business-rules.md#svc-br-bil-010-approve-only-when-status-%3D-'rev'---'error---incorrect-status-for-approval.')), begins a unit of work, calls Domain `Bill.Approve(DeterminePaymentIssueDate)` ([SVC-BR-BIL-060](<business-rules.md#svc-br-bil-060-issue-date-%3D-currentnextreviewdate-if-status-is-ext%2Fcom%2Fsus%2Fapp%2C-or-if-nextreviewdate-%E2%89%A5-ma-nextissuedate-and-the-last-status-user-contains-%22%40%22%3B-otherwise-getnextissuedate(today%2C-ma-nextissuedate)>)) reading `PaymentTransactionTypeCode` "MA", then commits.
- **DisallowAllServices(billId, reasonId) / AllowAllServices(billId)** call Domain `Bill.Audit.DisallowAllServices(reasonId, USER)` / `AllowAllServices(USER)` and commit; there is no validation on this path.
- **SendPendBill(req)** runs `SendPendRequestValidator` ([SVC-BR-BIL-020](business-rules.md#svc-br-bil-020-sendtomailid-notnull---'error---mail-id-is-required.')..[024](business-rules.md#svc-br-bil-024-recipient-must-have-confidential-access%3A-%22error%3A-send-pend-mailid-does-not-have-access-to-receive-confidential-bills%22)), then for each bill id calls Domain `Bill.SendPend(PendDate, MailId.ToUpper())` and commits.
- **SendToAudit(req)** runs `SendToAuditRequestValidator` ([SVC-BR-BIL-025](business-rules.md#svc-br-bil-025-notetext-notnull---'error---bill-note-is-required.')/[026](business-rules.md#svc-br-bil-026-notetext-%E2%89%A4600---'error---text-exceeds-600-characters.')), calls Domain `Bill.SendToAudit(NoteText)` per bill, and commits.
- **RegenerateBill(req)** runs `RegenerateRequestValidator` ([SVC-BR-BIL-027](business-rules.md#svc-br-bil-027-all-bills-status.iscompleted---'error---bill-must-be-in-completed-status-to-regenerate')..[029](business-rules.md#svc-br-bil-029-notetext-%E2%89%A4600---'error---text-exceeds-600-characters.')). Full-duplicate bills are skipped with a message ([SVC-BR-BIL-030](business-rules.md#svc-br-bil-030-full-duplicate-bills-are-skipped%3A-%22bill-%7B0%7D-was-not-regenerated-because-it-is-a-duplicate-of-bill-%7B1%7D%22)); otherwise it calls Domain `BillRegenerator.Regenerate(uow, bill, …, providers, claimApiService, gwClaimDataApiClient)`, commits, and adds a summary message ([SVC-BR-BIL-031](<business-rules.md#svc-br-bil-031-summary---'bill(s)-regenerated-with-the-exception-of--'-%2F-'bill(s)-successfully-regenerated.'>).
- **UpdatePaymentOptions(req)** runs `PaymentOptionsRequestValidator` ([SVC-BR-BIL-040](business-rules.md#svc-br-bil-040-issuedate-notempty---'error---issue-date-is-required!')..[043](business-rules.md#svc-br-bil-043-issuedate-must-be-a-working-day%3A-%22error%3A-issue-date-should-be-on-a-working-day.%22)), then Domain `Bill.ModifyPaymentOptions(req)` and commits.
- **GetPaymentOptions(billId, canUpdate)** sets `ChargeableCost`/`IssueDate` from `DeterminePaymentIssueDate` ([SVC-BR-BIL-060](<business-rules.md#svc-br-bil-060-issue-date-%3D-currentnextreviewdate-if-status-is-ext%2Fcom%2Fsus%2Fapp%2C-or-if-nextreviewdate-%E2%89%A5-ma-nextissuedate-and-the-last-status-user-contains-%22%40%22%3B-otherwise-getnextissuedate(today%2C-ma-nextissuedate)>)), and `CanUpdate` when the status is outside EXT/COM/SUS and `canUpdate` is true ([SVC-BR-BIL-061](business-rules.md#svc-br-bil-061-canupdate-%3D-status-not-in-ext%2Fcom%2Fsus-and-caller-flag)).
- **GetNextIssueDate(current, next)** uses `IWorkCalendarService.IsWorkingDay`/`NextWorkDay` ([SVC-BR-BIL-062](<business-rules.md#svc-br-bil-062-if-today-is-not-a-working-day%2C-or-today-%3D%3D-nextissuedate-%E2%86%92-nextworkday(nextissuedate)%3B-else-nextissuedate>).
- **GetBillHeader(billId)** reads claim detail from Guidewire ClaimCenter and throws "Claim does not exists in Claim Center" if missing ([SVC-BR-BIL-063](business-rules.md#svc-br-bil-063-header-requires-the-claim-in-claim-center%2C-else-throw-%22claim-does-not-exists-in-claim-center%22)).
- Read-only lookups: `GetBillStatusHistory`, `GetBillStatusCodes` (`CbpBillStatusCode`), `GetBillTypes` (`CbpBillTypeCode`), `GetDisallowReasonCodes`, `GetBillDiagnosis` (`CbpDiagnosis`), `GetBillProcedure`, `GetConditions`/`GetClaimRequestDecisions`/`GetClaimWcdStatusHistory` (`ClaimQueryProvider`), `GetAuditedService`, `GetOtherServices`, `GetBill` (Domain `Bill` mapped to contract), `GetPaymentSpecialHandling`.

### BillEntryService

`Claims/Bills/BillEntryService.cs` (`IBillEntryService`).

- **GetNextClaimBillFromQueue(hasConflictAccess)** calls `IClaimBillEntryRepository.GetNextClaimBillFromQueue`, looping while a duplicate `CbpBill` with the same `ImageId` already exists ([SVC-BR-BIL-070](business-rules.md#svc-br-bil-070-a-queue-task-whose-imageid-already-exists-on-a-cbpbill-is-a-duplicate%3B-fetch-the-next-task)) and handling return codes ([SVC-BR-BIL-071](<business-rules.md#svc-br-bil-071-return-code-0-%3D-ok%3B-1-%3D-empty-(%22no-bills-remain-in-the-queue.--break-time!!%22)%3B-2-%3D-throw-an-error-stating-the-maximum-number-of-autofeed-retrieval-attempts-was-met%3B-other-%3D-throw-%22there-was-an-unknown-problem-retrieving-a-bill-from-the-queue.%22>). If the queue is empty it warns "No bills remain in the queue. Break time!!". It fetches the image via `ImagingClient.GetImage(CLAIM, ImageId)` (OnBase); a missing image is logged and skipped in production, warned otherwise ([SVC-BR-BIL-072](business-rules.md#svc-br-bil-072-image-not-found%3A-in-production%2C-log-and-skip-to-the-next-task%3B-otherwise-warn-and-still-build-the-bill)), and other imaging errors restore the task to the queue with a warning ([SVC-BR-BIL-073](business-rules.md#svc-br-bil-073-other-imaging-errors%3A-restore-task-to-queue%3B-warn-%22problem-reading-image-from-onbase.--please-contact-support.%22)). It then builds a new "PEN"/Standard bill, filling `TowId`, `ImageId`, received date, claim number, `ClaimId` from `claimInformationService.GetClaimId`, and worker name/DOB ([SVC-BR-BIL-074](business-rules.md#svc-br-bil-074-new-queue-bill%3A-type-standard%2C-status-pen%2C-claim%23-from-image-else-task%3B-worker-autofill-failure-throws-%22unable-to-autofill-worker-information%22)), adds `CbpBill`, and commits.
- **GetClaimBillQueueRemainingCount** counts `Task` rows (`TaskUser="{CBPAPP}"`, not deleted).
- **GetBill(billId)** calls `billService.GetBill`, adding an error if not found and a warning if there is no image; gets the image URL via `ImagingClient.GetImage`/`GetImageByTowId` ([SVC-BR-BIL-075](business-rules.md#svc-br-bil-075-bill-not-found-%E2%86%92-error-%22unable-to-find-bill-with-id%22%3B-no-imageid%2Ftowid-%E2%86%92-warn-%22this-bill-does-not-have-an-associated-image.%22%3B-imageid-preferred-over-towid)).
- **GetBillNotes** excludes provider-contact notes ([SVC-BR-BIL-076](business-rules.md#svc-br-bil-076-provider-contact-notes-are-excluded-from-bill-notes)); `GetNoteCategories`/`GetNoteTypes` delegate to `NoteService`.
- **SaveBill(bill, saveOnSubmit)** validates with `BillValidator` ([SVC-BR-BIL-080](business-rules.md#svc-br-bil-080-billtotalamount-notnull---'error---bill-total-amount-is-required.')..[085](business-rules.md#svc-br-bil-085-cbpadmissiontypecode-must-be-in-the-acceptable-list---'error---cbp-admission-type-code-is-invalid.')). For a new bill it calls `CreateNewBill("PEN")`, `FillBillData`, then adds; `FillBillData` resolves the provider location via `ProviderQueryProvider.GetLocation` and the claim via `ClaimQueryProvider.GetClaim`, and handles bill-type changes ([SVC-BR-BIL-087](business-rules.md#svc-br-bil-087-a-bill-with-no-existing-row-is-created-with-status-pen%2C-entry-source-cbe)); an `UpdateNumber` mismatch raises a contention error ([SVC-BR-BIL-086](business-rules.md#svc-br-bil-086-optimistic-concurrency%3A-updatenumber-mismatch-%E2%86%92-%22bill-has-been-updated-by-someone-else.-please-re-open-the-bill-and-try-again.-last-updated-by%3A-%7Buser%7D.%22)); otherwise it updates and commits.
- **SubmitBill(bill)** runs submit checks ([SVC-BR-BIL-090](business-rules.md#svc-br-bil-090-submit-requires-a-claim-%23---'error---claim-number-is-required.')..[093](business-rules.md#svc-br-bil-093-received-date-must-be-%3E-today%E2%88%929y-and-%E2%89%A4-today---'error---invalid-received-date')) and calls `SaveBill(bill, true)`.
- **UpdateBillStatus(billId, recipientId, code)** looks up `CbpBill`/`CbpBillStatusCode`; an invalid code throws ([SVC-BR-BIL-094](<business-rules.md#svc-br-bil-094-status-code-must-exist%2C-else-throw-invaliddataexception-%22error%3A-new-bill-status-code-is-invalid%3A-%7Bcode%7D%22%3B-new-status-effective-today%2C-responsible-%3D-recipient.toupper()-or-current-user>)); otherwise adds a `CbpBillStatus` row and commits.
- **Lookups:** `GetBillTypeCodes`/`GetBillStatusCodes`, `GetInboxBillsByResponsiblePartyId`, `GetProviderLocations(tin)` via `ProviderQueryProvider.GetProviderByTaxId` ([SVC-BR-BIL-095](<business-rules.md#svc-br-bil-095-tin-required-(%22error%3A-no-tin-was-provided%22)%3B-provider-must-exist-(%22error%3A-no-provider-found-with-tin%22)>), `GetRenderingProviders(locId)` via `GetLocation`/`GetWorkingProviderRelationshipsByParentLocation` ([SVC-BR-BIL-096](business-rules.md#svc-br-bil-096-location-must-exist---'error---no-provider-location-found-with-location-id')), `GetNewBill`.

### BillDiagnosisService

`Claims/Bills/BillDiagnosisService.cs`.

- **CreateDiagnosis/CreateDiagnoses(billId, list)** loads the bill via `BillQueryProvider.GetBill`, cleans the codes (trim/uppercase), validates with `BillDiagnosisValidator` ([SVC-BR-BIL-100](<business-rules.md#svc-br-bil-100-diagnosiscode-notnull%2Fnotempty-(diagnosiscoderequiredmessage)>)/[101](<business-rules.md#svc-br-bil-101-diagnosistype-and-its-code-required-(diagnosistyperequiredmessage)>)/[102](<business-rules.md#svc-br-bil-102-diagnosistype.code-must-be-a-valid-code-(diagnosistypeinvalidmessage)>), requires a pointer on Standard bills ([SVC-BR-BIL-103](business-rules.md#svc-br-bil-103-standard-bills-require-a-pointer---'error---diagnosis-pointer-is-required-on-standard-bills')), checks for duplicates ([SVC-BR-BIL-104](business-rules.md#svc-br-bil-104-standard%3A-duplicate-code-%E2%86%92-%22error%3A-unable-to-save-diagnosis%3A-%7Bcode%7D.-please-remove-duplicate-diagnosis-codes.%22)/[105](business-rules.md#svc-br-bil-105-inpatient%2Foutpatient%3A-duplicate-code%2Btype-%E2%86%92-throw-an-error-citing-duplicate-diagnosis-code%2Ftype-combos%3B-unknown-type-%E2%86%92-throw-%22error%3A-unknown-bill-type-when-checking-for-duplicate-diagnoses.%22)), resolves `CbpDiagnosisTypeCode`, adds the diagnosis, commits, re-reads and sets `IsValidCode`/description via `DiagnosisService.GetDiagnosis` ([SVC-BR-BIL-106](<business-rules.md#svc-br-bil-106-codes-trimmed-and-uppercased%3B-isvalidcode-%3D-code-found-(including-expired)>).
- **UpdateDiagnosis/UpdateDiagnoses** follow the same flow against existing rows.
- **DeleteDiagnosis/DeleteDiagnoses** remove entries from `bill.Diagnoses` with no additional checks.
- **GetDiagnoses**, **GetDiagnosisTypeCodes** (`CbpDiagnosisTypeCode`) are plain reads.

### BillProcedureService

`Claims/Bills/BillProcedureService.cs`.

- **CreateProcedures/UpdateProcedures** load the bill via `BillQueryProvider.GetBill`, validate with `BillProcedureValidator` ([SVC-BR-BIL-110](<business-rules.md#svc-br-bil-110-procedurecode-required-(procedurecoderequiredmessage)>)..[114](<business-rules.md#svc-br-bil-114-proceduredate-%E2%89%A4-today-(proceduredateinvalidmessage)>), resolve `CbpPrimaryProcedureCode`, add or modify the line on the bill, and commit.
- **DeleteProcedures**, **GetProcedures**, **GetPrimaryProcedureCodes** are straightforward reads/deletes.

### BillServiceService

`Claims/Bills/BillServiceService.cs`.

- **CreateService(billId, svc)** loads the bill, validates with `BillServiceValidator` ([SVC-BR-BIL-120](business-rules.md#svc-br-bil-120-providerservicecode-notnull%2C-length-1-11---'error---invalid-primary-service-code%2C-value-is-too-long.')..[133](business-rules.md#svc-br-bil-133-prescribingproviderid-%E2%89%A410---'error---invalid-prescriber-id%2C-can-only-be-up-to-10-characters.')), resolves `PharmacyDispenseAsWrittenCode` and `ProviderLocation`, adds the line, and commits.
- **UpdateService** follows the same flow; **DeleteService** removes the line from the bill.
- **GetServices** reads `CbpBillService`; **GetChargedServicesTotal** sums `ChargeAmount` ([SVC-BR-BIL-136](business-rules.md#svc-br-bil-136-charged-total-%3D-sum-of-chargeamount-on-the-bill's-lines)).
- **GetServiceTypeCode(code)** uses `BillQueryProvider.GetCbpServiceCodeNotExpired`, falling back to `GetNationalDrug` ([SVC-BR-BIL-137](<business-rules.md#svc-br-bil-137-service-type-from-a-non-expired-cbp-service-code-(uppercased)%2C-else-national-drug-code>); `GetPrescribingPrescriberCodes` is a plain lookup.

### AuthorizationService

`Claims/Bills/AuthorizationService.cs`.

- **GetAuthorizations(claimId)** reads `CbpAuthorization`. **ValidCptCode(code)** matches a single `CbpServiceCode` where `Code==code && ServiceType=="M"` ([SVC-BR-BIL-140](business-rules.md#svc-br-bil-140-valid-cpt-%3D-a-cbpservicecode-with-servicetype-'m')).
- Surgery dates: `GetSurgeryDates`; `AddSurgeryDate` checks for a duplicate before adding `CbpAuthorizationSurgery` ([SVC-BR-BIL-141](business-rules.md#svc-br-bil-141-no-duplicate-surgery-date-per-claim)); `DeleteSurgeryDate` deletes.
- **CreateAuthorization** resolves the type code, PM group and `ProviderLocation`, validates with `AuthorizationItemValidator` ([SVC-BR-BIL-142](business-rules.md#svc-br-bil-142-claimid-notnull---'error---claim-is-required.')/[143](business-rules.md#svc-br-bil-143-billingprovidername-notnull---'error---billing-provider-is-required.')), then adds `CbpAuthorization`. **UpdateAuthorization** updates the existing row; **DeleteAuthorization** deletes it.
- Lookups: `GetAuthorizationTypeCodes`, `GetServiceTypeCodes`, `GetTimelyTypeCodes`.

### NoteService (bills)

`Claims/Bills/NoteService.cs`.

- Reads: `GetNote`, `GetClaimNotes`, and note type/category lookups via `GetAuditNote`/`GetAuditNotesForClaim`/`GetNoteTypes[ForCategory]`; `GetBillNotes` queries `CbpNote`; `IsBillNoteByOtherUser`.
- **CreateNote/UpdateNote(Note)** validate with `BillNoteValidator` ([SVC-BR-BIL-150](business-rules.md#svc-br-bil-150-notetext-required---'error---bill-note-is-required.')/[151](business-rules.md#svc-br-bil-151-notetext-%E2%89%A4600---'error---text-exceeds-600-characters.')) then add or update the note on the bill.
- **CreateAuditNote/EditAuditNote(NoteModifyRequest)** validate with `NoteModifyRequestValidator` ([SVC-BR-BIL-152](business-rules.md#svc-br-bil-152-'error---text-is-required.')..[159](business-rules.md#svc-br-bil-159-concurrency---'error---note-has-been-updated-by-another-user.')) then add or modify.
- **DeleteNote/DeleteAuditNote** validate with `NoteDeleteRequestValidator` ([SVC-BR-BIL-160](business-rules.md#svc-br-bil-160-id-required-and-must-exist---'error---invalid-bill-selected.')).
- **InactivateNotes(ids)** calls `GetAuditNote` for each id and inactivates it.

### Small lookup services

- **DiagnosisService.GetDiagnosis(code, includeExpired)** reads `CbpDiagnosis`.
- **ProcedureService.GetProcedure** reads `CbpProcedureCode`.
- **ServiceCodeService.GetServiceCode** calls `IServiceCodeMaintenanceQueryProvider.GetServiceCode`; `Update`/`Insert` throw `NotImplementedException` ([SVC-BR-BIL-165](business-rules.md#svc-br-bil-165-service-code-insert%2Fupdate-not-supported)).

### BillQueryService

`Claims/Bills/BillQueryService.cs`.

- **SearchByCriteria** queries `CbpBill` with filters; if `CheckNumber` is set it also calls Guidewire `IFinancialDataApiClient.GetBillDetailsForCheckNumber`, then enriches with `IClaimDataApiClient.GetClaims(claimIds)` and `GetPaymentsByInvoiceNumber`.
- **GetBillEntryBillsByCriteria** queries `CbpBill`. **SearchByResponsiblePartyId(id, displayAll)** queries `CbpBill` plus Guidewire `GetClaims`.
- **GetExplanationOfBenefits(billId)** calls `documentService.GenerateDocument(config)`, returning a `MemoryStream` (PDF rendering).

### EbillingService

`Claims/EBilling/EBillingService.cs` (`IEbillingService`). Processes 837/835/999 EDI files and drives EBilling batch state.

- **Process837File(file, batchId)**: resolves the clearinghouse via GS sender-id tax id, retrieves the "837"/"ACK" batch ([SVC-BR-BIL-200](business-rules.md#svc-br-bil-200-batch-retrieved-by-type%2Fstatus%3A-837%2Fack-for-extract%2C-837%2Fextr-for-index%2C-837%2Findex-for-cbp-generation%2C-837%2Fimage-for-assign%2C-835%2Feob-for-ack)), calls Domain `File837Extractor.ProcessFile`, marks the batch extracted, and commits on success; failure rolls back and throws ([SVC-BR-BIL-201](business-rules.md#svc-br-bil-201-837-extraction-success-%E2%86%92-batch-extracted-and-commit%3B-failure-%E2%86%92-rollback-and-throw-%22error-in-837-extraction---review-the-log-file%22)).
- **ProcessEOBAcknowledgementFile(999)**: retrieves the "835"/"EOB" batch, parses AK9 (throws if missing, [SVC-BR-BIL-202](business-rules.md#svc-br-bil-202-999-must-contain-ak9%2C-else-throw-'ak9-segment-not-found')), creates a new "835AK" batch, and either runs `ProcessEntireEobBatch` (AK9 R/A) or per-pair `ProcessEobStatus` ([SVC-BR-BIL-203](business-rules.md#svc-br-bil-203-ak9-r-or-a-applies-to-the-whole-batch%3A-r-%E2%86%92-ebills-eob-rejected%2C-a-%E2%86%92-completed%3B-counts-success%2Ffailure)/[204](<business-rules.md#svc-br-bil-204-otherwise-per-ik5%3A-r-%E2%86%92-eob-rejected-plus-reject-log%3B-else-completed-(updateuser-%22ebn0009%22)%3B-both-835-and-835ak-batches-%E2%86%92-completed>), then completes both batches, links the response batch, and commits.
- **Create835EobFile / Create277ResponseFile / Create837AckFile** build ISA/GS/ST/AK1/AK2/AK9/SE/GE/IEA segments via `EdiBuildSegments`; a null outfile throws `ArgumentNullException`.
- **CreateCbpBillsForBatch**: retrieves the "837"/"INDEX" batch, and for each indexed bill calls Domain `GenerateCbpBills.Generate(uow, ebill, user, …, serviceCodeManager, claimApiService)`, committing per bill, then sets the batch to Setup ([SVC-BR-BIL-205](business-rules.md#svc-br-bil-205-after-cbp-bills-are-generated%2C-batch-%E2%86%92-setup)).
- **IndexBatch**: retrieves the "837"/"EXTR" batch, then runs Domain `ProviderIndexerV2.IndexProvidersToLocation`, `PatientIndexer.IndexPatientsToClaim`, and `BillServiceIndexer.IndexBillServiceProvidersToLocation` (each in its own transaction), then sets the batch to Indexed ([SVC-BR-BIL-206](business-rules.md#svc-br-bil-206-after-provider%2Fpatient%2Fbill-service-indexing%2C-batch-%E2%86%92-indexed)).
- **AssignBills**: retrieves the "837"/"IMAGE" batch, calls Domain `BillAssignment.AssignBillsResponsibily(...)`, then sets the batch to Completed; failures are wrapped as "Batch Id: {id}" ([SVC-BR-BIL-207](business-rules.md#svc-br-bil-207-after-assignment%2C-batch-%E2%86%92-completed%3B-errors-wrapped-'batch-id---%7Bid%7D')).
- **GetBatchImagingResults / AssociateEbillImages / CreateAffiliateImages**: `DocumentQueries` plus `ImagingClient.GetImages(CLAIM, ImageListQuery)` (OnBase); matched images set the ebill status to Imaged ([SVC-BR-BIL-208](business-rules.md#svc-br-bil-208-matched-images-%E2%86%92-ebill-status-imaged%3B-parent%2Fchild-images-cross-referenced)) and are cross-referenced with `ImagingClient.SaveCrossReference`.
- **ExtractEobs(clearinghouseCode, rowLimit)** calls Domain `EobBuilder.GenerateEobs` for batch type "835"; wraps errors as "Unable to generate EBill Explination of Benefits".
- **ExtractEobForReject(eobId)** loads the eob (throws "Invalid Eob" if missing, [SVC-BR-BIL-209](business-rules.md#svc-br-bil-209-eob-must-exist---'invalid-eob---%7Bid%7D')), then calls Domain `EobBuilder.GenerateEob` using `GetLastBatch("835")`.
- **File checks:** `Read837TransactionSet`/`Read999TransactionSet` (EdiManager), `AreBillsAlreadyProcessed`, `IsClearinghouseRepetitionSeparatorAndElementDelimiterValid`, `IsValidFileStructure`, `IsFileAlreadyProcessed`, `IsValidSenderAndReceiver`, `IsValidFileBatchType`, `IsValidFileBatchNumber` ([SVC-BR-BIL-210](<business-rules.md#svc-br-bil-210-clearinghouse%2Ffile-must-not-be-null-(argumentnullexception)%3B-sender-resolved-via-gs-senderid-tax-id%3B-bills-already-processed-if-the-clearinghouse-control-number-exists>).
- **ProcessInvoiceFile(csv, month, total)**: builds an `EbillInvoice`; a prior in-balance invoice for the month throws `ArgumentException`, otherwise the old invoice/errors/bills are deleted and rerun ([SVC-BR-BIL-220](<business-rules.md#svc-br-bil-220-an-invoice-month-already-processed-and-in-balance-%E2%86%92-duplicate-(throw-argumentexception)%3B-otherwise-old-errors%2C-bills-and-invoice-are-deleted-for-a-rerun>). Runs per-line DCN checks ([SVC-BR-BIL-221](business-rules.md#svc-br-bil-221-dcn-found-%3E1-time-%E2%86%92-error-%22bill-was-found-on-ebill-table-%7Bn%7D-times%22%3B-0-%E2%86%92-%22bill-was-not-found-on-ebill-table%22)..[224](business-rules.md#svc-br-bil-224-data-starts-after-the-header-line%3B-stops-at-an-empty-submitter)), flags month ebills missing from the invoice ([SVC-BR-BIL-225](business-rules.md#svc-br-bil-225-month-ebill-missing-from-the-invoice-%E2%86%92-'bill-not-included-on-invoice')), and checks the balance ([SVC-BR-BIL-226](<business-rules.md#svc-br-bil-226-calculated-%3D-billcount-%C3%97-clearinghouse.transactionfeeamount%3B-%E2%89%A0-total-%E2%86%92-%22specified-invoice-amt-(%7B0%7D)-doesn't-balance-w%2F-calculated-amt-(%7B1%7D)%22>)/[227](business-rules.md#svc-br-bil-227-zero-errors-%E2%86%92-add-marker-'invoice-is-in-balance'%3B-invoice-saved)), then adds/deletes rows and commits.
- **Other:** `CleanEbillingAddress`, `IsAddressOverridden`, `WriteEdiFile`, `RetrieveBatch` (Domain `Batch`).

### EBilling imaging services

`Claims/EBilling/Imaging/`: supports document capture and status transitions for EBilling batches, delegating heavily to OnBase (`ImagingClient`) and PDF merge (`IPdfClient`).

- **EbillingImageService.ImageDocuments** delegates to `DocumentImageManager` and wraps errors as "Batch Id".
- **DocumentImageManager** (`DocumentManager.cs`): `ImageDocuments`, `UpdateBatchStatus`, `UpdateBillStatus`, `UpdateDocumentImageSequenceNumber`, `SendNewClaimEmail`, `CreateNewDocumentNote`; `ThrowExceptionIfZero` enforces [SVC-BR-BIL-230](business-rules.md#svc-br-bil-230-image-api-returning-0-%E2%86%92-throw-'image-api-is-returning-zero-').
- **DocumentManagementService**: `GetBillImageRequest` (non-imaged docs), `CreateNewDocumentNote` ("Claim not found - 827 sent to Fast Track."), `UpdateBillStatus` to DocumentLoad ([SVC-BR-BIL-231](business-rules.md#svc-br-bil-231-after-imaging%2C-bill-status-%E2%86%92-documentload)), `UpdateBatchStatusToDocumentLoad`, `UpdateDocumentImageId`, `FormatNewClaimEmail`, `GetNotYetImagedBills`.
- **DocumentImager**: `ImageDocument(s)` reads via `DocumentQueries.GetDocument`, merges via `IPdfClient.Merge`, uploads via `ImagingClient.Upload(CLAIM, …)`.
- **DocumentFileService**: `GetFile`, `MoveFile`, `ValidateDocuments`, `ValidateSource/DestinationDirectory`, `GetImageDocumentFileNames` ([SVC-BR-BIL-232](business-rules.md#svc-br-bil-232-file-must-exist---'file-not-found-for-document-control-number-%7B0%7D')..[235](business-rules.md#svc-br-bil-235-at-least-one-document---'no-documents-to-image-were-found-in-directory')).
- **ConfigurationService**: `AlertLegal` (`ImageQueries.AlertLegal`), `GetImageClaimAlertSecurity`, `GetImageConfiguration` (service date range from `ServiceQueries`), `GetImageTaskConfiguration`.
- **DocumentOverrideService.CheckForOverride**: `GetDocumentOverride(docType, providerType)`.

### Edi

`Edi/`: bill-level EDI overrides and 999-report validation.

- **BillOverrideService**: `Get(billId)`/`Get()` are queries; `Edit`/`Add` stamp the transaction and map a `MinValue` `StatementThruDate` to null ([SVC-BR-BIL-240](business-rules.md#svc-br-bil-240-statementthrudate-minvalue-%E2%86%92-null%3B-transaction-stamp-applied%3B-rollback-on-error)), then update or add `BillOverride`; `Delete` removes it. All roll back on error.
- **AuditedServiceOverrideService**: `Get(id, seq)`/`Get()`, `Edit(billId, seq)`, `Add` (`AuditedServiceOverride`), `Delete`: same pattern.
- **MedicalEdiBillService.IsValidEdiReport(batch#)**: `IEdiQueryProvider.GetBillReport`.
- **MedicalEdiFileService**: `Read999TransactionSet`, `IsFileStructureValid`, `IsFileBatchTypeValid`, `GetBatchNumberFrom999File`, `GetIk5SegmentResponseCode`, `IsSegmentValid`/`HasFileBeenAccepted` ([SVC-BR-BIL-241](business-rules.md#svc-br-bil-241-999-accepted-based-on-the-ik5-response-code)), `GetIk3Segments`.

### Icd10CodeService

`Claims/ICDCodeService/Icd10CodeService.cs`.

- **ProcessICDFile** processes each entry with `ProcessIcdEntry`, which routes by classification ([SVC-BR-BIL-250](<business-rules.md#svc-br-bil-250-icd-entry---pcs-%E2%86%92-procedure-path%3B-cm-%E2%86%92-diagnosis-path%3B-otherwise-not-processed-(false)>).
- **ProcessProcedure** looks up `ClaimQueryProvider.BillQueries.GetCbpProcedureCode`, inserting if missing (and not deleted) or updating otherwise ([SVC-BR-BIL-251](<business-rules.md#svc-br-bil-251-missing-code-that-is-not-deleted-%E2%86%92-insert%3B-existing-code-%E2%86%92-update-(procedure-%2F-z-service-code-%2F-service-description-%2F-diagnosis)>); the non-PCS branch mirrors this with `CbpServiceCodeStateless` ("Z") and `CbpServiceDescriptionStateless`.
- **ProcessDiagnosis** looks up `GetCbpDiagnosis`, then adds or updates `CbpDiagnosis` ([SVC-BR-BIL-251](<business-rules.md#svc-br-bil-251-missing-code-that-is-not-deleted-%E2%86%92-insert%3B-existing-code-%E2%86%92-update-(procedure-%2F-z-service-code-%2F-service-description-%2F-diagnosis)>).
- **Helpers:** `GetTrimmedDescription`, `GetValidMedicareReportFlag`, `WriteControlReport`.

### FinancialTransactionService

`Claims/Payments/FinancialTransactionService.cs`: creates Guidewire check and recovery transactions for legal bills and subpoenas.

- **CreateLegalCheckTransaction(legalBill, issueDate, approverId)**: null-checks the bill ([SVC-BR-BIL-260](business-rules.md#svc-br-bil-260-legal-bill-required%3A-%22a-valid-legal-bill-was-not-used-in-the-call-to-create-legal-bill-financial-transaction%22)), builds `CheckFinancialData` (send date today, issue date, claim id, provider location), requires a resolvable provider address ([SVC-BR-BIL-261](business-rules.md#svc-br-bil-261-valid-provider-address-required%3A-%22error%3A-no-valid-address-available-for-bill-with-provider-location-id-%3D-%7Bid%7D%22)), maps the delivery method ([SVC-BR-BIL-262](<business-rules.md#svc-br-bil-262-special-handling-maps-to-a-gw-delivery-method-(certified%2C-overnight%2C-foreign%2C-donotmail%2C-registered)%2C-default-send>), adds one Final/ExpenseDC/Legal payment line per service with `PaidAmount` and `SpendCategory`, sets the service date range from min/max `ServiceFromDate`, and calls Guidewire `IFinancialDataApiClient.CreateCheckAsync(claimId, data)`.
- **CreateSubpoenaCheckTransactions**: null-checks the subpoena ([SVC-BR-BIL-263](business-rules.md#svc-br-bil-263-subpoena-required%3A-%22a-valid-subpoena-was-not-used-in-the-call-to-create-a-subpoena-financial-transaction%22)); returns an empty list if any recipient provider address is missing ([SVC-BR-BIL-264](business-rules.md#svc-br-bil-264-all-recipient-provider-addresses-must-be-found%2C-else-no-checks-are-made)); for each recipient with `PaidAmount > 0` and not yet paid, creates one non-chargeable Legal check via `CreateCheckAsync` and marks `IsRecipientPaid` ([SVC-BR-BIL-265](<business-rules.md#svc-br-bil-265-only-recipients-with-paidamount-%3E-0-and-not-yet-paid-get-a-check%3B-they-are-then-marked-paid-(updateuser-%3D-approver)>)).
- **CreateRecoveryAsync**: delegates to Guidewire `IFinancialDataApiClient.CreateRecoveryAsync`.

### EdiFileService

`Utilities/Edi/EdiFileService.cs` (not behind an interface). **LoadFile(file, desc)** adds a `DataModel.EdiFile` row; **DeleteFile(id)** deletes it.

## Claims: authority, information, Medicare, severity model, tasks, notes, RTW, security override, pharmacy eligibility, reports, security, and investigations

This area covers `Claims/Authority/**`, `Claims/Information/**`, `Claims/Medicare/**`, `Claims/SeverityModel/**`, `Claims/Tasks/**`, `Claims/Notes/**`, `Claims/RTW/**`, `Claims/SecurityOverride/**`, `Claims/PharmacyElilgbility/**`, `Claims/Reports/**`, `Security/**` and `Investigations/**`. It implements claim payment/reserve authority limits, the claim information API consumed by claim-summary UI (the "claim node builder"), Medicare beneficiary batch reconciliation, Guidewire severity-model activity creation, claim/legal task and note management (including masking rules for conflict/secure claims), return-to-work (RTW) load-code access, security overrides, the pharmacy eligibility extract, broken-provider-relationship reporting, and claim security/fraud lookups. Rule citations use the [`SVC-BR-CLM-*`](business-rules.md#claims) prefix and already appear fully qualified in the source material.

### ClaimAuthorityService

`Claims/Authority/ClaimAuthorityService.cs` (`IClaimAuthorityService`). The constructor loads the current SAIF user into a static field and calls `AuthorityMappingHelper.MapTables()`.

- **IsValidPaymentAuthorityEdits / IsValidReserveAuthorityEdits** set the business process to Payments or Reserve, then call `ValidateAuthorityEdit`: in production it forces `IsOverrideAuthority = false` ([SVC-BR-CLM-010](business-rules.md#svc-br-clm-010-override-authority-is-disabled-in-production)), sets `AuthorizingEmployeePaymentAuthority` from the current user's authority, and validates with `ClaimAuthorityValidator` ([SVC-BR-CLM-001](<business-rules.md#svc-br-clm-001-at-least-one-limit-(amount-%E2%89%A5-0%2C-or-an-expiration-date-that-is-set)-is-required>)..[009](<business-rules.md#svc-br-clm-009-value-checks-only-run-if-the-authorization-checks-pass-(or-override-is-set)>).

- **AddEditAuthorityLimit(edit, historyTimestamp, ApprovalTypes)** maps the approval type to a business process ([SVC-BR-CLM-011](business-rules.md#svc-br-clm-011-approve%2Finitiate-%E2%86%92-payments-process%3B-otherwise-reserve)), validates, then reads `EmployeeAuthorityLimit` matching employee/function/purpose/process. A missing row goes through `AddAuthorityLimit`; an existing row goes through `UpdateAuthorityLimit`, which makes no change when the amount and expiration are unchanged ([SVC-BR-CLM-012](business-rules.md#svc-br-clm-012-an-unchanged-limit-is-not-updated)), reports a concurrency error on `UpdateNumber` mismatch ([SVC-BR-CLM-013](business-rules.md#svc-br-clm-013-optimistic-concurrency-on-the-authority-limit)), and writes an `EmployeeAuthorityPaymentLimitHistory` row on every change ([SVC-BR-CLM-014](business-rules.md#svc-br-clm-014-every-add%2C-update%2C-delete-or-revert-writes-a-history-row)).
- **AddAuthorityLimit** validates again, resolves `AuthorityBusinessProcessCode`/`AuthorityPaymentTypeCode` via `.Single` (throws if no match), stores a `MinValue` expiration as null, and adds the limit plus a history row; it does not begin/commit its own transaction.
- **DeleteOrRevertExpiredAuthorities(ids)**: for each id, reverts to the most recent earlier non-expiring history entry with an amount if one exists, otherwise deletes the limit and writes a zeroed history row ([SVC-BR-CLM-015](business-rules.md#svc-br-clm-015-an-expired-limit-reverts-to-the-latest-earlier-non-expiring-history-entry-that-has-an-amount%3B-otherwise-it-is-deleted)); `UpdateUser` falls back to "CMN0011" when empty ([SVC-BR-CLM-016](business-rules.md#svc-br-clm-016-default-audit-user-'cmn0011'-when-there-is-no-user-id)). **DeleteExpiredAuthorities** always deletes and writes history.
- **GetExpiredAuthorities(runDate)** reads limits expired before the run date. **GetTerminatedAuthorities** joins terminated (`EmployeeStatusCode != "A"`) employees to their limits ([SVC-BR-CLM-017](business-rules.md#svc-br-clm-017-'terminated'-means-employeestatuscode-%E2%89%A0-'a')).
- **GetClaimAuthority(userId, messages)**: in production, an inactive user or one with no employee id gets an empty authority and an "Unknown User" error ([SVC-BR-CLM-018](business-rules.md#svc-br-clm-018-in-production%2C-inactive-users-or-users-with-no-employee-id-get-no-authority)); otherwise loads Payments/Reserve limits and sets `CanUpdate`.
- **CanUserUpdate**: authorized for "ClaimAuthorityUpdate" or "ClaimAuthorityEAIPUpdate" ([SVC-BR-CLM-019](business-rules.md#svc-br-clm-019-updating-authority-requires-claimauthorityupdate-or-claimauthorityeaipupdate)).
- **Lookups:** `GetFunctionTypeList`, `GetApprovalTypeList`, `GetClaimAuthorityLimitHistory`.

### ClaimInformationService

`Claims/Information/ClaimInformationService.cs` (`IClaimInformationService`). Almost every method builds a SAIF.Domain `Claim` and reads one property from it.

- **IsValidClaim, GetClaimId, GetPolicyNumberForClaim, GetSaifClaimNumber, GetClaimNumber, GetInjuryDate** use `Claim.HasData`/`Claim.Data`, returning a default when there is no data.
- **GetClaimSecurity(number|id)** delegates to Domain `Claim.GetSecurityAttributes()`.
- **HasSecurityOverride(claimId, userId)** resolves the employee by `UserId.ToUpper()` (false if not found) and delegates to `ClaimSecurityOverrideProvider.HasClaimSecurityOverride` ([SVC-BR-CLM-040](business-rules.md#svc-br-clm-040-a-security-override-is-matched-by-employee-id)).
- Pass-through Domain `Claim` properties: `GetWorkStatus/Info`, `GetWorkReleaseStatus/Info`, `GetLegalRepresentative`, `GetClaimSupervisor`, `GetRecoverableBalance`, `GetClaimCurrentReserves`* (Guidewire-backed), `GetActiveAttendingPhysician`, `GetOdInjuryDate`, `GetFirstClosureDate`, `GetAggravationDates`, `GetPrimaryBodyPart/Side`, `GetKnowledgeDate`.
- **GetClaimDetail(id|number)** maps Claim to `ClaimDetail`, reads `MyClaimLastLogin` via `_profileService.GetInjuredWorkerUserByCustomerId`, swallows a specific NHibernate "no row" message as a test-environment workaround, and adds `GetGwReserves()` plus cost category codes.
- **GetClaimType / GetClaimStatus** use `Claim.DetailContract` plus `GetLatestClaimTypeActivity`/`GetLatestClaimStatus`, returning empty when there is no activity.
- **GetWorker(claimId)** builds a Domain `Worker` and masks the SSN as `***-**-` plus the last four characters, returning empty if the tax id is shorter than 11 characters ([SVC-BR-CLM-041](business-rules.md#svc-br-clm-041-ssn-is-masked-to-the-last-4-digits)).
- **GetClaimsMissingTaxId** reads `IClaimQueryProvider.GetClaimsMissingTaxId`.
- **GetPharmacyEligibilityReport, GetConditions, GetThirdPartyStatus, GetVocStatus** are query-provider reads.
- **GetLastPayment** sums TD, VocTD and SupTD detail amounts ([SVC-BR-CLM-042](business-rules.md#svc-br-clm-042-last-payment-%3D-sum-of-td%2C-voctd-and-suptd-details)).
- Contact methods delegate to `IClaimContactService`; **GetAdjusterLocation** reads the adjuster's location via `GetSaifUser`.

### ClaimContactService

`Claims/Information/ClaimContactService.cs`.

- **GetResponsibleAdjuster** returns empty if both `MailId` and `PseudoUser` are blank; otherwise looks up `MailId`, or `PseudoUser`; a pseudo user falls back to `PseudoPhone`/`PseudoName` ([SVC-BR-CLM-043](business-rules.md#svc-br-clm-043-pseudo-adjuster-falls-back-to-pseudophone-%2F-pseudoname)).
- **GetClaimsAssistant** uses `ClaimAssistantMailId`.
- **GetSupervisor**: for a secure claim (`HostileWorker` set), returns the adjuster's manager ([SVC-BR-CLM-044](business-rules.md#svc-br-clm-044-on-a-secure-claim%2C-the-supervisor-is-the-adjuster's-manager)); otherwise returns the responsibility's supervisor, unless it equals the adjuster ([SVC-BR-CLM-045](business-rules.md#svc-br-clm-045-the-supervisor-is-not-shown-if-the-same-person-as-the-adjuster)).
- **GetVocationAdjuster, GetThirdPartyAdjuster, GetRtwAdjuster, GetPolicySmcContact, GetUserContactInfo**: additional contact-role lookups.

### ClaimInformationApiService

`Claims/Information/Api/ClaimInformationApiService.cs` (`IClaimInformationApiService`). Powers the claim-summary "node" API consumed by claim UI.

- The static constructor builds AutoMapper claim/code-table/worker mappings; `Builder(claimId)` caches a `ClaimApiNodeBuilder` that creates a Domain `Claim` and a new `LegalService`.
- Each **BuildXxxNode(claimId)** returns an empty node when the claim is invalid; otherwise it chains builder steps for Claim (values, status, compensability, type, body part, region, work release/status, third party, voc, pharmacy eligibility, last payment, first closure, aggravation, MyClaim, attributes), Contacts (adjuster, supervisor, assistant, RTW, third party, voc, SMC), Policy, Legal, MCO, Reserves, Worker, WorkerRep, ProviderLocations, AttendingPhysician, Conditions.
- **BuildClaimAlertsNode** adds contact alerts for adjuster/assistant/voc/EAIP adjuster and a "{LEGAL}" alert when `LegalQueries.IsImageAlertNeeded` is true.
- **ClaimNodeBuilder.AddClaimAttributes** adds attribute labels (Fraud, secured worker, preferred worker, support order, worker conflict, multiple jobs, overpayment, CDA date, DCS) ([SVC-BR-CLM-046](<business-rules.md#svc-br-clm-046-claim-attribute-labels-(fraud%2C-secured-worker%2C-conflict%2C-overpayment%2C-cda-%5Bdate%5D%2C-dcs%2C-and-others)>).
- **ContactsNodeBuilder.AddAdjusterSupervisor** repeats the secure-claim manager rule ([SVC-BR-CLM-044](business-rules.md#svc-br-clm-044-on-a-secure-claim%2C-the-supervisor-is-the-adjuster's-manager)).
- **LegalNodeBuilder.AddLegalNode** takes non-Closed cases, latest MED/FMED mediation schedule, "None"/"Unassigned" numbering, and hearing fields from a "HEAR" schedule ([SVC-BR-CLM-047](business-rules.md#svc-br-clm-047-legal-node-excludes-closed-cases%3B-'none'-%2F-'unassigned'-numbering)).

### ClaimCustomRepoService

`Claims/Information/ClaimCustomRepoService.cs`. **GetClaimForOnBaseAsync(claimNumber)** calls Guidewire `ICCClaimApiService.GetClaimForOnBase`; on success it maps Y/N flags to bool and sets `HasLegalInformation` via `ILegalService.LegalCaseExistsAndHasImageAlertsFlag`. A 404 falls back to `IClaimDataApiClient.GetNonConvertedClaim`, mapping `IsNonConvertedClaim = true` and `IsConflictClaim`/`IsSecureClaim` from `IsRestricted`/`IsSecure` ([SVC-BR-CLM-048](business-rules.md#svc-br-clm-048-a-guidewire-404-falls-back-to-the-non-converted-claim)); other statuses pass Guidewire's status/error through.

### MissingTimeLossService

`Claims/Information/MissingTimeLossService.cs`. **SendMissingTimeLossReport(ILogWriter)** reads missing-timeloss payments, flags any with no "Zero Prorate Letter" imaged today via `IImagingClient.Exists` ([SVC-BR-CLM-050](business-rules.md#svc-br-clm-050-missing-timeloss-payment-%3D-no-'zero-prorate-letter'-imaged-today)), and emails a summary (subject "CMM0120.MISSING.TIMELOSS.REPORT has FOUND ROWS - Missing TL Payment Issue", from config `Noreplyemailsenderaddress`, to the injected recipient list) when any are flagged; returns the count.

### MedicareService

`Claims/Medicare/MedicareService.cs`. **ProcessNextBatch(ILogWriter)** deletes and reprocesses a fully-matched batch ([SVC-BR-CLM-060](<business-rules.md#svc-br-clm-060-a-fully-processed-batch-(counts-equal)-is-purged-before-the-next-one>)), then for the next batch runs `ProcessMedicareBeneficiarysStatelessMode`: matched beneficiaries with an earlier `LastSubmissionDate` update `MedicareQueryHistory` ([SVC-BR-CLM-061](business-rules.md#svc-br-clm-061-history-is-only-updated-when-the-new-submission-date-is-later)); unmatched beneficiaries insert a new history row. Throws `Exception("Counts Don't match")` if counts still differ after processing ([SVC-BR-CLM-062](business-rules.md#svc-br-clm-062-beneficiary-and-history-counts-must-match-after-processing)).

### SeverityModelActivityService

`Claims/SeverityModel/SeverityModelActivityService.cs` (SQL Server unit of work). **CreateClaimActivities(processDate)** reads `ClaimSeverityModelActivity` rows and builds a Guidewire `Activity` per distinct claim/activity (pattern `severity_activities`, subject "Severity Activity", mandatory). Role mapping: Adjuster/Supervisor/RTW Consultant map to adjuster/supervisor/rtw_ext; any other role is an error and the activity is skipped ([SVC-BR-CLM-070](business-rules.md#svc-br-clm-070-severity-activity-role-must-be-adjuster%2C-supervisor-or-rtw-consultant)). Calls `IActivityDataApiClient.CreateActivityRequest`; `processDate` is stored but never used*.

### ClaimTaskGenerationService

`Claims/Tasks/ClaimTaskGenerationService.cs` (Oracle unit of work). **CreateClaimActivities(processDate)** reads lookback-day config, caps the lookback at 01/03/2021 so only claims received on/after 04-JAN-21 count ([SVC-BR-CLM-080](business-rules.md#svc-br-clm-080-only-claims-received-after-01%2F03%2F2021)), loads claims missing a tax id, skips Void/fatal/Inmate/InProgress claims ([SVC-BR-CLM-081](business-rules.md#svc-br-clm-081-skip-void%2C-fatal%2C-inmate-and-in-progress-claims)), and creates REQ30/REQ60 tasks at the 30/60-day thresholds when not already present ([SVC-BR-CLM-082](business-rules.md#svc-br-clm-082-req30-task-at-30%E2%80%9359-days%3B-req60-at-60%2B-days-only-if-req30-exists%3B-skip-if-the-text-config-is-empty)), assigned to the responsible adjuster via `ITaskService.CreateTaskStateless(isBatchCommit: true)`.

### TaskService

`Claims/Tasks/TaskService.cs` (`ValidatingServiceBase`, `ITaskService`). Constructor reads note-related user preferences, builds `ClaimUserSecurityAccess`, and reads `ValidGroups` from config.

- **GetTasks(TaskFilter)** returns empty with no `ValidGroups` or an invalid group filter ([SVC-BR-CLM-090](business-rules.md#svc-br-clm-090-group-filter-must-be-in-claimactivities.validgroups)); base query is on `Task` (not deleted, source CLAIM or IMAGE/CLAIM|EAIP, `TaskUser != "INFO"`); returns empty without an assigned user or resolved claim ([SVC-BR-CLM-091](business-rules.md#svc-br-clm-091-needs-an-assigned-user-or-a-valid-claim)); external RTW users never see image/document tasks ([SVC-BR-CLM-092](<business-rules.md#svc-br-clm-092-external-rtw-users-never-see-document-(image)-tasks>)). `SetAdditionalClaimTaskData` masks the worker name "Confidential" unless the user has unrestricted/conflict/secure access or a security override applies ([SVC-BR-CLM-093](business-rules.md#svc-br-clm-093-worker-name-is-masked-'confidential'-for-restricted-claims-without-access-or-an-override)); `CanUpdateTaskUser`/`CanUserEdit` restrict external users to their own tasks ([SVC-BR-CLM-094](business-rules.md#svc-br-clm-094-external-users-can-only-edit-their-own-tasks-and-cannot-reassign)).
- **CreateTaskStateless(TaskEditContext, messages, isBatchCommit)**: category defaults to "CLMM"; an "INFO" `TaskUser` gets `MaxValue` date and Info priority, otherwise the date moves to the previous working day and priority is Urgent or the category default ([SVC-BR-CLM-095](business-rules.md#svc-br-clm-095-a-non-working-due-date-moves-to-the-previous-work-day)/[096](business-rules.md#svc-br-clm-096-taskuser-'info'-%E2%86%92-date-maxvalue%2C-info-priority)); validates with `ClaimTaskValidator` ([SVC-BR-CLM-100](business-rules.md#svc-br-clm-100-claim-must-exist)..[102](<business-rules.md#svc-br-clm-102-task-date-between-today-and-%2B2-years-(when-changed%2C-not-info)>). **CreateTask** wraps this in Begin/Commit and returns the reloaded task.
- **UpdateTask(id, ref task)**: reports "Error: Invalid Task ID." if missing (no rollback), a concurrency error on `UpdateNumber` mismatch ([SVC-BR-CLM-097](business-rules.md#svc-br-clm-097-task-concurrency-on-updatenumber)), validates, and on completion soft-deletes and creates a note. **DeleteTask/DeleteTasks/CompleteTasks** soft-delete and create a note when completing or when `IsSavedToNotepad` ([SVC-BR-CLM-098](business-rules.md#svc-br-clm-098-a-completed-or-notepad-required-task-creates-a-claim-note)).
- **CreateTaskNote**: throws if note preferences are missing ([SVC-BR-CLM-099](business-rules.md#svc-br-clm-099-note-preferences-are-required-before-completing)); for Inmate or Secure claims the note's `CreateUser` is the responsible adjuster's mail id ([SVC-BR-CLM-110](business-rules.md#svc-br-clm-110-on-inmate-or-secure-claims%2C-the-note-author-is-recorded-as-the-responsible-adjuster)); adds a `ClaimNote`.
- **ReassignTasks**: bulk update of user/date/priority/group/text, with text changes gated on `TaskTextCanBeBulkUpdated`.
- **Other:** `GetAdditionalInfoTask`, `GetOnBaseEncodedString` (OnBase custom query), `CanUserEdit`, `IsValidClaim`, `IsValidWorkDate`, `PrintTasks` (`GeneratePdf("Clm_TaskAndNotes_Tasks", …)`), `GetTaskGroups`, `GetTaskById`.

### ClaimNotesService

`Claims/Notes/ClaimNotesService.cs`.

- **IsUpdateUser**: CTNoteUpdate, CTNoteSupervisor, RTWAdmin, RTWInternalUpdate, RTWExternalUpdate or CTNCENotes. **IsNCEUser**: CTNCENotes.
- **GetNotes(criteria)/GetNote(id)** reads `ClaimNote` and computes `CanEdit`/`CanEditNoteInfo` from age, next-work-day cutoff and supervisor/creator/secured-access status ([SVC-BR-CLM-120](business-rules.md#svc-br-clm-120-note-edit-window%3A-created-fewer-than-6-days-ago-and-within-2-work-days%2C-and-supervisor%2C-creator-or-secure-access)).
- **SaveNote(id, NoteEdit)** checks concurrency on `UpdateDate` ([SVC-BR-CLM-121](business-rules.md#svc-br-clm-121-note-concurrency-on-updatedate)), sets `UpdateUser` (RTW-suffixed for external users, or the responsible adjuster for inmate/secure claims) ([SVC-BR-CLM-122](business-rules.md#svc-br-clm-122-external-rtw-users-are-stamped-'user-loadcode')), and appends or replaces the text.
- **DeleteNote** requires `CanEdit` ([SVC-BR-CLM-123](business-rules.md#svc-br-clm-123-delete-requires-canedit)) and performs a hard delete.
- **CreateNote** validates and sets `CreateUser` per the same inmate/secure/RTW rules. **PrintNotes** calls `GeneratePdf("Claims_TaskAndNotes_ClaimNotes", …)`.

### NotePreferenceConversionService

`Claims/Notes/NotePreferenceConversionService.cs`. **ConvertNotePreferences(path, …)** reads `*.config` XML files (file name = user id), collects `ClaimTasksNotes` category/type/externalview keys, and for users with CTNoteUpdate, CTNoteSupervisor or CTNCENotes adds a `Preference` row ([SVC-BR-CLM-124](business-rules.md#svc-br-clm-124-preferences-are-converted-only-for-note-editors)).

### RTWService

`Claims/RTW/RTWService.cs`. **GetRTWLoadCode(loadUser)** strips a "TEST" prefix then finds the unexpired `RtwLoadAssignment` by load user. **CanAccessRTWClaim** requires an `RTWCase` match on claim id and load code for RTWExternalUpdate users; otherwise returns true ([SVC-BR-CLM-130](business-rules.md#svc-br-clm-130-external-rtw-users-may-only-access-claims-whose-rtw-case-matches-their-load-code)).

### SecurityOverrideService

`Claims/SecurityOverride/SecurityOverrideService.cs`. AutoMapper maps `IsSecureClaim` from `HostileWorker` codes "R"/"T" and `IsConflictClaim` from `IsWorkerConflict` ([SVC-BR-CLM-140](business-rules.md#svc-br-clm-140-a-secure-claim-is-hostileworker-'r'-or-'t'%3B-conflict-is-isworkerconflict)).

- **GetSecurityOverrideById / GetSecurityOverrides(includeExpired)** reads `SecurityOverride`, sets `CanEdit`/`CanDelete` from expiration/effective dates ([SVC-BR-CLM-141](business-rules.md#svc-br-clm-141-an-override-is-editable-if-not-yet-expired%2C-and-deletable-if-not-yet-effective)).
- **CreateOverride** validates with `SecurityOverrideValidator`, copies `HostileWorkerCode`/`IsWorkerConflict` from the claim's worker, and commits. **UpdateOverride** validates, checks concurrency on `UpdateDate`, and commits. **DeleteOverride** is a hard delete that does not check `CanDelete`.
- **IsValidClaim / IsValidUserId**: existence checks.

### PharmacyEligibilityService

`Claims/PharmacyElilgbility/PharmacyEligibilityService.cs`. **ProcessEligibilityExtract(warningLog, addressLog)**:

1. **Eligible claims**: builds a Domain Claim per report row; skips Void and Denied claims with warnings ([SVC-BR-CLM-150](business-rules.md#svc-br-clm-150-void-or-denied-claims-are-excluded-from-the-pharmacy-extract)); writes the extract record.
2. **First-fill claims**: sets status to FirstFill in memory, writes the extract, then `UpdatePharmElig` restores the status to Ineligible with `TransmitDate = Now` and user "CMN0117" ([SVC-BR-CLM-151](<business-rules.md#svc-br-clm-151-first-fill-%E2%86%92-transmit%2C-then-set-back-to-ineligible-(user-cmn0117)>).
3. **WriteExtract**: a missing address is logged and skipped; a foreign address is logged and replaced with SAIF's own address ([SVC-BR-CLM-152](business-rules.md#svc-br-clm-152-a-worker-with-no-address-is-skipped)/[153](business-rules.md#svc-br-clm-153-a-foreign-address-is-replaced-with-saif's-address)); status/group-code mapping is Ineligible→"T" or otherwise "A" with GroupId by pharmacy tier ([SVC-BR-CLM-154](<business-rules.md#svc-br-clm-154-status-and-group-code-mapping-(a%2Ft%2C-001%E2%80%93013)>); language preference is only kept for codes 01–04 ([SVC-BR-CLM-155](business-rules.md#svc-br-clm-155-language-preference-is-only-kept-for-codes-01%E2%80%9304)).
4. **WriteExtractTrailer** archives the existing file and writes a fixed-width file with a trailer row.

### ClaimReportingService

`Claims/Reports/ClaimReportingService.cs`. **GetBrokenProviderRelationshipDetails(request)** finds expired provider relationships with no active replacement ([SVC-BR-CLM-160](business-rules.md#svc-br-clm-160-a-broken-relationship-is-expired-with-no-active-replacement)), gets affected claims, and reports only active claims with outstanding reserves ([SVC-BR-CLM-161](business-rules.md#svc-br-clm-161-only-active-claims-with-outstanding-reserves-are-reported-and-tasked)). When `CreateTasks` is set, it creates a task per affected relationship assigned to the adjuster via `ITaskService.CreateTaskStateless`.

### ClaimSecurityService

`Security/ClaimSecurityService.cs`. **GetClaimSecurity(claim)** computes `HasConflictAccess`/`HasSecureAccess` from the `ConflictClaim`/`SecureClaim` authorization resources ([SVC-BR-CLM-170](business-rules.md#svc-br-clm-170-conflict-or-secure-access-requires-the-conflictclaim-or-secureclaim-resource)). **GetClaimSecurity(userId, claim)** resolves a specific user via `ClaimUserSecurityAccess`; **LoadClaimSecurity** sets `claim.Security`.

### FraudInvestigationService

`Investigations/FraudInvestigationService.cs`. **GetSubjectNameAsync(caseNumber)** calls the `Investigation` repository extension `GetSubjectName`, falling back through legacy data worker name, Guidewire `IWorkerDataApiClient.GetWorker`, policy name, then "Unknown"; returns null if there is no record. **GetClaimId** delegates to a repository extension.

### ClaimDomainBuilder

`Claims/ClaimDomainBuilder.cs`. A factory for Domain `Claim`/`Worker` objects; `GetClaim(InjuryClaim)` uses the Claim constructor without `IClaimDataApiClient`.

## Legal

This area covers `Legal/**` (`Bills/`, `Notes/`, `Subpoenas/`, `Tasks/`). It implements the legal-bill lifecycle (entry, save, approval, rating against fee schedules, payment/check generation, recoveries and adjustments), legal notes, and the subpoena lifecycle (entry, approval, payment, document/label generation for the xPressions batch, and legal task management). Unqualified source citations in this area (an ID with the rule number omitted) map to the [`SVC-BR-LGL-*`](business-rules.md#legal) series in [Business rules](business-rules.md).

### LegalService

`Legal/LegalService.cs` (`ILegalService`; `IUnitOfWork`, `IUserProfileService`, `ILegalQueryProvider`, `ICCClaimApiService`).

- **GetClaimIdByClaimNumberOrCaseNumber(claimOrCaseNo, msgs)** looks up the claim id via `ILegalQueryProvider.GetClaimIdForCase`; a zero result adds the error "Case or claim not found" ([SVC-BR-LGL-001](business-rules.md#svc-br-lgl-001-case%2Fclaim-lookup-must-resolve-to-a-claim)).
- **GetClaimNumber(legalCaseId)**: throws `ArgumentException` if the legal case does not exist ([SVC-BR-LGL-002](business-rules.md#svc-br-lgl-002-legal-case-must-exist)); otherwise returns `ICCClaimApiService.GetClaim(claimId).ClaimNumber`.
- **GetCaseList(claimId, claimNumberOrCaseId, msgs)**: adds "No cases for this claim" if `GetLegalCases` is empty; builds a `CaseInfo` per case (mapped case, Guidewire claim, latest MCO); without LNConflict access, a conflict-worker **first** case restricts the caller to "You are not authorized to view this claim" and only non-conflict cases are returned ([SVC-BR-LGL-003](business-rules.md#svc-br-lgl-003-users-without-lnconflict-cannot-view-conflict-worker-cases)); otherwise each case gets an assigned attorney, a "(P)" primary-case suffix, a selection flag, and an opposing-counsel address (Correspondence, then Physical, "N/A" if absent) ([SVC-BR-LGL-005](business-rules.md#svc-br-lgl-005-opposing-counsel-address---correspondence%2C-then-physical%3B-'n%2Fa'-if-absent)); `SetSelectedCase` defaults to the primary case, else the first ([SVC-BR-LGL-004](business-rules.md#svc-br-lgl-004-default-selected-case-is-the-primary-case%2C-else-the-first-case)).
- **DetermineAssignedAttorney(caseInfo)** ([SVC-BR-LGL-006](business-rules.md#svc-br-lgl-006-assigned-attorney---appeal-attorney%2C-then-schedule-attorney%2C-then-outsidecncl%2Fother-firm)): appeal attorney first (name via `GetAdpEmployeeName`), else the case's most recent schedule attorney, else "OUTSIDECNCL" with the firm name from `GetOtherFirm`.
- **LegalAttorneyList** reads `AttorneyProfile` rows not expired (or expired within the last year), ordered by last name ([SVC-BR-LGL-007](business-rules.md#svc-br-lgl-007-attorney-list-includes-profiles-expired-within-the-last-year)).
- **LegalCaseExistsAndHasImageAlertsFlag(claimId)** and **UserHasLNConflictAccess** are supporting lookups used elsewhere (claim-summary alerts, conflict checks).

### CaseCostService

`Legal/CaseCostService.cs` (`ICaseCostService`; `IUserProfileService`, `ILegalQueryProvider`, `IBillServiceHelper`, `ICCClaimApiService`).

**PopulateAdditionalCaseListProperties(caseList, includeSubpoenas, includeNotes)** builds a combined cost history per case:

- Historic case costs from `GetCaseCost` (ordered by approved date), forced to status "Paid" with the rendering provider defaulting to the billing provider ([SVC-BR-LGL-008](business-rules.md#svc-br-lgl-008-historic-case-costs-display-as-'paid')).
- Subpoena rows (one per recipient) when included: status "Returned" if `ReturnedDate` is set, provider "Witness" without a provider location, billed = paid = `PaidAmount`, prepayment "Yes", payment id from `GetSubpoenaFinancialTransaction`, `IsPaidSubpoena` requires status Paid and LNBillUpdate ([SVC-BR-LGL-009](business-rules.md#svc-br-lgl-009-subpoena-recipients-appear-as-prepaid-cost-rows%3B-'returned'-if-returneddate-is-set)).
- Legal bill service lines (one row each): hours = units x 15/60, rounded away from zero, for Conference/Conference Cancellation/Deposition/Deposition Cancellation codes ([SVC-BR-LGL-010](<business-rules.md#svc-br-lgl-010-conference%2Fdeposition-units-are-15-minute-increments-(hours-%3D-units%C3%9715%2F60%2C-rounded-away-from-zero)>); MCO rate type shows the claim's MCO plan short description; adjusted (non-amended, with a parent) lines get a "[+] " status prefix; `IsPaidLegalBill` requires Paid and LNBillUpdate; free-text adjustment reasons use `FreeformDescription`; "OUTSIDECNCL" displays as "Outside Counsel"; the financial transaction comes from `BillServiceHelper.GetFinancialTransaction`.
- `MoveAmendedCaseCostsToAdjustedLists` walks each adjusted line's parent chain into `AdjustmentHistory`, removing parents from the main list ([SVC-BR-LGL-011](business-rules.md#svc-br-lgl-011-adjusted-lines-nest-their-parent-chain-as-adjustment-history)); the combined list is ordered by service-from date descending; notes (when included) come from `BillServiceHelper.GetBillNotes`.

### BillService (Legal, sealed)

`Legal/Bills/BillService.cs` (`IBillService`; `IUnitOfWork`, `IUserProfileService`, `IProviderInformationService`, `IEnvironmentService`, `IBillServiceHelper`, `ILegalService`, `ILogService`, Guidewire `IClaimDataApiClient`).

- **GetInboxItems(employeeIdOrMailId, legalAttorneys)**: loads legal bills not in the Paid grouping, bulk-fetches Guidewire claims, masks conflict-worker claimant names as "Confidential" without LNConflict ([SVC-BR-LGL-012](business-rules.md#svc-br-lgl-012-conflict-claimant-names-are-masked-'confidential'-without-lnconflict)), assigns attorneys (falling back to legal support employees), and orders by bill-status date.
- **GetPendingBills(userId)**: a specific active employee's pending-approval bills, or all pending-approval bills for "ALL"/blank; maps claims; computes the employee's Payments/LegalBill initiate and approve limits via `BillServiceHelper.UserAuthorityLevel`; `IsPaidAmountOverAuthority` is true when the paid-amount sum exceeds **both** limits ([SVC-BR-LGL-013](business-rules.md#svc-br-lgl-013-a-bill-is-over-authority-when-its-total-exceeds-both-the-initiate-and-approve-limits)); fills adjustment/quantity text and a case-notes summary; masks conflict claimants.
- **GetLegalBill(billId, msgs)**: adds "Bill not found" if missing; adds physical/payment addresses from `IProviderInformationService`.
- **BillUpdateEmployeesList**: legal support employees authorized for "LNBillUpdate".
- **LegalSupportEmployeeList** (private): department "005201", status active, job title starting "Legal " ([SVC-BR-LGL-014](business-rules.md#svc-br-lgl-014-legal-support-staff-%3D-dept-%22005201%22%2C-active%2C-title-starting-with-%22legal%22%3B-plus-a-hard-coded-dev-list-outside-production)); non-production environments also add hard-coded ids GREMIL, MELHIL, CHACAT, BRISLY, AARCHA.
- **CurrentUserPaymentLimit / CurrentUserApprovalLimit**: `UserAuthorityLevel` for Initiate or Approve, LegalBill type. Additional lookup lists (fees by IME/MCO, special handling, rate types, adjustment reasons, status codes, service codes) are simple reference reads.

### BillSaveService

`Legal/Bills/BillSaveService.cs` (`IBillSaveService`; `IValidationService`, `ILegalQueryProvider`, `IBillServiceHelper`, `ILegalService`).

- **SaveBill**: defaults status to InProgress, then `SaveLegalBill`.
- **SubmitBill**: `SetApproverId` ([SVC-BR-LGL-020](<business-rules.md#svc-br-lgl-020-approver-%3D-the-assigned-(or-requesting)-attorney-if-their-profile-is-active%2C-else-the-attorney's-manager%3B-no-profile-%E2%86%92-no-approver>)); if it fails, saves as-is; otherwise sets status PendingApproval and saves.
- **ApproveBill**: defaults a null status to PendingApproval; unless ApprovedWithChange, sets approver = current user's employee id and approved date = now; `UpdateBillStatusCode` sets Approved when the approver's initiate or approve limit is >= the paid-amount sum, otherwise routes to PendingApproval with the profile's manager as approver and an explanatory comment ([SVC-BR-LGL-021](business-rules.md#svc-br-lgl-021-approval-within-the-approver's-initiate-or-approve-limit-%E2%86%92-approved%3B-otherwise-pendingapproval-routed-to-the-manager-with-a-supervisor-comment)).
- **ApproveWithChanges**: sets approver = current user, status ApprovedWithChange, appends " [APPROVED WITH CHANGES]" ([SVC-BR-LGL-022](business-rules.md#svc-br-lgl-022-approve-with-changes-sets-status-approvedwithchange-and-appends-a-tag)).
- **DisapproveBill**: clears approver/date, sets status InProgress, appends " [Returned by approver]" ([SVC-BR-LGL-023](business-rules.md#svc-br-lgl-023-disapprove-returns-the-bill-to-inprogress%2C-clears-the-approver%2C-appends-a-tag)).
- **AdjustBill(bill, msgs, newStatus)** ([SVC-BR-LGL-024](business-rules.md#svc-br-lgl-024-adjustment-creates-a-child-bill-with-only-the-changed-lines%3B-a-partial-return%2C-or-a-service-change-with-unchanged-amount%2C-forces-approved%3B-no-approver-%E2%86%92-inprogress%3B-no-changes-%E2%86%92-error)): creates a child bill (`ParentBillId` = old id, `Id` = 0); compares each service line to the original, removing unchanged lines and generating comments for paid-amount decreases ("Partial return of...", forced Approved), increases ("Additonal payment of..."), service-code changes, and from/thru-date changes; a failed `SetApproverId` reverts status to InProgress; no remaining lines produces the error "No changes were made to any bill service lines." and returns 0.
- **DeleteBill(billId)**: deletes the legal bill and, if it has a parent, un-amends the parent's matching lines ([SVC-BR-LGL-025](business-rules.md#svc-br-lgl-025-deleting-an-adjustment-un-amends-the-parent's-lines)).
- **SaveLegalBill** (private): skips validation for PaymentReturned/Paid bills ([SVC-BR-LGL-026](business-rules.md#svc-br-lgl-026-bills-in-paymentreturned%2Fpaid-status-skip-validation)); otherwise validates with `LegalBillValidator`/`LegalBillServiceValidator` (received date between injury date and today ([SVC-BR-LGL-027](business-rules.md#svc-br-lgl-027-received-date-must-be-between-the-date-of-injury-and-today)); service-from date >= injury date and <= today, +6 months for prepayments ([SVC-BR-LGL-028](<business-rules.md#svc-br-lgl-028-service-from-date-%E2%89%A5-date-of-injury-and-%E2%89%A4-today-(prepayments-%E2%89%A4-%2B6-months)>); thru-date >= from-date and within the same window ([SVC-BR-LGL-029](<business-rules.md#svc-br-lgl-029-thru-date-%E2%89%A5-from-date-and-%E2%89%A4-today-(prepayments-%2B6-months)>); an `UpdateNumber` mismatch reports "Error: This record has been updated by someone else." and rolls back ([SVC-BR-LGL-031](business-rules.md#svc-br-lgl-031-optimistic-concurrency-on-updatenumber)); invoice text is uppercased and witness info truncated to 100 characters ([SVC-BR-LGL-032](business-rules.md#svc-br-lgl-032-invoice-text-uppercased%3B-witness-info-%E2%89%A4100-chars%3B-subpoena-special-delivery-%E2%89%A41000-chars)); status changes add a history row with "CREATED"/"*ADDED...*" annotations ([SVC-BR-LGL-033](business-rules.md#svc-br-lgl-033-status-change-writes-a-billstatushistory-row%3B-comments-are-annotated-created%2Fadded)); `SaveBillServices` upserts changed lines and keeps `AdjustmentReasonText` only for FreeformDescription; a new bill's matching parent lines are marked `IsAmended = true`.
- **SetApproverId** (private): resolves the assigned or requesting attorney's profile; returns false if none exists; the approver is the attorney if not expired, else the profile's manager ([SVC-BR-LGL-020](<business-rules.md#svc-br-lgl-020-approver-%3D-the-assigned-(or-requesting)-attorney-if-their-profile-is-active%2C-else-the-attorney's-manager%3B-no-profile-%E2%86%92-no-approver>)).

### BillRateService

`Legal/Bills/BillRateService.cs` (`IBillRateService`; `IProviderQueryProvider`, `IClaimQueryProvider`, `IValidationService`).

- **CalculateAllowedAmount(claimId, providerLocationId, serviceLine)**: caps the paid amount at billed ([SVC-BR-LGL-040](business-rules.md#svc-br-lgl-040-paid-amount-is-capped-at-billed-amount-before-rating)); invalid lines (per `LegalBillServiceValidator`) return false; CopyElectronic/CopyPaper use a fee lookup with reason ElectronicCopies/PaperCopies if reduced ([SVC-BR-LGL-041](business-rules.md#svc-br-lgl-041-copy-services-use-the-fee-schedule%3B-reason-electroniccopies%2Fpapercopies-if-reduced)); UnknownService pays 0 with reason Unknown ([SVC-BR-LGL-042](business-rules.md#svc-br-lgl-042-unknown-service-pays-0-with-reason-unknown)); an IME rate type with a matching IME fee is capped at `MaximumAmount` with reason ImeContract if reduced ([SVC-BR-LGL-043](business-rules.md#svc-br-lgl-043-ime-rate-type-uses-the-ime-specific-fee%2C-capped-at-maximumamount%3B-reason-imecontract-when-reduced)); otherwise the active MCO for the service date is checked and, if the provider is in-network, an MCO fee is applied and capped with reason McoContract ([SVC-BR-LGL-044](business-rules.md#svc-br-lgl-044-mco-rate-applies-when-the-worker's-mco-covers-the-service-date-and-the-provider-is-in-the-mco%3B-capped%3B-reason-mcocontract)); the fee formula is a flat amount, or initial-units x rate plus additional blocks, capped at billed ([SVC-BR-LGL-045](business-rules.md#svc-br-lgl-045-fee-formula---flat-amount%2C-else-initial-units%C3%97rate-%2B-additional-blocks%3B-%E2%89%A4-billed)); the fallback pays the billed amount at the Standard rate, clearing IME/MCO/NegotiatedRate reasons ([SVC-BR-LGL-046](business-rules.md#svc-br-lgl-046-fallback-is-the-standard-rate---pays-billed-amount%2C-clears-contract-reasons)).
- **CheckForDuplicateService(caseId, providerLocationId, billId, line)**: an exact match (provider, case, code, from-date, billed amount) on another bill pays 0 with reason DuplicateBill; a same-provider/case/date match sets `IsPossibleDuplicate = true` ([SVC-BR-LGL-047](<business-rules.md#svc-br-lgl-047-exact-duplicate-(provider%2C-case%2C-code%2C-from-date%2C-amount)-pays-0-with-reason-duplicatebill%3B-same-provider%2Fcase%2Fdate-flags-a-possible-duplicate>)).

### BillPaymentService

`Legal/Bills/BillPaymentService.cs` (`IBillPaymentService`; `IDocumentService`, `IFinancialTransactionService`, Guidewire `IClaimDataApiClient`/`IActivityDataApiClient`/`IFinancialDataApiClient`).

- **GenerateCheckTransactionsAsync(issueDate, controlReport)**: bulk status Approved to ReadyToPay, then for each ReadyToPay bill: zero/negative paid sum is counted as zero-pay only; bills with no parent go through `GenerateCheckTransactionAsync`; bills with a parent go through `GenerateTransactionsForAdjustedBillAsync` ([SVC-BR-LGL-050](business-rules.md#svc-br-lgl-050-batch---approved-%E2%86%92-readytopay%2C-then-checks-created), [SVC-BR-LGL-051](business-rules.md#svc-br-lgl-051-bills-with-total-%E2%89%A40-are-only-counted-as-zero-pay)).
  - **GenerateCheckTransactionAsync**: calls `IFinancialTransactionService.CreateLegalCheckTransaction(bill, issueDate, approverId)`; a non-empty response sets status Paid and `IssueDate`; an `InvalidPaymentAddressException` triggers `RejectBillForAddressCorrection` (status ApprovedWithChange plus an email through `IDocumentService.EmailService.SendEmail`) ([SVC-BR-LGL-052](<business-rules.md#svc-br-lgl-052-invalid-payment-address-%E2%86%92-approvedwithchange-%2B-comment-%2B-email-to-the-creator-(prod)-or-devs>)).
  - **GenerateTransactionsForAdjustedBillAsync**: requires exactly 1 line, a Paid-grouping parent, and an amended original line ([SVC-BR-LGL-053](business-rules.md#svc-br-lgl-053-adjusted-bill-must-have-1-line%2C-a-paid-group-parent-and-an-amended-original%3B-the-delta-decides-check-vs-recovery)); a positive delta creates a check via `CreateLegalCheckTransaction`; a negative delta calls Guidewire `GetGWClaimFinancials` to find the parent payment then `CreateRecoveryAsync` with `RecoveryFinancialData` (`CostCategory = Legal`, `CostType = CostTypeExpenseDC`, `Exposure = GeneralDamage`, `PayeeRole = CheckPayee`, `RecoveryCategory = recovprov_ext`); either path sets status Paid ([SVC-BR-LGL-054](business-rules.md#svc-br-lgl-054-successful-transaction-%E2%86%92-paid)).
- **GenerateZeroPaymentLetters(batchArgs)**: `XPrintControl`, ReadyToPay bills with paid sum 0, `ZeroPayment` per zero-paid line with an adjustment reason (payment address, Guidewire claim, "Legal Bills Zero Pay Letters" banner); sets Paid and adds an `XDocument` (".ps") ([SVC-BR-LGL-055](business-rules.md#svc-br-lgl-055-zero-pay-letters-go-to-readytopay-bills-with-total-0-for-lines-with-a-reason%3B-bills-%E2%86%92-paid)); writes the xPressions XML.
- **GetAttorneysWithPendingLegalBills**: per active attorney, count/email/name of PendingApproval bills.
- **UpdateBillStatus(from, to)**: bulk status change with history. **ReturnBillPayment(billId, msgs)**: requires Paid status and no amended line ("Error: The bill could not be found" / "Error: The amended bill cannot be returned"); `GetGWClaimFinancials` + `CreateRecoveryAsync` for the first line's amount; zeroes paid amounts and sets PaymentReturned ([SVC-BR-LGL-056](business-rules.md#svc-br-lgl-056-return-payment-only-for-paid%2C-non-amended-bills%3B-gw-recovery%3B-lines-zeroed%3B-paymentreturned)).

### BillServiceHelper

`Legal/Bills/BillServiceHelper.cs` (`IBillServiceHelper`). **UserAuthorityLevel(employeeId, purpose, paymentType)** reads active `EmployeeAuthorityLimit` rows for Payments/function/purpose ([SVC-BR-LGL-060](business-rules.md#svc-br-lgl-060-authority-limit-%3D-active-employeeauthoritylimit-for-payments%2Ffunction%2Fpurpose%2C-else-0)). **GetNextIssueDate(code)** returns `PaymentTransactionTypeCode.NextIssueDate` or today. **IsValidIssueDate** requires >= next issue date, <= today+14, and a working day ([SVC-BR-LGL-061](business-rules.md#svc-br-lgl-061-issue-date-must-be-%E2%89%A5-next-issue-date%2C-%E2%89%A4-today%2B14%2C-and-a-working-day)). **IsValidReceivedDate/IsValidServiceFromDate/IsValidServiceThruDate** implement the injury-date/today windows ([SVC-BR-LGL-027](business-rules.md#svc-br-lgl-027-received-date-must-be-between-the-date-of-injury-and-today), [028](<business-rules.md#svc-br-lgl-028-service-from-date-%E2%89%A5-date-of-injury-and-%E2%89%A4-today-(prepayments-%E2%89%A4-%2B6-months)>), [029](<business-rules.md#svc-br-lgl-029-thru-date-%E2%89%A5-from-date-and-%E2%89%A4-today-(prepayments-%2B6-months)>). **GetBillNotes, GetFinancialTransaction, LogError, UnitsToQuantityString** are supporting lookups/formatters.

### LegalNotesService

`Legal/Notes/LegalNotesService.cs` (`ILegalNotesService`). **GetNotes(criteria)**: throws `ArgumentNullException` if `CaseId == 0` ([SVC-BR-LGL-070](business-rules.md#svc-br-lgl-070-notes-query-requires-a-case-id)); filters by case/date/category, then `SetNoteAccessProperties` computes `CanEdit` (LNNoteSupervisor or creator, within 2 working days, not system-created) and `CanEditNoteInfo` ([SVC-BR-LGL-071](<business-rules.md#svc-br-lgl-071-note-editable-only-by-supervisor-(lnnotesupervisor)-or-creator%2C-within-2-working-days%2C-not-system-created>)). **CreateNote/UpdateNote** require LNUpdate ([SVC-BR-LGL-072](business-rules.md#svc-br-lgl-072-create%2Fupdate-notes-requires-lnupdate)), validate with `LegalNoteValidator`, and (for update) check `UpdateNumber` concurrency; append text is stamped "*ADDED {date} by {user}*". **DeleteNote** blocks system-created notes and requires `CanEdit` ([SVC-BR-LGL-073](business-rules.md#svc-br-lgl-073-delete---not-system-created-and-canedit)). **PrintNotes** calls `GeneratePdf("Legal_TaskAndNotes_CaseNotes", xml)`.

### SubpoenaService

`Legal/Subpoenas/SubpoenaService.cs` (`ISubpoenaService`; `ILocationQueryProvider`, `IGoogleMapsApiServiceProxy`, `ICCClaimApiService`).

- **GetInboxItems**: not-Paid/not-ReadyToPay subpoenas with Guidewire claims, conflict masking ([SVC-BR-LGL-012](business-rules.md#svc-br-lgl-012-conflict-claimant-names-are-masked-'confidential'-without-lnconflict)), requester user id, and a witness list.
- **GetCalculatedSubpoenaPayment(fromLocation, toLocation)**: round-trip mileage via `IGoogleMapsApiServiceProxy` (doubled), retried once with a stripped address line on failure, else mileage 0 with an error message; mileage cost = round(miles x `MileageRate`, 2); paid = flat fee + mileage cost when miles > 0 ([SVC-BR-LGL-080](<business-rules.md#svc-br-lgl-080-subpoena-witness-pay-%3D-flat-fee-%2B-round(round-trip-miles-%C3%97-mileage-rate%2C-2)%2C-only-when-miles-%3E0>); an overload accepts miles directly, treating negative miles as 0.
- **GetSubpoena**: loads the subpoena ("Subpoena not found" if missing) with formatted recipient addresses.
- **GetApprovalEmail**: sent to `LegalSubpoenaApprovers@saif.com` with a deep link to the Subpoena entry UI ([SVC-BR-LGL-081](business-rules.md#svc-br-lgl-081-over-authority-subpoenas-are-emailed-to-legalsubpoenaapprovers%40saif.com)).
- **InitiateEmployeeList, ListOfHearingLocations** (excludes "FED"), **DefaultComplianceDate/MinimumComplianceDate** (next LS issue date +14/+7 days, [SVC-BR-LGL-082](business-rules.md#svc-br-lgl-082-compliance-date-default-%3D-next-ls-issue-%2B14-days%3B-minimum-%2B7-days)), **CurrentUserPaymentLimit/CurrentUserApprovalLimit** for the Subpoena payment type.

### SubpoenaSaveService

`Legal/Subpoenas/SubpoenaSaveService.cs` (`ISubpoenaSaveService`).

- **ApproveSubpoena**: approved only if the user's initiate or approve limit >= the **maximum** recipient paid amount ([SVC-BR-LGL-083](business-rules.md#svc-br-lgl-083-subpoena-approved-only-if-the-user's-limit-%E2%89%A5-max-recipient-paid-amount)); otherwise unchanged.
- **DisapproveSubpoena**: status InProgress. **DeleteSubpoena**: hard delete.
- **SaveSubpoena**: defaults status InProgress and type MedicalRecords; validates with `SubpoenaValidator`/`SubpoenaRecipientValidator` unless Paid; `UpdateNumber` concurrency check; an opposing-counsel firm with no address blocks the save ([SVC-BR-LGL-084](business-rules.md#svc-br-lgl-084-opposing-counsel-firm-must-have-an-address)); `SetSubpoenaData` computes appearance date-time, nulls `MinValue` dates, truncates special-delivery text to 1000 characters, and sets `IssueDate` to the next LS issue date (recomputed on approval, +2 work days for witness types) ([SVC-BR-LGL-085](business-rules.md#svc-br-lgl-085-issue-date-%3D-next-ls-issue-date%3B-witness-types-%2B2-work-days-on-approval)); `SaveRecipients` upserts provider recipients (registered agent + provider location) and witness recipients (`LegalPerson`/`LegalPersonAddress`, uppercased state).

### SubpoenaPaymentService

`Legal/Subpoenas/SubpoenaPaymentService.cs` (`ISubpoenaPaymentService`; `IFinancialTransactionService`, `IFinancialDataApiClient`, Guidewire `IClaimDataApiClient`).

- **GenerateCheckTransactionsAsync(controlReport)**: spend category from `BillService` code `SubpoenaFee`; Approved subpoenas whose issue date matches the next issue date for `PaymentTransactionTypeCode.Subpoena` (non-"NOML" first); MedicalRecords/Claims types with an `InvalidPaymentAddressException` on any recipient reject to InProgress plus an email, otherwise call `IFinancialTransactionService.CreateSubpoenaCheckTransactions` and set ReadyToPay; witness types go straight to ReadyToPay with no check created here ([SVC-BR-LGL-086](business-rules.md#svc-br-lgl-086-payment-batch%3A-only-records%2Fclaims-types-get-checks%3B-invalid-address-%E2%86%92-inprogress-%2B-email%3B-witness-types-%E2%86%92-readytopay-only)).
- **UpdateSubpoenaStatus(from, to)**: only for the next-issue-date subpoenas.
- **ReturnSubpoenaPaymentAsync(recipientId)**: the recipient must exist and not already be returned; Guidewire `GetGWClaimFinancials` + `CreateRecoveryAsync` for `AmountPaid`; zeroes the paid amount and stamps `ReturnedDate` ([SVC-BR-LGL-087](business-rules.md#svc-br-lgl-087-recipient-payment-return---not-already-returned%3B-gw-recovery%3B-paid%3D0%2C-returneddate%3Dtoday)).

### SubpoenaDocumentService

`Legal/Subpoenas/SubpoenaDocumentService.cs` (`ISubpoenaDocumentService`; `IDocumentService`, `IProviderInformationService`, `IPolicyInformationService`, `ICCClaimApiService`).

- **GetPreviewSubpoena(id)**: first recipient; standard opening text; special-handling code (CERT/NOML) appended; renders package "Payment_Legal_Subpoena_sSub".
- **PrintSubpoena(id, processServerEmailList)**: package "Payment_Legal_Subpoena", duplex for MedicalRecords/Claims/OtherMedical types, 2 copies for a process server with no email list or a no-process-server zero-pay-no-mail case ([SVC-BR-LGL-088](business-rules.md#svc-br-lgl-088-print%3A-duplex-for-records%2Fclaims%2Fother-medical%3B-2-copies-for-process-server-without-email-or-zero-pay-no-mail%3B-witness-paid-%E2%86%92-f269-to-accounting)); non-zero-pay witness types also run `GenerateCashDisbursements` (emails package "Fin_DC_F269" to `APInvoices@saif.com` in production, GL `601.030`); an email list triggers `EmailSubpoenaToProcessServer` with the PDF attached and a computed serve-by date.
- **AddSubpoenaDocumentToList, GenerateCashDisbursement** support document records and cash-disbursement XML (empty when the approver is missing or paid = 0; account 601.030, division 5201).
- **CreateDocumentRecord** (private): the Guidewire claim must exist, else "The claim {id} does not exist in Claim Center." ([SVC-BR-LGL-089](business-rules.md#svc-br-lgl-089-claim-must-exist-in-claimcenter-to-render-a-subpoena)); attorney signature reads "Special Assistant Attorney General" for state-entity policies, else "Trial Counsel" ([SVC-BR-LGL-090](business-rules.md#svc-br-lgl-090-signature-'special-assistant-attorney-general'-for-state-entity-policies%2C-else-'trial-counsel')); opposing-counsel address falls back through Correspondence, Physical, then the worker's Home/Mailing/Primary address.

### SubpoenaLabelService

`Legal/Subpoenas/SubpoenaLabelService.cs` (`ISubpoenaLabelService`; `ISimpleCertifiedMailServiceProxy`). **GenerateBatchLabels/GenerateRecipientLabels**: ReadyToPay CERT-handling subpoenas get per-recipient labels via `ISimpleCertifiedMailServiceProxy.SaveLabelAsGif`; a `SimpleCertifiedException` triggers an "ACTION REQUIRED - Certified Label creation failure" email ([SVC-BR-LGL-091](business-rules.md#svc-br-lgl-091-certified-label-failure-%E2%86%92-action-email)). **GenerateOpposingCounselLabel**: firm label, else worker label; unknown page count returns size Unknown.

### ZeroPaySubpoenaProcess, WitnessSubpoenaProcess, OpposingCounselService

`Legal/Subpoenas/ZeroPaySubpoenaProcess.cs`, `WitnessSubpoenaProcess.cs`, `OpposingCounselService.cs` -- xPressions batch document generators.

- **ZeroPaySubpoenaProcess.GenerateZeroPayDocuments**: ReadyToPay provider-type subpoenas with `PaidAmount == 0`; generates labels for mailed recipients; document config (CERT, "Deliver to Mail Room"/"Deliver to Transformers Team" outside production), `XPrintControl`, and an `XDocument` with an OnBase IDX string (file LEGAL, category "LG - LEGAL WORK PRODUCT", type SUBPOENA) ([SVC-BR-LGL-092](business-rules.md#svc-br-lgl-092-zero-pay-subpoena-docs-for-provider-type-recipients-with-paid-0)); errors are emailed to developers.
- **WitnessSubpoenaProcess.GenerateWitnessDocuments**: ReadyToPay witness types split by `ProcessName` ("WITNESS"/"CASHDISBURSEMENT" vs process-server/zero-pay-no-mail) ([SVC-BR-LGL-093](business-rules.md#svc-br-lgl-093-witness-docs-split-by-process-server-%2F-zero-pay-no-mail-per-processname)); "WITNESS" also generates labels; `GenerateWitnessCashDisbursementRequest` concatenates each recipient's cash-disbursement XML.
- **OpposingCounselService.GenerateOpposingCounselDocuments**: ReadyToPay provider-type subpoenas (excluding Claims+DoNotMail), one document per subpoena with an envelope chosen from label size ([SVC-BR-LGL-094](<business-rules.md#svc-br-lgl-094-opposing-counsel-copies-(excluding-claims%2Bdonotmail)%3B-all-readytopay-%E2%86%92-paid%2C-all-or-nothing>); sets **all** selected subpoenas to Paid and commits only if the updated count matches the selected count, otherwise rolls back and returns 0.

### LegalTaskService

`Legal/Tasks/LegalTaskService.cs` (`ILegalTaskService`, extends `ValidatingServiceBase`; `IClaimInformationService`, `IImagingClient`, `IWorkCalendarService`, `IClaimQueryProvider`, `IWorkerQueryProvider`, `ICCClaimApiService`, `IClaimDataApiClient`).

- **GetTasks(filter)**: returns empty for an empty `ValidGroups` or an invalid group user ([SVC-BR-LGL-100](business-rules.md#svc-br-lgl-100-group-user-task-queries-must-use-a-configured-valid-group)); base query is LEGAL source (or IMAGE excluding POLCY) with `TaskUser != "INFO"`; returns empty without a user or resolved claim ([SVC-BR-LGL-101](business-rules.md#svc-br-lgl-101-tasks-need-a-user-or-a-valid-claim-filter)); top 300 results, newest first ([SVC-BR-LGL-102](business-rules.md#svc-br-lgl-102-task-list-is-capped-at-300%2C-newest-first)); claims from Guidewire `GetClaims`; worker name and task text are masked "Confidential" for restricted/conflict/secure claims without access or override ([SVC-BR-LGL-103](business-rules.md#svc-br-lgl-103-worker-name%2Ftext-masked-for-conflict%2Fsecure-claims-without-access-or-override)); images come from `IImagingClient.GetImages` in batches of 1000.
- **CreateTask/CreateTaskStateless**: `FillTaskData` (category LGLT with a case, CLMM otherwise; context key is the case id or claim number; INFO user gets `MaxValue` date and Info priority; otherwise the date moves to the previous working day and priority is Urgent or the category default), validated with `LegalTaskValidator` ([SVC-BR-LGL-104](<business-rules.md#svc-br-lgl-104-legaltask%3A-claimnumber-isvalidclaim-%22error%3A-invalid-claim-number.%22%3B-taskuser-canuseredit-(lnupdate)-unless-info-or-valid-group-%22error%3A-invalid-or-unauthorized-mail-id.%22%3B-taskdate-between-adjusttaskdate(today)-and-today%2B2-years-when-changed-and-not-info-%22error%3A-an-activity-must-have-a-current-date-or-a-date-not-more-than-2-years-in-the-future.%22%3B-caseid%E2%89%A00-for-lglt-%22error%3A-a-case-number-must-be-specified-for-legal-activities.%22%3B-legalnotecategory-notnull-for-lglt-and-not-info-%22error%3A-note-category-is-required.%22>)).
- **UpdateTask**: missing task gives "Error: Invalid Activity ID."; concurrency check; task text is cleaned only without restriction or with access; priority Urgent or Medium; `IsComplete` runs `CompleteTask`.
- **CompleteTask** (private): restricted/secured claims without access return null/false; an IMAGE LEGAL task requires a note category ([SVC-BR-LGL-105](business-rules.md#svc-br-lgl-105-completing-an-image-legal-task-requires-a-note-category)); soft-deletes and calls `CreateTaskNote`, which throws if note preferences (key "CategoryCode") are missing, attributes the note to the responsible adjuster for inmate/secure claims, and writes a `LegalNote` (LGLT/LEGAL) or a `ClaimNote` (category LEGAL, type UPDT) otherwise ([SVC-BR-LGL-106](<business-rules.md#svc-br-lgl-106-completion-writes-a-legalnote-(lglt%2Flegal)-or-claimnote-(legal%2Fupdt)%3B-preferences-required%3B-secure%2Finmate-claims-attribute-the-note-to-the-adjuster>)-or-claimnote-(legal%2Fupdt).
- **DeleteTask/CompleteTasks/DeleteTasks**: restricted/secured without access is blocked ([SVC-BR-LGL-108](business-rules.md#svc-br-lgl-108-restricted%2Fsecured-tasks-cannot-be-deleted-or-completed-without-access)); otherwise soft-deletes, noting when `IsSavedToNotepad`.
- **ReassignTasks**: optional user/date/priority/text changes; text changes require an image and an unmasked worker ([SVC-BR-LGL-107](business-rules.md#svc-br-lgl-107-task-dates-falling-on-non-working-days-move-to-the-previous-working-day)).
- **GetClaimDocumentURL/GetCaseDocumentURL, PrintTasks, CanUserEdit (LNUpdate), IsValidClaim, IsValidWorkDate, AdjustTaskDate, HasMedia**: supporting operations.

## Policies, certificates, output documents, RCT, and documents

This area covers `Policies/**` (Certificates, DcbsOregonBusiness, Information, PayrollReport, PolicyAssociation, PolicyDocumentsApi, SecurityServices), `OutputDocuments/**`, `Rct/**` and `Documents/**`. It implements certificate-of-insurance issuance/renewal/resend, certificate holder and preference maintenance, Oregon-business-registry file processing, policy security/association lookups, the policyholder/agency document inbox (release, hold, replace, view), Oregon RCT (Retroactive Coverage Transmission) staging/XML export, and the generic document-template/request/distribution engine. Unqualified source citations in this area (an ID with the rule number omitted) map to the [`SVC-BR-POL-*`](business-rules.md#policies-and-certificates) series (Policies and certificates) or the [`SVC-BR-DOC-*`](business-rules.md#documents) series (Documents), depending on the subsystem cited.

### CertificateService

`Policies/Certificates/CertificateService.cs` (`ICertificateService`; `IValidationService`, `ICertificateInformationService`, `ICertificateSecurityService`, certificate/util `IDocumentService`, `ICertificatePreferenceService`, `ICertificateDistributionService`).

- **AddCertificate(edit, distributionOptions)**: `CertificateSecurityService.CertificateCanBeAdded` checks add rights ([SVC-BR-POL-020](business-rules.md#svc-br-pol-020-standard%2Fbid-add-needs-maintainothers%2C-maintainown-or-the-ext-variant-of-either), [SVC-BR-POL-021](business-rules.md#svc-br-pol-021-osc-add-needs-maintainothersosc%2C-maintainownosc-or-the-ext-variant-of-either)); validates with `CertificateValidator` ([SVC-BR-POL-001](business-rules.md#svc-br-pol-001-'a-certificate-holder-is-required')..[009](<business-rules.md#svc-br-pol-009-'osc-limits-are-required.'-(osc-type)>); `CurrentCertificateHolderId == 0` forces the holder action to Add ([SVC-BR-POL-030](business-rules.md#svc-br-pol-030-a-holder-id-of-0-forces-holder-action-add)); builds the certificate (batch-print status, create/update/issue stamps, holder-action processing except for Bid certificates, [SVC-BR-POL-031](business-rules.md#svc-br-pol-031-bid-certificates-get-no-holder-processing)); images it via `DocumentService.GetCertificateDocumentStream` + `CertificateDistributionService.ImageCertificate`; saves and distributes; rolls back on any exception.
  - **ProcessCertificateHolderAction** (internal): NoUpdate loads the existing holder; Add creates a new one; Expire calls SAIF.Domain `CertificateHolder.Expire()` then creates a new holder; Supersede (only when `CurrentCertificateHolderId != 0`) creates a new holder then calls SAIF.Domain `CertificateHolder.Supersede(new)` ([SVC-BR-POL-032](business-rules.md#svc-br-pol-032-holder-expire-%2F-supersede-go-through-the-domain-object%3B-supersede-only-when-the-current-holder-id-%E2%89%A0-0)).
- **EditCertificate(id, edit, opts)**: throws "Error: Certificate not found." if missing; checks edit rights ([SVC-BR-POL-022](<business-rules.md#svc-br-pol-022-edit%2Fdelete%3A-maintainothers(osc)-%3D-any-certificate%3B-maintainown(osc)-%3D-only-if-the-current-user-is-createuser-(case-insensitive)>)-%3D-any-certificate%3B-maintainown(osc)..[024](<business-rules.md#svc-br-pol-024-cutoff%3A-editable%2Fdeletable-only-while-createdate-%2B-9-days-(securityresources.maximumeditibledays-%3D-9)-%3E-today.-cutoff-date-%3D-today-%E2%88%92-9>)) and validates; `DidCertificateChange` compares DBA, period, holder, names, address, job description, other coverages, and cancel-notice flag ([SVC-BR-POL-042](business-rules.md#svc-br-pol-042-re-image%2C-new-issue-date%2Fuser-and-distribution-point-new-only-if-tracked-fields-changed%3B-otherwise-resend)): a change sets a new issue date/user and distribution point New, otherwise Resend; re-images only when changed; distributes and commits.
- **DeleteCertificate(id)**: checks edit/delete rights (same POL-022..024 range) then deletes.
- **RenewCertificate(id, edit, renewalPeriod, opts)**: confirms the certificate has not already been renewed ([SVC-BR-POL-025](<business-rules.md#svc-br-pol-025-a-certificate-already-renewed-(hasbeenrenewed)-cannot-be-renewed-again>)) (the renew-authority check, [SVC-BR-POL-026](<business-rules.md#svc-br-pol-026-renew-authority%3A-standard-needs-certificatesrenew-or-certificatesrenewext%3B-osc-needs-certificatesrenewosc-or-certificatesrenewoscext-(not-called-by-renewcertificate)>); sets `RenewedFlag` on the original, builds the new certificate (`IsRenewalCertificate = true`, effective at the renewal period), images, adds, distributes and commits.
- **ResendCertificate(id, opts)**: previews the stream, redistributes it, and resets batch-print status in its own transaction only when mailing to the certificate holder ([SVC-BR-POL-043](business-rules.md#svc-br-pol-043-resend-resets-print-status-only-when-mailing-to-the-certificate-holder)).
- **Reads**: `GetCertificate`, `GetCertificates(policy, period)`, `GetCertificatesReadyForBatchDistribution`, `GetEditableCertificates`, `GetStandardOtherCoverageTexts`; `PreviewCertificate` throws `ImageNotFound` when no image exists; `SetCertificateBatchPrintStatus` implements the DoNotPrint/ReadyToPrint mapping ([SVC-BR-POL-040](business-rules.md#svc-br-pol-040-batch-print---both-statuses-donotprint%3B-certificate-holder-readytoprint-if-mailtocertificateholder%3B-stop-there-on-resend), [SVC-BR-POL-041](<business-rules.md#svc-br-pol-041-policyholder-copy-readytoprint-if-delivery-%E2%89%A0-none-and-either-(non-renewal-and-mail)-or-(renewal-and-renewalnotificationtype-copy-or-digest)>).

### CertificateSecurityService

`Policies/Certificates/CertificateSecurityService.cs` (`ICertificateSecurityService`). **CanCurrentUserViewOsc** ([SVC-BR-POL-050](business-rules.md#svc-br-pol-050-view-osc-needs-certificatesviewosc-or-certificatesviewoscext)); **CanCurrentUserAccess(OtherStatesPolicy)** ([SVC-BR-POL-051](business-rules.md#svc-br-pol-051-osc-policy-access%3A-agents-only-if-osc-agentid-or-agentagencyid-equals-the-external-customerid%3B-non-agents-allowed%3B-needs-pol-050)); **CanCurrentUserAdd/AddOsc/Renew/RenewOsc** and **CertificateCanBeAdded** are resource checks ([SVC-BR-POL-020](business-rules.md#svc-br-pol-020-standard%2Fbid-add-needs-maintainothers%2C-maintainown-or-the-ext-variant-of-either), [SVC-BR-POL-021](business-rules.md#svc-br-pol-021-osc-add-needs-maintainothersosc%2C-maintainownosc-or-the-ext-variant-of-either), [SVC-BR-POL-026](<business-rules.md#svc-br-pol-026-renew-authority%3A-standard-needs-certificatesrenew-or-certificatesrenewext%3B-osc-needs-certificatesrenewosc-or-certificatesrenewoscext-(not-called-by-renewcertificate)>); **CertificateCanBeEdited/Deleted** ([SVC-BR-POL-022](<business-rules.md#svc-br-pol-022-edit%2Fdelete%3A-maintainothers(osc)-%3D-any-certificate%3B-maintainown(osc)-%3D-only-if-the-current-user-is-createuser-(case-insensitive)>)-%3D-any-certificate%3B-maintainown(osc)..[024](<business-rules.md#svc-br-pol-024-cutoff%3A-editable%2Fdeletable-only-while-createdate-%2B-9-days-(securityresources.maximumeditibledays-%3D-9)-%3E-today.-cutoff-date-%3D-today-%E2%88%92-9>)); **CertificateCanBeResent** is always true ([SVC-BR-POL-027](<business-rules.md#svc-br-pol-027-resend-is-always-allowed-(cancel-date-rule-is-commented-out)>); **CertificateOtherCoveragesCanBeMaintained** ([SVC-BR-POL-052](business-rules.md#svc-br-pol-052-other-coverages-editable-with-certificatesothercoverages-or-certificatesothercoveragesext)); **GetCertificateModificationCutoff** = today minus 9 days ([SVC-BR-POL-024](<business-rules.md#svc-br-pol-024-cutoff%3A-editable%2Fdeletable-only-while-createdate-%2B-9-days-(securityresources.maximumeditibledays-%3D-9)-%3E-today.-cutoff-date-%3D-today-%E2%88%92-9>)).

### PolicyAssociationService

`Policies/PolicyAssociation/PolicyAssociationService.cs` (`IPolicyAssociationService`). **GetPolicyAssociations(policy[, type])/GetAllPolicyAssociations** query `PolicyRelationship` (parent/member) and `Policy` (relationship and experience-rating associations); an unsupported association type throws "Invalid associationType for this method."

### PolicyInformationService and PayrollReportService

`Policies/Information/PolicyInformationService.cs` (`IPolicyInformationService`) and `Policies/PayrollReport/PayrollReportService.cs` (`IPayrollReportService`). **GetPolicyName, IsPolicyStateEntity** are `Policy` queries. **GetPayrollReport** delegates to `PolicyQueryProvider.GetPayrollReportForReportId`.

### OregonBusinessService

`Policies/DcbsOregonBusiness/OregonBusinessService.cs` (`IOregonBusinessService`; `IErrorConditionHandler`). **ProcessOregonBusinessFile(log, path)** parses the DCBS file, adds an `OregonBusiness` row per record, and throws "Error in Processing Oregon Businesses- review the log file" on any parse/save error.

### PolicyDocumentsApiService

`Policies/PolicyDocumentsApi/PolicyDocumentsApiService.cs` (`PolicyDocumentApi.IPolicyDocumentApiService`; `IPolicyDocumentsService`, `IPayrollReportService`, `IImagingClient`, `IPdfClient`).

- **GetPayrollReport(reportId)**: `ValidateReportId` (a null report or id 0 gives InvalidReportId, [SVC-BR-POL-080](business-rules.md#svc-br-pol-080-payroll-report-id-invalid-if-the-report-is-null-or-reportid-%3D-0)); `ValidatePayrollReportStatus` maps null docs to UnknownError, no docs to HistoricalReportNotFound, and any not-released to ReportNotReleased ([SVC-BR-POL-081](business-rules.md#svc-br-pol-081-null-docs-%E2%86%92-unknownerror%3B-no-docs-%E2%86%92-historicalreportnotfound%3B-any-not-released-%E2%86%92-reportnotreleased)); on failure returns an empty PDF; otherwise takes the first packet, fetches each image via `IImagingClient.GetPdf(DocTypeGroup.POLICY, id)`, and merges with `IPdfClient.Merge` (any exception returns an empty byte array).
- **AreDocumentsViewableToPolicyholder**: unless the policy status group is "APP" ([SVC-BR-POL-082](business-rules.md#svc-br-pol-082-policyholder-can-view-documents-unless-policystatusgroup-%3D-'app')). **GetPolicyholderViewableDocuments** delegates to `PolicyDocumentsService`. **HasValidCurrentUserAccess**: requires PolicyPayrollReportExternal, PolInfoMntUpdtExt, or the SAIF domain ([SVC-BR-POL-083](business-rules.md#svc-br-pol-083-api-access-if-policypayrollreportexternal%2C-polinfomntupdtext-or-domain-'saif')). **GetPolicyNumberForPayrollReport**: supporting lookup.

### PolicyDocumentsService

`OutputDocuments/PolicyDocumentsService.cs` (`IPolicyDocumentsService`; `IPolicyAssociationService`, `IPolicySecurityService`, `IPolicyQueryProvider`, `IImageQueryProvider`, `IPolicyOutputDocumentRepository`).

- **GetPolicy(policy, assocType)**: loads the policy and its non-application associations, filters with `PolicySecurityService.ValidatePoliciesForCurrentUser` ([SVC-BR-POL-076](business-rules.md#svc-br-pol-076-list-validation---any-other-user-type-gets-an-empty-result)); a single remaining association is displayed as "None" ([SVC-BR-POL-084](business-rules.md#svc-br-pol-084-a-single-validated-association-is-shown-as-none%3B-applications-are-excluded)); demo users see masked names ("Policyholder", "Policyholder Legal Name", "SAIF Appointed Agent") ([SVC-BR-POL-085](<business-rules.md#svc-br-pol-085-demo-users-see-masked-names-('policyholder'%2C-'policyholder-legal-name'%2C-'saif-appointed-agent')>).
- **GetPayrollReportDocuments/GetPayrollReportPackets**: `GetPayrollReportForReportId`, then `GetPolicyOutputDocumentsBySourceKeyText(packetSeq)`, then SAIF.Domain `PacketManager.CreatePackets`.
- **GetAgencyViewableDocuments/GetPolicyholderViewableDocuments (with/without category)**: released agency-viewable documents packeted, with the policyholder versions also keeping only `IsPolicyholderViewable`.
- **GetHeldAndPendingDocuments**: held-and-pending agency-viewable documents. **GetDocumentsForAgency(filter)**: cutoff = `DocumentSystemDate` minus `DaysBack` ([SVC-BR-POL-086](business-rules.md#svc-br-pol-086-inbox-release-cutoff-%3D-system-date-%E2%88%92-daysback)); packets a producer-filtered inbox, applies demo masking, adds LEASE ("Client of... #") / WRAP ("Subcontractor of... #") description prefixes ([SVC-BR-POL-087](business-rules.md#svc-br-pol-087-lease-%E2%86%92-'client-of---%23'%3B-wrap-%E2%86%92-'subcontractor-of---%23'-description-prefix)), and sorts newest-first.
- **AddDocument/AddPacket**: resolves the type/package code and adds a Pending `PolicyOutputDocument`. **DeleteDocument**: concurrency check ([SVC-BR-POL-088](business-rules.md#svc-br-pol-088-optimistic-concurrency---updatenumber-must-match)) then status Deleted. **HoldDocuments**: toggles Held/Pending, erroring "There are no document holds to..." when nothing changes ([SVC-BR-POL-089](business-rules.md#svc-br-pol-089-hold-toggles-held%2Fpending%3B-no-change-at-all-%E2%86%92-%22there-are-no-document-holds-to-save.%22)). **ReplaceDocument**: concurrency check; on failure adds a Deleted document for the replacement image and rethrows; on success marks the old document Deleted, adds the new one with the old status, and updates `OutputLog.ImageId`. **MarkAgencyDocumentViewed/MarkAgencyDocumentsDeleted/UnDeleted**: viewed flag and bulk delete/restore, erroring when nothing changes.
- **GetImage, GetDocument, GetDocumentsForPacket** (requires a released document, [SVC-BR-POL-090](business-rules.md#svc-br-pol-090-packet-requires-a-released-document%3A-%22%7Bid%7D-document-is-not-released%22%3B-include-child-documents-when-the-policy-is-its-own-relationship-parent)), **GetCategories, GetManualAddDocumentTypes, GetDocumentDate**: supporting reads.

### PolicyDocumentsBatchService

`OutputDocuments/PolicyDocumentsBatchService.cs` (`IPolicyDocumentsBatchService`; `ITaskFactory`, `IAgencyService`, `IEmailService`, `INoteService`).

- **ReleaseDocuments(reportOnly)**: releases pending documents whose `DocumentDate <= document system date` ([SVC-BR-POL-091](business-rules.md#svc-br-pol-091-release-pending-documents-with-documentdate-%E2%89%A4-document-system-date)), stamping `ReleaseDate`; generates direct-writer notifications only for paperless SAIF agencies ([SVC-BR-POL-092](business-rules.md#svc-br-pol-092-direct-writer-notifications-only-if-saif-agency-ispaperlessforpolicyholdercopyofdocuments)); `reportOnly` rolls back instead of committing and skips emails/updates ([SVC-BR-POL-093](business-rules.md#svc-br-pol-093-reportonly-means-rollback-and-no-emails%2Fupdates)).
- **GenerateAllExternalAgencyNotifications**: emails agency/producer lists for released, inbox-viewable, not-yet-viewed documents with an agency id (skipped under `reportOnly`).
- **RevertUserlessAgencies**: for each reverting agency, unsets paperless flags and sends an internal email (default calling user "SYSTEM"; skipped under `reportOnly`).
- **GenerateHoldTasks**: creates hold-notification tasks via `ITaskFactory`/`INoteService`.

### ViewDocumentService

`OutputDocuments/ViewDocumentService.cs` (`IViewDocumentService`; `IContextValidator`, `IPdfClient`, `IImagingClient`). **GetImageFile(imageId, packetId)**: throws `FileNotFoundException` if the document is missing; `ContextValidator.ValidatePolicyNumber` failures become `UnauthorizedAccessException` ([SVC-BR-POL-094](business-rules.md#svc-br-pol-094-policy-context%3A-the-policy-must-exist%2C-the-user-must-pass-policysecurity%2C-and-the-status-group-must-not-be-%22app%22)); fetches the PDF (single image or merged packet), retrying once if null.

### ContextValidator and ContextParser

`OutputDocuments/ContextValidator.cs` / `ContextParser.cs`. **ValidatePolicyNumber** implements the POL-094 policy-context check. **ValidateAgencyNumber** throws "InvalidAgencyContext" for a missing agency. **ContextParser.GetPolicyNumber/GetAgencyNumber** throw No/InvalidContext exceptions when the context is absent or malformed.

### Rct (Retroactive Coverage Transmission)

`Rct/RctPolicySelectedService.cs`, `RctPolicyToTransmitService.cs`, `RctServicePlanService.cs`, `RctStagingService.cs`, `RctXmlService.cs`.

- **RctPolicySelectedService**: Get/Insert/Modify CRUD on `RctPolicySelected`.
- **RctPolicyToTransmitService**: Get/RemoveAll/Remove/Insert/Update CRUD on `RctPolicyToTransmit`; **PolicySelectionAsync(reloadFlag, processDate)** calls Guidewire `ICCClaimApiService`.
- **RctServicePlanService.ProcessAllServicePlans(csv)**: for each row, the policy must exist ([SVC-BR-POL-100](business-rules.md#svc-br-pol-100-service-plan-policy-must-exist---'error---the-policynumber-'%7Bn%7D'-is-not-valid')) and the service-plan code must be valid or "NA" for removal ([SVC-BR-POL-101](<business-rules.md#svc-br-pol-101-service-plan-code-must-exist-in-rctserviceplancode-or-be-%22na%22-(removal)%3A-%22error%3A-the-serviceplancode-'%7Bc%7D'-is-not-valid%22>); `ProcessOneServicePlan` expires/inserts/ignores an `RctPolicyServicePlan` row; any per-row failure rolls back the entire batch ([SVC-BR-POL-102](business-rules.md#svc-br-pol-102-a-per-row-processing-failure-rolls-back-the-whole-batch)).
- **RctStagingService**: `LoadRctPolicyStagingData` adds staging policies/locations; `RemoveAllRctPolicyStagingData` deletes them; both accept `reportOnly`.
- **RctXmlService**: `GetXmlAndWriteXmlFilesOut` reads staging data and county codes then formats the policy XML; `GetRctXmlMemoryString` validates against the embedded `EvolutionSAIF.xsd`; `WriteXmlToFtpFolder` throws "Rct xml file write not successful..." on failure.

### Documents (generic template/request/distribution engine)

`Documents/DocumentBuildService.cs`, `DocumentDeferredService.cs`, `DocumentParameterService.cs`, `DocumentRequestService.cs`, `TemplateSearchService.cs`.

- **DocumentBuildService.GetRequestedDocument(request[, params])**: combines the request and parameter services, then renders through `IDocumentService`.
- **DocumentDeferredService**: `SheetCount` reads the output file under config `xpressionsoutputdocumentspath`; `GetCertifiedLabel` calls `ISimpleCertifiedMailServiceProxy` and copies `IsProductionMode`; `GetDocumentLabel`, `AddressErrorMessage` are supporting helpers.
- **DocumentParameterService**: gets template/request parameters; `StoreOriginalDocumentRequestParameters` adds a `DocumentRequestParameter` per template parameter (failure throws "Error: Store paramaters failed"); `SaveChangedDocumentRequestParameters` (failure throws "Error: Change paramaters failed"); `NeedVocCoordinators` applies when a parameter's DataType is "SELECTVOC" ([SVC-BR-DOC-010](business-rules.md#svc-br-doc-010-a-voc-coordinator-is-needed-when-a-request-parameter-has-datatype-'selectvoc')); `LoadClaimDataToRequestParameters` uses the claim unit of work.
- **DocumentRequestService**: `SubmitDocumentRequest` requires the template to exist ([SVC-BR-DOC-001](business-rules.md#svc-br-doc-001-template-must-exist---'error---template-does-not-exist.')) and adds a `DocumentRequestLog` (failure throws "Error: Store request failed"); `UpdateRequestStatus` validates against `DocRequestLogStatusCode` ([SVC-BR-DOC-002](business-rules.md#svc-br-doc-002-request-status-must-exist-in-docrequestlogstatuscode---'this-request-status-is-invalid---%7Bs%7D.')) and requires the request to exist ([SVC-BR-DOC-003](business-rules.md#svc-br-doc-003-request-must-exist---'this-document-request-does-not-exist%5B---id%5D.')); `SetRequestAsComplete` reuses the same existence check; distribution-log CRUD requires the distribution to exist ([SVC-BR-DOC-004](business-rules.md#svc-br-doc-004-distribution-log-must-exist---'this-document-distribution-does-not-exist---%7Bid%7D.')); `MarkDocumentRequestsInProcess`, `UpdateDocumentRequestAndDistribution`, `ResetDocumentRequest`, `IsWordDocument` (output profile "WORD") are supporting operations.
- **TemplateSearchService**: template searches are filtered by admin role, DocCenterDevAdmin/DocCenterProdAdmin seeing all templates and others a filtered set ([SVC-BR-DOC-020](business-rules.md#svc-br-doc-020-users-with-doccenterdevadmin-or-doccenterprodadmin-see-all-templates%3B-others-get-a-filtered-set)); `CreateTemplate/UpdateTemplate` validate and throw the first error ([SVC-BR-DOC-022](business-rules.md#svc-br-doc-022-create%2Fupdate-template-validation---the-first-error-message-is-thrown)); adds/deletes template output profiles and options; `DeleteTemplate` requires the template to exist ("That document template does not exist.", [SVC-BR-DOC-021](business-rules.md#svc-br-doc-021-template-must-exist---'that-document-template-does-not-exist.')) and cascades to distributions and request logs; `DisableTemplate`, `GetDocumentSqlLines` and code-table lookups round out the service.

## Collections, agencies, groups, imaging, and utilities

This area covers `Collections/**`, `Agencies/**`, `Groups/**`, `Imaging/**`, `OnlineSupport/**`, `OnlineMessages/**`, `Notifications/**`, `SystemUsers/**`, `Tasks/**` (collections/marketing/underwriting task factory), `Utilities/**` (including CleanAddress adapters, PDF tracking, Workfront), `AddressVerificationService/**`, `PositivePay/**`, `Corporate/**` and `Employees/**`. It implements the collections-case ledger (debtors, notes, tasks, transactions, interest, cash-receipts batch posting, SOIL department-of-revenue offsets), agency and group profile/imaging maintenance, imaging auto-import (IDX generation), MCO enrollment-document export, online-support error logging, online messages/tutorials, OnBase notification rule matrices, the Guidewire user-access extract, marketing/underwriting task creation, address cleansing/verification, PDF-tracking purges, IS/Workfront incident reporting, positive-pay file generation, purchase-order notifications, and the employee directory. Rule citations that use the [`SVC-BR-COL-*`](business-rules.md#collections) prefix link directly (numbers 050-056 and 060-064 are consolidated under the single [SVC-BR-COL-050](<business-rules.md#svc-br-col-050-to-056-task-generation-rules-(see-1.4)>) and [SVC-BR-COL-060](<business-rules.md#svc-br-col-060-to-064-soil-rules-(see-1.5)>) headings respectively). Many [`SVC-BR-MSC-*`](business-rules.md#miscellaneous) numbers cited in the source (roughly 030 and above) have no corresponding heading in [Business rules](business-rules.md); those behaviors are described here in prose without a bracketed rule citation.

### Collections: CollectionsService

`Collections/CollectionsService.cs` (`ICollectionsRepository`; extends `ValidatingServiceBase`). Every save runs through `ValidateAndAddErrors<T>` -> `IValidationService.Validate`.

- **GetCaseById/GetCaseSummaryById/GetRequestById**: loads `DataModel.Case` (a missing case yields an empty `Case`, or null for `GetCaseById` with `Id == 0`); summary variants add category totals and, for a policy case, the market type; `CaseType` is derived from the case code ([SVC-BR-COL-001](business-rules.md#svc-br-col-001-case-type-from-code---wrkr%3Dclaim%3B-foth%3Dclaimother%3B-thrd%2Fcthrd%3Dthirdparty%3B-pol%2Fcpol%2Ffpol-and-anything-else%3Dpolicy)); county is resolved via `GetCountyCodes()`.
- **GetCasesByReference(referenceId)**: a 7-or-8-character reference is shortened to 7 characters ([SVC-BR-COL-002](business-rules.md#svc-br-col-002-a-7-or-8-character-reference-is-shortened-to-7-characters)); for WRKR/FOTH/THRD/CTHRD codes it fetches the Guidewire claim synchronously to build a "claim# - Last, First Middle" description.
- **GetDebtors(caseId)/GetDebtor(id)**: `GetAdditionalDebtorData` resolves contact info by case type (ClaimOther, Claim via Guidewire worker address, Policy, ThirdParty); name/SSN fall back to the worker API when blank ([SVC-BR-COL-003](business-rules.md#svc-br-col-003-debtor-name-and-ssn-fall-back-to-the-worker-api-when-the-database-value-is-blank)); for POL/FPOL, the location defaults to the policy "POL" mailing location, showing "-- Invalid address --" when it is not an active policy location ([SVC-BR-COL-004](business-rules.md#svc-br-col-004-for-pol%2Ffpol%2C-debtor-locid-defaults-to-the-policy-%22pol%22-mailing-location.-a-non-active-location-shows-%22---invalid-address---%22)); `GetDebtors` masks the SSN to `xxx-xx-####` without LegalColUpdate ([SVC-BR-COL-005](business-rules.md#svc-br-col-005-ssn-is-masked-to-xxx-xx-%23%23%23%23-unless-the-user-has-legalcolupdate)).
- **GetDebtorName/GetPrimaryDebtorName/GetDebtorSsn**: repository read, falling back to the worker API when blank and `WorkerID > 0`.
- **CreateDebtor(caseId, edit, msgs)**: validates with `DebtorValidator` ([SVC-BR-COL-030](business-rules.md#svc-br-col-030-%22error%3A-first-name-is-required.%22-%2F-%22error%3A-last-name-is-required.%22-when-there-is-no-case-or-the-case-is-foth)..[036](business-rules.md#svc-br-col-036-'error---combined-text-may-not-be-more-than-4000-characters.')); adds a `Location` (code LGCOL, primary, uppercased state) and a `Debtor` linked to the case; if the statement is not held, a second transaction sets `LastStatementDate` to the first of the current month and recomputes the balance ([SVC-BR-COL-006](<business-rules.md#svc-br-col-006-when-a-statement-is-not-held-(or-is-released)%2C-laststatementdate-%3D-first-of-the-month-and-the-balance-comes-from-debtorlaststatementbalance(date-1)>).
- **SaveDebtor**: validates, updates or creates the Location, updates debtor fields, and recomputes the last statement date/balance when the statement-held flag toggles from true to false (same [SVC-BR-COL-006](<business-rules.md#svc-br-col-006-when-a-statement-is-not-held-(or-is-released)%2C-laststatementdate-%3D-first-of-the-month-and-the-balance-comes-from-debtorlaststatementbalance(date-1)>) rule).
- **UpdateDebtors(caseId, list)**: for POL/FPOL, a chosen location equal to the default mailing location is stored as null ([SVC-BR-COL-007](business-rules.md#svc-br-col-007-for-pol%2Ffpol%2C-choosing-the-default-mailing-location-stores-locid-as-null)); the first invalid debtor stops the batch with nothing committed.
- **DeleteDebtor**: deletes the debtor's Location (if any) then the Debtor.
- **Notes**: `GetNotes/GetNote` extend the thru-date to 23:59:59 and set `CanEdit` when today is within 2 working days of creation ([SVC-BR-COL-008](business-rules.md#svc-br-col-008-a-note-can-be-edited-until-2-working-days-after-it-was-created)); `SaveNote` appends "*ADDED {date} by {USER}*" for append text ([SVC-BR-COL-009](business-rules.md#svc-br-col-009-appended-note-text-gets-a-'added-date-by-user'-annotation)) and validates with `NoteValidator` ([SVC-BR-COL-033](<business-rules.md#svc-br-col-033-note---'error---text-is-required.'-(notetext-not-null)>)..[036](business-rules.md#svc-br-col-036-'error---combined-text-may-not-be-more-than-4000-characters.')); `CreateNote` validates then adds; `DeleteNote` deletes with no checks.
- **SaveRequest(caseId, Request, activities, msgs)**: `UpdateNum` concurrency check ([SVC-BR-COL-010](business-rules.md#svc-br-col-010-optimistic-concurrency-on-updatenum---'error---this-record-has-been-updated-by-someone-else.')); validates the request (`RequestValidator`, [SVC-BR-COL-037](business-rules.md#svc-br-col-037-request%3A-%22error%3A-collection-type-is-required.%22%2C-%22error%3A-case-status-is-required.%22%2C-%22error%3A-county-is-required.%22%2C-%22error%3A-credit-representative-is-required.%22%2C-%22error%3A-judgment-years-is-required.%22%2C-%22error%3A-interest-rate-is-required.%22)..[039](<business-rules.md#svc-br-col-039-%22error%3A-request-date-must-be-greater-than-1%2F1%2F1982-and-no-more-than-7-days-in-the-future.%22-(the-code-actually-uses-1980-01-01)>)) and each activity (`ActivityValidator`, [SVC-BR-COL-025](<business-rules.md#svc-br-col-025-activity%3A-%22error%3A-activity-date-must-be-greater-than-01%2F01%2F1982-and-no-more-than-7-days-in-the-future.%22-(exclusive-between-1982-01-01-and-today%2B7)>)..[027](<business-rules.md#svc-br-col-027-'error---activity-amount-must-be-between-zero-and-one-million.'-(exclusive)>); county code "C7" sets `State = "WA"`, otherwise "OR" ([SVC-BR-COL-011](business-rules.md#svc-br-col-011-county-c7-means-state-wa%2C-otherwise-or.-county-list-%3D-or-plus-synthetic-c7)); for a new case, sets the collection code and zero balance, then runs `GenerateDebtors` ([SVC-BR-COL-012](business-rules.md#svc-br-col-012-new-claim-case---one-debtor-linked-to-the-claim-worker)..[015](business-rules.md#svc-br-col-015-foth-cases-get-no-automatic-debtors.-generated-debtors-are-held-and-not-on-soil)).
  - **GenerateDebtors** (private): Claim cases get one debtor via the Guidewire claim's worker; Policy cases get one generic debtor (CPOL) or one per active `BusinessAffiliate` (other codes) ([SVC-BR-COL-013](business-rules.md#svc-br-col-013-new-cpol-case%3A-one-generic-debtor.-other-policy-codes%3A-one-debtor-per-active-type-%221%22-business-affiliate)); ThirdParty cases get debtors from `ThirdPartyCase` split by THRD (has a Person) / CTHRD (no Person) ([SVC-BR-COL-014](business-rules.md#svc-br-col-014-thrd---third-parties-that-have-a-person.-cthrd---third-parties-with-no-person)); FOTH cases get no automatic debtors, and generated debtors default to statement-held and excluded from SOIL ([SVC-BR-COL-015](business-rules.md#svc-br-col-015-foth-cases-get-no-automatic-debtors.-generated-debtors-are-held-and-not-on-soil)).
- **ValidateContext(caseType, context, out name)**: a "POL" type resolves `Policy.PolicyName` ("Invalid policy number." on any exception); otherwise resolves the Guidewire claim ("Invalid claim number." if the worker is null or an exception occurs) ([SVC-BR-COL-038](<business-rules.md#svc-br-col-038-%22error%3A-a-valid-policy-number-is-required.%22-(codes-containing-pol%2C-checked-with-validatecontext)%3B-%22error%3A-a-valid-claim-number-is-required.%22-(other-codes)>).
- **GetSearchResults(criteria)**: `_workerDataApiClient.FindClaims` when `PolicyName` is blank; wildcards `term%` (StartsWith) or `%term%` otherwise, with a missing first/last name becoming `%` ([SVC-BR-COL-016](business-rules.md#svc-br-col-016-search-wildcards%3A-startswith-%E2%86%92-x%25%2C-otherwise-%25x%25.-a-missing-first-or-last-name-becomes-%25)); results capped at 300 rows ([SVC-BR-COL-017](business-rules.md#svc-br-col-017-search-results-limited-to-300)).
- **Tasks**: `CreateTask` validates with `CollectionTaskValidator` ([SVC-BR-COL-028](<business-rules.md#svc-br-col-028-task---'error---a-valid-case-number-is-required.'-(case-must-exist)>)/[029](<business-rules.md#svc-br-col-029-%22error%3A-due-date-must-be-between-today-and-one-year-in-the-future.%22-(inclusive)%2C-plus-base-taskvalidator-rules-(svc-br-msc-060%2F061)>), sets `TaskUser = "SYSTEM"`, resolves `ContextKey` to PolicyNum or claim number, and stamps Source COLL/Category COLL/Action NONE/Priority MED ([SVC-BR-COL-018](business-rules.md#svc-br-col-018-manual-collection-tasks---taskuser-'system'%2C-coll%2Fcoll%2Fnone%2Fmed.-context-is-the-policy-or-the-claim)); `GetTasks` filters source COLL, not deleted, capped at the first 300 by date descending ([SVC-BR-COL-019](business-rules.md#svc-br-col-019-task-list---source-coll%2C-not-deleted%2C-first-300-by-date-descending)); `DeleteTask` soft-deletes; `SaveTask` updates date/text and validates; `ValidateCollectionTaskCriteria` requires `ThruDate >= FromDate`.
- **Transactions**: `GetTransactions` sets `CanEdit = false` when the transaction predates the last statement date, is referenced by a `PaymentRef`, has `PayDownOrder == 0`, or is system-generated ([SVC-BR-COL-020](business-rules.md#svc-br-col-020-a-transaction-can't-be-edited-if-it-is-on-or-before-the-last-statement%2C-has-a-paymentref%2C-has-paydownorder-0%2C-or-is-system-generated)); **InsertTransaction** validates with `TransactionValidator` (returns -1 if invalid), lowers the balance for `PayDownOrder == 0` (payment) or raises it otherwise ([SVC-BR-COL-021](<business-rules.md#svc-br-col-021-inserting-a-transaction-with-paydownorder-0-(payment)-lowers-the-balance%3B-any-other-category-raises-it>)), then in a separate transaction calls `CollectionsRepo.ApplyPayments(caseId, date)` ([SVC-BR-COL-022](<business-rules.md#svc-br-col-022-after-an-insert%2C-collectionsrepo.applypayments(caseid%2C-date)-runs>)%60-runs)); **SaveTransaction** checks `UpdateNum` concurrency, adjusts the balance by the delta when `CanEdit`, and clears `ApprovedByUser` when approval is not required ([SVC-BR-COL-023](business-rules.md#svc-br-col-023-approvedbyuser-is-cleared-when-the-code-doesn't-require-approval)); **DeleteTransaction** deletes and reduces the balance; **GetTransactionCodes** sets `IsInterestAllowed = PayDownOrder != 0` ([SVC-BR-COL-024](business-rules.md#svc-br-col-024-interest-allowed-only-when-paydownorder-%E2%89%A0-0)).
- **Lookup lists**: `GetActivityCodes` (with a "--Select--" option), `GetCountyCodes` (Oregon counties plus synthetic "C7" out-of-state/WA), `GetCreditReps`/`GetWriteOffApprovalUsers` (non-expired only). **IsUpdateUser**: `IsAuthorized("LegalColUpdate")`.

### Collections: CalculateInterestService

`Collections/CalculateInterestService.cs` (`ICalculateInterestService`). **CalculateInterest(runDate, caseId, reportOnlyMode, controlLog, interestReport, emailList)**: computes interest via `CollectionsRepo.GetInterest(caseId, runDate)` ([SVC-BR-COL-045](business-rules.md#svc-br-col-045-interest-%3D-amount-returned-by-getinterest.-it's-added-to-the-balance-as-an-int-system-transaction-ref-%22lcb00003%22-dated-rundate)); always sends the report first (PDF package "Claims_Batch_LCB0003", emailed from `Helpdesk@saif.com` to the given recipients, even in report-only mode, [SVC-BR-COL-046](<business-rules.md#svc-br-col-046-interest-report-(pdf-lcb0003)-is-always-emailed%2C-including-in-report-only-mode%2C-before-posting>)); when not report-only, adds the interest amount to the case balance and creates an `INT` "Interest Charge" transaction (`IsSystemGenerated = true`) per row, then commits; always returns true.

### Collections: CashReceiptsService

`Collections/CashReceiptsService.cs` (`ICashReceiptsService`). **ApplyCashReceipts(runDate, controlReport, controlLog, emailList)** returns 0, 5 or 6: no unbalanced `BatchControl` rows for the run date returns 6 without posting ([SVC-BR-COL-040](business-rules.md#svc-br-col-040-no-unbalanced-batches-for-the-run-date-%E2%86%92-report-only%2C-return-code-6)); the first batch that fails to balance returns 5 with no report ([SVC-BR-COL-041](business-rules.md#svc-br-col-041-any-batch-that-doesn't-balance-%E2%86%92-return-code-5)). **ProcessBatch**: adds a Transaction per `CashReceipts` row (code = receipt type, "Payment", user "LCB0009"), reduces the case balance, and marks the receipt processed; a count/total mismatch against `BatchCount`/`BatchAmount` rolls back and returns false ([SVC-BR-COL-042](business-rules.md#svc-br-col-042-a-batch-must-match-batchcount-and-batchamount%2C-or-its-postings-are-rolled-back)); on success, calls `ApplyPayments(caseId, batchDate)` for each distinct affected case ([SVC-BR-COL-043](<business-rules.md#svc-br-col-043-each-receipt-creates-a-system-%22payment%22-transaction-(user-lcb0009)%2C-lowers-the-balance-and-is-marked-processed-y.-the-batch-is-marked-balanced-y>). **GenerateReport** groups receipts into Fraud (WRKR/FPOL/FOTH) and Third-party (THRD/CTHRD) totals ([SVC-BR-COL-044](<business-rules.md#svc-br-col-044-report-groups---fraud-(wrkr%2Ffpol%2Ffoth)-and-third-party-(thrd%2Fcthrd)>)); PDF package "Claims_Batch_LCB0009", emailed as "CashReceipt.pdf".

### Collections: GenerateTasksService

`Collections/GenerateTasksService.cs` (`IGenerateTasksService`). **BatchTasks(prevWorkDay, ...)** runs, in order: `RemoveExitingTasks` (soft-deletes today's own tasks to prevent rerun duplicates); `PolicyPaymentTasks` (missing policy payments, [SVC-BR-COL-050](<business-rules.md#svc-br-col-050-to-056-task-generation-rules-(see-1.4)>); `ThirdPartyPaymentTasks`; `DebtorsWithExpiredLocTasks`; `BalanceAtMinimumTasks` (balance brought to the minimum threshold, ~$100*); `JudgmentExpiringTasks` (judgment-years cases nearing expiry, filtered in memory because the LINQ provider does not support the date computation). `SetCommonPropteryValues` stamps `TaskUser` (case credit rep), `TaskDate = Today`, `ContextKey` (PolicyNum or SAIF claim number), Source COLL/Category COLL/Action NONE/Priority MED (all rolled up under [SVC-BR-COL-050](<business-rules.md#svc-br-col-050-to-056-task-generation-rules-(see-1.4)>).

### Collections: SoilService

`Collections/SoilService.cs` (`ISoilService`; `IWorkerDataApiClient`). **LoadDeptRevenueSoilBalance(fileName, ...)**: skips lines <= 60 characters; parses fixed-position SSN/name/reference/balance fields; merges duplicate SSNs by summing balances and concatenating reference text ([SVC-BR-COL-060](<business-rules.md#svc-br-col-060-to-064-soil-rules-(see-1.5)>); inserts into `DeptRevenueSoilBalance`. **UpdateSoilDebtorReference(runDate)**: stages worker addresses in `TempDataPersonAddress`, calls `CreateSoilDebtorReferenceRecords` (stored procedure), then clears the temp rows. **GenerateSoilAdjustments(runDate)**: the same temp-table pattern with `CreateSoilAdjustmentRecords`, then tallies `DeptRevenueSoilAdjustment` rows by transaction code (A=add, C=change, D=delete). **CreateSoilFile(runDate, fileName)**: writes a fixed-format ASCII CSV ordered by SSN with program code "879", a fixed interest block, and a placeholder address, stripping commas from name parts (all under the consolidated [SVC-BR-COL-060](<business-rules.md#svc-br-col-060-to-064-soil-rules-(see-1.5)>) heading).

### Collections validators

`Collections/Validators/ActivityValidator.cs`, `CollectionTaskValidator.cs` (extends `Tasks/TaskValidator<T>`), `DebtorValidator.cs`, `NoteValidator.cs`, `RequestValidator.cs`, `TransactionValidator.cs` implement the field-level rules cited above ([SVC-BR-COL-025](<business-rules.md#svc-br-col-025-activity%3A-%22error%3A-activity-date-must-be-greater-than-01%2F01%2F1982-and-no-more-than-7-days-in-the-future.%22-(exclusive-between-1982-01-01-and-today%2B7)>)..[039](<business-rules.md#svc-br-col-039-%22error%3A-request-date-must-be-greater-than-1%2F1%2F1982-and-no-more-than-7-days-in-the-future.%22-(the-code-actually-uses-1980-01-01)>)).

### Agencies: AgencyService

`Agencies/AgencyService.cs` (`IAgencyService`; `IDocumentService`, `IEnvironmentService`, `IImagingClient`; mapping in `Agencies/AgenciesMappingHelper.cs`).

- **GetAgency(id)**: maps latest contract description, paperless status (Paperless/Pending/Papered), agency agent vs. producers, underwriter (team function with `UnderwriterCode`), title-cased names, and `IsActive` (no expiration date); demo users see producer/agent names masked as "Producer" ([SVC-BR-MSC-001](business-rules.md#svc-br-msc-001-demo-users-see-producer-and-agent-names-as-'producer')).
- **GetAgencySearchResults(term, maxHits)**: matches id or name; with `maxHits > 0`, orders active-first, then starts-with, then contains, then name ([SVC-BR-MSC-002](business-rules.md#svc-br-msc-002-search-result-ordering)).
- **GetPaperlessAgencyIds/GetRevertingAgencies**: paperless agencies with no active users are "reverting" ([SVC-BR-MSC-003](business-rules.md#svc-br-msc-003-paperless-agency-with-no-active-external-users-%E2%86%92-reverting)).
- **GetAgencyActiveUserStatus(agencyId)**: id 0 is always true; otherwise checks for a non-deleted `ExternalUserType` matching the customer group, excluding test/production-test users in production ([SVC-BR-MSC-004](business-rules.md#svc-br-msc-004-in-production%2C-test-users-and-production-test-users-don't-count-as-active)).
- **GetEmailListForAgencyNotification/GetEmailListForProducerNotification**: only active, non-blank, non-deleted, opted-in emails ([SVC-BR-MSC-005](business-rules.md#svc-br-msc-005-notification-emails-only-for-opted-in%2C-active%2C-non-deleted-users-with-an-email-address)).
- **UpdateEmailNotification**: creates/deletes a `DocumentNotification`; returns false if already in the requested state. **UpdatePaperlessRequest/UpdateAgencyPaperless**: sets `AgencyPaperless` flags with stamps.
- **ProcessConfirmationEmail(agencyId, pdfPath, fileName)**: emails the PDF from `NoReply@saif.com`, images the email (package "General_Image_EmailNotification", category "AG - ADMINISTRATION"), and uploads the agreement PDF via `ImagingApiClient.Upload` ([SVC-BR-MSC-006](business-rules.md#svc-br-msc-006-paperless-confirmation%3A-email-plus-image-of-the-email-plus-image-of-the-agreement-in-agency-%2F-%22ag---administration%22)).

### Agencies: Agencies.Imaging.ImageService

`Agencies/Imaging/ImageService.cs` (`IAgencyImageService`; security levels 0 = non-confidential, 50 = confidential).

- **AddImage**: links an `ImageAgency` to an existing agency and image ("Error: Agency not be found."/"Error: Image not be found."). **DeleteImage**: soft delete, no permission check.
- **EditImage**: requires `CanUserEditImages` ([SVC-BR-MSC-011](business-rules.md#svc-br-msc-011-editing-an-image-needs-agencyimagingedit)); may move the image to another agency; validates with `ImageEditValidator` ([SVC-BR-MSC-007](business-rules.md#svc-br-msc-007-agency-image-description-%E2%89%A4-256%3A-%22error%3A-description-may-not-be-more-than-256-characters.%22%3B-%22error%3A-category-is-required.%22%3B-%22error%3A-type-is-required.%22)); the document type must match the agency file code/category/type ("Error: Type not found."); sets `SecurityLevel` to 50 or 0.
- **GetImage/GetImages**: filtered on deleted/confidential flags ([SVC-BR-MSC-010](business-rules.md#svc-br-msc-010-image-security-filter---deleted-images-need-agencyimagingdeleted%3B-securitylevel-%E2%89%A0-0-needs-agencyimagingconfidential)); `ShowDeleted` is forced false without the delete role. **GetImageFile**: `IImagingService.GetPDF(imageId)`. **GetCategories**: `ImageCategoryCode` for the agency file code.

### Groups: GroupService

`Groups/GroupService.cs` (`IGroupService`). Read-only access to `GroupAssociation`: search list optionally filtered by acronym/name (case-insensitive contains); `GetGroupHeader(acronym)` formats the phone number and sets `IsParent` from `GroupType.ParentType`; `GetGroup(id)`.

### Groups: Groups.Imaging

`Groups/Imaging/ImageService.cs` (`IImageService`; `IGroupImageSecurityProvider`, `IGroupImageRepository`, `IImageCategoryCodeRepository`, `IImageUpdater`, `IImageDeleter`).

- **GetImages**: adds `CanUserSeeConfidentialImages`/`CanUserSeeDeletedImages` to the filter, then delegates security filtering to `IGroupImageRepository.GetFilteredImageGroups` ([SVC-BR-MSC-020](business-rules.md#svc-br-msc-020-group-image-list-is-filtered-by-the-confidential-and-deleted-roles)). **GetImage/GetImageOrDefault**: call the unfiltered `GetImageGroup`/`GetImageGroupOrDefault` (not security-filtered**). **UpdateImage/DeleteImage**: wrap the updater/deleter in Begin/Commit. **GetCategories**: `GetImageCategoryCodes()`.
- **ImageUpdater**: validates ([SVC-BR-MSC-021](<business-rules.md#svc-br-msc-021-'error---user-not-authorized-to-edit-images.'-(groupimagingedit)>)/[023](<business-rules.md#svc-br-msc-023-'error---image-updated-by-another-user.'-(updatedate-must-equal-the-stored-value)>)/[024](business-rules.md#svc-br-msc-024-%22error%3A-description-may-not-be-more-than-%7Bimage.maxdescriptionlength%7D-characters.%22%3B-%22error%3A-category-is-required.%22%3B-%22error%3A-type-is-required.%22%3B-%22error%3A-type-not-found.%22%3B-%22error%3A-group-could-not-be-found.%22)), then sets the group, date, text, document type and `SecurityLevel` (Confidential/NonConfidential constants). **ImageDeleter**: validates, then `IGroupImageRepository.Delete` with `ImageDelete{ImageGroupId, UpdateDate, UserId}` -- delete does not require the edit or delete role ([SVC-BR-MSC-025](<business-rules.md#svc-br-msc-025-delete-doesn't-require-the-edit-or-delete-role-(the-validator-only-checks-that-the-image-exists)>). **GroupImageSecurityProvider**: resolves the `IsAuthorized` role checks.

### Imaging: AutoImportHelperService

`Imaging/AutoImportHelperService.cs` (`IAutoImportHelperService`; `IClaimInformationService`, `IConfigurationService`, `IPolicyQueryProvider`, `IClaimQueryProvider`).

**GenerateIdxFile(details)**: loads `AutoImportIndexDefaults` by `DocDescriptionCode` (case-insensitive), throwing "No Auto Import Index File defaults were found." when none exist**; `TaskDate` is the default date if in the future, else Today; `CreateUser` is the uppercased source; an `LPTCode` replaces the reference with the claim number plus check character via `GetClaimFromInjuryClaimCrossReference` (throws if missing)**; `LoadPolicyOrClaimInfo` resolves CLAIM via `IClaimInformationService.GetClaimDetail` or POLCY via `IPolicyQueryProvider.GetPolicy`, throwing for any other file code**; `LoadTaskMailIDList` resolves ADJUSTER/LEGAL/REQUESTER/VOC/other sources to mail ids, with VOC unimplemented**; `LoadDocumentDate` resolves DOI (claim injury date, required), POLICY-DEFAULT (05/31/2007), or the file-name date, rejecting dates more than 7 days in the future**; writes `KEY=value` lines to `<idmfilepath>\<name>.idx` with `NPages` fixed at 1. (These IDX-generation and file-routing behaviors are described in prose because the source's `SVC-BR-MSC-030`+ citations have no corresponding heading in [Business rules](business-rules.md).**)

### Imaging: ImportPreProcessor

`Imaging/ImportPreProcessor.cs` (`IImportPreProcessor`). **ProcessFile(fileInfo)**: strips U+200C from the file name; requires 3 or 4 underscore-separated segments (CLAIM_DESC_DATE_UNIQUE.ext, date optional), otherwise a format error**; parses ten accepted date formats, and a trailing `NofM` segment appends " (N of M)" to the comment; copies the file to the auto-import path with an epoch-timestamp suffix, generates the IDX, and moves the source to `complete\`; on failure, deletes the copy and routes "MAJORIS-MEDICAL-" files with a "no defaults found" error to `UndefinedDocument\`, otherwise to `error\`, then rethrows; moves never overwrite (append `(n)`).**

### Imaging: McoImageExporter and MajorisImageExporter

`Imaging/McoImageExporter.cs` (abstract) and `MajorisImageExporter.cs` (`IMcoImageExporter`; `IClaimDataApiClient`, `IFileSystem`).

- **SetWorkingDirectory**: the root must exist; creates `<root>\Majoris`.
- **ExportActiveEnrollmentDocuments**: gets active MCO claims (Majoris via `GetGWActiveMCOClaims`), fetches images by `DocTypeGroup.CLAIM`, keeps images whose claim is still active, renders each with `GetPdf` and writes the file**; a config-key overload reads a JSON `{Type, SubTypes[]}` list from `IEnvironmentService`.
- **ExportNewEnrollmentDocuments(date)**: claims with a matching `mcoEffectiveDate`, images fetched claim by claim. File name pattern: `{claim+check}_{DOCTYPE}_{SUBTYPE}_{date-or-01-01-1970}_{00000}.pdf`.
- **InsertErrorImage/TranslateError**: adds a `DocumentImagingError` for a new failure (timeout -> "PDF Timed out, file too large Error"; a `NoMatchingDataException` with image id 0 -> "TOWID/ISN Error"; otherwise trimmed to 300/100/999 characters)**. **ExportErrorEnrollmentDocuments**: retries each error row via `GetImageByDocHandle`/`GetPdf`, writes the file, deletes the error row on success, and reports resolved vs. current errors.

### OnlineSupport

`OnlineSupport/ErrorLogs/ErrorLogService.cs` (`IErrorLogService`), `EnvironmentManager` (`IEnvironmentManager`), `OnlineSupport/Repositories/ErrorLogRepository.cs` (`IErrorLogRepository`), `OnlineSupportRepository`.

- **ErrorLogService**: exposes environment groups, `FilterTimeRange` values and application names; queries delegate to the repository. **EnvironmentManager**: group code "O" returns all environments; other codes return that group's environments. **EnvironmentOptions**: hard-coded D/T/Q/P/L/O options with environment-name search strings (netprod, webprod, netqa, nettest01/02, netdev01/02, localhost, tfs, inetpub, "program files (x86)", others).
- **ErrorLogRepository.GetErrorLogs**: an `ErrorId` takes priority; environment "O" excludes the list, other codes include it (blank excluded); filters on application, time range (2h/today/24h/week/month/range with thru+1 day), user, and a substring search in state or error info; with no criteria it effectively returns nothing (`ErrorId 0` lookup)**. Long error text (>100 characters) is split on a marker string or truncated; `ErrorInformation` gets an "Id:" prefix. (These behaviors correspond to the source's `SVC-BR-MSC-040` citation, which has no matching heading in [Business rules](business-rules.md).**)

### OnlineMessages: OnlineMessagesApiService

`OnlineMessages/OnlineMessagesApiService.cs` (`IOnlineMessagesApiService`). The constructor defers a query of active `OnlineMessages` (`StartDate <= Now <= TerminationDate` or null).

- **UserHasAccessToMessage(userType, priority)**: not-in-AD users with type All and Urgent priority get access; employees or type All get access; non-employees with type SAIFEmployee are denied; otherwise `IsAuthorized(userCode.SecurityGroupName)` from the database resource name.**
- **GetMessagePriorities**: "tutorial" maps to "low"; an unknown value defaults to urgent/high/medium.
- **GetMessagesForUser/GetTutorialList**: filters by permitted user type, priority list, and application (`{app, "all"}` unless "none"); orders newest-first; shows Spanish text when present unless the language is "en".** An unknown user type falls back to "all". (These `SVC-BR-MSC-041`/`042`-cited behaviors have no matching heading in [Business rules](business-rules.md).**)

### Notifications: NotificationService

`Notifications/NotificationService.cs` (`INotificationService`; entity `OnBaseNotificationRuleMatrix`). **GetNotificationMatrixRules(docType, subtype)**: doc type compared case-insensitively; a null/empty subtype matches rows with no subtype, otherwise an exact case-insensitive match**; maps alert flags for Legal, CBP, EAIP, Agency, CreditColRep, Legcol, OSHA, PLUS, PWP, Underwriter. (Cites `SVC-BR-MSC-043`, not present in [Business rules](business-rules.md).**)

### SystemUsers: UserExtractService

`SystemUsers/UserExtractService.cs` (`IUserExtractService`).

- **UpdateUserAccess**: for each `SystemUserRef`, checks `IsAuthorized` for PolicyCenter/BillingCenter/ContactManager; updates changed flags with user "GWI0001U" and returns the changed count.**
- **ExtractUsers(file, log)**: includes a user when `UpdateDate > LastExtractDate`, or when never extracted but has an update date and any Guidewire access**; sources values from `SystemUser`/`WcisEmployee`/inactive default (no employee id) or `AdpEmployeeFeed` (active when status "A", skipped if not found); email falls back to `<id>@saif.com`; writes XML with string surgery (`Remove(39,117)`) and stamps `LastExtractDate` with user "GWI0001X". (Cites `SVC-BR-MSC-044`/`045`, not present in [Business rules](business-rules.md).**)

### Tasks: TaskFactory and validators

`Tasks/TaskFactory.cs` (`ITaskFactory`). **CreateMarketingTask/CreateUnderwritingTask**: validates (throwing `SaifValidationException` on failure); resolves category "MA"/"U" with priority/source/action from category defaults**; resolves the policy via `PolicyQueries.GetPolicy`; adds the task (no unit-of-work Begin/Commit; the caller is responsible*). **TaskValidator&lt;T&gt;**: due date required and must be a working day; text required, <= 4000 characters.** **CreatePolicyTaskRequestValidator**: adds "Invalid Policy Number." plus the same due-date/text rules.** (Cites `SVC-BR-MSC-050`/`060`/`061`/`062`, not present in [Business rules](business-rules.md).**)

### Utilities: general helpers, CleanAddress adapters, PDF tracking, Workfront

- **ValidatorFactory**: Autofac resolution of `IValidator<T>`. **EnumerableExtensions.ChunkBy**: chunk size must be > 0. **PhoneNumberFormatter/PhoneNumberParser**: 10 digits formatted `AAA.PPP.NNNN xEXT`.** **Utilities/Edi/EdiFileService**: stores EDI segments/elements (composite children inherit the parent's index) and deletes files.
- **CleanAddress adapters** (built on `CleanAddressServiceBase`, disposing the `CleanAddressSvc.clsCAServices` proxy): `AddressSearchService.FindCityStateByZip` parses fixed 55-character records (city 28, state 2, county 25); `CleanAddressBatchService.RunCleanAddressBatch` maps the return code to `BatchReturnType` or Unknown; `CleanAddressService.ConvertToProperCase`; `CleanAddressService.CleanAddress` (25 ref outputs, exception sets return code -999). Result classification: clean = RC 1, or RC -1 without error code "11"; unmatched = RC -1/-99/-999; multiple matches = neither clean nor unmatched; POSTNET barcode = zip + zip4(if 4 chars) + DPC(if 2 chars) + check digit.** `CleanAddressErrorHelper` parses 2-character USPS codes (00-81) against a description table.
- **PdfTrackingService** (`Utilities/Pdf/PdfTrackingService.cs`, `IPdfTrackingService`): lookups on `PDFTracking`/`PDFStatusCode`/`PDFApplicationErrorCode`/`PDFTrackingCode`. **Purge**: a record qualifies when `PurgeDate` is between `MinValue` and cutoff, or `CreateDate` is older than cutoff-30 days, optionally filtered by application code or file id**; `GetCutoffDate` caps the cutoff at Now; file deletion under `{pdfDataDirectory}\{TrackingCode.Description}\{orig|out|err}\...` only runs when `commit` is true (otherwise it is a preview); database-record deletion and orphaned-folder purge are commented out/dead code in the current source.*
- **ISRequestService** (`Utilities/Workfront/ISRequestService.cs`, `IISRequestService`): help-desk address is `HelpDesk@saif.com` in production, else the current user's email**; `GetRequestLists` calls the Workfront API (`WorkRequest/ApplicationLists`) with default credentials; `SendIncidentReport` sends an HTML email ("Report a problem" with urgency for type P, else "How to question"), CC'ing the user and attaching request files.

### AddressVerificationService

`AddressVerificationService/AddressVerificationService.cs` (`IAddressVerificationService`; `IAddressVerficationQueryProvider`, `IProviderQueryProvider`, `IVerifiedAddressProviderMatchService`, `IProviderDataApiClient`).

- **StatelessProcessProviderAddressesForUpdates(lastProcessDate, ...)**: for each active provider address needing an update, gets a verified address (`GetLocationVerificationRequests` -> `GetAddressVerificationRequests` -> `GetVerifiedAddress`); when the update is valid, expires the old `BatchProviderAddress` and inserts a new one effective tomorrow (city <= 25 characters, country US, not foreign), then updates Guidewire via `IProviderDataApiClient.UpdateProviderAddressAsync`/`AddProviderAddressAsync`, mapping PHY->Business, COR->Mailing, PAY->Billing, with `isPrimary` true for PHY always, COR when there is no PHY, and PAY when there is no COR** ; on API failure it manually restores the old row and deletes the new one, then rethrows.
- **Match rules**: an exact match on all fields needs no update; an update is triggered when line 1 differs only by a PO-box variant or matches the first 3 characters (with line 2/city/state matching), or only city/state/zip/zip4 differs; otherwise the address is written to the unchanged report as a Failure** ; the provider zip is left-padded to 5 digits, comparisons are trimmed with null treated as equal to null. (These `SVC-BR-MSC-067`/`068`-cited rules have no matching heading in [Business rules](business-rules.md).**)

### BatchProcessingService, PositivePayService, PurchaseOrderService, EmployeeDirectoryService

- **BatchProcessingService**: gets or updates `BatchProcessLog.RunDate` by module name, returning a default when no row exists.
- **PositivePayService** (`PositivePay/PositivePayService.cs`): newly issued checks (`IssueDate == date`, reconciliation status in {1,3,4,5,6}, reconciliation date 1700-01-01) and newly canceled checks (reconciliation date == date, status in {3,4,5,6}); detail record code "01", totals record "02" (count + summed amount); written to `{path}\{file}.prn` via `FileWriter<PositivePayView>`.** (Cites `SVC-BR-MSC-069`, not present in [Business rules](business-rules.md).**)
- **PurchaseOrderService** (`Corporate/PurchaseOrderService.cs`; `IDocumentService`): selects POs that are issue-pending/not closed/not canceled, or matching a requested id substring; comment text is everything after the 3rd comma; PDF package "Fin_OLCA_VendPO", emailed from `helpdesk@saif.com` (To/CC split on ";"), and also written to `DocumentStore` as `PurchaseOrders-{s}.pdf`.** (Cites `SVC-BR-MSC-070`, not present in [Business rules](business-rules.md).**)
- **EmployeeDirectoryService** (`IRepository<Employee>`): active employees (status "A"); `GetEmployee` also returns direct reports and peers; `EmployeesMappingHelper` derives `PolicyUser` email as `<Id>@saif.com`.

> **Note:** Statements marked with * were inferred from code structure, naming conventions, or
> contextual evidence where direct confirmation was not available in source artifacts. Statements
> marked with ** could not be confirmed; treat as unknown until verified.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [Database tables](database-tables.md)
- [SQL catalog](sql/index.md)
- [API reference](api/index.md)

## Sources

- `Services/SAIF.Services/Claims/Bills/BillService.cs`, `BillEntryService.cs`, `BillDiagnosisService.cs`, `BillProcedureService.cs`, `BillServiceService.cs`, `AuthorizationService.cs`, `NoteService.cs`, `BillQueryService.cs`
- `Services/SAIF.Services/Claims/EBilling/` (EbillingService and imaging-related services)
- `Services/SAIF.Services/Edi/` (Edi override/report services)
- `Services/SAIF.Services/Claims/ICDCodeService/Icd10CodeService.cs`
- `Services/SAIF.Services/Claims/Payments/FinancialTransactionService.cs`
- `Services/SAIF.Services/Utilities/Edi/EdiFileService.cs`
- `Services/SAIF.Services/Claims/Authority/ClaimAuthorityService.cs`
- `Services/SAIF.Services/Claims/Information/ClaimInformationService.cs`, `ClaimContactService.cs`, `ClaimCustomRepoService.cs`, `MissingTimeLossService.cs`, `Api/ClaimInformationApiService.cs`
- `Services/SAIF.Services/Claims/Medicare/MedicareService.cs`
- `Services/SAIF.Services/Claims/SeverityModel/SeverityModelActivityService.cs`
- `Services/SAIF.Services/Claims/Tasks/ClaimTaskGenerationService.cs`, `TaskService.cs`
- `Services/SAIF.Services/Claims/Notes/ClaimNotesService.cs`, `NotePreferenceConversionService.cs`
- `Services/SAIF.Services/Claims/RTW/RTWService.cs`
- `Services/SAIF.Services/Claims/SecurityOverride/SecurityOverrideService.cs`
- `Services/SAIF.Services/Claims/PharmacyElilgbility/PharmacyEligibilityService.cs`
- `Services/SAIF.Services/Claims/Reports/ClaimReportingService.cs`
- `Services/SAIF.Services/Security/ClaimSecurityService.cs`
- `Services/SAIF.Services/Investigations/FraudInvestigationService.cs`
- `Services/SAIF.Services/Claims/ClaimDomainBuilder.cs`
- `Services/SAIF.Services/Legal/LegalService.cs`, `CaseCostService.cs`
- `Services/SAIF.Services/Legal/Bills/BillService.cs`, `BillSaveService.cs`, `BillRateService.cs`, `BillPaymentService.cs`, `BillServiceHelper.cs`
- `Services/SAIF.Services/Legal/Notes/LegalNotesService.cs`
- `Services/SAIF.Services/Legal/Subpoenas/SubpoenaService.cs`, `SubpoenaSaveService.cs`, `SubpoenaPaymentService.cs`, `SubpoenaDocumentService.cs`, `SubpoenaLabelService.cs`, `ZeroPaySubpoenaProcess.cs`, `WitnessSubpoenaProcess.cs`, `OpposingCounselService.cs`
- `Services/SAIF.Services/Legal/Tasks/LegalTaskService.cs`
- `Services/SAIF.Services/Policies/Certificates/` (CertificateService, CertificateSecurityService, CertificateHolderService, CertificatePreferenceService, CertificateDistributionService, DocumentService, ReportingService, Information/CertificateInformationService)
- `Services/SAIF.Services/Policies/SecurityServices/PolicySecurityService.cs`
- `Services/SAIF.Services/Policies/PolicyAssociation/PolicyAssociationService.cs`
- `Services/SAIF.Services/Policies/Information/PolicyInformationService.cs`, `Policies/PayrollReport/PayrollReportService.cs`
- `Services/SAIF.Services/Policies/DcbsOregonBusiness/OregonBusinessService.cs`
- `Services/SAIF.Services/Policies/PolicyDocumentsApi/PolicyDocumentsApiService.cs`
- `Services/SAIF.Services/OutputDocuments/` (PolicyDocumentsService, PolicyDocumentsBatchService, ViewDocumentService, ContextValidator, ContextParser)
- `Services/SAIF.Services/Rct/` (RctPolicySelectedService, RctPolicyToTransmitService, RctServicePlanService, RctStagingService, RctXmlService)
- `Services/SAIF.Services/Documents/` (DocumentBuildService, DocumentDeferredService, DocumentParameterService, DocumentRequestService, TemplateSearchService)
- `Services/SAIF.Services/Collections/` (CollectionsService, CalculateInterestService, CashReceiptsService, GenerateTasksService, SoilService, Validators/)
- `Services/SAIF.Services/Agencies/AgencyService.cs`, `Imaging/ImageService.cs`
- `Services/SAIF.Services/Groups/GroupService.cs`, `Imaging/ImageService.cs`
- `Services/SAIF.Services/Imaging/` (AutoImportHelperService, ImportPreProcessor, McoImageExporter, MajorisImageExporter)
- `Services/SAIF.Services/OnlineSupport/` (ErrorLogs/ErrorLogService, Repositories/ErrorLogRepository)
- `Services/SAIF.Services/OnlineMessages/OnlineMessagesApiService.cs`
- `Services/SAIF.Services/Notifications/NotificationService.cs`
- `Services/SAIF.Services/SystemUsers/UserExtractService.cs`
- `Services/SAIF.Services/Tasks/TaskFactory.cs`
- `Services/SAIF.Services/Utilities/` (ValidatorFactory, EnumerableExtensions, PhoneNumberFormatter/Parser, CleanAddress adapters, Pdf/PdfTrackingService, Workfront/ISRequestService)
- `Services/SAIF.Services/AddressVerificationService/AddressVerificationService.cs`
- `Services/SAIF.Services/PositivePay/PositivePayService.cs`
- `Services/SAIF.Services/Corporate/PurchaseOrderService.cs`
- `Services/SAIF.Services/Employees/EmployeeDirectoryService.cs`

