Commit Graph
203 Commits
Author SHA1 Message Date
Claude 332b21c1ae diag(qlik): sonde cube [Article Code, Mois] x Quantite N
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.
2026-07-23 11:52:52 +00:00
Claude 473ed1e467 perf+diag(qlik): chemin rapide activé par défaut + dump mesures/dimensions
- 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×).
2026-07-23 11:38:36 +00:00
Claude 74b183abb3 diag(qlik): distribution codes variants vs identiques (mois) + exemples 2026-07-23 11:35:27 +00:00
Claude 5b74133ecd diag(qlik): log échantillon qteByMonth (vérifie si les mois varient) 2026-07-23 11:14:58 +00:00
Claude 71563500a0 feat(grille): tendance réseau (sparkline) + modal ventes réseau par mois
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').
2026-07-22 14:17:57 +00:00
Claude 6c5e28b05e perf(qlik): chemin rapide select-all (sélection Article Code unique) derrière flag
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.
2026-07-22 09:47:31 +00:00
Claude eba4cb0774 fix(hit-parade,stock): retire le filtre montant (théorie invalidée) + tri stock négatif
Analyse de cohérence de stock sur le cas 487673 site 579 : 24 réceptions,
31 ventes brutes, 7 retours clients → 24 ventes nettes, stock final 3.
Le « 38 » affiché = 31 + 7 : signature de l'ancienne formule SUM(ABS)
qui additionnait les retours. La formule nette SUM(-qtemvt) déjà en place
donne 24 — le filtre mntmvtttc <> 0 (hypothèse de lignes fantômes 0 €)
était inutile : aucune ligne de ce type dans les données réelles. Retiré
pour rester strictement aligné sur le calcul de la Grille.

Gestion de stock : ORDER BY cs.qte (colonne affichée) au lieu de
cs.stockdispo (colonne différente) dans pgGetStockNegatif.
2026-07-07 06:46:45 +00:00
Claude ea19c5771e fix(hit-parade): exclut les lignes de quantité sans montant (corrections)
Cas réel : codein 487673 site 579 affichait 38 vendus au lieu de 24,
avec un CA pourtant exact (215,76 €) — mvtart contient des lignes
genremvt=3 portant une quantité mais aucun montant TTC (corrections /
régularisations), que les endpoints officiels de l'API excluent.

Ajout du filtre mntmvtttc IS NOT NULL AND <> 0 dans l'agrégation :
une vente réelle a toujours un montant TTC non nul. Validé sur
PostgreSQL local en reproduisant le cas : sans filtre 38/215,76 €
(= symptôme), avec filtre 24/215,76 € (= réalité). Le CA et les
requêtes Analytics ne changent pas (ces lignes valent 0 €).
2026-07-06 21:15:44 +00:00
Claude 159508132c fix(hit-parade): agrégation isolée sur mvtart×articles — corrige les chiffres faux des 2 magasins
La version précédente agrégeait les ventes avec les jointures fournisseur
(LATERAL par ligne de mouvement + fouident) dans la même passe : si
fouident contient des codes dupliqués, chaque mouvement était compté
plusieurs fois → chiffres faux sur les deux magasins, et le LATERAL
s'exécutait par mouvement (très lent).

Restructuration, validée sur 12 000 mouvements réels de l'API chargés
dans un PostgreSQL 16 local :
- CTE ventes : mvtart JOIN articles uniquement (1:1), SUM(-qtemvt) /
  SUM(-mntmvtttc) / SUM(margemvt) par (codein, site) — le calcul
  canonique de la Grille (pgGetMensuelByFournisseur), signes vérifiés
  sur les mouvements réels (ventes négatives, genremvt=3).
- CTE attrs : libellé/fournisseur/nomenclature joints APRÈS agrégation,
  dédupliqués par DISTINCT ON — aucune jointure ne peut plus fausser
  les sommes (totaux invariants aux doublons artfou1/fouident injectés).
