process-aware-ai / frontend /lib /norm-utils.ts
borndeveloper's picture
fix: add missing norm-utils.ts to git
1db0c83
Raw
History Blame Contribute Delete
5.24 kB
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);
}