Skip to content

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.

Sources

  • src/SAIF.Policy.RenewalNotice/Program.cs
  • src/SAIF.Policy.RenewalNotice/Arguments.cs
  • src/SAIF.Policy.RenewalNotice/RenewalNoticeApplication.cs
  • src/SAIF.Policy.RenewalNotice/NoticeGenerator.cs
  • src/SAIF.Policy.RenewalNotice/NoticeQueueWriter.cs
  • src/SAIF.Policy.RenewalNotice/Mappings/PolicyTermMap.cs