Credit-Risk Committee Solver Skill (task_group_011)
A self-contained playbook for producing committee-ready JSON answers against the
shared credit-office public API. A solver sees only: a task prompt, the target
branch/segment identifier, an answer_template.json schema, this skill, and the
public API. Apply the workflow rules below; emit ONLY valid JSON matching the
task's template (no narrative).
0. Environment — Public REST API (read-only)
Base URL: <remote-env-url> (remote; do not look for a local server/db).
All endpoints return JSON, no auth. Use curl (pipe through jq to shape).
| Method |
Path |
Feeds which answer section |
| GET |
/api/health |
sanity check + record counts |
| GET |
/api/manifest |
benchmark_version strings (fdic_q4_2024, ncua_q1_2025), policy_version (credit_policy_v2025Q1), file list |
| GET |
/api/policies |
ALL business rules below (risk-rating, CDFI, stress, concentration). Read first; it is the source of truth. |
| GET |
/api/branches |
list; filter ?institution_type=bank / credit_union |
| GET |
/api/branches/{branch_id} |
branch_id, state_code, institution_type, lending_capacity_q1, sector_ceiling_pct, cre_policy_limit_pct, total_assets, fdic_benchmark_set |
| GET |
/api/branches/{branch_id}/metrics |
array by quarter; use the latest quarter matching the review date. Fields: total_loans_outstanding, nonperforming_loans, delinquency_30_plus_pct, allowance_for_loan_losses, net_charge_offs, total_deposits |
| GET |
/api/branches/{branch_id}/loans |
loans; optional ?loan_type=, ?payment_status=, ?min_current_rating= |
| GET |
/api/branches/{branch_id}/sector-exposures |
per-sector current_exposure + limit_pct (may override sector_ceiling_pct) + grandfathered flag |
| GET |
/api/branches/{branch_id}/applications |
pending applications; optional ?loan_type= |
| GET |
/api/benchmarks/fdic/q4-2024 |
FDIC Q4-2024 ratios (one object): total_loans_noncurrent_pct, total_real_estate_noncurrent_pct, construction_development_noncurrent_pct, total_real_estate_30_89_pct, construction_development_30_89_pct |
| GET |
/api/benchmarks/ncua/q1-2025 |
NCUA Q1-2025; optional ?state_code=. rows[] per state + US: delinquency_bps, loan_to_share_pct, roaa_bps, positive_net_income_pct |
| GET |
/api/credit-union-segments/{segment_id} |
segment: state_code, peer_states, quarterly_capacity, current_outstanding, risk_tolerance, minimum_checklist, internal_context (recent_delinquency_bps, control_issue, staffing_constraint, portfolio_yield_pct), portfolio_focus |
branch_id values are uppercase (REDWOOD, LAKEVIEW, CIVIC-style segments via the segment endpoint). Query-param filters are the documented way to scope loan/application lists (e.g. ?min_current_rating=3, ?min_current_rating=6).
1. Numeric & ordering conventions (universal)
- Money/USD → 2 decimals.
- Ratios / percentages expressed as ratios → 4 decimals (e.g. 0.4695, not 46.95%).
- Basis points → 2 decimals, SIGNED (branch minus benchmark; positive = branch worse than benchmark / over policy limit).
- Counts and rating/score factors → integers (exactly as reported for NCUA state metrics).
variance_bps = (branch_ratio - benchmark_ratio) * 10000, computed from FULL-PRECISION ratios then rounded to 2 dp. Do NOT recompute bps from already-rounded 4-dp ratios (rounding-then-multiplying loses precision and yields a different bps value). Always carry full precision through the subtraction and the *10000, then round only the final bps to 2 dp.
- Every list that has an
ordering rule in its template MUST be sorted that way before output. Common orderings: ascending loan_id (string sort), ascending by final_rating, ascending by action, descending exposure then ascending loan_id, ascending current_rating then payment_status, ascending state code, ascending alphabetically (reason codes / conditions).
loan_id / application_id are strings → sort lexicographically ("RED-LN-011" < "RED-LN-901").
2. Risk-rating re-derivation (from /api/policies risk_rating)
Re-derive a loan's rating from its objective factors, then take the WORST (max numeric) across available factors. Higher number = worse.
- DSCR thresholds:
>=1.5→3, >=1.25→4, >=1.05→5, >=1.0→6, <1.0→7.
- LTV thresholds:
<=0.65→3, <=0.75→4, <=0.85→5, <=1.0→6, >1.0→7.
- Delinquency floor (payment_status → minimum rating):
30 Days Past Due→4, 60 Days Past Due→5, 90+ Days Past Due→7, Nonaccrual→8, Current→none.
final_rating = max( available dscr_rating, available ltv_rating, delinquency_floor ). "Available" = skip a factor only when it is genuinely null (e.g. consumer/HELOC with no DSCR/LTV). If NO factor is available, retain the current rating.
downgrade_notches = final_rating - current_rating. material_downgrade_notches = 2 (from policy). A loan is a MATERIAL downgrade iff notches >= 2 (downgrades of exactly 1 notch are NOT material). Upgrades (negative notches) are not downgrades.
- Regrade population filter:
/loans?min_current_rating=N returns loans with current_rating >= N. "Loans currently rated 3 or worse" ⇒ target_current_rating_min = 3, i.e. ALL rated loans EXCLUDING those rated better than 3 (rating 1/2 exist and are excluded). Do NOT assume the scale starts at 3.
- Severe-delinquency override on the recommended action: a Nonaccrual loan's action is
partial_chargeoff_review when its final rating is 8. Confirmed via feedback: using legal_referral for a Nonaccrural/underwater credit LOWERED the score — partial_chargeoff_review is the correct action for rating-8 Nonaccrual. Reserve legal_referral for the most severe escalations only (do not trigger it merely from Nonaccrual). 90+ Days Past Due (floor 7) maps to workout.
3. Watch-list / action mapping (enum: monitor, watchlist, special-assets, workout, partial_chargeoff_review, legal_referral)
Map by the credit's RATING (re-derived final_rating for regrade tasks; current_rating for watch-list stress tasks), with payment_status overrides that coincide with the rating floor:
| rating |
action |
| 3 (and clean 4) |
monitor |
| 4 |
monitor |
| 5 |
watchlist |
| 6 |
special-assets |
| 7 |
workout |
| 8 (Nonaccrual) |
partial_chargeoff_review |
Payment-status override: 90+ Days Past Due → workout; Nonaccrual → partial_chargeoff_review. (These align with the delinquency floor so the action is consistent with the rating.)
Watch-list action COVERAGE includes the FULL regrade/watch-list population (every regraded loan is assigned an action, including monitor for the cleanest) — confirmed: covering all regraded loans scored higher than covering only problem credits. covered_loan_count / covered_exposure = total population; by_action is grouped ascending by action with per-action loan_count, exposure, loan_ids (ascending loan_id).
Note: the exact action for the mildest adverse bucket (rating 6 / Satisfactory-class) was not fully resolved by feedback — special-assets (by rating) and monitor/watchlist (by CDFI class) were both attempted; prefer the RATING-based mapping above for consistency with the regrade task.
4. NPA / FDIC benchmark variance
- For a BANK branch NPA review, choose the FDIC metric that matches the portfolio breadth. A mixed-portfolio branch (C&I + CRE + consumer) ⇒
total_loans_noncurrent_pct. The FDIC benchmark object is the single /api/benchmarks/fdic/q4-2024 record.
branch_npa_exposure = branch nonperforming_loans from the metrics table for the review quarter (this equals the sum of 90+DPD + Nonaccrual loan balances — they reconcile). branch_total_loans = total_loans_outstanding (NOT total_assets — using total_assets is the classic mistake).
branch_npa_ratio = branch_npa_exposure / branch_total_loans. variance_ratio = branch_npa_ratio - fdic_benchmark_ratio. variance_bps = variance_ratio * 10000, signed, full-precision-then-round.
- For CRE-specific tasks the FDIC metric enum is fixed to
total_real_estate_30_89_pct (0.0051). The branch delinquency ratio to compare is the branch's REPORTED delinquency_30_plus_pct from the metrics table (confirmed: using the reported metric scored higher than recomputing a real-estate-30-89 ratio from loan records). fdic_variance_bps = (branch_delinquency_ratio - 0.0051) * 10000, signed.
5. NCUA / credit-union segment posture
state_metrics = the target state's row from /api/benchmarks/ncua/q1-2025?state_code=.., integers EXACTLY as reported: delinquency_bps, loan_to_share_pct, roaa_bps, positive_net_income_pct. benchmark_version = "ncua_q1_2025".
peer_states = the segment's peer_states list, sorted ascending state code.
nc_vs_peer_median: median of the peer_states' NCUA rows (odd count ⇒ middle value after sorting). For each of the 4 metrics, direction higher/lower/equal = NC value vs comparison value (literal numeric compare, NOT better/worse).
external_risk_status: compare NC vs US AND NC vs peer-median across all 4 metrics. If NC is worse on every metric vs both ⇒ weaker_than_national_and_peers; mixed ⇒ mixed_vs_national_and_peers; better on all ⇒ stronger_than_national_and_peers.
posture: continue_with_tighter_conditions when capacity is available but external risk is weaker and/or operating-control issues exist (the common case); temporarily_pause only for a control breakdown severe enough to halt; continue_approving only when external risk is strong + controls clean.
capacity_status: capacity_available when the segment notes/capacity field say so (do NOT flip to capacity_constrained merely from a staffing note — the notes are authoritative). risk_tolerance = the segment's risk_tolerance field (e.g. moderate). committee_message:capacity_available_but_external_risk_weaker pairs with capacity_available + weaker external; pause_until_state_metrics_recover with pause; routine_approval_path_supported with continue_approving + strong.
required_checklist_gates = the segment's minimum_checklist (a subset of: board_authorization, equipment_invoice, fleet_replacement_plan, payer_contract_summary, public_contract_or_tax_support, proof_of_insurance, ucc_or_title_lien).
added_operating_controls — map each segment risk signal to a control: insurance-binder control_issue ⇒ pre_close_insurance_binder_verification; "added closing controls" note ⇒ lien_perfection_prior_to_funding; staffing_constraint (single senior underwriter) ⇒ senior_underwriter_second_review; recent delinquency elevated ⇒ monthly_segment_delinquency_watch; state delinquency above national median ⇒ quarterly_state_benchmark_monitoring; throughput/capacity overrun risk ⇒ committee_exception_for_capacity_overrun. Confirmed: adding senior_underwriter_second_review for the staffing constraint improved the score.
escalation_triggers (one per condition, ascending trigger_id) with owners by governance domain: missing_insurance_or_lien_exception→operations_control_manager; quarterly_capacity_exceeded_or_exception_requested→lending_committee_chair; segment_recent_delinquency_ge_90_bps→credit_risk_manager; state_delinquency_gap_widens_25_bps→operations_control_manager (external-benchmark monitoring). condition_choices / owner_choices are fixed enums.
6. CDFI-style risk classes (factor_score)
From /api/policies cdfi_factor_scores. Four objective factor tables, each scored (higher = worse); MISSING factor ⇒ score its WORST tier (confirmed: skip-missing scored far worse than missing→worst):
debt_to_asset: <0.40→0, 0.40-0.60→2, 0.60-0.80→4, >0.80→6; missing → 6.
fico: >720→0, 680-720→1, 580-679→3, <580→5; missing → 5.
liquidity_months: >12→0, 6-12→1, 3-6→3, <3→5; missing → 5.
ltv: <0.40→0, 0.40-0.60→2, 0.60-0.80→4, >0.80→6; missing → 6.
factor_score = SUM of the four sub-scores (worst-tier for missing).
risk_class from total: Prime 0-5, Desirable 6-9, Satisfactory 10-13, Watch 14-18, Doubtful >=19, Projected Loss >=19 AND ltv>1.0. (The missing→worst rule is what lets an underwater Nonaccrual credit reach >=19 and qualify as Projected Loss; with skip-missing it can never reach 19 — that is why missing→worst is required.)
7. DSCR stress (from /api/policies stress)
Use the policy formula LITERALLY (the policy string is the source of truth, not the +200bp label):
- Watch-list parallel shock
+200bp: stressed_dscr = dscr / (1 + 0.18) (= dscr / 1.18). coverage_breach_threshold = 1.0; breach = stressed_dscr < 1.0.
- CRE dual stress:
stressed_dscr = dscr * 0.85 / (1 + 0.18) (= dscr * 0.72034). Same threshold 1.0.
- Apply stress ONLY to loans/applications that HAVE a DSCR (
dscr non-null). Exclude consumer/residential/HELOC with null DSCR from stress_results (the template says "loans with DSCR available").
breach_loan_ids (where the schema has it) = ascending loan_id list of loans with stressed_dscr < threshold.
shock_label = +200bp. formula field = the policy formula string verbatim.
8. Lending capacity & sector concentration (allocation tasks)
lending_capacity_q1 = branch field. Approve (and conditionally-approved) applications consume bank_capacity_used = their approved amount (full retention unless participation truly reduces retained exposure). Sum cannot exceed capacity → lower-priority approvable applications are declined for capacity_limit (or deferred) once capacity is exhausted. gross_approved_amount = sum of approved_amount over approve+conditional_approve; committed_capacity_amount = sum of bank_capacity_used (same set); remaining_capacity = capacity - committed.
priority_ranking = ordered application_ids, highest priority first, INCLUDING approved AND conditionally approved only (not declines/defers).
- Sector concentration per application =
(sector_exposure + requested_amount) / total_loans_outstanding (denominator = total_loans_outstanding, NOT total_assets). limit_pct comes from the sector-exposure row (may override sector_ceiling_pct, e.g. Healthcare 0.19). Flag (breach) when post > limit. handling = the decision applied to that application (conditional_approve / decline / participation_required / approve / none).
- Post-approval FINAL sector concentrations:
exposure_after_approval = existing_sector_exposure + sum(approved amounts in that sector); post_approval_pct = exposure_after_approval / total_loans_outstanding (denominator FIXED at the current total_loans_outstanding for the per-sector final view — confirmed for concentration flags; for CRE-policy-concentration in the CRE task the denominator GROWS by the selected amount, see §9). over_limit = post_approval_pct > limit_pct. Include ALL sectors that have any exposure (existing or newly approved), sorted ascending by sector.
- Sector-breach handling per policy: "Existing over-ceiling exposure may be grandfathered, but new approvals may not worsen that sector WITHOUT mitigation." Allowed mitigations:
participation_required, reduced_amount, board_exception. So a sector-breaching STRONG credit is mitigated (conditional_approve / participation_required) rather than auto-declined; an already-breached sector with a weak credit may be declined. (The exact mitigation-vs-decline boundary was not fully pinned by feedback — when capacity is ample, prefer mitigating strong credits; reserve sector_breach decline for weak credits that breach.)
- CRE policy concentration limit = branch
cre_policy_limit_pct (e.g. 0.29/0.31). Compare existing + selected CRE exposure to this limit (see §9).
9. CRE competing-application decision
existing_cre_exposure = sum of outstanding_balance over loans with loan_type == "CRE". existing_cre_concentration = existing_cre_exposure / total_loans_outstanding (4 dp).
selected_post_approval_cre_concentration = (existing_cre_exposure + selected_amount) / (total_loans_outstanding + selected_amount) — denominator GROWS by the selected amount (confirmed: growing denominator scored higher than fixed). selected_policy_variance_bps = (selected_post_approval_cre_concentration - cre_policy_limit_pct) * 10000, signed.
- CRE weighted score (
/api/policies cre_weighted_score): weights capacity 0.45, capital 0.03, character 0.05, collateral_exposure 0.36, conditions 0.11 (sum 1.0). Score each of the 5 Cs 0..5 (0 best, lower is better) from the application's objective factors (capacity≈DSCR, capital≈debt_to_asset=total_debt/total_assets, character≈fico or guarantor-strength when fico null, collateral_exposure≈LTV, conditions≈sector/conditions). weighted_cdfi_score = Σ weight·factor_score (1 dp). score_class: approve_quality if score <= 2.0, conditional if <= 3.0, weak if > 3.0. Select the application with the LOWER score (better credit). (The exact per-factor 0..5 thresholds are not in the policy; derive them consistently so the stronger credit scores lower and lands in the better class. Tolerate residual uncertainty on the exact 1-dp value; the class + selection is the load-bearing output.)
recommended_path: selected_application_id = stronger (lower-score) application; path = conditional_approve when CRE is already/policy-over-limit (committee exception + conditions) or approve when within policy; unselected_application_id = the other. unselected_disposition = "defer" (NOT decline) — the unselected competing credit is DEFERRED, and unselected_reason_codes are the DEFERRAL reasons drawn from {sector_breach, weak_dscr, high_ltv, fdic_adverse_variance} (sorted alphabetically). Confirmed: switching the unselected disposition from decline to defer raised the score.
applications_compared[].decision mirrors the path decision for the selected and defer for the unselected. reason_codes per application use the full reason-code enum (sorted alphabetically); the selected (clean) application typically has [].
conditions (selected credit, ascending alphabetically) from the enum: bank_retained_exposure_cap, committee_cre_exception, minimum_dscr_covenant_1_25, no_additional_cre_without_committee_review, quarterly_financial_reporting, tenant_roll_and_lease_review, updated_appraisal_before_close. For a CRE credit over policy limit: include committee_cre_exception and no_additional_cre_without_committee_review at minimum; add minimum_dscr_covenant_1_25, updated_appraisal_before_close, tenant_roll_and_lease_review, quarterly_financial_reporting for a full CRE condition set.
10. Output field & enum reference (from each task's answer_template.json — always re-read it)
Decision enum: approve, conditional_approve, decline, defer, participation_required.
Conditions enum (allocation): participation_required, reduced_amount, board_exception, sba_guaranty_required, startup_monitoring, none. (NOTE: conditions is NOT the place for decline reason codes — reason codes go ONLY in decline_reasons. Putting reason codes in conditions is a schema-breaking mistake.)
Decline reason-code enum: capacity_limit, sector_breach, weak_dscr, high_ltv, low_fico, recent_bankruptcy, startup_risk, underwater_collateral, policy_floor_missing, documentation_gap, fdic_adverse_variance, ncua_peer_weakness. decline_reasons maps each DECLINED application_id → its sorted list of reason codes (only declined apps appear).
concentration_flags handling enum: approve, conditional_approve, decline, participation_required, none.
Payment-status enum (used in loans/answers): Current, 30 Days Past Due, 60 Days Past Due, 90+ Days Past Due, Nonaccrual.
Watch-list/Stress action enum: monitor, watchlist, special-assets, workout, partial_chargeoff_review, legal_referral.
CDFI risk-class enum: Prime, Desirable, Satisfactory, Watch, Doubtful, Projected Loss.
Posture enum: continue_approving, continue_with_tighter_conditions, temporarily_pause. capacity_status: capacity_available, capacity_constrained, no_capacity. external_risk_status: stronger_than_national_and_peers, mixed_vs_national_and_peers, weaker_than_national_and_peers. risk_tolerance: restrained, moderate, expansive. committee_message: capacity_available_but_external_risk_weaker, pause_until_state_metrics_recover, routine_approval_path_supported. monitoring_cadence: monthly, quarterly, semiannual.
11. Common misjudgments & exclusion rules (generalized from feedback)
- Regrade population ≠ watch-list population. "Rated 3 or worse" ⇒
min_current_rating=3 ⇒ ALL rated loans except those rated 1/2. "Adversely rated 6 or worse" (watch-list stress) ⇒ min_current_rating=6. Do not conflate the two filters.
- Watch-list action COVERAGE = full population. Cover every regraded loan with an action (incl.
monitor), not just problem credits. covered_loan_count = the whole regrade/watch-list count.
- Severe-delinquency action override: Nonaccrual →
partial_chargeoff_review (rating 8), NOT legal_referral. Using legal_referral for a Nonaccrual/underwater credit was penalized. 90+DPD → workout.
conditions field uses the conditions enum only; decline REASON codes live in decline_reasons. Never cross the two.
- Concentration denominator =
total_loans_outstanding, NEVER total_assets. For per-application concentration flags and existing concentration it is the CURRENT total_loans_outstanding. For the CRE policy variance the post-approval denominator GROWS by the selected amount.
variance_bps from full precision, then round. Do not round ratios to 4 dp first and then multiply by 10000.
- CDFI
factor_score: missing factor ⇒ worst tier (dta→6, fico→5, liq→5, ltv→6), NOT skip. Skip-missing was strongly penalized and makes Projected Loss (>=19) unreachable for fico-missing underwater credits.
- Stress = policy formula literal (
dscr/1.18 watch-list; dscr*0.85/1.18 CRE dual). The +200bp is the label, not a per-loan rate add-on. Apply only where DSCR exists.
- NPA branch ratio uses the metrics table (
nonperforming_loans, delinquency_30_plus_pct); do not hand-recompute a different denominator unless the metric definition demands it.
- Unselected competing credit ⇒
defer with deferral reason codes, not decline.
material_downgrades = notches >= 2 only (policy material_downgrade_notches=2); 1-notch downgrades and upgrades are excluded from that list.
- Always sort lists per the template's
ordering before emitting; ascending loan_id is lexicographic string sort.
- Operating controls are warranted by specific segment signals (control_issue→insurance-binder verification; staffing_constraint→senior-underwriter second review; recent delinquency→monthly delinquency watch; state delinquency above median→quarterly state benchmark monitoring; throughput overrun risk→committee exception for capacity overrun).
- NCUA directions are literal numeric (higher/lower/equal), not better/worse — a higher delinquency_bps is "higher" even though it is worse.
12. Solving procedure per task
GET /api/policies first; load the risk-rating, CDFI, stress, concentration rules into memory.
GET /api/branches/{branch_id} + /metrics (latest quarter matching the review date) + /sector-exposures + /loans (with the documented min_current_rating/loan_type/payment_status filter for the task) + /applications. For credit-union tasks GET /api/credit-union-segments/{segment_id} + /api/benchmarks/ncua/q1-2025. For FDIC tasks GET /api/benchmarks/fdic/q4-2024.
- Re-derive ratings / CDFI classes / stress / concentrations per §§2–9, computing from full precision and rounding outputs to the template's precision (§1).
- Build the JSON object matching the task's
answer_template.json exactly (required top-level keys, required sub-keys, enums from §10, orderings from the template).
- Validate every list ordering and every enum value before emitting. Emit ONLY the JSON.
1---2name: reflect-3-attempt-01-183description: Credit-Risk Committee Solver Skill (task_group_011)4---5# Credit-Risk Committee Solver Skill (task_group_011)67A self-contained playbook for producing committee-ready JSON answers against the8shared credit-office public API. A solver sees only: a task prompt, the target9branch/segment identifier, an `answer_template.json` schema, this skill, and the10public API. Apply the workflow rules below; emit ONLY valid JSON matching the11task's template (no narrative).1213## 0. Environment — Public REST API (read-only)1415Base URL: `<remote-env-url>` (remote; do not look for a local server/db).16All endpoints return JSON, no auth. Use `curl` (pipe through `jq` to shape).1718| Method | Path | Feeds which answer section |19| --- | --- | --- |20| GET | `/api/health` | sanity check + record counts |21| GET | `/api/manifest` | `benchmark_version` strings (`fdic_q4_2024`, `ncua_q1_2025`), `policy_version` (`credit_policy_v2025Q1`), file list |22| GET | `/api/policies` | ALL business rules below (risk-rating, CDFI, stress, concentration). Read first; it is the source of truth. |23| GET | `/api/branches` | list; filter `?institution_type=bank` / `credit_union` |24| GET | `/api/branches/{branch_id}` | branch_id, state_code, institution_type, lending_capacity_q1, sector_ceiling_pct, cre_policy_limit_pct, total_assets, fdic_benchmark_set |25| GET | `/api/branches/{branch_id}/metrics` | array by quarter; use the latest quarter matching the review date. Fields: total_loans_outstanding, nonperforming_loans, delinquency_30_plus_pct, allowance_for_loan_losses, net_charge_offs, total_deposits |26| GET | `/api/branches/{branch_id}/loans` | loans; optional `?loan_type=`, `?payment_status=`, `?min_current_rating=` |27| GET | `/api/branches/{branch_id}/sector-exposures` | per-sector current_exposure + limit_pct (may override sector_ceiling_pct) + grandfathered flag |28| GET | `/api/branches/{branch_id}/applications` | pending applications; optional `?loan_type=` |29| GET | `/api/benchmarks/fdic/q4-2024` | FDIC Q4-2024 ratios (one object): total_loans_noncurrent_pct, total_real_estate_noncurrent_pct, construction_development_noncurrent_pct, total_real_estate_30_89_pct, construction_development_30_89_pct |30| GET | `/api/benchmarks/ncua/q1-2025` | NCUA Q1-2025; optional `?state_code=`. `rows[]` per state + US: delinquency_bps, loan_to_share_pct, roaa_bps, positive_net_income_pct |31| GET | `/api/credit-union-segments/{segment_id}` | segment: state_code, peer_states, quarterly_capacity, current_outstanding, risk_tolerance, minimum_checklist, internal_context (recent_delinquency_bps, control_issue, staffing_constraint, portfolio_yield_pct), portfolio_focus |3233`branch_id` values are uppercase (`REDWOOD`, `LAKEVIEW`, `CIVIC`-style segments via the segment endpoint). Query-param filters are the documented way to scope loan/application lists (e.g. `?min_current_rating=3`, `?min_current_rating=6`).3435## 1. Numeric & ordering conventions (universal)3637- Money/USD → 2 decimals.38- Ratios / percentages expressed as ratios → 4 decimals (e.g. 0.4695, not 46.95%).39- Basis points → 2 decimals, SIGNED (branch minus benchmark; positive = branch worse than benchmark / over policy limit).40- Counts and rating/score factors → integers (exactly as reported for NCUA state metrics).41- `variance_bps = (branch_ratio - benchmark_ratio) * 10000`, computed from FULL-PRECISION ratios then rounded to 2 dp. Do NOT recompute bps from already-rounded 4-dp ratios (rounding-then-multiplying loses precision and yields a different bps value). Always carry full precision through the subtraction and the `*10000`, then round only the final bps to 2 dp.42- Every list that has an `ordering` rule in its template MUST be sorted that way before output. Common orderings: `ascending loan_id` (string sort), `ascending by final_rating`, `ascending by action`, `descending exposure then ascending loan_id`, `ascending current_rating then payment_status`, `ascending state code`, `ascending alphabetically` (reason codes / conditions).43- `loan_id` / `application_id` are strings → sort lexicographically ("RED-LN-011" < "RED-LN-901").4445## 2. Risk-rating re-derivation (from `/api/policies` `risk_rating`)4647Re-derive a loan's rating from its objective factors, then take the WORST (max numeric) across available factors. Higher number = worse.4849- DSCR thresholds: `>=1.5→3`, `>=1.25→4`, `>=1.05→5`, `>=1.0→6`, `<1.0→7`.50- LTV thresholds: `<=0.65→3`, `<=0.75→4`, `<=0.85→5`, `<=1.0→6`, `>1.0→7`.51- Delinquency floor (payment_status → minimum rating): `30 Days Past Due→4`, `60 Days Past Due→5`, `90+ Days Past Due→7`, `Nonaccrual→8`, `Current→none`.52- `final_rating = max( available dscr_rating, available ltv_rating, delinquency_floor )`. "Available" = skip a factor only when it is genuinely null (e.g. consumer/HELOC with no DSCR/LTV). If NO factor is available, retain the current rating.53- `downgrade_notches = final_rating - current_rating`. `material_downgrade_notches = 2` (from policy). A loan is a MATERIAL downgrade iff notches >= 2 (downgrades of exactly 1 notch are NOT material). Upgrades (negative notches) are not downgrades.54- Regrade population filter: `/loans?min_current_rating=N` returns loans with current_rating >= N. "Loans currently rated 3 or worse" ⇒ `target_current_rating_min = 3`, i.e. ALL rated loans EXCLUDING those rated better than 3 (rating 1/2 exist and are excluded). Do NOT assume the scale starts at 3.55- Severe-delinquency override on the recommended action: a Nonaccrual loan's action is `partial_chargeoff_review` when its final rating is 8. Confirmed via feedback: using `legal_referral` for a Nonaccrural/underwater credit LOWERED the score — `partial_chargeoff_review` is the correct action for rating-8 Nonaccrual. Reserve `legal_referral` for the most severe escalations only (do not trigger it merely from Nonaccrual). `90+ Days Past Due` (floor 7) maps to `workout`.5657## 3. Watch-list / action mapping (enum: monitor, watchlist, special-assets, workout, partial_chargeoff_review, legal_referral)5859Map by the credit's RATING (re-derived final_rating for regrade tasks; current_rating for watch-list stress tasks), with payment_status overrides that coincide with the rating floor:6061| rating | action |62| --- | --- |63| 3 (and clean 4) | monitor |64| 4 | monitor |65| 5 | watchlist |66| 6 | special-assets |67| 7 | workout |68| 8 (Nonaccrual) | partial_chargeoff_review |6970Payment-status override: `90+ Days Past Due → workout`; `Nonaccrual → partial_chargeoff_review`. (These align with the delinquency floor so the action is consistent with the rating.)71Watch-list action COVERAGE includes the FULL regrade/watch-list population (every regraded loan is assigned an action, including `monitor` for the cleanest) — confirmed: covering all regraded loans scored higher than covering only problem credits. `covered_loan_count` / `covered_exposure` = total population; `by_action` is grouped ascending by action with per-action `loan_count`, `exposure`, `loan_ids` (ascending loan_id).72Note: the exact action for the mildest adverse bucket (rating 6 / Satisfactory-class) was not fully resolved by feedback — `special-assets` (by rating) and `monitor`/`watchlist` (by CDFI class) were both attempted; prefer the RATING-based mapping above for consistency with the regrade task.7374## 4. NPA / FDIC benchmark variance7576- For a BANK branch NPA review, choose the FDIC metric that matches the portfolio breadth. A mixed-portfolio branch (C&I + CRE + consumer) ⇒ `total_loans_noncurrent_pct`. The FDIC benchmark object is the single `/api/benchmarks/fdic/q4-2024` record.77- `branch_npa_exposure` = branch `nonperforming_loans` from the metrics table for the review quarter (this equals the sum of 90+DPD + Nonaccrual loan balances — they reconcile). `branch_total_loans` = `total_loans_outstanding` (NOT total_assets — using total_assets is the classic mistake).78- `branch_npa_ratio = branch_npa_exposure / branch_total_loans`. `variance_ratio = branch_npa_ratio - fdic_benchmark_ratio`. `variance_bps = variance_ratio * 10000`, signed, full-precision-then-round.79- For CRE-specific tasks the FDIC metric enum is fixed to `total_real_estate_30_89_pct` (0.0051). The branch delinquency ratio to compare is the branch's REPORTED `delinquency_30_plus_pct` from the metrics table (confirmed: using the reported metric scored higher than recomputing a real-estate-30-89 ratio from loan records). `fdic_variance_bps = (branch_delinquency_ratio - 0.0051) * 10000`, signed.8081## 5. NCUA / credit-union segment posture8283- `state_metrics` = the target state's row from `/api/benchmarks/ncua/q1-2025?state_code=..`, integers EXACTLY as reported: `delinquency_bps`, `loan_to_share_pct`, `roaa_bps`, `positive_net_income_pct`. `benchmark_version = "ncua_q1_2025"`.84- `peer_states` = the segment's `peer_states` list, sorted ascending state code.85- `nc_vs_peer_median`: median of the `peer_states`' NCUA rows (odd count ⇒ middle value after sorting). For each of the 4 metrics, direction `higher`/`lower`/`equal` = NC value vs comparison value (literal numeric compare, NOT better/worse).86- `external_risk_status`: compare NC vs US AND NC vs peer-median across all 4 metrics. If NC is worse on every metric vs both ⇒ `weaker_than_national_and_peers`; mixed ⇒ `mixed_vs_national_and_peers`; better on all ⇒ `stronger_than_national_and_peers`.87- `posture`: `continue_with_tighter_conditions` when capacity is available but external risk is weaker and/or operating-control issues exist (the common case); `temporarily_pause` only for a control breakdown severe enough to halt; `continue_approving` only when external risk is strong + controls clean.88- `capacity_status`: `capacity_available` when the segment notes/capacity field say so (do NOT flip to `capacity_constrained` merely from a staffing note — the notes are authoritative). `risk_tolerance` = the segment's `risk_tolerance` field (e.g. `moderate`). `committee_message`:`capacity_available_but_external_risk_weaker` pairs with capacity_available + weaker external; `pause_until_state_metrics_recover` with pause; `routine_approval_path_supported` with continue_approving + strong.89- `required_checklist_gates` = the segment's `minimum_checklist` (a subset of: board_authorization, equipment_invoice, fleet_replacement_plan, payer_contract_summary, public_contract_or_tax_support, proof_of_insurance, ucc_or_title_lien).90- `added_operating_controls` — map each segment risk signal to a control: insurance-binder control_issue ⇒ `pre_close_insurance_binder_verification`; "added closing controls" note ⇒ `lien_perfection_prior_to_funding`; staffing_constraint (single senior underwriter) ⇒ `senior_underwriter_second_review`; recent delinquency elevated ⇒ `monthly_segment_delinquency_watch`; state delinquency above national median ⇒ `quarterly_state_benchmark_monitoring`; throughput/capacity overrun risk ⇒ `committee_exception_for_capacity_overrun`. Confirmed: adding `senior_underwriter_second_review` for the staffing constraint improved the score.91- `escalation_triggers` (one per condition, ascending trigger_id) with owners by governance domain: `missing_insurance_or_lien_exception`→`operations_control_manager`; `quarterly_capacity_exceeded_or_exception_requested`→`lending_committee_chair`; `segment_recent_delinquency_ge_90_bps`→`credit_risk_manager`; `state_delinquency_gap_widens_25_bps`→`operations_control_manager` (external-benchmark monitoring). condition_choices / owner_choices are fixed enums.9293## 6. CDFI-style risk classes (factor_score)9495From `/api/policies` `cdfi_factor_scores`. Four objective factor tables, each scored (higher = worse); MISSING factor ⇒ score its WORST tier (confirmed: skip-missing scored far worse than missing→worst):96- `debt_to_asset`: `<0.40→0`, `0.40-0.60→2`, `0.60-0.80→4`, `>0.80→6`; missing → 6.97- `fico`: `>720→0`, `680-720→1`, `580-679→3`, `<580→5`; missing → 5.98- `liquidity_months`: `>12→0`, `6-12→1`, `3-6→3`, `<3→5`; missing → 5.99- `ltv`: `<0.40→0`, `0.40-0.60→2`, `0.60-0.80→4`, `>0.80→6`; missing → 6.100- `factor_score` = SUM of the four sub-scores (worst-tier for missing).101- `risk_class` from total: `Prime 0-5`, `Desirable 6-9`, `Satisfactory 10-13`, `Watch 14-18`, `Doubtful >=19`, `Projected Loss >=19 AND ltv>1.0`. (The missing→worst rule is what lets an underwater Nonaccrual credit reach >=19 and qualify as Projected Loss; with skip-missing it can never reach 19 — that is why missing→worst is required.)102103## 7. DSCR stress (from `/api/policies` `stress`)104105Use the policy formula LITERALLY (the policy string is the source of truth, not the `+200bp` label):106- Watch-list parallel shock `+200bp`: `stressed_dscr = dscr / (1 + 0.18)` (= dscr / 1.18). `coverage_breach_threshold = 1.0`; breach = stressed_dscr < 1.0.107- CRE dual stress: `stressed_dscr = dscr * 0.85 / (1 + 0.18)` (= dscr * 0.72034). Same threshold 1.0.108- Apply stress ONLY to loans/applications that HAVE a DSCR (`dscr` non-null). Exclude consumer/residential/HELOC with null DSCR from `stress_results` (the template says "loans with DSCR available").109- `breach_loan_ids` (where the schema has it) = ascending loan_id list of loans with stressed_dscr < threshold.110- `shock_label` = `+200bp`. `formula` field = the policy formula string verbatim.111112## 8. Lending capacity & sector concentration (allocation tasks)113114- `lending_capacity_q1` = branch field. Approve (and conditionally-approved) applications consume `bank_capacity_used` = their approved amount (full retention unless participation truly reduces retained exposure). Sum cannot exceed capacity → lower-priority approvable applications are declined for `capacity_limit` (or deferred) once capacity is exhausted. `gross_approved_amount` = sum of approved_amount over approve+conditional_approve; `committed_capacity_amount` = sum of bank_capacity_used (same set); `remaining_capacity = capacity - committed`.115- `priority_ranking` = ordered application_ids, highest priority first, INCLUDING approved AND conditionally approved only (not declines/defers).116- Sector concentration per application = `(sector_exposure + requested_amount) / total_loans_outstanding` (denominator = total_loans_outstanding, NOT total_assets). `limit_pct` comes from the sector-exposure row (may override `sector_ceiling_pct`, e.g. Healthcare 0.19). Flag (breach) when post > limit. `handling` = the decision applied to that application (conditional_approve / decline / participation_required / approve / none).117- Post-approval FINAL sector concentrations: `exposure_after_approval = existing_sector_exposure + sum(approved amounts in that sector)`; `post_approval_pct = exposure_after_approval / total_loans_outstanding` (denominator FIXED at the current total_loans_outstanding for the per-sector final view — confirmed for concentration flags; for CRE-policy-concentration in the CRE task the denominator GROWS by the selected amount, see §9). `over_limit = post_approval_pct > limit_pct`. Include ALL sectors that have any exposure (existing or newly approved), sorted ascending by sector.118- Sector-breach handling per policy: "Existing over-ceiling exposure may be grandfathered, but new approvals may not worsen that sector WITHOUT mitigation." Allowed mitigations: `participation_required`, `reduced_amount`, `board_exception`. So a sector-breaching STRONG credit is mitigated (conditional_approve / participation_required) rather than auto-declined; an already-breached sector with a weak credit may be declined. (The exact mitigation-vs-decline boundary was not fully pinned by feedback — when capacity is ample, prefer mitigating strong credits; reserve `sector_breach` decline for weak credits that breach.)119- CRE policy concentration limit = branch `cre_policy_limit_pct` (e.g. 0.29/0.31). Compare existing + selected CRE exposure to this limit (see §9).120121## 9. CRE competing-application decision122123- `existing_cre_exposure` = sum of `outstanding_balance` over loans with `loan_type == "CRE"`. `existing_cre_concentration = existing_cre_exposure / total_loans_outstanding` (4 dp).124- `selected_post_approval_cre_concentration = (existing_cre_exposure + selected_amount) / (total_loans_outstanding + selected_amount)` — denominator GROWS by the selected amount (confirmed: growing denominator scored higher than fixed). `selected_policy_variance_bps = (selected_post_approval_cre_concentration - cre_policy_limit_pct) * 10000`, signed.125- CRE weighted score (`/api/policies` `cre_weighted_score`): weights `capacity 0.45, capital 0.03, character 0.05, collateral_exposure 0.36, conditions 0.11` (sum 1.0). Score each of the 5 Cs 0..5 (0 best, lower is better) from the application's objective factors (capacity≈DSCR, capital≈debt_to_asset=total_debt/total_assets, character≈fico or guarantor-strength when fico null, collateral_exposure≈LTV, conditions≈sector/conditions). `weighted_cdfi_score = Σ weight·factor_score` (1 dp). `score_class`: `approve_quality` if score <= 2.0, `conditional` if <= 3.0, `weak` if > 3.0. Select the application with the LOWER score (better credit). (The exact per-factor 0..5 thresholds are not in the policy; derive them consistently so the stronger credit scores lower and lands in the better class. Tolerate residual uncertainty on the exact 1-dp value; the class + selection is the load-bearing output.)126- `recommended_path`: `selected_application_id` = stronger (lower-score) application; `path` = `conditional_approve` when CRE is already/policy-over-limit (committee exception + conditions) or `approve` when within policy; `unselected_application_id` = the other. `unselected_disposition = "defer"` (NOT decline) — the unselected competing credit is DEFERRED, and `unselected_reason_codes` are the DEFERRAL reasons drawn from `{sector_breach, weak_dscr, high_ltv, fdic_adverse_variance}` (sorted alphabetically). Confirmed: switching the unselected disposition from `decline` to `defer` raised the score.127- `applications_compared[].decision` mirrors the path decision for the selected and `defer` for the unselected. `reason_codes` per application use the full reason-code enum (sorted alphabetically); the selected (clean) application typically has `[]`.128- `conditions` (selected credit, ascending alphabetically) from the enum: `bank_retained_exposure_cap, committee_cre_exception, minimum_dscr_covenant_1_25, no_additional_cre_without_committee_review, quarterly_financial_reporting, tenant_roll_and_lease_review, updated_appraisal_before_close`. For a CRE credit over policy limit: include `committee_cre_exception` and `no_additional_cre_without_committee_review` at minimum; add `minimum_dscr_covenant_1_25`, `updated_appraisal_before_close`, `tenant_roll_and_lease_review`, `quarterly_financial_reporting` for a full CRE condition set.129130## 10. Output field & enum reference (from each task's answer_template.json — always re-read it)131132Decision enum: `approve, conditional_approve, decline, defer, participation_required`.133Conditions enum (allocation): `participation_required, reduced_amount, board_exception, sba_guaranty_required, startup_monitoring, none`. (NOTE: `conditions` is NOT the place for decline reason codes — reason codes go ONLY in `decline_reasons`. Putting reason codes in `conditions` is a schema-breaking mistake.)134Decline reason-code enum: `capacity_limit, sector_breach, weak_dscr, high_ltv, low_fico, recent_bankruptcy, startup_risk, underwater_collateral, policy_floor_missing, documentation_gap, fdic_adverse_variance, ncua_peer_weakness`. `decline_reasons` maps each DECLINED application_id → its sorted list of reason codes (only declined apps appear).135concentration_flags `handling` enum: `approve, conditional_approve, decline, participation_required, none`.136Payment-status enum (used in loans/answers): `Current, 30 Days Past Due, 60 Days Past Due, 90+ Days Past Due, Nonaccrual`.137Watch-list/Stress action enum: `monitor, watchlist, special-assets, workout, partial_chargeoff_review, legal_referral`.138CDFI risk-class enum: `Prime, Desirable, Satisfactory, Watch, Doubtful, Projected Loss`.139Posture enum: `continue_approving, continue_with_tighter_conditions, temporarily_pause`. capacity_status: `capacity_available, capacity_constrained, no_capacity`. external_risk_status: `stronger_than_national_and_peers, mixed_vs_national_and_peers, weaker_than_national_and_peers`. risk_tolerance: `restrained, moderate, expansive`. committee_message: `capacity_available_but_external_risk_weaker, pause_until_state_metrics_recover, routine_approval_path_supported`. monitoring_cadence: `monthly, quarterly, semiannual`.140141## 11. Common misjudgments & exclusion rules (generalized from feedback)1421431. **Regrade population ≠ watch-list population.** "Rated 3 or worse" ⇒ `min_current_rating=3` ⇒ ALL rated loans except those rated 1/2. "Adversely rated 6 or worse" (watch-list stress) ⇒ `min_current_rating=6`. Do not conflate the two filters.1442. **Watch-list action COVERAGE = full population.** Cover every regraded loan with an action (incl. `monitor`), not just problem credits. `covered_loan_count` = the whole regrade/watch-list count.1453. **Severe-delinquency action override: Nonaccrual → `partial_chargeoff_review` (rating 8), NOT `legal_referral`.** Using `legal_referral` for a Nonaccrual/underwater credit was penalized. `90+DPD → workout`.1464. **`conditions` field uses the conditions enum only; decline REASON codes live in `decline_reasons`.** Never cross the two.1475. **Concentration denominator = `total_loans_outstanding`, NEVER `total_assets`.** For per-application concentration flags and existing concentration it is the CURRENT total_loans_outstanding. For the CRE policy variance the post-approval denominator GROWS by the selected amount.1486. **`variance_bps` from full precision, then round.** Do not round ratios to 4 dp first and then multiply by 10000.1497. **CDFI `factor_score`: missing factor ⇒ worst tier** (dta→6, fico→5, liq→5, ltv→6), NOT skip. Skip-missing was strongly penalized and makes `Projected Loss` (>=19) unreachable for fico-missing underwater credits.1508. **Stress = policy formula literal** (`dscr/1.18` watch-list; `dscr*0.85/1.18` CRE dual). The `+200bp` is the label, not a per-loan rate add-on. Apply only where DSCR exists.1519. **NPA branch ratio uses the metrics table** (`nonperforming_loans`, `delinquency_30_plus_pct`); do not hand-recompute a different denominator unless the metric definition demands it.15210. **Unselected competing credit ⇒ `defer`** with deferral reason codes, not `decline`.15311. **`material_downgrades` = notches >= 2 only** (policy `material_downgrade_notches=2`); 1-notch downgrades and upgrades are excluded from that list.15412. **Always sort lists per the template's `ordering`** before emitting; ascending `loan_id` is lexicographic string sort.15513. **Operating controls are warranted by specific segment signals** (control_issue→insurance-binder verification; staffing_constraint→senior-underwriter second review; recent delinquency→monthly delinquency watch; state delinquency above median→quarterly state benchmark monitoring; throughput overrun risk→committee exception for capacity overrun).15614. **NCUA directions are literal numeric (higher/lower/equal), not better/worse** — a higher delinquency_bps is "higher" even though it is worse.157158## 12. Solving procedure per task1591601. `GET /api/policies` first; load the risk-rating, CDFI, stress, concentration rules into memory.1612. `GET /api/branches/{branch_id}` + `/metrics` (latest quarter matching the review date) + `/sector-exposures` + `/loans` (with the documented `min_current_rating`/`loan_type`/`payment_status` filter for the task) + `/applications`. For credit-union tasks `GET /api/credit-union-segments/{segment_id}` + `/api/benchmarks/ncua/q1-2025`. For FDIC tasks `GET /api/benchmarks/fdic/q4-2024`.1623. Re-derive ratings / CDFI classes / stress / concentrations per §§2–9, computing from full precision and rounding outputs to the template's precision (§1).1634. Build the JSON object matching the task's `answer_template.json` exactly (required top-level keys, required sub-keys, enums from §10, orderings from the template).1645. Validate every list ordering and every enum value before emitting. Emit ONLY the JSON.