- Même principe pour pgGetCaByFournisseur, pgGetStockNegatif,
  pgGetSansVente6Mois : sous-requête artfou1 DISTINCT ON au lieu du
  LATERAL par ligne.
2026-07-06 20:49:12 +00:00
Claude d733755659 fix(hit-parade): ventes nettes par magasin — corrige les chiffres faussés (579)
La requête Hit Parade divergeait du calcul canonique de la Grille :
- SUM(ABS(qtemvt)) additionnait les retours clients comme des ventes au
  lieu de les déduire → quantités et CA gonflés sur le magasin ayant des
  retours/avoirs sur la période (visible sur 579).
- ABS(SUM(mntmvtttc)) inversait le signe d'un CA net négatif.
- GROUP BY no_id + jointure artfou1 preference=1 non dédupliquée pouvait
  produire plusieurs lignes par (codein, site), écrasées par le pivot.

Corrections :
- Hit Parade : ventes nettes SUM(-qtemvt) / SUM(-mntmvtttc), agrégation
  par (codein, site), fournisseur préféré via LATERAL ... LIMIT 1,
  stock agrégé par codein ; pivot en accumulation (+=) par sécurité.
- Analytics (même classe de bug) : pgGetCaByFournisseur et
  pgGetCaByNomenclature passent en CA net ; jointure fournisseur
  dédupliquée (LATERAL LIMIT 1) pour éviter le double comptage.
- Gestion de stock : pgGetStockNegatif et pgGetSansVente6Mois dédupliquent
  le fournisseur préféré (LATERAL LIMIT 1) pour éviter les lignes en double.

Audit complet du mapping par magasin (292/579) côté client : aucun champ
inversé (hit-parade, analytics, dashboard, grid, heatmap). Build OK.
2026-07-06 15:55:43 +00:00
R0m1k3 abac5c5ee5 perf(qlik): optimize network sync extraction 2026-06-22 09:58:49 +00:00
R0m1k3 1cb6c2496d fix(qlik): retry engine aborts after selections 2026-06-22 06:13:05 +00:00
R0m1k3 f005c08ad6 fix(qlik): recreate hypercube per batch 2026-06-22 05:44:37 +00:00
R0m1k3 e7bcba169c fix(qlik): extract by month and code batches 2026-06-21 18:24:53 +00:00
R0m1k3 c4eeab11d2 fix(qlik): select all articles per month 2026-06-21 16:43:36 +00:00
R0m1k3 649a1df267 fix(qlik): aggregate multi-month extraction correctly 2026-06-21 14:32:51 +00:00
R0m1k3 6036ea5600 fix(qlik): avoid abort on batched network extraction 2026-06-21 12:08:13 +00:00
R0m1k3 6d27c7935d fix: align qlik grid extraction date range 2026-06-21 11:26:49 +00:00
MichaelandClaude Opus 4.8 de8a435c14 fix(qlik): batch SelectValues to avoid Engine abort + drop couv stock metric
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>
2026-06-21 10:55:03 +02:00
MichaelandClaude Opus 4.8 524faf4a7a feat(qlik): drop network rupture% metric and the "Sans vente 6 mois" grid tab
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>
2026-06-21 09:32:03 +02:00
MichaelandClaude Opus 4.8 af93e362e0 chore(qlik): read measure expressions via GetMeasure/GetProperties
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>
2026-06-21 09:20:43 +02:00
MichaelandClaude Opus 4.8 ae1deedd19 chore(qlik): log measure expressions to diagnose rupture%=100%
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>
2026-06-21 09:01:51 +02:00
MichaelandClaude Opus 4.8 da73693ea0 fix(qlik): resolve 4 new measures by title, not QRS GUID
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>
2026-06-21 08:50:43 +02:00
MichaelandClaude Opus 4.8 8bd45518f2 chore(qlik): log raw sample of 4 new metrics to isolate empty columns
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>
2026-06-21 08:39:35 +02:00
MichaelandClaude Opus 4.8 bd9bc78f4c feat(qlik): extract 4 decision metrics (CA/mag, couverture, marge%, rupture%)
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>
2026-06-21 08:34:54 +02:00
MichaelandClaude Opus 4.8 d54c3ae520 feat(qlik): add step-by-step logging to sync extraction
Logs each stage (PG codes -> NTLM session -> chromium -> csrf token
-> in-page ws -> OpenDoc -> SelectValues -> cube size -> rows) so a
hanging sync reveals where it stalls. Forwards in-page browser console
to the node logger.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 08:14:12 +02:00
MichaelandClaude Opus 4.8 6cdb845a1f fix(qlik): use qlik-csrf-token + real engine qIds — extraction works
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>
2026-06-21 00:37:04 +02:00
MichaelandClaude Opus 4.8 b755a69258 fix(qlik): in-page engine ws with authenticated browser session
The recent Qlik client uses qmfe/single-spa (no js/qlik Capability API). Go
back to a raw engine websocket but opened in-page from the authenticated
browser (injected X-Qlik-Session cookie), which the proxy accepts. Use the
session xrfkey and capture ws close code/reason for diagnostics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:19:28 +02:00
MichaelandClaude Opus 4.8 d319107076 fix(qlik): extract via Qlik Capability API (reuse page session)
Raw and in-page websockets with a self-generated xrfkey are rejected (403).
Switch to the Qlik Capability API (require(['js/qlik']) → openApp → createCube),
which reuses the authenticated page session and manages the engine ws + xrfkey
internally. Auth via injected X-Qlik-Session cookie (Node ticket flow) +
httpCredentials. Logs rich page diagnostics on failure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 00:13:45 +02:00
MichaelandClaude Opus 4.8 694b780160 feat(qlik): extract network data via Playwright (in-page ws)
The Qlik proxy refuses a raw server-side websocket (403, even internally), so
extraction now runs through headless Chromium (NTLM via httpCredentials, like
the hermes agent) and opens the Engine websocket in-page — same origin, which
the proxy accepts. Efficient: selects the supplier's article codes on the
"Article Code" field so the hypercube returns only those rows.

