Skip to content

Business rules

SAIF.Agency.Api is the only writer of the AGENCY schema, so the rules below are the whole of SAIF's agreed behaviour for agency data: what a valid agency number looks like, which agencies and agents are visible to a reader, and what must be true before an agency's contact details may be changed. Contact field validation is delegated to SAIF.Framework.Validation and keeps that library's VAL- identifiers; every other rule is enforced by AgencyService and is reported to the caller as a 400, 404, or 409 response.


BR-001 Agency number must be six digits

Field Value
Statement An agency number is exactly six numeric digits with no separators or alphabetic characters, because it is the key the whole distribution channel is filed under and a malformed value cannot identify an agency.
Trigger Evaluated in the action body, immediately after model binding and before any service call.
Enforcement AgencyApiBase.ValidateAgencyNumber applies the ^[0-9]{6}$ pattern and returns ValidationFailure when it does not match.
Origin api
Reached from GET /v1/agencies/{agencyNumber}, GET /v1/agencies/{agencyNumber}/agents, PUT /v1/agencies/{agencyNumber}/contact
Outcome On pass, processing continues. On failure the action returns 400 Bad Request with code AGENCY_NUMBER_FORMAT; no database call is made and nothing is logged beyond the request entry.
Source src/SAIF.Agency.Api/Controllers/AgencyApiBase.cs:74

BR-002 Agency search must be bounded by a producer code

Field Value
Statement A search must supply a producer code of one to ten characters. An unbounded search would return the entire distribution channel and is refused, because no consumer has a business need for it and the query would scan the whole AGENCY table.
Trigger Evaluated in the action body of SearchAgencies, before the repository query is built.
Enforcement AgenciesController.SearchAgencies rejects a null, empty, whitespace, or over-long producerCode through AgencyApiBase.ValidationFailure.
Origin api
Reached from GET /v1/agencies
Outcome On pass the search runs. On failure the caller receives 400 Bad Request with code PRODUCER_CODE_REQUIRED; a search that is valid but matches nothing returns 200 OK with an empty array, which is not a failure.
Source src/SAIF.Agency.Api/Controllers/v1/AgenciesController.cs:96

BR-003 Terminated agencies are excluded unless explicitly requested

Field Value
Statement A terminated agency is invisible to reads by default, so that a consumer attributing new business cannot place it with an agency that no longer holds an appointment; a caller that genuinely needs historical data must ask for it.
Trigger Evaluated in the service layer when the query filter is assembled, on every read of agency data.
Enforcement AgencyService.BuildAgencyFilter adds STATUS <> 'Terminated' unless the caller passed includeTerminated=true on the lookup or activeOnly=false on the search.
Origin api
Reached from GET /v1/agencies/{agencyNumber}, GET /v1/agencies, GET /v1/appointments
Outcome A filtered-out agency is reported as 404 Not Found with code AGENCY_NOT_FOUND on the lookup endpoint and is simply absent from the array on the search endpoints. The two behaviours differ deliberately: a lookup names one agency, a search does not.
Source src/SAIF.Agency.Api.Services/AgencyService.cs:58

BR-004 An agency roster returns only agents with an active appointment

Field Value
Statement The roster of an agency is the set of agents whose appointment at that agency is in effect on the evaluation date. An agent who has left, or whose appointment has not yet started, is not part of the roster even though the appointment record is retained.
Trigger Evaluated in the service layer when the roster query runs, against the asOfDate supplied by the caller or the current date.
Enforcement AgencyService.GetAgentsForAgency joins AGENCY_APPOINTMENT on EFFECTIVE_DATE <= :asOf and (END_DATE IS NULL OR END_DATE > :asOf).
Origin api
Reached from GET /v1/agencies/{agencyNumber}/agents
Outcome Matching agents are returned as 200 OK with an array; an agency with no agents in effect returns 200 OK with an empty array rather than 404, because the agency itself exists.
Source src/SAIF.Agency.Api.Services/AgencyService.cs:131

BR-005 Contact details must be a deliverable email address and phone number

Field Value
Statement An agency's contact email must be a syntactically deliverable address of at most 255 characters, and its contact phone must reduce to exactly ten digits once punctuation is removed, because renewal notices and claim correspondence are sent to these values without further checking.†
Trigger Evaluated in the service layer when the contact update is applied, before the agency's state is checked.
Enforcement Delegated to SAIF.Framework.Validation rules VAL-051 (email form) and VAL-052 (ten digit phone), applied by ContactValidator.Validate and raised to the caller as AgencyValidationException.
Origin library
Reached from PUT /v1/agencies/{agencyNumber}/contact
Outcome On pass the update continues to BR-006. On failure AgencyApiExceptionFilter returns 400 Bad Request with code CONTACT_INVALID and one details entry per failing field, naming VAL-051 or VAL-052; nothing is written.
Source src/SAIF.Agency.Api.Services/AgencyService.cs:188

BR-006 Contact details may only be changed on an active agency

