Skip to Content
DomainValidation checklist — thresholds & severities for SME sign-off

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)

  1. 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.)
  2. 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.
  3. Income detection — business/professional income gets a looser variation cap (CV ≤ 0.60 vs 0.40 general; income_business_max_cv).
  4. 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.
  5. 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.
  6. 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

RuleDefaultQuestion
Knockout (forces high risk)FOIR > 60%Right ceiling for your segment?
Heavy penaltyFOIR > 50%
Moderate penaltyFOIR > 35%Should ceilings be income-band dependent (tighter for lower-income borrowers)?

2. Which obligations count toward FOIR · ontology §5

ObligationDefault
EMI / loanCounts (always)
Rent paidCountsAgree rent is a fixed obligation?
Insurance premiumCounts
SIP / investmentExcludedAgree SIP is discretionary (can be paused)?
Subscriptions (OTT/SaaS)Excluded
Utilities (electricity/gas/telecom)Excluded
TaxExcluded
Unclassified recurring debit ≥ ₹1,000Counts (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:

ClassCues
SalarySALARY, SAL, WAGES, STIPEND
GovernmentPENSION, DBT, PFMS, SUBSIDY, NREGA, SCHOLARSHIP
RentalRENT, LEASE (on credits)
InterestINTEREST, DIVIDEND, FD, MATURITY
Businessdefault 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

SignalDefault severityTriggerQuestion
NACH / ECS bounceHighanyIs one bounce = High appropriate? Recency window?
Cheque returnHighany
Penal / late chargeLowany
Negative-balance daysMedium ≤ 5 days, else Highany negative day
High cash depositsMediumcash deposits > 25% of total creditsRight threshold?
Circular transfersMediumsame party in & out, ≥ 80% offsetting, ≥ ₹10,000
Large one-off creditMediumcredit > 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.

FactorDefault
High-severity flags2+ → 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 / noneknockout
Negative-balance days−2 / day, capped −12
Verification — reconciliation failknockout
Verification — confidence < 90−12
BandsLow ≥ 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

#ItemResolution (applied)
1Bounce recencyRecency-weighted: ≤ bounce_recent_months (6) → full High; 7–12 (bounce_score_months) → Medium (partial); older → audit-only, excluded from the score.
2Minimum coverage< min_coverage_months (3) → worker refers to manual review (“insufficient data”) and the score can’t read Low; minfull (3–5) → reduced-confidence review flag; ≥ full_coverage_months (6) → full. (Skipped on partial/in-progress runs.)
3Stale incomeDays 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.
4Knockout compositionAuto-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.
5Large one-off creditEscalates 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_credit flag.
  • 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_stacking flag (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_stress flag.
  • D3 — service-failure charges: mandate/SI/min-balance/returned-payment charges → service_charges flag (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 shows coreMonthlyIncome / 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 to obligation_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_equivalent used in FOIR, so paying annually no longer looks artificially stronger.
  • D4 — speculative activity: gambling/betting/crypto raise a speculative_activity flag (surfaced for volatility, not auto-penalised; AML separate).
  • E1 — joint / co-applicant: detected from the account-holder name → a joint_account caveat (“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)

QCurrent behaviour
A1Variable pay — bonus, incentives, arrears, overtime: count as income? averaged or annualised?
A2Reimbursements (travel/medical) — exclude as non-income?
A3Non-income credits to exclude — loan disbursals, refunds/cashback, maturity/redemption, gifts, P2P top-ups. Confirm the full list.
A4Business vs noise — where’s the line between professional receipts and incidental P2P UPI credits?

B. Obligations — detection method + completeness (ontology §5)

QCurrent behaviour
B1Is 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?
B2Non-monthly obligations (quarterly / annual insurance) — capture and amortise?
B3Credit-card bill payments — treat full payment as an obligation, the minimum-due, or neither? Does revolving CC use signal risk?
B4Missing obligation types — BNPL, gold loan / LAP, overdraft interest, informal loans?
B5Loan-stacking — multiple concurrent EMIs across lenders: surface as a signal?

C. Channels / rails — completeness (ontology §3)

QCurrent behaviour
C1Missing rails worth modelling: AePS, mobile wallet / PPI, BBPS, standing-instruction (SI) auto-debit (distinct from NACH), FASTag?
C2Is a high-value RTGS (≥ ₹2L) worth its own informational flag?

D. Additional risk signals (ontology §6)

QCurrent behaviour
D1Salary-day overdraft / paycheck-to-paycheck (balance near zero right after income)?
D2Loan-stacking (multiple loan disbursals in the window)?
D3SI / min-balance / mandate-failure charges beyond NACH bounce?
D4High-risk merchant categories (gambling / betting / crypto) — behaviour / AML?

E. Entities (ontology §1)

QCurrent behaviour
E1Joint / 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:

#ItemResolution (applied)
F1Transfer dedupWindow 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.
F2Trust gateConfidence-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).
F3Counterparty identityKey hierarchy: VPA > IFSC > masked account > narration tokens (counterparty_identity); bare digit runs (txn refs) are not used.
F4Consolidation seamsGap tiers ≤7 ok / 8–30 warn / >30 significant; seam discontinuity flags + routes to review, never blocks consolidation.
F5Circular thresholdSmaller side must exceed max(₹10,000, 10% of monthly income) — scales across income segments.
F6Balance metricsDefinitions confirmed; added median balance, liquidity-buffer ratio (median month-end ÷ income), and days-below-10%-income.
F7Spend categoriesAdded Healthcare, Education, Travel & Transport, Transfers to Family.
F8Data qualityHigh 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

Producttarget FOIR (sizing)APRtenureapprove <decline >
Personal loan50%18%60m40%60%
LAP55%10.5%180m45%65%
Business loan55%20%48m40%65%
Microfinance50%24%24m35%55%
Consumer durable45%16%18m40%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).