# Credit Score Implementation Plan

## 1. Objective

Implement a transparent, auditable customer credit-scoring system for the microfinance application that uses the financial and repayment data already stored by the platform.

The score will be used as **decision support only**. It must not automatically approve or reject a loan. Final lending decisions remain with authorized staff and the existing loan approval workflow.

The implementation must preserve the existing branch-isolation rules, auditability, loan workflow, repayment calculations, and current UI conventions.

---

## 2. Current System State

The repository already contains most of the foundation needed for credit scoring:

- `CustomerRiskProfile` already stores `risk_level`, `score`, `factors`, `explanation`, and `calculated_at`.
- `CustomerRiskService` already exists but currently only checks active blacklist status and otherwise leaves the customer unrated.
- `Customer` already exposes loans, loan payments, collateral, blacklist records, monthly income, documents, guarantors, addresses, and a risk profile.
- `Loan` already stores outstanding balances, overdue balances, `days_overdue`, `arrears_bucket`, total paid, status, write-off state, payments, restructurings, penalties, and collateral relationships.
- `LoanInstallment` already stores due date, amount due, amount paid, remaining balance, status, penalties, and active schedule version.
- `Collateral` already stores estimated value, accepted value, custody/status information, and customer/loan relationships.
- `BlacklistRecord` already supports active/resolved blacklist and watchlist records.
- Child models already follow the branch-isolation strategy, including parent-based branch scopes.

Therefore, the implementation should **extend the existing risk subsystem**, not create an unrelated second risk system.

---

## 3. Core Design Principles

1. **Explainable** — every score must show the main positive and negative factors that produced it.
2. **Auditable** — historical scores must never be overwritten without retaining a snapshot.
3. **Branch safe** — no score, history, recalculation, report, or API response may expose customers outside the current authorized branch context.
4. **Deterministic** — identical customer data under the same score version must produce the same score.
5. **Versioned** — every calculation must record which scoring formula produced it.
6. **Non-discriminatory** — do not use gender, marital status, age/date of birth, ethnicity, religion, geographic location, or similar personal traits as scoring inputs.
7. **NIN-neutral** — customers registered without NIN must not automatically receive a lower score solely because they do not have NIN.
8. **Human-reviewed** — the score supports loan officers/inspectors; it does not replace the approval process.
9. **Thin-file aware** — new customers with little repayment history should be marked as low-confidence rather than automatically classified as bad borrowers.
10. **No hidden manual score editing** — staff may add review notes or overrides to a loan decision, but the computed credit score itself should remain system-calculated.

---

## 4. Score Scale

Use a familiar customer-facing range of **300–850**.

Internally, calculate a normalized score from `0–100`, then map it to the displayed credit score:

```text
credit_score = round(300 + (normalized_score * 5.5))
```

This guarantees the score always remains between 300 and 850.

### Proposed Bands

| Score | Band | Internal Risk Level |
|---|---|---|
| 800–850 | Excellent | low |
| 740–799 | Very Good | low |
| 670–739 | Good | moderate |
| 580–669 | Fair | high |
| 300–579 | Poor | very_high |

`unrated` remains valid only when the system cannot calculate a meaningful score.

The exact boundaries must live in configuration and be versioned so they can be calibrated later without scattering values throughout controllers or views.

---

## 5. Proposed Scoring Model — Version 1

The first version should use only data already present or naturally derived from the system.

### 5.1 Repayment Behaviour — 40%

This should be the strongest component once loan history exists.

Signals:

- percentage of installments paid on or before due date;
- percentage paid within a small grace window;
- number and percentage of missed installments;
- current `days_overdue`;
- maximum historical days overdue;
- average historical days overdue;
- current overdue amount relative to scheduled/loan amount;
- frequency and amount of late-payment penalties;
- number of loans that entered serious arrears.

Important implementation detail:

- use only active schedule versions for current repayment calculations;
- preserve historical schedule data where it is necessary to evaluate actual payment behaviour;
- do not double-count installments that were replaced by a restructuring.

### 5.2 Loan Performance History — 20%

Signals:

- number of successfully completed/fully paid loans;
- number of active loans with good standing;
- write-offs;
- unrecovered written-off balances;
- restructurings;
- closed-through-top-up history;
- successful early settlements where they reflect good repayment rather than distress.

