# Credit Intelligence

Credit Intelligence is a standalone scoring service. It can be consumed by
partners or internal services without writing to Fineract. Core is only needed
when an approved score becomes an actual MobiCred credit product.

The service has two connected branches:

1. Data ingestion and normalization.
2. Feature materialization and score production.

## Identity Model

Partners create and keep their own tenant-local users. If one person appears in
four partner systems, each partner can have its own customer row and customer
reference. Credit Intelligence links those references to one canonical MobiCred
subject for scoring and audit.

When credit is actually issued, Core maps that canonical subject to the single
Core/Fineract customer. Credit Intelligence does not create Fineract customers,
accounts, loans, or disbursements.

## Ingestion

The ingestion branch needs adapters for API feeds, flat files, batch exports,
webhooks, and manual uploads. Sources can include MobiCred account activity,
Fineract loan history, Orange, MTN, Moov, WAE, partner transactions, POS
activity, and mobile app telemetry.

Ingestion must preserve lineage: who provided the data, when it arrived, what
consent or contract allows its use, and which normalized subject it was linked
to.

## Standalone Score Request

Use the [Credit Intelligence API reference](/api/credit-intelligence) or the
Bruno collection in `bruno/mobicred-partner-credit-demo`.

The partner scoring request identifies the person with:

- `tenant_id`
- `partner_code`
- `subject.source_system`
- `subject.external_subject_ref`

Core or the gateway should also send trusted boundary headers:
`X-MobiCred-Tenant-Id` and `X-MobiCred-Partner-Code`. In strict mode, Credit
Intelligence rejects the request unless those headers match `tenant_id` and
`partner_code` in the body. This keeps a partner from choosing another tenant or
partner code by editing the payload.

For traceability, send `X-Request-Id` for the current HTTP attempt and
`X-Correlation-Id` for the full customer/operator flow. Core, Credit
Intelligence, and the scoring-admin proxy echo those headers so one mobile
registration, pay-in, scoring request, or operator review can be followed across
service logs without putting secrets in payloads.

It does not send `core_customer_id` or `fineract_client_id`. Those values come
from a verified subject link when the score must be handed off to Core for
credit execution.

## Scoring Admin Dashboard APIs

The scoring admin dashboard can run in two modes:

- Live mode: a Keycloak scoring-admin bearer token is stored in the dashboard
  session, and the dashboard reads Credit Intelligence admin APIs.
- Seeded demo mode: no token is present, so the dashboard clearly shows seeded
  demo data rather than pretending it is live production data.

Live mode uses these Keycloak-protected read models:

- `GET /score-decisions/admin/dashboard-summary`
- `GET /score-decisions/admin/search`
- `POST /score-decisions/admin/search/reindex`
- `GET /score-decisions/admin/search/sync-status`
- `POST /score-decisions/admin/search/sync-retry`
- `GET /score-decisions/admin/filter-facets`
- `GET /score-decisions/admin/geo-aggregates`
- `GET /score-decisions/admin/geo-intelligence`
- `GET /score-decisions/admin/subjects/{subjectId}`
- `GET /score-decisions/admin/session`
- `GET /score-decisions/admin/notifications`
- `POST /score-decisions/admin/{decisionId}/review-actions`

The database remains the source of truth. Elasticsearch is an acceleration and
exploration layer for search, audit, analytics, and facets; writes still land in
the database first, then sync to search. Operator review actions are recorded in
the score decision trace and reindexed for search/audit exploration. If
Elasticsearch was unavailable or a staging environment was rebuilt after data
already existed, run `GET /score-decisions/admin/search/sync-status` first. Use
`POST /score-decisions/admin/search/sync-retry` for short Elasticsearch outages
where writes were recorded in the retry outbox. Use `POST
/score-decisions/admin/search/reindex` for broader staging resets, template
changes, or historical replay from Postgres into the search indexes. The sync
status response now reports `synced`, `catching_up`, or `blocked` freshness plus
pending, failed, dead-letter, retry-ready, and oldest retryable age totals.

Geo Intelligence uses `GET /score-decisions/admin/geo-intelligence` for the
operator dashboard panels. It composes live score geography with identity-link
coverage, source freshness, partner footprint, country policy packs, and zone
references. The response keeps Postgres as the source of truth and marks
geography as context only, never an approval or decline reason by itself. Small
zone cohorts suppress score and decision-mix fields so the dashboard can show
coverage without exposing sensitive credit-outcome patterns.

## Feature Materialization

The feature branch turns raw facts into stable scoring inputs:

- Demographics and declared profile completeness.
- Payment habits, repayment behavior, defaults, reversals, and disputes.
- Location and zoning signals such as Cocody versus Abobo, without making
  neighborhood a hidden unfair substitute for protected characteristics.
- Mobility pattern stability based on app position, with explicit consent and
  retention limits.
- Account age, income proxy, balance volatility, transaction regularity, and
  device risk.

## Decision Rule

Every score response should return a score, a band, the model version, the data
freshness window, and human-readable reason codes. A score without explainable
reason codes is not partner-ready.
