Skip to content

fix: remove duplicate credit-tier table from LoansService #100

Description

@EmeditWeb

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.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:

  1. Read context/architecture-context.md in full
  2. Read context/code-standards.md in full
  3. Read context/progress-tracker.md to understand what has already been built and why, so you do not duplicate or contradict recent work
  4. Read src/modules/credit-scoring/credit-scoring.service.ts in full, especially assess() and its AssessParams, to understand what the canonical implementation already provides
  5. Read src/modules/loans/loans.service.ts around lines 340 to 370 and 660 to 695 to see both call paths before changing either
  6. 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
  7. 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

  1. 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.
  2. Delete mapScoreToCreditTier() from loans.service.ts entirely. Do not leave it unused.
  3. Change the call site at line 343 to use CreditScoringService, so both paths in this file resolve tiers through the same service.
  4. Move the tier thresholds and limits into named constants in credit-scoring.service.ts. No magic numbers inline. One table, one place.
  5. 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.
  6. 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.
  7. 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.
  8. Do not change scoring inputs or introduce a data-driven model. That is feat: implement data-driven credit-scoring model #65.

Files To Touch

  • src/modules/loans/loans.service.ts
  • src/modules/credit-scoring/credit-scoring.service.ts
  • src/modules/credit-scoring/dto/credit-scoring-response.dto.ts
  • src/modules/loans/dto/available-credit-response.dto.ts
  • src/modules/credit-scoring/credit-scoring.service.spec.ts
  • src/modules/loans/loans.service.spec.ts

Acceptance Criteria

  • 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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions