/nabard, reachable only by a NABARD role. Four screens: national view with the India→zone→state→district drill-down, partner-bank scorecard that expands into branches, early-warning digest, and the institution register. Deliberately a different chrome from the bank console. A NABARD user and a branch officer are not two ranks of one job; a portal that looked identical would invite the assumption that the apex user is a bank user with more rights. The sidebar states on every screen that this tier holds no borrower identity, so a supervisor can tell a partner bank what it can see without checking. Response rate is the column the scorecard is built around. Stress rate is mostly a fact about a bank's districts and sectors; response rate is a fact about the bank, it is the one no bank self-reports, and low values are coloured to draw the eye. Suppressed cells keep their row and lose their numbers — dropping the row reads as 'no enterprises here', a different and wrong claim, and the one a reader would most easily act on. Login routes by tier: both staff tiers share one sign-in and land in different portals. RequireApex bounces a bank session to /officer rather than to /login, because signed in as the wrong tier is not signed out. The routing is a convenience only — the backend re-derives the tier from the signed JWT and refuses the other surface with a 403. The demo fallback includes a partner bank below the suppression threshold, so the withholding behaviour is visible without a backend. tsc, oxlint and npm run build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
171 lines
5.5 KiB
TypeScript
171 lines
5.5 KiB
TypeScript
import type { ApexCell, ApexDashboard, ApexDigest, BankScorecardRow, InstitutionRow } from '../api'
|
|
|
|
/**
|
|
* Demo fallback for the NABARD portal.
|
|
*
|
|
* <p>Mirrors the real shape closely, including a partner bank below the
|
|
* suppression threshold. A demo where every cell is publishable would hide the
|
|
* one behaviour this tier is judged on: that small cells are withheld rather
|
|
* than quietly rounded.
|
|
*/
|
|
|
|
export function demoApexDashboard(): ApexDashboard {
|
|
return {
|
|
partnerBanks: 3,
|
|
branches: 6,
|
|
enterprises: 142,
|
|
red: 34,
|
|
watch: 8,
|
|
standard: 100,
|
|
stressRatePct: 23.9,
|
|
portfolioValue: 32_320_000,
|
|
openAlerts: 75,
|
|
minCellSize: 5,
|
|
dataAgeDays: 0,
|
|
}
|
|
}
|
|
|
|
export function demoBankScorecard(): BankScorecardRow[] {
|
|
return [
|
|
{
|
|
institutionCode: 'MPGB',
|
|
institutionName: 'Madhya Pradesh Gramin Bank',
|
|
shortName: 'MP Gramin',
|
|
type: 'RRB',
|
|
branches: 2,
|
|
enterprises: 44,
|
|
suppressed: false,
|
|
red: 10,
|
|
watch: 3,
|
|
standard: 31,
|
|
stressRatePct: 22.7,
|
|
avgScore: 34.1,
|
|
alertsRaised: 24,
|
|
alertsActioned: 2,
|
|
responseRatePct: 8.3,
|
|
outstanding: 9_940_000,
|
|
},
|
|
{
|
|
institutionCode: 'SEHDCCB',
|
|
institutionName: 'Sehore District Central Cooperative Bank',
|
|
shortName: 'Sehore DCCB',
|
|
type: 'COOPERATIVE',
|
|
branches: 2,
|
|
enterprises: 39,
|
|
suppressed: false,
|
|
red: 7,
|
|
watch: 2,
|
|
standard: 30,
|
|
stressRatePct: 17.9,
|
|
avgScore: 30.6,
|
|
alertsRaised: 22,
|
|
alertsActioned: 8,
|
|
responseRatePct: 36.4,
|
|
outstanding: 8_610_000,
|
|
},
|
|
{
|
|
institutionCode: 'VSFB',
|
|
institutionName: 'Vindhya Small Finance Bank',
|
|
shortName: 'Vindhya SFB',
|
|
type: 'SFB',
|
|
branches: 2,
|
|
enterprises: 59,
|
|
suppressed: false,
|
|
red: 17,
|
|
watch: 3,
|
|
standard: 39,
|
|
stressRatePct: 28.8,
|
|
avgScore: 37.9,
|
|
alertsRaised: 34,
|
|
alertsActioned: 17,
|
|
responseRatePct: 50,
|
|
outstanding: 13_770_000,
|
|
},
|
|
{
|
|
institutionCode: 'NEWCOOP',
|
|
institutionName: 'Betul Cooperative Bank (onboarding)',
|
|
shortName: 'Betul Coop',
|
|
type: 'COOPERATIVE',
|
|
branches: 1,
|
|
enterprises: 3,
|
|
// Below the platform's minimum cell size — nothing else may be published.
|
|
suppressed: true,
|
|
},
|
|
]
|
|
}
|
|
|
|
export function demoDistricts(): ApexCell[] {
|
|
return [
|
|
{ districtName: 'Bhopal', enterprises: 35, suppressed: false, red: 9, watch: 2, standard: 24, stressRatePct: 25.7, avgScore: 35.2 },
|
|
{ districtName: 'Raisen', enterprises: 34, suppressed: false, red: 8, watch: 2, standard: 24, stressRatePct: 23.5, avgScore: 33.8 },
|
|
{ districtName: 'Sehore', enterprises: 37, suppressed: false, red: 9, watch: 2, standard: 26, stressRatePct: 24.3, avgScore: 34.5 },
|
|
{ districtName: 'Vidisha', enterprises: 36, suppressed: false, red: 8, watch: 2, standard: 26, stressRatePct: 22.2, avgScore: 33.1 },
|
|
]
|
|
}
|
|
|
|
export function demoSectors(): ApexCell[] {
|
|
return [
|
|
{ sectorName: 'Handicrafts', enterprises: 30, suppressed: false, red: 12, watch: 1, standard: 17, stressRatePct: 40, avgScore: 42.6 },
|
|
{ sectorName: 'Poultry', enterprises: 27, suppressed: false, red: 6, watch: 1, standard: 20, stressRatePct: 22.2, avgScore: 33.4 },
|
|
{ sectorName: 'Food processing', enterprises: 28, suppressed: false, red: 6, watch: 3, standard: 19, stressRatePct: 21.4, avgScore: 32.8 },
|
|
{ sectorName: 'Dairy', enterprises: 30, suppressed: false, red: 5, watch: 3, standard: 22, stressRatePct: 16.7, avgScore: 30.2 },
|
|
{ sectorName: 'Rural retail', enterprises: 27, suppressed: false, red: 5, watch: 0, standard: 22, stressRatePct: 18.5, avgScore: 31.1 },
|
|
]
|
|
}
|
|
|
|
export function demoDigest(): ApexDigest {
|
|
const banks = demoBankScorecard().filter((b) => !b.suppressed)
|
|
return {
|
|
asOf: '2026-08-06',
|
|
districtsUnderStress: demoDistricts().sort((a, b) => (b.red ?? 0) - (a.red ?? 0)),
|
|
sectorsUnderStress: demoSectors().sort((a, b) => (b.red ?? 0) - (a.red ?? 0)),
|
|
slowestResponders: banks.sort((a, b) => (a.responseRatePct ?? 0) - (b.responseRatePct ?? 0)),
|
|
}
|
|
}
|
|
|
|
export function demoInstitutions(): InstitutionRow[] {
|
|
return [
|
|
{
|
|
code: 'NABARD',
|
|
name: 'National Bank for Agriculture and Rural Development',
|
|
shortName: 'NABARD',
|
|
type: 'APEX',
|
|
state: 'Madhya Pradesh',
|
|
branches: [],
|
|
},
|
|
{
|
|
code: 'MPGB',
|
|
name: 'Madhya Pradesh Gramin Bank',
|
|
shortName: 'MP Gramin',
|
|
type: 'RRB',
|
|
state: 'Madhya Pradesh',
|
|
branches: [
|
|
{ code: 'SEH01', name: 'Sehore Main Branch', region: 'Bhopal Region', blocks: 'Sehore' },
|
|
{ code: 'VID01', name: 'Vidisha Main Branch', region: 'Bhopal Region', blocks: 'Vidisha' },
|
|
],
|
|
},
|
|
{
|
|
code: 'SEHDCCB',
|
|
name: 'Sehore District Central Cooperative Bank',
|
|
shortName: 'Sehore DCCB',
|
|
type: 'COOPERATIVE',
|
|
state: 'Madhya Pradesh',
|
|
branches: [
|
|
{ code: 'SEH02', name: 'Sehore Cooperative Branch', region: 'Sehore Region', blocks: 'Sehore' },
|
|
{ code: 'RAI01', name: 'Raisen Cooperative Branch', region: 'Sehore Region', blocks: 'Raisen' },
|
|
],
|
|
},
|
|
{
|
|
code: 'VSFB',
|
|
name: 'Vindhya Small Finance Bank',
|
|
shortName: 'Vindhya SFB',
|
|
type: 'SFB',
|
|
state: 'Madhya Pradesh',
|
|
branches: [
|
|
{ code: 'BPL01', name: 'Bhopal City Branch', region: 'Bhopal Region', blocks: 'Bhopal' },
|
|
{ code: 'RAI02', name: 'Raisen Branch', region: 'Bhopal Region', blocks: 'Raisen' },
|
|
],
|
|
},
|
|
]
|
|
}
|