An Angular 19 front-end application that recommends optimal collections outreach strategies for delinquent borrowers using deterministic business rules, recovery probability scoring, and AI-generated customer communications.
Collections teams manage hundreds of delinquent accounts with varying risk profiles, engagement levels, and hardship situations. Manual prioritization is slow, inconsistent, and difficult to audit. This application provides:
- Automated borrower segmentation based on delinquency attributes
- Next-best-action recommendations (channel, timing, outreach type)
- Recovery probability estimation to prioritize high-yield accounts
- AI-generated, customer-safe messaging for outreach
- Explainability so agents understand why a recommendation was made
- Role-based access and audit logging for compliance
┌─────────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Dashboard │ │ Details │ │ Audit Log │ │
│ │ Page │ │ Page │ │ (Supervisor) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
│ │ │ │ │
│ ┌──────┴─────────────────┴──────────────────────┴───────────┐ │
│ │ Shared Components & Guards │ │
│ │ Header | Explainability Panel | Badges | SupervisorGuard │ │
│ └──────────────────────────┬────────────────────────────────┘ │
└─────────────────────────────┼───────────────────────────────────┘
│
┌─────────────────────────────┼───────────────────────────────────┐
│ Service Layer (Business Logic) │
│ ┌────────────────┐ ┌───────────────────┐ ┌───────────────┐ │
│ │ BorrowerService│ │SegmentationService│ │RecoveryProb. │ │
│ └───────┬────────┘ └─────────┬─────────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌───────┴─────────────────────┴─────────────────────┴───────┐ │
│ │ RecommendationService (Orchestrator) │ │
│ └──────────────────────────┬────────────────────────────────┘ │
│ ┌───────────────────────────┼────────────────────────────────┐ │
│ │ AiService AuditService AuthService │ │
│ └──────────────────────────┴────────────────────────────────┘ │
└─────────────────────────────┼───────────────────────────────────┘
│
┌─────────────────────────────┼───────────────────────────────────┐
│ Data Layer │
│ mock-borrowers.json (assets) | In-memory signals (state) │
│ External LLM API (optional) | environment.aiToken │
└─────────────────────────────────────────────────────────────────┘
src/app/
├── components/ # Reusable UI (header, explainability panel)
├── pages/ # Route-level views (dashboard, details, audit)
├── services/ # All business logic
├── models/ # TypeScript interfaces & types
├── shared/ # Guards, badges, utilities
└── data/ # (reserved for static constants)
src/assets/
└── mock-borrowers.json # 12 realistic borrower records
src/environments/
├── environment.ts # Dev config (aiToken here)
└── environment.prod.ts # Production config
| Principle | Implementation |
|---|---|
| Separation of concerns | UI components are thin; all logic in services |
| Deterministic rules | Segmentation & scoring are rule-based, testable |
| AI as augmentation | LLM used for message generation & natural-language explainability |
| Signals for reactivity | Auth role, audit logs, recommendations use Angular signals |
| No backend | Mock JSON + in-memory state; suitable for demo/interview |
| Feature | Description |
|---|---|
| Borrower Dashboard | Table with Name, DPD, Amount, Segment, Action, Recovery % |
| Borrower Details | Full profile, recommendation, channel/time, AI message |
| Explainability Panel | Rule-by-rule breakdown of segmentation decision |
| AI Message Generation | Customer-safe outreach via LLM API (with fallback templates) |
| Recovery Probability | Scoring model (0–100) based on borrower attributes |
| Role-Based Access | Agent vs Supervisor (audit log restricted) |
| Audit Logging | Tracks analyze, recommend, message-generate, and explanation events |
| Security | Token from environment, route guards, no PII in prompts beyond name |
This project is intentionally scoped for a clear demo and interview discussion. Each assessment criterion is addressed as follows:
| Criterion | How it is covered |
|---|---|
| Clear end-to-end flow | Single Angular SPA: Dashboard → Borrower Details → Analyze → Why? → Generate Message → Supervisor Audit Log. No backend, no NgRx — logic lives in services. |
| Small, realistic mock data | 12 synthetic borrowers in mock-borrowers.json, each designed to trigger a distinct segmentation path (see §3.2). |
| Grounded, concise, auditable AI | LLM prompts are fed only deterministic outputs (segment, action, channel, DPD, amount). Explain prompts are capped at 3–5 sentences. All AI actions are audit-logged. |
| Documented assumptions & trade-offs | §9 Assumptions, §10 Limitations, §10.1 Trade-offs, §10.2 Edge Cases, §10.3 Failure Modes. |
| Demo-ready explanations | §3.3 Demo Script, §6 AI prompts, ARCHITECTURE.md for deeper design Q&A. |
| Borrower | Key attributes | Expected segment | Why it matters |
|---|---|---|---|
| BRW-001, BRW-006, BRW-010 | High response, DPD < 30 | Willing but Delayed | Early delinquency, gentle SMS/email reminder |
| BRW-002, BRW-008, BRW-011 | Default catch-all pattern | Habitual Late Payer | BRW-011: high response but DPD 55 → not "Willing" (needs DPD < 30) |
| BRW-003, BRW-007 | hardshipIndicator: true |
Hardship Case | BRW-007: hardship wins over high DPD + low response (priority rule) |
| BRW-005 | Low response, DPD 38 | Unresponsive | Agent call vs escalation threshold at DPD 60 |
| BRW-004, BRW-009, BRW-012 | DPD > 90 and amount > ₹50k | High-Risk Escalation | Escalation vs manual review by response history |
- Dashboard — App auto-analyzes on first load. Show segment, action, and recovery % columns.
- BRW-001 (Priya Sharma) — Open details. Point out deterministic explainability factors (rule breakdown panel).
- Analyze — Re-run if needed; show toast and updated recommendation.
- Why? — AI explanation is grounded in the deterministic context string (segment, factors, recovery %). Not a black box.
- Generate Message — Customer-safe outreach copy; falls back to template if API unavailable.
- Switch to Supervisor — Header role toggle.
- Audit Log — Show
borrower_analyzed,recommendation_generated,message_generated, andexplanation_generatedentries with user, role, and timestamp.
Talking points: Segmentation/scoring = local rules (no API). AI = message + explanation only. Audit trail = compliance.
Rules are evaluated in priority order (first match wins):
| Priority | Segment | Condition |
|---|---|---|
| 1 | Hardship Case | hardshipIndicator === true |
| 2 | High-Risk Escalation | daysPastDue > 90 AND overdueAmount > 50000 |
| 3 | Unresponsive | responseHistory === 'low' AND daysPastDue > 30 |
| 4 | Willing but Delayed | responseHistory === 'high' AND daysPastDue < 30 |
| 5 | Habitual Late Payer | Default (poor/moderate payment behavior pattern) |
| Segment | Typical Action |
|---|---|
| Willing but Delayed | SMS Reminder / Email Reminder |
| Habitual Late Payer | Agent Call / Payment Plan Offer |
| Hardship Case | Hardship Support / Payment Plan Offer |
| Unresponsive | Agent Call / Escalation |
| High-Risk Escalation | Escalation / Manual Review |
Base score: 50 (neutral starting point)
| Factor | Adjustment |
|---|---|
| Good payment history | +15 |
| Fair payment history | +5 |
| Poor payment history | −10 |
| High response rate | +15 |
| Medium response rate | +5 |
| Low response rate | −15 |
| DPD > 90 | −20 |
| DPD 61–90 | −15 |
| DPD 31–60 | −10 |
| DPD < 15 | +5 |
| Hardship indicator | −10 |
| 3+ broken promises | −10 |
| 1–2 broken promises | −5 |
| Amount > ₹50,000 | −10 |
Final score is clamped to [0, 100].
Endpoint: POST https://llm-wrapper-741152993481.asia-south1.run.app/llm/query
Configuration: Token is read from environment.aiToken — never hardcoded.
// src/environments/environment.ts
export const environment = {
production: false,
aiToken: '', // Set your token here
aiApiUrl: 'https://llm-wrapper-741152993481.asia-south1.run.app/llm/query',
};Uses:
- Message generation — empathetic, customer-safe outreach copy
- Natural-language explainability — "Why?" button on borrower details
Grounding: AI never decides the segment or action. It only rephrases outputs from SegmentationService, RecoveryProbabilityService, and RecommendationService. The explainability context passed to the LLM is a structured text block built from rule factors.
Conciseness: The explanation prompt explicitly limits responses to 3–5 sentences and forbids exposing internal scoring formulas.
Auditability: Every AI touchpoint writes to AuditService:
message_generated— when outreach copy is createdexplanation_generated— when "Why?" is answered
Message generation prompt:
Write a customer-safe, empathetic collections outreach message for the following scenario.
Do not use threatening language. Be professional, concise, and action-oriented.
Borrower Name: {name}
Days Past Due: {dpd}
Overdue Amount: ₹{amount}
Segment: {segment} ← from deterministic rules
Recommended Action: {action} ← from deterministic rules
Channel: {channel}
Write only the message body suitable for {channel}. No subject lines unless email.
Explanation prompt:
You are a collections strategy assistant. Answer the following question clearly and
professionally for a collections agent. Keep the response concise (3-5 sentences).
Do not include sensitive internal scoring details.
Context:
{deterministic explainability context — factors, segment, action, recovery %, channel, time}
Question: Why is this borrower assigned to {segment} with recommended action {action}?
Fallback: If no token is configured or the API fails, deterministic template messages are used. The UI remains functional and auditable.
All types live under src/app/models/. Mock borrower records are in src/assets/mock-borrowers.json.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique borrower ID (e.g. BRW-001) |
name |
string |
Borrower display name |
email |
string |
Contact email |
phone |
string |
Contact phone (E.164-style) |
daysPastDue |
number |
Days past due (DPD) |
overdueAmount |
number |
Outstanding overdue balance (INR) |
priorPaymentBehavior |
'good' | 'fair' | 'poor' |
Historical repayment track record |
responseHistory |
'high' | 'medium' | 'low' |
Past outreach engagement level |
preferredChannel |
'sms' | 'email' | 'phone' | 'mail' |
Borrower channel preference |
repaymentPromises |
number |
Count of broken repayment promises |
hardshipIndicator |
boolean |
Financial hardship flag |
Produced by RecommendationService.analyze() and stored in memory per borrower.
| Field | Type | Description |
|---|---|---|
borrowerId |
string |
FK to borrower |
segment |
BorrowerSegment |
One of 5 segments (see §4) |
recommendedAction |
RecommendedAction |
Next-best action (7 types) |
bestChannel |
CommunicationChannel |
SMS, Email, Phone, or Mail |
bestTime |
string |
Suggested outreach time window |
recoveryProbability |
number |
Estimated recovery likelihood (0–100) |
explainability |
ExplainabilityFactor[] |
Rule factors shown in UI panel |
reasoningSummary |
string |
Human-readable summary |
generatedMessage |
string? |
AI/template outreach copy (after generate) |
analyzedAt |
string |
ISO timestamp of analysis |
ExplainabilityFactor: { attribute, value, impact } — one row per rule input.
| Field | Type | Description |
|---|---|---|
id |
string |
Unique audit entry ID |
timestamp |
string |
ISO timestamp |
action |
AuditActionType |
borrower_analyzed, recommendation_generated, message_generated, explanation_generated |
borrowerId |
string |
Affected borrower |
borrowerName |
string |
Borrower name at time of action |
performedBy |
string |
Agent/supervisor display name |
role |
string |
agent or supervisor |
details |
string |
Free-text action summary |
| Field | Type | Description |
|---|---|---|
id |
string |
Mock user ID |
name |
string |
Display name |
role |
'agent' | 'supervisor' |
RBAC role |
| Area | Measure |
|---|---|
| API token | Stored in environment.ts, excluded from source control in production via CI secrets |
| Role-based access | AuthService + supervisorGuard restrict audit log route |
| Audit trail | All analyze, recommend, message, and explanation actions logged with user, role, timestamp |
| AI prompts | Customer-safe instructions; no internal scoring formulas exposed to borrowers |
| Input validation | Borrower data loaded from trusted mock JSON (no user-supplied attributes) |
| HTTPS | LLM API called over HTTPS with Bearer auth |
| PII handling | Demo uses synthetic data; production would require data masking in prompts |
Even though auth is mocked client-side, a real lending deployment would add:
| Concern | Production approach |
|---|---|
| Authentication | OAuth2 / OIDC or enterprise SSO; short-lived JWT access tokens |
| Authorization | Server-enforced RBAC — agents see assigned portfolios only; supervisors see team/org scope |
| Borrower data isolation | Row-level security in the API/DB (agent_id / team_id on every query); no client-side-only guards |
| Sensitive data in AI | Mask or tokenize PII in LLM prompts; log prompt hashes, not full borrower records |
| Audit persistence | Append-only audit store (PostgreSQL / immutable log stream); retention per compliance policy |
| Secrets | LLM API keys in vault/CI secrets — never in frontend bundles |
| Transport | TLS everywhere; optional field-level encryption for contact details at rest |
- Borrower attributes are pre-validated and normalized (enums for behavior/response levels).
- Segmentation rules are mutually exclusive via priority ordering.
- INR (₹) is the currency for overdue amounts; threshold of ₹50,000 for high-risk.
- "Habitual Late Payer" is the catch-all for accounts not matching higher-priority rules.
- Role switching is simulated client-side (no real authentication backend).
- AI token is provided by the interviewer/assessor at runtime.
- Communication time windows are heuristic-based, not ML-optimized.
- No persistent storage — recommendations and audit logs reset on page refresh
- No real authentication — role switching is a UI toggle for demo purposes
- Mock data only — no integration with loan servicing systems
- Single-tenant — no multi-org or team-level permissions
- AI dependency — message quality depends on external LLM availability
- No A/B testing — recovery probability is estimated, not validated against outcomes
| Decision | Benefit | Cost |
|---|---|---|
| No backend | Fast to build and demo; zero infra | No persistence, no real auth |
| Priority-ordered rules | Auditable, testable, explainable | May miss nuanced cases that ML could catch |
| Hybrid AI (rules + LLM) | Compliance-friendly decisions + natural language | Two systems to maintain; LLM can still hallucinate on edge phrasing |
| In-memory audit log | Simple signal-based reactivity | Lost on refresh |
| Client-side RBAC | Easy role demo | Not secure for production |
| Fallback AI templates | Works offline / without token | Less personalized than live LLM |
| 12 mock borrowers | Covers all segments without noise | Not representative of production portfolio scale |
| Scenario | Behavior |
|---|---|
| Hardship + high DPD + low response (BRW-007) | Hardship rule wins (priority 1) → Hardship Case, not High-Risk or Unresponsive |
| High response but DPD ≥ 30 (BRW-011) | Does not qualify as Willing but Delayed → falls through to Habitual Late Payer |
| High-Risk with medium response (BRW-009) | Segment = High-Risk Escalation, action = Manual Review (not Escalation) |
| Generate message before analyze | RecommendationService auto-analyzes first, then calls AI |
| Missing borrower ID in URL | Details page shows empty state; no API calls |
| Unknown borrower ID | Borrower set to null after mock lookup |
| No AI token configured | Fallback templates used; no HTTP call to LLM |
| LLM returns empty string | Falls back to deterministic template |
Agent opens /audit |
Guard redirects; audit page shows access-restricted message |
| Failure | User impact | System response |
|---|---|---|
| LLM API down / 5xx | Message or explanation may be generic | AiService catches error → fallback template; toast on message failure |
| LLM returns empty body | User still sees usable text | map() falls back to template |
| Mock JSON load fails | Dashboard empty | HttpClient error surfaces in console; no borrowers shown |
| Page refresh | All in-memory state lost | User re-analyzes; audit log clears |
Invalid environment.aiToken |
API 401/403 | Same as API failure — fallback templates |
| Concurrent analyze on same borrower | Last write wins | Recommendation map overwritten (acceptable for demo) |
- Backend API with persistent audit store and real RBAC (OAuth/JWT)
- ML-based recovery probability trained on historical collection outcomes
- A/B testing framework for outreach strategies
- Batch analyze all borrowers with progress indicator
- Export audit logs to CSV/PDF for compliance
- Integration with SMS/email gateways for direct outreach
- Real-time borrower data sync from core banking system
- Unit test coverage for segmentation, scoring, and services (100% coverage)
- E2E tests (Playwright/Cypress) for full user flows
- Node.js 18+ (tested with v22)
- npm 9+
cd collections-optimizer
npm installEdit src/environments/environment.ts:
export const environment = {
production: false,
aiToken: 'your-bearer-token-here',
aiApiUrl: 'https://llm-wrapper-741152993481.asia-south1.run.app/llm/query',
};Without a token, the app uses fallback message templates.
npm startnpm run buildOutput: dist/collections-optimizer/
- Dashboard — View all borrowers; click Analyze to generate recommendations
- Borrower Details — Click a name for full profile and recommendation
- Why? — Get AI-powered explanation of the recommendation
- Generate Message — Create customer-safe outreach copy
- Role Switch — Toggle Agent/Supervisor in header
- Audit Log — (Supervisor only) View all system actions
npm testHeadless CI run with coverage:
npm test -- --no-watch --browsers=ChromeHeadless --code-coverageCoverage: 100% statements, branches, functions, and lines (105 tests).
Test coverage includes:
| Spec File | What it tests |
|---|---|
auth.service.spec.ts |
Role switching, RBAC permissions, escalation |
borrower.service.spec.ts |
Mock JSON loading via HttpClient |
segmentation.service.spec.ts |
All 5 segmentation rules + priority |
recovery-probability.service.spec.ts |
Scoring model, clamping 0–100 |
recommendation.service.spec.ts |
Analyze orchestration, audit, AI message, explanation audit |
ai.service.spec.ts |
LLM API calls, no-token path, fallback on error/empty |
audit.service.spec.ts |
Audit log creation and filtering |
toast.service.spec.ts |
Toast show/dismiss/auto-dismiss |
supervisor.guard.spec.ts |
Route guard for audit page |
borrower-dashboard.component.spec.ts |
Dashboard load, analyze all, toasts |
borrower-details.component.spec.ts |
Details flow, analyze, explain, generate message |
borrower.fixtures.spec.ts |
Test data factory |
| Component specs | Header, explainability panel, badges, toast, audit log, app root |
- Angular 19 (Standalone Components, Signals)
- TypeScript 5.7
- Tailwind CSS 4
- Angular HttpClient
- RxJS 7
Assessment project — not for production use without further hardening.