/** * Zentrale Produktions- und Preisformeln (muss mit der Spielwirtschaft konsistent bleiben). * Wird von falukantService und der Produkt-Initialisierung genutzt. * * Mindest-Erlös (Ertrags-Tabelle, Branch): bei 100 % Produktwissen ist der Verkaufspreis * das obere Ende der Spanne = basePrice = sellCost * (effectiveWorth/100), mit * effectiveWorth >= 75 (siehe effectiveWorthPercent in falukantService). * Engster Fall für Gewinn/Stück: Zertifikat = Produktkategorie (kein Headroom-Rabatt auf * Stückkosten) und regionale Nachfrage am Boden (75 %). */ export const PRODUCTION_COST_BASE = 6.0; export const PRODUCTION_COST_PER_PRODUCT_CATEGORY = 1.0; export const PRODUCTION_HEADROOM_DISCOUNT_PER_STEP = 0.035; export const PRODUCTION_HEADROOM_DISCOUNT_CAP = 0.14; export function productionPieceCost(certificate, category) { const c = Math.max(1, Number(category) || 1); const cert = Math.max(1, Number(certificate) || 1); const raw = PRODUCTION_COST_BASE + (c * PRODUCTION_COST_PER_PRODUCT_CATEGORY); const headroom = Math.max(0, cert - c); const discount = Math.min( headroom * PRODUCTION_HEADROOM_DISCOUNT_PER_STEP, PRODUCTION_HEADROOM_DISCOUNT_CAP ); return raw * (1 - discount); } export function productionCostTotal(quantity, category, certificate) { const q = Math.min(100, Math.max(1, Number(quantity) || 1)); return q * productionPieceCost(certificate, category); } export function effectiveWorthPercent(worthPercent) { const w = Number(worthPercent); if (Number.isNaN(w)) return 75; return Math.min(100, Math.max(75, w)); } /** Untergrenze für den Wissens-Multiplikator auf den regionalen Basispreis. */ export const KNOWLEDGE_PRICE_FLOOR = 0.7; export function calcSellPrice(product, knowledgeFactor = 0) { const max = product.sellCost; const min = max * KNOWLEDGE_PRICE_FLOOR; return min + (max - min) * (knowledgeFactor / 100); } export function calcRegionalSellPriceSync(product, knowledgeFactor, worthPercent) { if (product.sellCost === null || product.sellCost === undefined) return null; const w = effectiveWorthPercent(worthPercent); const basePrice = product.sellCost * (w / 100); const min = basePrice * KNOWLEDGE_PRICE_FLOOR; const max = basePrice; return min + (max - min) * (knowledgeFactor / 100); } /** Untergrenze für worthPercent nach effectiveWorthPercent (75–100). */ export const EFFECTIVE_WORTH_PERCENT_MIN = 75; /** * Minimaler ganzzahliger Basis-sell_cost (vor Steuer-/Regions-Faktoren in der DB), * sodass bei Zertifikat = Produktkategorie, 100 % Wissen und 75 % Nachfrage * der Erlös pro Stück >= Stückkosten (kein struktureller Verlust in der Ertrags-Tabelle). */ export function minBaseSellCostForTightProduction(category) { const c = Math.max(1, Number(category) || 1); const cost = productionPieceCost(c, c); return Math.ceil((cost * 100) / EFFECTIVE_WORTH_PERCENT_MIN); } /** * Prüft Vordefinierungen; meldet Abweichungen nur per warn (kein Throw), damit Deployments * mit alter DB nicht brechen — Balance-Anpassung erfolgt bewusst im Code/SQL. */ export function validateProductBaseSellCosts(products, { warn = console.warn } = {}) { const issues = []; for (const p of products) { const min = minBaseSellCostForTightProduction(p.category); if (Number(p.sellCost) < min) { issues.push({ labelTr: p.labelTr, category: p.category, sellCost: p.sellCost, minRequired: min, }); } } if (issues.length && typeof warn === 'function') { warn( '[falukantProductEconomy] sell_cost unter Mindestbedarf (Zertifikat=Kategorie, 100% Wissen, 75% Nachfrage):', issues ); } return issues; }