- qlik-playwright.ts: browser singleton, in-page hypercube extraction (paginated)
- /api/qlik/sync: use the Playwright extractor
- Dockerfile: install chromium + headless deps, PLAYWRIGHT_CHROMIUM_PATH, copy playwright-core
- deps: playwright-core (lockfiles synced)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:54:03 +02:00
MichaelandClaude Opus 4.8 1281b0e21f fix(qlik): send Origin/User-Agent on engine ws + verbose 403 diagnostics
Qlik proxy validates the WebSocket Origin header (whitelist) — a browser
always sends it. Add Origin + User-Agent to the engine ws. Also surface the
403 response (server/www-authenticate/body) for diagnosis.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:35:09 +02:00
MichaelandClaude Opus 4.8 abed369bfb feat(settings): configure Qlik credentials in the UI
Add a "Qlik Sense — Données réseau" section in Paramètres (host/user/password
+ Save & Test buttons), stored in data/.db-config.json. qlik-client reads this
file first (env fallback), so no env vars needed for the sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 23:28:55 +02:00
MichaelandClaude Opus 4.8 aee968d30f feat(grid): replace ranking/AI/score with Qlik network data
Remove ranking, AI analysis (routes + ai-copilot module), and score-engine
from the grid. Add Qlik Sense network metrics (CA, Qte, nb magasins per
product) joined by code centrale (articles.artcentrale).

