156 lines
5.1 KiB
JavaScript
156 lines
5.1 KiB
JavaScript
function createConsumptionModel() {
|
|
function normalizeConsumptionTrendItems(items) {
|
|
const points = new Map();
|
|
|
|
(Array.isArray(items) ? items : []).forEach((item) => {
|
|
const dateKey = String(item.fyDate || (Array.isArray(item.fiscalTime) ? item.fiscalTime.join("|") : ""));
|
|
|
|
if (!dateKey) {
|
|
return;
|
|
}
|
|
|
|
if (!points.has(dateKey)) {
|
|
points.set(dateKey, {
|
|
dateKey,
|
|
date: parseConsumptionDate(item.fyDate),
|
|
fiscalTime: Array.isArray(item.fiscalTime) ? item.fiscalTime.slice() : [],
|
|
fiscalQtr: item.fiscalQtr || "",
|
|
funded: 0,
|
|
overage: 0,
|
|
arr: 0,
|
|
currencyCode: item.currencyCode || "USD"
|
|
});
|
|
}
|
|
|
|
const point = points.get(dateKey);
|
|
const amount = toFiniteNumber(item.usedAmount);
|
|
const category = String(item.usageCategory || "Funded Allocation").toUpperCase();
|
|
|
|
if (category === "OVERAGE") {
|
|
point.overage += amount;
|
|
} else {
|
|
point.funded += amount;
|
|
}
|
|
|
|
point.arr = Math.max(point.arr, toFiniteNumber(item.arrCd));
|
|
});
|
|
|
|
return Array.from(points.values())
|
|
.map((point) => ({ ...point, total: point.funded + point.overage }))
|
|
.sort((left, right) => left.dateKey.localeCompare(right.dateKey));
|
|
}
|
|
|
|
function getConsumptionPointTotal(point, activeSeries) {
|
|
const selectedSeries = activeSeries && typeof activeSeries.has === "function"
|
|
? activeSeries
|
|
: new Set(["funded", "overage"]);
|
|
return (selectedSeries.has("funded") ? point.funded : 0)
|
|
+ (selectedSeries.has("overage") ? point.overage : 0);
|
|
}
|
|
|
|
function calculateAverageConsumption(points, hasPartialLatestPoint, activeSeries) {
|
|
const completePoints = hasPartialLatestPoint && points.length > 1 ? points.slice(0, -1) : points;
|
|
return completePoints.reduce(
|
|
(sum, point) => sum + getConsumptionPointTotal(point, activeSeries),
|
|
0
|
|
) / Math.max(completePoints.length, 1);
|
|
}
|
|
|
|
function createNiceConsumptionMaximum(value) {
|
|
if (!Number.isFinite(value) || value <= 0) {
|
|
return 1;
|
|
}
|
|
|
|
const magnitude = 10 ** Math.floor(Math.log10(value));
|
|
const normalized = value / magnitude;
|
|
const step = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
|
return step * magnitude;
|
|
}
|
|
|
|
function formatConsumptionPointLabel(point, period) {
|
|
if (period === "quarterly") {
|
|
return point.fiscalTime[0] || point.fiscalQtr || point.dateKey;
|
|
}
|
|
|
|
if (period === "monthly") {
|
|
return point.fiscalTime[1] || formatConsumptionDate(point.date, { month: "short" });
|
|
}
|
|
|
|
return point.fiscalTime[0] || formatConsumptionDate(point.date, {
|
|
day: "2-digit",
|
|
month: "short",
|
|
year: "2-digit"
|
|
});
|
|
}
|
|
|
|
function formatConsumptionRange(points, period) {
|
|
if (points.length === 0) {
|
|
return "No data";
|
|
}
|
|
|
|
if (period === "quarterly") {
|
|
return `${formatConsumptionPointLabel(points[0], period)} - ${formatConsumptionPointLabel(points[points.length - 1], period)}`;
|
|
}
|
|
|
|
if (period === "monthly") {
|
|
const options = { month: "short", year: "numeric" };
|
|
return `${formatConsumptionDate(points[0].date, options)} - ${formatConsumptionDate(points[points.length - 1].date, options)}`;
|
|
}
|
|
|
|
const options = { day: "2-digit", month: "short" };
|
|
return `${formatConsumptionDate(points[0].date, options)} - ${formatConsumptionDate(points[points.length - 1].date, options)}`;
|
|
}
|
|
|
|
function parseConsumptionDate(value) {
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
const parts = String(value).split("-").map(Number);
|
|
return parts.length === 3 && parts.every(Number.isFinite)
|
|
? new Date(parts[0], parts[1] - 1, parts[2])
|
|
: null;
|
|
}
|
|
|
|
function formatConsumptionDate(date, options) {
|
|
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
return "-";
|
|
}
|
|
|
|
return new Intl.DateTimeFormat("en-US", options).format(date).replace(",", "");
|
|
}
|
|
|
|
function formatCompactConsumptionNumber(value) {
|
|
const amount = toFiniteNumber(value);
|
|
|
|
if (Math.abs(amount) >= 1000000) {
|
|
return `${(amount / 1000000).toFixed(amount >= 10000000 ? 0 : 1).replace(".0", "")}M`;
|
|
}
|
|
|
|
if (Math.abs(amount) >= 1000) {
|
|
return `${Math.round(amount / 1000)}K`;
|
|
}
|
|
|
|
return String(Math.round(amount));
|
|
}
|
|
|
|
function formatConsumptionCurrency(value, currencyCode) {
|
|
try {
|
|
return new Intl.NumberFormat("en-US", {
|
|
style: "currency",
|
|
currency: currencyCode || "USD",
|
|
maximumFractionDigits: 0
|
|
}).format(toFiniteNumber(value));
|
|
} catch (error) {
|
|
return `${currencyCode || "USD"} ${Math.round(toFiniteNumber(value)).toLocaleString("en-US")}`;
|
|
}
|
|
}
|
|
|
|
function toFiniteNumber(value) {
|
|
const number = Number(value);
|
|
return Number.isFinite(number) ? number : 0;
|
|
}
|
|
|
|
return { normalizeConsumptionTrendItems, getConsumptionPointTotal, calculateAverageConsumption, createNiceConsumptionMaximum, formatConsumptionPointLabel, formatConsumptionRange, parseConsumptionDate, formatConsumptionDate, formatCompactConsumptionNumber, formatConsumptionCurrency, toFiniteNumber };
|
|
}
|