Skip to main content

PowerFill Module — Phase 2 Complete

Date: 2026-04-16 Agent: Systems Architect Scope: Data access layer over the 17 Phase 1 pfill_* entities, PowerFill-owned projections over 9 upstream tables, a PowerFillPreflightService with 9 composable checks wired behind POST /api/powerfill/preflight, and both legacy views (pfillv_existng_pool_disposition + pfillv_pf_forensics_tradeside) shipped as SQL DDL. Includes a SharedDomain project extraction (Choice A3) that eliminates potential duplicate entity paths between BestEx and PowerFill.

Why

Phase 1 gave PowerFill tables and schema. Phase 2 makes those tables queryable through a SaaS-native read surface and gives tenants a single concrete question they can ask of the module: "Is my tenant ready to run PowerFill right now?"

The preflight service is also the first PowerFill module endpoint — it proves out the module → service → endpoint → DI wiring and the TenantDbContext + multi-tenant plumbing in a read-only context. Everything later phases add (pre-processing in Phase 3, constraint CRUD in Phase 4, the allocation engine in Phase 6) extends this surface rather than replacing it.

What Was Done

Phase 0/1 corrections (landed in this commit per same-commit discipline)

Three Phase-0 Truth Rot findings from the plan's Primary-Source Verification step:

  1. Two views, not one. The NVO creates pfillv_pf_forensics_tradeside (NVO line 6706) alongside pfillv_existng_pool_disposition (line 6730). Phase 0 spec + deep dive only mentioned one. The deep dive now documents both.
  2. pscat_securities_sec_rules is fictional. The NVO uses pscat_securitization_rules. Corrected in the deep dive (5 occurrences), the spec (BR-1), the ADR-023 context, and Assumption A4.
  3. pssaas-db was missing 8 tables PowerFill Phase 2 reads. Extended infra/sql/init/seed-schema.sql with minimal column sets (only the columns Phase 2 actually touches — full PS_DemoData replication is a separate infra concern).

Shared domain extraction (Choice A3)

New project PowerSeller.SaaS.SharedDomain holding Loan, Instrument, ProfileInstrumentMapping, TodaysPrice plus the two composite-key configurations. Both BestEx and PowerFill reference it. Registration is idempotent (TenantDbContext.RegisterEntityAssembly dedups), so both modules calling RegisterEntityAssembly(typeof(Loan).Assembly) is safe. Zero BestEx test regressions — all 32 still pass with no source edits needed beyond namespace re-import.

PowerFill Phase 2 code

  • 9 upstream read-only entities in PowerFill/Domain/UpstreamTrade, Pool, TradePoolRelation, LoanStage, PairOff, LoanShipped, RmcatLoan, SecuritizationRule, TradeAnalysisHistory. Each is a narrow column projection (not full-table mirror); column sets determined from NVO references + view requirements.
  • 2 keyless read-models in PowerFill/Domain/ReadModelsPoolDispositionReadModel, TradeForensicsReadModel. Configured via builder.HasNoKey().ToView("...").
  • Both legacy views in Sql/002_CreatePowerFillViews.sql — transcribed verbatim from NVO functions of_get_pfillv_existng_pool_disposition (line 12485) and of_get_pfillv_pf_forensics_tradeside (line 11187). CREATE OR ALTER VIEW for idempotent deployment.
  • docker-compose.yml mounts the PowerFill Sql/ directory into the db container; setup.sh loops module subdirectories and applies numeric-prefixed scripts. Phase 1's ValidatePS_DemoDataSchema.sql renamed (no prefix) so it's not auto-deployed.
  • PowerFillPreflightService + IPowerFillPreflightCheck interface + 9 concrete checks:
CodeSeverityTriggers when
NO_CONSTRAINTSErrorpfill_constraints is empty
NO_CARRY_COSTSErrorpfill_carry_cost is empty
BESTEX_SETUP_EMPTYErrorA constraint's investor_instrument_name has no row in rmcat_bx_setup_instr_inv
PRICES_STALEWarningrmcat_todays_prices latest market_date older than max_prices_age_days
SEC_RULE_MISSINGErrorA pfill_constraint_sec_rule_rel.rule_name doesn't exist in pscat_securitization_rules
PIPELINE_EMPTYErrorNo loans at or above min_status in pipeline
NO_OPEN_TRADESWarningNo open non-phantom pscat_trades with settlement_date within max_trade_settle_days
LOCKDOWN_ORPHAN_POOLWarningA pfill_lockdown_guide.pool_name doesn't exist in pscat_pools
CARRY_COST_COVERAGE_GAPWarningA constraint's investor_instrument_name has no pfill_carry_cost curve
  • POST /api/powerfill/preflight endpoint returning the aggregated report. 200 OK with is_ready: false when errors present; 400 only for malformed requests.
  • /powerfill/status updated from phase-1-schema-onlyphase-2-preflight-ready.

Tests

  • 16 new preflight unit tests covering happy path, sad path, and orchestration for each check. Uses EF Core InMemory.
  • ViewsIntegrationTests.Views_DeployCleanly_AndAreQueryableFromEfReadModels — transient SQL DB, deploys seed + 001 + 002, re-runs 002 (idempotency), queries both views through EF keyless read-models.
  • Full suite: 60 passing tests, 0 failures (32 BestEx + 27 PowerFill + 1 Api).

Key Decisions

  • Choice A3 (shared domain extraction). Eliminates the "silent parallel code paths" antipattern — four entities shared by both modules now live in one place. Costs one project + a module-registration line per consumer. Pays back as more modules come online.
  • Choice C1 (SQL views). Shipped both legacy views as SQL DDL rather than LINQ projections. The disposition view's 6-level CTE + ROW_NUMBER() OVER PARTITION BY + UNION ALL semantics are not something I want to re-derive in LINQ, for the same reason ADR-022 rejects rewriting the allocation engine.
  • Preflight is standalone (B1), not part of a future RunService. One service per concern matches the BestEx style; Phase 6 can add a thin readiness facade if needed.
  • Hardcoded preflight defaults for Phase 2. min_status = "Closed", max_prices_age_days = 2, max_trade_settle_days = 60. Phase 4 moves these to tenant-configurable values. Open Q #4 of the plan flagged these for Kevin/Jay/Greg to calibrate.

Files Produced / Modified

New

Modified

What's Next

Phase 3 per the spec: the pre-processing stored procedures, an orchestration service that calls them in the right order, and POST /api/powerfill/preprocess. The procedure set will be finalized via Primary-Source Verification Gate at Phase 3 planning time (procedure names and count will be confirmed against n_cst_powerfill.sru directly, not against this devlog).

Risks Captured

  1. Minimal seed schema means some Phase 3 work may reveal additional upstream columns needed. Mitigation: extend the seed incrementally; full PS_DemoData fidelity still tracked as a future infra task.
  2. pscat_securities and pscat_pools_sec_rule_rel are cited in the entity-relationship.md legacy docs but not in the Phase 2 scope. Phase 3 or Phase 6 will need them when sec-rule evaluation is implemented.
  3. Hardcoded preflight thresholds are Architect guesses. Kevin/Jay/Greg may have calibrated values. Phase 4 makes them configurable.
  4. Recurring Phase-0 Truth Rot. Three more findings in Phase 2 planning (after Phase 1's three). The gate is working; the pattern is that Phase 0 has unknown-unknowns we discover only by executing against primary source. Phase 3 planning will continue budget time for verification + corrections.