Proof of Concept Milestone: is_ready: true Against PS_DemoData
Date: 2026-04-16 evening (continuation of the prior PoC milestone devlog)
Agent: Collaborator
Outcome: Configured a real client database via Phase 4 CRUD APIs; PowerFill preflight returns is_ready: true with a single contextual warning that's correct behavior for a 4-year-old snapshot.
This closes the proof-of-concept loop: PSSaaS not only runs against a real PowerSeller client database, it can be configured through its own APIs and return a "ready to allocate" verdict. Tom-and-Greg-credible end-to-end.
What Was Done
1. Logic bug fix — PipelineReadinessCheck (Phase 2 finding, surfaced by demo)
The previous preflight returned PIPELINE_EMPTY even though loan had 7,564 rows with curr_status = "Closed". Root cause: when min_status doesn't exist in pscat_loan_stages, the check was accepting the entire stages table as the at-or-above set rather than falling back to exact-string-match. With PS_DemoData using different vocabularies in pscat_loan_stages vs loan.curr_status, this produced a meaningless IN-clause that matched zero loans.
Fix: rewrite the fallback ladder so the check distinguishes three cases — (a) min_status not in stages → exact match, (b) stage exists but no rank → exact match for that stage, (c) stage exists with rank → at-or-above set. Tests: 87/87 still pass; preflight against PS_DemoData now finds the closed loans.
2. Configured 3 PowerFill constraints via Phase 4 CRUD API
Each POST /api/powerfill/constraints succeeded with 201 + row_version token (proving the rowversion column from 005 works against PS_DemoData). All three constraints designed against the demo client's real instrument vocabulary (per pfill_carry_cost and pscat_pools data):
# Constraint 1: 85k bucket, priority 10, 85% pool target
POST /api/powerfill/constraints
{
"investor_id": "FHLMC",
"investor_instrument_name": "15 fhlmc cash 85k",
"constraint_name": "min_85k_pool_target",
"constraint_priority": 10,
"lo_limit": 0.0, "hi_limit": 1.0, "min_target": 0.85
}
→ 201 with row_version "AAAAAAABYbI="
# Constraint 2: 110k bucket, priority 20, 80% pool target
→ 201 with row_version "AAAAAAABYbM="
# Constraint 3: 125k bucket, priority 30, 75% pool target
→ 201 with row_version "AAAAAAABYbQ="
The incrementing rowversion tokens demonstrate the 005 ROWVERSION column is being populated by SQL Server on each insert as designed.
3. Configured 1 lockdown via Phase 4 lockdown CRUD
POST /api/powerfill/lockdown
{
"pool_name": "3006000026",
"investor_instrument_name": "HFS - PI",
"lock_pool": "y"
}
→ 201; subsequent GET /lockdown returned the row verbatim
Pool 3006000026 is a real HFS - PI (Held For Sale - Pre-Issuance) pool from the demo client.
4. Final preflight — is_ready: true
POST /api/powerfill/preflight
X-Tenant-Id: ps-demodata
{}
→ 200 OK
{
"tenant_id": "ps-demodata",
"checked_at": "2026-04-17T01:26:53Z",
"is_ready": true,
"summary": {"error_count": 0, "warning_count": 1, "info_count": 0},
"checks": [
{
"code": "PRICES_STALE",
"severity": 1,
"message": "Latest BestEx prices are older than 2 day(s).",
"context": {"most_recent_market_date": "2023-04-26", "threshold_days": 2}
}
]
}
The single remaining warning is correct behavior — a 4-year-old snapshot's prices are inherently stale by today's clock; the system noticed and reported it. No action needed for PoC purposes. (For a real production tenant, fresh prices would land via the daily import.)
The PoC Story (Finished)
PSSaaS, configured to point at a real PowerSeller client database (PS_DemoData on Azure SQL MI), accepts configuration through its own REST API, executes diagnostic checks against the legacy schema, returns structured "ready" verdicts, and produces actionable warnings about anything operators need to attend to.
This is what "the Desktop App can be migrated to SaaS, a piece at a time" actually looks like at the smallest end:
- The Connector Plugin (future) calls
POST /api/powerfill/preflightfrom PowerBuilder - PSSaaS reads the same SQL MI database the Desktop App reads
- PSSaaS returns
is_readyplus typed findings - The Desktop App displays the findings to the trader
- If
is_ready: true, the trader proceeds with allocation - If not, the trader fixes the issues (potentially through PSSaaS configuration APIs the Desktop App also calls)
Same database, same operations, gradually reimplemented in PSSaaS without rewriting the Desktop App.
Substantive Findings This Session
Counting from the start of the PS_DemoData wiring:
| # | Finding | Disposition | Surfaced in |
|---|---|---|---|
| T1 | LoanStage column names: loan_status → status_code, stage_order → stage_rank | Corrected in entity + seed-schema | Iteration 1 |
| T2 | pscat_pools.pool_status doesn't exist | Removed field from entity + seed-schema | Step B audit |
| T3 | pscat_pair_offs is a relationship table not event table; PK shape wrong | Rewrote entity + UpstreamEntityConfiguration + seed-schema | Step B audit |
| T4 | rmcat_ra_history_trades PK needs profile_name; analysis_id is int | Corrected entity + config + seed-schema | Step B audit |
| T5 | PS_DemoData compat level 120 incompatible with EF Core 8 OPENJSON | PO bumped to 150 in Azure Portal | Iteration 2 |
| T6 | PipelineReadinessCheck fallback logic loses against vocabulary mismatch | Rewrote fallback ladder | Iteration 3 (this devlog) |
Six findings, six fixes, no dead-ends. Each fix committed with traceable rationale. Each fix made the PoC more honest about what real customer data looks like.
Verification
dotnet build: 0 warnings, 0 errorsdotnet test: 87/87 passpfill_constraintsin PS_DemoData: 3 rows (was 0)pfill_lockdown_guidein PS_DemoData: 1 row (was 0)pfill_preflight_settingsin PS_DemoData: 1 row (default seed from script004, untouched)- Preflight returns 200 with
is_ready: true
What's Next (PO Decision)
This concrete demo opens three legitimate next moves:
- Connector Plugin spec — write the architecture story now that we have curl-by-curl evidence to anchor it
- Phase 5 dispatch (carry-cost calculator) — original next module phase
- Configure more PS_DemoData state — sec-rule associations on the constraints, more lockdowns, stress the CRUD surface
PO call — but the demo artifact is done.