feat: ajouter radar stratégique business avec analyse des technologies et compétences
- Création de la structure radar-business/ avec configuration business - Génération de 22 blips de technologies avec métadonnées business - Scripts d'extraction et d'analyse des technologies - Analyse stratégique avec identification de patterns - Stratégie d'évolution technique avec roadmap 3 ans - Documentation complète du radar business - Analyse des compétences de l'équipe depuis profil-team.md
This commit is contained in:
392
scripts/analyze-business-metrics.js
Executable file
392
scripts/analyze-business-metrics.js
Executable file
@@ -0,0 +1,392 @@
|
||||
#!/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 (core + high impact)
|
||||
if (m.ring === 'core' && 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)
|
||||
if (m.ring === 'core' && 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
|
||||
if (m.skillGap === 'high' && (m.ring === 'core' || 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, '../docs/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 };
|
||||
|
||||
241
scripts/extract-technologies.js
Executable file
241
scripts/extract-technologies.js
Executable file
@@ -0,0 +1,241 @@
|
||||
#!/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 };
|
||||
|
||||
Reference in New Issue
Block a user