#!/usr/bin/env node /** * Script pour analyser les métriques business du radar * et identifier des patterns stratégiques */ const fs = require('fs'); const path = require('path'); // Parser un blip function parseBlip(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!frontMatterMatch) return null; const frontMatter = frontMatterMatch[1]; const body = frontMatterMatch[2]; const metadata = {}; for (const line of frontMatter.split('\n')) { const match = line.match(/^(\w+):\s*(.+)$/); if (match) { const key = match[1]; let value = match[2].trim(); // Parser les valeurs if (value === 'true') value = true; else if (value === 'false') value = false; else if (!isNaN(value) && value !== '') value = Number(value); else if (value.startsWith('[')) { value = value.slice(1, -1).split(',').map(v => v.trim().replace(/['"]/g, '')); } metadata[key] = value; } } return { metadata, body }; } // Analyser tous les blips function analyzeRadar(radarDir) { const files = fs.readdirSync(radarDir).filter(f => f.endsWith('.md')); const blips = []; for (const file of files) { const blip = parseBlip(path.join(radarDir, file)); if (blip) { blips.push({ file, ...blip }); } } return blips; } // Calculer les métriques function calculateMetrics(blips) { const metrics = { total: blips.length, byQuadrant: {}, byRing: {}, totalCostToReplace: 0, totalMaintenanceCost: 0, riskDistribution: { high: 0, medium: 0, low: 0 }, competencyDistribution: { expert: 0, intermediate: 0, beginner: 0 }, skillGapDistribution: { high: 0, medium: 0, low: 0 }, businessImpactDistribution: { high: 0, medium: 0, low: 0 }, differentiationDistribution: { high: 0, medium: 0, low: 0 }, totalTeamCoverage: 0, technologiesByRisk: { high: [], medium: [], low: [] }, technologiesBySkillGap: { high: [], medium: [], low: [] }, criticalTechnologies: [], emergingTechnologies: [] }; for (const blip of blips) { const m = blip.metadata; // Par quadrant if (!metrics.byQuadrant[m.quadrant]) { metrics.byQuadrant[m.quadrant] = 0; } metrics.byQuadrant[m.quadrant]++; // Par ring if (!metrics.byRing[m.ring]) { metrics.byRing[m.ring] = 0; } metrics.byRing[m.ring]++; // Coûts metrics.totalCostToReplace += m.costToReplace || 0; metrics.totalMaintenanceCost += m.maintenanceCost || 0; // Distributions if (m.riskLevel) metrics.riskDistribution[m.riskLevel]++; if (m.competencyLevel) metrics.competencyDistribution[m.competencyLevel]++; if (m.skillGap) metrics.skillGapDistribution[m.skillGap]++; if (m.businessImpact) metrics.businessImpactDistribution[m.businessImpact]++; if (m.differentiation) metrics.differentiationDistribution[m.differentiation]++; // Coverage metrics.totalTeamCoverage += m.teamCoverage || 0; // Technologies à risque if (m.riskLevel === 'high') { metrics.technologiesByRisk.high.push({ name: m.title, quadrant: m.quadrant, ring: m.ring, riskLevel: m.riskLevel }); } // Technologies avec gap de compétences if (m.skillGap === 'high') { metrics.technologiesBySkillGap.high.push({ name: m.title, quadrant: m.quadrant, ring: m.ring, teamCoverage: m.teamCoverage, competencyLevel: m.competencyLevel }); } // Technologies critiques (adopt + high impact) - anciennement "core" if (m.ring === 'adopt' && m.businessImpact === 'high') { metrics.criticalTechnologies.push({ name: m.title, quadrant: m.quadrant, riskLevel: m.riskLevel, skillGap: m.skillGap, teamCoverage: m.teamCoverage }); } // Technologies émergentes if (m.quadrant === 'technologies-emergentes') { metrics.emergingTechnologies.push({ name: m.title, ring: m.ring, businessImpact: m.businessImpact, differentiation: m.differentiation }); } } return metrics; } // Identifier les patterns function identifyPatterns(blips, metrics) { const patterns = { criticalNonDifferentiating: [], singleVendorDependencies: [], obsoleteTechnologies: [], innovationOpportunities: [], skillGaps: [] }; for (const blip of blips) { const m = blip.metadata; // Technologies critiques non différenciantes (commodité critique) - anciennement "core" if (m.ring === 'adopt' && m.differentiation === 'low') { patterns.criticalNonDifferentiating.push({ name: m.title, quadrant: m.quadrant, costToReplace: m.costToReplace, maintenanceCost: m.maintenanceCost }); } // Technologies obsolètes (legacy) if (m.ring === 'legacy') { patterns.obsoleteTechnologies.push({ name: m.title, riskLevel: m.riskLevel, costToReplace: m.costToReplace }); } // Opportunités d'innovation (émergentes + high differentiation) if (m.quadrant === 'technologies-emergentes' && m.differentiation === 'high') { patterns.innovationOpportunities.push({ name: m.title, ring: m.ring, businessImpact: m.businessImpact }); } // Gaps de compétences critiques - anciennement "core", maintenant "adopt" if (m.skillGap === 'high' && (m.ring === 'adopt' || m.businessImpact === 'high')) { patterns.skillGaps.push({ name: m.title, ring: m.ring, businessImpact: m.businessImpact, teamCoverage: m.teamCoverage, competencyLevel: m.competencyLevel }); } } return patterns; } // Générer le rapport function generateReport(metrics, patterns) { const report = `# Analyse Stratégique - Radar Business Duniter/Ğ1 Date: ${new Date().toLocaleDateString('fr-FR')} ## Vue d'ensemble - **Total de technologies analysées** : ${metrics.total} - **Coût total de remplacement** : ${metrics.totalCostToReplace.toLocaleString('fr-FR')}€ - **Coût total de maintenance annuel** : ${metrics.totalMaintenanceCost.toLocaleString('fr-FR')}€ - **Couverture moyenne de l'équipe** : ${(metrics.totalTeamCoverage / metrics.total).toFixed(1)} personnes par technologie ## Répartition par Quadrant ${Object.entries(metrics.byQuadrant).map(([q, count]) => `- **${q}** : ${count} technologies`).join('\n')} ## Répartition par Ring ${Object.entries(metrics.byRing).map(([r, count]) => `- **${r}** : ${count} technologies`).join('\n')} ## Distribution des Risques - **Risque élevé** : ${metrics.riskDistribution.high} technologies - **Risque modéré** : ${metrics.riskDistribution.medium} technologies - **Risque faible** : ${metrics.riskDistribution.low} technologies ## Distribution des Compétences - **Expert** : ${metrics.competencyDistribution.expert} technologies - **Intermédiaire** : ${metrics.competencyDistribution.intermediate} technologies - **Débutant** : ${metrics.competencyDistribution.beginner} technologies ## Distribution des Gaps de Compétences - **Gap élevé** : ${metrics.skillGapDistribution.high} technologies - **Gap modéré** : ${metrics.skillGapDistribution.medium} technologies - **Gap faible** : ${metrics.skillGapDistribution.low} technologies ## Technologies à Risque Élevé ${metrics.technologiesByRisk.high.length > 0 ? metrics.technologiesByRisk.high.map(t => `- **${t.name}** (${t.quadrant}, ${t.ring})`).join('\n') : 'Aucune technologie à risque élevé identifiée.'} ## Technologies avec Gap de Compétences Élevé ${metrics.technologiesBySkillGap.high.length > 0 ? metrics.technologiesBySkillGap.high.map(t => `- **${t.name}** (${t.teamCoverage} personne(s), niveau: ${t.competencyLevel})`).join('\n') : 'Aucun gap de compétences élevé identifié.'} ## Technologies Critiques ${metrics.criticalTechnologies.length > 0 ? metrics.criticalTechnologies.map(t => `- **${t.name}** (risque: ${t.riskLevel}, gap: ${t.skillGap}, couverture: ${t.teamCoverage})`).join('\n') : 'Aucune technologie critique identifiée.'} ## Technologies Émergentes ${metrics.emergingTechnologies.length > 0 ? metrics.emergingTechnologies.map(t => `- **${t.name}** (impact: ${t.businessImpact}, différenciation: ${t.differentiation})`).join('\n') : 'Aucune technologie émergente identifiée.'} ## Patterns Identifiés ### Technologies Critiques Non Différenciantes ${patterns.criticalNonDifferentiating.length > 0 ? patterns.criticalNonDifferentiating.map(t => `- **${t.name}** (coût remplacement: ${t.costToReplace}€, maintenance: ${t.maintenanceCost}€/an)`).join('\n') : 'Aucune technologie critique non différenciante identifiée.'} ### Technologies Obsolètes ${patterns.obsoleteTechnologies.length > 0 ? patterns.obsoleteTechnologies.map(t => `- **${t.name}** (risque: ${t.riskLevel}, coût remplacement: ${t.costToReplace}€)`).join('\n') : 'Aucune technologie obsolète identifiée.'} ### Opportunités d'Innovation ${patterns.innovationOpportunities.length > 0 ? patterns.innovationOpportunities.map(t => `- **${t.name}** (ring: ${t.ring}, impact: ${t.businessImpact})`).join('\n') : 'Aucune opportunité d\'innovation identifiée.'} ### Gaps de Compétences Critiques ${patterns.skillGaps.length > 0 ? patterns.skillGaps.map(t => `- **${t.name}** (ring: ${t.ring}, impact: ${t.businessImpact}, couverture: ${t.teamCoverage}, niveau: ${t.competencyLevel})`).join('\n') : 'Aucun gap de compétences critique identifié.'} ## Recommandations Stratégiques ### Priorité 1 : Gérer les Risques Critiques ${patterns.skillGaps.length > 0 ? `- **Formation et recrutement** : Investir dans la formation ou le recrutement pour les technologies suivantes : ${patterns.skillGaps.map(t => ` - ${t.name} (${t.teamCoverage} personne(s), niveau ${t.competencyLevel})`).join('\n')}` : '- Aucune action urgente requise.'} ### Priorité 2 : Optimiser les Coûts ${patterns.criticalNonDifferentiating.length > 0 ? `- **Optimisation des commodités** : Réduire les coûts de maintenance pour : ${patterns.criticalNonDifferentiating.map(t => ` - ${t.name} (${t.maintenanceCost}€/an)`).join('\n')}` : '- Aucune optimisation majeure identifiée.'} ### Priorité 3 : Planifier les Migrations ${patterns.obsoleteTechnologies.length > 0 ? `- **Plan de migration** : Planifier le remplacement de : ${patterns.obsoleteTechnologies.map(t => ` - ${t.name} (coût estimé: ${t.costToReplace}€)`).join('\n')}` : '- Aucune migration urgente requise.'} ### Priorité 4 : Investir dans l'Innovation ${patterns.innovationOpportunities.length > 0 ? `- **Technologies émergentes** : Évaluer l\'adoption de : ${patterns.innovationOpportunities.map(t => ` - ${t.name} (ring: ${t.ring})`).join('\n')}` : '- Aucune opportunité d\'innovation identifiée.'} ## Matrice Risques/Opportunités ### Zone Critique (Risque élevé + Impact élevé) ${metrics.criticalTechnologies.filter(t => t.riskLevel === 'high').length > 0 ? metrics.criticalTechnologies.filter(t => t.riskLevel === 'high').map(t => `- **${t.name}** : Action immédiate requise`).join('\n') : 'Aucune technologie en zone critique.'} ### Zone d'Opportunité (Faible risque + Différenciation élevée) ${metrics.emergingTechnologies.filter(t => t.differentiation === 'high').length > 0 ? metrics.emergingTechnologies.filter(t => t.differentiation === 'high').map(t => `- **${t.name}** : Opportunité d'investissement`).join('\n') : 'Aucune opportunité majeure identifiée.'} `; return report; } // Main function main() { const radarDir = path.join(__dirname, '../radar-business/2025-01-15'); const outputFile = path.join(__dirname, '../data/analyse-strategique.md'); if (!fs.existsSync(radarDir)) { console.error(`Répertoire non trouvé: ${radarDir}`); process.exit(1); } console.log('Analyse du radar business...'); const blips = analyzeRadar(radarDir); console.log(`${blips.length} blips analysés`); const metrics = calculateMetrics(blips); const patterns = identifyPatterns(blips, metrics); const report = generateReport(metrics, patterns); fs.writeFileSync(outputFile, report, 'utf-8'); console.log(`Rapport généré: ${outputFile}`); } if (require.main === module) { // Vérifier si js-yaml est disponible (optionnel) try { require('js-yaml'); } catch (e) { // Pas grave, on n'en a pas besoin pour ce script } main(); } module.exports = { analyzeRadar, calculateMetrics, identifyPatterns, generateReport };