-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathallocation-engine.ts
More file actions
39 lines (33 loc) · 2.51 KB
/
Copy pathallocation-engine.ts
File metadata and controls
39 lines (33 loc) · 2.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { CatalogItem, ToolkitConfig, toolkitConfig } from "../config/toolkit";
export type UseCase = "daily_camper" | "bar_service" | "collab_activation" | "gifting" | "build_crew" | "hospitality";
export type AllocationInput = { headcount: number; daysOnSite: number; useCase: UseCase; context?: string; formatPreference?: CatalogItem["format"] };
export type AllocationLine = { sku: string; name: string; qtyUnits: number; qtyServings: number; rationale: string };
export type AllocationProposal = { targetServings: number; lines: AllocationLine[]; estimatedCostCents: number; rule: string };
function firstMatch(catalog: readonly CatalogItem[], useCase: string, format?: CatalogItem["format"]) {
return catalog.find((item) => (!format || item.format === format) && item.bestFor.includes(useCase))
?? catalog.find((item) => !format || item.format === format)
?? catalog[0];
}
export function buildAllocationProposal(input: AllocationInput, config: ToolkitConfig = toolkitConfig): AllocationProposal {
const headcount = Math.max(1, Math.ceil(input.headcount));
const days = Math.max(1, Math.ceil(input.daysOnSite));
let multiplier = config.allocation.giftingMultiplier;
let duration = 1;
let rule = "single-event gifting";
if (input.useCase === "daily_camper" || input.useCase === "build_crew") {
multiplier = config.allocation.dailyCamperMultiplier;
duration = days;
rule = input.useCase === "build_crew" ? "build crew priority" : "daily camper coverage";
} else if (input.useCase === "bar_service" || input.useCase === "collab_activation") {
multiplier = config.allocation.barServiceMultiplier;
rule = "communal service volume";
}
const targetServings = Math.ceil(headcount * duration * multiplier);
const product = firstMatch(config.catalog, input.useCase, input.formatPreference);
let qtyUnits = Math.ceil(targetServings / product.servingsPerUnit);
if (product.format === "bulk") qtyUnits = Math.max(config.allocation.minimumBulkUnits, qtyUnits);
const qtyServings = qtyUnits * product.servingsPerUnit;
const context = input.context?.trim() ? ` Context: ${input.context.trim()}.` : "";
const rationale = `${rule}: ${headcount} people × ${duration} day${duration === 1 ? "" : "s"} × ${multiplier} buffer = ${targetServings} target servings; rounded to ${qtyUnits} ${product.name} unit${qtyUnits === 1 ? "" : "s"}.${context}`;
return { targetServings, lines: [{ sku: product.sku, name: product.name, qtyUnits, qtyServings, rationale }], estimatedCostCents: qtyUnits * product.unitCostCents, rule };
}