A completed loan with clean repayment should progressively improve the score.

A write-off must strongly reduce risk quality, but recovery of written-off amounts should be reflected separately so the effect can improve over time.

### 5.3 Affordability and Current Exposure — 20%

Signals:

- monthly scheduled debt obligations compared with `monthly_income`;
- total active loan outstanding compared with monthly income;
- number of concurrent active loans;
- current overdue obligations;
- proposed loan installment burden when calculating a loan-application preview score.

Suggested measures:

```text
monthly_debt_service_ratio = active_monthly_installments / monthly_income
outstanding_to_income_ratio = active_total_outstanding / monthly_income
```

If income is missing or zero, do not silently assume the customer has no income. Use a neutral component value, lower confidence, and require human review.

### 5.4 Collateral Coverage — 10%

Signals:

- accepted collateral value relative to active/proposed principal;
- whether collateral is inspected/accepted;
- whether collateral is currently in valid custody/status;
- whether required collateral is missing.

Suggested measure:

```text
collateral_coverage_ratio = accepted_collateral_value / relevant_principal
```

Do not use estimated value when an accepted value exists.

### 5.5 Profile and Financial Data Completeness — 5%

This is a small confidence-oriented component, not a demographic component.

Signals can include availability of:

- occupation/business information;
- monthly income;
- address;
- primary next of kin;
- active guarantor when required;
- required customer documents.

Do **not** award or deduct points based on gender, marital status, age, location, or lack of NIN by itself.

### 5.6 Conduct and Control Flags — 5%

Signals:

- active blacklist;
- active watchlist;
- repeated serious collection escalation;
- unresolved fraud/control flags if such flags are introduced later.

An active blacklist should force `manual_review_required = true`.

It should not silently auto-reject the loan through the scoring engine.

---

## 6. Overlay Rules

Some events are important enough to affect how a score is interpreted even after weighted calculation.

Proposed V1 overlays:

- active blacklist → manual review required, risk cannot be presented as `low`;
- unresolved write-off → manual review required and high-risk classification;
- 90+ current days overdue → manual review required and high-risk classification;
- missing monthly income when an affordability decision is needed → manual review required;
- no repayment history → mark as `thin_file`, reduce confidence, but do not automatically classify as poor.

Overlay thresholds must be declared in `config/credit_score.php` and covered by tests.

---

## 7. Thin-File / New Customer Handling

A new customer without loan history must not be treated as a defaulter.

For components that genuinely have no historical data:

- use a neutral component score instead of zero;
- reduce the overall confidence level;
- add `thin_file = true` to factors;
- explain which historical signals are unavailable;
- keep manual loan review mandatory where appropriate.

Suggested confidence behaviour:

- full repayment history and income data → high confidence;
- limited loan history → medium confidence;
- no loan history → confidence capped around 50–60%;
- no loan history plus missing income → low confidence/manual review.

Confidence must be stored separately from the credit score. A score of 700 with 45% confidence must not look equivalent to 700 with 95% confidence.

---

## 8. Database Changes

### Phase 1 Migration — Extend `customer_risk_profiles`

Add fields such as:

```text
score_band
score_version
confidence
thin_file
manual_review_required
last_trigger
```

Keep the existing fields:

```text
customer_id
risk_level
score
factors
explanation
calculated_at
```

`factors` remains JSON and should contain the detailed component calculations used for explainability.

### Create `customer_credit_score_histories`

Purpose: immutable snapshots for audit, trends, and calibration.

Suggested columns:

```text
id
customer_id
branch_id
score
score_band
risk_level
confidence
thin_file
manual_review_required
score_version
trigger
component_scores JSON
factors JSON
explanation TEXT
calculated_at
created_by nullable
timestamps
```

Indexes:

```text
(customer_id, calculated_at)
(branch_id, calculated_at)
(score_band, calculated_at)
(score_version, calculated_at)
```

The history model must use the same branch-isolation strategy already established elsewhere in the application.

### Link Loan Applications to a Score Snapshot

Add a nullable field to `loans`:

```text
application_credit_score_history_id
```

When an application is submitted for review:

1. refresh the customer credit score;
2. create an immutable score-history row;
3. attach that history row to the loan.

This preserves the exact score the reviewer saw when the application entered review.