- qlik-client.ts: NTLM + ticket SSO auth, QIX hypercube extraction (master items)
- qlik-network-cache.ts + qlik_network_metrics table (db-init): cache layer
- POST /api/qlik/sync?fournisseur=: per-supplier sync into cache
- SyncQlikButton: per-supplier sync, grid reload, last-update date display
- get-product-rows Phase 8: enrich rows with cached network metrics
- heatmap-grid: CA reseau / Qte reseau / Magasins (/270) / % presence columns
- discovery: app 9872ee6e, master items CA N / Quantite N / Magasin Ventes Nb N
- deps: httpntlm, ws

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 22:52:39 +02:00
Claude ae0d8d3a4c feat(commandes-auto): cadencier d'alertes de commande par fournisseur et magasin
Ajoute un onglet "Cadencier / Alertes" dans la page Commandes auto.
Depuis la liste des fournisseurs, on active un fournisseur en alerte de
commande sur un magasin (292/579) avec une fréquence en X semaines,
gérée indépendamment par magasin.

L'échéance est calculée à partir de la dernière réception réelle
(mvtart, genremvt 1/2, fournisseur principal artfou1.preference=1) +
X semaines. Statut: à commander / bientôt / OK, avec temps restant.

- Table commande_cadences (schema + db-init)
- pgGetDerniereReceptionParFournisseur (mvtart par fournisseur/site)
- Server actions CRUD (list/upsert/toggle/remove)
- UI à onglets + cadencier (ajout, édition intervalle, activer/supprimer)
2026-06-03 08:19:50 +00:00
Michael 422d4108ae feat: implement admin AI chat interface with markdown support and token cost tracking 2026-04-29 11:44:17 +02:00
Michael 26ba285c0e feat: implement bulk PostgreSQL client for FF Nancy data retrieval and configure local environment permissions 2026-04-24 12:09:17 +02:00
Michael 0ad403651d feat: implement pg-ff-client for direct SQL bulk data retrieval and add .claude permission settings 2026-04-24 11:57:26 +02:00
Michael a1579de6d7 feat: implement direct PostgreSQL bulk queries for FF Nancy data fetching to replace per-article HTTP requests 2026-04-24 11:25:42 +02:00
Michael f448f4f624 feat: implement PostgreSQL FF Nancy client to replace per-article HTTP API calls with bulk SQL queries 2026-04-24 11:10:19 +02:00
Michael df1d059952 feat: implement API clients for Nancy FF data sync and add local configuration settings 2026-04-24 10:43:32 +02:00
Michael a06d7cd7e8 feat: create API client for FF Nancy REST API to synchronize supplier, article, and movement data 2026-04-24 10:22:05 +02:00
Michael 5a3ac3ed7f feat: implement high-performance PostgreSQL bulk fetchers to replace per-article HTTP API calls 2026-04-23 11:26:15 +02:00
Michael 038e87f554 feat: introduce PostgreSQL client module to replace per-article HTTP requests with bulk SQL queries 2026-04-23 11:11:23 +02:00
Michael c7d5bbe3b1 feat: implement PostgreSQL client for bulk data retrieval to replace HTTP per-article requests 2026-04-23 10:56:45 +02:00
Michael b337d1ff10 feat: add pg-ff-client to replace HTTP API calls with direct PostgreSQL bulk queries 2026-04-23 10:51:43 +02:00
Michael 79bc66f4d5 Please provide the list of changes or file diffs so I can generate the commit message for you. 2026-04-23 10:48:09 +02:00
Michael db90ed6e41 feat: implement PostgreSQL FF Nancy client to replace per-article HTTP API calls with bulk SQL queries 2026-04-23 10:43:30 +02:00
Michael 94018fa885 feat: implement bulk PostgreSQL data fetching client to replace per-article HTTP requests 2026-04-23 10:38:47 +02:00
Michael bd8339b27d feat: add pg-ff-client to replace HTTP API calls with bulk PostgreSQL queries 2026-04-23 10:36:37 +02:00
Michael 32c1e06782 feat: implement PostgreSQL-based bulk fetching for article and sales data to replace HTTP API calls 2026-04-23 10:32:29 +02:00