import type { Unit } from "@app/shared-types"; import { toGrams, type IngredientUnitInfo } from "@app/nutrition-engine"; export interface CostableIngredient { canonicalIngredientId: string; quantity: number; unit: Unit; optional: boolean; } export interface PriceInfo extends IngredientUnitInfo { /** SEK per kg – från kvittohistorik, användardata eller standardvärde (spec §26). */ pricePerKgMinor: number; source: "receipt_history" | "user" | "default"; } export interface CostEstimate { totalMinor: number | null; perPortionMinor: number | null; /** Andel av ingredienserna (viktat) som hade prisdata. */ priceCoverage: number; sourcesUsed: PriceInfo["source"][]; } /** Kostnadsuppskattning per portion – alltid märkt som uppskattning. */ export function estimateCost( ingredients: CostableIngredient[], portions: number, prices: Map, ): CostEstimate { let total = 0; let pricedCount = 0; let mandatoryCount = 0; const sources = new Set(); for (const ing of ingredients) { if (ing.optional) continue; mandatoryCount += 1; const price = prices.get(ing.canonicalIngredientId); if (!price) continue; const grams = toGrams(ing.quantity, ing.unit, price); if (grams == null) continue; total += (grams / 1000) * price.pricePerKgMinor; pricedCount += 1; sources.add(price.source); } if (pricedCount === 0 || mandatoryCount === 0) { return { totalMinor: null, perPortionMinor: null, priceCoverage: 0, sourcesUsed: [] }; } const coverage = pricedCount / mandatoryCount; return { totalMinor: Math.round(total), perPortionMinor: portions > 0 ? Math.round(total / portions) : null, priceCoverage: Math.round(coverage * 100) / 100, sourcesUsed: [...sources], }; }