export interface NormEntry { id: number; division_factor: string; sub_type: string; composition: string; count_range: string; route: string; rules: { upto_3000m: string; above_3000m: string; }; tolerance_adjustments: { tolerance_3_percent: string; tolerance_5_7_percent: string; tolerance_10_percent: string; tolerance_plus0_minus3_5: string; tolerance_1_2_percent: string; }; } export interface ToleranceOption { key: keyof NormEntry["tolerance_adjustments"]; label: string; } export const TOLERANCE_OPTIONS: ToleranceOption[] = [ { key: "tolerance_1_2_percent", label: "+/- 1% or +/- 2%" }, { key: "tolerance_3_percent", label: "+/- 3%" }, { key: "tolerance_5_7_percent", label: "+/- 5%, 6% or 7%" }, { key: "tolerance_10_percent", label: "+/- 10%" }, { key: "tolerance_plus0_minus3_5", label: "+0/-3% or +0/-5%" }, ]; export interface CalculationResult { applicableRule: string; ruleDescription: string; baseReservation: number; toleranceAdjustment: string; adjustedPercentage: number; finalReservation: number; orderQty: number; } interface ParsedRule { percentage: number; fixedMeters: number; } interface ToleranceAdjustment { adjustmentPercent: number; isLess: boolean; label: string; } export function parseRuleString(ruleString: string): ParsedRule { let percentage = 0; let fixedMeters = 0; const pattern = /(\d+(?:\.\d+)?)\s*%\s*or\s*(\d+(?:\.\d+)?)\s*m/i; const match = ruleString.match(pattern); if (match) { percentage = parseFloat(match[1]); fixedMeters = parseFloat(match[2]); } return { percentage, fixedMeters }; } export function parseToleranceAdjustment(toleranceString: string): ToleranceAdjustment { if (toleranceString.toLowerCase().includes("std norms") || toleranceString.toLowerCase().includes("standard")) { return { adjustmentPercent: 0, isLess: false, label: "As per Std Norms" }; } const extraPattern = /(\d+(?:\.\d+)?)\s*%\s*extra/i; const lessPattern = /-?\s*(\d+(?:\.\d+)?)\s*%\s*less/i; const extraMatch = toleranceString.match(extraPattern); if (extraMatch) return { adjustmentPercent: parseFloat(extraMatch[1]), isLess: false, label: `+${extraMatch[1]}% Extra` }; const lessMatch = toleranceString.match(lessPattern); if (lessMatch) return { adjustmentPercent: parseFloat(lessMatch[1]), isLess: true, label: `-${lessMatch[1]}% Less` }; return { adjustmentPercent: 0, isLess: false, label: toleranceString }; } export function calculateBaseReservation(orderQty: number, percentage: number, fixedMeters: number): number { const percentageReservation = orderQty * (percentage / 100); return Math.max(percentageReservation, fixedMeters); } export function calculateFinalReservation(orderQty: number, ruleString: string, toleranceString: string): CalculationResult { const { percentage, fixedMeters } = parseRuleString(ruleString); const tolerance = parseToleranceAdjustment(toleranceString); const baseReservation = calculateBaseReservation(orderQty, percentage, fixedMeters); let adjustedPercentage = percentage; if (tolerance.isLess) { adjustedPercentage = Math.max(0, percentage - tolerance.adjustmentPercent); } else { adjustedPercentage = percentage + tolerance.adjustmentPercent; } const finalReservation = calculateBaseReservation(orderQty, adjustedPercentage, fixedMeters); const ruleDescription = orderQty <= 3000 ? `Upto 3000m: ${ruleString}` : `Above 3000m: ${ruleString}`; return { applicableRule: ruleString, ruleDescription, baseReservation: Math.round(baseReservation * 100) / 100, toleranceAdjustment: tolerance.label, adjustedPercentage, finalReservation: Math.round(finalReservation * 100) / 100, orderQty }; } // Grouping functions export function getUniqueDivisionFactors(data: NormEntry[]): string[] { return [...new Set(data.map(item => item.division_factor))].sort(); } export function getSubTypesByDivision(data: NormEntry[], division: string): string[] { return [...new Set(data.filter(item => item.division_factor === division).map(item => item.sub_type))].sort(); } export function getCompositionsByDivisionAndSubType(data: NormEntry[], division: string, subType: string): string[] { return [...new Set(data.filter(item => item.division_factor === division && item.sub_type === subType).map(item => item.composition))].sort(); } export function getCountRangesByComposition(data: NormEntry[], division: string, subType: string, composition: string): string[] { return [...new Set(data.filter(item => item.division_factor === division && item.sub_type === subType && item.composition === composition).map(item => item.count_range))].sort(); } export function findNormEntry(data: NormEntry[], division: string, subType: string, composition: string, countRange: string): NormEntry | undefined { return data.find(item => item.division_factor === division && item.sub_type === subType && item.composition === composition && item.count_range === countRange); }