b41c1d2501
CI / Architecture Guardrails (push) Successful in 2m38s
CI / Assistant Split Regression (push) Successful in 3m33s
CI / Typecheck (push) Successful in 3m51s
CI / Lint (push) Successful in 5m2s
CI / E2E Tests (push) Has been cancelled
CI / Fresh-Linux Docker Deploy (push) Has been cancelled
CI / Release Images (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI (#61) Co-authored-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com> Co-committed-by: Hartmut Nörenberg <hn@hartmut-noerenberg.com>
154 lines
4.1 KiB
TypeScript
154 lines
4.1 KiB
TypeScript
import { computeBudgetStatus } from "@nexus/engine";
|
||
import type { TRPCContext } from "../trpc.js";
|
||
import { fmtEur } from "../lib/format-utils.js";
|
||
import { type GraphLink, type GraphNode, l, n } from "./computation-graph-shared.js";
|
||
|
||
type ResourceBudgetProject = {
|
||
id: string;
|
||
name: string;
|
||
budgetCents: number | null;
|
||
winProbability: number;
|
||
};
|
||
|
||
type ResourceBudgetAssignment = {
|
||
project: ResourceBudgetProject;
|
||
};
|
||
|
||
export type ResourceBudgetGraph = {
|
||
nodes: GraphNode[];
|
||
links: GraphLink[];
|
||
};
|
||
|
||
export async function readResourceBudgetGraph(
|
||
db: TRPCContext["db"],
|
||
assignments: ResourceBudgetAssignment[],
|
||
monthStart: Date,
|
||
monthEnd: Date,
|
||
): Promise<ResourceBudgetGraph> {
|
||
const budgetProject = assignments.find(
|
||
(assignment) => assignment.project.budgetCents != null && assignment.project.budgetCents > 0,
|
||
)?.project;
|
||
|
||
if (!budgetProject?.budgetCents) {
|
||
return { nodes: [], links: [] };
|
||
}
|
||
|
||
const projectAllocs = await db.assignment.findMany({
|
||
where: { projectId: budgetProject.id },
|
||
select: {
|
||
status: true,
|
||
dailyCostCents: true,
|
||
startDate: true,
|
||
endDate: true,
|
||
hoursPerDay: true,
|
||
},
|
||
});
|
||
const budgetStatus = computeBudgetStatus(
|
||
budgetProject.budgetCents,
|
||
budgetProject.winProbability,
|
||
projectAllocs.map((projectAllocation) => ({
|
||
status: projectAllocation.status as unknown as string,
|
||
dailyCostCents: projectAllocation.dailyCostCents,
|
||
startDate: projectAllocation.startDate,
|
||
endDate: projectAllocation.endDate,
|
||
hoursPerDay: projectAllocation.hoursPerDay,
|
||
})) as Parameters<typeof computeBudgetStatus>[2],
|
||
monthStart,
|
||
monthEnd,
|
||
);
|
||
|
||
return {
|
||
nodes: [
|
||
n(
|
||
"input.budgetCents",
|
||
"Project Budget",
|
||
fmtEur(budgetProject.budgetCents),
|
||
"EUR",
|
||
"INPUT",
|
||
`Budget for ${budgetProject.name}`,
|
||
0,
|
||
),
|
||
n(
|
||
"input.winProbability",
|
||
"Win Probability",
|
||
`${budgetProject.winProbability}%`,
|
||
"%",
|
||
"INPUT",
|
||
"Project win probability",
|
||
0,
|
||
),
|
||
n(
|
||
"budget.confirmedCents",
|
||
"Confirmed",
|
||
fmtEur(budgetStatus.confirmedCents),
|
||
"EUR",
|
||
"BUDGET",
|
||
"Sum of CONFIRMED/ACTIVE allocation costs",
|
||
2,
|
||
"Σ(confirmed allocs)",
|
||
),
|
||
n(
|
||
"budget.proposedCents",
|
||
"Proposed",
|
||
fmtEur(budgetStatus.proposedCents),
|
||
"EUR",
|
||
"BUDGET",
|
||
"Sum of PROPOSED allocation costs",
|
||
2,
|
||
"Σ(proposed allocs)",
|
||
),
|
||
n(
|
||
"budget.allocatedCents",
|
||
"Allocated",
|
||
fmtEur(budgetStatus.allocatedCents),
|
||
"EUR",
|
||
"BUDGET",
|
||
"Total allocated budget",
|
||
2,
|
||
"confirmed + proposed",
|
||
),
|
||
n(
|
||
"budget.remainingCents",
|
||
"Remaining",
|
||
fmtEur(budgetStatus.remainingCents),
|
||
"EUR",
|
||
"BUDGET",
|
||
"Remaining budget",
|
||
3,
|
||
"budget - allocated",
|
||
),
|
||
n(
|
||
"budget.utilizationPct",
|
||
"Utilization",
|
||
`${budgetStatus.utilizationPercent.toFixed(1)}%`,
|
||
"%",
|
||
"BUDGET",
|
||
"Budget utilization percentage",
|
||
3,
|
||
"allocated / budget × 100",
|
||
),
|
||
n(
|
||
"budget.weightedCents",
|
||
"Win-Weighted",
|
||
fmtEur(budgetStatus.winProbabilityWeightedCents),
|
||
"EUR",
|
||
"BUDGET",
|
||
"Win-probability-weighted cost",
|
||
3,
|
||
"allocated × winProb / 100",
|
||
),
|
||
],
|
||
links: [
|
||
l("alloc.totalCostCents", "budget.confirmedCents", "per assignment", 1),
|
||
l("budget.confirmedCents", "budget.allocatedCents", "+", 2),
|
||
l("budget.proposedCents", "budget.allocatedCents", "+", 2),
|
||
l("input.budgetCents", "budget.remainingCents", "−", 2),
|
||
l("budget.allocatedCents", "budget.remainingCents", "−", 2),
|
||
l("budget.allocatedCents", "budget.utilizationPct", "÷ budget × 100", 2),
|
||
l("input.budgetCents", "budget.utilizationPct", "÷", 1),
|
||
l("budget.allocatedCents", "budget.weightedCents", "× winProb / 100", 1),
|
||
l("input.winProbability", "budget.weightedCents", "×", 1),
|
||
],
|
||
};
|
||
}
|