---
title: SAIF.Business.View.Policy business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF Doc Gen
last_reviewed: "2026-08-17"
library: SAIF.Business.View.Policy
reference_type: package
---


# SAIF.Business.View.Policy business rules

This page captures the business rules enforced by the `SAIF.Business.View.Policy` library. The library is a read-only policy-view layer, so its rules govern who may see a policy, how policy associations are classified, and which objects are considered valid. Authorization rules are also summarised, from the enforcement point of view, on the [Security and authorization](security-authorization.md) page.

## BR-001: Secure SAIF policies require explicit authorization

| Field | Value |
| --- | --- |
| **Statement** | A policy whose `SecurePolicyCode` equals the SAIF secure-policy constant is denied to any principal that does not hold the `SAIFpolicyInfo` authorization. |
| **Trigger** | `clsPolicySecurity.IsPolicyValidForUser(clsPolicyData)` is entered. |
| **Enforcement** | `clsPolicySecurity.IsPolicyValidForUser` checks the secure-policy code first and consults the security principal's `SAIFpolicyInfo` authorization (`clsPolicySecurity.vb`, lines 173-176). |
| **Outcome** | Access is refused immediately and no further user-type evaluation occurs. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-002: Invalid or unnumbered policies are never accessible

| Field | Value |
| --- | --- |
| **Statement** | Access is refused when the policy fails validation or its policy number is zero. |
| **Trigger** | Authorization evaluation continues past the secure-policy check. |
| **Enforcement** | `clsPolicySecurity.IsPolicyValidForUser` tests `inPolicyData.IsValid` and `PolicyNum` before any user-type branch (`clsPolicySecurity.vb`, lines 178-180). |
| **Outcome** | The caller receives a refusal, so an unloaded or invalid policy can never be exposed. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-003: Internal and provider user types have unrestricted policy visibility

| Field | Value |
| --- | --- |
| **Statement** | SAIF employees, MCO users, medical providers, and bookkeepers may view any valid policy. |
| **Trigger** | The user type resolved from the security principal is an internal or provider type. |
| **Enforcement** | `clsPolicySecurity.IsPolicyValidForUser` grants access for those user types without further filtering (`clsPolicySecurity.vb`, lines 184-187). |
| **Outcome** | Access is granted for every policy that passes the preceding validity checks. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-004: Agents see only their own book

| Field | Value |
| --- | --- |
| **Statement** | An agent may view a policy only when the agent's user number matches the policy's agent number or the policy's agency sequence number. |
| **Trigger** | The user type is `Agent`. |
| **Enforcement** | `clsPolicySecurity.IsPolicyValidForUser` compares the principal's user number against `AgentNumber` and `AgencySeqNum` on the policy (`clsPolicySecurity.vb`, lines 189-194). |
| **Outcome** | Access is granted on a match and refused otherwise, confining agents to their own book of business. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-005: Policyholders see their own policy and policies under the same parent association

