Files
Nexus/packages/api/src/router/computation-graph-resource-budget.ts
T
Hartmut 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)
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>
2026-05-21 16:28:40 +02:00

154 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
],
};
}