Run lifecycle¶
A run of SAIF.Policy.RenewalNotice starts in Main in Program.cs, which does
nothing but call Program.Run() on the SAIF.Framework.Windows runner. The
runner builds the Arguments container from the command line, resolves and
validates it, registers the application's dependencies in an Autofac container,
and constructs RenewalNoticeApplication. The application then runs
Initialize to acquire its configuration, database session, and API client,
Execute to generate and queue the notice batch, and Finish to write totals
and send the operations summary. Every exit path ends with the framework
disposing the container, writing the footer, and echoing Exit Code: n as the
final line of console output.
Execution flow¶
flowchart TD
MAIN[Main] --> RUN["Program.Run()"]
RUN --> ARGS["CreateArgumentContainer(args)"]
ARGS --> RESOLVE["Arguments.ResolveArguments()"]
RESOLVE -->|Invalid| FAIL["Write failures to Console.Error<br/>ExitCode = 1<br/>application never runs"]
RESOLVE -->|Valid| REG["RegisterAppDependencies(builder)"]
REG --> BUILD["Build container + CreateApplication(context)"]
BUILD --> INIT["Initialize()"]
INIT --> ZERO["ExitCode = 0"]
ZERO --> EXEC["Execute()"]
EXEC --> FIN["Finish()"]
FIN --> DISPOSE["Dispose() + WriteFooter()"]
DISPOSE --> ECHO["Print 'Exit Code: n'"]
INIT -.->|SaifConsoleException| ERR["ExitCode = ex.ExitCode"]
EXEC -.->|SaifConsoleException| ERR
EXEC -.->|Unhandled Exception| ERR5["ExitCode = 5"]
Argument resolution¶
CreateArgumentContainer(args) constructs the Arguments class, which derives
from StandardArgumentContainerBase<Arguments> and exposes NoticeDate,
NoticeType, BatchSize, and WhatIf as writable properties.
BuildArgumentResolvers configures NoticeDate and NoticeType explicitly and
marks BatchSize and WhatIf as not required with defaults of 500 and
false. ResolveArguments() then matches each command-line key to a property
name case-insensitively, converts the value to the property type, and collects
every failure before returning. Because resolution runs before the container is
built, an invalid command line writes its messages to Console.Error and exits
with code 1 without opening a database session or calling the Agency API.
See Arguments for the complete argument contract.
Dependency registration¶
RegisterAppDependencies(builder) calls RegisterCommonDependencies with a
single OracleConfig named policyDb, whose instance name is resolved from the
policy.db.instance environment config key, and passes the assembly containing
src/SAIF.Policy.RenewalNotice/Mappings so that the NHibernate class maps for
PolicyTerm, RenewalNotice, RenewalNoticeQueueItem, and NoticeTemplate
are added to the session factory. It then registers the application's own
services: NoticeGenerator, NoticeQueueWriter, AgentResolver, and the
IAgencyClient implementation from SAIF.Agency.Api.Client, built from the
agency.api.baseUrl and agency.api.apiKey settings. The resolved Arguments
instance is registered as a single instance so every component reads the same
notice date, notice type, and WhatIf state.
Initialize¶
Initialize() performs the one-time setup that must succeed before any notice
is generated. It retrieves the application settings from the Environment API,
resolves the policyDb IUnitOfWork, loads the POLICY.NOTICE_TEMPLATE row
for the requested notice type, verifies that the print vendor drop path is
writable, and registers the file0-file2 log destinations with
IConsoleLogService.
| Step | Action | On failure |
|---|---|---|
| 1 | Retrieve renewalnotice.* and agency.api.* settings through IEnvironmentService |
BadParameterException when a required setting is missing; exit code 1 |
| 2 | Open the policyDb unit of work and run the session probe |
DatabaseErrorException; exit code 2 |
| 3 | Load the POLICY.NOTICE_TEMPLATE row for NoticeType and its statutory lead days |
DataErrorException when no template row exists for the notice type; exit code 3 |
| 4 | Verify the print vendor drop path in renewalnotice.printQueuePath exists and is writable |
FileInputOutputException; exit code 4 |
| 5 | Register the file0-file2 log destinations and write the header block |
FileInputOutputException; exit code 4 |
| 6 | Construct the IAgencyClient and call GetAgency for the health-probe agency number |
Unhandled exception surfaced by the client; exit code 5 |
Execute¶
Execute() selects the eligible policy terms and generates the batch. It queries
POLICY.POLICY_TERM for terms whose renewal effective date falls inside the
window — the notice date plus the statutory lead days for the notice type — that
have no POLICY.RENEWAL_NOTICE row for this renewal and notice type, ordered by
renewal effective date and policy number and capped at BatchSize. If the query
returns no rows the run throws NoDataFoundException and exits with code 6.
Otherwise each term is processed in turn: the servicing agent is resolved
through SAIF.Agency.Api.Client, the notice document is produced by
NoticeGenerator, and — unless WhatIf is set — the document is written to the
print vendor drop path and a POLICY.RENEWAL_NOTICE_QUEUE row is inserted
alongside the POLICY.RENEWAL_NOTICE record inside one unit-of-work
transaction. The per-notice logic is documented in the operations pages; this
page records only the shape of the loop.
Processing pattern¶
| Step | Action | On success | On failure |
|---|---|---|---|
| 1 | Query eligible policy terms for the notice window, capped at BatchSize |
Continue to the loop | DatabaseErrorException (exit code 2); an empty result throws NoDataFoundException (exit code 6) |
| 2 | Resolve the servicing agency and agent through SAIF.Agency.Api.Client |
Continue to generation | Log to file2, count the notice as skipped, continue with the next term |
| 3 | Generate the notice document from the loaded template | Continue to queuing | DataErrorException when a required merge field is missing on the term (exit code 3) |
| 4 | Write the print file and insert the queue and notice rows in one transaction | Commit, log the notice to file1 |
Roll back the term's transaction, log to file2, continue with the next term |
| 5 | Write run totals: terms read, notices queued, skipped, failed | — | — |
When WhatIf is set, steps 4 and 5 still run but the print file write and both
inserts are skipped; the notice is logged to file1 with a WHATIF marker so a
dry run and a real run produce comparable detail reports.
Finish¶
Finish() flushes the file0-file2 streams, writes the totals block to
file0, and sends the operations summary mail to the addresses in
renewalnotice.notificationRecipients with the counts and the elapsed time. When
the skipped or failed count is above zero the summary names the first ten
affected policy numbers so operations can triage without opening the log. The
unit of work and the IAgencyClient are released here; the framework then
disposes the container, writes the footer with the total running time, and
echoes the exit code.
Exit codes¶
Exit codes are inherited from the SaifConsoleException hierarchy. The table below records which conditions in this application produce each code.
| Exit code | Exception | Triggered by |
|---|---|---|
| 0 | — | Successful completion; all eligible terms generated and queued, or logged when WhatIf is set |
| 1 | Argument validation / BadParameterException |
A missing or unparseable argument, a NoticeType outside Standard, NonRenewal, Conditional, a BatchSize outside 1-5000, or a missing required Environment API setting |
| 2 | DatabaseErrorException |
The policyDb session cannot be opened, or the eligible-term query or a notice insert fails |
| 3 | DataErrorException |
No POLICY.NOTICE_TEMPLATE row exists for the requested notice type, or a term is missing a merge field the template requires |
| 4 | FileInputOutputException |
The print vendor drop path is unreachable or not writable, or a file0-file2 log destination cannot be opened |
| 5 | Unhandled exception | Any unexpected failure, including an Agency API fault during the Initialize health probe |
| 6 | NoDataFoundException |
No policy terms fall inside the notice window for the requested notice date and notice type |
The application defines no CustomErrorException subclass, so no exit code above
6 is produced.
Error handling¶
Recoverable errors¶
A failure that affects one policy term does not stop the run. Agent resolution
failures — an agency the Agency API does not know, or an agency with no
appointed servicing agent — are logged to file2 with the policy number and the
agency number, counted as skipped, and left for the next run to retry. A
transaction failure while writing the print file or the queue and notice rows is
rolled back so no partial notice survives, logged to file2 with the policy
number and the exception message, counted as failed, and followed by the next
term. Terms that are skipped or fail keep no POLICY.RENEWAL_NOTICE row, so
they remain eligible and are picked up again on the following run.
Fatal errors¶
Anything that makes the whole batch impossible ends the run immediately with the
mapped exit code: a failure in any Initialize step, a database error on the
eligible-term query, or a missing notice template. An empty result set is
treated as fatal rather than as success, so a night where the window is empty
ends with exit code 6; operations treats that as an expected outcome for the
NonRenewal and Conditional runs and as an alert for the nightly Standard
run. Because notices are recorded only when their transaction commits, a fatal
exit part way through a batch is safe to rerun with the same arguments.
Related documentation¶
- Application index
- Overview
- Architecture
- Arguments
- Dependencies
- Configuration
- Operations and processes
- Business rules
- Data model
- Operational runbook
- Tech debt
Sources¶
src/SAIF.Policy.RenewalNotice/Program.cssrc/SAIF.Policy.RenewalNotice/Arguments.cssrc/SAIF.Policy.RenewalNotice/RenewalNoticeApplication.cssrc/SAIF.Policy.RenewalNotice/NoticeGenerator.cssrc/SAIF.Policy.RenewalNotice/NoticeQueueWriter.cssrc/SAIF.Policy.RenewalNotice/Mappings/PolicyTermMap.cs