Premier run reussi : 12 mois couverts, 40 411 produits ecrits dans le
cache, statut final=success.
Ce que la sonde paginee montre enfin :
Periode « (aucune) » → master exposee : 2024-01…2026-07 (31 mois)
Sans aucune Periode selectionnee, « Quantite N » couvre 31 mois, et une
seule passe (Annee=2026) sert les 12 mois de la fenetre. L'etat libre
etait donc le bon depuis le debut ; ce sont deux defauts de l'outil de
MESURE qui le cachaient — la sonde tronquee a 60 lignes, puis le Clear
appele sur le list object. Le commentaire de choisirPeriode affirmait
l'inverse (« sans Periode la master est morte ») : il est rectifie, avec
la mesure a l'appui.
Sonde fournisseur : elle lisait 1 207 205 codes en 240 pages avant de
conclure, trois fois de suite — 87 s pour un rejet joue d'avance. La
lecture s'arrete desormais des le plafond depasse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Le journal complet (enfin lisible) donne la cause, et elle est unique :
choix de « Periode » impossible :
{"code":-32601,"parameter":"Clear","message":"Method not found"}
extraction par date brute interrompue :
{"code":-32601,"parameter":"Clear","message":"Method not found"}
Clear et SelectValues sont des methodes de CHAMP. Je les appelais sur le
list object « Periode ». Consequence : la cartographie des periodes ET le
chemin d'extraction par date brute n'ont jamais tourne — d'ou la
couverture master annoncee a 0/12 alors que la master vit (total mesure :
11 591 263).
- Handle du champ « Periode » obtenu comme ceux d'Annee et de Date ; le
list object ne sert plus qu'a ENUMERER les valeurs.
- appliquerPeriode selectionne par texte et ne propage plus d'erreur :
une periode qu'on n'arrive pas a poser doit degrader le resultat, pas
interrompre l'extraction.
- Chaque candidat de la cartographie est isole : une valeur en echec est
notee et ignoree, elle n'emporte plus toute la carte.
Sonde fournisseur : « J009 » laissait 1 207 205 articles visibles, soit
tout le catalogue — la selection ne restreignait rien, et la
« couverture » de 97 % ne faisait que constater que le catalogue contient
nos codes. Une selection qui laisse plus de 3x les codes demandes est
desormais rejetee immediatement (elle coutait 3 x 24 s de lecture).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Le log livre le fait qui manquait :
dimension « Mois » → 35602068 (100% du total sans filtre)
100 %, donc TOUTE la table de faits tient dans les 12 mois. Le test de
champ date « il doit faire baisser le total » etait donc structurellement
faux : n'importe quel champ correct donne 100 % quand la fenetre couvre
tout. Il rejetait les bons champs, ce qui condamnait le seul chemin sain
et renvoyait vers la dimension Mois — celle qui demultiplie les faits.
Nouveau chemin moisParDateBrute(), essaye AVANT l'agregation par
dimension :
- Type_Cal / Periode / Annee effaces : on veut les faits nus, pas un
contexte de calendrier. Plus aucun mois « inatteignable », puisqu'un
mois y est un filtre de dates.
- detection du champ date sur UN SEUL MOIS : valide si le total est non
nul ET une fraction du total (ni 0, ni 100 %).
- cube [Article Code] x 4 expressions, SANS dimension Mois : c'est elle
qui passe par le pont et rattache un fait a plusieurs contextes
(mesure : x5,3).
- 12 selections de dates, 12 cubes pagines, un point de controle par
mois.
Controle d'integrite : chaque mois doit etre servi, et la somme des 12
mois ne doit pas depasser le total sans filtre de date — une
demultiplication ferait exploser ce rapport. Sinon rien n'est ecrit.
Corrige aussi le meme test defectueux dans moisParExpressions, et permet
d'effacer Periode meme si son list object n'a pas pu etre cree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Deux defauts, l'un cachant l'autre.
1. La sonde de couverture ne lisait que les 60 premieres lignes du cube
[Mois] x Quantite N. La dimension Mois porte tout l'historique de
l'app : les mois recents tombaient hors de la premiere page et la
sonde annoncait 0/12 pour TOUTES les valeurs de Periode — alors que
la master vivait (mesure dans le meme log : total 9 721 153). C'est
ce faux zero qui faisait effacer Periode et condamnait l'extraction.
Le cube est desormais pagine integralement, et la sonde journalise
aussi ce que la master expose hors fenetre.
2. La bonne valeur de Periode DEPEND DE L'ANNEE. « Annee a date »
convient a 2026 (janvier -> mois courant) mais pas a 2025, dont on
veut les 12 mois. Le choix global ne pouvait donc satisfaire que
l'une des deux passes — ce que montrait le log : 2026 servait
2026-01..06, la passe 2025 ne servait rien.
choisirPeriode cartographie maintenant, pour chaque annee de la
fenetre, la valeur qui sert le plus de mois attendus de cette
annee-la (l'etat sans Periode etant un candidat comme un autre), et
chaque passe annuelle pose SA valeur avant de selectionner l'annee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Le log de production tranche : sans selection de Periode, les master
measures sont MUETTES. Les passes annuelles renvoyaient bien 270 056
lignes, mais toutes mesures nulles — d'ou « 0 point mensuel ajoute »
puis le refus des 12 mois.
choisirPeriode() jugeait chaque valeur de Periode SANS annee
selectionnee. « Annee a date » y parait couvrir 6/12 mois, donc etait
rejetee, donc Periode etait effacee — et tout mourait. Or avec
Annee=2025 selectionnee, la meme valeur donne l'annee 2025 COMPLETE
(l'app affiche « Du 02/01/2025 au 31/12/2025 »), et avec Annee=2026 elle
s'arrete au mois courant. L'union des deux passes fait bien 12 mois.
- La couverture d'une Periode est desormais mesuree comme l'UNION des
passes annuelles, exactement comme monthDimPath les executera.
- La meilleure valeur est CONSERVEE meme partielle ; seule une
couverture nulle justifie d'effacer.
- Annees traitees la plus recente d'abord (usage de l'app), arret des
que la fenetre est complete.
- Diagnostics : mois gagnes / encore manquants apres chaque passe, et
liste des mois manquants dans les messages d'echec.
- Une passe ne peut plus ecraser un mois servi par un zero.
Deux defauts prouves par le meme log, cote agregation directe :
- la master « Quantite N » morte vidait TOUT l'hypercube (4 expressions
valides a 6 mois, cube complet a 0 ligne) : elle est desormais sondee
et omise si morte ;
- ventilee par Mois, Sum(quantite) totalise 179 574 616 contre
33 945 285 sans dimension (x5,3) : la dimension Mois passe par le pont
de periodes et demultiplie les faits. Ce chemin est refuse plutot que
d'ecrire des ventes reseau fausses.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Le code fournisseur FF de la base SQL/API existe aussi dans Qlik : une
seule valeur y designe le meme perimetre que les dizaines de milliers de
codes articles. Comme SelectValues sur « Article Code » coute 8 a 100 s
par appel et doit etre refait a chaque passe annuelle, la bascule
supprime la partie la plus couteuse de la sync.
- ETAPE 0 : selectionnerParFournisseur() avant choisirPeriode().
- Essaie QLIK_FIELD_CODE_FOURNISSEUR puis code_fournisseur, Fournisseur,
fournisseur_code_centrale.
- Verifie la couverture reelle (Article Code visibles vs codes demandes)
et n'accepte la bascule qu'au-dela de QLIK_SUPPLIER_MIN_COVERAGE
(0.99 par defaut) ; sinon annule la selection et revient aux codes.
- Quand la bascule est retenue, plus aucun SelectValues sur Article Code
(passes annuelles et agregation par expressions).
- La sync fournisseur transmet job.fournisseur ; la sync produit (un seul
code centrale) reste inchangee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Deux remarques de l'utilisateur ont débloqué le sujet : « sur ce tableau j'ai
2024, 2025, 2026 et je sors mois par mois », et « tu mets 2026 et tu as la
comparaison N-1 ».
Type_Cal='N' ne désigne donc pas « l'année civile en cours » mais L'ANNÉE
SÉLECTIONNÉE. Il suffit d'enchaîner les années couvertes par la fenêtre (2025
puis 2026) pour reconstituer 12 mois glissants — chaque passe livrant les 5
mesures, et non la seule quantité comme « Quantité COMP ».
Ce mécanisme existait déjà (monthDimPath(yearN - 1)) mais restait sans effet :
[qlik-pw] (mois) passe N-1 terminée : 299905 → 299905 points mensuels
La sélection « Période », héritée de l'ouverture de l'app et jamais choisie par
nous (observé « Période:1/4 » dès le premier diagnostic de la session),
épinglait le contexte sur l'année en cours et annulait la sélection d'année.
- choisirPeriode() teste désormais EN PREMIER l'état sans aucune sélection de
Période — l'état dans lequel un utilisateur voit 2024/2025/2026 — et efface la
sélection dès que la couverture est incomplète.
- L'orchestration boucle sur les années de la fenêtre au lieu d'une passe
principale plus une passe N-1 dérivée de COMP.
- Chaque passe n'écrit que les mois DE LA FENÊTRE et les totaux s'additionnent
sur ces mois : deux années ne peuvent pas se doubler puisqu'elles ne partagent
aucun mois. « Quantité COMP » n'est plus utilisée.
- L'agrégation directe des faits reste en secours, et le garde-fou d'intégrité
reste actif : sans les 12 mois, le cache n'est pas écrit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Deux constats de la dernière sync.
1. AUCUNE valeur de « Période » ne porte 12 mois glissants — c'est mesuré :
« juin 2026 » 1/12, « Année à date » 6/12, « Mois à date » 0/12,
« Semaine 2026/30 » 0/12
Ce sont 4 contextes relatifs à aujourd'hui, pas des types de période. Les
master measures ne pourront donc jamais couvrir la fenêtre, et l'agrégation
directe des faits devient obligatoire.
D'où un bug du commit précédent : garder la meilleure Période (6/12) RESTREINT
les faits à 2026-01→06 et ampute l'agrégation directe, qui sait pourtant lire
tout l'historique. choisirPeriode() efface désormais la sélection quand la
couverture est incomplète, et ne la garde que si elle couvre les 12 mois.
2. Le cube à 5 mesures calculait 26 s puis rendait 0 ligne, alors que
Sum(quantite) seul donnait 35 424 324 : signature d'une mesure invalide qui
invalide tout l'hypercube, sans qu'aucun message ne dise laquelle.
validerExpression() teste maintenant chaque expression isolément sur un petit
cube [Mois] avant de construire le gros. Une expression invalide est remplacée
par la colonne neutre 0 — le cube reste exploitable et le log nomme la
coupable. Seule la quantité est bloquante.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
Fusion de deux lignes de travail parallèles sur main, complémentaires et non
concurrentes :
cd2e77e selectionnerFenetreViaDimensionMois() — sélectionne les 12 mois par
qElemNumber sur la dimension maître Mois. C'est ce qui marche : les
champs Date exposés sont dissociés des faits (100 % du total avant
comme après), la dimension maître porte les vraies valeurs.
4ef0e9f garde-fou d'intégrité — refuse d'écrire le cache si la fenêtre n'est
pas entièrement couverte. C'est pourquoi le cache est resté inchangé
au lieu d'être corrompu.
726fb84 choisirPeriode() — le maillon manquant.
Le dump des expressions donne la cause de fond :
« Quantité N » = Sum({<Type_Cal={'N'}>} quantite)
« Quantité COMP » = Sum({<Type_Cal={'$(vPeriod_comp)'}…>} quantite)
Un pont de périodes duplique les lignes de faits ; Type_Cal marque la période
analysée (N) ou de comparaison (COMP), et le champ Période (4 valeurs) décide
de quelle période il s'agit. Sélectionner correctement les mois ne suffit donc
pas : sous la période par défaut, « Quantité N » rend 0 sur tout mois hors
période courante, même parfaitement sélectionné.
Orchestration fusionnée :
1. choisirPeriode() essaie chaque valeur de Période et MESURE, sur un cube
[Mois] × Quantité N, combien de mois de la fenêtre elle rend disponibles.
Aucun libellé n'est deviné.
2. Si une période couvre les 12 mois → master measures (exactes sur toute la
fenêtre). Sinon → agrégation directe des faits, en secours.
3. Dans les deux cas, la couverture des 12 mois est revérifiée avant
persistance ; sinon l'extraction est refusée et le cache laissé intact.
La tendance conserve la règle stricte de 4ef0e9f (les 12 clés explicitement
présentes, ou pas de tendance), qui écarte aussi le « +4100 % » calculé sur deux
points pour un article vendu depuis mai.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DfqUihgixw4K1AmJhizWiu
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
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
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.
- 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').
La sync Qlik itérait mois × lots de 150 articles, en re-sélectionnant les
mêmes Article Code à chaque lot. La SelectValues répétée (~22 s × ~120)
représentait ~72% du temps total (~60 min pour 12 mois).
Ajoute un chemin rapide (flag env QLIK_SELECT_ALL_CODES=1) qui sélectionne
TOUS les Article Code une seule fois, puis n'itère que la sélection Date
par mois avec un seul cube paginé (Article Code en dimension). Passe de
~120 SelectValues Article Code à 1, et de ~120 cubes à 12 → gain estimé
~60 min → ~5-8 min.
Repli automatique et sûr : en cas d'erreur moteur code 15, les résultats
partiels et les sélections sont réinitialisés et l'ancien chemin par lots
reprend le relais. Flag désactivé par défaut : comportement inchangé tant
qu'on n'active pas la variable. À valider sur le Qlik réel.
Large supplier (4816 codes) triggered Qlik Engine "Request aborted"
(code 15): a single SelectValues over all codes forced the hypercube to
calculate too many rows at once. Now select in 1200-code batches and
accumulate, keeping each calc small. 1417 codes worked before, 4816 did
not - chunking scales to any supplier.
Also remove the "Couverture de stock" network column end-to-end (grid,
types, Qlik clients, cache, schema, db-init), shifting the marge measure
index from 6 to 5 and the hypercube width from 7 to 6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stock Rupture % N saturated at 100% for high-presence products (degenerate
at the article x network grain), so remove it end to end: config id,
NetworkMetric, hypercube (width 8->7), cache, schema, enrich and grid
column. db-init drops the column on existing DBs.
Also remove the "Sans vente 6 mois" tab from the grid page: the grid now
renders directly without the tab switcher; delete the NoSalesTab component.
(The separate Stock negatif page keeps its own sans-vente tabs.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The qData expr path returned empty. Fetch the real master-measure
definition through GetMeasure -> GetProperties (qMeasure.qDef.qDef) and
log rupture, couverture and CA/mag expressions to understand why
Stock Rupture % saturates at 100% for high-presence products.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 of 4 new measures now resolve to engine qIds and return varied values
(CA/mag, couverture, marge%). Stock Rupture % N resolves but returns a
constant 1.0 (100%) per article — a measure-semantics issue. Log the
resolved rupture & couverture expressions to understand the definition,
and make the enrich sample pick a non-zero match (the previous sample hit
a no-sales code and looked all-zero).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new measures returned 0 because their qLibraryId was set to the QRS
object GUID, but the engine references master measures by qInfo.qId
(short codes like JhqJ) — an unknown GUID is silently ignored and yields
null. Resolve CA par Magasin / Couverture / Marge % / Rupture % by title
via the in-page MeasureList (whitespace+case normalized), falling back to
the configured id, and log the resolved ids.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a sample log of the first extracted rows (all 8 hypercube columns)
and a sample of the network metrics matched from cache in getProductRows,
to pinpoint whether the new measures return null from Qlik, fail to
persist, or fail to render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds CA par Magasin, Couverture stock Qte, Marge % and Stock Rupture %
network measures to the Qlik hypercube extraction (in-page Playwright ws
+ legacy server ws), persists them in qlik_network_metrics, joins them in
getProductRows and renders 4 new grid columns with buy-signal coloring
(low coverage / high margin = green; high rupture = rose).
- qlik-client: config IDs (env-overridable, master GUIDs of app 9872ee6e),
NetworkMetric fields, hypercube width 4->8, page height 2500->1250
- qlik-playwright: same 4 measures in the in-page hypercube + row mapping
- schema + db-init: 4 numeric columns (ALTER ADD COLUMN IF NOT EXISTS for
existing DBs)
- cache + grid types + enrich: plumb the new fields end to end
Note: percent measures stored as raw Qlik ratio; UI renders x100 when |v|<=1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Validated end-to-end. The recent Qlik client authorizes the engine websocket
via ?reloadUri=...&qlik-csrf-token=<token> (not Xrfkey). Capture that token
from the client's own ws, then open ours in-page with it. Use the real engine
master-item qIds (dim Article Code=yesCP, CA N=JhqJ, Quantité N=41516861...,
Magasin Ventes Nb N=yNBLjc) — the QRS object IDs returned error 7001. Select
the supplier's codes on field "Article Code" (the app holds ~1.2M codes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>