#!/usr/bin/env node /** * Script pour extraire les technologies de technologies-duniter.md * et générer les blips pour le radar business */ const fs = require('fs'); const path = require('path'); // Mapping des compétences de l'équipe const teamSkills = { 'poka': ['Flutter', 'Dart', 'ProxMox', 'bash', 'Python', 'infrastructure'], 'ManUtopiK': ['VueJS', 'Nuxt.js', 'JavaScript', 'TypeScript', 'CMS', 'web'], 'aya': ['Linux', 'glusterfs', 'cephfs', 'ipfs', 'infrastructure', 'systèmes distribués'], 'Eloïs': ['Rust', 'blockchain', 'Substrate', 'migration'], 'Fred': ['IPFS', 'Secure ScuttleButt', 'Nostr', 'TiddlyWiki', 'développement'], 'Vivien': ['Cesium', 'Godot'], '1000i100': ['Serverless', 'GitLab', 'CI/CD', 'Docker', 'web'], 'tuxmain': ['cryptographie', 'chiffrage', 'math', 'électronique'], 'boris': ['UX', 'UI', 'Figma', 'LLM', 'JavaScript', 'TypeScript', 'APIs', 'Vis.js'], 'Syoul': ['bidouille', 'résilience', 'domotique'], 'Hugo': ['financement', 'rédaction', 'gestion'], 'Yvv': ['gestion', 'médiathèque', 'wiki'] }; // Mapping technologies -> compétences de l'équipe function findTeamCoverage(techName, techKeywords) { const coverage = new Set(); const techLower = techName.toLowerCase(); const keywords = techKeywords.map(k => k.toLowerCase()); for (const [member, skills] of Object.entries(teamSkills)) { const memberSkills = skills.map(s => s.toLowerCase()); // Vérifier si le nom de la technologie correspond if (memberSkills.some(skill => techLower.includes(skill) || skill.includes(techLower))) { coverage.add(member); } // Vérifier les mots-clés for (const keyword of keywords) { if (memberSkills.some(skill => keyword.includes(skill) || skill.includes(keyword))) { coverage.add(member); } } } return { count: coverage.size, members: Array.from(coverage), level: coverage.size >= 3 ? 'expert' : coverage.size >= 2 ? 'intermediate' : 'beginner', gap: coverage.size === 0 ? 'high' : coverage.size === 1 ? 'high' : coverage.size === 2 ? 'medium' : 'low' }; } // Classification par quadrant et ring (à affiner manuellement) function classifyTechnology(techName, category) { const name = techName.toLowerCase(); // Technologies différenciantes (core/strategic) if (name.includes('rust') || name.includes('substrate') || name.includes('blockchain')) { return { quadrant: 'technologies-differentiantes', ring: 'core', businessImpact: 'high', differentiation: 'high', riskLevel: 'medium' }; } // Technologies de commodité (support) if (name.includes('docker') || name.includes('postgresql') || name.includes('linux')) { return { quadrant: 'technologies-commodite', ring: 'support', businessImpact: 'medium', differentiation: 'low', riskLevel: 'low' }; } // Technologies émergentes (strategic/assess) if (name.includes('ipfs') || name.includes('nostr') || name.includes('serverless')) { return { quadrant: 'technologies-emergentes', ring: 'strategic', businessImpact: 'medium', differentiation: 'high', riskLevel: 'medium' }; } // Par défaut return { quadrant: 'technologies-commodite', ring: 'support', businessImpact: 'medium', differentiation: 'medium', riskLevel: 'medium' }; } // Générer un blip function generateBlip(tech, category, description) { const classification = classifyTechnology(tech.name, category); const coverage = findTeamCoverage(tech.name, tech.keywords || []); const blip = `--- title: "${tech.name}" ring: ${classification.ring} quadrant: ${classification.quadrant} tags: [${(tech.tags || []).join(', ')}] businessImpact: ${classification.businessImpact} costToReplace: ${tech.costToReplace || 0} revenueImpact: ${tech.revenueImpact || 'indirect'} riskLevel: ${classification.riskLevel} competencyLevel: ${coverage.level} maintenanceCost: ${tech.maintenanceCost || 0} differentiation: ${classification.differentiation} teamCoverage: ${coverage.count} skillGap: ${coverage.gap} --- ${description || tech.description || `Description de ${tech.name}.`} ## Impact Business ${tech.businessImpact || 'À compléter'} ## Coûts - Coût de remplacement : ${tech.costToReplace || 0}€ - Coût de maintenance annuel : ${tech.maintenanceCost || 0}€ ## Compétences - Nombre de personnes maîtrisant : ${coverage.count} - Membres de l'équipe : ${coverage.members.join(', ') || 'Aucun'} - Niveau moyen : ${coverage.level} - Risque de compétence manquante : ${coverage.gap} ## Recommandations ${tech.recommendations || 'À compléter avec des recommandations stratégiques.'} `; return blip; } // Parser le fichier technologies-duniter.md function parseTechnologiesFile(filePath) { const content = fs.readFileSync(filePath, 'utf-8'); const technologies = []; // Liste des technologies principales à extraire const techList = [ { name: 'Rust', keywords: ['Rust', 'blockchain', 'Substrate'], category: 'Langages' }, { name: 'Python', keywords: ['Python', 'CLI'], category: 'Langages' }, { name: 'JavaScript/TypeScript', keywords: ['JavaScript', 'TypeScript', 'web'], category: 'Langages' }, { name: 'Dart', keywords: ['Dart', 'Flutter'], category: 'Langages' }, { name: 'Substrate Framework', keywords: ['Substrate', 'Rust', 'blockchain'], category: 'Frameworks' }, { name: 'Nuxt.js', keywords: ['Nuxt', 'Vue', 'SSR'], category: 'Frameworks' }, { name: 'Vue.js', keywords: ['Vue', 'JavaScript'], category: 'Frameworks' }, { name: 'Flutter', keywords: ['Flutter', 'Dart'], category: 'Frameworks' }, { name: 'NetlifyCMS', keywords: ['CMS', 'Git'], category: 'CMS' }, { name: 'WordUp CMS', keywords: ['CMS'], category: 'CMS' }, { name: 'Docker', keywords: ['Docker', 'conteneurisation'], category: 'Infrastructure' }, { name: 'Kubernetes', keywords: ['Kubernetes', 'orchestration'], category: 'Infrastructure' }, { name: 'PostgreSQL', keywords: ['PostgreSQL', 'base de données'], category: 'Infrastructure' }, { name: 'ProxMox', keywords: ['ProxMox', 'virtualisation'], category: 'Infrastructure' }, { name: 'Linux', keywords: ['Linux', 'système'], category: 'Infrastructure' }, { name: 'IPFS', keywords: ['IPFS', 'distribué'], category: 'Technologies distribuées' }, { name: 'Nostr', keywords: ['Nostr', 'protocole'], category: 'Technologies distribuées' }, { name: 'GitLab CI/CD', keywords: ['GitLab', 'CI/CD'], category: 'DevOps' }, { name: 'Serverless', keywords: ['Serverless'], category: 'Architecture' }, { name: 'Squid', keywords: ['Squid', 'indexer', 'GraphQL'], category: 'Outils' }, { name: 'Cryptographie', keywords: ['cryptographie', 'chiffrage'], category: 'Sécurité' }, { name: 'Bash', keywords: ['bash', 'scripting'], category: 'Outils' } ]; // Pour chaque technologie, créer un blip for (const tech of techList) { // Extraire la description depuis le fichier si disponible let description = ''; const techRegex = new RegExp(`#### ${tech.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=#### |## |$)`, 'i'); const match = content.match(techRegex); if (match) { description = match[0].split('\n').slice(1).join('\n').trim(); // Limiter à 10 lignes description = description.split('\n').slice(0, 10).join('\n'); } else { description = `Technologie ${tech.name} utilisée dans l'écosystème Duniter/Ğ1.`; } technologies.push({ name: tech.name, category: tech.category, description: description, keywords: tech.keywords, tags: tech.keywords.slice(0, 3) }); } return technologies; } // Main function main() { const techFile = path.join(__dirname, '../docs/technologies-duniter.md'); const outputDir = path.join(__dirname, '../radar-business/2025-01-15'); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } console.log('Extraction des technologies...'); const technologies = parseTechnologiesFile(techFile); console.log(`Trouvé ${technologies.length} technologies`); for (const tech of technologies) { const blip = generateBlip(tech, tech.category, tech.description); const filename = tech.name.toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-|-$/g, '') + '.md'; const filePath = path.join(outputDir, filename); fs.writeFileSync(filePath, blip, 'utf-8'); console.log(`Généré: ${filename}`); } console.log(`\n${technologies.length} blips générés dans ${outputDir}`); } if (require.main === module) { main(); } module.exports = { parseTechnologiesFile, generateBlip, findTeamCoverage };