Files
Gestion-des-Marches-RLA/routes/matrice-risque.js
T
Nabil DerouicheandClaude Sonnet 4.6 88a0dbe6d2 feat: RLA API v1.0.0 — API complète + exports + thème McKinsey
- 9 endpoints métier : synthese, alertes, en-service, en-cours,
  par-region, clotures, pilotage-proactif, matrice-risque
- Exports PDF (tous rôles) / XLSX PPTX DOCX (superadmin)
- services/calc.js : helpers normalisés partagés
- services/export-pdf.js : PDF async PDFKit par vue
- Thème McKinsey (#1C2B4B / bleu pétrole / gris sobre)
- Boutons XLSX/DOCX front (superadmin uniquement)
- BASEROW_API_URL → https://baserow.bolbol.tn/api/
- dotenv override: true

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:47:10 +01:00

91 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* GET /api/matrice-risque
* Matrice de risque : probabilité × impact (délai × avancement)
*/
const express = require('express');
const router = express.Router();
const { getMarches } = require('../services/baserow');
const {
isCloture, normalizeMarche,
getDelaiRestant, niveauAlerte, niveauRisque,
parseNum, SEUIL_STANDARD, SEUIL_CRITIQUE_PCT,
DELAI_CRITIQUE, DELAI_ATTENTION,
} = require('../services/calc');
/**
* Score délai : 1 (ok) → 3 (critique)
*/
function scoreDelai(delai) {
if (delai === null) return 1; // inconnu → faible
if (delai <= DELAI_CRITIQUE) return 3;
if (delai <= DELAI_ATTENTION) return 2;
return 1;
}
/**
* Score avancement : 1 (ok) → 3 (dépassé)
*/
function scoreAvancement(tauxPhy) {
const t = parseNum(tauxPhy);
if (t >= SEUIL_CRITIQUE_PCT) return 3;
if (t >= SEUIL_STANDARD) return 2;
return 1;
}
router.get('/', async (req, res) => {
try {
const { region, entrepreneur, nature } = req.query;
const regionFilter = req.regionFilter;
let rows = await getMarches();
rows = rows.filter(r => !isCloture(r));
if (regionFilter) rows = rows.filter(r => r.region === regionFilter);
else if (region) rows = rows.filter(r => r.region === region);
if (entrepreneur) rows = rows.filter(r => String(r.entrepreneur || '').toLowerCase().includes(entrepreneur.toLowerCase()));
if (nature) rows = rows.filter(r => String(r.nature || '').toLowerCase().includes(nature.toLowerCase()));
// Matrice 3×3 : delai (1-3) × avancement (1-3)
const matrice = {};
for (let d = 1; d <= 3; d++) {
for (let a = 1; a <= 3; a++) {
matrice[`${d}_${a}`] = { score_delai: d, score_avancement: a, items: [] };
}
}
const items = rows.map(r => {
const delai = getDelaiRestant(r);
const sd = scoreDelai(delai);
const sa = scoreAvancement(r.taux_phy ?? r.avt_phy);
const risque = niveauRisque(r);
const m = normalizeMarche(r);
return { ...m, score_delai: sd, score_avancement: sa, niveau_risque: risque };
});
// Remplissage matrice
for (const item of items) {
const key = `${item.score_delai}_${item.score_avancement}`;
if (matrice[key]) matrice[key].items.push(item);
}
// Comptage par niveau de risque
const parNiveau = { critique: 0, élevé: 0, moyen: 0, faible: 0 };
for (const item of items) {
const n = item.niveau_risque;
if (parNiveau[n] !== undefined) parNiveau[n]++;
else parNiveau[n] = 1;
}
res.json({
total: items.length,
par_niveau: parNiveau,
matrice, // grille 3×3
items, // liste complète triée par risque décroissant
});
} catch (err) {
res.status(502).json({ error: 'Erreur Baserow', detail: err.message });
}
});
module.exports = router;