Credit Office Decision Skill
API Workflow (all tasks)
Base URL is in environment_access.md (do not run env/setup.sh).
GET /api/manifest→ discover endpoints.GET /api/policies→ credit_policy_v2025Q1 thresholds, score tables, stress formulas.GET /api/branches→ list branches; findbranch_id,total_assets,lending_capacity_q1,sector_ceiling_pct,cre_policy_limit_pct,institution_type.- Fetch branch-specific data:
GET /api/branches/{branch_id}GET /api/branches/{branch_id}/metricsGET /api/branches/{branch_id}/loansGET /api/branches/{branch_id}/sector-exposuresGET /api/branches/{branch_id}/applications
- For credit-union tasks:
GET /api/credit-union-segments/{segment_id}GET /api/benchmarks/ncua/q1-2025
- For bank benchmark tasks:
GET /api/benchmarks/fdic/q4-2024
Task-Type Detection
Read the prompt and answer template to determine which of the five patterns applies:
| Pattern | Keywords | Branch Type |
|---|---|---|
| Portfolio Regrade | "portfolio regrade", "re-derive risk rating", "NPA benchmark" | bank |
| Credit Decisions | "credit decisions", "allocation", "concentration flags", "decline reasons" | bank |
| CU Segment Review | "credit union", "segment review", "NCUA", "posture", "escalation triggers" | credit_union |
| Watch List Review | "watch list", "adverse loans", "workout queue", "stress results" | bank |
| Competing CRE | "competing CRE", "compare", "recommended path", "stress", "weighted_cdfi_score" | bank |
Pattern 1: Portfolio Regrade
Business Rules
- Re-derive each loan’s final_rating from the worst numeric rating produced by:
- DSCR (if present): ≥1.5→3, ≥1.25→4, ≥1.05→5, ≥1.0→6, <1.0→7
- LTV (if present): ≤0.65→3, ≤0.75→4, ≤0.85→5, ≤1.0→6, >1.0→7
- Delinquency (from
payment_status): Current→null, 30 DPD→4, 60 DPD→5, 90+ DPD→7, Nonaccrual→8
- Final rating =
max(available factor ratings, current_rating if no factors apply). - Material downgrades:
final_rating - current_rating ≥ 2. List every such loan withdowngrade_notches. - Watch-list actions (based on final_rating):
- Rating 8 →
partial_chargeoff_review - Rating 7 →
special_assets - Rating 6 →
watchlist
- Rating 8 →
- NPA benchmark:
branch_npa_exposure= sum ofoutstanding_balancefor loans withpayment_statusin (Nonaccrual,90+ Days Past Due)branch_total_loans= sum of all loanoutstanding_balancebranch_npa_ratio=branch_npa_exposure / branch_total_loans(4 decimals)- Use FDIC benchmark
total_loans_noncurrent_pct variance_ratio = branch_npa_ratio - fdic_benchmark_ratiovariance_bps = variance_ratio * 10000(2 decimals)
Output Fields
branch_id,review_dateportfolio_regrade:target_current_rating_min,target_loan_count,target_exposure,final_rating_exposure_totals[](group by final_rating, sum exposure/count),migration_from_current_rating_3[](loans whose current_rating was 3, grouped by final_rating),watch_list_action_coveragenpa_benchmarkmaterial_downgrades[]top_problem_credit(highest final_rating, then highest exposure)
Pattern 2: Credit Decisions
Business Rules
- Evaluate each application in ascending
application_idorder. - Decline reasons (alphabetical, from controlled 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. - DSCR < 1.25 →
weak_dscr; LTV > 0.80 →high_ltv(use applicationltvwhen present). - FICO < 620 →
low_fico. - Bankruptcy within last 24 months (i.e.
bankruptcy_months_ago≤ 24) →recent_bankruptcy. years_in_business< 2 →startup_risk.documentation_complete == 0→documentation_gap.- Capacity:
lending_capacity_q1is the ceiling. Approved amounts cannot exceed remaining capacity. If an app would exhaust capacity →capacity_limit. - Sector concentration:
post_approval_pct = (current_exposure + approved_amount) / total_assets. If >limit_pct→sector_breach.- If sector is new (no existing exposure),
current_exposure = 0. - Grandfathered exposures may stay over-limit, but new approvals may not worsen the breach without mitigation.
- If sector is new (no existing exposure),
- SBA guaranty: If
sba_guaranty_pctis present,bank_capacity_used = approved_amount * (1 - sba_guaranty_pct). - Participation required: If post-approval would breach limit, set decision to
conditional_approvewith conditionparticipation_requiredand reducebank_capacity_usedaccordingly (see train_002 LAK-APP-901: approved_amount 1,650,000 but bank_capacity_used 1,508,113.31 implies ~8.6% participation). - Priority ranking: list
application_ids of approved and conditionally approved applications, highest priority first. Sort by: stronger credits first (lower risk), or by amount/largest approved first if tied. gross_approved_amount= sum of allapproved_amountfor approved/conditionally approved.committed_capacity_amount= sum of allbank_capacity_usedfor approved/conditionally approved.remaining_capacity = lending_capacity_q1 - committed_capacity_amount.
Output Fields
branch_idallocation:lending_capacity_q1,gross_approved_amount,committed_capacity_amount,remaining_capacity,priority_rankingdecisions[](ascendingapplication_id):application_id,decision,approved_amount,bank_capacity_used,conditions[]concentration_flags[](sort by sector then application_id):sector,application_id,limit_pct,post_approval_pct(4 decimals),flag(boolean),handlingdecline_reasons: mapapplication_id→ sorted list of reason stringspost_approval_concentrations[](sort by sector ascending):sector,exposure_after_approval,post_approval_pct(4 decimals),limit_pct,over_limit(boolean)
Pattern 3: Credit Union Segment Review
Business Rules
- Fetch segment data and NCUA Q1 2025 benchmarks.
state_metrics: pull the row for the segment’sstate_codefrom NCUA benchmarks.peer_comparison:peer_statesfrom segment data.- For each metric (
delinquency_bps,loan_to_share_pct,roaa_bps,positive_net_income_pct), compare segment state vs US median and vs peer-state median. Label:higher/lower/equal.
posture: derive from capacity and risk:- If
current_outstandingis near or abovequarterly_capacity→pause_new_originationsorcontinue_with_tighter_conditions - If external risk is weaker than peers →
continue_with_tighter_conditions - Otherwise →
continue_normal_underwriting
- If
controls.required_checklist_gates: from segmentminimum_checklist, sorted alphabetically.controls.added_operating_controls: add segment-appropriate controls (e.g. lien perfection, monthly delinquency watch, quarterly benchmark monitoring, senior underwriter second review, pre-close insurance verification). Sort alphabetically.escalation_triggers: create 3 triggers with IDsET001–ET003covering delinquency threshold, missing documentation/lien exceptions, and capacity overrun.interpretation:capacity_status(capacity_available/at_capacity),external_risk_status(weaker_than_national_and_peers/stronger_than_peers),risk_tolerancefrom segment,committee_messageconcise summary.
Output Fields
segment_id,posturestate_metricspeer_comparisoncontrolsescalation_triggers[]interpretation
Pattern 4: Watch List Review
Business Rules
- Scope: loans with
current_rating >= 6or loans the prompt explicitly flags as adverse (e.g. "watch list pool"). If the prompt says "adverse_rating_min": 6, include all loans with current_rating ≥ 6. - Compute CDFI factor score for each scoped loan using loan-level data:
fico: >720→0, 680-720→1, 580-679→3, <580→5ltv: <0.40→0, 0.40-0.60→2, 0.60-0.80→4, >0.80→6debt_to_asset: <0.40→0, 0.40-0.60→2, 0.60-0.80→4, >0.80→6liquidity_months: >12→0, 6-12→1, 3-6→3, <3→5- Sum available factor scores. If a factor is null, skip it.
- Risk class from total score:
- 0-5 →
Prime - 6-9 →
Desirable - 10-13 →
Satisfactory - 14-18 →
Watch - ≥19 and ltv > 1.0 →
Projected Loss - ≥19 otherwise →
Doubtful
- 0-5 →
- Stress test (watch-list formula from policy):
stressed_dscr = base_dscr / (1 + 0.18)- Breach threshold = 1.0
- Only test loans that have a
dscrvalue.
- Workout queue: sort scoped loans by severity:
Projected Lossfirst- Then by
payment_statusseverity (Nonaccrual > 90+ DPD > 60 DPD > 30 DPD > Current) - Then by
current_ratingdescending - Then by
outstanding_balancedescending
recommended_actionper loan:Projected Lossor Nonaccrual →partial_chargeoff_review- 90+ DPD or rating 7+ →
special_assets - Others →
watchlist
monitoring_cadence:monthlyfor any Watch/Projected Loss present, otherwisequarterly.severe_bucket_counts: group scoped loans by (current_rating,payment_status), count and sum exposure.
Output Fields
branch_idwatch_list_summary:adverse_rating_min,adverse_loan_count,adverse_balance,risk_classes[],monitoring_cadencestress_results:shock_label(+200bp),breach_threshold,results[],breach_loan_ids[]workout_queue[]severe_bucket_counts[]
Pattern 5: Competing CRE Decision
Business Rules
- Compare exactly the two CRE applications specified in the prompt.
- CRE weighted score (from policy weights):
capacity0.45,capital0.03,character0.05,collateral_exposure0.36,conditions0.11- Compute each factor as a normalized score (typically 1-5 scale based on thresholds), then
weighted_cdfi_score = sum(factor_score * weight). - Round to 1 decimal.
- Score class:
- ≤2.0 →
approve_quality - ≤3.0 →
conditional 3.0 →
weak
- ≤2.0 →
- Decision per application:
approve_quality+ no concentration breach →approveconditional+ no major issues →conditional_approveweakor concentration breach or FDIC variance adverse →decline,defer, orparticipation_required
- Stress test (CRE dual-stress formula):
stressed_dscr = dscr * 0.85 / (1 + 0.18)(i.e.dscr * 0.85 / 1.18)- Threshold = 1.0
- Concentration:
existing_cre_exposure= sum ofoutstanding_balancefor all branch loans whereloan_type == "CRE"existing_cre_concentration = existing_cre_exposure / total_assets(4 decimals)selected_post_approval_cre_concentration = (existing_cre_exposure + selected_approved_amount) / total_assets(4 decimals)selected_policy_variance_bps = (selected_post_approval_cre_concentration - cre_policy_limit_pct) * 10000(2 decimals)
- FDIC comparison:
branch_delinquency_ratio= branch’sdelinquency_30_plus_pctfrom metrics (2025Q1 row) (4 decimals)fdic_benchmark_ratio= relevant FDIC metric (e.g.total_real_estate_30_89_pct) (4 decimals)fdic_variance_ratio = branch_delinquency_ratio - fdic_benchmark_ratio(4 decimals)fdic_variance_bps = fdic_variance_ratio * 10000(2 decimals)
- Recommended path:
- Select the application with lower
weighted_cdfi_score(better credit). - If tied, select the one with higher DSCR or lower LTV.
path= decision of selected application.unselected_disposition=declineordefer.unselected_reason_codes= sorted list from the allowed subset (sector_breach,weak_dscr,high_ltv,fdic_adverse_variance).
- Select the application with lower
- Conditions: select all applicable conditions from the allowed enum, sorted alphabetically. Include items like
bank_retained_exposure_cap,committee_cre_exception,updated_appraisal_before_close,tenant_roll_and_lease_review,minimum_dscr_covenant_1_25,quarterly_financial_reporting,no_additional_cre_without_committee_review.
Output Fields
branch_idapplications_compared[](ascendingapplication_id):application_id,weighted_cdfi_score(1 decimal),score_class,decision,reason_codes[](sorted)recommended_path:selected_application_id,path,unselected_application_id,unselected_disposition,unselected_reason_codes[]stress:formula,coverage_breach_threshold,results[]concentration:cre_policy_limit_pct,existing_cre_exposure,existing_cre_concentration,selected_post_approval_cre_concentration,selected_policy_variance_bps,fdic_benchmark_metric,branch_delinquency_ratio,fdic_benchmark_ratio,fdic_variance_ratio,fdic_variance_bpsconditions[](sorted alphabetically)
Universal Precision Rules
- Currency / balances: round to 2 decimals.
- Percentages as ratios: round to 4 decimals.
- Percentage points / bps: round to 2 decimals.
- Scores: round to 1 decimal.
- DSCR / stress values: round to 2 decimals.
- Sort all lists alphabetically or numerically as specified in the template.
- JSON output must match the exact keys and nesting of the provided
answer_template.json.
Common Pitfalls
- Using wrong benchmark set: Banks use FDIC Q4 2024; credit unions use NCUA Q1 2025.
- Null handling: Skip null factors in CDFI scoring; do not treat null as 0.
- Sector-exposure vs. limit:
limit_pctfor a sector may differ from branchsector_ceiling_pctif the exposure is grandfathered. Use the value from/api/branches/{branch_id}/sector-exposures. - SBA guaranty math:
bank_capacity_usedis reduced by the guaranty percentage, butapproved_amountstays at the full requested (or reduced) amount. - CRE stress formula: use
* 0.85 / 1.18, not the watch-list/(1+0.18). - Total assets denominator: always use the branch’s
total_assetsfrom/api/branches/{branch_id}, not loans outstanding or deposits. - Do not include narrative text outside the JSON.