Skip to content

feat: design backend risk scoring model interfaces#61

Closed
spartan124 wants to merge 7 commits intoCreditra:mainfrom
spartan124:feature/risk-scoring-model-design
Closed

feat: design backend risk scoring model interfaces#61
spartan124 wants to merge 7 commits intoCreditra:mainfrom
spartan124:feature/risk-scoring-model-design

Conversation

@spartan124
Copy link
Contributor

Summary

Closes #13

Designs and stubs the backend risk scoring model — defining all TypeScript types/interfaces for inputs, outputs, and weights; implementing a documented placeholder scoring function; and wiring it into the risk service. All acceptance criteria from issue #13 are met.


Changes

New Files

File Description
src/risk/types.ts RiskInputs, RiskOutput, RiskTier, RiskWeights interfaces with full JSDoc on every field
src/risk/riskModel.ts All named constants (no magic numbers), clamp, normalise, validateWeights, classifyRiskTier helpers, and fully documented scoreWallet() stub
src/risk/riskService.ts Async evaluateRisk() wrapper — intentionally async so the route layer requires zero changes when a real engine replaces the stub
src/risk/index.ts Barrel re-exports for clean consumer imports
src/risk/__tests__/riskModel.test.ts 59 unit tests covering all constants, helpers, tier classification, scoring logic, edge cases, and weight validation
src/risk/__tests__/riskService.test.ts 16 async contract tests for evaluateRisk() including concurrency and rejection cases
vitest.config.ts Vitest config with v8 coverage, 95% thresholds scoped to src/risk/**

Modified Files

File Description
src/routes/risk.ts Full input validation for all 6 required fields (presence, type, non-negative numeric), calls evaluateRisk(), proper 400/500 error handling
package.json Added vitest, @vitest/coverage-v8 dev dependencies and test, test:watch, test:coverage scripts

Unchanged Files

  • src/index.ts
  • src/routes/credit.ts

Risk Model Design

Inputs (RiskInputs)

Field Type Description
walletAddress string 0x-prefixed wallet identifier
transactionCount number Total on-chain tx count (activity proxy)
walletAgeDays number Days since first observed transaction
defiActivityVolumeUsd number Trailing 90-day DeFi volume in USD
currentBalanceUsd number Current wallet balance in USD (collateral signal)
hasHighRiskInteraction boolean Interaction with flagged/sanctioned contracts

Outputs (RiskOutput)

Field Type Description
walletAddress string Echoed for traceability
riskScore number Normalised score [0, 100] — lower is better
creditLimitUsd number Max credit in USD cents (integer, avoids float rounding)
interestRateBps number Annual rate in basis points (500 bps = 5.00% APR)
riskTier RiskTier LOW / MEDIUM / HIGH / BLOCKED
isStub boolean Always true until real engine is connected

Scoring Algorithm (Documented, Pending Real Engine)

  1. Normalise each continuous input to [0, 1] using named ceiling constants
  2. Weighted sum using RiskWeights (validated to sum to 1.0 at runtime)
  3. Invert — higher on-chain signals → lower risk score
  4. Penalty+40 bps flat penalty if hasHighRiskInteraction === true
  5. Clamp to [0, 100] → classify tier → derive credit limit and rate

Risk Tiers & Credit Terms

Tier Score Range Credit Limit Interest Rate
LOW 0 – 30 $10,000 8.00% APR
MEDIUM 31 – 60 $3,000 15.00% APR
HIGH 61 – 100 $500 25.00% APR
BLOCKED $0 0%

Test Results

Test Files  2 passed (2)
     Tests  75 passed (75)
  Start at  xx:xx:xx
  Duration  ~410ms

Coverage (src/risk/** — executable files only)

File           | % Stmts | % Branch | % Funcs | % Lines
---------------|---------|----------|---------|--------
riskModel.ts   |     100 |      100 |     100 |    100
riskService.ts |     100 |      100 |     100 |    100
---------------|---------|----------|---------|--------
All files      |     100 |      100 |     100 |    100

types.ts (interfaces only) and index.ts (barrel re-exports) excluded — no executable statements.


API

POST /api/risk/evaluate

Request body:

{
  "walletAddress": "0xabc...123",
  "transactionCount": 250,
  "walletAgeDays": 365,
  "defiActivityVolumeUsd": 12000,
  "currentBalanceUsd": 8000,
  "hasHighRiskInteraction": false
}

200 Response:

{
  "walletAddress": "0xabc...123",
  "riskScore": 50,
  "creditLimitUsd": 300000,
  "interestRateBps": 1500,
  "riskTier": "MEDIUM",
  "isStub": true
}

400 Response (missing fields):

{
  "error": "Missing required fields",
  "fields": ["transactionCount", "walletAgeDays"]
}

400 Response (invalid types):

{
  "error": "Fields must be non-negative finite numbers",
  "fields": ["currentBalanceUsd"]
}

Notes for Reviewers

  • isStub: true is present on every response — consumers must check this flag and treat output as non-binding until a real engine is wired in
  • All thresholds, ceilings, rate bounds, and penalties are named exports with JSDoc rationale — zero magic numbers
  • evaluateRisk() is intentionally async for forward compatibility — swapping in a real HTTP/oracle engine requires changing only riskService.ts
  • Weights are validated at runtime via validateWeights() — misconfiguration throws immediately rather than silently producing wrong scores

@greatest0fallt1me
Copy link
Contributor

please resolve the conflicts

@spartan124
Copy link
Contributor Author

resolved

@greatest0fallt1me
Copy link
Contributor

@spartan124 Can you resolve the conflicts?

@spartan124
Copy link
Contributor Author

spartan124 commented Feb 27, 2026

All conflicts resolved. Other merges messed with the package.json, package-lock.json and other critical files in the code base. You should avoid this by resolving merge conflicts yourself, as a contributor might not fully understand the implications of picking one implementation over another and some just accept both changes ending up breaking both syntax and code base.

@greatest0fallt1me Merge as soon as possible, before other PRs gets merged.

@spartan124 spartan124 closed this Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Design and stub risk scoring model (backend representation)

2 participants