merge: intègre main — main prioritaire, abandon de la page en doublon

main a beaucoup avancé (53 commits) pendant que cette branche construisait
l'API. La fusion a révélé que /produits, sur main, fait la même chose que la
page Recherche réseau d'ici, en plus abouti : fiche produit, opportunités,
URL partageables, et sa propre chaîne Qlik (lib/qlik-search, pgSearchProduits).

Résolution, main prioritaire :
- sidebar, heatmap-grid, qlik-playwright : version de main telle quelle. Son
  travail sur la tendance et Qlik est postérieur et plus complet.
- get-product-rows : import NB_MAGASINS_RESEAU de main, plus l'appel
  upsertGridRows d'ici — sans lui /api/v1 n'aurait aucune donnée à servir.
- settings : les deux côtés (ServerLogs de main + les sections API d'ici).

Suppression du doublon : page recherche-reseau, /api/qlik/search, ses types et
composants. searchNetworkProductsPlaywright disparaît avec la version main de
qlik-playwright ; rien d'autre ne l'utilisait.

api-enrich se branche désormais sur le module de tendance de main
(features/grid/lib/network-trend) plutôt que sur le mien, supprimé : sa mesure
est meilleure — variation sur la quantité par magasin vendeur, ce qui distingue
« vend mieux » de « est distribué plus largement » — et l'API renvoie ainsi
exactement ce qu'affiche la Grille. Le champ trend gagne surQteParMagasin et
nouveau.

Apport conservé de la branche : l'API /api/v1, absente de main.

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 11:13:44 +00:00
commit 3fa92e891b
50 files changed
+8488 -2017

No files matched your search

+26 -2
View File
@@ -15,10 +15,10 @@ import { db } from "@/db";
import { sessionSnapshots } from "@/db/schema";
import { eq, desc } from "drizzle-orm";
import { getNetworkMetricsByCodeCentrale } from "@/lib/qlik-network-cache";
import { NB_MAGASINS_RESEAU } from "@/features/grid/lib/network-trend";
// Persiste l'instantané lu par /api/v1 : sans lui, l'API n'aurait aucune donnée.
import { upsertGridRows } from "@/lib/grid-store";
const NB_MAGASINS_RESEAU = 270;
interface GetProductRowsInput {
codeFournisseur: string;
magasin?: string;
@@ -507,6 +507,29 @@ async function reconcileSelectedStoreFromMensuelApi(
}
}
/**
* Extrait la série « nombre de magasins vendeurs » du détail mensuel complet.
*
* `metricsByMonth` porte les cinq mesures Qlik du mois ; seule `nbMag` sert à la
* deuxième courbe de la carte tendance. Renvoie `null` plutôt qu'un objet vide
* pour que l'UI distingue « pas de donnée » de « zéro magasin ».
*/
function nbMagParMois(
metricsByMonth: Record<string, { nbMag?: number }> | null | undefined,
): Record<string, number> | null {
if (!metricsByMonth) return null;
const parMois: Record<string, number> = {};
for (const [mois] of Object.entries(metricsByMonth)) {
// Un mois PRÉSENT dans le détail est un mois extrait : s'il n'a pas de
// `nbMag`, c'est qu'aucun magasin n'a vendu, donc zéro. Le traiter comme
// « manquant » amputait la série et faisait disparaître toute la courbe
// (les anciens caches n'écrivaient `nbMag` que sur les mois avec vente).
const nb = Number(metricsByMonth[mois]?.nbMag);
parMois[mois] = Number.isFinite(nb) ? nb : 0;
}
return Object.keys(parMois).length > 0 ? parMois : null;
}
/**
* Enrichit les produits avec les metriques reseau Qlik (cache qlik_network_metrics),
* jointes par code centrale. Degradation propre si la sync Qlik n'a jamais tourne
@@ -537,6 +560,7 @@ async function enrichWithNetworkMetrics(productMap: Map<string, ProductRow>): Pr
product.margePctReseau = m.margePctReseau;
product.tauxPresenceReseau = m.nbMagasinsReseau / NB_MAGASINS_RESEAU;
product.qteReseauByMonth = m.qteByMonth ?? null;
product.nbMagReseauByMonth = nbMagParMois(m.metricsByMonth);
product.networkFetchedAt = m.fetchedAt ?? undefined;
}
}