Files
itinova-budget-si/client/src/lib/opexTrends.ts

74 lines
2.3 KiB
TypeScript

/**
* Calculs purs des tendances OPEX utilisées par les vignettes de catégories.
*
* Cette fonction ne dépend volontairement ni de React, ni des fichiers JSON
* historiques : la comparaison est toujours faite avec les montants réellement
* chargés pour l'exercice précédent.
*/
export type OpexCategoryLine = {
categorie: string | null | undefined;
montant: number;
};
export type OpexCategoryTrend = {
montantN1: number | null;
variationPct: number | null;
tendance: "hausse" | "baisse" | "stable" | "new";
};
/** Toute variation strictement inférieure à ce seuil est considérée stable. */
export const STABLE_VARIATION_THRESHOLD_PERCENT = 2;
function toCategoryTotals(lines: OpexCategoryLine[]): Record<string, number> {
return lines.reduce<Record<string, number>>((totals, line) => {
const category = line.categorie?.trim() || "Autre";
totals[category] = (totals[category] ?? 0) + line.montant;
return totals;
}, {});
}
/**
* Retourne les tendances par catégorie pour l'exercice courant.
*
* Les pourcentages sont arrondis à une décimale pour correspondre à l'affichage
* et éviter des différences entre le badge et les données fournies au composant.
*/
export function calculateCategoryTrends(
currentLines: OpexCategoryLine[],
previousLines: OpexCategoryLine[],
): Record<string, OpexCategoryTrend> {
const currentTotals = toCategoryTotals(currentLines);
const previousTotals = toCategoryTotals(previousLines);
return Object.fromEntries(
Object.entries(currentTotals).map(([category, currentAmount]) => {
const previousAmount = previousTotals[category] ?? 0;
if (previousAmount === 0) {
return [
category,
{
montantN1: null,
variationPct: null,
tendance: "new" as const,
},
];
}
const rawVariation = ((currentAmount - previousAmount) / previousAmount) * 100;
const variationPct = Math.round(rawVariation * 10) / 10;
const tendance =
Math.abs(variationPct) < STABLE_VARIATION_THRESHOLD_PERCENT
? "stable"
: variationPct > 0
? "hausse"
: "baisse";
return [
category,
{ montantN1: previousAmount, variationPct, tendance },
];
}),
);
}