← All work
Deployed technical projectFinancial services / Consumer lending

A retrospective credit-policy platform over 233,154 Indian vehicle loans: portfolio and vintage reporting, score-decile and segment risk analytics, a policy workbench with editable economics, a per-loan inspector, and post-deployment monitoring.

Automobile-Loan First-EMI Default Strategy Portfolio

The business question

Where should we draw the first-EMI risk line, and what does each line cost us?

The answer

No evaluated band clears zero at the published assumptions. The conservative band admits 44.69% of applications at a 15.29% observed first-EMI default rate and lands at −₹0.07 cr once its 27,585 manual reviews are costed, above zero in only 39% of holdout resamples. The refusal is conditional: it clears zero if just 0.32% of its first-EMI misses are payment-mandate failures rather than credit events, which is a number a lender's operations team already holds.

Demonstrated value

A credit-policy analyst can test where to draw a first-EMI risk line, see the confidence interval around the answer and who it declines, and take a recommendation or a refusal to governance. On the published assumptions the honest output is a refusal: no evaluated band clears zero.

Focus

Data Analyst

Found that a bureau score column encoded 'no usable score' as numeric values of 11-18 for 12,835 loans, and that the thin-file flag derived from it was inverted; correcting it moved the recommended policy band from +₹1.52 cr to −₹0.07 cr and withdrew the recommendation.
Primary stakeholder

A credit-policy analyst preparing a band recommendation for governance review.

Context and stakes

The decision context behind the system.

Across 233,154 Indian vehicle loans, none of the three evaluated first-EMI risk bands clears zero at the published assumptions. The best, a conservative 16% cut-off, admits 44.69% of applications at a 15.29% observed default rate and lands at −₹0.07 cr once its 27,585 manual reviews are costed; resampling puts it above zero in only 39% of draws. That conclusion emerged from correcting a data defect: the bureau column encoded 'no score' as a number, and fixing it moved the same band from +₹1.52 cr to negative. But the refusal is conditional. A 21.7% first-EMI rate cannot be pure credit risk, and loans that missed because a mandate failed carry no credit loss, so the workbench reports how much of the label would have to be operational for each band to break even. Conservative needs 0.32%. The output is therefore a threshold a lender can test against its own payment plumbing, not a verdict on the loans.

A credit-policy analyst can test where to draw a first-EMI risk line across 233,154 real vehicle loans, see the confidence interval around the answer and which states it declines, and drill to the individual loans behind any number.

Approach

A bounded approach shaped by the published constraints.

No evaluated band clears zero at the published assumptions. The conservative band admits 44.69% of applications at a 15.29% observed first-EMI default rate and lands at −₹0.07 cr once its 27,585 manual reviews are costed, above zero in only 39% of holdout resamples. The refusal is conditional: it clears zero if just 0.32% of its first-EMI misses are payment-mandate failures rather than credit events, which is a number a lender's operations team already holds.

Design constraints

  • The outcome is first-EMI default, not fraud and not long-horizon credit performance; the source has no fraud flag and no channel field.
  • A 21.7% first-EMI default rate implies the label mixes operational and mandate failures with genuine credit risk, and nothing in the source separates them.
  • Loss severity is an ultimate-loss assumption applied to a first-payment miss; the source has no recovery or roll-rate data.
  • Three months of 2018 disbursements, evaluated retrospectively, with October in festival season. Nothing here establishes forward performance.

Decision journey

The system moves through an inspectable sequence.

Each stage below comes from the published project manifest.

  1. Stage

    Ingest and validate the

    SQLite star schema built by scripts/build_db.py from the quarantined source archive, with all three vintages scored

  2. Stage

    Build a SQLite star

    Parameterized SQL layer (cohort, policy_bands, score_deciles, segment_risk, kpi_monthly) shared by every surface

  3. Stage

    Train a logistic baseline

    FastAPI JSON API serving cohort aggregates, risk analytics and per-loan detail on a single port

  4. Stage

    Evaluate policy bands on

    React application served from the same process at the root: portfolio, risk analytics, policy workbench, loan inspector, monitoring

  5. Stage

    Cost the manual reviews

    Charts are hand-authored inline SVG, so no charting library ships to the browser

  6. Stage

    Report who each band

    Monthly KPI report pack as a public PNG, with private CSV and XLSX generation

Interactive architecture

The published architecture, made interactive.

One SQLite star schema feeds every surface through a shared parameterized SQL layer, so the API, the React application, the KPI pack and the published evidence cannot drift apart. A calibrated challenger is selected on calibration error rather than ranking alone, because a policy band needs probabilities that mean what they say - and the monitoring view then shows that on this holdout they do not, since observed default exceeds predicted in every decile. The economics are exposed as sliders rather than asserted, because the band ranking flips inside the plausible range of its own assumptions. Headline figures carry bootstrap intervals because the net result is a small difference between two much larger numbers. The whole platform runs as one process in one scale-to-zero container, with hand-authored inline SVG charts so no charting library ships to the browser.

