D360-Frontend/src/lib/mockData.ts
2026-01-15 14:25:07 +05:30

270 lines
10 KiB
TypeScript

import { State, City } from 'country-state-city';
export interface Dealer {
id: string;
state: string;
district: string;
city: string;
dealerName: string;
mfmsId: string;
noOfProducts: number;
noOfCompanies: number;
salesRating: number;
buyRating: number;
avgLiquidityCycle: number;
avgAcknowledgmentCycle: number;
currentStock: number;
agedStock: number;
creditScore: number;
dealerType: "Retailer" | "Wholesaler";
totalSales6M: number;
totalPurchase6M: number;
stockAge: number;
mobile?: string;
aadhaar?: string;
dealerLicense?: string;
}
// Get all Indian states using country-state-city
const allInStates = State.getStatesOfCountry('IN');
export const indianStates = allInStates.map(s => s.name);
// Map state names to their cities (districts)
export const stateDistricts: Record<string, string[]> = allInStates.reduce((acc, state) => {
acc[state.name] = City.getCitiesOfState('IN', state.isoCode).map(city => city.name);
return acc;
}, {} as Record<string, string[]>);
// Dealer names for generation
const DEALER_NAMES = [
"Krishna Agro Traders", "Sharma Seeds & Fertilizers", "Patel Farm Solutions",
"Reddy Agriculture Supplies", "Singh Crop Care", "Kumar Agri Inputs",
"Gupta Farm Depot", "Verma Seeds Co.", "Joshi Agricultural Store",
"Mehta Fertilizer House", "Yadav Agro Center", "Shah Farm Products",
"Desai Agro Services", "Iyer Fertilizers", "Nair Seeds & Supply",
"Pillai Farm Inputs", "Rao Agri Solutions", "Shetty Crop Care",
"Malhotra Agricultural Hub", "Chopra Seeds Depot", "Agarwal Farm Center",
"Jain Agro Supplies", "Bansal Fertilizer Store", "Khanna Agriculture Products",
"Bhatia Farm Solutions", "Kapoor Agri Depot", "Sethi Seeds Trading",
"Mittal Crop Care Center", "Arora Farm Supplies", "Sinha Agricultural Store"
] as const;
// Seeded random number generator for consistent data
const seededRandom = (seed: number): number => {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
};
// Generate a single dealer with seeded randomness for consistency
const generateDealer = (index: number): Dealer => {
const seed = index + 1;
const stateIndex = Math.floor(seededRandom(seed * 1) * indianStates.length);
const state = indianStates[stateIndex];
const districts = stateDistricts[state] || [];
const districtIndex = districts.length > 0 ? Math.floor(seededRandom(seed * 2) * districts.length) : 0;
const district = districts[districtIndex] || `District ${state}`;
const creditScore = Math.floor(seededRandom(seed * 3) * 951) + 50;
const dealerType: "Retailer" | "Wholesaler" = seededRandom(seed * 4) > 0.3 ? "Retailer" : "Wholesaler";
return {
id: `DLR${String(index + 1).padStart(6, '0')}`,
state,
district,
city: district,
dealerName: `${DEALER_NAMES[index % DEALER_NAMES.length]} ${district}`,
mfmsId: `MFMS${String(Math.floor(seededRandom(seed * 5) * 999999)).padStart(6, '0')}`,
noOfProducts: Math.floor(seededRandom(seed * 6) * 15) + 3,
noOfCompanies: Math.floor(seededRandom(seed * 7) * 8) + 2,
salesRating: Math.floor(seededRandom(seed * 8) * 401) + 600,
buyRating: Math.floor(seededRandom(seed * 9) * 401) + 600,
avgLiquidityCycle: Math.floor(seededRandom(seed * 10) * 40) + 10,
avgAcknowledgmentCycle: Math.floor(seededRandom(seed * 11) * 7) + 1,
currentStock: Math.floor(seededRandom(seed * 12) * 800) + 100,
agedStock: Math.floor(seededRandom(seed * 13) * 200),
creditScore,
dealerType,
totalSales6M: Math.floor(seededRandom(seed * 14) * 500) + 100,
totalPurchase6M: Math.floor(seededRandom(seed * 15) * 700) + 200,
stockAge: Math.floor(seededRandom(seed * 16) * 60) + 20,
mobile: `+91 ${Math.floor(seededRandom(seed * 17) * 9000000000) + 1000000000}`,
aadhaar: `${Math.floor(seededRandom(seed * 18) * 9000) + 1000} ${Math.floor(seededRandom(seed * 19) * 9000) + 1000} ${Math.floor(seededRandom(seed * 20) * 9000) + 1000}`,
dealerLicense: `DL${String(Math.floor(seededRandom(seed * 21) * 999999)).padStart(6, '0')}`
};
};
// Lazy dealer cache
let dealersCache: Dealer[] | null = null;
const DEALER_COUNT = 2600;
// Generate dealers lazily (on first access)
export const getDealers = (): Dealer[] => {
if (dealersCache === null) {
dealersCache = Array.from({ length: DEALER_COUNT }, (_, i) => generateDealer(i));
}
return dealersCache;
};
// For backwards compatibility - but prefer getDealers() for lazy loading
export const dealers = getDealers();
// Credit score utilities
export const getCreditScoreColor = (score: number): 'success' | 'warning' | 'danger' => {
if (score >= 750) return "success";
if (score >= 500) return "warning";
return "danger";
};
export const getCreditScoreLabel = (score: number): string => {
if (score >= 750) return "Excellent";
if (score >= 650) return "Good";
if (score >= 500) return "Fair";
return "Poor";
};
// Mock score breakdown
export interface ScoreParameter {
parameter: string;
weight: number;
dealerScore: number;
remarks: string;
}
// Memoization cache for score breakdown
const scoreBreakdownCache = new Map<string, ScoreParameter[]>();
export const getScoreBreakdown = (dealerId: string, creditScore: number): ScoreParameter[] => {
const cacheKey = `${dealerId}-${creditScore}`;
if (scoreBreakdownCache.has(cacheKey)) {
return scoreBreakdownCache.get(cacheKey)!;
}
const proportion = creditScore / 1000;
const breakdown: ScoreParameter[] = [
{ parameter: "Sales Performance", weight: 25, dealerScore: Math.round(600 * proportion), remarks: "Steady movement" },
{ parameter: "Purchase Rating", weight: 15, dealerScore: Math.round(700 * proportion), remarks: "Slight inconsistency" },
{ parameter: "Liquidity Cycle", weight: 25, dealerScore: Math.round(800 * proportion), remarks: "Good cycle" },
{ parameter: "Acknowledgment Cycle", weight: 10, dealerScore: Math.round(990 * proportion), remarks: "Delay within limit" },
{ parameter: "Current Stock", weight: 10, dealerScore: Math.round(500 * proportion), remarks: "Healthy inventory" },
{ parameter: "Ageing", weight: 10, dealerScore: Math.round(150 * proportion), remarks: "Hoarding not noticed" },
{ parameter: "Regional Risk Factor", weight: 5, dealerScore: 0, remarks: "Contextual adjustment" },
];
scoreBreakdownCache.set(cacheKey, breakdown);
return breakdown;
};
// Type definitions for chart data
interface CreditTrendItem {
month: string;
score: number;
}
interface SalesPurchaseTrendItem {
month: string;
sales: number;
purchase: number;
}
// Memoization for chart data
const creditTrendCache = new Map<number, CreditTrendItem[]>();
export const getCreditScoreTrend = (creditScore: number): CreditTrendItem[] => {
if (creditTrendCache.has(creditScore)) {
return creditTrendCache.get(creditScore)!;
}
const months = ["Jul 2024", "Aug 2024", "Sep 2024", "Oct 2024", "Nov 2024", "Dec 2024"];
const variation = 20;
const trend: CreditTrendItem[] = [
{ month: months[0], score: Math.max(50, Math.min(1000, creditScore - variation)) },
{ month: months[1], score: Math.max(50, Math.min(1000, creditScore - variation / 2)) },
{ month: months[2], score: Math.max(50, Math.min(1000, creditScore - variation / 3)) },
{ month: months[3], score: Math.max(50, Math.min(1000, creditScore - variation / 4)) },
{ month: months[4], score: Math.max(50, Math.min(1000, creditScore - 2)) },
{ month: months[5], score: creditScore },
];
creditTrendCache.set(creditScore, trend);
return trend;
};
const salesPurchaseCache = new Map<string, SalesPurchaseTrendItem[]>();
export const getSalesPurchaseTrend = (totalSales6M: number, totalPurchase6M: number): SalesPurchaseTrendItem[] => {
const cacheKey = `${totalSales6M}-${totalPurchase6M}`;
if (salesPurchaseCache.has(cacheKey)) {
return salesPurchaseCache.get(cacheKey)!;
}
const months = ["Jul 2024", "Aug 2024", "Sep 2024", "Oct 2024", "Nov 2024", "Dec 2024"];
const avgSales = totalSales6M / 6;
const avgPurchase = totalPurchase6M / 6;
const trend: SalesPurchaseTrendItem[] = [
{ month: months[0], sales: Math.round(avgSales * 0.93), purchase: Math.round(avgPurchase * 0.95) },
{ month: months[1], sales: Math.round(avgSales * 1.03), purchase: Math.round(avgPurchase * 1.00) },
{ month: months[2], sales: Math.round(avgSales * 0.98), purchase: Math.round(avgPurchase * 0.97) },
{ month: months[3], sales: Math.round(avgSales * 1.07), purchase: Math.round(avgPurchase * 1.05) },
{ month: months[4], sales: Math.round(avgSales * 1.02), purchase: Math.round(avgPurchase * 1.02) },
{ month: months[5], sales: Math.round(avgSales * 0.97), purchase: Math.round(avgPurchase * 1.01) },
];
salesPurchaseCache.set(cacheKey, trend);
return trend;
};
// Static stock age distribution (doesn't need memoization)
const STOCK_AGE_DISTRIBUTION = [
{ range: "0-30 Days", value: 35 },
{ range: "31-60 Days", value: 25 },
{ range: "61-90 Days", value: 15 },
{ range: "91-120 Days", value: 10 },
{ range: "121-150 Days", value: 8 },
{ range: "151-180 Days", value: 4 },
{ range: "181+ Days", value: 3 },
] as const;
export const getStockAgeDistribution = () => [...STOCK_AGE_DISTRIBUTION];
// Monthly product scores types and memoization
interface MonthlyProductScore {
product: string;
months: string[];
m1: number;
m2: number;
m3: number;
m4: number;
m5: number;
}
const monthlyScoresCache = new Map<string, MonthlyProductScore[]>();
export const getMonthlyProductScores = (dealerId: string): MonthlyProductScore[] => {
if (monthlyScoresCache.has(dealerId)) {
return monthlyScoresCache.get(dealerId)!;
}
const products = ["Urea", "NPK", "DAP", "SSP"];
const months = ["Aug 2024", "Sep 2024", "Oct 2024", "Nov 2024", "Dec 2024"];
// Use seeded random based on dealer ID for consistency
const seed = parseInt(dealerId.replace('DLR', ''), 10) || 1;
const scores: MonthlyProductScore[] = products.map((product, i) => ({
product,
months,
m1: i === 0 ? 925 : Math.floor(seededRandom(seed * (i + 1) * 100) * 200) + 750,
m2: Math.floor(seededRandom(seed * (i + 1) * 101) * 200) + 750,
m3: Math.floor(seededRandom(seed * (i + 1) * 102) * 200) + 750,
m4: Math.floor(seededRandom(seed * (i + 1) * 103) * 200) + 750,
m5: Math.floor(seededRandom(seed * (i + 1) * 104) * 200) + 750,
}));
monthlyScoresCache.set(dealerId, scores);
return scores;
};