You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
There are two credit tier tables in the API and they disagree with each other and with the documented protocol parameters.
src/modules/loans/loans.service.ts defines a private mapScoreToCreditTier() at line 675 returning gold 5000, silver 3000, bronze 1500, poor 500. context/architecture-context.md in StepFi-Contracts documents the protocol tiers as Gold 10000, Silver 5000, Bronze 2500, Starter 1000. Every number differs, and the lowest tier is named poor here and Starter there.
CreditScoringService is properly injected at line 94 and assess() is called at line 671. But line 343 does not use it. It calls the local mapScoreToCreditTier() and assigns the result to reputationTier at line 366. So the credit-scoring module is wired up and then bypassed on the path that actually determines a borrower's tier.
Answering the Phase 2 question directly: credit scoring is not an unused separate module, it is imported and called. The defect is that a second hardcoded table shadows it and wins on the tier assignment path.
For a real learner: the credit limit they are shown depends on which code path computed it, and neither path matches the limits the contracts enforce. A learner can be told they qualify for an amount the chain will reject.
This is distinct from #65, which is about replacing the heuristic with a data-driven model. This issue is about removing a duplicate that contradicts both the existing service and the documented parameters. Fixing this first makes #65 tractable, because there will be one place to change.
Ground Rules
This issue must be solved according to StepFi's established engineering standards. Before writing any code:
Read context/architecture-context.md in full
Read context/code-standards.md in full
Read context/progress-tracker.md to understand what has already been built and why, so you do not duplicate or contradict recent work
Read src/modules/credit-scoring/credit-scoring.service.ts in full, especially assess() and its AssessParams, to understand what the canonical implementation already provides
Read src/modules/loans/loans.service.ts around lines 340 to 370 and 660 to 695 to see both call paths before changing either
Read the Reputation to Credit Tiers table in context/architecture-context.md in the StepFi-Contracts repo, and parameters-contract for the values the chain actually enforces
Read src/modules/loans/dto/available-credit-response.dto.ts and loan-quote-response.dto.ts, since the tier name is exposed through these
Your PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.
What To Build
Establish which tier values are authoritative before writing code. The contracts and parameters-contract govern what the chain enforces, so the API must match them, not the reverse. Record the decision in the PR description.
Delete mapScoreToCreditTier() from loans.service.ts entirely. Do not leave it unused.
Change the call site at line 343 to use CreditScoringService, so both paths in this file resolve tiers through the same service.
Move the tier thresholds and limits into named constants in credit-scoring.service.ts. No magic numbers inline. One table, one place.
Reconcile the tier naming. poor and Starter refer to the same tier. Pick one, apply it consistently across the service, the DTOs, and any consumer, and note the choice in the PR. If this changes an API response value, flag it as a breaking change for StepFi-Web and StepFi-App.
Add a test asserting the API's tier boundaries match the values documented for the contracts, so the two cannot silently drift apart again. This is the test that would have caught the original divergence.
Add tests for the boundary scores themselves: exactly 90, 89, 75, 74, 60, 59. Off-by-one errors at tier edges directly change how much a learner can borrow.
mapScoreToCreditTier() no longer exists in loans.service.ts
Every tier resolution in the API goes through CreditScoringService
Tier thresholds and limits exist as named constants in exactly one file
API tier limits match the values enforced by the contracts, verified by a test
Tier naming is consistent across service, DTOs, and consumers
Boundary scores 90, 89, 75, 74, 60, and 59 each have an explicit test
Any response-shape change is called out as breaking for StepFi-Web and StepFi-App
All monetary operations validated and safe against overflow and invalid input
Mandatory Checks Before Opening PR
All context/ files read and understood
Code follows context/code-standards.md exactly
context/progress-tracker.md updated with an accurate entry describing the change and why
Build passes with zero errors (npm run build)
All existing tests still pass, test count has not decreased
New tests written covering the new functionality
No new any types
Full Swagger decorators on any new or changed endpoint
PR template filled out completely, including the "problem this solves" and "how it was tested" sections
PR references this issue number exactly
PRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.
Problem
There are two credit tier tables in the API and they disagree with each other and with the documented protocol parameters.
src/modules/loans/loans.service.tsdefines a privatemapScoreToCreditTier()at line 675 returning gold 5000, silver 3000, bronze 1500, poor 500.context/architecture-context.mdin StepFi-Contracts documents the protocol tiers as Gold 10000, Silver 5000, Bronze 2500, Starter 1000. Every number differs, and the lowest tier is namedpoorhere andStarterthere.CreditScoringServiceis properly injected at line 94 andassess()is called at line 671. But line 343 does not use it. It calls the localmapScoreToCreditTier()and assigns the result toreputationTierat line 366. So the credit-scoring module is wired up and then bypassed on the path that actually determines a borrower's tier.Answering the Phase 2 question directly: credit scoring is not an unused separate module, it is imported and called. The defect is that a second hardcoded table shadows it and wins on the tier assignment path.
For a real learner: the credit limit they are shown depends on which code path computed it, and neither path matches the limits the contracts enforce. A learner can be told they qualify for an amount the chain will reject.
This is distinct from #65, which is about replacing the heuristic with a data-driven model. This issue is about removing a duplicate that contradicts both the existing service and the documented parameters. Fixing this first makes #65 tractable, because there will be one place to change.
Ground Rules
This issue must be solved according to StepFi's established engineering standards. Before writing any code:
src/modules/credit-scoring/credit-scoring.service.tsin full, especiallyassess()and itsAssessParams, to understand what the canonical implementation already providessrc/modules/loans/loans.service.tsaround lines 340 to 370 and 660 to 695 to see both call paths before changing eithercontext/architecture-context.mdin the StepFi-Contracts repo, andparameters-contractfor the values the chain actually enforcessrc/modules/loans/dto/available-credit-response.dto.tsandloan-quote-response.dto.ts, since the tier name is exposed through theseYour PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.
What To Build
parameters-contractgovern what the chain enforces, so the API must match them, not the reverse. Record the decision in the PR description.mapScoreToCreditTier()fromloans.service.tsentirely. Do not leave it unused.CreditScoringService, so both paths in this file resolve tiers through the same service.credit-scoring.service.ts. No magic numbers inline. One table, one place.poorandStarterrefer to the same tier. Pick one, apply it consistently across the service, the DTOs, and any consumer, and note the choice in the PR. If this changes an API response value, flag it as a breaking change for StepFi-Web and StepFi-App.Files To Touch
src/modules/loans/loans.service.tssrc/modules/credit-scoring/credit-scoring.service.tssrc/modules/credit-scoring/dto/credit-scoring-response.dto.tssrc/modules/loans/dto/available-credit-response.dto.tssrc/modules/credit-scoring/credit-scoring.service.spec.tssrc/modules/loans/loans.service.spec.tsAcceptance Criteria
mapScoreToCreditTier()no longer exists inloans.service.tsCreditScoringServiceMandatory Checks Before Opening PR
npm run build)anytypesPRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.