fix(qlik): piloter le champ Période du modèle au lieu de lutter contre lui

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
This commit is contained in:
Claude committed 2026-07-31 11:22:56 +00:00
1 parent db886cd8d9
commit 726fb8418d
3 files changed
+249 -46

No files matched your search

+15 -14
View File
@@ -56,16 +56,18 @@ export const TREND_COLOR: Record<NetworkTrend["direction"], string> = {
*
* La fenêtre est reconstruite à partir de la date du jour (et non des clés
* présentes dans `qteByMonth`) : c'est le seul moyen de garantir une tendance
* réellement glissante. Deux conséquences voulues :
* - un mois en cours (partiel) ou un mois plus vieux que 12 mois présent dans
* le cache est **ignoré** — sinon la pente est faussée par un mois tronqué ;
* - un mois de la fenêtre **absent** du cache vaut 0 : côté Qlik, un produit
* non vendu sur un mois ne produit tout simplement pas de ligne.
* réellement glissante. Un mois en cours (partiel), ou plus vieux que 12 mois,
* présent dans le cache est donc **ignoré** — sinon la pente est faussée par un
* mois tronqué.
*
* Seule exception au remplissage par zéro : les mois **antérieurs au premier
* mois réellement extrait**. Ils traduisent une extraction plus courte que 12
* mois (`QLIK_SYNC_MONTHS_BACK`), pas une absence de ventes — les inventer à 0
* simulerait une croissance qui n'existe pas.
* Un mois **absent** du cache n'est PAS supposé nul : l'extraction ne couvre pas
* toujours les 12 mois (les mesures Qlik « N » ne savent pas remonter au-delà de
* l'année en cours). La sync écrit un zéro **explicite** sur chaque mois qu'elle
* a réellement couvert sans vente ; la tendance ne retient donc que les mois
* présents comme clés, et ignore purement et simplement les autres.
*
* Sans cette distinction, un article vendu seulement depuis mai voyait sa
* tendance calculée sur deux points — « +4 100 % · Forte hausse ».
*
* Utilise tous les points (robuste au bruit d'un mois isolé). L'indicateur `pct` est la
* variation modélisée sur la période (pente × durée) rapportée à la moyenne.
@@ -81,11 +83,10 @@ export function computeNetworkTrend(
if (presents.length === 0) return empty;
const fenetre = buildRolling12QlikMonths(now);
// Aucun mois extrait ne tombe dans la fenêtre glissante (cache périmé) :
// rien de fiable à tracer.
const premierMoisExtrait = presents[0];
const labels = fenetre.filter((m) => m >= premierMoisExtrait);
if (labels.length === 0 || !labels.some((m) => qteByMonth[m] != null)) return empty;
// Seuls les mois RÉELLEMENT extraits comptent : ceux que la sync n'a pas
// couverts sont absents des clés et sont écartés, plutôt que comptés à zéro.
const labels = fenetre.filter((m) => qteByMonth[m] != null);
if (labels.length === 0) return empty;
const values = labels.map((l) => Number(qteByMonth[l]) || 0);
const n = values.length;