Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
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);
      }
    });
  });
});