Skip to main content

ADR-023: PowerFill Constraint Model

Status

Proposed

Context

PowerFill's behavior is driven by a constraint model. Constraints determine which loans are eligible for which trades/pools and in what order the allocation engine considers candidates. The legacy Desktop App represents constraints as a hierarchical tree:

  • Investor level — e.g., "Fannie Mae loans only"
  • Instrument level — e.g., "FNMA 30yr Fixed only"
  • Sub-constraint level — references securitization rules, loan stage filters, etc.

This is captured in pfill_constraints (tree nodes) and pfill_constraint_sec_rule_rel (links to pscat_securitization_rules).

Constraints have a priority order (lower priority number = higher precedence). The allocation engine iterates constraints in priority order, letting higher-priority constraints consume loan/trade capacity first.

The legacy UI is a tree view in uo_tab_powerfill_constraints.sru with:

  • Add Constraint, Delete Constraint, Expand All, Collapse All, Expand Current (menu actions)
  • Prioritize Constraints modal (w_maint_pfill_constraint_order.srw) for bulk priority editing
  • Securitization rule assignment at any tree level

The PSSaaS port needs a constraint data model. Three options:

Option A: Preserve the Legacy Tree

Keep pfill_constraints and pfill_constraint_sec_rule_rel as-is. Expose tree operations via API: get tree, add node, delete node, reparent node, reorder priority.

Option B: Flatten to a Rule List

Replace the tree with a flat list of rules, each with a priority number and a rule definition (investor + instrument + conditions). No hierarchy — just prioritized rules.

Option C: Expression-Based Rules

Use a domain-specific language (like the BestEx feature adjustment rules) where each rule has a condition expression and an action. More flexible than Options A/B.

Decision

Adopt Option A: Preserve the legacy tree structure.

Specifically

  • PSSaaS pfill_constraints table has the same schema as the Desktop App's pfill_constraints
  • PSSaaS pfill_constraint_sec_rule_rel has the same schema
  • The C# domain model represents constraints as a Constraint entity with parent-child relationships
  • API exposes tree operations: GET /api/powerfill/constraints returns a tree; mutations respect tree semantics
  • Priority ordering is preserved: lower number = higher precedence
  • Bulk re-prioritization is a dedicated operation (POST /api/powerfill/constraints/reprioritize)

API Shape

GET /api/powerfill/constraints
→ 200 OK
[
{
"id": 1,
"priority": 10,
"level": "Investor",
"investor_name": "Fannie Mae",
"parent_id": null,
"sec_rule_ids": [],
"children": [
{
"id": 12,
"priority": 10,
"level": "Instrument",
"instrument_name": "FNMA 30yr Fixed",
"parent_id": 1,
"sec_rule_ids": [5, 12],
"children": []
}
]
},
...
]

Rationale

  • ADR-006 (Schema Preservation) — the pfill_* tables must match the Desktop App's schema during the coexistence period so both systems can operate against the same tenant database. Changing the constraint schema would force a migration before the Desktop App can be retired.
  • ADR-022 (Allocation Algorithm) — the allocation engine is a verbatim port of the T-SQL that traverses the constraint tree in priority order. Changing the constraint data model would force algorithm changes, which ADR-022 explicitly rejects.
  • Parallel validation — Tom/Greg's eventual review must be able to compare PSSaaS constraints to Desktop App constraints line-by-line. A different data model would make that comparison harder.
  • Customer migration — existing Desktop App tenants have hundreds of constraints defined. A tree-to-flat migration would have edge cases (how do child constraints inherit sec rules from parents?). Preserving the tree eliminates the migration.

Consequences

Positive

  • Zero migration required from Desktop App — existing constraints work as-is when a tenant transitions to PSSaaS
  • Faithful behavioral parity — same data model in + same algorithm = same output
  • Matches Tom/Greg's mental model — the tree is how they think about it
  • UI mapping is obvious — React tree component directly mirrors the data structure

Negative

  • Tree operations are more complex to expose via API — adds, deletes, moves, and reparents must preserve tree integrity
  • Not the most flexible model — adding a new constraint type (e.g., "loan-level conditions" beyond investor/instrument/sec rule) requires schema changes
  • Priority is a separate dimension from hierarchy — users must learn both (priority order + tree position)
  • Doesn't leverage modern rule-engine patterns (expression-based, attribute-based access control style)

Risks and Mitigations

RiskMitigation
Tree integrity violations (orphan nodes, circular references)Database constraints enforce parent-child integrity; API validates before write
Priority conflicts (two nodes with same priority)The legacy behavior appears to use insertion order for ties; we preserve that
User confusion between priority and hierarchyDocumentation and UI hints; priority editor modal makes precedence explicit
Tree depth unboundedPractically bounded by the three-level legacy convention (investor → instrument → sub); enforce max depth = 5 as a safeguard

Alternatives Considered and Rejected

Option B (Flat Rule List)

Rejected because: the legacy tree has semantic meaning. A constraint at the investor level applies to all child instruments; flattening would require duplicating the investor constraint onto every child rule. This would be a migration with edge cases (e.g., what if a user adds an investor-level sec rule after creating instrument-level children?).

Option C (Expression-Based Rules)

Rejected for Phase 1. An expression-based rule engine (like BestEx's feature adjustments) is a future modernization candidate. Introducing it in the initial port would diverge from the Desktop App behavior and make parallel validation impossible. A future ADR can supersede this one to adopt an expression model when warranted.

Future Modernization Path

If the legacy tree model becomes limiting, a future ADR could:

  1. Introduce expression-based rules alongside the tree (both coexist)
  2. Migrate existing tree constraints to equivalent expressions
  3. Deprecate the tree model
  4. Remove tree tables in a later migration

The key is that PSSaaS v1 = Desktop App parity. Modernization comes later, on customer demand.

Relationship to Other ADRs

  • ADR-006 (Schema Preservation) — this ADR is a direct application of the schema preservation principle
  • ADR-021 (Port Strategy) — constraint CRUD lives in C#; constraint evaluation lives in T-SQL procedures; the hybrid pattern applies
  • ADR-022 (Allocation Algorithm) — the algorithm walks the tree in priority order; this ADR preserves that tree
  • BestEx feature adjustments — a potential inspiration for Option C in the future, but not adopted here

Revision Triggers

This ADR should be revisited if:

  • Customer feedback indicates the tree model is too rigid
  • A specific use case (e.g., cross-investor sec rules, multi-dimensional conditions) cannot be expressed in the tree
  • The PSSaaS v1 port is complete and we want to modernize the data model for v2
  • A compelling expression-based rule engine emerges that clearly outperforms the tree