Field Value
Statement Contact details may be changed only while the agency's status is Active. A suspended or terminated agency's contact block is frozen as the record of where correspondence was sent while the appointment was in force.
Trigger Evaluated in the service layer after field validation passes and before the agency is updated.
Enforcement AgencyService.UpdateContact raises AgencyStateConflictException when AGENCY.STATUS is anything other than Active.
Origin api
Reached from PUT /v1/agencies/{agencyNumber}/contact
Outcome On pass the update proceeds. On failure AgencyApiExceptionFilter returns 409 Conflict with code AGENCY_NOT_ACTIVE; the unit of work is rolled back and no audit row is written.
Source src/SAIF.Agency.Api.Services/AgencyService.cs:197

BR-007 Every accepted contact change writes an audit row

Field Value
Statement An accepted change to an agency's contact block must record what the values were, what they became, who changed them, and when, so that a misdirected renewal notice can be traced to the change that caused it.
Trigger Evaluated at persistence, inside the same unit of work that writes the new contact values.
Enforcement AgencyService.UpdateContact inserts an AGENCY_CONTACT_AUDIT row carrying the previous and new values, CHANGED_BY from AgencyApiBase.Caller, and the request's correlation id, then commits both statements together.
Origin api
Reached from PUT /v1/agencies/{agencyNumber}/contact
Outcome The caller receives 204 No Content only when both writes commit. If the audit insert fails the whole unit of work rolls back and the caller receives 500 Internal Server Error; a contact change is never persisted without its audit row.
Source src/SAIF.Agency.Api.Services/AgencyService.cs:221

BR-008 A contact update must carry the expected agency version

Field Value
Statement An update must state the agency version the caller read, and is refused if the stored version has moved on. Two portal users editing the same agency must not silently overwrite each other's address changes.
Trigger Evaluated at persistence, when the update statement is issued against the version column.
Enforcement AgencyRepository.UpdateContact includes VERSION = :expectedVersion in the WHERE clause and increments VERSION on success; a zero row count raises StaleAgencyVersionException.
Origin api
Reached from PUT /v1/agencies/{agencyNumber}/contact
Outcome On pass, the new version is returned in the X-Agency-Version response header with 204 No Content. On failure the action catches the exception, attaches the current version, and AgencyApiExceptionFilter returns 409 Conflict with code AGENCY_VERSION_STALE; the caller must re-read the agency and resubmit.
Source src/SAIF.Agency.Api.Data/AgencyRepository.cs:164

BR-009 An agent is returned as licensed only while the license is active

Field Value
Statement An agent is reported as licensed in a state only while the state license is unexpired and not revoked, because an appointment written against a lapsed license is not enforceable.
Trigger Evaluated in the service layer on every agent read and when an appointment is created.
Enforcement AgentService.ResolveLicense compares AGENT_LICENSE.EXPIRATION_DATE and REVOKED_FLAG against the evaluation date; AppointmentService.Create calls the same method before inserting.
Origin api
Reached from GET /v1/agents/{agentNumber}, GET /v1/agents, POST /v1/appointments
Outcome On the read endpoints a lapsed license is returned with licenseStatus Expired rather than being hidden. On POST /v1/appointments it raises AgencyValidationException and the caller receives 400 Bad Request with code AGENT_LICENSE_INACTIVE.
Source src/SAIF.Agency.Api.Services/AgentService.cs:87

BR-010 An agent holds at most one active appointment at an agency

Field Value
Statement An agent may hold only one appointment at a given agency at a time. Overlapping appointments would make commission attribution ambiguous, so a new appointment may not start before the previous one at that agency has ended.
Trigger Evaluated in the service layer before an appointment row is inserted, and when an agent's appointment history is read.
Enforcement AppointmentService.Create queries AGENCY_APPOINTMENT for an existing row with the same agent and agency and no END_DATE, raising AgencyStateConflictException when one exists; the unique index AGENCY_APPOINTMENT_UK1 backs the check at the database.
Origin api
Reached from GET /v1/agents/{agentNumber}/appointments, POST /v1/appointments
Outcome On pass the appointment is inserted and 201 Created is returned with the new appointment id. On failure the caller receives 409 Conflict with code APPOINTMENT_ALREADY_ACTIVE and the existing appointment id in details.
Source src/SAIF.Agency.Api.Services/AppointmentService.cs:64

Note: Statements marked with † were retrieved from dependency library documentation via the Legends CLI RAG service. They describe inherited behavior from the indicated origin library and should be verified if the dependency version changes.

Sources

  • src/SAIF.Agency.Api/Controllers/AgencyApiBase.cs
  • src/SAIF.Agency.Api/Controllers/v1/AgenciesController.cs
  • src/SAIF.Agency.Api.Services/AgencyService.cs
  • src/SAIF.Agency.Api.Services/AgentService.cs
  • src/SAIF.Agency.Api.Services/AppointmentService.cs
  • src/SAIF.Agency.Api.Data/AgencyRepository.cs
  • src/SAIF.Agency.Api.Data/Maps/AgencyAppointmentMap.cs