feat(api): expose la tendance réseau calculée dans /api/v1

La colonne « Tendance » de la Grille était la seule information visible que
l'API ne fournissait pas : elle est calculée dans le navigateur par
computeNetworkTrend(), qui vivait dans un composant "use client" donc
inaccessible côté serveur. L'API livrait la série mensuelle brute et laissait
chaque appelant refaire la régression — au risque de diverger de l'affichage.

- Le calcul part dans src/lib/network-trend.ts (module pur, sans React) ;
  le composant le réexporte, aucun import existant ne change.
- enrichRows ajoute `trend` : { direction, pct, label, monthsUsed }, calculé
  sur la même série que la colonne de l'application.
- openapi.json : schéma Trend documenté, et il est dit explicitement que la
  liste des propriétés de Product n'est pas exhaustive — la réponse porte la
  ligne de grille entière.

Vérifié au passage que rien n'est perdu en chemin : upsertGridRows stocke le
ProductRow complet dans la colonne jsonb `payload` (les colonnes scalaires ne
servent qu'aux index), ne filtre que les lignes sans codein, et pickFields
renvoie tout tant que `fields` n'est pas précisé.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y26nRZxTR57K7h8yqsF675
This commit is contained in:
Claude committed 2026-08-06 10:36:35 +00:00
1 parent 3a825866d2
commit 18edda7167
4 files changed
+128 -54

No files matched your search

+34
View File
@@ -22,6 +22,19 @@ import "server-only";
import type { ProductRow } from "@/types/grid";
import { getNetworkMetricsByCodeCentrale, type NetworkMetricCached } from "@/lib/qlik-network-cache";
import { pgGetGammesByCodeins } from "@/lib/pg-ff-client";
import { computeNetworkTrend, trendLabel, type TrendDirection } from "@/lib/network-trend";
/** Tendance réseau 12 mois, telle qu'affichée dans la colonne « Tendance » de la Grille. */
export interface ProductTrend {
/** "up" | "down" | "flat" — seuil à ±8 % de variation modélisée. */
direction: TrendDirection;
/** Variation modélisée sur la période (0.23 = +23 %). `null` si non calculable. */
pct: number | null;
/** Libellé prêt à afficher : « Forte hausse », « Stable »… */
label: string;
/** Nombre de mois effectivement utilisés (12 au maximum). */
monthsUsed: number;
}
export interface EnrichedProductRow extends ProductRow {
/**
@@ -31,6 +44,13 @@ export interface EnrichedProductRow extends ProductRow {
codeGammeServeur: string | null;
/** Métriques réseau Qlik en cache, ou `null` si le produit n'en a pas. */
network: NetworkMetricCached | null;
/**
* Tendance réseau calculée par régression linéaire sur les 12 derniers mois.
* `null` quand il n'y a aucune série mensuelle. Fournie pour que l'appelant —
* en particulier un agent — n'ait pas à réimplémenter le calcul et à diverger
* de ce que montre l'application.
*/
trend: ProductTrend | null;
}
/**
@@ -69,6 +89,7 @@ export async function enrichRows(rows: ProductRow[]): Promise<EnrichedProductRow
...row,
codeGammeServeur: gammeServeur,
network: metrics,
trend: null, // renseignée plus bas, une fois la série mensuelle connue
};
// `codeGammeInit` porte déjà la sémantique « état serveur » dans la Grille :
@@ -89,6 +110,19 @@ export async function enrichRows(rows: ProductRow[]): Promise<EnrichedProductRow
enriched.networkFetchedAt = metrics.fetchedAt ?? undefined;
}
// Tendance calculée sur la série finale (celle du cache si elle existe,
// sinon celle de l'instantané) — même entrée que la colonne « Tendance ».
const serie = enriched.qteReseauByMonth;
const t = computeNetworkTrend(serie);
if (t.hasData) {
enriched.trend = {
direction: t.direction,
pct: t.pct,
label: t.pct != null ? trendLabel(t.pct) : "Indéterminée",
monthsUsed: t.values.length,
};
}
return enriched;
});
}