Credit decisioning

The model estimates.
The policy decides.

Aperture underwrites thin-file and new-to-credit applicants from consented cash-flow evidence. Four independent checks, one deterministic policy, and every decision replayable offline with its reasons and a path to yes.

FastAPI · PostgreSQL 16 · pgvector · React 18

Decision recordAPL-1112

Requested principal

₹25,000.00

REFER
PD
0.049UNCAL
Coverage
79
Verification
CLEAR

Routed because: income was not observable from the connected sources, so gate 3 referred the case rather than failing it.

prev 9f2c…a81b → entry 04e7…dd10 · verify_chain() ok

Recorded once. Immutable. Reproducible offline.

  • 9

    policy gates

  • 8

    fraud detectors

  • 4

    independent assessments

  • 11

    scorecard features

  • 362

    backend tests

  • 0

    LLM calls in the decision path

The problem

Rejected for having no history, not for being unable to repay.

New-to-credit and thin-file applicants get declined because they have no traditional score — and the systems doing the declining are black boxes. You can't reproduce a decision, prove it wasn't edited afterwards, or tell an applicant what would change the answer.

Aperture treats those as the actual requirements. It reads how money moves through a consented bank, UPI, utility and telecom history, runs four independent checks, and hands the result to a fixed rulebook that is the only thing allowed to decide. Every outcome is deterministic, immutable, explained in plain language, and shipped with a concrete path to yes.

What it does

Built so an auditor, an analyst and an applicant can all get an answer.

  • Replayable decisions

    Any decision reproduces exactly, offline — no network, no AI — from stored point-in-time artifacts.

  • Immutable audit trail

    Decisions, evidence and snapshots are immutable at the database trigger level; the ledger is hash-chained.

  • The policy decides, not the model

    A pure, versioned 9-gate engine makes the call. The risk model only ranks — the split is enforced in code.

  • Reasons and a path to yes

    Every decision ships plain-language reasons plus concrete recourse: a decline says what would flip it.

  • Transparent risk model

    An additive-logistic scorecard, 11 published weights, exact closed-form contributions. No SHAP approximation.

  • Manipulation detection

    Eight independent detectors — circular flow, inflow bursts, balance-arithmetic tampering — band fraud risk.

Under the hood

One path from evidence to decision, and nothing may skip a step.

Consent gates ingestion; adapters normalise evidence into an immutable, hash-chained ledger. A point-in-time snapshot computes features from only what was knowable at decision time. Four independent assessments feed a pure policy engine — the single component permitted to decide.

  1. Consent-gated ingestion

    Bank, UPI, utility, telecom or an uploaded statement. Raw events land in an append-only ledger.

  2. Point-in-time snapshot

    Only occurred_at <= as_of. Full lineage, a reproducible input hash, frozen immutably.

  3. Four assessments

    Risk, affordability, coverage, manipulation — none can read the others' results.

  4. Pure policy engine

    An ordered 9-gate ladder. No network, no clock, no unseeded randomness.

  5. One atomic write

    Decision, reasons and the audit entry commit in a single transaction.

  6. Phrasing only

    The LLM writes the notice in English or Hindi from typed facts. It never sees a path into the decision.

The ladder that decides

Gates are walked in order and the first match wins. The same ordered tuple is the single source of truth for both the engine and the policy validator, so a rule can't exist in one and not the other. Select a gate to see what it does.

Gate 03

Affordability INDETERMINATE

Income could not be observed. Unobservable is not the same as unaffordable, so this refers — it never fails.

REVIEW_EVIDENCE

Two modifier rules sit on top: approvals over the mandatory-review ceiling are forced to a human, and a seeded, reproducible exploration draw can lift a near-miss decline with an otherwise clean profile.

Three decisions worth defending

Transaction boundaries

The decision and its audit entry commit together. Recourse does not.

The decision row, its reasons, and the append-only ledger entry all commit inside a single database transaction, so an auditable decision without its ledger entry can never exist. But the path-to-yes search runs outside that transaction — a slow or failed recourse computation must never roll back a decision that already legally stands. The ledger append serialises per tenant behind a row lock, so concurrent decisions can't reorder the hash chain.

orchestrator/service.py:94

Point-in-time features

Missing is null, never zero — and there is no fallback score.

The feature snapshot loads only ledger events with occurred_at <= as_of, so a decision can only ever see what was knowable when it was made. Absent data is recorded as null with a lineage entry rather than coerced to 0, because a zero is a claim and an absence isn't. If the model artifact is missing, its hash mismatches, or the PD falls outside [0,1], the request raises SYSTEM_UNAVAILABLE — nothing is persisted. A wrong number is worse than no number.

features/service.py:144

Enforced separation

The language model has no import path into the decision.

The LLM does three read-only jobs: phrasing applicant notices in English or Hindi, explaining a decision to an analyst, and answering questions about the architecture — each from typed facts only, with a deterministic fallback when it times out. It is kept out of the decision path structurally, not by convention: import-linter forbids the manipulation module from importing risk, coverage or affordability, and its context type cannot even carry a probability of default.

policy/engine.py — no LLM import

Stack

Backend
Python 3.11FastAPIPydantic 2SQLAlchemy 2 asyncasyncpgAlembicArgon2id
Data
PostgreSQL 16pgvector29 tables · 8 trigger-immutableHash-chained ledgers
ML & retrieval
Additive-logistic scorecardscikit-learnMiniLM-L6-v2 (local)HNSW · cosine · 384-dim
Frontend
React 18TypeScript 5.6ViteTanStack QueryTailwindPlaywright
Delivery
Docker ComposeGitHub Actionsruff · mypy --strictimport-linter

Honest limitations: the scorecard is deliberately labelled UNCALIBRATED until real repayment outcomes exist, the Account Aggregator and lender integrations are simulated for the demo, and no accuracy or latency benchmarks have been published yet.