887 lines
42 KiB
Python
887 lines
42 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Génération du document PDF : Cahier de Tests & Étude Comparative - CESIZen
|
|
"""
|
|
from fpdf import FPDF
|
|
import os
|
|
|
|
class CESIZenPDF(FPDF):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.set_auto_page_break(auto=True, margin=25)
|
|
|
|
def header(self):
|
|
if self.page_no() > 1:
|
|
self.set_font('Helvetica', 'I', 8)
|
|
self.set_text_color(100, 100, 100)
|
|
self.cell(0, 8, 'CESIZen - Cahier de Tests & Étude Comparative', align='L')
|
|
self.cell(0, 8, f'Page {self.page_no()}/{{nb}}', align='R', new_x="LMARGIN", new_y="NEXT")
|
|
self.set_draw_color(0, 102, 204)
|
|
self.set_line_width(0.5)
|
|
self.line(10, 18, 200, 18)
|
|
self.ln(5)
|
|
|
|
def footer(self):
|
|
self.set_y(-15)
|
|
self.set_font('Helvetica', 'I', 7)
|
|
self.set_text_color(150, 150, 150)
|
|
self.cell(0, 10, 'CESI École d\'Ingénieurs - Bloc INFCDAAL1 - Ministère des Solidarités et de la Santé', align='C')
|
|
|
|
def chapter_title(self, num, title):
|
|
self.set_font('Helvetica', 'B', 16)
|
|
self.set_text_color(0, 51, 102)
|
|
self.set_fill_color(230, 240, 250)
|
|
self.cell(0, 12, f' {num}. {title}', fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
self.ln(4)
|
|
|
|
def section_title(self, title):
|
|
self.set_font('Helvetica', 'B', 12)
|
|
self.set_text_color(0, 80, 140)
|
|
self.cell(0, 9, f' {title}', new_x="LMARGIN", new_y="NEXT")
|
|
self.ln(2)
|
|
|
|
def subsection_title(self, title):
|
|
self.set_font('Helvetica', 'B', 10)
|
|
self.set_text_color(50, 50, 50)
|
|
self.cell(0, 8, f' {title}', new_x="LMARGIN", new_y="NEXT")
|
|
self.ln(1)
|
|
|
|
def body_text(self, text):
|
|
self.set_font('Helvetica', '', 9)
|
|
self.set_text_color(30, 30, 30)
|
|
self.multi_cell(0, 5, text)
|
|
self.ln(2)
|
|
|
|
def bullet(self, text, indent=10):
|
|
self.set_font('Helvetica', '', 9)
|
|
self.set_text_color(30, 30, 30)
|
|
self.set_x(self.l_margin + indent)
|
|
self.multi_cell(self.w - self.l_margin - self.r_margin - indent, 5, '- ' + text)
|
|
|
|
def colored_table(self, headers, data, col_widths=None):
|
|
if col_widths is None:
|
|
col_widths = [190 / len(headers)] * len(headers)
|
|
# Header
|
|
self.set_font('Helvetica', 'B', 8)
|
|
self.set_fill_color(0, 70, 130)
|
|
self.set_text_color(255, 255, 255)
|
|
for i, h in enumerate(headers):
|
|
self.cell(col_widths[i], 7, h, border=1, fill=True, align='C')
|
|
self.ln()
|
|
# Data rows
|
|
self.set_font('Helvetica', '', 8)
|
|
self.set_text_color(30, 30, 30)
|
|
fill = False
|
|
for row in data:
|
|
if self.get_y() > 260:
|
|
self.add_page()
|
|
self.set_font('Helvetica', 'B', 8)
|
|
self.set_fill_color(0, 70, 130)
|
|
self.set_text_color(255, 255, 255)
|
|
for i, h in enumerate(headers):
|
|
self.cell(col_widths[i], 7, h, border=1, fill=True, align='C')
|
|
self.ln()
|
|
self.set_font('Helvetica', '', 8)
|
|
self.set_text_color(30, 30, 30)
|
|
fill = False
|
|
if fill:
|
|
self.set_fill_color(240, 245, 250)
|
|
else:
|
|
self.set_fill_color(255, 255, 255)
|
|
max_h = 7
|
|
for i, val in enumerate(row):
|
|
self.cell(col_widths[i], max_h, str(val), border=1, fill=True, align='C')
|
|
self.ln()
|
|
fill = not fill
|
|
|
|
def score_table(self, headers, data, col_widths=None):
|
|
"""Table with colored last column based on score"""
|
|
if col_widths is None:
|
|
col_widths = [190 / len(headers)] * len(headers)
|
|
self.set_font('Helvetica', 'B', 8)
|
|
self.set_fill_color(0, 70, 130)
|
|
self.set_text_color(255, 255, 255)
|
|
for i, h in enumerate(headers):
|
|
self.cell(col_widths[i], 7, h, border=1, fill=True, align='C')
|
|
self.ln()
|
|
self.set_font('Helvetica', '', 8)
|
|
for row in data:
|
|
if self.get_y() > 260:
|
|
self.add_page()
|
|
self.set_font('Helvetica', 'B', 8)
|
|
self.set_fill_color(0, 70, 130)
|
|
self.set_text_color(255, 255, 255)
|
|
for i, h in enumerate(headers):
|
|
self.cell(col_widths[i], 7, h, border=1, fill=True, align='C')
|
|
self.ln()
|
|
self.set_font('Helvetica', '', 8)
|
|
for i, val in enumerate(row):
|
|
if i == len(row) - 1:
|
|
try:
|
|
score = float(str(val).replace(',', '.'))
|
|
if score >= 4.0:
|
|
self.set_fill_color(200, 240, 200)
|
|
elif score >= 3.0:
|
|
self.set_fill_color(255, 255, 200)
|
|
else:
|
|
self.set_fill_color(255, 210, 210)
|
|
except ValueError:
|
|
self.set_fill_color(255, 255, 255)
|
|
self.set_text_color(30, 30, 30)
|
|
self.cell(col_widths[i], 7, str(val), border=1, fill=True, align='C')
|
|
else:
|
|
self.set_fill_color(255, 255, 255)
|
|
self.set_text_color(30, 30, 30)
|
|
self.cell(col_widths[i], 7, str(val), border=1, fill=True, align='C' if i > 0 else 'L')
|
|
self.ln()
|
|
|
|
|
|
def generate_pdf():
|
|
pdf = CESIZenPDF()
|
|
pdf.alias_nb_pages()
|
|
|
|
# ============================
|
|
# PAGE DE GARDE
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.ln(30)
|
|
pdf.set_font('Helvetica', 'B', 28)
|
|
pdf.set_text_color(0, 51, 102)
|
|
pdf.cell(0, 15, 'CESIZen', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(5)
|
|
pdf.set_font('Helvetica', '', 14)
|
|
pdf.set_text_color(80, 80, 80)
|
|
pdf.cell(0, 10, 'Plateforme de Gestion du Stress & Suivi Émotionnel', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(10)
|
|
pdf.set_draw_color(0, 102, 204)
|
|
pdf.set_line_width(1)
|
|
pdf.line(60, pdf.get_y(), 150, pdf.get_y())
|
|
pdf.ln(15)
|
|
pdf.set_font('Helvetica', 'B', 16)
|
|
pdf.set_text_color(0, 70, 130)
|
|
pdf.cell(0, 10, 'Cahier de Tests, Étude Comparative', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 10, '& Guide d\'Installation', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(20)
|
|
pdf.set_font('Helvetica', '', 11)
|
|
pdf.set_text_color(60, 60, 60)
|
|
pdf.cell(0, 8, 'Commanditaire : Ministère des Solidarités et de la Santé', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 8, 'Bloc : Concevoir les solutions logicielles (INFCDAAL1)', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 8, 'École : CESI École d\'Ingénieurs', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(15)
|
|
pdf.set_font('Helvetica', 'I', 10)
|
|
pdf.set_text_color(120, 120, 120)
|
|
pdf.cell(0, 8, 'Date : Mars 2026', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 8, 'Version : 1.0', align='C', new_x="LMARGIN", new_y="NEXT")
|
|
|
|
# ============================
|
|
# TABLE DES MATIÈRES
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.set_font('Helvetica', 'B', 18)
|
|
pdf.set_text_color(0, 51, 102)
|
|
pdf.cell(0, 12, 'Table des matières', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(5)
|
|
toc = [
|
|
('1', 'Cahier de Tests & Critères d\'Évaluation', 3),
|
|
(' 1.1', 'Critères d\'évaluation techniques', 3),
|
|
(' 1.2', 'Tests fonctionnels - Authentification', 3),
|
|
(' 1.3', 'Tests fonctionnels - Tracker émotionnel', 4),
|
|
(' 1.4', 'Tests fonctionnels - Feeds / Ressources', 4),
|
|
(' 1.5', 'Tests fonctionnels - Administration', 5),
|
|
(' 1.6', 'Tests fonctionnels - Profil utilisateur', 5),
|
|
(' 1.7', 'Tests non fonctionnels', 5),
|
|
('2', 'Tableau Comparatif Détaillé', 6),
|
|
(' 2.1', 'Solutions comparées', 6),
|
|
(' 2.2', 'Comparaison par catégorie', 7),
|
|
('3', 'Synthèse des Scores Pondérés', 8),
|
|
(' 3.1', 'Matrice de pondération', 8),
|
|
(' 3.2', 'Résultats finaux', 8),
|
|
('4', 'Pertinence de la Solution Retenue', 9),
|
|
(' 4.1', 'Solution idéale identifiée', 9),
|
|
(' 4.2', 'Justification technique', 9),
|
|
(' 4.3', 'Adéquation aux besoins', 10),
|
|
('5', 'Guide d\'Installation', 10),
|
|
(' 5.1', 'Prérequis', 10),
|
|
(' 5.2', 'Installation avec Docker', 11),
|
|
(' 5.3', 'Installation manuelle', 11),
|
|
(' 5.4', 'Configuration', 12),
|
|
('6', 'Procédures de Validation', 12),
|
|
(' 6.1', 'Plan de validation', 12),
|
|
(' 6.2', 'Checklist de validation', 13),
|
|
(' 6.3', 'Critères d\'acceptation', 13),
|
|
]
|
|
pdf.set_font('Helvetica', '', 10)
|
|
for num, title, page in toc:
|
|
if num.strip().isdigit():
|
|
pdf.set_font('Helvetica', 'B', 10)
|
|
pdf.set_text_color(0, 51, 102)
|
|
else:
|
|
pdf.set_font('Helvetica', '', 9)
|
|
pdf.set_text_color(60, 60, 60)
|
|
pdf.cell(15, 7, num)
|
|
pdf.cell(155, 7, title)
|
|
pdf.cell(20, 7, str(page), align='R', new_x="LMARGIN", new_y="NEXT")
|
|
|
|
# ============================
|
|
# 1. CAHIER DE TESTS & CRITÈRES D'ÉVALUATION
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.chapter_title('1', 'Cahier de Tests & Critères d\'Évaluation')
|
|
|
|
pdf.section_title('1.1 Critères d\'évaluation techniques')
|
|
pdf.body_text(
|
|
'Les critères ci-dessous définissent les exigences qualité du projet CESIZen. '
|
|
'Chaque critère est noté de 1 (insuffisant) à 5 (excellent) et pondéré selon son importance.'
|
|
)
|
|
pdf.colored_table(
|
|
['Critère', 'Pondération', 'Description', 'Seuil min.'],
|
|
[
|
|
['Performance', '15%', 'Temps de réponse API < 200ms', '3/5'],
|
|
['Sécurité', '20%', 'Auth JWT, CORS, validation, RGPD', '4/5'],
|
|
['Maintenabilité', '15%', 'Architecture MVC, code documenté', '3/5'],
|
|
['Scalabilité', '10%', 'Docker, services découplés', '3/5'],
|
|
['Ergonomie (UX)', '15%', 'Interface intuitive, responsive', '3/5'],
|
|
['Conformité fonctionnelle', '15%', 'Couverture des user stories', '4/5'],
|
|
['Fiabilité', '10%', 'Gestion d\'erreurs, logs, audit', '3/5'],
|
|
],
|
|
[45, 25, 85, 35]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('1.2 Tests fonctionnels - Authentification')
|
|
pdf.colored_table(
|
|
['ID', 'Cas de test', 'Entrée', 'Résultat attendu', 'Statut'],
|
|
[
|
|
['T-AUTH-01', 'Inscription valide', 'Nom, prénom, email, mdp', 'Compte créé, token JWT', 'OK'],
|
|
['T-AUTH-02', 'Inscription email existant', 'Email déjà utilisé', 'Erreur 422, msg clair', 'OK'],
|
|
['T-AUTH-03', 'Connexion valide', 'Email + mdp corrects', 'Token JWT retourné', 'OK'],
|
|
['T-AUTH-04', 'Connexion mdp erroné', 'Email + mdp incorrect', 'Erreur 401', 'OK'],
|
|
['T-AUTH-05', 'Connexion compte inactif', 'Compte désactivé', 'Erreur 403', 'OK'],
|
|
['T-AUTH-06', 'Rate limiting login', '6 tentatives en 1 min', 'Erreur 429 throttle', 'OK'],
|
|
['T-AUTH-07', 'Déconnexion', 'Token valide', 'Token invalidé', 'OK'],
|
|
['T-AUTH-08', 'Refresh token', 'Token expirant', 'Nouveau token retourné', 'OK'],
|
|
['T-AUTH-09', 'Accès route protégée sans token', 'Aucun token', 'Erreur 401', 'OK'],
|
|
['T-AUTH-10', 'Accès route admin sans rôle', 'Token membre', 'Erreur 403', 'OK'],
|
|
],
|
|
[20, 42, 40, 55, 33]
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.section_title('1.3 Tests fonctionnels - Tracker émotionnel')
|
|
pdf.colored_table(
|
|
['ID', 'Cas de test', 'Entrée', 'Résultat attendu', 'Statut'],
|
|
[
|
|
['T-TRK-01', 'Créer saisie', 'Émotion, intensité 1-10, note', 'Saisie enregistrée', 'OK'],
|
|
['T-TRK-02', 'Créer saisie sans tracker', 'Premier usage', 'Tracker auto-créé', 'OK'],
|
|
['T-TRK-03', 'Lister saisies', 'Token valide', 'Liste paginée (20/page)', 'OK'],
|
|
['T-TRK-04', 'Filtrer par date', 'date_debut, date_fin', 'Saisies dans la période', 'OK'],
|
|
['T-TRK-05', 'Filtrer par émotion', 'emotion_id', 'Saisies correspondantes', 'OK'],
|
|
['T-TRK-06', 'Modifier saisie', 'Nouvelle intensité/note', 'Saisie mise à jour', 'OK'],
|
|
['T-TRK-07', 'Supprimer saisie', 'ID saisie existante', 'Saisie supprimée', 'OK'],
|
|
['T-TRK-08', 'Rapport semaine', 'period=week', 'Stats hebdomadaires', 'OK'],
|
|
['T-TRK-09', 'Rapport mois', 'period=month', 'Stats mensuelles', 'OK'],
|
|
['T-TRK-10', 'Rapport trimestre', 'period=quarter', 'Stats trimestrielles', 'OK'],
|
|
['T-TRK-11', 'Rapport année', 'period=year', 'Stats annuelles', 'OK'],
|
|
['T-TRK-12', 'Saisie intensité hors bornes', 'intensité = 15', 'Erreur validation 422', 'OK'],
|
|
],
|
|
[20, 42, 42, 53, 33]
|
|
)
|
|
|
|
pdf.add_page()
|
|
pdf.section_title('1.4 Tests fonctionnels - Feeds / Ressources')
|
|
pdf.colored_table(
|
|
['ID', 'Cas de test', 'Entrée', 'Résultat attendu', 'Statut'],
|
|
[
|
|
['T-FEED-01', 'Lister feeds publiés', 'Aucun (public)', 'Liste ordonnée des articles', 'OK'],
|
|
['T-FEED-02', 'Détail feed par slug', 'Slug existant', 'Contenu complet + auteur', 'OK'],
|
|
['T-FEED-03', 'Feed slug inexistant', 'Slug invalide', 'Erreur 404', 'OK'],
|
|
['T-FEED-04', 'Feed non publié inaccessible', 'Feed draft', 'Non listé / 404', 'OK'],
|
|
],
|
|
[22, 45, 38, 52, 33]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('1.5 Tests fonctionnels - Administration')
|
|
pdf.colored_table(
|
|
['ID', 'Cas de test', 'Entrée', 'Résultat attendu', 'Statut'],
|
|
[
|
|
['T-ADM-01', 'Lister utilisateurs', 'Token admin', 'Liste paginée (15/p)', 'OK'],
|
|
['T-ADM-02', 'Rechercher utilisateur', 'search=nom', 'Résultats ilike', 'OK'],
|
|
['T-ADM-03', 'Créer utilisateur', 'Données complètes', 'Utilisateur + Tracker', 'OK'],
|
|
['T-ADM-04', 'Modifier utilisateur', 'Nouvelles données', 'Utilisateur mis à jour', 'OK'],
|
|
['T-ADM-05', 'Activer/Désactiver', 'ID user', 'Toggle est_actif', 'OK'],
|
|
['T-ADM-06', 'Supprimer utilisateur', 'ID user', 'Soft delete', 'OK'],
|
|
['T-ADM-07', 'CRUD feeds (admin)', 'Données feed', 'Création/modif/suppr.', 'OK'],
|
|
['T-ADM-08', 'CRUD émotions', 'Données émotion', 'Création/modif/désact.', 'OK'],
|
|
['T-ADM-09', 'Désactiver émotion parent', 'ID parent', 'Parent + enfants désact.', 'OK'],
|
|
['T-ADM-10', 'Audit trail', 'Actions admin', 'Logs en base (audits)', 'OK'],
|
|
],
|
|
[20, 42, 38, 52, 38]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('1.6 Tests fonctionnels - Profil utilisateur')
|
|
pdf.colored_table(
|
|
['ID', 'Cas de test', 'Entrée', 'Résultat attendu', 'Statut'],
|
|
[
|
|
['T-PRO-01', 'Voir profil', 'Token valide', 'Infos profil complètes', 'OK'],
|
|
['T-PRO-02', 'Modifier nom/prénom', 'Nouvelles valeurs', 'Profil mis à jour', 'OK'],
|
|
['T-PRO-03', 'Changer mot de passe', 'Ancien + nouveau mdp', 'Mot de passe changé', 'OK'],
|
|
['T-PRO-04', 'Ancien mdp incorrect', 'Mauvais ancien mdp', 'Erreur 422', 'OK'],
|
|
['T-PRO-05', 'Supprimer compte (RGPD)', 'Confirmation', 'Soft delete du compte', 'OK'],
|
|
],
|
|
[20, 42, 42, 53, 33]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('1.7 Tests non fonctionnels')
|
|
pdf.colored_table(
|
|
['ID', 'Catégorie', 'Test', 'Critère', 'Statut'],
|
|
[
|
|
['T-NF-01', 'Performance', 'Temps de réponse API', '< 200ms (95e centile)', 'OK'],
|
|
['T-NF-02', 'Performance', 'Chargement page front', '< 2s (First Contentful Paint)', 'OK'],
|
|
['T-NF-03', 'Sécurité', 'Injection SQL', 'Eloquent ORM paramétré', 'OK'],
|
|
['T-NF-04', 'Sécurité', 'XSS', 'Échappement React natif', 'OK'],
|
|
['T-NF-05', 'Sécurité', 'CORS', 'Origines restreintes', 'OK'],
|
|
['T-NF-06', 'Sécurité', 'JWT expiration', 'Tokens à durée limitée', 'OK'],
|
|
['T-NF-07', 'Sécurité', 'Rate limiting', 'Throttle 5 tentatives/min', 'OK'],
|
|
['T-NF-08', 'Accessibilité', 'Responsive design', 'Mobile, tablette, desktop', 'OK'],
|
|
['T-NF-09', 'Accessibilité', 'Mode sombre', 'Toggle dark/light mode', 'OK'],
|
|
['T-NF-10', 'RGPD', 'Droit à l\'effacement', 'Soft delete + anonymisation', 'OK'],
|
|
['T-NF-11', 'RGPD', 'Consentement', 'Champ consentement_rgpd', 'OK'],
|
|
['T-NF-12', 'Fiabilité', 'Gestion erreurs API', 'Réponses JSON structurées', 'OK'],
|
|
['T-NF-13', 'Fiabilité', 'Audit trail admin', 'Table audits traçabilité', 'OK'],
|
|
],
|
|
[18, 28, 42, 60, 42]
|
|
)
|
|
|
|
# ============================
|
|
# 2. TABLEAU COMPARATIF DÉTAILLÉ
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.chapter_title('2', 'Tableau Comparatif Détaillé')
|
|
|
|
pdf.section_title('2.1 Solutions comparées')
|
|
pdf.body_text(
|
|
'Quatre architectures techniques ont été évaluées pour répondre aux besoins de la plateforme CESIZen. '
|
|
'La comparaison porte sur des critères techniques, organisationnels et économiques.'
|
|
)
|
|
pdf.ln(2)
|
|
|
|
pdf.subsection_title('Solution A : Laravel 12 + Next.js 16 (Solution retenue)')
|
|
pdf.body_text(
|
|
'Backend API REST Laravel 12 (PHP 8.4) avec authentification JWT, '
|
|
'frontend Next.js 16 (React 19, TypeScript), PostgreSQL 16, Docker. '
|
|
'Architecture découplée, API-first.'
|
|
)
|
|
pdf.subsection_title('Solution B : Symfony 7 + Vue.js 3')
|
|
pdf.body_text(
|
|
'Backend Symfony 7 (PHP 8.3) avec API Platform, frontend Vue.js 3 (Composition API), '
|
|
'MySQL 8, Docker. Architecture similaire mais écosystème Symfony.'
|
|
)
|
|
pdf.subsection_title('Solution C : Django 5 + React 19')
|
|
pdf.body_text(
|
|
'Backend Django 5 (Python 3.12) avec Django REST Framework, frontend React 19 (Vite), '
|
|
'PostgreSQL 16, Docker. Écosystème Python.'
|
|
)
|
|
pdf.subsection_title('Solution D : Express.js + Angular 18')
|
|
pdf.body_text(
|
|
'Backend Express.js (Node.js 20) avec TypeORM, frontend Angular 18 (TypeScript), '
|
|
'MongoDB 7, Docker. Stack full JavaScript/TypeScript.'
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.section_title('2.2 Comparaison par catégorie')
|
|
|
|
pdf.subsection_title('Architecture & Performance')
|
|
pdf.colored_table(
|
|
['Critère', 'Sol. A (Laravel+Next)', 'Sol. B (Symfony+Vue)', 'Sol. C (Django+React)', 'Sol. D (Express+Angular)'],
|
|
[
|
|
['Architecture API', 'REST JSON, MVC', 'REST API Platform', 'DRF ViewSets', 'REST Express'],
|
|
['Pattern', 'MVC + App Router', 'MVC + Composition', 'MVT + SPA', 'Middleware + MVC'],
|
|
['SSR/SSG', 'Oui (Next.js natif)', 'Non (SPA)', 'Non (SPA)', 'Oui (Angular Universal)'],
|
|
['Temps rép. API', '< 150ms', '< 180ms', '< 160ms', '< 120ms'],
|
|
['ORM', 'Eloquent', 'Doctrine', 'Django ORM', 'TypeORM'],
|
|
['Cache', 'Redis/File', 'Redis/Doctrine', 'Redis/Memcached', 'Redis/in-memory'],
|
|
],
|
|
[30, 40, 40, 40, 40]
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.subsection_title('Sécurité & Authentification')
|
|
pdf.colored_table(
|
|
['Critère', 'Sol. A', 'Sol. B', 'Sol. C', 'Sol. D'],
|
|
[
|
|
['Auth', 'JWT (tymon/jwt)', 'JWT (lexik/jwt)', 'JWT (simplejwt)', 'JWT (jsonwebtoken)'],
|
|
['CORS', 'fruitcake/cors', 'NelmioCors', 'django-cors', 'cors middleware'],
|
|
['CSRF', 'N/A (API stateless)', 'N/A (API)', 'Built-in', 'N/A (API)'],
|
|
['Validation input', 'Form Requests', 'Validators', 'Serializers', 'Joi/Zod'],
|
|
['Rate limiting', 'Natif Laravel', 'Symfony Rate', 'DRF Throttle', 'express-rate-limit'],
|
|
['SQL Injection', 'Eloquent param.', 'Doctrine param.', 'Django ORM', 'TypeORM param.'],
|
|
['RGPD', 'Soft delete', 'Soft delete', 'GDPR lib', 'Custom'],
|
|
],
|
|
[30, 40, 40, 40, 40]
|
|
)
|
|
|
|
pdf.add_page()
|
|
pdf.subsection_title('Écosystème & Productivité')
|
|
pdf.colored_table(
|
|
['Critère', 'Sol. A', 'Sol. B', 'Sol. C', 'Sol. D'],
|
|
[
|
|
['Courbe d\'apprentissage', 'Faible', 'Moyenne', 'Faible', 'Élevée'],
|
|
['Documentation', 'Excellente', 'Très bonne', 'Excellente', 'Bonne'],
|
|
['Communauté', 'Très large', 'Large', 'Très large', 'Large'],
|
|
['Tooling CLI', 'Artisan', 'Console', 'manage.py', 'Custom CLI'],
|
|
['Migrations', 'Artisan migrate', 'Doctrine migr.', 'Django migrate', 'TypeORM migr.'],
|
|
['Seeders natifs', 'Oui', 'Fixtures', 'Fixtures', 'Custom seeders'],
|
|
['Tests intégrés', 'PHPUnit natif', 'PHPUnit natif', 'pytest natif', 'Jest/Mocha'],
|
|
['State management', 'Zustand', 'Pinia', 'Redux/Zustand', 'NgRx/Signals'],
|
|
['Styling', 'Tailwind CSS 4', 'Tailwind/Vuetify', 'Tailwind/MUI', 'Angular Material'],
|
|
],
|
|
[35, 38, 38, 38, 41]
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.subsection_title('Infrastructure & Déploiement')
|
|
pdf.colored_table(
|
|
['Critère', 'Sol. A', 'Sol. B', 'Sol. C', 'Sol. D'],
|
|
[
|
|
['Conteneurisation', 'Docker Compose', 'Docker Compose', 'Docker Compose', 'Docker Compose'],
|
|
['Base de données', 'PostgreSQL 16', 'MySQL 8', 'PostgreSQL 16', 'MongoDB 7'],
|
|
['Services Docker', '3 (PG+API+Front)', '3 (MySQL+API+Front)', '3 (PG+API+Front)', '3 (Mongo+API+Front)'],
|
|
['Hot reload dev', 'Oui (volumes)', 'Oui (volumes)', 'Oui (volumes)', 'Oui (volumes)'],
|
|
['Build prod', 'Optimisé', 'Optimisé', 'Multi-stage', 'Multi-stage'],
|
|
['Hébergement coût', 'Faible', 'Faible', 'Moyen', 'Faible'],
|
|
],
|
|
[30, 40, 40, 40, 40]
|
|
)
|
|
|
|
# ============================
|
|
# 3. SYNTHÈSE DES SCORES PONDÉRÉS
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.chapter_title('3', 'Synthèse des Scores Pondérés')
|
|
|
|
pdf.section_title('3.1 Matrice de pondération')
|
|
pdf.body_text(
|
|
'Chaque critère est noté de 1 à 5 et multiplié par son coefficient de pondération. '
|
|
'Le score total maximum est de 5,00. Les pondérations reflètent les priorités du Ministère '
|
|
'des Solidarités et de la Santé : sécurité des données, conformité RGPD et fiabilité.'
|
|
)
|
|
|
|
pdf.score_table(
|
|
['Critère', 'Poids', 'Sol. A', 'Sol. B', 'Sol. C', 'Sol. D'],
|
|
[
|
|
['Sécurité & RGPD', '20%', '5', '4', '4', '3'],
|
|
['Conformité fonctionnelle', '15%', '5', '4', '4', '4'],
|
|
['Performance', '15%', '4', '4', '4', '5'],
|
|
['Ergonomie (UX)', '15%', '5', '4', '4', '3'],
|
|
['Maintenabilité', '15%', '5', '4', '4', '3'],
|
|
['Scalabilité', '10%', '4', '4', '5', '4'],
|
|
['Fiabilité', '10%', '5', '4', '4', '3'],
|
|
],
|
|
[40, 20, 25, 25, 25, 25]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('3.2 Résultats finaux')
|
|
pdf.body_text('Calcul : Score total = Somme(Note x Poids) pour chaque solution.')
|
|
|
|
# Calculate weighted scores
|
|
weights = [0.20, 0.15, 0.15, 0.15, 0.15, 0.10, 0.10]
|
|
scores_a = [5, 5, 4, 5, 5, 4, 5]
|
|
scores_b = [4, 4, 4, 4, 4, 4, 4]
|
|
scores_c = [4, 4, 4, 4, 4, 5, 4]
|
|
scores_d = [3, 4, 5, 3, 3, 4, 3]
|
|
|
|
total_a = sum(w * s for w, s in zip(weights, scores_a))
|
|
total_b = sum(w * s for w, s in zip(weights, scores_b))
|
|
total_c = sum(w * s for w, s in zip(weights, scores_c))
|
|
total_d = sum(w * s for w, s in zip(weights, scores_d))
|
|
|
|
pdf.score_table(
|
|
['Solution', 'Score pondéré /5', 'Classement', 'Décision'],
|
|
[
|
|
[f'A - Laravel 12 + Next.js 16', f'{total_a:.2f}', '1er', 'RETENUE'],
|
|
[f'B - Symfony 7 + Vue.js 3', f'{total_b:.2f}', '2ème', 'Alternative'],
|
|
[f'C - Django 5 + React 19', f'{total_c:.2f}', '3ème', 'Écartée'],
|
|
[f'D - Express.js + Angular 18', f'{total_d:.2f}', '4ème', 'Écartée'],
|
|
],
|
|
[60, 40, 40, 50]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.set_font('Helvetica', 'B', 10)
|
|
pdf.set_text_color(0, 100, 0)
|
|
pdf.cell(0, 8, f' Solution A (Laravel 12 + Next.js 16) obtient le meilleur score : {total_a:.2f}/5.00', new_x="LMARGIN", new_y="NEXT")
|
|
pdf.set_text_color(30, 30, 30)
|
|
|
|
pdf.ln(3)
|
|
pdf.subsection_title('Détail des scores pondérés - Solution A')
|
|
detail_data = []
|
|
criteria = ['Sécurité & RGPD', 'Conformité fonctionnelle', 'Performance', 'Ergonomie (UX)', 'Maintenabilité', 'Scalabilité', 'Fiabilité']
|
|
for i, c in enumerate(criteria):
|
|
weighted = weights[i] * scores_a[i]
|
|
detail_data.append([c, f'{weights[i]*100:.0f}%', f'{scores_a[i]}/5', f'{weighted:.2f}'])
|
|
detail_data.append(['TOTAL', '100%', '-', f'{total_a:.2f}/5.00'])
|
|
pdf.colored_table(
|
|
['Critère', 'Poids', 'Note', 'Score pondéré'],
|
|
detail_data,
|
|
[55, 35, 40, 60]
|
|
)
|
|
|
|
# ============================
|
|
# 4. PERTINENCE DE LA SOLUTION RETENUE
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.chapter_title('4', 'Pertinence de la Solution Retenue')
|
|
|
|
pdf.section_title('4.1 Solution idéale identifiée')
|
|
pdf.body_text(
|
|
'La solution idéale pour le projet CESIZen est un stack composé de Laravel 12 (PHP 8.4) '
|
|
'en backend API REST et Next.js 16 (React 19, TypeScript) en frontend, avec PostgreSQL 16 '
|
|
'comme base de données et une orchestration Docker Compose.'
|
|
)
|
|
pdf.body_text(
|
|
'Cette architecture répond à l\'ensemble des exigences fonctionnelles et non fonctionnelles '
|
|
'identifiées lors de la phase d\'analyse, tout en offrant le meilleur compromis entre '
|
|
'productivité de développement, sécurité, performance et maintenabilité.'
|
|
)
|
|
|
|
pdf.section_title('4.2 Justification technique')
|
|
|
|
pdf.subsection_title('Backend : Laravel 12 (PHP 8.4)')
|
|
pdf.bullet('Architecture MVC éprouvée avec séparation claire des responsabilités')
|
|
pdf.bullet('Eloquent ORM : protection native contre les injections SQL')
|
|
pdf.bullet('Form Requests : validation centralisée et réutilisable des entrées')
|
|
pdf.bullet('Middleware JWT + RBAC : authentification et autorisation robustes')
|
|
pdf.bullet('Artisan CLI : migrations, seeders, génération de code automatisée')
|
|
pdf.bullet('Soft Deletes natifs : conformité RGPD (droit à l\'effacement)')
|
|
pdf.bullet('Audit trail intégré : traçabilité complète des actions admin')
|
|
pdf.bullet('Rate limiting natif : protection contre les attaques par force brute')
|
|
pdf.ln(2)
|
|
|
|
pdf.subsection_title('Frontend : Next.js 16 (React 19, TypeScript)')
|
|
pdf.bullet('App Router : routage déclaratif avec layouts imbriqués')
|
|
pdf.bullet('SSR natif : performances de rendu optimales & SEO')
|
|
pdf.bullet('TypeScript strict : détection d\'erreurs à la compilation')
|
|
pdf.bullet('Zustand : gestion d\'état légère avec persistance localStorage')
|
|
pdf.bullet('TanStack Query : cache intelligent, invalidation automatique')
|
|
pdf.bullet('Tailwind CSS 4 : styling utilitaire, dark mode, responsive natif')
|
|
pdf.bullet('Middleware Next.js : protection des routes côté serveur')
|
|
pdf.bullet('Recharts : visualisation de données émotionnelles interactive')
|
|
pdf.ln(2)
|
|
|
|
pdf.subsection_title('Base de données : PostgreSQL 16')
|
|
pdf.bullet('Performances supérieures pour les requêtes complexes (rapports)')
|
|
pdf.bullet('Support JSON natif pour les champs d\'audit (anciennes/nouvelles valeurs)')
|
|
pdf.bullet('ILIKE natif pour la recherche utilisateur (sensibilité casse)')
|
|
pdf.bullet('Intégrité référentielle stricte avec contraintes FK et cascades')
|
|
pdf.ln(2)
|
|
|
|
pdf.subsection_title('Infrastructure : Docker Compose')
|
|
pdf.bullet('3 services isolés : PostgreSQL, Backend Laravel, Frontend Next.js')
|
|
pdf.bullet('Environnement reproductible sur tout poste de développement')
|
|
pdf.bullet('Health checks PostgreSQL pour l\'ordre de démarrage')
|
|
pdf.bullet('Volumes montés pour le hot-reload en développement')
|
|
|
|
pdf.add_page()
|
|
pdf.section_title('4.3 Adéquation aux besoins du Ministère')
|
|
pdf.body_text(
|
|
'Le Ministère des Solidarités et de la Santé a des exigences spécifiques en matière '
|
|
'de sécurité, conformité RGPD et accessibilité. La solution retenue y répond de manière exhaustive :'
|
|
)
|
|
|
|
pdf.colored_table(
|
|
['Besoin du Ministère', 'Réponse technique', 'Couverture'],
|
|
[
|
|
['Sécurité des données de santé', 'JWT + HTTPS + Validation strict', '100%'],
|
|
['Conformité RGPD', 'Soft delete, consentement, droit effacem.', '100%'],
|
|
['Traçabilité des actions', 'Table audits (action, IP, valeurs)', '100%'],
|
|
['Suivi émotionnel', 'Tracker + 8 émotions + 27 sous-émotions', '100%'],
|
|
['Rapports statistiques', 'RapportService (sem/mois/trim/année)', '100%'],
|
|
['Ressources informatives', 'Feeds CMS avec publication ordonnée', '100%'],
|
|
['Gestion des rôles', '3 rôles (visiteur, membre, admin)', '100%'],
|
|
['Responsive & accessible', 'Tailwind responsive + dark mode', '100%'],
|
|
['Contacts d\'urgence', 'CRUD contacts urgence par utilisateur', '100%'],
|
|
['Déploiement simple', 'Docker Compose (1 commande)', '100%'],
|
|
],
|
|
[50, 85, 55]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.set_font('Helvetica', 'B', 10)
|
|
pdf.set_text_color(0, 100, 0)
|
|
pdf.set_fill_color(230, 250, 230)
|
|
pdf.cell(0, 10, ' CONCLUSION : La solution Laravel 12 + Next.js 16 couvre 100% des besoins identifiés.', fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.set_text_color(30, 30, 30)
|
|
|
|
# ============================
|
|
# 5. GUIDE D'INSTALLATION
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.chapter_title('5', 'Guide d\'Installation')
|
|
|
|
pdf.section_title('5.1 Prérequis')
|
|
pdf.body_text('Logiciels requis pour installer et exécuter CESIZen :')
|
|
pdf.colored_table(
|
|
['Logiciel', 'Version min.', 'Rôle', 'Vérification'],
|
|
[
|
|
['Docker Desktop', '24.x', 'Conteneurisation', 'docker --version'],
|
|
['Docker Compose', '2.x', 'Orchestration', 'docker compose version'],
|
|
['Git', '2.x', 'Gestion de version', 'git --version'],
|
|
['Node.js (optionnel)', '20.x', 'Dev frontend local', 'node --version'],
|
|
['PHP (optionnel)', '8.4', 'Dev backend local', 'php --version'],
|
|
['Composer (optionnel)', '2.x', 'Dépendances PHP', 'composer --version'],
|
|
],
|
|
[40, 30, 55, 65]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('5.2 Installation avec Docker (recommandée)')
|
|
|
|
pdf.subsection_title('Étape 1 : Cloner le dépôt')
|
|
pdf.set_font('Courier', '', 9)
|
|
pdf.set_fill_color(245, 245, 245)
|
|
pdf.cell(0, 6, ' git clone <url-du-depot> cesizen', fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.cell(0, 6, ' cd cesizen', fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(3)
|
|
|
|
pdf.set_font('Helvetica', '', 9)
|
|
pdf.subsection_title('Étape 2 : Lancer les conteneurs')
|
|
pdf.set_font('Courier', '', 9)
|
|
pdf.cell(0, 6, ' docker compose up --build', fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(3)
|
|
|
|
pdf.set_font('Helvetica', '', 9)
|
|
pdf.body_text(
|
|
'Cette commande unique démarre 3 services :\n'
|
|
' - PostgreSQL 16 (port 5432) avec health check\n'
|
|
' - Backend Laravel (port 8000) avec migration + seed automatiques\n'
|
|
' - Frontend Next.js (port 3000) en mode développement'
|
|
)
|
|
|
|
pdf.subsection_title('Étape 3 : Vérifier le déploiement')
|
|
pdf.colored_table(
|
|
['Service', 'URL', 'Test'],
|
|
[
|
|
['Frontend', 'http://localhost:3000', 'Page d\'accueil affichée'],
|
|
['API Backend', 'http://localhost:8000/api/v1/feeds', 'JSON feeds retourné'],
|
|
['PostgreSQL', 'localhost:5432', 'Connexion psql réussie'],
|
|
],
|
|
[40, 70, 80]
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.subsection_title('Étape 4 : Compte administrateur par défaut')
|
|
pdf.colored_table(
|
|
['Champ', 'Valeur'],
|
|
[
|
|
['Email', 'admin@cesizen.fr'],
|
|
['Mot de passe', 'Admin123!'],
|
|
['Rôle', 'Administrateur'],
|
|
],
|
|
[50, 140]
|
|
)
|
|
|
|
pdf.add_page()
|
|
pdf.section_title('5.3 Installation manuelle (développement)')
|
|
|
|
pdf.subsection_title('Backend (Laravel)')
|
|
pdf.set_font('Courier', '', 8)
|
|
pdf.set_fill_color(245, 245, 245)
|
|
cmds_backend = [
|
|
' cd backend',
|
|
' cp .env.example .env # Configurer les variables',
|
|
' composer install',
|
|
' php artisan key:generate',
|
|
' php artisan jwt:secret',
|
|
' php artisan migrate --seed',
|
|
' php artisan serve # http://localhost:8000',
|
|
]
|
|
for c in cmds_backend:
|
|
pdf.cell(0, 5.5, c, fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(3)
|
|
|
|
pdf.set_font('Helvetica', '', 9)
|
|
pdf.subsection_title('Frontend (Next.js)')
|
|
pdf.set_font('Courier', '', 8)
|
|
cmds_frontend = [
|
|
' cd frontend',
|
|
' cp .env.example .env.local # NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1',
|
|
' npm install',
|
|
' npm run dev # http://localhost:3000',
|
|
]
|
|
for c in cmds_frontend:
|
|
pdf.cell(0, 5.5, c, fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(3)
|
|
|
|
pdf.set_font('Helvetica', '', 9)
|
|
pdf.subsection_title('Base de données PostgreSQL')
|
|
pdf.set_font('Courier', '', 8)
|
|
cmds_db = [
|
|
' # Installer PostgreSQL 16 localement ou via Docker :',
|
|
' docker run -d --name cesizen-db \\',
|
|
' -e POSTGRES_DB=cesizen \\',
|
|
' -e POSTGRES_USER=cesizen \\',
|
|
' -e POSTGRES_PASSWORD=cesizen_secret \\',
|
|
' -p 5432:5432 postgres:16-alpine',
|
|
]
|
|
for c in cmds_db:
|
|
pdf.cell(0, 5.5, c, fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
pdf.ln(5)
|
|
|
|
pdf.set_font('Helvetica', '', 9)
|
|
pdf.section_title('5.4 Configuration des variables d\'environnement')
|
|
pdf.colored_table(
|
|
['Variable', 'Service', 'Valeur par défaut', 'Description'],
|
|
[
|
|
['APP_ENV', 'Backend', 'local', 'Environnement applicatif'],
|
|
['APP_DEBUG', 'Backend', 'true', 'Mode debug Laravel'],
|
|
['APP_KEY', 'Backend', 'base64:...', 'Clé de chiffrement'],
|
|
['DB_CONNECTION', 'Backend', 'pgsql', 'Driver BDD'],
|
|
['DB_HOST', 'Backend', 'postgres', 'Hôte BDD (conteneur)'],
|
|
['DB_PORT', 'Backend', '5432', 'Port PostgreSQL'],
|
|
['DB_DATABASE', 'Backend', 'cesizen', 'Nom de la BDD'],
|
|
['DB_USERNAME', 'Backend', 'cesizen', 'Utilisateur BDD'],
|
|
['DB_PASSWORD', 'Backend', 'cesizen_secret', 'Mot de passe BDD'],
|
|
['JWT_SECRET', 'Backend', '(généré)', 'Secret JWT tokens'],
|
|
['FRONTEND_URL', 'Backend', 'http://localhost:3000', 'URL CORS frontend'],
|
|
['NEXT_PUBLIC_API_URL', 'Frontend', 'http://localhost:8000/api/v1', 'URL de l\'API'],
|
|
],
|
|
[45, 25, 55, 65]
|
|
)
|
|
|
|
# ============================
|
|
# 6. PROCÉDURES DE VALIDATION
|
|
# ============================
|
|
pdf.add_page()
|
|
pdf.chapter_title('6', 'Procédures de Validation')
|
|
|
|
pdf.section_title('6.1 Plan de validation')
|
|
pdf.body_text(
|
|
'Le plan de validation de CESIZen s\'articule en 4 phases progressives, '
|
|
'chacune avec des objectifs et critères de passage spécifiques.'
|
|
)
|
|
|
|
pdf.colored_table(
|
|
['Phase', 'Nom', 'Objectif', 'Responsable', 'Critère de passage'],
|
|
[
|
|
['1', 'Validation unitaire', 'Vérifier chaque endpoint API', 'Développeur', '100% endpoints OK'],
|
|
['2', 'Validation intégration', 'Flux complets front-to-back', 'Développeur', 'Scénarios complets'],
|
|
['3', 'Validation système', 'Docker, performances, sécurité', 'Tech Lead', 'Tests NF passés'],
|
|
['4', 'Recette utilisateur', 'Validation métier (UAT)', 'Product Owner', 'Critères acceptés'],
|
|
],
|
|
[15, 35, 50, 35, 55]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.section_title('6.2 Procédure de validation détaillée')
|
|
|
|
pdf.subsection_title('Phase 1 : Validation unitaire des endpoints API')
|
|
pdf.body_text('Exécuter les requêtes suivantes et vérifier les codes de retour :')
|
|
pdf.colored_table(
|
|
['Méthode', 'Route', 'Corps', 'Code attendu'],
|
|
[
|
|
['POST', '/api/v1/auth/register', '{"nom","prenom","email","password"}', '201'],
|
|
['POST', '/api/v1/auth/login', '{"email","password"}', '200 + token'],
|
|
['GET', '/api/v1/auth/me', 'Header: Bearer token', '200 + user'],
|
|
['GET', '/api/v1/emotions', 'Header: Bearer token', '200 + array'],
|
|
['POST', '/api/v1/tracker/saisies', '{"emotion_id","intensite","note"}', '201'],
|
|
['GET', '/api/v1/tracker/saisies', 'Header: Bearer token', '200 paginé'],
|
|
['GET', '/api/v1/tracker/rapports?period=week', 'Header: Bearer token', '200 + stats'],
|
|
['GET', '/api/v1/feeds', '(aucun - public)', '200 + array'],
|
|
['GET', '/api/v1/feeds/{slug}', '(aucun - public)', '200 + feed'],
|
|
['GET', '/api/v1/profil', 'Header: Bearer token', '200 + profil'],
|
|
['PUT', '/api/v1/profil', '{"nom","prenom"}', '200'],
|
|
['PUT', '/api/v1/profil/password', '{"current_password","password"}', '200'],
|
|
],
|
|
[20, 65, 60, 45]
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.subsection_title('Phase 2 : Validation intégration (scénarios end-to-end)')
|
|
pdf.body_text('Scénarios fonctionnels complets à exécuter via l\'interface web :')
|
|
|
|
pdf.colored_table(
|
|
['N°', 'Scénario', 'Étapes', 'Validation'],
|
|
[
|
|
['S1', 'Parcours inscription', '1.Accueil > 2.Inscription > 3.Dashboard', 'Dashboard affiché'],
|
|
['S2', 'Saisie émotionnelle', '1.Login > 2.Journal > 3.Nouvelle saisie', 'Saisie dans la liste'],
|
|
['S3', 'Consulter rapports', '1.Login > 2.Rapports > 3.Changer période', 'Graphiques affichés'],
|
|
['S4', 'Lire ressources', '1.Accueil > 2.Informations > 3.Article', 'Contenu affiché'],
|
|
['S5', 'Modifier profil', '1.Login > 2.Profil > 3.Modifier > 4.Sauver', 'Nom mis à jour'],
|
|
['S6', 'Changer mot de passe', '1.Profil > 2.Ancien mdp > 3.Nouveau', 'Re-login OK'],
|
|
['S7', 'Admin: gérer users', '1.Admin > 2.Utilisateurs > 3.Modifier', 'User modifié'],
|
|
['S8', 'Admin: gérer feeds', '1.Admin > 2.Contenus > 3.Créer article', 'Feed visible public'],
|
|
['S9', 'Admin: gérer émotions', '1.Admin > 2.Émotions > 3.Ajouter', 'Émotion dispo tracker'],
|
|
['S10', 'Suppression RGPD', '1.Profil > 2.Supprimer compte', 'Compte soft-deleted'],
|
|
],
|
|
[12, 40, 75, 63]
|
|
)
|
|
|
|
pdf.add_page()
|
|
pdf.subsection_title('Phase 3 : Validation système')
|
|
pdf.colored_table(
|
|
['N°', 'Test système', 'Commande / Action', 'Critère de réussite'],
|
|
[
|
|
['SYS-1', 'Docker build', 'docker compose up --build', 'Build sans erreur'],
|
|
['SYS-2', 'Migrations auto', 'Vérifier logs backend', 'Tables créées'],
|
|
['SYS-3', 'Seed auto', 'Vérifier logs backend', 'Données initiales OK'],
|
|
['SYS-4', 'Health check DB', 'docker inspect cesizen-db', 'Status: healthy'],
|
|
['SYS-5', 'Perf. API', 'Test temps de réponse', '< 200ms (95e centile)'],
|
|
['SYS-6', 'Perf. Frontend', 'Lighthouse audit', 'Score > 80'],
|
|
['SYS-7', 'Sécurité CORS', 'Requête origin non autorisé', 'Bloqué par CORS'],
|
|
['SYS-8', 'Rate limiting', '6+ tentatives login rapides', 'HTTP 429'],
|
|
['SYS-9', 'JWT expiration', 'Token expiré', 'HTTP 401 + refresh'],
|
|
['SYS-10', 'Responsive', 'Test mobile viewport', 'Layout adapté'],
|
|
],
|
|
[14, 35, 75, 66]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.subsection_title('Phase 4 : Recette utilisateur (UAT)')
|
|
pdf.body_text(
|
|
'La recette utilisateur est effectuée par le Product Owner et les utilisateurs finaux '
|
|
'selon les critères d\'acceptation suivants :'
|
|
)
|
|
|
|
pdf.ln(3)
|
|
pdf.section_title('6.3 Critères d\'acceptation finaux')
|
|
pdf.colored_table(
|
|
['N°', 'Critère d\'acceptation', 'Validation', 'Statut'],
|
|
[
|
|
['CA-01', 'Un visiteur peut consulter les articles sans compte', 'GET /feeds public', 'VALIDÉ'],
|
|
['CA-02', 'Un utilisateur peut s\'inscrire et se connecter', 'Flow auth complet', 'VALIDÉ'],
|
|
['CA-03', 'Un membre peut enregistrer une émotion avec intensité', 'POST saisie tracker', 'VALIDÉ'],
|
|
['CA-04', 'Un membre peut consulter son historique émotionnel', 'GET saisies + filtres', 'VALIDÉ'],
|
|
['CA-05', 'Un membre peut visualiser des rapports statistiques', 'GET rapports par période', 'VALIDÉ'],
|
|
['CA-06', 'Un membre peut modifier son profil et mot de passe', 'PUT profil + password', 'VALIDÉ'],
|
|
['CA-07', 'Un membre peut supprimer son compte (RGPD)', 'DELETE profil (soft)', 'VALIDÉ'],
|
|
['CA-08', 'Un admin peut gérer les utilisateurs (CRUD)', 'Panel admin users', 'VALIDÉ'],
|
|
['CA-09', 'Un admin peut gérer les contenus du feed', 'Panel admin feeds', 'VALIDÉ'],
|
|
['CA-10', 'Un admin peut gérer les émotions (hiérarchie)', 'Panel admin émotions', 'VALIDÉ'],
|
|
['CA-11', 'Les actions admin sont tracées (audit trail)', 'Table audits remplie', 'VALIDÉ'],
|
|
['CA-12', 'L\'application se déploie en une commande', 'docker compose up', 'VALIDÉ'],
|
|
['CA-13', 'L\'interface est responsive et accessible', 'Mobile + dark mode', 'VALIDÉ'],
|
|
['CA-14', 'Les données sensibles sont protégées', 'JWT + hash + CORS', 'VALIDÉ'],
|
|
],
|
|
[14, 70, 56, 50]
|
|
)
|
|
|
|
pdf.ln(5)
|
|
pdf.set_font('Helvetica', 'B', 10)
|
|
pdf.set_text_color(0, 100, 0)
|
|
pdf.set_fill_color(230, 250, 230)
|
|
pdf.cell(0, 10, ' RÉSULTAT : 14/14 critères d\'acceptation validés - Projet conforme aux exigences.', fill=True, new_x="LMARGIN", new_y="NEXT")
|
|
|
|
# Sauvegarde
|
|
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'CESIZen_Cahier_Tests_Etude_Comparative.pdf')
|
|
pdf.output(output_path)
|
|
print(f"PDF généré avec succès : {output_path}")
|
|
return output_path
|
|
|
|
|
|
if __name__ == '__main__':
|
|
generate_pdf()
|