mirror of
https://github.com/R0m1k3/CollectFlow.git
synced 2026-10-20 21:48:33 +02:00
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>
This commit is contained in:
1 parent
0b9846f55d
commit
bd9bc78f4c
8 files changed
+148
-12
No files matched your search
+29
-3
@@ -48,6 +48,10 @@ export interface QlikConfig {
|
||||
measCaId: string; // master measure "CA N"
|
||||
measQteId: string; // master measure "Quantite N"
|
||||
measNbMagId: string; // master measure "Magasin Ventes Nb N"
|
||||
measCaMagId: string; // master measure "CA par Magasin N"
|
||||
measCouvQteId: string; // master measure "Couverture de stock Quantite N"
|
||||
measMargePctId: string; // master measure "Marge % N"
|
||||
measRuptPctId: string; // master measure "Stock Rupture % N"
|
||||
tlsInsecure: boolean;
|
||||
timeoutMs: number;
|
||||
}
|
||||
@@ -67,6 +71,12 @@ export function getQlikConfig(): QlikConfig {
|
||||
measCaId: process.env.QLIK_MEAS_CA_ID ?? "JhqJ",
|
||||
measQteId: process.env.QLIK_MEAS_QTE_ID ?? "41516861-5997-4635-8187-3643a2bde422",
|
||||
measNbMagId: process.env.QLIK_MEAS_NBMAG_ID ?? "yNBLjc",
|
||||
// Master GUIDs de l'app "Magasins Vision Consolidée" (9872ee6e). Si l'engine
|
||||
// les refuse (qId court attendu), override via env après discovery.
|
||||
measCaMagId: process.env.QLIK_MEAS_CAMAG_ID ?? "16778c00-a4e0-41c7-ab1c-179810d78172",
|
||||
measCouvQteId: process.env.QLIK_MEAS_COUVQTE_ID ?? "dbdf957d-a380-495f-bdc4-3f7b6688f3b5",
|
||||
measMargePctId: process.env.QLIK_MEAS_MARGEPCT_ID ?? "ea1e8feb-49ba-4eb3-8db6-72dcfb4c1222",
|
||||
measRuptPctId: process.env.QLIK_MEAS_RUPTPCT_ID ?? "a60a5c1a-5966-484c-9771-dbe55bd83a3c",
|
||||
tlsInsecure: (process.env.QLIK_TLS_INSECURE ?? "true") === "true",
|
||||
timeoutMs: Number(process.env.QLIK_TIMEOUT_MS ?? "60000"),
|
||||
};
|
||||
@@ -77,6 +87,14 @@ export interface NetworkMetric {
|
||||
caReseau: number;
|
||||
qteReseau: number;
|
||||
nbMagasinsReseau: number;
|
||||
/** CA moyen par magasin (productivité normalisée présence) */
|
||||
caParMagasinReseau: number;
|
||||
/** Couverture de stock en quantité (rotation : bas = tourne vite) */
|
||||
couvertureStockReseau: number;
|
||||
/** Taux de marge réseau (ratio brut Qlik, ex 0.32 = 32%) */
|
||||
margePctReseau: number;
|
||||
/** Taux de rupture réseau (ratio brut Qlik, ex 0.05 = 5%) */
|
||||
rupturePctReseau: number;
|
||||
periode?: string;
|
||||
}
|
||||
|
||||
@@ -229,8 +247,12 @@ function hyperCubeDef(cfg: QlikConfig, height: number) {
|
||||
{ qLibraryId: cfg.measCaId },
|
||||
{ qLibraryId: cfg.measQteId },
|
||||
{ qLibraryId: cfg.measNbMagId },
|
||||
{ qLibraryId: cfg.measCaMagId },
|
||||
{ qLibraryId: cfg.measCouvQteId },
|
||||
{ qLibraryId: cfg.measMargePctId },
|
||||
{ qLibraryId: cfg.measRuptPctId },
|
||||
],
|
||||
qInitialDataFetch: [{ qTop: 0, qLeft: 0, qWidth: 4, qHeight: height }],
|
||||
qInitialDataFetch: [{ qTop: 0, qLeft: 0, qWidth: 8, qHeight: height }],
|
||||
qSuppressZero: false,
|
||||
qSuppressMissing: true,
|
||||
},
|
||||
@@ -253,7 +275,7 @@ export async function fetchNetworkMetrics(
|
||||
const openRes = await conn.rpc("OpenDoc", { qDocName: cfg.appNetwork });
|
||||
const docHandle = (((openRes.qReturn as Record<string, unknown>) ?? {}).qHandle as number) ?? 1;
|
||||
|
||||
const pageHeight = 2500; // 4 colonnes * 2500 = 10000 cellules max
|
||||
const pageHeight = 1250; // 8 colonnes * 1250 = 10000 cellules max
|
||||
const createRes = await conn.rpc("CreateSessionObject", { qProp: hyperCubeDef(cfg, pageHeight) }, docHandle);
|
||||
const objHandle = (((createRes.qReturn as Record<string, unknown>) ?? {}).qHandle as number);
|
||||
|
||||
@@ -261,7 +283,7 @@ export async function fetchNetworkMetrics(
|
||||
for (;;) {
|
||||
const dataRes = await conn.rpc(
|
||||
"GetHyperCubeData",
|
||||
{ qPath: "/qHyperCubeDef", qPages: [{ qTop: top, qLeft: 0, qWidth: 4, qHeight: pageHeight }] },
|
||||
{ qPath: "/qHyperCubeDef", qPages: [{ qTop: top, qLeft: 0, qWidth: 8, qHeight: pageHeight }] },
|
||||
objHandle,
|
||||
);
|
||||
const pages = (dataRes.qDataPages as Array<{ qMatrix?: Matrix }>) ?? [];
|
||||
@@ -277,6 +299,10 @@ export async function fetchNetworkMetrics(
|
||||
caReseau: Number(row[1]?.qNum ?? 0) || 0,
|
||||
qteReseau: Number(row[2]?.qNum ?? 0) || 0,
|
||||
nbMagasinsReseau: Number(row[3]?.qNum ?? 0) || 0,
|
||||
caParMagasinReseau: Number(row[4]?.qNum ?? 0) || 0,
|
||||
couvertureStockReseau: Number(row[5]?.qNum ?? 0) || 0,
|
||||
margePctReseau: Number(row[6]?.qNum ?? 0) || 0,
|
||||
rupturePctReseau: Number(row[7]?.qNum ?? 0) || 0,
|
||||
});
|
||||
}
|
||||
if (matrix.length < pageHeight) break;
|
||||
|
||||
Reference in new issue
Block a user