UNPKG

monte-carlo-simulator

Version:

Business decision framework with Monte Carlo risk analysis - instant via npx

287 lines (286 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigurableSimulation = void 0; const MonteCarloEngine_1 = require("./MonteCarloEngine"); const name_converter_1 = require("../cli/utils/name-converter"); const ARRBusinessContext_1 = require("./ARRBusinessContext"); class ConfigurableSimulation extends MonteCarloEngine_1.MonteCarloEngine { config; arrInjector; enhancedConfig; constructor(config) { super(); this.config = config; this.arrInjector = ARRBusinessContext_1.globalARRInjector; this.enhancedConfig = this.enhanceConfigWithBusinessContext(config); } /** * Enhances simulation config with optional business context injection * Only injects ARR if explicitly requested or detected via strategic keywords */ enhanceConfigWithBusinessContext(config) { const parameterKeys = config.parameters.map(p => p.key); // Check if simulation needs business context const needsBusinessContext = this.shouldInjectBusinessContext(config); if (!needsBusinessContext) { return config; } // Check if ARR is already present if (this.arrInjector.hasARRParameter(parameterKeys)) { // ARR already exists, just enhance simulation logic return { ...config, simulation: { logic: this.injectBusinessContext(config.simulation?.logic || '', parameterKeys) } }; } // Inject ARR parameter and business context const arrParam = this.arrInjector.getARRParameterDefinition('Strategic Analysis'); const budgetParam = { key: 'budgetPercent', label: 'Budget Allocation (% of ARR)', type: 'number', default: 10, min: 1, max: 50, step: 0.5, description: 'Budget as percentage of ARR for this strategic analysis' }; const arrGroup = this.arrInjector.getARRParameterGroup(); const allParameterKeys = [arrParam.key, budgetParam.key, ...parameterKeys]; return { ...config, parameters: [arrParam, budgetParam, ...config.parameters], groups: [arrGroup, ...(config.groups || [])], simulation: { logic: this.injectBusinessContext(config.simulation?.logic || '', allParameterKeys) } }; } /** * Determines if this simulation should have business context injected */ shouldInjectBusinessContext(config) { // Check for explicit business context request if (config.businessContext === true) { return true; } // Check for strategic/business keywords in various places const strategicKeywords = [ 'roi', 'investment', 'cost', 'benefit', 'revenue', 'profit', 'budget', 'runway', 'burn', 'hiring', 'scaling', 'strategy', 'payback', 'npv' ]; const textToCheck = [ config.name.toLowerCase(), config.description.toLowerCase(), config.category.toLowerCase(), ...(config.tags?.map(t => t.toLowerCase()) || []), config.simulation?.logic?.toLowerCase() || '' ].join(' '); return strategicKeywords.some(keyword => textToCheck.includes(keyword)); } /** * Injects business context into simulation logic */ injectBusinessContext(originalLogic, parameterKeys = []) { const contextInjection = this.arrInjector.getBusinessContextInjectionCode(parameterKeys); return `${contextInjection} // Original simulation logic: ${originalLogic}`; } getMetadata() { return { id: (0, name_converter_1.toId)(this.config.name), name: this.config.name, description: this.config.description, category: this.config.category, version: this.config.version }; } getParameterDefinitions() { return this.enhancedConfig.parameters.map(param => ({ key: param.key, label: param.label, type: param.type, defaultValue: param.default, min: param.min, max: param.max, step: param.step, options: param.options, description: param.description || '' })); } simulateScenario(parameters) { try { // Create a safe execution context const parameterNames = Object.keys(parameters); const parameterValues = Object.values(parameters); // Add some common utilities to the execution context const mathUtils = { random: Math.random, sqrt: Math.sqrt, pow: Math.pow, log: Math.log, exp: Math.exp, abs: Math.abs, min: Math.min, max: Math.max, floor: Math.floor, ceil: Math.ceil, round: Math.round }; // Create the function with parameter names and logic const functionBody = ` const { random, sqrt, pow, log, exp, abs, min, max, floor, ceil, round } = mathUtils; ${this.enhancedConfig.simulation?.logic || ''} `; const simulationFunction = new Function(...parameterNames, 'mathUtils', functionBody); // Execute the simulation logic const result = simulationFunction(...parameterValues, mathUtils); // Validate the result if (typeof result !== 'object' || result === null) { throw new Error('Simulation logic must return an object'); } // Validate output types according to configuration const validatedResult = {}; const outputConfigs = this.enhancedConfig.outputs || this.config.outputs || []; for (const [key, value] of Object.entries(result)) { const outputConfig = outputConfigs.find(o => o.key === key); const expectedType = outputConfig?.type || 'number'; // Default to number for backward compatibility let validatedValue; if (expectedType === 'string') { validatedValue = String(value); } else if (expectedType === 'boolean') { if (typeof value === 'boolean') { validatedValue = value; } else if (typeof value === 'string') { validatedValue = value.toLowerCase() === 'true'; } else { validatedValue = Boolean(value); } } else { // number or default case const numericValue = Number(value); if (isNaN(numericValue)) { throw new Error(`Output '${key}' must be a number, got: ${typeof value}`); } validatedValue = numericValue; } validatedResult[key] = validatedValue; } // Validate that returned keys match expected outputs const expectedOutputs = (this.enhancedConfig.outputs || this.config.outputs || []).map(o => o.key); const actualOutputs = Object.keys(validatedResult); const missingOutputs = expectedOutputs.filter(key => !actualOutputs.includes(key)); if (missingOutputs.length > 0) { throw new Error(`Missing expected outputs: ${missingOutputs.join(', ')}`); } return validatedResult; } catch (error) { if (error instanceof Error) { throw new Error(`Simulation execution failed: ${error.message}`); } throw new Error(`Simulation execution failed: ${String(error)}`); } } setupParameterGroups() { if (!this.enhancedConfig.groups) return; const schema = this.getParameterSchema(); this.enhancedConfig.groups.forEach(group => { schema.addGroup({ name: group.name, description: group.description, parameters: group.parameters }); }); } getOutputDefinitions() { return this.enhancedConfig.outputs || this.config.outputs || []; } getConfiguration() { return { ...this.enhancedConfig }; } validateConfiguration() { const errors = []; try { // Test with default parameters const defaultParams = {}; this.getParameterDefinitions().forEach(param => { defaultParams[param.key] = param.defaultValue; }); const result = this.simulateScenario(defaultParams); // Verify all expected outputs are present const expectedOutputs = (this.enhancedConfig.outputs || this.config.outputs || []).map(o => o.key); const actualOutputs = Object.keys(result); const missingOutputs = expectedOutputs.filter(key => !actualOutputs.includes(key)); if (missingOutputs.length > 0) { errors.push(`Simulation doesn't return expected outputs: ${missingOutputs.join(', ')}`); } } catch (error) { errors.push(`Simulation logic validation failed: ${error instanceof Error ? error.message : String(error)}`); } return { valid: errors.length === 0, errors }; } /** * Run Monte Carlo simulation with multiple iterations and statistical analysis */ async runSimulation(parameters, iterations = 1000, onProgress) { const startTime = new Date(); const results = []; // Run simulation iterations for (let i = 0; i < iterations; i++) { const result = this.simulateScenario(parameters); results.push(result); // Call progress callback if provided if (onProgress && i % Math.max(1, Math.floor(iterations / 100)) === 0) { onProgress(i / iterations, i); } } // Final progress callback if (onProgress) { onProgress(1, iterations); } // Calculate statistical summary const outputKeys = Object.keys(results[0] || {}); const summary = {}; outputKeys.forEach(key => { const values = results.map(r => Number(r[key])).filter(v => !isNaN(v)); if (values.length > 0) { const sorted = values.slice().sort((a, b) => a - b); const mean = values.reduce((sum, val) => sum + val, 0) / values.length; const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length; summary[key] = { count: values.length, mean, median: sorted[Math.floor(sorted.length / 2)], standardDeviation: Math.sqrt(variance), percentile10: sorted[Math.floor(sorted.length * 0.1)], percentile90: sorted[Math.floor(sorted.length * 0.9)] }; } }); const endTime = new Date(); return { metadata: this.getMetadata(), parameters, results, summary, startTime, endTime, duration: endTime.getTime() - startTime.getTime() }; } } exports.ConfigurableSimulation = ConfigurableSimulation; //# sourceMappingURL=ConfigurableSimulation.js.map