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_constraintstable has the same schema as the Desktop App'spfill_constraints - PSSaaS
pfill_constraint_sec_rule_relhas the same schema - The C# domain model represents constraints as a
Constraintentity with parent-child relationships - API exposes tree operations:
GET /api/powerfill/constraintsreturns 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
| Risk | Mitigation |
|---|---|
| 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 hierarchy | Documentation and UI hints; priority editor modal makes precedence explicit |
| Tree depth unbounded | Practically 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:
- Introduce expression-based rules alongside the tree (both coexist)
- Migrate existing tree constraints to equivalent expressions
- Deprecate the tree model
- 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