If the customer's score changes later, the loan should be able to display both:

- **Application Score** — immutable snapshot;
- **Current Score** — latest customer score.

---

## 9. Service Architecture

Do not place the scoring formula inside controllers.

Keep `CustomerRiskService` as the public orchestration entry point, because it already exists in the repository.

Introduce a dedicated credit-score layer:

```text
app/Services/CreditScore/
    CreditScoreCalculator.php
    CreditScoreResult.php
    Components/
        RepaymentBehaviourComponent.php
        LoanHistoryComponent.php
        AffordabilityComponent.php
        CollateralComponent.php
        ProfileCompletenessComponent.php
        ConductFlagsComponent.php
```

### `CreditScoreCalculator`

Responsibilities:

- load/receive normalized scoring inputs;
- execute each component;
- combine weighted normalized scores;
- apply overlay rules;
- map normalized score to 300–850;
- calculate band, risk level, confidence, and thin-file state;
- return a `CreditScoreResult` object;
- never persist data directly.

### `CustomerRiskService`

Responsibilities:

- authorize/operate within branch scope;
- gather customer data efficiently;
- invoke `CreditScoreCalculator`;
- update the latest `CustomerRiskProfile` snapshot;
- append `CustomerCreditScoreHistory` when required;
- produce the human-readable explanation;
- expose methods for current score and application snapshot calculation.

Suggested methods:

```php
refresh(Customer $customer, string $trigger = 'manual'): CustomerRiskProfile
refreshWithHistory(Customer $customer, string $trigger): CustomerCreditScoreHistory
previewForLoan(Customer $customer, Loan $loan): CreditScoreResult
```

---

## 10. Configuration

Create:

```text
config/credit_score.php
```

It should contain:

- score version (`v1`);
- minimum/maximum score;
- component weights;
- score bands;
- risk-level mapping;
- debt-service thresholds;
- arrears thresholds;
- collateral coverage thresholds;
- overlay rules;
- confidence rules;
- thin-file rules.

Do not make V1 weights editable from a normal settings page. Silent weight changes would make historical scores difficult to audit.

If configurable scoring is added later, every configuration change must create a new version and an audit record.

---

## 11. Data Query Strategy

The scorer must avoid N+1 queries.

Load only required data, preferably through aggregates where possible:

- active/completed/written-off loan counts;
- total active outstanding;
- total overdue;
- max/average days overdue;
- active installment totals;
- paid-on-time installment counts;
- restructuring count;
- write-off count/recovery amount;
- accepted collateral totals;
- blacklist/watchlist state.

Do not iterate through all historical records in PHP if the database can calculate the aggregate safely.

For larger deployments, introduce cached scoring metrics or scheduled aggregate tables only after profiling proves they are necessary.

---

## 12. Recalculation Triggers

Credit scores should refresh when meaningful credit data changes.

### Immediate / Event-Driven Triggers

Recalculate after:

- customer financial/profile fields affecting score are updated;
- a loan is activated/disbursed;
- a loan payment is recorded, corrected, reversed, or deleted;
- installment/arrears state changes;
- a loan becomes completed/fully paid;
- a restructuring is approved/applied;
- a write-off is approved;
- write-off recovery is recorded;
- collateral accepted value or credit-relevant status changes;
- blacklist/watchlist record is added, resolved, or reactivated.

Prefer domain events/listeners or service-layer hooks over broad model observers that can cause unexpected recalculation inside unrelated transactions.

Where possible, dispatch recalculation **after the originating transaction commits**.

### Scheduled Refresh

Add a daily scheduled command to refresh stale scores, for example:

```text
credit-scores:refresh-stale
```

This acts as a safety net if an event is ever missed.

The command must respect branch/business isolation and support chunked processing.

---

## 13. UI Integration

### 13.1 Customer Profile

Add a compact **Credit Score** section showing:

- score, e.g. `724`;
- band, e.g. `Good`;
- risk level;
- confidence;
- last calculated time;
- thin-file badge when applicable;
- manual-review warning when applicable;
- top positive factors;
- top negative factors;
- score trend from recent history.

Actions for authorized staff:

- `Recalculate`;
- `View score history`.

The UI should explain the score in ordinary language rather than exposing raw formulas only.

### 13.2 Loan Application / Review Page

Display:

- application credit score snapshot;
- current credit score when different;
- confidence;
- major risk factors;
- affordability ratio;
- arrears summary;
- collateral coverage summary;
- manual-review flags.

The reviewer must still make the actual approval/rejection decision through the existing approval workflow.

### 13.3 Customer List

Later phase:

- optional score column;
- score-band filter;
- risk-level filter;
- manual-review filter.

Do not add this until query performance and branch isolation are covered by tests.

---

## 14. Explainability Format

Every calculation should produce structured factor data similar to:

```json
{
  "score_version": "v1",
  "thin_file": false,
  "components": {
    "repayment_behaviour": {
      "weight": 40,
      "normalized_score": 82,
      "on_time_rate": 0.91,
      "max_days_overdue": 7
    },
    "loan_history": {
      "weight": 20,
      "normalized_score": 80,
      "completed_loans": 3,
      "write_offs": 0
    },
    "affordability": {
      "weight": 20,
      "normalized_score": 68,
      "debt_service_ratio": 0.31
    },
    "collateral": {
      "weight": 10,
      "normalized_score": 75,
      "coverage_ratio": 1.4
    },
    "profile_completeness": {
      "weight": 5,
      "normalized_score": 90
    },
    "conduct_flags": {
      "weight": 5,
      "normalized_score": 100,
      "active_blacklist": false
    }
  }
}
```

Human-readable explanation example:

```text
Good credit profile. Most previous installments were paid on time and the customer has completed three loans without a write-off. The main factor limiting the score is the current debt-service ratio of 31%.
```

Never expose internal notes or data belonging to another branch through explanations.

---

## 15. Permissions and Audit

Add or map permissions for:

```text
credit-score.view
credit-score.history
credit-score.recalculate
```

A future scoring-configuration permission can be added separately if configuration becomes editable.

Audit:

- manual recalculation requests;
- score configuration/version changes;
- loan decisions where the score snapshot was reviewed;
- any future manual risk override.

The score itself should be recalculated, not manually typed over.

---

## 16. Reporting and Calibration

After enough real repayment data exists, add management reports:

- customers by credit-score band;
- active portfolio outstanding by score band;
- arrears rate by score band;
- write-off/default rate by score band;
- average score by branch;
- score migration/trend over time;
- approval outcome by score band;
- repayment performance by score version.

These reports allow the organization to calibrate V2 using its own real portfolio rather than guessing forever.

A new scoring version must never rewrite historical V1 score rows.

---

## 17. Testing Strategy

### Unit Tests

Test each component independently:

- perfect repayment history;
- repeated late installments;
- serious arrears;
- fully paid loans;
- write-offs and recoveries;
- restructurings;
- missing income;
- high debt-service ratio;
- good/poor collateral coverage;
- blacklist/watchlist overlays;
- thin-file customers;
- score band boundaries;
- 300 minimum and 850 maximum;
- deterministic calculation.

### Feature Tests

Test:

- score refresh updates `customer_risk_profiles`;
- history snapshots are appended rather than overwritten;
- application snapshot remains immutable;
- payment recording triggers a refresh;
- loan completion changes relevant metrics;
- blacklist resolution changes relevant flags;
- recalculation permissions;
- score history permissions;
- manual review is not equivalent to automatic rejection.

### Branch Isolation Tests

Critical scenarios:

- branch user cannot view another branch's current score;
- branch user cannot view another branch's score history;
- recalculation endpoint cannot target an inaccessible customer ID;
- score reports return only authorized branch data;
- background jobs preserve business/branch context safely;
- application snapshots cannot be joined to inaccessible customers/loans.

### Sensitive-Field Guard Test

Add a test or static assertion ensuring the scoring calculator does not reference prohibited profile fields such as:

```text
gender
marital_status
date_of_birth
region
district
ward
```

NIN presence should not be a score penalty by itself.

---

## 18. Performance Requirements

For a single customer recalculation:

- use bounded query count;
- avoid loading unnecessary documents/files/binary data;
- aggregate repayment metrics in SQL where practical;
- eager-load only required relationships.

For batch refresh:

- process customers in chunks;
- make the job idempotent;
- support safe retry;
- prevent duplicate history spam for identical scheduled refreshes unless the score/factors changed materially;
- log failures without stopping all remaining customers.

---

