Spaces:
Sleeping
Sleeping
| # 📚 Documentation Technique - CV Generator | |
| ## 🎯 Vue d'ensemble du Projet | |
| **CV Generator** est une application web moderne permettant de générer des CV professionnels au format PDF, optimisés pour les systèmes ATS (Applicant Tracking System). L'application utilise l'intelligence artificielle pour améliorer le contenu du CV. | |
| --- | |
| ## 🏗️ Architecture du Projet | |
| ### Structure des Dossiers | |
| ``` | |
| CVGENERATOR/ | |
| ├── backend/ | |
| │ ├── app/ | |
| │ │ ├── models/ # Modèles de données Pydantic | |
| │ │ │ └── cv_models.py | |
| │ │ ├── services/ # Logique métier | |
| │ │ │ ├── gemini_service.py | |
| │ │ │ └── document_service.py | |
| │ │ ├── routes/ # Points d'entrée API | |
| │ │ │ └── cv_routes.py | |
| │ │ └── main.py # Configuration FastAPI | |
| │ ├── venv/ # Environnement virtuel Python | |
| │ └── requirements.txt | |
| ├── frontend/ | |
| │ ├── index.html # Interface utilisateur | |
| │ ├── styles.css # Styles responsive | |
| │ └── script.js # Logique frontend | |
| ├── app.py # Point d'entrée Hugging Face | |
| ├── Dockerfile # Configuration Docker | |
| ├── requirements.txt # Dépendances production | |
| └── README.md | |
| ``` | |
| --- | |
| ## 🔧 Principes SOLID Appliqués | |
| Notre architecture respecte les 5 principes SOLID pour garantir un code maintenable et évolutif. | |
| ### 1. **S** - Single Responsibility Principle (SRP) | |
| Chaque classe a une seule responsabilité : | |
| | Classe | Responsabilité Unique | | |
| |--------|----------------------| | |
| | `GeminiService` | Communication avec l'API Gemini AI | | |
| | `DocxGenerator` | Génération de documents DOCX | | |
| | `PDFConverter` | Conversion DOCX vers PDF | | |
| | `CVGeneratorService` | Orchestration du processus de génération | | |
| ```python | |
| class DocxGenerator(DocumentGeneratorInterface): | |
| def generate(self, cv_data: CVData, output_path: str) -> str: | |
| # Génère uniquement le document DOCX | |
| pass | |
| ``` | |
| ### 2. **O** - Open/Closed Principle (OCP) | |
| Les classes sont ouvertes à l'extension mais fermées à la modification : | |
| ```python | |
| # Interface abstraite - peut être étendue sans modifier l'existant | |
| class DocumentGeneratorInterface(ABC): | |
| @abstractmethod | |
| def generate(self, cv_data: CVData, output_path: str) -> str: | |
| pass | |
| # Extension possible sans modifier l'interface | |
| class DocxGenerator(DocumentGeneratorInterface): | |
| pass | |
| class LatexGenerator(DocumentGeneratorInterface): # Futur | |
| pass | |
| ``` | |
| ### 3. **L** - Liskov Substitution Principle (LSP) | |
| Les sous-classes peuvent remplacer leurs classes parentes : | |
| ```python | |
| # N'importe quelle implémentation de DocumentGeneratorInterface | |
| # peut être utilisée de manière interchangeable | |
| def create_cv(generator: DocumentGeneratorInterface, data: CVData): | |
| return generator.generate(data, "output.docx") | |
| # Fonctionne avec DocxGenerator ou toute autre implémentation | |
| create_cv(DocxGenerator(), cv_data) | |
| ``` | |
| ### 4. **I** - Interface Segregation Principle (ISP) | |
| Des interfaces spécifiques plutôt qu'une interface monolithique : | |
| ```python | |
| # Interfaces séparées pour chaque fonctionnalité | |
| class DocumentGeneratorInterface(ABC): | |
| @abstractmethod | |
| def generate(self, cv_data, output_path) -> str: pass | |
| class AIServiceInterface(ABC): | |
| @abstractmethod | |
| def generate_summary(self, data) -> str: pass | |
| @abstractmethod | |
| def enhance_experience(self, description) -> str: pass | |
| ``` | |
| ### 5. **D** - Dependency Inversion Principle (DIP) | |
| Les modules de haut niveau dépendent d'abstractions : | |
| ```python | |
| # Le service dépend d'abstractions, pas d'implémentations concrètes | |
| class CVGeneratorService: | |
| def __init__(self, | |
| docx_generator: DocxGenerator, # Injection | |
| pdf_converter: PDFConverter): # de dépendances | |
| self.docx_generator = docx_generator | |
| self.pdf_converter = pdf_converter | |
| ``` | |
| --- | |
| ## 🔄 Flux de Données | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ FRONTEND │ | |
| │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ | |
| │ │ Étape 1 │───▶│ Étape 2 │───▶│ Étape 3 │───▶│ Étape 4 │ │ | |
| │ │ Infos │ │Formation │ │Expérience│ │Compétences│ │ | |
| │ │Personnels│ │ │ │ │ │& Langues │ │ | |
| │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ | |
| │ │ │ | |
| │ ┌─────────▼──────┐ │ | |
| │ │ Étape 5 │ │ | |
| │ │ Aperçu & │ │ | |
| │ │ Génération │ │ | |
| │ └────────┬───────┘ │ | |
| └───────────────────────────────────────────────────────┼─────────┘ | |
| │ | |
| ▼ | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ API FASTAPI │ | |
| │ │ | |
| │ POST /api/cv/generate │ | |
| │ ┌─────────────────────────────────────────────────────────┐ │ | |
| │ │ │ │ | |
| │ │ 1. Validation des données (Pydantic) │ │ | |
| │ │ │ │ │ | |
| │ │ ▼ │ │ | |
| │ │ 2. Génération résumé IA (GeminiService) │ │ | |
| │ │ │ │ │ | |
| │ │ ▼ │ │ | |
| │ │ 3. Création DOCX (DocxGenerator) │ │ | |
| │ │ │ │ │ | |
| │ │ ▼ │ │ | |
| │ │ 4. Conversion PDF (PDFConverter) │ │ | |
| │ │ │ │ │ | |
| │ │ ▼ │ │ | |
| │ │ 5. Retour URLs de téléchargement │ │ | |
| │ │ │ │ | |
| │ └─────────────────────────────────────────────────────────┘ │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| ``` | |
| --- | |
| ## 🤖 Intégration de l'IA (Gemini) | |
| ### Fonctionnalités IA | |
| 1. **Génération automatique de résumé professionnel** | |
| - Analyse des expériences et compétences | |
| - Création d'un profil percutant | |
| 2. **Amélioration des descriptions d'expérience** | |
| - Reformulation professionnelle | |
| - Ajout de verbes d'action | |
| ### Gestion des Erreurs et Fallback | |
| ```python | |
| class GeminiService: | |
| FALLBACK_MODELS = [ | |
| "gemini-2.0-flash", | |
| "gemini-1.5-flash", | |
| "gemini-1.0-pro" | |
| ] | |
| def _call_with_retry(self, prompt: str) -> str: | |
| """Essaie plusieurs modèles en cas d'échec""" | |
| for model_name in self.FALLBACK_MODELS: | |
| try: | |
| model = genai.GenerativeModel(model_name) | |
| response = model.generate_content(prompt) | |
| return response.text | |
| except Exception: | |
| continue | |
| return self._generate_fallback_summary() | |
| ``` | |
| ### Gestion des Quotas | |
| En cas de dépassement de quota API, l'application génère automatiquement un résumé basé sur les données fournies : | |
| ```python | |
| def _generate_fallback_summary(self, cv_data: CVData) -> str: | |
| """Génère un résumé sans IA en cas de quota dépassé""" | |
| skills = ", ".join([s.name for s in cv_data.skills[:5]]) | |
| return f"Professionnel expérimenté avec des compétences en {skills}..." | |
| ``` | |
| --- | |
| ## 📄 Génération de Documents ATS-Friendly | |
| ### Principes de Design ATS | |
| Les systèmes ATS (Applicant Tracking System) analysent automatiquement les CV. Notre générateur respecte ces règles : | |
| | ✅ Bonnes Pratiques | ❌ À Éviter | | |
| |---------------------|-------------| | |
| | Structure linéaire | Colonnes multiples | | |
| | Police standard (Calibri) | Polices décoratives | | |
| | Titres en majuscules | Graphiques/Images | | |
| | Format simple | Tableaux complexes | | |
| | Texte pur | En-têtes/Pieds de page | | |
| ### Structure du CV Généré | |
| ``` | |
| ┌────────────────────────────────────────┐ | |
| │ NOM COMPLET │ ← Centré, 24pt, Gras | |
| │ ✉ email | ☎ téléphone | 📍 ville │ ← Infos contact | |
| │ LinkedIn | Portfolio │ ← Liens | |
| ├────────────────────────────────────────┤ | |
| │ PROFIL PROFESSIONNEL │ ← Section titre | |
| │ ───────────────────────────── │ ← Ligne bleue | |
| │ Résumé généré par IA... │ | |
| ├────────────────────────────────────────┤ | |
| │ EXPÉRIENCE PROFESSIONNELLE │ | |
| │ ───────────────────────────── │ | |
| │ Poste | Jan 2020 - Présent │ ← Titre + dates | |
| │ Entreprise │ ← Italique | |
| │ ▸ Réalisation 1 │ ← Bullet points | |
| │ ▸ Réalisation 2 │ | |
| ├────────────────────────────────────────┤ | |
| │ FORMATION │ | |
| │ ───────────────────────────── │ | |
| │ Diplôme | 2015 - 2018 │ | |
| │ École │ | |
| ├────────────────────────────────────────┤ | |
| │ COMPÉTENCES │ | |
| │ ───────────────────────────── │ | |
| │ Expert: Python • JavaScript • React │ ← Groupées par niveau | |
| │ Intermédiaire: Docker • AWS │ | |
| ├────────────────────────────────────────┤ | |
| │ LANGUES │ | |
| │ ───────────────────────────── │ | |
| │ ▸ Français: Natif │ | |
| │ ▸ Anglais: Courant │ | |
| ├────────────────────────────────────────┤ | |
| │ CENTRES D'INTÉRÊT │ | |
| │ ───────────────────────────── │ | |
| │ Lecture • Sport • Voyages │ ← Sur une ligne | |
| └────────────────────────────────────────┘ | |
| ``` | |
| --- | |
| ## 🎨 Design Frontend Responsive | |
| ### Breakpoints CSS | |
| ```css | |
| /* Desktop */ | |
| @media (min-width: 1200px) { ... } | |
| /* Tablette */ | |
| @media (max-width: 768px) { ... } | |
| /* Mobile */ | |
| @media (max-width: 480px) { ... } | |
| /* Très petit écran */ | |
| @media (max-width: 360px) { ... } | |
| ``` | |
| ### Fonctionnalités Responsive | |
| - **Formulaires adaptatifs** : Colonnes → Lignes sur mobile | |
| - **Navigation tactile** : Boutons agrandis | |
| - **Typographie fluide** : `clamp()` pour tailles de police | |
| - **Labels masqués** : Sur très petits écrans | |
| --- | |
| ## 🚀 Déploiement | |
| ### Architecture de Déploiement (Hugging Face Spaces) | |
| ``` | |
| ┌─────────────────────────────────────────────────────┐ | |
| │ Hugging Face Spaces │ | |
| │ ┌───────────────────────────────────────────────┐ │ | |
| │ │ Docker Container │ │ | |
| │ │ ┌─────────────────────────────────────────┐ │ │ | |
| │ │ │ Python 3.10-slim │ │ │ | |
| │ │ │ ┌───────────┐ ┌─────────────────┐ │ │ │ | |
| │ │ │ │ FastAPI │ │ LibreOffice │ │ │ │ | |
| │ │ │ │ (app.py) │ │ (PDF Conv.) │ │ │ │ | |
| │ │ │ └─────┬─────┘ └─────────────────┘ │ │ │ | |
| │ │ │ │ │ │ │ | |
| │ │ │ ┌─────▼─────┐ │ │ │ | |
| │ │ │ │ Frontend │ │ │ │ | |
| │ │ │ │ (static) │ │ │ │ | |
| │ │ │ └───────────┘ │ │ │ | |
| │ │ └─────────────────────────────────────────┘ │ │ | |
| │ └───────────────────────────────────────────────┘ │ | |
| │ │ │ | |
| │ Port 7860 │ | |
| └────────────────────────┼────────────────────────────┘ | |
| │ | |
| Internet | |
| ``` | |
| ### Variables d'Environnement | |
| | Variable | Description | Requis | | |
| |----------|-------------|--------| | |
| | `GEMINI_API_KEY` | Clé API Google Gemini | ✅ | | |
| | `PORT` | Port du serveur (défaut: 7860) | ❌ | | |
| --- | |
| ## 🧪 Tests et Validation | |
| ### Points de Test API | |
| ```bash | |
| # Health Check | |
| curl http://localhost:8000/api/cv/health | |
| # Génération de CV | |
| curl -X POST http://localhost:8000/api/cv/generate \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "personal_info": {...}, | |
| "education": [...], | |
| "experiences": [...], | |
| "skills": [...], | |
| "languages": [...] | |
| }' | |
| ``` | |
| ### Validation des Données | |
| Toutes les données sont validées par **Pydantic** : | |
| ```python | |
| class PersonalInfo(BaseModel): | |
| full_name: str = Field(..., min_length=2, max_length=100) | |
| email: EmailStr | |
| phone: str | |
| address: str | |
| linkedin: Optional[str] = None | |
| portfolio: Optional[str] = None | |
| summary: Optional[str] = None | |
| ``` | |
| --- | |
| ## 📈 Améliorations Futures | |
| ### Roadmap | |
| - [ ] **Templates multiples** : Différents designs de CV | |
| - [ ] **Export LaTeX** : Pour les CV académiques | |
| - [ ] **Analyse ATS** : Score de compatibilité | |
| - [ ] **Multi-langues** : Interface en plusieurs langues | |
| - [ ] **Stockage cloud** : Sauvegarde des CV | |
| - [ ] **Authentification** : Comptes utilisateurs | |
| --- | |
| ## 👥 Contribution | |
| ### Standards de Code | |
| - **Python** : PEP 8, type hints | |
| - **JavaScript** : ES6+, async/await | |
| - **CSS** : BEM methodology, variables CSS | |
| - **Commits** : Conventional Commits | |
| ### Structure des Commits | |
| ``` | |
| feat: ajout de la génération PDF | |
| fix: correction du bug de conversion | |
| docs: mise à jour de la documentation | |
| style: formatage du code | |
| refactor: restructuration du service Gemini | |
| ``` | |
| --- | |
| ## 📝 Licence | |
| Ce projet est développé dans un cadre éducatif. | |
| --- | |
| *Documentation générée le 31 décembre 2025* | |
| --- | |
| # 🎯 Approche Technique : Génération de CV avec Template et Ajustement Automatique | |
| ## 📋 Vue d'ensemble de l'Approche | |
| Notre système de génération de CV utilise une approche hybride innovante combinant : | |
| 1. **Template Word (docx)** comme base de design | |
| 2. **Remplissage dynamique** avec Jinja2/docxtpl | |
| 3. **Ajustement automatique** des tailles et espacements selon le volume de contenu | |
| Cette approche garantit que **le CV tient toujours sur une seule page**, quelle que soit la quantité d'informations. | |
| --- | |
| ## 🏗️ Architecture de la Solution | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ PROCESSUS DE GÉNÉRATION │ | |
| │ │ | |
| │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ | |
| │ │ Données │───▶│ Calcul │───▶│ Sélection │ │ | |
| │ │ CV (JSON) │ │ Volume │ │ Preset │ │ | |
| │ └──────────────┘ └──────────────┘ └──────────────┘ │ | |
| │ │ │ | |
| │ ▼ │ | |
| │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ | |
| │ │ Template │───▶│ Remplissage │───▶│ Ajustement │ │ | |
| │ │ .docx │ │ Jinja2 │ │ Tailles │ │ | |
| │ └──────────────┘ └──────────────┘ └──────────────┘ │ | |
| │ │ │ | |
| │ ▼ │ | |
| │ ┌──────────────┐ │ | |
| │ │ CV Final │ │ | |
| │ │ (1 page) │ │ | |
| │ └──────────────┘ │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| ``` | |
| --- | |
| ## 🔧 Composants Principaux | |
| ### 1. Template Word (template.docx) | |
| Le template est un fichier Word avec un design 2 colonnes contenant des **placeholders Jinja2** : | |
| ``` | |
| ┌──────────────────┬────────────────────────────────────┐ | |
| │ COLONNE GAUCHE │ COLONNE DROITE │ | |
| │ │ │ | |
| │ CONTACT │ {{ full_name }} │ | |
| │ {{ phone }} │ │ | |
| │ {{ email }} │ PROFIL │ | |
| │ {{ address }} │ {{ summary }} │ | |
| │ │ │ | |
| │ COMPÉTENCES │ FORMATION │ | |
| │ {% for skill │ {% for edu in education %} │ | |
| │ in skills %} │ {{ edu.degree }} │ | |
| │ • {{ skill. │ ({{ edu.period }}) │ | |
| │ display }} │ {% endfor %} │ | |
| │ {% endfor %} │ │ | |
| │ │ EXPÉRIENCES │ | |
| │ LANGUES │ {% for exp in experiences %} │ | |
| │ {% for lang %} │ {{ exp.job_title }} │ | |
| │ • {{ lang. │ {{ exp.company }} │ | |
| │ display }} │ {% endfor %} │ | |
| │ {% endfor %} │ │ | |
| └──────────────────┴────────────────────────────────────┘ | |
| ``` | |
| ### 2. Calcul du Volume de Contenu | |
| L'algorithme calcule un **score de volume** basé sur tous les éléments du CV : | |
| ```python | |
| def _calculate_content_volume(self, cv_data): | |
| volume = 0 | |
| # Expériences : 2 points + lignes de description | |
| for exp in cv_data.experiences: | |
| volume += 2 # Titre + entreprise | |
| if exp.description: | |
| lines = exp.description.split('\n') | |
| volume += len([l for l in lines if l.strip()]) | |
| # Formations : 2 points chacune | |
| for edu in cv_data.education: | |
| volume += 2 | |
| if edu.description: | |
| volume += 1 | |
| # Autres éléments : 1 point chacun | |
| volume += len(cv_data.skills) | |
| volume += len(cv_data.languages) | |
| volume += len(cv_data.hobbies) | |
| # Résumé : 1 point par 100 caractères | |
| if cv_data.personal_info.summary: | |
| volume += len(cv_data.personal_info.summary) // 100 + 1 | |
| return volume | |
| ``` | |
| ### 3. Système de Presets (Paliers) | |
| Selon le volume calculé, un **preset** est sélectionné : | |
| | Preset | Volume | Police Nom | Police Section | Police Normale | Police Petite | | |
| |--------|--------|------------|----------------|----------------|---------------| | |
| | `light` | < 18 | 22pt | 11pt | 10pt | 9pt | | |
| | `medium` | 18-25 | 20pt | 10pt | 9pt | 8pt | | |
| | `dense` | 25-35 | 18pt | 9pt | 8pt | 7pt | | |
| | `very_dense` | > 35 | 16pt | 8pt | 7pt | 6pt | | |
| ### 4. Espacements Adaptatifs | |
| Les espacements entre paragraphes varient également : | |
| | Preset | Espace Avant | Espace Après | Interligne | | |
| |--------|--------------|--------------|------------| | |
| | `light` | 3pt | 1pt | 0.9 | | |
| | `medium` | 2pt | 1pt | 0.9 | | |
| | `dense` | 1pt | 0pt | 0.9 | | |
| | `very_dense` | 0pt | 0pt | 0.9 | | |
| --- | |
| ## 📊 Diagramme du Flux de Données | |
| ``` | |
| ┌─────────────────┐ | |
| │ CVData JSON │ | |
| │ │ | |
| │ • personal_info │ | |
| │ • experiences │ | |
| │ • education │ | |
| │ • skills │ | |
| │ • languages │ | |
| │ • hobbies │ | |
| └────────┬────────┘ | |
| │ | |
| ▼ | |
| ┌──────────────────────────────┐ | |
| │ _calculate_content_volume │ | |
| │ │ | |
| │ Volume = Σ(éléments) │ | |
| │ │ | |
| │ Ex: 3 exp + 2 edu + 5 skills│ | |
| │ + 2 lang + 3 hobbies │ | |
| │ = ~25 points │ | |
| └──────────────┬───────────────┘ | |
| │ | |
| ▼ | |
| ┌──────────────────────────────┐ | |
| │ _get_size_preset │ | |
| │ │ | |
| │ if volume > 35: very_dense │ | |
| │ elif volume > 25: dense │ | |
| │ elif volume > 18: medium │ | |
| │ else: light │ | |
| └──────────────┬───────────────┘ | |
| │ | |
| ▼ | |
| ┌──────────────────────────────┐ | |
| │ _prepare_context │ | |
| │ │ | |
| │ Transforme CVData en dict │ | |
| │ compatible Jinja2 │ | |
| │ │ | |
| │ • description_lines │ | |
| │ • period formaté │ | |
| │ • display strings │ | |
| └──────────────┬───────────────┘ | |
| │ | |
| ▼ | |
| ┌──────────────────────────────┐ | |
| │ DocxTemplate.render() │ | |
| │ │ | |
| │ Remplit les placeholders │ | |
| │ {{ variable }} │ | |
| │ {% for %} ... {% endfor %} │ | |
| └──────────────┬───────────────┘ | |
| │ | |
| ▼ | |
| ┌──────────────────────────────┐ | |
| │ _adjust_document_sizes │ | |
| │ │ | |
| │ Parcourt tous les éléments │ | |
| │ • Paragraphes │ | |
| │ • Cellules de tableau │ | |
| │ │ | |
| │ Applique : │ | |
| │ • Tailles de police │ | |
| │ • Espacements │ | |
| │ • Interligne │ | |
| └──────────────┬───────────────┘ | |
| │ | |
| ▼ | |
| ┌──────────────────────────────┐ | |
| │ CV Final .docx │ | |
| │ │ | |
| │ ✅ Tient sur 1 page │ | |
| │ ✅ Design préservé │ | |
| │ ✅ Lisible │ | |
| └──────────────────────────────┘ | |
| ``` | |
| --- | |
| ## 🎨 Avantages de l'Approche Template | |
| ### ✅ Points Forts | |
| | Avantage | Description | | |
| |----------|-------------| | |
| | **Design préservé** | Le template Word garde toutes les mises en forme (couleurs, polices, bordures) | | |
| | **Facilement modifiable** | Un designer peut modifier le template sans toucher au code | | |
| | **Séparation des responsabilités** | Design (template) vs Logique (Python) | | |
| | **Réutilisable** | Plusieurs templates possibles pour différents styles | | |
| | **Ajustement intelligent** | Le CV s'adapte automatiquement au contenu | | |
| ### ⚠️ Limitations | |
| | Limitation | Solution | | |
| |------------|----------| | |
| | Template doit exister | Guide de création fourni | | |
| | Syntaxe Jinja2 requise | Documentation des placeholders | | |
| | Ajustement post-rendu | Peut légèrement modifier le design | | |
| --- | |
| ## 🔄 Algorithme d'Ajustement Détaillé | |
| ```python | |
| def _adjust_document_sizes(self, doc, preset_name): | |
| """ | |
| Parcourt le document et ajuste dynamiquement : | |
| 1. Les tailles de police selon leur taille actuelle | |
| 2. Les espacements avant/après paragraphes | |
| 3. L'interligne (line_spacing) | |
| """ | |
| preset = self.SIZE_PRESETS[preset_name] | |
| spacing = self.SPACING_PRESETS[preset_name] | |
| # Règle de mapping des tailles | |
| # ≥ 20pt → taille 'name' (titre principal) | |
| # ≥ 11pt → taille 'section' (titres de section) | |
| # ≥ 9pt → taille 'normal' (texte courant) | |
| # < 9pt → taille 'small' (détails, dates) | |
| for paragraph in doc.paragraphs: | |
| paragraph.space_before = spacing['before'] | |
| paragraph.space_after = spacing['after'] | |
| paragraph.line_spacing = 0.9 # Compact | |
| for run in paragraph.runs: | |
| if run.font.size >= Pt(20): | |
| run.font.size = preset['name'] | |
| elif run.font.size >= Pt(11): | |
| run.font.size = preset['section'] | |
| # ... etc | |
| ``` | |
| --- | |
| ## 📈 Exemples de Résultats | |
| ### Cas 1 : CV Léger (Volume < 18) | |
| - 1 expérience | |
| - 1 formation | |
| - 3 compétences | |
| - 2 langues | |
| **Résultat** : Preset `light`, grandes polices, espacement confortable | |
| ### Cas 2 : CV Moyen (Volume 18-25) | |
| - 2 expériences | |
| - 2 formations | |
| - 5 compétences | |
| - 3 langues | |
| - 3 hobbies | |
| **Résultat** : Preset `medium`, polices légèrement réduites | |
| ### Cas 3 : CV Dense (Volume 25-35) | |
| - 3 expériences avec descriptions détaillées | |
| - 3 formations | |
| - 8 compétences | |
| - 3 langues | |
| - 5 hobbies | |
| **Résultat** : Preset `dense`, polices compactes, espacements minimaux | |
| ### Cas 4 : CV Très Dense (Volume > 35) | |
| - 5+ expériences | |
| - 4+ formations | |
| - 10+ compétences | |
| **Résultat** : Preset `very_dense`, tout est compressé pour tenir sur 1 page | |
| --- | |
| ## 🛠️ Technologies Utilisées | |
| | Technologie | Rôle | | |
| |-------------|------| | |
| | **docxtpl** | Moteur de template Jinja2 pour Word | | |
| | **python-docx** | Manipulation programmatique des documents Word | | |
| | **LibreOffice** | Conversion DOCX → PDF (headless) | | |
| | **Pydantic** | Validation des données d'entrée | | |
| | **FastAPI** | API REST pour le service | | |
| --- | |
| ## 📝 Guide de Création de Template | |
| Pour créer un nouveau template : | |
| 1. **Ouvrir Word/LibreOffice** | |
| 2. **Designer votre CV** avec le style souhaité | |
| 3. **Remplacer les données** par les placeholders Jinja2 : | |
| - `{{ full_name }}` pour le nom | |
| - `{% for exp in experiences %}...{% endfor %}` pour les boucles | |
| 4. **Sauvegarder** en `.docx` dans `backend/app/templates/` | |
| ### Placeholders Disponibles | |
| ```jinja2 | |
| {# Informations personnelles #} | |
| {{ full_name }} | |
| {{ email }} | |
| {{ phone }} | |
| {{ address }} | |
| {{ summary }} | |
| {# Expériences #} | |
| {% for exp in experiences %} | |
| {{ exp.job_title }} | |
| {{ exp.company }} | |
| {{ exp.period }} | |
| {% for line in exp.description_lines %} | |
| {{ line }} | |
| {% endfor %} | |
| {% endfor %} | |
| {# Formation #} | |
| {% for edu in education %} | |
| {{ edu.degree }} | |
| {{ edu.institution }} | |
| {{ edu.period }} | |
| {% endfor %} | |
| {# Compétences #} | |
| {% for skill in skills %} | |
| {{ skill.display }} | |
| {% endfor %} | |
| {# Langues #} | |
| {% for lang in languages %} | |
| {{ lang.display }} | |
| {% endfor %} | |
| {# Hobbies #} | |
| {% for hobby in hobbies %} | |
| {{ hobby }} | |
| {% endfor %} | |
| ``` | |
| --- | |
| ## 🎯 Conclusion | |
| Cette approche **Template + Ajustement Automatique** offre le meilleur des deux mondes : | |
| - **Flexibilité du design** : Le template Word permet n'importe quel style visuel | |
| - **Garantie de pagination** : L'ajustement automatique assure toujours 1 page | |
| - **Maintenabilité** : Séparation claire entre design et logique | |
| - **Évolutivité** : Facile d'ajouter de nouveaux templates ou presets | |
| --- | |
| *Documentation technique - Janvier 2026* | |