Hello from MCP server
import { describe, it, expect } from "vitest";
import calculatePrice from "@/framework/calculatePrice";
import tnfrLegacyWithHours from "../tnfrLegacyWithHours";
import {
calculateWithImpliedRate,
createPricingVars,
type PricingVars,
} from "../pricingFormula";
/**
* Test that tnfrLegacyWithHours (JSON-based formula) produces the same results
* as pricingFormula.ts (direct TypeScript implementation)
*/
// Helper to run the JSON formula with given inputs
async function runJsonFormula(inputs: {
material: number;
time: number;
hourlyFee?: number;
salesTax?: number;
serviceCallFee?: number;
multiplier?: number;
saDiscount?: number;
standardDeduction?: number;
additionalHourDiscount?: number;
additionalTime?: number;
additionalMaterial?: number;
}): Promise<number> {
const formula = tnfrLegacyWithHours();
// Set input variables (new format uses .value property)
formula.vars.materialCostBase.value = inputs.material;
formula.vars.timeCostBase.value = inputs.time;
if (inputs.hourlyFee !== undefined) formula.vars.hourlyFee.value = inputs.hourlyFee;
if (inputs.salesTax !== undefined) formula.vars.salesTax.value = inputs.salesTax;
if (inputs.serviceCallFee !== undefined) formula.vars.serviceCallFee.value = inputs.serviceCallFee;
if (inputs.multiplier !== undefined) formula.vars.multiplier.value = inputs.multiplier;
if (inputs.saDiscount !== undefined) formula.vars.saDiscount.value = inputs.saDiscount;
if (inputs.standardDeduction !== undefined) formula.vars.standardDeduction.value = inputs.standardDeduction;
if (inputs.additionalHourDiscount !== undefined) formula.vars.additionalHourDiscount.value = inputs.additionalHourDiscount;
if (inputs.additionalTime !== undefined) formula.vars.additionalTime.value = inputs.additionalTime;
if (inputs.additionalMaterial !== undefined) formula.vars.additionalMaterial.value = inputs.additionalMaterial;
// Run through calculatePrice
// Note: calculatePrice expects costsMaterial and costsTime arrays, but we're setting vars directly
// so we pass empty arrays and the formula vars will be used
const result = await calculatePrice([], [], formula);
return result.finalPrice;
}
// Helper to run the TypeScript formula
function runTsFormula(inputs: {
material: number;
time: number;
hourlyFee?: number;
salesTax?: number;
serviceCallFee?: number;
multiplier?: number;
saDiscount?: number;
standardDeduction?: number;
additionalHourDiscount?: number;
additionalTime?: number;
additionalMaterial?: number;
}): number {
// Only include defined values to avoid overwriting defaults with undefined
const overrides: Partial<PricingVars> = {
material: inputs.material,
time: inputs.time,
};
if (inputs.hourlyFee !== undefined) overrides.hourlyFee = inputs.hourlyFee;
if (inputs.salesTax !== undefined) overrides.salesTax = inputs.salesTax;
if (inputs.serviceCallFee !== undefined) overrides.serviceCallFee = inputs.serviceCallFee;
if (inputs.multiplier !== undefined) overrides.multiplier = inputs.multiplier;
if (inputs.saDiscount !== undefined) overrides.saDiscount = inputs.saDiscount;
if (inputs.standardDeduction !== undefined) overrides.standardDeduction = inputs.standardDeduction;
if (inputs.additionalHourDiscount !== undefined) overrides.additionalHourDiscount = inputs.additionalHourDiscount;
if (inputs.additionalTime !== undefined) overrides.additionalTime = inputs.additionalTime;
if (inputs.additionalMaterial !== undefined) overrides.additionalMaterial = inputs.additionalMaterial;
const vars = createPricingVars(overrides);
const result = calculateWithImpliedRate(vars);
return result.finalPrice;
}
describe("tnfrLegacyWithHours vs pricingFormula", () => {
describe("basic calculations (no additional time/material)", () => {
it("should match for zero inputs", () => {
const inputs = { material: 0, time: 0 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match for material only", () => {
const inputs = { material: 2, time: 0 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match for time only", () => {
const inputs = { material: 0, time: 1.5 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match for material and time", () => {
const inputs = { material: 1.5, time: 2 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with sales tax", () => {
const inputs = { material: 3, time: 1, salesTax: 1.07 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with service call fee", () => {
const inputs = { material: 2, time: 1, serviceCallFee: 5 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with tier multiplier", () => {
const inputs = { material: 2, time: 1, multiplier: 1.5 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with SA discount", () => {
const inputs = { material: 2, time: 1, saDiscount: 0.9 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with standard deduction", () => {
const inputs = { material: 2, time: 1, standardDeduction: 0.85 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
});
describe("with additional time (implied rate)", () => {
it("should match with additional time", () => {
const inputs = { material: 2, time: 1, additionalTime: 0.5 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with additional time and multiplier", () => {
const inputs = { material: 2, time: 1, multiplier: 1.3, additionalTime: 1 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with additional time and custom discount", () => {
const inputs = { material: 2, time: 1, additionalTime: 2, additionalHourDiscount: 0.5 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
});
describe("with additional material", () => {
it("should match with additional material", () => {
const inputs = { material: 2, time: 1, additionalMaterial: 1 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match with additional material and sales tax", () => {
const inputs = { material: 2, time: 1, salesTax: 1.07, additionalMaterial: 3 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
});
describe("complex scenarios", () => {
it("should match with all options enabled", () => {
const inputs = {
material: 5,
time: 2,
hourlyFee: 6,
salesTax: 1.08,
serviceCallFee: 3,
multiplier: 1.2,
saDiscount: 0.95,
standardDeduction: 0.9,
additionalHourDiscount: 0.4,
additionalTime: 1.5,
additionalMaterial: 2,
};
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match for high material cost (low markup)", () => {
const inputs = { material: 10, time: 1 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match for low material cost (high markup)", () => {
const inputs = { material: 0.1, time: 1 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
});
it("should match across all markup scale thresholds", () => {
const thresholds = [0, 0.1, 0.2, 0.4, 0.6, 1, 1.2, 2, 3, 4, 5, 7, 10];
for (const material of thresholds) {
const inputs = { material, time: 1 };
const jsonResult = runJsonFormula(inputs);
const tsResult = runTsFormula(inputs);
expect(jsonResult).toBeCloseTo(tsResult, 6);
}
});
});
});