AI-Based Earnings Forecasting Model (Simulator)
Forecast Setup
Define "AI" Factors (Influencing Variables)
Factor Impact & Weighting
Define how each factor influences earnings and its relative importance (weight).
Input Factor Values for Forecast Period
Enter the expected values for each factor for your chosen forecast period.
Forecast & Analysis
Your earnings forecast will appear here.
Factor Contributions to Forecast
No factors defined. Please add factors in Tab 2.
'; return; } aibefm_model.factors.forEach(f => { const card = document.createElement('div'); card.className = 'aibefm_dynamic_item_card'; // Re-use card style card.innerHTML = `${f.name || 'Unnamed Factor'}
No factors defined. Please add factors in Tab 2.
'; return; } aibefm_model.factors.forEach(f => { const div = document.createElement('div'); div.className = 'aibefm_input_group'; let inputHTML = ''; const currentValue = (f.currentValue !== null && f.currentValue !== undefined) ? f.currentValue : (f.type === 'categorical' && f.categories && f.categories.length > 0 ? f.categories[0] : (f.scaleMin || 0)); if (f.type === 'categorical') { const options = (f.categories || []).map(cat => ``).join(''); inputHTML = ``; } else if (f.type === 'numerical') { inputHTML = ``; } else { // percentage inputHTML = ``; } div.innerHTML = `${inputHTML}`; container.appendChild(div); }); } // --- Tab 5: Forecast & Analysis --- function aibefm_generateForecast() { aibefm_collectModelSetup(); // Ensure model object has latest UI data from Tab 1 const resultDiv = aibefm_getEl('aibefm_forecastResultDisplay'); const contributionsDiv = aibefm_getEl('aibefm_factorContributionsDisplay'); const contributionsList = aibefm_getEl('aibefm_contributionsList'); if (!resultDiv || !contributionsDiv || !contributionsList) return; if (aibefm_model.factors.length === 0) { resultDiv.innerHTML = 'No factors defined to make a forecast. Please define factors and their impacts/weights.
'; contributionsDiv.style.display = 'none'; aibefm_forecastOutput = null; document.getElementById('aibefm_downloadPdfButton').style.display = 'none'; return; } let totalPercentageChangeContribution = 0; let factorContributions = []; aibefm_model.factors.forEach(f => { let normalizedValue = 0; // Normalized value of the factor (0 to 1, where 1 is full positive impact) const currentValue = (f.currentValue === null || f.currentValue === undefined) ? (f.type === 'categorical' && f.categories && f.categories.length > 0 ? f.categories[0] : (f.scaleMin || 0)) : f.currentValue; if (f.type === 'numerical') { const min = f.scaleMin || 0; const max = f.scaleMax || 0; if (max > min) { normalizedValue = (parseFloat(currentValue) - min) / (max - min); } else if (parseFloat(currentValue) >= min) { // if max=min, treat as binary normalizedValue = 1; } } else if (f.type === 'categorical') { // Simple: assume first category is "worst" (0), last is "best" (1) const catIndex = (f.categories || []).indexOf(currentValue); if (catIndex !== -1 && (f.categories || []).length > 1) { normalizedValue = catIndex / ((f.categories || []).length - 1); } else if ((f.categories || []).length === 1 && catIndex === 0) { // Single category normalizedValue = 0.5; // Neutral } } else { // percentage // For percentage, treat the value directly as a percentage point (e.g., 2 means 2%) // Normalize it relative to a "neutral" 0 and its typical range to get a 0-1 factor strength later. // For simplicity, let's assume the 'weight' is the direct % impact if factorValue is 1 (or 100% if it's a percentage factor) // A weight of 5 means this factor, at its most positive interpretation, contributes +5% to earnings. // The actual contribution will be Normalized_Factor_Strength * Weight * Impact_Direction // For 'percentage' type, if currentValue is 2 (for 2%), its "strength" relative to neutral 0 needs context. // Let's assume a direct interpretation: a 2% GDP growth contributes based on its weight if positive. // For a simpler model: normalizedValue for percentage could be currentValue / (f.scaleMax for percentages, e.g. 10% being max "good" input) // Or, more simply, if factor type is 'percentage', its current value *is* its direct contribution scaling factor. // Let's simplify: if a factor is 'percentage', its 'currentValue' is treated as is (e.g. 2 for 2%). // The 'weight' will scale this. So, effective contribution = (currentValue / 100) * weight. This is too complex. // Simpler for 'percentage' type: // Normalized value: (currentValue - (neutral point, e.g. 0)) / (expected max deviation from neutral, e.g. scaleMax) const val = parseFloat(currentValue); const min = f.scaleMin || 0; // Use min/max to normalize even percentages. Assume 0 is neutral. const max = f.scaleMax || 0; if (val > 0 && max > 0) normalizedValue = Math.min(val/max, 1); // Cap at 1 for positive else if (val < 0 && min < 0) normalizedValue = Math.min(val/min, 1); // Cap at 1 for negative (strength of negative) // This normalization of percentage factor needs refinement or clearer user instruction. // For now, if type is percentage, treat currentValue as points, normalize 0-1 from min to max. if (max > min) { normalizedValue = (val - min) / (max - min); } else if (val >= min) { normalizedValue = 1; } } normalizedValue = Math.max(0, Math.min(1, normalizedValue)); // Clamp 0-1 const impactDirectionModifier = (f.impactDirection === 'positive') ? 1 : -1; // The actual percentage points this factor contributes const factorPercentageContribution = normalizedValue * (f.weight || 0) * impactDirectionModifier; totalPercentageChangeContribution += factorPercentageContribution; factorContributions.push({name: f.name || 'Unnamed', contribution: factorPercentageContribution }); }); // Cap total contribution by maxTotalImpactFromFactors if (totalPercentageChangeContribution > aibefm_model.maxTotalImpactFromFactors) { totalPercentageChangeContribution = aibefm_model.maxTotalImpactFromFactors; } else if (totalPercentageChangeContribution < -aibefm_model.maxTotalImpactFromFactors) { totalPercentageChangeContribution = -aibefm_model.maxTotalImpactFromFactors; } const forecastedValue = aibefm_model.baselineValue * (1 + (totalPercentageChangeContribution / 100)); const percentageChange = ((forecastedValue - aibefm_model.baselineValue) / aibefm_model.baselineValue) * 100; aibefm_forecastOutput = { companyName: aibefm_model.companyName, sector: aibefm_model.sector, baselineMetric: aibefm_model.baselineMetric, baselineValue: aibefm_model.baselineValue, forecastPeriod: aibefm_model.forecastPeriod, forecastedValue: forecastedValue, percentageChange: percentageChange, factors: JSON.parse(JSON.stringify(aibefm_model.factors)), // Deep copy for PDF factorContributions: factorContributions }; resultDiv.innerHTML = `${aibefm_model.baselineMetric} Forecast for ${aibefm_model.companyName || 'N/A'} (${aibefm_model.forecastPeriod})
Forecasted ${aibefm_model.baselineMetric}: $${forecastedValue.toFixed(2)} (${(percentageChange >=0 ? '+': '')}${percentageChange.toFixed(2)}%)
Baseline ${aibefm_model.baselineMetric}: $${aibefm_model.baselineValue.toFixed(2)}
`; contributionsList.innerHTML = ''; factorContributions.sort((a,b) => Math.abs(b.contribution) - Math.abs(a.contribution)); // Sort by magnitude factorContributions.forEach(c => { const item = document.createElement('div'); item.className = 'aibefm_factor_contribution_item'; item.innerHTML = `${c.name}: ${(c.contribution >=0 ? '+': '')}${c.contribution.toFixed(2)} pp`; contributionsList.appendChild(item); }); contributionsDiv.style.display = 'block'; document.getElementById('aibefm_downloadPdfButton').style.display = 'block'; } function aibefm_collectModelSetup() { aibefm_model.companyName = aibefm_getInputValue('aibefm_companyName'); aibefm_model.sector = aibefm_getInputValue('aibefm_sector'); aibefm_model.baselineMetric = aibefm_getInputValue('aibefm_baselineMetric'); aibefm_model.baselineValue = aibefm_getInputNumValue('aibefm_baselineValue', 0); aibefm_model.forecastPeriod = aibefm_getInputValue('aibefm_forecastPeriod'); // Factors are updated directly via their oninput events } // --- PDF Generation --- function aibefm_downloadPDF() { if (!aibefm_forecastOutput) { alert("Please generate a forecast first."); return; } if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('PDF library (jsPDF) is not loaded.'); return; } const { jsPDF: JSPDF } = window.jspdf; const doc = new JSPDF(); let y = 15; const m = 15; const cw = doc.internal.pageSize.getWidth() - (2 * m); const data = aibefm_forecastOutput; function addLine(text, size, style = 'normal', indent = 0, spacing = 2.5) { if (y > 270) { doc.addPage(); y = m; } doc.setFontSize(size); doc.setFont(undefined, style); const lines = doc.splitTextToSize(text, cw - indent); doc.text(lines, m + indent, y); y += (lines.length * (size * 0.35)) + spacing; } addLine(`Earnings Forecast: ${data.companyName || 'N/A'}`, 18, 'bold', 0, 5); addLine(`Sector: ${data.sector}, Period: ${data.forecastPeriod}`, 10); addLine(`Baseline ${data.baselineMetric}: $${data.baselineValue.toFixed(2)}`, 10); addLine(`Forecasted ${data.baselineMetric}: $${data.forecastedValue.toFixed(2)} (${(data.percentageChange >=0 ? '+': '')}${data.percentageChange.toFixed(2)}%)`, 12, 'bold', 0, 6); addLine("Influencing Factors & Settings:", 14, 'bold', 0, 4); data.factors.forEach(f => { let factorDesc = `- ${f.name || 'Unnamed'}: Type: ${f.type}, Impact: ${f.impactDirection}, Weight (Max Contrib.): ${f.weight}%`; if(f.type === 'numerical') factorDesc += `, Range: ${f.scaleMin}-${f.scaleMax}`; if(f.type === 'categorical') factorDesc += `, Cats: ${(f.categories||[]).join('/')}`; if(f.type === 'percentage') factorDesc += `, Input as % points`; factorDesc += `, Forecast Input Value: ${f.currentValue === null || f.currentValue === undefined ? 'N/A' : f.currentValue} ${f.unit || ''}`; addLine(factorDesc, 9, 'normal', 5, 1.5); }); y += 3; addLine("Factor Contributions to Forecast Change:", 14, 'bold', 0, 4); data.factorContributions.forEach(c => { addLine(`- ${c.name}: ${(c.contribution >=0 ? '+': '')}${c.contribution.toFixed(2)} percentage points`, 10, 'normal', 5, 2); }); addLine(`Note: This is a simulated forecast based on user-defined parameters. Max total impact from factors capped at +/-${aibefm_model.maxTotalImpactFromFactors}%.`, 8, 'italic', 0, 5); doc.save(`${(data.companyName || 'forecast').replace(/\s+/g, '_')}_earnings_forecast.pdf`); }