Le dump des expressions donne enfin la cause de fond :
« Quantité N » = Sum({<Type_Cal={'N'}>} quantite)
« Quantité COMP » = Sum({<Type_Cal={'$(vPeriod_comp)'}…>} quantite)
« Magasin Ventes Nb N » = Count({<Type_Cal={'N'}>} Distinct ventes_code_site)
L'app n'est pas un modèle « faits + calendrier ». Un pont de périodes duplique
les lignes de faits : Type_Cal='N' marque celles de la période analysée, 'COMP'
celles de la période de comparaison, et le champ Période (4 valeurs, une
sélectionnée) décide de quelle période il s'agit. Tout en découle :
- sélectionner Date ne change rien (mesuré : 100 % du total avant comme après),
le périmètre venant du pont de périodes et non du champ date ;
- « Quantité N » ne couvre que la période courante, d'où août→décembre vides ;
- « Quantité COMP » ne couvre que les mois ayant un comparable, d'où
janvier→juillet de l'année précédente ;
- Sum(quantite) brut additionne les lignes N ET COMP : il double compte, il ne
pouvait pas servir de substitut.
choisirPeriode() essaie donc chaque valeur de Période, mesure sur un cube
[Mois] × Quantité N combien de mois de la fenêtre elle rend réellement
disponibles, et garde la meilleure. Aucun libellé n'est deviné : c'est la
couverture mesurée qui décide, et elle est tracée. Si aucune valeur ne couvre
les 12 mois, les mois manquants restent absents du cache.
Aussi :
- Les mois réellement couverts reçoivent un zéro EXPLICITE quand l'article n'a
pas vendu ; la tendance ne retient que les mois présents comme clés. Un article
vendu depuis mai voyait sa tendance calculée sur deux points (« +4100 % »).
- Les diagnostics du chemin d'extraction sont répétés en fin de sync
([qlik-pw][diag]) : ils étaient au début d'un log de plusieurs milliers de
lignes.
- QLIK_USE_EXPR passe à désactivé par défaut.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Le log de la dernière sync montre batches=3 et des lignes « (rattrapage) » : le
chemin par expressions a bien tourné mais a été rejeté (quantité totale nulle),
et c'est le rattrapage qui a de nouveau recopié le total de période dans les mois
manquants — d'où les plateaux à 87 648 d'août à décembre et les « -162 % ».
- Passe de rattrapage SUPPRIMÉE. Elle ré-extrayait un mois vide en ne
sélectionnant que ses dates ; les master measures ignorant la sélection Date,
le cube lui renvoyait le total de période. Un mois qu'on ne sait pas extraire
doit rester absent, jamais rempli d'une valeur plausible.
- Le chemin par expressions vérifie maintenant qu'un champ date filtre RÉELLEMENT
les faits, ce que personne n'avait pu contrôler tant que seules des mesures
insensibles à la sélection étaient utilisées. Un total de contrôle
(Sum(quantite) sans dimension) est comparé avec et sans filtre ; un champ n'est
retenu que si le total est non nul ET strictement inférieur au total global.
Candidats : QLIK_DATE_FIELD, Date, Date calendrier, Date_Key (ce dernier
sélectionné au format AAAAMMJJ). Si aucun ne filtre, l'extraction le dit
clairement au lieu de produire une fenêtre fausse.
- Diagnostic : échantillon BRUT du cube loggué avant tout filtrage (mois brut,
mois normalisé, appartenance à la fenêtre, valeurs des cinq mesures), et
compteur de lignes écartées comme hors fenêtre. Ces deux traces distinguent une
expression invalide d'un format de dimension Mois inattendu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Le serveur Qlik est filtré par IP : injoignable depuis l'extérieur du réseau FF
(la passerelle d'egress tombe en connection timeout sur 443 comme sur 80, alors
qu'elle atteint le reste d'Internet). Impossible donc de valider à distance les
expressions par défaut ni la clé de jointure.
Ce script répond aux deux questions en une exécution, à lancer depuis le
conteneur de l'app :
- expressions réelles des master measures (CA N, Quantité N, COMP…) ;
- tableau mois par mois comparant Sum(quantite) / Sum(ca_ht) / Sum(ca_ttc) /
Count(DISTINCT [Magasin Code]) aux mesures « N », sur l'année en cours (où
elles doivent coïncider) et sur l'année précédente (où les « N » sont
censées être à 0) ;
- Article Code, article_no_centrale et article_codein côte à côte, pour
trancher la jointure avec articles.artcentrale.
Lecture seule : sélections en soft lock, objets de session détruits. Le mot de
passe vient de l'environnement, rien n'est écrit ni affiché.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Preuve arithmétique dans les logs : pour l'article 10000397784, qteReseau=164 et
caReseau=2220,75 € sont EXACTEMENT la somme de janvier à juillet 2026
(12+19+24+27+22+31+29 = 164). Et la passe de rattrapage, avec août 2025 seul
sélectionné, a renvoyé ces mêmes 164 / 2220,75 — puis les a recopiés dans les
cinq mois manquants.
« CA N » et « Quantité N » sont donc des mesures « cumul année en cours »,
insensibles au champ Date. Aucune sélection ne peut leur faire produire une
fenêtre 12 mois glissants. D'où, simultanément :
- des totaux réseau qui étaient un cumul année en cours, mois courant partiel
inclus, et non 12 mois glissants ;
- les mois de l'année précédente non couverts par « Quantité COMP » vides sur
tous les articles ;
- un rattrapage qui y recopiait le total de période.
Le chemin nominal agrège désormais directement les champs de faits, qui eux
respectent les sélections : Sum(quantite), Sum(ca_ht),
Count(DISTINCT [Magasin Code]), Sum(marge) — tous surchargeables par
QLIK_EXPR_QTE / _CA / _NBMAG / _MARGE. Un seul cube [Article Code, Mois] couvre
les 12 mois : ni passe N-1 ni rattrapage, c'est ce qu'ils compensaient. Les
totaux deviennent la somme des 12 mois de la fenêtre.
Garde-fous : « Quantité N » reste dans le cube pour calibrage et le log compare
les deux sur les mois de l'année en cours (seul périmètre où elle est juste) ;
quantité totale nulle ou erreur Engine → repli automatique sur l'ancien chemin ;
QLIK_USE_EXPR=0 le force.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Les timings de production sont sans appel : SelectValues sur « Article Code »
coûte 8 à 100 s PAR APPEL, quasi indépendamment du nombre de valeurs (le moteur
balaie un symbole de plus d'un million d'entrées). Avec 7 200 codes en lots de
300, cela faisait 24 appels, ~15 min de sync, et la session Qlik mourait avant
la fin — « Socket closed » puis « Execution context was destroyed » — en perdant
la totalité de l'extraction.
- Le chemin mensuel fait désormais UNE seule sélection pour tous les codes et un
seul cube paginé : la dimension Mois livre déjà tous les mois d'un coup, rien
n'obligeait à découper. La lecture des pages coûte ~50 ms. Repli automatique
sur les lots si l'Engine refuse (les lignes déjà lues sont retirées pour ne
pas doubler les totaux).
- rattraperMoisVides() adopte le même principe : une sélection de codes, puis
seule la fenêtre de dates change d'un mois à l'autre.
- Sonde de diagnostic de fin de sync retirée : elle refaisait une sélection
complète des codes et des 365 jours pour rien.
- Point de contrôle (cfCheckpoint) poussé après chaque passe. Si la session
meurt, l'extraction repart de là : les codes déjà traités ont des données
complètes, seul le reliquat manque. Le log signale « résultat PARTIEL ».
- Le script in-page remonte aussi ses lignes en cas d'erreur interne, et
« rows=[object Object] » dans les logs est corrigé.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Cinq mois consécutifs à 0 sur TOUS les articles n'est pas un résultat métier.
Les mesures « N » de l'app Qlik sont bornées à une année civile ; le chemin
« dimension Mois » sélectionne les 365 jours d'un coup, donc la mesure ne se
résout que sur une année et les mois de l'année précédente ressortent vides.
« Quantité COMP » ne rattrape que les mois ayant un comparable dans l'année en
cours — d'où le trou d'août à décembre 2025 observé en juillet 2026.
- La passe principale n'écrit plus de 0 pour un mois hors année N, et COMP
n'écrit rien quand elle est vide : un mois absent peut être rattrapé, un mois
à 0 se lit comme « pas de vente » et masque le trou définitivement.
- Nouvelle passe rattraperMoisVides() : tout mois resté vide pour tous les
articles est ré-extrait en ne sélectionnant QUE ses dates. La mesure se résout
alors sur la bonne année quelle que soit l'écriture de son set analysis. Elle
ne touche que le mensuel, jamais les totaux de période.
- Les expressions des master measures sont dumpées au log : sans elles,
impossible de savoir comment la mesure est bornée dans le temps.
Contournement sans redéploiement : QLIK_MONTH_DIM=0 (itération mois par mois,
plus lente mais sans trou). Les données déjà en cache gardent leurs zéros — il
faut relancer la sync pour les corriger.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
La sync de la Grille marche parce qu'elle est SEULE sur le serveur Qlik. La
recherche, elle, ouvrait une seconde session concurrente pour les mesures
pendant que la première tenait une énorme sélection : l'Engine coupait les
requêtes (code 15 « Request aborted »).
- Les mesures réseau (CA, quantité, magasins, CA/magasin, marge %) sortent
désormais du MÊME cube que l'identité, dans la même session : la fenêtre 12
mois glissants est sélectionnée sur le champ Date, les master measures sont
résolues par titre comme dans l'extraction de la Grille, et le cube est trié
par quantité réseau décroissante. Plus d'appel à
fetchNetworkMetricsPlaywright pendant une recherche.
- Sélection plafonnée à 300 libellés au lieu de 1 000 : sélectionner les 13 446
libellés contenant « tapis » prenait 81 s puis 486 s. Le cube étant trié, 300
suffisent pour remonter les meilleures ventes.
- Une seule recherche simultanée (deux jobs en parallèle aggravaient la
saturation).
- Le détail mensuel vient du cache : le cube de recherche est agrégé. L'upsert
passe les colonnes mensuelles en COALESCE pour qu'une recherche n'efface pas
un mensuel déjà extrait par une sync.
Jointure catalogue : les 40 codes ne trouvaient aucune correspondance dans
articles.artcentrale (formats différents — « Article Code » renvoie 8 chiffres,
artcentrale en a 11). Le champ article_no_centrale est maintenant remonté comme
seconde clé candidate, la jointure essaie les deux, et un échantillon des deux
clés est loggué quand aucune ne joint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
« Unexpected token '<', "<html> <h"... is not valid JSON » : ce n'était pas du
JSON parce que ce n'était pas l'application qui répondait. La recherche enchaîne
deux allers-retours Qlik (dont une extraction mensuelle) et dépassait le délai du
proxy, qui renvoyait sa page d'erreur HTML.
- L'API passe en asynchrone, sur le modèle déjà éprouvé de POST /api/qlik/sync :
POST démarre un job et rend la main immédiatement, GET renvoie l'avancement
puis le résultat. Le client interroge toutes les 2 s et affiche l'étape en
cours (recherche des articles / extraction des ventes / rapprochement
catalogue). Deux recherches simultanées au maximum.
- Le client ne présume plus que la réponse est du JSON : les statuts 504, 502,
503, 401 et 403 sont traduits en message actionnable au lieu d'une erreur de
parsing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Les logs de production ont tranché les trois causes.
1. La recherche de list object Qlik applique un OU entre les mots : « tapis
anti » ramenait 17 696 libellés sur 1 070 645 — presque tous ne contenant
que « anti ». On cherche désormais mot par mot pour retenir le plus sélectif,
on filtre en ET côté client (tous les mots présents, insensible
casse/accents), et on ne sélectionne que les valeurs retenues.
2. Sélectionner ces 17 694 valeurs faisait abandonner le cube suivant
(code 15 « Request aborted »), d'où le repli systématique sur le catalogue
local. La sélection est maintenant plafonnée à 1 000 valeurs, faite par
numéro d'élément (SelectListObjectValues) plutôt que par texte, et les
appels Engine retentent trois fois sur code 15.
3. Les champs détectés étaient les mauvais : article_libelle_ticket (libellé
ticket tronqué) au lieu d'Article, et code_fournisseur (le code, d'où les
« F005 » affichés) au lieu de Fournisseur. Une liste de préférences précède
désormais l'heuristique, avec repli automatique sur le candidat suivant si
un champ ne ramène rien.
Aussi :
- Un repli catalogue n'est plus mis en cache 10 min : une panne Qlik passagère
se corrigeait uniquement avec « Relancer ».
- Le code 15 est traduit en message actionnable, et un terme trop large pour le
moteur est signalé au lieu d'un « aucun résultat » trompeur.
- Les logs de la page ne relaient plus le bruit du client Qlik (thèmes,
extensions).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
« Server Action "…" was not found on the server » n'est pas une panne du
snapshot : Next.js identifie chaque Server Action par un hash calculé à la
compilation, et un onglet ouvert avant un nouveau build continue d'envoyer les
anciens identifiants. La seule issue est de recharger la page.
- src/lib/stale-action.ts : détection de cette erreur + message utilisateur.
- Grille et Snapshots : au lieu d'afficher l'erreur technique brute, on propose
« Recharger la page ». Les brouillons de la Grille sont persistés
(localStorage), recharger ne perd rien.
- SuccessModal accepte `variant="error"` : icône d'alerte rouge au lieu de la
coche verte, et surtout plus de fermeture automatique au bout de 3 s — un
message d'échec doit rester lisible. Les erreurs s'affichaient jusqu'ici sous
une coche de succès.
- SuccessModal accepte une action secondaire optionnelle, et joue son animation
d'entrée en CSS au lieu d'un setState synchrone dans un effet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Symptôme : « tapis anti » renvoyait 40 articles sans rapport (mugs, miroirs,
couvertures) avec des codes centraux contigus — c'est-à-dire le début du
catalogue Qlik, pas un résultat de recherche.
Cause : SearchResults + SelectValues. On ré-injectait les qText renvoyés par la
recherche globale dans un SelectValues sur le champ, sans vérifier le retour et
sans laisser à l'Engine le temps d'évaluer la sélection. Quand la sélection
n'est pas appliquée, le cube renvoie simplement les premières lignes de la
dimension « Article Code » — soit exactement les 40 lignes demandées.
Correctifs :
- Sélection via list object : SearchListObjectFor + AcceptListObjectSearch,
c'est-à-dire le mécanisme d'un volet de filtre Qlik. Qlik applique sa propre
sémantique de recherche et sélectionne lui-même les valeurs, sans réinjection
de valeurs texte. Libellé d'abord, code centrale en repli.
- Délai d'évaluation (QLIK_SETTLE_MS, défaut 250 ms) après ClearAll, après la
recherche et après l'acceptation, comme le fait déjà l'extraction.
- Garde-fou côté client : chaque ligne du cube est revérifiée contre le terme
(tous les mots présents dans le libellé, insensible casse/accents, ou code
correspondant). Une liste sans rapport avec la recherche est pire que zéro
résultat.
- Dédoublonnage par code centrale : un article référencé chez plusieurs
fournisseurs produisait autant de lignes (vu sur « Couverture de Pique-nique »,
code 10636135, listé deux fois).
- Diagnostic : inventaire des champs, nombre de valeurs trouvées, sélections
actives et compteur de lignes écartées sont tracés dans les logs ; si tout est
écarté, l'UI dit que le filtre n'a pas été appliqué au lieu d'afficher un
« aucun résultat » trompeur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Les intitulés des champs varient d'une app Qlik à l'autre. La détection reste
heuristique, mais l'inventaire complet des champs est désormais loggué et deux
variables d'environnement permettent de corriger le tir sans redéploiement de
code : QLIK_FIELD_ARTICLE_LIBELLE et QLIK_FIELD_FOURNISSEUR.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Recherche produit
- La page /produits interroge désormais Qlik Sense EN PREMIER
(src/lib/qlik-search.ts) : recherche globale Engine sur les champs article
(code + libellé détectés dynamiquement via FieldList), sélection des valeurs
trouvées, puis liste des Article Code correspondants.
- Les métriques réseau des articles trouvés sont extraites sur 12 mois
glissants et mises en cache (qlik_network_metrics), puis rapprochées du
catalogue FF Nancy par code centrale (pgGetProduitsByCodeCentrale).
- Repli propre sur la recherche catalogue local si Qlik est injoignable, signalé
dans l'UI. Résultats mis en cache mémoire 10 min, requête dédupliquée.
- Nouvelle route GET /api/produits/search (runtime nodejs, 300 s) ; la liste est
chargée côté client avec état de chargement, l'extraction Qlik prenant
plusieurs secondes.
Informations affichées
- Résultats : magasins vendeurs (+ % du réseau), quantité réseau, quantité par
magasin, prix moyen réseau, CA par magasin, marge %, sparkline de tendance,
fournisseur, et présence ou non au catalogue Nancy.
- Fiche : carte « Performance réseau · 12 mois glissants » en tête (8 indicateurs
+ détail mensuel qté / magasins / qté par magasin / CA / prix moyen / marge %),
puis carte « Fournisseur » dédiée, puis identité, stock, nos ventes 12 mois.
- La fiche s'ouvre aussi par code centrale (?cc=) pour les produits que le réseau
travaille et que Nancy ne référence pas — le libellé et le fournisseur Qlik
sont persistés dans le cache réseau pour ce cas.
Tendance réseau
- computeNetworkTrend reconstruit sa fenêtre depuis la date du jour : 12 mois
complets, mois en cours EXCLU (il est partiel et écrasait la pente). Les mois
absents du cache valent 0 ; ceux antérieurs à la première extraction réelle
sont écartés au lieu d'être inventés à 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Deux régressions constatées en production sur la page /produits.
1. La recherche par libellé ne remontait rien. `ILIKE '%tapis anti-poussière%'`
exige l'accent ET le tiret exacts, alors que les libellés sont stockés en
majuscules sans accent ni ponctuation ("TAPIS ANTI POUSSIERE"). La recherche
est désormais normalisée des deux côtés (minuscules, accents retirés,
ponctuation ramenée à un espace) et fonctionne mot à mot : tous les mots
doivent être présents, dans n'importe quel ordre. « tapis poussiere » retrouve
donc « TAPIS ANTI-POUSSIÈRE », et inversement.
Postgres n'a pas unaccent() sans extension et on ne crée rien sur la base FF :
la normalisation SQL passe par translate() + regexp_replace().
2. Secteur et Famille restaient vides sur la fiche. La table `nomenclature`
n'expose en production que no_id / code / libelle / niveau / chemin_pere :
aucune FK entière vers le parent, et chemin_pere est un chemin texte, donc la
découverte automatique renvoyait null. La hiérarchie est maintenant
reconstruite par préfixe de code quand aucune colonne parent n'existe
(secteur = 2 chiffres, famille = 4, sous-famille = 6, ex "330702" → "3307" → "33").
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XR8cMbsdjXWChX8seL9NCP
Nouvelle page /produits : recherche par libellé ou par code centrale, puis
fiche complète d'un produit — identité, prix, stock par magasin, ventes sur
12 mois glissants, et l'intégralité des métriques réseau Qlik.
Jusqu'ici les données produit n'étaient accessibles que par fournisseur
(la Grille) ou par classement (Hit Parade) : aucune route n'acceptait un
codein. Les données réseau Qlik n'étaient visibles que dans six colonnes
de la grille d'arbitrage.
Deux analyses ajoutées :
- comparatif local vs réseau, ramené au magasin moyen (indice de
performance sur les quantités, seule base commune fiable — le CA local
est TTC et la base du CA Qlik n'est pas documentée, donc indicatif) ;
- opportunités de la même famille : références que le réseau vend bien et
que nous vendons peu, écart calculé par magasin.
Détail mensuel réseau enrichi : le cube Qlik renvoyait déjà CA, nb de
magasins, CA/magasin et marge % par (article, mois) mais seule la quantité
était conservée. Ces mesures sont désormais stockées dans une nouvelle
colonne metrics_by_month, sans requête Qlik supplémentaire. qte_by_month
reste inchangée — la colonne « Tendance / Réseau » de la Grille en dépend.
L'extraction Qlik peut désormais cibler un seul code centrale
(/api/qlik/sync?codeCentrale=…), ouverte à tout utilisateur connecté et
plafonnée à 3 extractions simultanées ; le mode fournisseur reste
réservé aux admins.
Refactor sans changement de comportement : les helpers de mois, le calcul
de tendance et les graphes SVG quittent heatmap-grid.tsx pour des modules
partagés, et le polling des jobs Qlik devient le hook useQlikSyncJob,
réutilisé par la Grille et par la fiche.
Les calculs de ventes reprennent le SQL canonique de la Grille (genremvt=3
avec quantités niées pour déduire les retours, stock de fin de mois pris
sur le dernier mouvement, report sur les mois sans mouvement), afin que
les chiffres des deux pages soient identiques par construction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XR8cMbsdjXWChX8seL9NCP
CA / Quantité / Nb magasins étaient pris depuis des qId d'environnement
(GUID), sans garantie qu'ils pointent la bonne mesure — les deux autres
mesures étaient déjà résolues par titre pour cette raison. Toutes le sont
désormais ('CA N', 'Quantité N', 'Magasin Ventes Nb N'), avec repli sur
l'env.
Ajout de deux logs de vérification :
- les identifiants de mesure réellement utilisés ;
- un échantillon de ligne mensuelle avec chaque valeur ÉTIQUETÉE, pour
comparer directement avec Qlik et confirmer que la courbe utilise bien
la quantité vendue et non le nombre de magasins.
Le graphe sautait de 07/25 à 01/26 : l'astuce COMP ne fournit un mois N-1
que s'il existe un mois N correspondant. L'année N s'arrêtant au mois
courant (juillet), les mois août→décembre N-1 étaient introuvables.
Ajout d'une passe complémentaire : on sélectionne le champ 'Année' sur
N-1, ce qui fait porter les mesures 'N' sur cette année → ses 12 mois
sont récupérés directement (prioritaires sur les valeurs COMP dérivées).
Les totaux réseau (CA/Qté) ne sont alimentés que par la passe principale
pour éviter tout double comptage. Passe ignorée proprement si le champ
'Année' est absent ou si elle échoue.
Résultat attendu : mois contigus 2025-08 → 2026-07 dans le modal (l'UI
garde les 12 derniers mois triés).
Le cube [Article Code, Mois] ne renvoie que les mois de l'année N (les
mesures 'N' sont nulles ailleurs → lignes supprimées). Le code cherchait
des lignes d'année N-1 qui n'existent pas, donc 'Quantité COMP' n'était
jamais exploitée → seulement ~7 mois affichés.
'Quantité COMP' étant le comparable N-1, à la ligne Mois=YYYYMM elle donne
la quantité du MÊME mois l'année précédente. On dérive donc deux points
par ligne : YYYY-MM (Quantité N) et (YYYY-1)-MM (Quantité COMP) → 12 mois
sans ligne supplémentaire ni requête en plus.
UI : l'en-tête du modal affiche le nombre RÉEL de mois disponibles au lieu
de '12 derniers mois' en dur.
Quand l'app Qlik ne peut pas se charger faute de RAM serveur (code 6
'Not enough memory to load file' / code 3002 'File corrupted' / 'Out of
memory'), le job affiche un message clair ('Serveur Qlik saturé…') au
lieu de l'erreur technique. Ce n'est pas un bug CollectFlow : le sync
échoue à l'ouverture de l'app, avant toute requête.
- Corrige le tooltip erroné 'X% (4 derniers mois vs 4 précédents)' →
'Tendance X% sur 12 mois (régression)'. Le calcul est déjà une
régression linéaire sur 12 mois ; seul le texte était resté l'ancien.
- Réduit la taille des lots du cube [Article Code, Mois] (800→300, replis
150/75) pour alléger la mémoire moteur Qlik et limiter les 'Out of
memory / File corrupted' (3002) observés sur le sync.
Le modal doit couvrir 12 mois glissants (pas seulement depuis janvier).
Comme 'Quantité N' ne couvre que l'année en cours, on réintègre la mesure
'Quantité COMP' (N-1) pour les mois de l'année précédente : mois année N
→ Quantité N, mois année N-1 → Quantité COMP. La courbe du modal affiche
donc les 12 derniers mois.
- Modal : les 12 barres remplacées par UNE seule courbe SVG des ventes
réseau mensuelles (points + quantités affichées + axe des mois).
- Sync : retrait de la mesure 'Quantité COMP' (année N-1) — trop lent à
récupérer. Retour au cube [Article Code, Mois] à 5 mesures (Quantité N).
Le détail mensuel couvre donc les mois de l'année en cours ; la tendance
(régression) se calcule sur ces mois.
UI:
- La colonne Tendance (renommée 'Réseau · 12 m') ouvre le modal au clic
(au lieu de la colonne Nb magasins, remise en simple affichage).
- Tendance calculée par RÉGRESSION LINÉAIRE (moindres carrés) sur les 12
derniers mois : robuste au bruit, % = pente×durée/moyenne. Libellé
Forte hausse/Hausse/Stable/Baisse/Forte baisse (seuils ±8% / ±25%).
Sync:
- Ajout de la mesure 'Quantité COMP' (année N-1) au cube [Article Code,
Mois] → 12 mois glissants : mois de l'année N via Quantité N, mois de
l'année N-1 via Quantité COMP. Cube 8 colonnes, PAGEM 1200 (<10000
cellules). Total réseau reste en année N (non gonflé).
Configurable : QLIK_MEAS_QTE_COMP_ID.
La sonde a prouvé que les mesures 'Quantité N' se découpent correctement
par la dimension 'Mois' (id pfGAwTs, format 'YYYYMM'). L'itération par
sélection de Date, elle, renvoyait la valeur annuelle identique chaque
mois (tendance plate + total sommé 12×).
Nouvelle extraction réseau (activée par défaut, QLIK_MONTH_DIM=0 pour
revenir à l'ancienne) : un cube [Article Code, Mois] × 5 mesures par lot
de codes (avec repli code 15 800>400>200), sélection de la fenêtre de
dates une seule fois. Chaque ligne (code, mois, ...) alimente qteByMonth
en 'YYYY-MM' et le total par somme RÉELLE des mois.
Résultat attendu : la colonne Tendance affiche enfin de vraies variations,
le modal montre les ventes réseau mois par mois, et le total réseau n'est
plus gonflé 12×. Nécessite un nouveau Sync pour re-remplir le cache.
Note : 'Quantité N' = année N, donc les mois disponibles sont ceux de
l'année en cours (pas un glissant 12 mois strict) — suffisant pour la
tendance ; on pourra ajouter 'Quantité COMP' pour compléter N-1 si besoin.
Le dump révèle une dimension 'Mois' (id pfGAwTs) et 'Période' (tKTPVU).
La sonde crée un mini-cube [Article Code, Mois] × Quantité N et logue le
format des valeurs Mois + si la quantité varie par mois — pour concevoir
le vrai découpage mensuel (dimension Mois) qui règlera tendance + total 12x.
- Chemin rapide select-all activé par DÉFAUT (fiable, aucun code 15 en
prod) — désactivable seulement via QLIK_SELECT_ALL_CODES=0. Plus besoin
de la variable d'env (elle sautait à chaque mise à jour de la stack).
- Dump diagnostic des mesures + dimensions Qlik disponibles (titre+id)
pour identifier une mesure Quantité non-annuelle / une dimension Mois,
car les mesures 'CA N'/'Quantité N' renvoient la valeur annuelle figée
(mois identiques → tendance impossible + total réseau sommé 12×).
Le sync Qlik calculait déjà les ventes réseau mois par mois mais les
sommait en un total. On conserve désormais le détail mensuel.
- Sync : accumulation additive qteByMonth { code: { 'YYYY-MM': qté } }
in-page (chemins rapide + par lots), rattachée au métrique réseau.
Reset propre en cas de repli code 15. Total inchangé.
- Stockage : colonne jsonb qte_by_month sur qlik_network_metrics
(schema + db-init ALTER), upsert + lecture.
- ProductRow.qteReseauByMonth propagé jusqu'à la grille.
- UI grille :
· colonne '% Présence réseau' remplacée par 'Tendance réseau' :
sparkline 12 mois teintée + flèche + variation % (moyenne 4 derniers
mois vs 4 précédents), triable ;
· cellule 'Nb magasins' rendue cliquable → modal des ventes réseau
mois par mois (barres, 12 derniers mois).
Nécessite un nouveau Sync Qlik pour peupler le détail mensuel (les
données déjà en cache n'ont que le total → modal 'pas encore de détail').