| Field | Value |
| --- | --- |
| **Statement** | A policyholder may view the requested policy when it is their own policy, or when it belongs to an association whose parent association the user's own policy also belongs to. |
| **Trigger** | The user type is `PolicyHolder`. |
| **Enforcement** | `clsPolicySecurity.IsPolicyValidForUser` delegates to `IsPolicyValidForParentPolicy`, which compares parent-policy numbers derived from the policy associations of both policies (`clsPolicySecurity.vb`, lines 136-146 and 196-202). |
| **Outcome** | Access is granted only for policies inside the user's own parent association group. |
| **SQL** | [SQL-026](sql/SQL-026-policy-association-union.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-006: Group users see only policies in their group or chapter

| Field | Value |
| --- | --- |
| **Statement** | A group user may view a policy only when the group-membership query returns at least one row for the group identifier, chapter, and policy number. |
| **Trigger** | The user type is `Group`. |
| **Enforcement** | `clsPolicySecurity.IsInGroup` executes the SQL Server procedure `cp_policy_in_group` against the separately configured BDS data store and requires a count greater than zero (`clsPolicySecurity.vb`, lines 204-205 and 253-261). |
| **Outcome** | Access is granted only for policies explicitly listed in the caller's group or chapter. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-007: Loss-control consultants see only policies they are assigned to

| Field | Value |
| --- | --- |
| **Statement** | A loss-control user may view a policy only when the policy's loss-control consultant team-assignment name begins with the user's identifier. Demo loss-control users are matched against the assignment's user-id name instead of the user name, and a `%` character in the user identifier truncates the comparison prefix. |
| **Trigger** | The user type is `LossControl` and the policy carries a loss-control team assignment. |
| **Enforcement** | `clsPolicySecurity.CanSafetyConsultantAccessPolicy` performs the prefix comparison over the team assignments retrieved for the policy (`clsPolicySecurity.vb`, lines 207-218 and 227-236). |
| **Outcome** | Access is granted on a prefix match; a policy with no loss-control assignment stays inaccessible. |
| **SQL** | [SQL-009](sql/SQL-009-team-assignments.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-008: Fee auditors see only policies with a recent, non-cancelled audit

| Field | Value |
| --- | --- |
| **Statement** | An auditor may view a policy only when an audit exists for that policy under the auditor's vendor number whose cancel date and completed date are no older than 60 days before the data-source date. |
| **Trigger** | The user type is `Auditor`. |
| **Enforcement** | `clsPolicySecurity.CanAuditorAccessPolicy` runs a vendor-scoped, date-windowed audit count and requires a result greater than zero (`clsPolicySecurity.vb`, lines 220-221 and 263-282). |
| **Outcome** | Access is granted only while an auditor's engagement on the policy is current. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-009: A policy view requires a positive policy number

| Field | Value |
| --- | --- |
| **Statement** | `clsPolicyData` is valid only when its policy number is greater than zero. |
| **Trigger** | Broken-rule evaluation runs during a read of `IsValid`. |
| **Enforcement** | `clsPolicyData.ValidPolicyNum` is registered in `AddBusinessRules` and reports the message "Error: Invalid policy number." (`clsPolicyData.vb`, lines 2211-2219). |
| **Outcome** | `IsValid` reports false, and the authorization path then refuses access under [BR-002](business-rules.md#br-002-invalid-or-unnumbered-policies-are-never-accessible). |
| **SQL** | [SQL-007](sql/SQL-007-policy-header.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicyData.vb` |

---

## BR-010: A non-employer account requires a non-zero account number

| Field | Value |
| --- | --- |
| **Statement** | `clsNonEmployerAccount` is valid only when its account number is not zero. |
| **Trigger** | Broken-rule evaluation runs after the object is loaded. |
| **Enforcement** | `clsNonEmployerAccount.IsValidAccountNumber` is registered as a business rule and reports the message "Error: Invalid Non Employer Account Number" (`clsNonEmployerAccount.vb`, lines 273-279). |
| **Outcome** | The object reports itself invalid, so callers can reject an unusable account before display. |
| **Tables** | [NON_EMPR_ACCT](database-tables.md#non-empr-acct) |
| **SQL** | [SQL-004](sql/SQL-004-non-employer-account.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsNonEmployerAccount.vb` |

---

## BR-011: Affiliated-policy visibility depends on user access level and affiliate type

| Field | Value |
| --- | --- |
| **Statement** | Quick-file users never see affiliated policies. Internal, agent, auditor, group, and loss-control levels see them for every affiliate type except non-affiliated and affiliation parent or member. Policyholder levels see them only when the policy is the parent of the association. |
| **Trigger** | `clsPolicyData.DisplayAffiliatedPolices` is read. |
| **Enforcement** | `clsPolicyData.DisplayAffiliatedPolices` combines the caller's `UserAccessLevels` value with the resolved affiliate type (`clsPolicyData.vb`, lines 233-264). The member is marked `<Obsolete>` in favour of the `Associations` property. |
| **Outcome** | Presentation layers receive a single boolean display decision for the affiliated-policy panel. |
| **SQL** | [SQL-001](sql/SQL-001-affiliated-policy-list.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicyData.vb` |

---

## BR-012: Demo access levels are treated as a distinct user population

| Field | Value |
| --- | --- |
| **Statement** | A user is a demo user when the access level is any of demo agent, demo group, demo policyholder, demo auditor, or demo loss control. Demo status is propagated into team-assignment retrieval and loss-control name matching. |
| **Trigger** | `clsPolicyData.IsDemoUser` is read, or `clsPolicySecurity.ConvertUserTypeToUserAccessLevel` runs. |
| **Enforcement** | `clsPolicyData.IsDemoUser` tests the access level against the five demo values (`clsPolicyData.vb`, lines 266-273 and 1264-1272); `clsPolicySecurity.ConvertUserTypeToUserAccessLevel` selects the demo variant when the principal is flagged as a demo user (`clsPolicySecurity.vb`, lines 367-405). |
| **Outcome** | Demo users receive masked demo identity data in team assignments and are matched on user-id name for loss-control access under [BR-007](business-rules.md#br-007-loss-control-consultants-see-only-policies-they-are-assigned-to). |
| **SQL** | [SQL-009](sql/SQL-009-team-assignments.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicyData.vb`, `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-013: User type maps to a ranked access level

| Field | Value |
| --- | --- |
| **Statement** | Each authenticated user type maps to exactly one `UserAccessLevels` value: SAIF employee 1000, auditor 900 or 850, loss control 800 or 750, agent 700 or 650, group 600 or 550, policyholder, MCO, and medical provider 500 or 450, and bookkeeper to quick file 300. The lower value of each pair is selected when the user is flagged as a demo user. |
| **Trigger** | `clsPolicySecurity.UserAccessLevel` is read, or the conversion is called explicitly. |
| **Enforcement** | `clsPolicySecurity.ConvertUserTypeToUserAccessLevel` performs the mapping (`clsPolicySecurity.vb`, lines 367-405) against the `UserAccessLevels` enumeration declared in `clsPolicyData.vb` (lines 82-95). |
| **Outcome** | The resulting ranked level is passed into `clsPolicyData` criteria and drives visibility decisions such as [BR-011](business-rules.md#br-011-affiliated-policy-visibility-depends-on-user-access-level-and-affiliate-type). |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb`, `Business/SAIF.Business.View.Policy/clsPolicyData.vb` |

---

## BR-014: Association date defaults differ per association family and detail-load failures are non-fatal

| Field | Value |
| --- | --- |
| **Statement** | When no as-of date is supplied, experience-rating combination selection uses the data-source date while discount-combination selection uses the policy's latest policy-period effective date; when an as-of date is supplied, both use it. Separately, a failure while lazily loading policy detail is swallowed so that the header remains usable. |
| **Trigger** | `PolicyAssociationList.GetPolicyAssociationList` is invoked, or `clsPolicyData` lazily reads its detail block. |
| **Enforcement** | `PolicyAssociations.vb` (lines 258-272) selects the two default dates; `clsPolicyData.vb` (lines 1508-1520) catches and discards detail-load failures. |
| **Outcome** | Association results are date-scoped predictably, and detail properties fall back to empty values instead of raising. |
| **Tables** | [POLICY_PERIOD](database-tables.md#policy-period) |
| **SQL** | [SQL-013](sql/SQL-013-policy-affiliate-type.md), [SQL-019](sql/SQL-019-parent-policy.md), [SQL-027](sql/SQL-027-max-policy-period-eff-date.md), [SQL-028](sql/SQL-028-association-period-dates.md) |
| **Source** | `Business/SAIF.Business.View.Policy/PolicyAssociations.vb`, `Business/SAIF.Business.View.Policy/clsPolicyData.vb` |

---

## BR-015: Unrecognized relationship codes are classified as not specified

| Field | Value |
| --- | --- |
| **Statement** | A relationship code returned by the association query that does not match a known code (`WRAP`, `OREGN`, `LEASE`, `SUBAC`, their `M.`-prefixed manual forms, `DiscountCombination`, and `ExperienceCombination`) is classified as not specified. |
| **Trigger** | Rows are projected in `GetPolicyAssociationList`. |
| **Enforcement** | `PolicyAssociations.ConvertAssociationType` maps unrecognised codes to `NotSpecified` (`PolicyAssociations.vb`, lines 220-252). |
| **Outcome** | The association is retained but carries no meaningful type and maps to `AffiliateTypes.NonAffiliated`. |
| **Tables** | [POLICY_RELATIONSHIP](database-tables.md#policy-relationship) |
| **SQL** | [SQL-003](sql/SQL-003-association-type-code-table.md), [SQL-026](sql/SQL-026-policy-association-union.md), [SQL-029](sql/SQL-029-non-typed-associations.md) |
| **Source** | `Business/SAIF.Business.View.Policy/PolicyAssociations.vb` |

---

## BR-016: Manually maintained affiliations are distinguished by an M-prefixed code

| Field | Value |
| --- | --- |
| **Statement** | Associations sourced from `POLICY_RELATIONSHIP` are emitted with an `M.` prefix on the affiliation type code and resolve to the manual association types. `IsManual` and `ToNonManual` convert between the manual and system-derived forms. |
| **Trigger** | Associations are retrieved and their type codes converted. |
| **Enforcement** | `PolicyAssociations.vb` applies the prefix during projection and supplies the conversion helpers (lines 68-96, 220-252, and 396-420). |
| **Outcome** | Consumers can distinguish user-entered affiliations from system-derived combinations. |
| **Tables** | [POLICY_RELATIONSHIP](database-tables.md#policy-relationship) |
| **SQL** | [SQL-026](sql/SQL-026-policy-association-union.md) |
| **Source** | `Business/SAIF.Business.View.Policy/PolicyAssociations.vb` |

---

## BR-017: Association rows without a matching type code are discarded

| Field | Value |
| --- | --- |
| **Statement** | A policy-header association row is retained only when its category-prefixed association type code matches an entry in the association type code table; unmatched rows are discarded. All types within one category grouping must additionally share a single source stored procedure. |
| **Trigger** | `clsAssociationTypeCode.GetGroupedPolicyHeaderAssociations` or `GetGroupedAssociations` runs. |
| **Enforcement** | `Associations/clsAssociationTypeCode.vb` (lines 320-333) filters rows against the loaded code table and resolves the shared source procedure with `.Distinct.Single`. |
| **Outcome** | Unknown codes are dropped silently, and a category whose types disagree on their source procedure raises an error. |
| **Tables** | [ASSOCIATION_TYPE_CODE](database-tables.md#association-type-code) |
| **SQL** | [SQL-003](sql/SQL-003-association-type-code-table.md), [SQL-029](sql/SQL-029-non-typed-associations.md) |
| **Source** | `Business/SAIF.Business.View.Policy/Associations/clsAssociationTypeCode.vb` |

---

## BR-018: Agent policy lists are produced only for numeric agent identifiers

| Field | Value |
| --- | --- |
| **Statement** | The agent policy-list query runs only when the agent's user number is numeric. The agency-scope flag is set to the yes/no form of "the user is not a producer", so a non-producer agent receives the whole agency's book. |
| **Trigger** | `clsPolicySecurity.PolicyList` is read for user type `Agent`. |
| **Enforcement** | `clsPolicySecurity.GetPolicyListForAgent` performs the numeric test and sets the `@ForAgency` flag from producer status (`clsPolicySecurity.vb`, lines 73-75 and 324-337). |
| **Outcome** | A non-numeric agent identifier yields an empty policy list rather than an error or an unfiltered list. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-019: Policy lists fail closed

| Field | Value |
| --- | --- |
| **Statement** | When no user-type branch produces a policy list, an empty list is returned rather than `Nothing` or an unrestricted list. |
| **Trigger** | `clsPolicySecurity.PolicyList` is read. |
| **Enforcement** | `clsPolicySecurity.PolicyList` initialises the backing list and returns it unchanged when no branch matches (`clsPolicySecurity.vb`, lines 88-94). |
| **Outcome** | Callers receive zero permitted policies, so an unrecognised user type cannot widen access. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb` |

---

## BR-020: Affiliated policies are filtered to the requesting agent or agency when specified

| Field | Value |
| --- | --- |
| **Statement** | When the criteria carry a non-zero agent number or agency sequence number, only affiliated policies matching that agent or agency are added to the collection. When both are zero, every affiliated policy is added. |
| **Trigger** | `clsAffiliatedPolicyCollection.AfterRetrieve` processes result rows. |
| **Enforcement** | `clsAffiliatedPolicyCollection.vb` (lines 104-125) filters each row against the criteria before adding it. |
| **Outcome** | Agents see only their own affiliated policies within an affiliation group. |
| **SQL** | [SQL-001](sql/SQL-001-affiliated-policy-list.md) |
| **Source** | `Business/SAIF.Business.View.Policy/clsAffiliatedPolicyCollection.vb` |

---

## BR-021: This library never writes policy data

| Field | Value |
| --- | --- |
| **Statement** | Create, insert, update, and delete SQL are unsupported for every business object in this library. Attempting one either yields an empty statement or raises `ApplicationException(clsConstants.Messages.InvalidSQLProperty)`. |
| **Trigger** | The framework attempts any non-retrieve lifecycle operation. |
| **Enforcement** | The `Criteria` SQL properties in all twelve compiled criteria classes refuse the operation (for example `clsPolicyData.vb`, lines 2382-2404, `clsTradingAs.vb`, and `Associations/clsAssociationTypeCode.vb`). |
| **Outcome** | The library stays read-only; persistence must occur in another package. |
| **Source** | `Business/SAIF.Business.View.Policy/clsPolicyData.vb`, `Business/SAIF.Business.View.Policy/clsTradingAs.vb`, `Business/SAIF.Business.View.Policy/Associations/clsAssociationTypeCode.vb` |

---

## Inherited business rules

Rules in this section originate from base classes provided by the `SAIF.Framework.Core` dependency. They are enforced by inherited behaviour rather than by code in this library, and they apply to every business object and collection documented in the [API reference](api/index.md).

### From `clsBusinessBase` (`SAIF.Framework.Core`)

#### INH-001: Broken rules determine object validity

| Field | Value |
| --- | --- |
| **Statement** | A business object is valid only while its broken-rules collection is empty; every registered rule is re-evaluated when its associated property changes.† |
| **Trigger** | A property registered through `AddBusinessRules` changes, or `IsValid` is read. |
| **Enforcement** | `clsBusinessBase` maintains a `clsRulesManager` instance and recomputes the broken-rules collection on property change.† |
| **Outcome** | `IsValid` reports false and `BrokenRulesCollection` carries the rule messages until the condition clears.† |
| **Origin** | `SAIF.Framework.Core` — `clsBusinessBase` |
| **Source** | Retrieved from indexed documentation via Legends CLI |

#### INH-002: Retrieval is mediated by the data portal, not by the object itself

| Field | Value |
| --- | --- |
| **Statement** | A business object never opens a connection directly; it supplies SQL and parameters through its `Criteria` object and the data portal executes the request.† |
| **Trigger** | A shared factory method calls the framework retrieve entry point. |
| **Enforcement** | `clsBusinessBase` orchestrates `BeforeRetrieve`, portal execution, and `AfterRetrieve` around an `IPortalRequest`.† |
| **Outcome** | All database access in this library is funnelled through a single, auditable execution path.† |
| **Origin** | `SAIF.Framework.Core` — `clsBusinessBase` |
| **Source** | Retrieved from indexed documentation via Legends CLI |

#### INH-003: Lifecycle hooks are the only supported extension points

| Field | Value |
| --- | --- |
| **Statement** | Derived types customise persistence behaviour exclusively through the protected `BeforeRetrieve` and `AfterRetrieve` overrides.† |
| **Trigger** | The framework executes a retrieve request. |
| **Enforcement** | `clsBusinessBase` declares the overridable hooks and invokes them around portal execution.† |
| **Outcome** | Every retrieval in this library follows the same predictable sequence, which is why each SQL document names a lifecycle method.† |
| **Origin** | `SAIF.Framework.Core` — `clsBusinessBase` |
| **Source** | Retrieved from indexed documentation via Legends CLI |

---

### From `clsBusinessCollectionBase` (`SAIF.Framework.Core`)

#### INH-004: Collections track added, removed, and deleted members

| Field | Value |
| --- | --- |
| **Statement** | A business collection maintains a deleted-items list alongside its live members so that removals can be distinguished from absences.† |
| **Trigger** | An item is removed from the collection. |
| **Enforcement** | `clsBusinessCollectionBase` moves the item into its deleted-items list rather than discarding it.† |
| **Outcome** | `ContainsDeleted` on the policy-period and affiliated-policy collections can answer questions about removed members.† |
| **Origin** | `SAIF.Framework.Core` — `clsBusinessCollectionBase` |
| **Source** | Retrieved from indexed documentation via Legends CLI |

---

## Notes

- Rule identifiers are stable within this library: [BR-001](business-rules.md#br-001-secure-saif-policies-require-explicit-authorization) through [BR-021](business-rules.md#br-021-this-library-never-writes-policy-data) cover locally enforced behaviour and `INH-` identifiers cover behaviour inherited from `SAIF.Framework.Core`.
- [BR-001](business-rules.md#br-001-secure-saif-policies-require-explicit-authorization), [BR-003](business-rules.md#br-003-internal-and-provider-user-types-have-unrestricted-policy-visibility), [BR-004](business-rules.md#br-004-agents-see-only-their-own-book), [BR-005](business-rules.md#br-005-policyholders-see-their-own-policy-and-policies-under-the-same-parent-association), [BR-006](business-rules.md#br-006-group-users-see-only-policies-in-their-group-or-chapter), [BR-007](business-rules.md#br-007-loss-control-consultants-see-only-policies-they-are-assigned-to), and [BR-008](business-rules.md#br-008-fee-auditors-see-only-policies-with-a-recent-non-cancelled-audit) together form the complete authorization matrix. The matching enforcement points are catalogued on the [Security and authorization](security-authorization.md) page.
- [BR-011](business-rules.md#br-011-affiliated-policy-visibility-depends-on-user-access-level-and-affiliate-type) and [BR-014](business-rules.md#br-014-association-date-defaults-differ-per-association-family-and-detail-load-failures-are-non-fatal) are enforced by members that the source marks `<Obsolete>`. They remain in force for existing callers but are superseded by the `Associations` property and the association type code table.
- The group-membership check behind [BR-006](business-rules.md#br-006-group-users-see-only-policies-in-their-group-or-chapter) runs against a second, SQL Server data store that is configured separately from the Oracle data portal. See [Configuration](configuration.md).

> **Note:** Statements marked with `†` are derived from dependency library
> documentation via the Legends CLI. Statements marked with `*` were inferred
> from code structure or contextual evidence.

## Related documentation

- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Security and authorization](security-authorization.md)
- [Database tables](database-tables.md)
- [SQL catalog](sql/index.md)
- [Procedures](procedures/index.md)
- [API reference](api/index.md)
- [Configuration](configuration.md)

## Sources

- `Business/SAIF.Business.View.Policy/clsPolicySecurity.vb`
- `Business/SAIF.Business.View.Policy/clsPolicyData.vb`
- `Business/SAIF.Business.View.Policy/clsNonEmployerAccount.vb`
- `Business/SAIF.Business.View.Policy/clsAffiliatedPolicyCollection.vb`
- `Business/SAIF.Business.View.Policy/clsTradingAs.vb`
- `Business/SAIF.Business.View.Policy/PolicyAssociations.vb`
- `Business/SAIF.Business.View.Policy/Associations/clsAssociationTypeCode.vb`

## Stored procedure business rules

Rules in this section are enforced inside the Oracle stored procedures and functions that this library calls. They are not implemented in .NET code, but they change the values that callers observe.

| Rule ID | Rule | Source | Link |
| --- | --- | --- | --- |
| PROC-RULE-001 | The per-row cancellation status is always computed live, not stored | `pk73094_policy_data.getpolicy` | [Details](procedures/pk73094_policy_data.md#business-rules) |
| PROC-RULE-002 | Sales-plan name is masked for reduced-form (`RF`) policy periods | `pk72846_view_policy.getviewdetail` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-003 | A policy has a coverage gap indicator when `getgapindicator` returns non-zero | `pk72846_view_policy.getviewdetail` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-004 | A policy is flagged as a combination-wrap sub-account only when a sibling policy in the same experience-rating combination carries `policy_relationship_code = 'WRAP'` | `pk72846_view_policy.getviewdetail` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-005 | Manual (policyholder-portal) affiliation takes priority over all other affiliation types, but only for policyholder users | `pk72846_view_policy.getpolicyaffiliatetype` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-006 | Affiliation family precedence after the manual check is wrap-up/leasing, then sub-account/state-agency, then experience combination, then premium-discount combination | `pk72846_view_policy.getpolicyaffiliatetype` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-007 | The parent policy number is derived from the affiliate type rather than stored directly | `pk72846_view_policy.getparentpolicy` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-008 | The legal name shown for a wrap-up chain cascades from the policy to its wrap-up relationship policy to that policy's rate account | `pk72846_view_policy.getwrapuplegalname` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-009 | A coverage gap exists between two consecutive periods unless the period is flat-cancelled or a PMS prospect period | `pk72846_view_policy.getgaps` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-010 | The agency and producer shown for a policy are always resolved through the current agent-of-record rather than a stored foreign key | `pk72846_view_policy.getagency` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-011 | When the caller allows mail-address fallback and no active claim address exists, the mailing address type is substituted for the claim type | `pk72846_view_policy.getpolicyaddress` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-012 | When multiple contacts exist for a location, only the highest-priority contact type is returned | `pk72846_view_policy.getpolicyaddressbylocseqnum` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-013 | Risk-type code, sales-plan name, risk-type factor, and standard premium are masked for reduced-form periods until the information page has printed | `pk72846_view_policy.getpolicyperiods` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-014 | The affiliated-policy list and its optional totals row are keyed to a relationship-code family selected from the caller's affiliate-type prefix | `pk72846_view_policy.getpolicyaffiliatelist` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-015 | A team member's employee-status code can be overridden by an alias mapping | `pk72846_view_policy.team_assign_query` | [Details](procedures/pk72846_view_policy.md#business-rules) |
| PROC-RULE-016 | A policy "has a waiver" is defined purely as having at least one waiver-detail row | `Pk82033_ThirdParty.get_policy_waiver_count` | [Details](procedures/pk82033_thirdparty.md#business-rules) |
| PROC-RULE-017 | Employer-liability limits fall back through a fixed hierarchy from policy-specific current limits to legacy non-WCIS defaults | `coveragelimits_pkg.getSpecificLimitsForPolicyNum` | [Details](procedures/coveragelimits_pkg.md#business-rules) |
| PROC-RULE-018 | Cancellation and collection status is derived from a fixed priority order gated by the policy's status group | `sp72268_get_cancel_status` | [Details](procedures/sp72268_get_cancel_status.md#business-rules) |
| PROC-RULE-019 | Coverage-bound status is a thin Y/N wrapper over an external boolean check | `sp73089_get_policy_bound_flag` | [Details](procedures/sp73089_get_policy_bound_flag.md#business-rules) |
| PROC-RULE-020 | Information-page-printed status is a thin Y/N wrapper over an external boolean check | `sp72347_info_page_printed_yn` | [Details](procedures/sp72347_info_page_printed_yn.md#business-rules) |
| PROC-RULE-021 | External client e-mail lookup is a case-insensitive, trimmed exact match on both mail identifier and last name | `sp73341_external_client_lookup` | [Details](procedures/sp73341_external_client_lookup.md#business-rules) |
| <a id="proc-rule-022"></a>PROC-RULE-022 | External client e-mail lookup (legacy path) is a trimmed exact match on both mail identifier and last name | `cp_external_client_lookup` | [Details](procedures/cp_external_client_lookup.md#business-rules) |
| <a id="proc-rule-023"></a>PROC-RULE-023 | Only active, non-test registrant rows are eligible for the legacy e-mail lookup | `cp_external_client_lookup` | [Details](procedures/cp_external_client_lookup.md#business-rules) |
| <a id="proc-rule-024"></a>PROC-RULE-024 | When multiple registrant rows match the legacy e-mail lookup, the most recently updated row wins | `cp_external_client_lookup` | [Details](procedures/cp_external_client_lookup.md#business-rules) |