Drag to pan / use controls to zoom / select any stage
100%
feedsfeedsfeedsfeedsfeedsStage 1SQLite star schema builtPythonStage 2Parameterized SQL layer (cohort,JavaScriptStage 3FastAPI JSON API servingscikit-learnStage 4React application served fromSQLiteStage 5Charts are hand-authored inlineFastAPIStage 6Monthly KPI report packReact
Read the architecture as text
  1. SQLite star schema builtStage 1 / Python

    SQLite star schema built by scripts/build_db.py from the quarantined source archive, with all three vintages scored

  2. Parameterized SQL layer (cohort,Stage 2 / JavaScript

    Parameterized SQL layer (cohort, policy_bands, score_deciles, segment_risk, kpi_monthly) shared by every surface

  3. FastAPI JSON API servingStage 3 / scikit-learn

    FastAPI JSON API serving cohort aggregates, risk analytics and per-loan detail on a single port

  4. React application served fromStage 4 / SQLite

    React application served from the same process at the root: portfolio, risk analytics, policy workbench, loan inspector, monitoring

  5. Charts are hand-authored inlineStage 5 / FastAPI

    Charts are hand-authored inline SVG, so no charting library ships to the browser

  6. Monthly KPI report packStage 6 / React

    Monthly KPI report pack as a public PNG, with private CSV and XLSX generation

Technology used
PythonJavaScriptscikit-learnSQLiteFastAPIReactDockerAzure Container Apps

Pivotal decisions

Technology choices recorded with their trade-offs.

These choices are derived from the project's published decision record.

Decision

Use SQLite within the published boundary

A 233,154-row star schema fits comfortably in a single file, ships inside the container, and needs no separate database service on a free tier.

Constraints
  • Contribution figures are assumption-led (12% contribution rate, 65% loss severity, ₹1,500 per manual review), not observed profit and loss. All three are analyst inputs; the review cost has no published source at all and is labelled an unsourced stand-in wherever it appears.
  • No band clears zero at the published assumptions. The best of the three sits at −₹0.07 cr with a 95% interval of −₹0.65 to +₹0.49 cr, so it is not distinguishable from zero, and in 13 of 27 published assumption combinations no band clears zero either.
  • The refusal is conditional, not absolute. Under an explicit assumption that an operational miss cures with zero credit loss, conservative needs 0.32% of its first-EMI misses to be operational to clear zero, roughly 22 of 6,722 defaulted loans. The source cannot identify operational misses, later cure, or realized loss, so the scenario frames the lender evidence required rather than claiming it.
Alternatives
  • PostgreSQL on a managed free tier
Criteria
Fit for the stated scope / Inspectable evidence / Explicit operating boundary
Selected
SQLite
Why
A 233,154-row star schema fits comfortably in a single file, ships inside the container, and needs no separate database service on a free tier.
Trade-off
No concurrent writers and no network access to the store; acceptable because this workload is read-only and single-container.
What supported it
−₹0.07 cr Net contribution, conservative band: Best of three at the published assumptions, with a 95% interval of −₹0.65 to +₹0.49 cr; reference −₹3.52 cr and expansion −₹13.08 cr. No band clears zero.
Revisit when
The store is the binding constraint, not the model. SQLite serves 233,154 rows from a single file well past this demo's needs; a genuine origination volume with concurrent writers would move it to Postgres, and the shared parameterized SQL layer is the seam that makes that a swap rather than a rewrite.
Decision

Use A shared parameterized SQL layer within the published boundary

The API, dashboard, and KPI pack all read the same .sql files, so the evaluated evidence and the reported figures cannot diverge.

Constraints
  • Contribution figures are assumption-led (12% contribution rate, 65% loss severity, ₹1,500 per manual review), not observed profit and loss. All three are analyst inputs; the review cost has no published source at all and is labelled an unsourced stand-in wherever it appears.
  • No band clears zero at the published assumptions. The best of the three sits at −₹0.07 cr with a 95% interval of −₹0.65 to +₹0.49 cr, so it is not distinguishable from zero, and in 13 of 27 published assumption combinations no band clears zero either.
  • The refusal is conditional, not absolute. Under an explicit assumption that an operational miss cures with zero credit loss, conservative needs 0.32% of its first-EMI misses to be operational to clear zero, roughly 22 of 6,722 defaulted loans. The source cannot identify operational misses, later cure, or realized loss, so the scenario frames the lender evidence required rather than claiming it.
Alternatives
  • Per-surface query code
Criteria
Fit for the stated scope / Inspectable evidence / Explicit operating boundary
Selected
A shared parameterized SQL layer
Why
The API, dashboard, and KPI pack all read the same .sql files, so the evaluated evidence and the reported figures cannot diverge.
Trade-off
Less freedom to shape queries per surface, in exchange for figures that reconcile by construction.
What supported it
22 of 27 Assumption combinations the conservative band wins: In 13 of those same 27 combinations, no band clears zero at all.
Revisit when
Scoring is offline and batch. Real-time decisioning would need the calibrated model behind a service with its own latency budget and a feature store, neither of which exists here and neither of which this retrospective evidence would justify building yet.
Decision

Use React with hand-authored inline SVG charts within the published boundary

The five analytics views share one client and ship no charting runtime.

Constraints
  • Contribution figures are assumption-led (12% contribution rate, 65% loss severity, ₹1,500 per manual review), not observed profit and loss. All three are analyst inputs; the review cost has no published source at all and is labelled an unsourced stand-in wherever it appears.
  • No band clears zero at the published assumptions. The best of the three sits at −₹0.07 cr with a 95% interval of −₹0.65 to +₹0.49 cr, so it is not distinguishable from zero, and in 13 of 27 published assumption combinations no band clears zero either.
  • The refusal is conditional, not absolute. Under an explicit assumption that an operational miss cures with zero credit loss, conservative needs 0.32% of its first-EMI misses to be operational to clear zero, roughly 22 of 6,722 defaulted loans. The source cannot identify operational misses, later cure, or realized loss, so the scenario frames the lender evidence required rather than claiming it.
Alternatives
  • A second dashboard framework and process
Criteria
Fit for the stated scope / Inspectable evidence / Explicit operating boundary
Selected
React with hand-authored inline SVG charts
Why
The five analytics views share one client and ship no charting runtime.
Trade-off
The project owns its chart primitives in exchange for one coherent application and a smaller browser bundle.
What supported it
0.04542 Decile calibration error: Against 0.25169 for the logistic baseline, on the same holdout.
Revisit when
The economics are recomputed in the browser from three per-band totals because the risk cut-off does not move with the assumptions. Any change that lets an analyst move the cut-off itself breaks that shortcut and needs the scoring path back on the server.

Results and evaluation

Measured results within the disclosed evaluation boundary.

−₹0.07 crNet contribution, conservative band
Method
Gross contribution on admitted principal, less credit loss on observed defaults, less the cost of every referral the band generates, on the 98,364-loan October holdout.
Meaning
The only evaluated band that pays for itself once its own operating cost is counted.
Limitation
The outcome is first-EMI default, not fraud and not long-horizon credit performance; the source has no fraud flag and no channel field.

Limitations and next steps

Useful evidence, bounded claims.

What this does not prove

  • The outcome is first-EMI default, not fraud and not long-horizon credit performance; the source has no fraud flag and no channel field.
  • A 21.7% first-EMI default rate implies the label mixes operational and mandate failures with genuine credit risk, and nothing in the source separates them.
  • Loss severity is an ultimate-loss assumption applied to a first-payment miss; the source has no recovery or roll-rate data.
  • Three months of 2018 disbursements, evaluated retrospectively, with October in festival season. Nothing here establishes forward performance.
  • The money figures are assumption-led. No band clears zero at the published assumptions, and in 13 of 27 published combinations none clears zero either.
  • The best band's net contribution is not statistically distinguishable from zero: a 95% interval of −₹0.65 to +₹0.49 cr.
  • Predicted probabilities are miscalibrated on the holdout by 3 to 5 points in every decile; the scores rank but do not price.
  • Under the conservative band 11 of 18 readable state codes fall below a four-fifths approval screen. The source has no protected attribute, so this is impact reporting, not a fair-lending test.
  • There is no status-quo baseline to compare against, because no incumbent policy is known for this dataset.

What I would do next

  1. The store is the binding constraint, not the model. SQLite serves 233,154 rows from a single file well past this demo's needs; a genuine origination volume with concurrent writers would move it to Postgres, and the shared parameterized SQL layer is the seam that makes that a swap rather than a rewrite.
  2. Scoring is offline and batch. Real-time decisioning would need the calibrated model behind a service with its own latency budget and a feature store, neither of which exists here and neither of which this retrospective evidence would justify building yet.
  3. The economics are recomputed in the browser from three per-band totals because the risk cut-off does not move with the assumptions. Any change that lets an analyst move the cut-off itself breaks that shortcut and needs the scoring path back on the server.
  4. Nothing is monitored automatically. Before this carried an operational decision it would need alerting, a scheduled reproducibility check, and drift monitoring on the score distribution.

This dataset measures first-EMI default, not fraud. The source is a public Kaggle archive of Indian vehicle loans whose published licence is recorded as unknown and which is used here on the repository owner's authority; it is not client data. Source identifiers, precise location, birth date, workforce identifiers, contact flags, and identity-document fields are excluded from every response. Nothing here makes an approval, denial, pricing, underwriting, or automated decision, and no fraud-detection capability is claimed.

Explore the rest of the work