chore(repo): initialize planarchy workspace
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { AllocationStatus } from "@planarchy/shared";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { computeBudgetStatus } from "../budget/monitor.js";
|
||||
|
||||
const now = new Date("2025-01-06");
|
||||
const endDate = new Date("2025-03-07");
|
||||
|
||||
const mkAlloc = (
|
||||
hoursPerDay: number,
|
||||
dailyCostCents: number,
|
||||
status: AllocationStatus = AllocationStatus.CONFIRMED,
|
||||
) => ({
|
||||
status,
|
||||
dailyCostCents,
|
||||
hoursPerDay,
|
||||
startDate: now,
|
||||
endDate,
|
||||
});
|
||||
|
||||
describe("computeBudgetStatus", () => {
|
||||
it("returns 0% utilization for no allocations", () => {
|
||||
const result = computeBudgetStatus(1000000, 100, [], now, endDate);
|
||||
expect(result.allocatedCents).toBe(0);
|
||||
expect(result.utilizationPercent).toBe(0);
|
||||
expect(result.remainingCents).toBe(1000000);
|
||||
expect(result.warnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("calculates correct utilization", () => {
|
||||
// 1 alloc, 10 working days, 8000 cents/day = 80000 cents
|
||||
const alloc = { ...mkAlloc(8, 8000), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-17") };
|
||||
const result = computeBudgetStatus(400000, 100, [alloc], now, endDate);
|
||||
expect(result.confirmedCents).toBe(80000);
|
||||
expect(result.utilizationPercent).toBeCloseTo(20, 0);
|
||||
});
|
||||
|
||||
it("emits WARNING at 85% utilization", () => {
|
||||
// Budget 100000, alloc cost 87000 = 87%
|
||||
const alloc = { ...mkAlloc(8, 87000 / 10), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-17") };
|
||||
const result = computeBudgetStatus(100000, 100, [alloc], now, endDate);
|
||||
const hasWarning = result.warnings.some((w) => w.code === "BUDGET_WARNING");
|
||||
expect(hasWarning).toBe(true);
|
||||
});
|
||||
|
||||
it("emits CRITICAL at 95% utilization", () => {
|
||||
const alloc = { ...mkAlloc(8, 96000 / 10), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-17") };
|
||||
const result = computeBudgetStatus(100000, 100, [alloc], now, endDate);
|
||||
const hasCritical = result.warnings.some((w) => w.code === "BUDGET_CRITICAL");
|
||||
expect(hasCritical).toBe(true);
|
||||
});
|
||||
|
||||
it("emits EXCEEDED when allocations exceed budget", () => {
|
||||
const alloc = { ...mkAlloc(8, 120000 / 10), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-17") };
|
||||
const result = computeBudgetStatus(100000, 100, [alloc], now, endDate);
|
||||
const hasExceeded = result.warnings.some((w) => w.code === "BUDGET_EXCEEDED");
|
||||
expect(hasExceeded).toBe(true);
|
||||
});
|
||||
|
||||
it("applies win probability weighting", () => {
|
||||
const alloc = { ...mkAlloc(8, 10000), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-17") };
|
||||
const result = computeBudgetStatus(1000000, 50, [alloc], now, endDate);
|
||||
// allocated = 10 days × 10000 = 100000, weighted = 50% = 50000
|
||||
expect(result.winProbabilityWeightedCents).toBe(Math.round(result.allocatedCents * 0.5));
|
||||
});
|
||||
|
||||
it("separates proposed from confirmed costs", () => {
|
||||
const confirmed = { ...mkAlloc(8, 5000, AllocationStatus.CONFIRMED), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-10") };
|
||||
const proposed = { ...mkAlloc(8, 5000, AllocationStatus.PROPOSED), startDate: new Date("2025-01-06"), endDate: new Date("2025-01-10") };
|
||||
const result = computeBudgetStatus(1000000, 100, [confirmed, proposed], now, endDate);
|
||||
expect(result.confirmedCents).toBeGreaterThan(0);
|
||||
expect(result.proposedCents).toBeGreaterThan(0);
|
||||
expect(result.confirmedCents).toBe(result.proposedCents);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user