#!/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'); // Charger les compétences de l'équipe depuis les fichiers individuels function loadTeamSkills() { const teamDir = path.join(__dirname, '../docs/data/team'); const teamSkills = {}; if (!fs.existsSync(teamDir)) { console.warn(`⚠️ Dossier ${teamDir} introuvable, utilisation du mapping par défaut`); return { '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', 'ThreeFold'], 'Eloïs': ['Rust', 'blockchain', 'Substrate', 'migration'], 'Fred': ['IPFS', 'Secure ScuttleButt', 'Nostr', 'TiddlyWiki', 'développement', 'ThreeFold'], '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', 'infrastructure décentralisée'], 'Hugo': ['financement', 'rédaction', 'gestion'], 'Yvv': ['gestion', 'médiathèque', 'wiki'] }; } const files = fs.readdirSync(teamDir).filter(f => f.endsWith('.md')); for (const file of files) { const filePath = path.join(teamDir, file); const content = fs.readFileSync(filePath, 'utf-8'); // Extraire le front matter YAML const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/); if (!yamlMatch) continue; const yamlContent = yamlMatch[1]; const nameMatch = yamlContent.match(/^name:\s*["']?([^"'\n]+)["']?/m); if (!nameMatch) continue; const memberName = nameMatch[1]; const skills = []; // Extraire les compétences depuis la section skills // Trouver la section skills et extraire toutes les lignes " - name:" const lines = yamlContent.split('\n'); let inSkillsSection = false; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Détecter le début de la section skills if (line.match(/^skills:\s*$/)) { inSkillsSection = true; continue; } // Si on est dans la section skills et qu'on trouve une nouvelle section (ligne qui commence par un mot-clé sans indentation) if (inSkillsSection && line.match(/^\w+:/) && !line.match(/^\s+/)) { inSkillsSection = false; continue; } // Extraire les noms de compétences dans la section skills if (inSkillsSection) { const nameMatch = line.match(/^\s+-\s+name:\s*["']?([^"'\n]+)["']?/); if (nameMatch) { skills.push(nameMatch[1]); } } } if (skills.length > 0) { teamSkills[memberName] = skills; } } return teamSkills; } // Mapping des compétences de l'équipe (chargé dynamiquement) const teamSkills = loadTeamSkills(); // 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') || name.includes('threefold') || name.includes('zero os') || name.includes('mycelium') || name.includes('aibox') || name.includes('3node') || name.includes('d3.js') || name.includes('echarts') || name.includes('grafana') || name.includes('leaflet') || name.includes('cytoscape')) { return { quadrant: 'technologies-emergentes', ring: 'strategic', businessImpact: 'high', 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' }, // Technologies ThreeFold { name: 'Zero OS', keywords: ['Zero OS', 'bare metal', 'cloud décentralisé'], category: 'Infrastructure décentralisée' }, { name: 'ThreeFold Grid', keywords: ['ThreeFold', 'Grid', 'infrastructure décentralisée'], category: 'Infrastructure décentralisée' }, { name: '3Node', keywords: ['3Node', 'nœuds', 'serveurs'], category: 'Infrastructure décentralisée' }, { name: 'ThreeFold Compute', keywords: ['ThreeFold', 'Compute', 'edge computing'], category: 'Infrastructure décentralisée' }, { name: 'ThreeFold Data Storage', keywords: ['ThreeFold', 'Storage', 'stockage distribué'], category: 'Infrastructure décentralisée' }, { name: 'Mycelium Network', keywords: ['Mycelium', 'Network', 'réseau overlay'], category: 'Infrastructure décentralisée' }, { name: 'ThreeFold Blockchain', keywords: ['ThreeFold', 'Blockchain'], category: 'Blockchain' }, { name: 'ThreeFold Cloud', keywords: ['ThreeFold', 'Cloud', 'Kubernetes'], category: 'Cloud décentralisé' }, { name: 'AIBox', keywords: ['AIBox', 'IA', 'machine learning'], category: 'IA' }, // Technologies DataViz { name: 'D3.js', keywords: ['D3.js', 'DataViz', 'JavaScript'], category: 'Data Visualization' }, { name: 'ECharts', keywords: ['ECharts', 'DataViz', 'Apache'], category: 'Data Visualization' }, { name: 'Grafana', keywords: ['Grafana', 'Monitoring', 'Dashboard'], category: 'Data Visualization' }, { name: 'Leaflet', keywords: ['Leaflet', 'Cartographie', 'Map'], category: 'Data Visualization' }, { name: 'Cytoscape.js', keywords: ['Cytoscape', 'Graphes', 'Réseaux'], category: 'Data Visualization' } ]; // 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/data/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 };