Validation checklist — thresholds & severities for SME sign-off
Status: ✅ reviewed by credit SME on 2026-06-18 and applied to the engine. The defaults below were the pre-review proposal; the resolution summary records what changed. The tables are kept as the audit trail of what was asked. All values remain per-lender overridable in-app (Settings → Risk scoring policy).
Resolution summary (SME review · 2026-06-18 → applied)
- FOIR — now income-band dependent. ≤ ₹25k: heavy > 40%, decline > 55%. ₹25–75k: standard
(heavy > 50%, decline > 60%). > ₹75k: heavy > 55%, decline > 65%. (
foir_bands_for_income.) - FOIR inclusion unchanged (rent/insurance in, SIP/subscription/utility/tax out) except
an unclassified ‘other’ recurring debit now counts only if it persists ≥ 3 months
(
foir_other_min_months) — no longer auto-counted. - Income detection — business/professional income gets a looser variation cap (CV ≤ 0.60 vs
0.40 general;
income_business_max_cv). - Income classes — added narration cues (PAYROLL/HRMS; EPFO/PM-KISAN/TREASURY/MUNICIPAL/…; business cues). Deviation: bare “PAY” was not added as a salary cue — it collides with the SME’s own business list (“PAYMENT RECEIVED”). Sources now rank by class hierarchy.
- Risk-flag severities — negative days tiered (1–3 low / 4–10 medium / >10 high); cash-deposit threshold segment-dependent (salaried high > 25%, self-employed medium > 40%); circular transfers escalate to high across ≥ 3 months.
- Scorecard — single income source −15 → −10; irregular income no longer a knockout (−30 penalty instead); low extraction confidence no longer penalises the score (review trigger only); bands tightened to Low ≥ 80, Medium ≥ 60.
Deviations from the literal feedback, by judgement: (a) “PAY” excluded as above; (b) high-income FOIR flexibility is applied by income band alone (we don’t yet have a clean “stable salaried employment” signal to gate it further, as the SME suggested) — noted as a future refinement.
Original agenda (pre-review) follows. When citing in
references.md, reference “SME review, 2026-06-18”.
1. FOIR (Fixed Obligation to Income Ratio) bands · metrics §4, risk §2
| Rule | Default | Question |
|---|---|---|
| Knockout (forces high risk) | FOIR > 60% | Right ceiling for your segment? |
| Heavy penalty | FOIR > 50% | |
| Moderate penalty | FOIR > 35% | Should ceilings be income-band dependent (tighter for lower-income borrowers)? |
2. Which obligations count toward FOIR · ontology §5
| Obligation | Default | |
|---|---|---|
| EMI / loan | Counts (always) | |
| Rent paid | Counts | Agree rent is a fixed obligation? |
| Insurance premium | Counts | |
| SIP / investment | Excluded | Agree SIP is discretionary (can be paused)? |
| Subscriptions (OTT/SaaS) | Excluded | |
| Utilities (electricity/gas/telecom) | Excluded | |
| Tax | Excluded | |
| Unclassified recurring debit ≥ ₹1,000 | Counts (conservative) |
3. Income detection · metrics §1
A recurring credit is treated as income if salary-tagged, or it meets all of:
- appears in ≥ 60% of covered months
- median amount ≥ ₹10,000
- variation (coefficient of variation, CV) ≤ 0.40
Income reads as “regular” when it’s monthly and the monthly-income CV ≤ 0.20.
Q: Reasonable for self-employed / variable / seasonal income? Should the 60% / ₹10,000 / 0.40 differ by borrower type?
4. Income classification — narration cues · ontology §4
Each income source is tagged by narration keywords:
| Class | Cues |
|---|---|
| Salary | SALARY, SAL, WAGES, STIPEND |
| Government | PENSION, DBT, PFMS, SUBSIDY, NREGA, SCHOLARSHIP |
| Rental | RENT, LEASE (on credits) |
| Interest | INTEREST, DIVIDEND, FD, MATURITY |
| Business | default for any other recurring inflow |
Q: Any narration patterns we’re missing — especially for business/professional receipts or government benefit credits?
5. Risk-flag severities · ontology §6, risk §2
| Signal | Default severity | Trigger | Question |
|---|---|---|---|
| NACH / ECS bounce | High | any | Is one bounce = High appropriate? Recency window? |
| Cheque return | High | any | |
| Penal / late charge | Low | any | |
| Negative-balance days | Medium ≤ 5 days, else High | any negative day | |
| High cash deposits | Medium | cash deposits > 25% of total credits | Right threshold? |
| Circular transfers | Medium | same party in & out, ≥ 80% offsetting, ≥ ₹10,000 | |
| Large one-off credit | Medium | credit > max(3× monthly income, ₹50,000) and not income |
6. Scorecard weights · risk §2
Start at 100, deduct; knockouts force the High band; a serious flag floors the band at Medium.
| Factor | Default |
|---|---|
| High-severity flags | 2+ → knockout; exactly 1 → −18 pts and band can’t read “Low” |
| Medium-severity flags | −5 each, capped −15 |
| Income — single credit only | −15 |
| Income — irregular / none | knockout |
| Negative-balance days | −2 / day, capped −12 |
| Verification — reconciliation fail | knockout |
| Verification — confidence < 90 | −12 |
| Bands | Low ≥ 75, Medium ≥ 50, else High; any knockout caps the score at 45 |
Q: Is “one bounce keeps a borrower out of Low” right? Are the 75 / 50 band cut-offs where you’d set them?
Round 2 — ✅ SME-reviewed 2026-06-18 and applied
| # | Item | Resolution (applied) |
|---|---|---|
| 1 | Bounce recency | Recency-weighted: ≤ bounce_recent_months (6) → full High; 7–12 (bounce_score_months) → Medium (partial); older → audit-only, excluded from the score. |
| 2 | Minimum coverage | < min_coverage_months (3) → worker refers to manual review (“insufficient data”) and the score can’t read Low; min–full (3–5) → reduced-confidence review flag; ≥ full_coverage_months (6) → full. (Skipped on partial/in-progress runs.) |
| 3 | Stale income | Days from last income to statement end, by segment: salaried > 90 / self-employed > 120 → “income inactive” (High flag, can’t read Low); 45–90 / 60–120 → “may be stale” caveat. |
| 4 | Knockout composition | Auto-decline reserved for ≥ 2 payment dishonours (dishonour_knockout_count). Non-dishonour high flags (liquidity, cash, circular) accumulate (capped) and floor the band, but never knock out alone. |
| 5 | Large one-off credit | Escalates Medium→High when ≥ large_credit_reversal_fraction (80%) exits within large_credit_reversal_days (7) — pass-through — or the credit has no identifiable counterparty (untraceable). |
Minor confirmations — accepted as-is: “salaried” = salary ≥ 50% of income; stability score = 60% presence + 40% amount; trend = ±10% half-over-half.
Deviation: the SME’s graded recency (100/75/50%) is operationalised through the severity bands (≤6mo High ≈ full, 7–12mo Medium ≈ partial, >12mo ignored) rather than literal per-event percentages — same intent, fits the points scorecard. “Untraceable” uses “no identifiable counterparty in the narration” as the practical signal.
Round 3 — Ontology review (✅ SME-reviewed 2026-06-18; high-impact set applied)
Rounds 1–2 validated the thresholds and severities. Round 3 validated the ontology itself. The SME’s prioritised, high-impact items are implemented; a few refinements are queued (below).
Applied:
- A3 — non-income credit detection (the #1 gap): funding events, asset conversions, refunds/
reversals, transfers, exceptional receipts, and A2 reimbursements are detected
(
taxonomy.NON_INCOME_PATTERNS) and excluded from income before any income test. FD/MF maturity moved out of “interest” income into asset-conversion. - B3 — credit cards: a recurring CC bill payment is no longer treated as an obligation (no more
double-counting spend as debt); card finance/late charges raise a
revolving_creditflag. - B4 — obligation types: BNPL, gold loan, LAP, microfinance, payday, salary advance, OD interest added; all count toward FOIR as real debt.
- B5 / D2 — loan stacking: distinct concurrent lenders (EMIs + disbursals) and multiple fresh
disbursals raise a
loan_stackingflag (high when many). - D1 — paycheck-to-paycheck: balance falling below 10% of monthly income within 7 days of income
in ≥2 cycles raises a
liquidity_stressflag. - D3 — service-failure charges: mandate/SI/min-balance/returned-payment charges →
service_chargesflag (medium when repeated). - C1 — channels: AePS, BBPS, standing-instruction (SI), wallet/PPI added.
Also applied (round 3b — the queued refinements):
- A1 — core vs supplementary income:
IncomeSource.tier; salary/pension/stable business+rental are core; bonus/incentive/overtime/commission and unstable business/rental are supplementary. FOIR and the income band are computed on core income only — supplementary supports, doesn’t drive affordability. Report showscoreMonthlyIncome/supplementaryMonthlyIncome. - A4 — business confidence: low-confidence business income (unstable / irregular) falls to the supplementary tier (the practical proxy for counterparty-diversity confidence).
- B1 — fixed vs variable obligations:
Obligation.variable; a named credit obligation may vary up toobligation_variable_max_cv(floating EMI) and still count; variable non-credit debits are treated as lumpy spend, not obligations. - B2 — amortisation: non-monthly obligations (quarterly/annual) are amortised to a
monthly_equivalentused in FOIR, so paying annually no longer looks artificially stronger. - D4 — speculative activity: gambling/betting/crypto raise a
speculative_activityflag (surfaced for volatility, not auto-penalised; AML separate). - E1 — joint / co-applicant: detected from the account-holder name → a
joint_accountcaveat (“do not assume 100% of inflows belong to the borrower”).
Still open:
- C2 RTGS informational flag (kept as a channel only — RTGS isn’t inherently risky).
- E1 is a caveat only; true joint-account income attribution (split inflows by owner) needs ownership data the statement rarely provides cleanly — left as a manual underwriting step.
The original agenda follows for the audit trail.
A. Income — inclusion / exclusion (ontology §4)
| Q | Current behaviour |
|---|---|
| A1 | Variable pay — bonus, incentives, arrears, overtime: count as income? averaged or annualised? |
| A2 | Reimbursements (travel/medical) — exclude as non-income? |
| A3 | Non-income credits to exclude — loan disbursals, refunds/cashback, maturity/redemption, gifts, P2P top-ups. Confirm the full list. |
| A4 | Business vs noise — where’s the line between professional receipts and incidental P2P UPI credits? |
B. Obligations — detection method + completeness (ontology §5)
| Q | Current behaviour |
|---|---|
| B1 | Is recurring-debit clustering the right way to identify an obligation (same payee, ≥ 2 months, amount CV ≤ 0.25, median ≥ ₹1,000)? Too strict for step-up EMIs? |
| B2 | Non-monthly obligations (quarterly / annual insurance) — capture and amortise? |
| B3 | Credit-card bill payments — treat full payment as an obligation, the minimum-due, or neither? Does revolving CC use signal risk? |
| B4 | Missing obligation types — BNPL, gold loan / LAP, overdraft interest, informal loans? |
| B5 | Loan-stacking — multiple concurrent EMIs across lenders: surface as a signal? |
C. Channels / rails — completeness (ontology §3)
| Q | Current behaviour |
|---|---|
| C1 | Missing rails worth modelling: AePS, mobile wallet / PPI, BBPS, standing-instruction (SI) auto-debit (distinct from NACH), FASTag? |
| C2 | Is a high-value RTGS (≥ ₹2L) worth its own informational flag? |
D. Additional risk signals (ontology §6)
| Q | Current behaviour |
|---|---|
| D1 | Salary-day overdraft / paycheck-to-paycheck (balance near zero right after income)? |
| D2 | Loan-stacking (multiple loan disbursals in the window)? |
| D3 | SI / min-balance / mandate-failure charges beyond NACH bounce? |
| D4 | High-risk merchant categories (gambling / betting / crypto) — behaviour / AML? |
E. Entities (ontology §1)
| Q | Current behaviour |
|---|---|
| E1 | Joint / co-applicant accounts — model distinctly, or treat as the borrower’s? |
Round 4 — Definitions & detection heuristics (✅ SME-reviewed 2026-06-18, applied)
Rounds 1–3 validated thresholds, severities, and the taxonomy. Round 4 validated the definitions and matching heuristics — what data we trust and how we group/merge it. All applied:
| # | Item | Resolution (applied) |
|---|---|---|
| F1 | Transfer dedup | Window 3→7 calendar days (≈5 business days; SME cap), transfer_window_days. Low-confidence (0.6–0.85) matches stay excluded from income and flagged — underwriting optimises against income inflation. |
| F2 | Trust gate | Confidence-weighted, not binary: reconcile ≥97.5% → pass/auto-score, ≥90% → warn (manual-review band), <90% → fail. Confidence = reconcile rate; confidence_threshold 90→97 (auto-score floor). Data-quality vs borrower-risk kept separate (a failed extraction routes to review, not a high-risk verdict). |
| F3 | Counterparty identity | Key hierarchy: VPA > IFSC > masked account > narration tokens (counterparty_identity); bare digit runs (txn refs) are not used. |
| F4 | Consolidation seams | Gap tiers ≤7 ok / 8–30 warn / >30 significant; seam discontinuity flags + routes to review, never blocks consolidation. |
| F5 | Circular threshold | Smaller side must exceed max(₹10,000, 10% of monthly income) — scales across income segments. |
| F6 | Balance metrics | Definitions confirmed; added median balance, liquidity-buffer ratio (median month-end ÷ income), and days-below-10%-income. |
| F7 | Spend categories | Added Healthcare, Education, Travel & Transport, Transfers to Family. |
| F8 | Data quality | High unclassified (‘OTHER’) share raises a data_quality caveat (10–25% moderate / >25% high) — surfaced, no borrower-risk impact. |
Deviation: transfer window is 7 calendar days (the SME’s hard cap) as a clean proxy for “5 business days” rather than a business-day calendar. Counterparty confidence-per-cluster exposure (beyond the identity key) remains a lighter future enhancement.
Decision layer — ✅ SME-reviewed 2026-06-18, core implemented
The SME specified a four-layer model (data quality → risk → eligibility → decision). Layers 3 & 4
are now a deterministic, per-product, auditable engine (src/domain/decision.py,
POST /v1/statements/{id}/decision); full spec in decision.md.
Applied: 5-way action (approve / approve-with-conditions / counter-offer / refer / decline) via
mandatory-decline → affordability → refer-trigger → policy-matrix; eligibility sizing (supportable
EMI, max & recommended loan); product presets (personal / LAP / business / microfinance / consumer
durable); structured adverse-action reasons + policy_version for audit.
Deferred (operational phase): the in-app decision UI; the override / delegated-authority
workflow (Level 1/2/3 with immutable, reason-coded override records); and a per-tenant
decision_policy store + editor (today: per-product presets, API-overridable).
Round 5 — ✅ SME-reviewed 2026-06-22; applied
Resolution: presets approved except Microfinance (tightened: target FOIR 50→45%, approve
30%, decline 55→50% — mfi-v2). Eligibility sizing confirmed (target FOIR is the sizing lever,
distinct from decline FOIR). Authority hierarchy + hard-blocks approved. “Policy-prohibited borrower
types” modelled as an external hard-stop input (external_hard_stop on the decision request →
mandatory, hard-blocked decline) — KYC/fraud/sanctions/PEP/blacklist come from external systems, not
the statement. Reason codes extended (data_discrepancy_resolved, income_verified_externally,
banking_relationship_exception, temporary_income_disruption, manual_credit_committee_approval).
SME guidance: freeze further policy changes until the backtest. Business-lending should later
weight turnover/DSCR/seasonality over FOIR — a Phase 3 item, post-backtest, not changed now.
The pre-review proposal follows for the audit trail.
A. Product presets (decision.py: PRODUCT_PRESETS) — confirm rates/tenures/cutoffs
| Product | target FOIR (sizing) | APR | tenure | approve < | decline > |
|---|---|---|---|---|---|
| Personal loan | 50% | 18% | 60m | 40% | 60% |
| LAP | 55% | 10.5% | 180m | 45% | 65% |
| Business loan | 55% | 20% | 48m | 40% | 65% |
| Microfinance | 50% | 24% | 24m | 35% | 55% |
| Consumer durable | 45% | 16% | 18m | 40% | 55% |
B. Eligibility sizing — confirm method
Max supportable EMI = target_FOIR × core_income − existing_obligations; max loan via reducing-balance
annuity at the product APR/tenure. Q: is target-FOIR-for-sizing the right lever (vs the decline FOIR)?
C. Override authority mapping (authority.py) — confirm
- L1 (junior): clear only data-quality/documentation refers; mark approve-with-conditions met.
- L2 (senior): risk/affordability refers + relax a (non-hard) decline only with a moderate reason (co-applicant / collateral / additional income / existing relationship).
- L3 (manager): any non-hard-blocked override.
- Hard-blocked for all: reconciliation failure, suspected tampering. Q: “policy-prohibited borrower types” — we don’t model these; what defines one, and should it hard-block?
- Q: reason-code list complete? (documentation_provided, conditions_met, temporary_foir_breach, strong_co_applicant, additional_collateral, existing_relationship, proven_additional_income, other.)
D. Backtest (the evidence step) — harness ✅ built, awaiting data
Real decided cases (statements + actual approve/refer/decline, ideally with performance) to validate
the rubric against ground truth and promote ⚠️ defaults to ✅ grounded. The harness is built and
ready (scripts/backtest.py + src/domain/backtest.py): point it at a manifest CSV + a folder of
statements and it runs each through the live pipeline + decision engine and emits the SME’s four
metrics — decision agreement, risk ranking, per-flag predictiveness, affordability accuracy. The
only thing missing is the data (the request below).