(() => { function create(dependencies) { const { toNumber, normalizeString, parseDatePreservingDateOnly, formatWeekNumber, } = dependencies; function buildRampForecastComparisonRows(workloads) { return (workloads || []) .flatMap((workload) => { const startDate = parseDatePreservingDateOnly( workload.consumptionStartDate ); if (Number.isNaN(startDate.getTime())) { return []; } const startMonth = startDate.getMonth() + 1; const startYear = startDate.getFullYear(); const workloadLabel = `${workload.name || "Unnamed workload"}${ workload.description ? ` | ${workload.description}` : "" }`; return (workload.forecast || []) .map((forecastItem) => { const forecastMonth = parseForecastMonthNumber(forecastItem.month); const forecastYear = toNumber(forecastItem.year); if (!forecastMonth || !forecastYear) { return null; } const monthIndex = (forecastYear - startYear) * 12 + (forecastMonth - startMonth) + 1; const currentValue = toNumber( forecastItem.adjustedConsumptionAmount ); const calculatedValue = monthIndex >= 1 && monthIndex <= 12 ? calculateRampForecastAmount({ adjustedACR: workload.adjustedACR, rampMonths: workload.rampMonths, monthIndex, consumptionStartDate: startDate, }) : 0; return { workloadLabel, periodLabel: formatForecastPeriodLabel(forecastYear, forecastMonth), currentValue, calculatedValue, delta: currentValue - calculatedValue, monthIndex, year: forecastYear, month: forecastMonth, sortKey: `${workloadLabel}|${String(forecastYear).padStart( 4, "0" )}-${String(forecastMonth).padStart(2, "0")}`, }; }) .filter(Boolean); }) .sort((left, right) => left.sortKey.localeCompare(right.sortKey)); } function hasRampForecastMismatch(workload) { return buildRampForecastComparisonRows([workload]).some((row) => isNonZeroAmount(row.delta) ); } function calculateRampForecastAmount({ adjustedACR, rampMonths, monthIndex, consumptionStartDate, }) { const annualValue = toNumber(adjustedACR); const ramp = Math.max(0, toNumber(rampMonths)); const month = Math.max(1, toNumber(monthIndex)); if (!annualValue) { return 0; } if (ramp <= 0) { return Math.round( (annualValue / 12) * getStartMonthProrationFactor(month, consumptionStartDate) ); } const steadyStateMonthlyAmount = (2 * annualValue) / (24 - ramp); const rampStepCount = ramp + 1; const calculatedAmount = month <= rampStepCount ? steadyStateMonthlyAmount * (month / rampStepCount) : steadyStateMonthlyAmount; return Math.round( calculatedAmount * getStartMonthProrationFactor(month, consumptionStartDate) ); } function getStartMonthProrationFactor(monthIndex, consumptionStartDate) { if (toNumber(monthIndex) !== 1) { return 1; } const startDate = parseDatePreservingDateOnly(consumptionStartDate); if (Number.isNaN(startDate.getTime())) { return 1; } const daysInMonth = new Date( startDate.getFullYear(), startDate.getMonth() + 1, 0 ).getDate(); const remainingDays = daysInMonth - startDate.getDate() + 1; return Math.max(0, Math.min(1, remainingDays / daysInMonth)); } function isNonZeroAmount(value) { return Math.abs(toNumber(value)) > 0.000001; } function parseForecastMonthNumber(value) { if (typeof value === "number") { return value >= 1 && value <= 12 ? value : 0; } const normalized = normalizeString(value); const numericMonth = Number.parseInt(normalized, 10); if (numericMonth >= 1 && numericMonth <= 12) { return numericMonth; } const monthMap = { JAN: 1, JANUARY: 1, FEB: 2, FEBRUARY: 2, MAR: 3, MARCH: 3, APR: 4, APRIL: 4, MAY: 5, JUN: 6, JUNE: 6, JUL: 7, JULY: 7, AUG: 8, AUGUST: 8, SEP: 9, SEPT: 9, SEPTEMBER: 9, OCT: 10, OCTOBER: 10, NOV: 11, NOVEMBER: 11, DEC: 12, DECEMBER: 12, }; return monthMap[normalized] || 0; } function formatForecastPeriodLabel(year, month) { const fiscalYear = month >= 6 ? year + 1 : year; const fiscalYearLabel = formatWeekNumber(fiscalYear % 100); const monthLabel = new Intl.DateTimeFormat("en-US", { month: "short", }).format(new Date(year, month - 1, 1)); return `FY${fiscalYearLabel} ${monthLabel}`; } function createForecastUpdatePayload(rows, userEmail = "") { return (rows || []) .filter((row) => row.year && row.month) .map((row) => ({ year: toNumber(row.year), month: toNumber(row.month), amount: toNumber(row.calculatedValue), updatedBy: userEmail, })); } return { buildRampForecastComparisonRows, hasRampForecastMismatch, calculateRampForecastAmount, getStartMonthProrationFactor, isNonZeroAmount, parseForecastMonthNumber, formatForecastPeriodLabel, createForecastUpdatePayload, }; } globalThis.ArchPanelRampForecastDomain = Object.freeze({ create, }); })();