## 19. Implementation Phases

### Phase 1 — Foundation

- add `config/credit_score.php`;
- extend `customer_risk_profiles`;
- create `customer_credit_score_histories`;
- add model relationships;
- add application score-snapshot FK to `loans`;
- preserve parent/branch scopes.

Suggested branch:

```text
feat/credit-score-phase-1-foundation
```

### Phase 2 — Calculation Engine

- add `CreditScoreResult`;
- create component calculators;
- implement normalized weighted score;
- implement 300–850 mapping;
- implement bands/risk levels;
- implement confidence/thin-file logic;
- implement overlay rules;
- unit-test all calculations.

Suggested branch:

```text
feat/credit-score-phase-2-engine
```

### Phase 3 — Risk Service Integration

- upgrade `CustomerRiskService`;
- write latest profile snapshot;
- write immutable history rows;
- add trigger metadata;
- add efficient aggregate queries;
- add audit hooks.

Suggested branch:

```text
feat/credit-score-phase-3-service
```

### Phase 4 — Automatic Refresh

- payment events;
- arrears/installment events;
- loan status events;
- restructuring/write-off/recovery events;
- collateral events;
- blacklist events;
- customer affordability-data updates;
- scheduled stale-score refresh command.

Suggested branch:

```text
feat/credit-score-phase-4-refresh
```

### Phase 5 — Customer UI

- credit score card;
- factor explanation;
- confidence/thin-file/manual-review states;
- score history page;
- recalculation action with permission/audit.

Suggested branch:

```text
feat/credit-score-phase-5-customer-ui
```

### Phase 6 — Loan Workflow Integration

- refresh and freeze score on application submission;
- link snapshot to loan;
- show application vs current score;
- show score factors during review;
- ensure approval remains a human action.

Suggested branch:

```text
feat/credit-score-phase-6-loan-review
```

### Phase 7 — Reporting and Calibration

- score distribution;
- portfolio at risk by score band;
- arrears/default by score band;
- score trend;
- score-version calibration report.

Suggested branch:

```text
feat/credit-score-phase-7-reporting
```

### Phase 8 — Hardening

- full feature test suite;
- branch-isolation regression tests;
- permission tests;
- performance profiling;
- explainability review;
- audit review;
- deployment migration review.

Suggested branch:

```text
feat/credit-score-phase-8-hardening
```

---

## 20. Recommended Implementation Order

Implement in this exact order:

1. configuration and score definitions;
2. schema/history foundation;
3. component calculator unit tests;
4. scoring calculator;
5. `CustomerRiskService` integration;
6. manual recalculation endpoint/action;
7. customer profile UI;
8. automatic recalculation events;
9. immutable loan application snapshot;
10. loan review UI;
11. reports/calibration;
12. performance and branch-isolation hardening.

This order allows the score engine to be verified before it influences any loan-review screen.

---

## 21. Acceptance Criteria

The credit score implementation is complete when:

- every eligible customer can have a reproducible 300–850 score;
- the calculation is versioned;
- component weights and thresholds live in configuration;
- score explanations identify major positive and negative factors;
- new customers are handled as thin-file rather than automatically poor;
- gender, age, marital status, geographic location, and similar traits are not used;
- lack of NIN alone does not lower the score;
- score confidence is visible separately from the score;
- current and historical scores are branch-isolated;
- score changes create auditable history;
- loan applications preserve an immutable score snapshot;
- the current score can still change after application submission;
- staff can see score factors during review;
- no score directly auto-approves or auto-rejects a loan;
- relevant loan/payment/arrears events refresh the score;
- nightly stale-score refresh exists as a fallback;
- tests cover score math, thin files, overlays, branch isolation, permissions, and history immutability;
- score calculation performs efficiently for both single customers and batches.

---

## 22. Future V2 Opportunities

Do not include these in V1 unless the foundation is stable:

- statistically calibrated weights from real portfolio performance;
- configurable scorecards with immutable versions;
- bureau/CRB integration where legally and commercially appropriate;
- income verification integrations;
- probability-of-default model separate from the customer-facing score;
- champion/challenger score models;
- score monitoring for drift;
- automated calibration dashboards;
- customer score improvement recommendations.

V2 should be based on observed repayment outcomes from the actual microfinance portfolio, not arbitrary formula changes.
