tests/generate_pdf_report.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""
Generateur PDF du rapport de campagne de tests MCP.
Utilise fpdf2.
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/..')
from datetime import datetime
from fpdf import FPDF
REPORT_PATH = os.path.join(os.path.dirname(__file__), 'test_campaign_report.txt')
PDF_PATH = os.path.join(os.path.dirname(__file__), 'test_campaign_report.pdf')
STATUS_COLORS = {
'PASS': (34, 139, 34), # vert
'PARTIAL': (255, 165, 0), # orange
'RULE_MISS': (100, 149, 237), # bleu
'FAIL': (220, 20, 60), # rouge
}
CATEGORY_COLORS = {
'FEDORA': (70, 130, 180),
'BACKUP': (60, 179, 113),
'DENON': (147, 112, 219),
'TV': (255, 140, 0),
'HUE': (255, 215, 0),
'CATT': (32, 178, 170),
'EDGE': (128, 128, 128),
}
def parse_report(path):
"""Parse le rapport texte et retourne une structure."""
with open(path, 'r', encoding='utf-8') as f:
lines = f.readlines()
tests = []
scores = {}
current = None
section = None
i = 0
while i < len(lines):
line = lines[i].rstrip()
# Section
if line.startswith('=== ') and line.endswith(' ==='):
section = line.strip('= ').strip()
i += 1
continue
# Debut d'un test
if line.startswith('[') and ']' in line:
status_end = line.index(']')
status = line[1:status_end].strip()
desc = line[status_end+1:].strip()
current = {
'status': status,
'desc': desc,
'category': section,
'query': '',
'expected': '',
'obtained': '',
'args': '',
'bug': '',
}
tests.append(current)
i += 1
continue
if current is not None:
if line.startswith(' Requete'):
current['query'] = line.split(':', 1)[1].strip().strip('"')
elif line.startswith(' Attendu'):
current['expected'] = line.split(':', 1)[1].strip()
elif line.startswith(' Obtenu'):
current['obtained'] = line.split(':', 1)[1].strip()
elif line.startswith(' Args'):
current['args'] = line.split(':', 1)[1].strip()
elif line.startswith(' BUG'):
current['bug'] = line.split(':', 1)[1].strip()
# Scores par categorie
if line.startswith(' ') and ':' in line and 'PASS' in line and 'FAIL' in line and '//' not in line:
parts = line.strip().split(':')
if len(parts) >= 2:
cat = parts[0].strip()
rest = parts[1].strip()
# parse: "23 PASS 11 PARTIAL 0 RULE_MISS 1 FAIL / 35 tests [81%]"
try:
import re
m = re.match(
r'(\d+) PASS\s+(\d+) PARTIAL\s+(\d+) RULE_MISS\s+(\d+) FAIL\s+/\s+(\d+) tests\s+\[(\d+)%\]',
rest
)
if m:
scores[cat] = {
'pass': int(m.group(1)),
'partial': int(m.group(2)),
'rule_miss': int(m.group(3)),
'fail': int(m.group(4)),
'total': int(m.group(5)),
'pct': int(m.group(6)),
}
except Exception:
pass
i += 1
return tests, scores
def score_color(pct):
if pct >= 80:
return (34, 139, 34)
elif pct >= 60:
return (255, 165, 0)
else:
return (220, 20, 60)
class PDF(FPDF):
def header(self):
# Bandeau superieur
self.set_fill_color(30, 30, 50)
self.rect(0, 0, 210, 14, 'F')
self.set_text_color(200, 200, 255)
self.set_font('Helvetica', 'B', 10)
self.set_xy(10, 3)
self.cell(0, 8, 'Lyra RAG v2 - Campagne de Tests MCP', ln=0, align='L')
self.set_xy(0, 3)
self.set_font('Helvetica', '', 8)
self.set_text_color(150, 150, 200)
self.cell(200, 8, f'Genere le {datetime.now().strftime("%d/%m/%Y %H:%M")}', ln=0, align='R')
self.set_text_color(0, 0, 0)
self.set_y(16)
def footer(self):
self.set_y(-12)
self.set_fill_color(30, 30, 50)
self.rect(0, self.get_y(), 210, 12, 'F')
self.set_text_color(150, 150, 200)
self.set_font('Helvetica', '', 8)
self.cell(0, 12, f'Page {self.page_no()} / {{nb}}', align='C')
self.set_text_color(0, 0, 0)
def chapter_title(self, title, color=(30, 30, 80)):
self.set_fill_color(*color)
self.set_text_color(255, 255, 255)
self.set_font('Helvetica', 'B', 12)
self.cell(0, 8, f' {title}', ln=True, fill=True)
self.ln(3)
self.set_text_color(0, 0, 0)
def status_badge(self, status, x, y, w=28, h=6):
color = STATUS_COLORS.get(status, (128, 128, 128))
self.set_fill_color(*color)
self.set_text_color(255, 255, 255)
self.set_font('Helvetica', 'B', 7)
self.set_xy(x, y)
self.cell(w, h, status, align='C', fill=True, border=0)
self.set_text_color(0, 0, 0)
def score_bar(self, label, pct, total, pass_n, partial_n, rule_miss_n, fail_n, color):
bar_w = 130
bar_h = 6
x0 = 10
y0 = self.get_y()
# Label categorie
self.set_fill_color(*color)
self.set_text_color(255, 255, 255)
self.set_font('Helvetica', 'B', 8)
self.set_xy(x0, y0)
self.cell(30, bar_h, f' {label}', fill=True, border=0)
self.set_text_color(0, 0, 0)
# Barre proportionnelle
x_bar = x0 + 32
segments = [
(pass_n, STATUS_COLORS['PASS']),
(partial_n, STATUS_COLORS['PARTIAL']),
(rule_miss_n, STATUS_COLORS['RULE_MISS']),
(fail_n, STATUS_COLORS['FAIL']),
]
x_cur = x_bar
for count, col in segments:
if count > 0:
w = bar_w * count / total
self.set_fill_color(*col)
self.rect(x_cur, y0, w, bar_h, 'F')
x_cur += w
# Contour barre
self.set_draw_color(180, 180, 180)
self.rect(x_bar, y0, bar_w, bar_h)
# Score %
sc_col = score_color(pct)
self.set_text_color(*sc_col)
self.set_font('Helvetica', 'B', 9)
self.set_xy(x_bar + bar_w + 3, y0)
self.cell(20, bar_h, f'{pct}%', align='L')
# Compteurs
self.set_text_color(80, 80, 80)
self.set_font('Helvetica', '', 7)
self.set_xy(x_bar + bar_w + 24, y0)
self.cell(0, bar_h, f'{pass_n}P {partial_n}Pa {rule_miss_n}R {fail_n}F / {total}')
self.set_text_color(0, 0, 0)
self.ln(bar_h + 2)
def test_row(self, test, row_num):
status = test['status']
bg = (252, 252, 255) if row_num % 2 == 0 else (245, 245, 250)
# Fond leger selon status
if status == 'FAIL':
bg = (255, 240, 240)
elif status == 'RULE_MISS':
bg = (240, 245, 255)
y0 = self.get_y()
row_h = 18
# Fond
self.set_fill_color(*bg)
self.rect(10, y0, 190, row_h, 'F')
# Ligne de separation
self.set_draw_color(220, 220, 230)
self.line(10, y0, 200, y0)
# Badge status
self.status_badge(status, 10, y0 + 1, w=24, h=6)
# Description
self.set_font('Helvetica', 'B', 8)
self.set_text_color(30, 30, 60)
self.set_xy(36, y0 + 1)
desc = test['desc']
if len(desc) > 45:
desc = desc[:42] + '...'
self.cell(70, 6, desc, ln=0)
# Outil attendu/obtenu
self.set_font('Helvetica', '', 7)
self.set_text_color(80, 80, 80)
self.set_xy(108, y0 + 1)
expected = test['expected'].replace('fedora.', '').replace('catt.', '').replace('hue.', '').replace('denon.', '').replace('tv.', '')
if len(expected) > 25:
expected = expected[:22] + '...'
self.cell(50, 6, f'attendu: {expected}', ln=0)
# Requete
self.set_font('Helvetica', 'I', 7)
self.set_text_color(100, 100, 100)
self.set_xy(36, y0 + 8)
query = f'"{test["query"]}"'
if len(query) > 80:
query = query[:77] + '..."'
self.cell(155, 5, query, ln=0)
# Args
if test['args'] and test['args'] != '{}':
self.set_font('Helvetica', '', 6)
self.set_text_color(120, 120, 140)
self.set_xy(36, y0 + 13)
args_str = test['args']
if len(args_str) > 80:
args_str = args_str[:77] + '...'
self.cell(155, 5, args_str, ln=0)
# Bug note
if test.get('bug'):
self.set_font('Helvetica', 'B', 6)
self.set_text_color(200, 40, 40)
self.set_xy(108, y0 + 8)
bug_str = test['bug']
if len(bug_str) > 50:
bug_str = bug_str[:47] + '...'
self.cell(80, 5, f'BUG: {bug_str}', ln=0)
self.set_text_color(0, 0, 0)
self.set_xy(10, y0 + row_h)
def generate_pdf(tests, scores):
pdf = PDF(orientation='P', unit='mm', format='A4')
pdf.alias_nb_pages()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.set_margins(10, 16, 10)
# === PAGE DE GARDE ===
pdf.add_page()
# Titre principal
pdf.set_y(35)
pdf.set_fill_color(20, 20, 50)
pdf.rect(10, 30, 190, 40, 'F')
pdf.set_text_color(255, 255, 255)
pdf.set_font('Helvetica', 'B', 22)
pdf.set_xy(10, 38)
pdf.cell(190, 12, 'Campagne de Tests MCP', align='C', ln=True)
pdf.set_font('Helvetica', '', 13)
pdf.set_xy(10, 53)
pdf.cell(190, 8, 'Lyra RAG v2 - Pipeline _rule_based_detect()', align='C', ln=True)
pdf.set_text_color(0, 0, 0)
# Date et version
pdf.set_y(80)
pdf.set_font('Helvetica', '', 10)
pdf.set_text_color(80, 80, 80)
pdf.cell(0, 6, f'Date : {datetime.now().strftime("%d %B %Y")}', align='C', ln=True)
pdf.cell(0, 6, 'Environnement : Pipeline Lyra sans execution MCP reelle', align='C', ln=True)
pdf.set_text_color(0, 0, 0)
# Score global
total = sum(s['total'] for s in scores.values())
pass_n = sum(s['pass'] for s in scores.values())
partial_n = sum(s['partial'] for s in scores.values())
rule_miss_n = sum(s['rule_miss'] for s in scores.values())
fail_n = sum(s['fail'] for s in scores.values())
pct = round(100 * pass_n / total) if total else 0
pdf.set_y(100)
pdf.set_fill_color(240, 240, 250)
pdf.rect(30, pdf.get_y(), 150, 55, 'F')
pdf.set_draw_color(100, 100, 180)
pdf.rect(30, pdf.get_y(), 150, 55)
pdf.set_font('Helvetica', 'B', 14)
pdf.set_text_color(30, 30, 80)
pdf.set_xy(30, pdf.get_y() + 5)
pdf.cell(150, 8, 'SCORE GLOBAL', align='C', ln=True)
sc_col = score_color(pct)
pdf.set_text_color(*sc_col)
pdf.set_font('Helvetica', 'B', 36)
pdf.set_xy(30, pdf.get_y())
pdf.cell(150, 20, f'{pct}%', align='C', ln=True)
pdf.set_font('Helvetica', '', 10)
pdf.set_text_color(50, 50, 50)
pdf.set_xy(30, pdf.get_y())
pdf.cell(150, 6, f'{total} tests au total', align='C', ln=True)
# Compteurs status
y_cnt = pdf.get_y() + 2
items = [
('PASS', pass_n, STATUS_COLORS['PASS']),
('PARTIAL', partial_n, STATUS_COLORS['PARTIAL']),
('RULE_MISS', rule_miss_n, STATUS_COLORS['RULE_MISS']),
('FAIL', fail_n, STATUS_COLORS['FAIL']),
]
x_start = 35
for label, count, color in items:
pdf.set_fill_color(*color)
pdf.rect(x_start, y_cnt, 32, 10, 'F')
pdf.set_text_color(255, 255, 255)
pdf.set_font('Helvetica', 'B', 8)
pdf.set_xy(x_start, y_cnt)
pdf.cell(32, 5, label, align='C', ln=False)
pdf.set_xy(x_start, y_cnt + 5)
pdf.set_font('Helvetica', 'B', 10)
pdf.cell(32, 5, str(count), align='C', ln=False)
x_start += 35
pdf.set_text_color(0, 0, 0)
# Legende
pdf.set_y(170)
pdf.set_font('Helvetica', 'B', 10)
pdf.set_text_color(30, 30, 80)
pdf.cell(0, 6, 'Legende des statuts', ln=True)
pdf.set_y(pdf.get_y() + 1)
legend = [
('PASS', 'Outil correct + tous les arguments attendus detectes', STATUS_COLORS['PASS']),
('PARTIAL', 'Outil correct + args obligatoires OK + args optionnels manquants', STATUS_COLORS['PARTIAL']),
('RULE_MISS', 'Aucune regle statique ne matche - traite par EPHAISTOS (LLM) en production', STATUS_COLORS['RULE_MISS']),
('FAIL', 'Mauvais outil detecte ou argument obligatoire manquant - BUG reel', STATUS_COLORS['FAIL']),
]
for status, desc, color in legend:
y_l = pdf.get_y()
pdf.set_fill_color(*color)
pdf.rect(15, y_l, 22, 6, 'F')
pdf.set_text_color(255, 255, 255)
pdf.set_font('Helvetica', 'B', 7)
pdf.set_xy(15, y_l)
pdf.cell(22, 6, status, align='C')
pdf.set_text_color(50, 50, 50)
pdf.set_font('Helvetica', '', 8)
pdf.set_xy(40, y_l)
pdf.cell(0, 6, desc, ln=True)
pdf.set_text_color(0, 0, 0)
# Analyse narrative
pdf.set_y(220)
pdf.set_font('Helvetica', 'B', 10)
pdf.set_text_color(30, 30, 80)
pdf.cell(0, 6, 'Analyse', ln=True)
pdf.set_font('Helvetica', '', 8)
pdf.set_text_color(60, 60, 60)
analysis = [
"Les RULE_MISS correspondent aux outils TV, HUE complexes (brightness/color/scene),",
"CATT (cast_play/pause/stop...) qui ne sont pas couverts par _rule_based_detect.",
"Ces requetes sont traitees par EPHAISTOS (LLM Qwen 7B) en production - comportement attendu.",
"",
"Les PARTIAL indiquent que l'outil est correctement identifie mais que les arguments",
"optionnels mentionnes dans la requete ne sont pas extraits par les regles statiques.",
"Ex: 'vm_stop avec timeout 120' -> vm_stop(vm_name=X) sans timeout=120.",
"",
"Les 8 FAIL sont des bugs reels dans _rule_based_detect a corriger:",
"- Mots-cles 'force'/'detaille'/'live' captures comme vm_name par le regex",
"- 'lance commande' -> vm_start au lieu de vm_exec (collision 'lance')",
"- 'sourdine' -> power_on au lieu de denon.mute_on",
"- 'lance la scene' -> vm_start (collision 'lance')",
"- 'backup + verifie' -> backup_verify au lieu de backup_create",
]
for line in analysis:
pdf.cell(0, 4.5, line, ln=True)
pdf.set_text_color(0, 0, 0)
# === PAGE SCORES PAR CATEGORIE ===
pdf.add_page()
pdf.chapter_title('Scores par Categorie', color=(30, 30, 80))
pdf.ln(2)
pdf.set_font('Helvetica', '', 8)
pdf.set_text_color(80, 80, 80)
# Legende mini-barre
x_leg = 10
y_leg = pdf.get_y()
for label, color in [('PASS', STATUS_COLORS['PASS']), ('PARTIAL', STATUS_COLORS['PARTIAL']),
('RULE_MISS', STATUS_COLORS['RULE_MISS']), ('FAIL', STATUS_COLORS['FAIL'])]:
pdf.set_fill_color(*color)
pdf.rect(x_leg, y_leg, 8, 4, 'F')
pdf.set_xy(x_leg + 9, y_leg)
pdf.cell(20, 4, label)
x_leg += 38
pdf.set_text_color(0, 0, 0)
pdf.ln(8)
# Barres par categorie
ordered = sorted(scores.items(), key=lambda x: -x[1]['pct'])
for cat, s in ordered:
color = CATEGORY_COLORS.get(cat, (100, 100, 100))
pdf.score_bar(
cat, s['pct'], s['total'],
s['pass'], s['partial'], s['rule_miss'], s['fail'],
color
)
pdf.ln(1)
# Graphique synthese (grille simple)
pdf.ln(6)
pdf.set_font('Helvetica', 'B', 10)
pdf.set_text_color(30, 30, 80)
pdf.cell(0, 6, 'Distribution globale des resultats', ln=True)
pdf.ln(2)
# Grand barre globale
bar_w = 180
y0 = pdf.get_y()
x0 = 10
total_all = pass_n + partial_n + rule_miss_n + fail_n
segs = [(pass_n, 'PASS'), (partial_n, 'PARTIAL'), (rule_miss_n, 'RULE_MISS'), (fail_n, 'FAIL')]
x_cur = x0
for count, status in segs:
w = bar_w * count / total_all if total_all else 0
color = STATUS_COLORS[status]
pdf.set_fill_color(*color)
pdf.rect(x_cur, y0, w, 12, 'F')
if w > 12:
pdf.set_text_color(255, 255, 255)
pdf.set_font('Helvetica', 'B', 7)
pdf.set_xy(x_cur, y0 + 3)
pct_seg = round(100 * count / total_all)
pdf.cell(w, 6, f'{pct_seg}%', align='C')
x_cur += w
pdf.set_draw_color(100, 100, 100)
pdf.rect(x0, y0, bar_w, 12)
pdf.set_text_color(0, 0, 0)
pdf.ln(18)
# Tableau recap
pdf.set_font('Helvetica', 'B', 9)
pdf.set_fill_color(30, 30, 80)
pdf.set_text_color(255, 255, 255)
cols = ['Categorie', 'PASS', 'PARTIAL', 'RULE_MISS', 'FAIL', 'Total', 'Score']
widths = [35, 22, 22, 28, 20, 22, 22]
for col, w in zip(cols, widths):
pdf.cell(w, 6, col, align='C', fill=True, border=1)
pdf.ln()
for i, (cat, s) in enumerate(sorted(scores.items())):
bg = (248, 248, 255) if i % 2 == 0 else (235, 235, 248)
pdf.set_fill_color(*bg)
pdf.set_text_color(30, 30, 60)
pdf.set_font('Helvetica', 'B', 8)
pdf.cell(widths[0], 6, cat, fill=True, border=1)
pdf.set_font('Helvetica', '', 8)
pdf.set_text_color(34, 139, 34)
pdf.cell(widths[1], 6, str(s['pass']), align='C', fill=True, border=1)
pdf.set_text_color(255, 165, 0)
pdf.cell(widths[2], 6, str(s['partial']), align='C', fill=True, border=1)
pdf.set_text_color(100, 149, 237)
pdf.cell(widths[3], 6, str(s['rule_miss']), align='C', fill=True, border=1)
pdf.set_text_color(220, 20, 60)
pdf.cell(widths[4], 6, str(s['fail']), align='C', fill=True, border=1)
pdf.set_text_color(50, 50, 50)
pdf.cell(widths[5], 6, str(s['total']), align='C', fill=True, border=1)
sc_c = score_color(s['pct'])
pdf.set_text_color(*sc_c)
pdf.set_font('Helvetica', 'B', 8)
pdf.cell(widths[6], 6, f"{s['pct']}%", align='C', fill=True, border=1)
pdf.set_text_color(0, 0, 0)
pdf.ln()
# Ligne TOTAL
pdf.set_fill_color(20, 20, 50)
pdf.set_text_color(255, 255, 255)
pdf.set_font('Helvetica', 'B', 8)
pdf.cell(widths[0], 6, 'TOTAL', fill=True, border=1)
pdf.set_text_color(180, 255, 180)
pdf.cell(widths[1], 6, str(pass_n), align='C', fill=True, border=1)
pdf.set_text_color(255, 220, 150)
pdf.cell(widths[2], 6, str(partial_n), align='C', fill=True, border=1)
pdf.set_text_color(180, 200, 255)
pdf.cell(widths[3], 6, str(rule_miss_n), align='C', fill=True, border=1)
pdf.set_text_color(255, 150, 150)
pdf.cell(widths[4], 6, str(fail_n), align='C', fill=True, border=1)
pdf.set_text_color(220, 220, 220)
pdf.cell(widths[5], 6, str(total), align='C', fill=True, border=1)
sc_c = score_color(pct)
pdf.set_text_color(*[min(255, c + 80) for c in sc_c])
pdf.cell(widths[6], 6, f'{pct}%', align='C', fill=True, border=1)
pdf.set_text_color(0, 0, 0)
pdf.ln()
# === PAGES TESTS DETAILLES PAR CATEGORIE ===
categories_order = ['FEDORA', 'BACKUP', 'DENON', 'TV', 'HUE', 'CATT', 'EDGE']
for cat in categories_order:
cat_tests = [t for t in tests if t.get('category') == cat]
if not cat_tests:
continue
pdf.add_page()
color = CATEGORY_COLORS.get(cat, (80, 80, 80))
pdf.chapter_title(f'Tests {cat} ({len(cat_tests)} tests)', color=color)
# Mini-stats
cat_pass = sum(1 for t in cat_tests if t['status'] == 'PASS')
cat_partial = sum(1 for t in cat_tests if t['status'] == 'PARTIAL')
cat_rule_miss = sum(1 for t in cat_tests if t['status'] == 'RULE_MISS')
cat_fail = sum(1 for t in cat_tests if t['status'] == 'FAIL')
cat_score = scores.get(cat, {}).get('pct', 0)
pdf.set_font('Helvetica', '', 8)
pdf.set_text_color(80, 80, 80)
stats_line = (
f'PASS: {cat_pass} | PARTIAL: {cat_partial} | '
f'RULE_MISS: {cat_rule_miss} | FAIL: {cat_fail} | Score: {cat_score}%'
)
pdf.cell(0, 5, stats_line, ln=True)
pdf.set_text_color(0, 0, 0)
pdf.ln(2)
# En-tete colonnes
pdf.set_fill_color(*[min(255, c + 80) for c in color])
pdf.set_text_color(30, 30, 60)
pdf.set_font('Helvetica', 'B', 7)
pdf.cell(26, 5, 'Statut', fill=True, border=0, align='C')
pdf.cell(72, 5, 'Description / Requete', fill=True, border=0)
pdf.cell(52, 5, 'Outil / Bug', fill=True, border=0)
pdf.cell(38, 5, 'Args extraits', fill=True, border=0)
pdf.set_text_color(0, 0, 0)
pdf.ln(5)
for row_num, test in enumerate(cat_tests):
if pdf.get_y() > 270:
pdf.add_page()
pdf.chapter_title(f'Tests {cat} (suite)', color=color)
pdf.ln(2)
pdf.test_row(test, row_num)
# === PAGE BUGS ===
fail_tests = [t for t in tests if t['status'] == 'FAIL']
if fail_tests:
pdf.add_page()
pdf.chapter_title('Bugs Detectes (FAIL)', color=(180, 20, 20))
pdf.set_font('Helvetica', '', 8)
pdf.set_text_color(80, 20, 20)
pdf.cell(0, 5, f'{len(fail_tests)} bugs reels identifies dans _rule_based_detect() a corriger.', ln=True)
pdf.set_text_color(0, 0, 0)
pdf.ln(3)
for i, test in enumerate(fail_tests, 1):
y0 = pdf.get_y()
# Numero + fond
pdf.set_fill_color(255, 235, 235)
pdf.rect(10, y0, 190, 26, 'F')
pdf.set_draw_color(220, 50, 50)
pdf.rect(10, y0, 190, 26)
# Numero
pdf.set_fill_color(220, 20, 60)
pdf.rect(10, y0, 10, 26, 'F')
pdf.set_text_color(255, 255, 255)
pdf.set_font('Helvetica', 'B', 10)
pdf.set_xy(10, y0 + 9)
pdf.cell(10, 8, str(i), align='C')
# Details
pdf.set_text_color(30, 30, 30)
pdf.set_font('Helvetica', 'B', 9)
pdf.set_xy(22, y0 + 2)
pdf.cell(0, 5, f'[{test["category"]}] {test["desc"]}', ln=True)
pdf.set_font('Helvetica', 'I', 7)
pdf.set_text_color(80, 80, 80)
pdf.set_xy(22, y0 + 8)
pdf.cell(0, 4, f'Requete : "{test["query"]}"', ln=True)
pdf.set_font('Helvetica', '', 7)
pdf.set_xy(22, y0 + 13)
pdf.set_text_color(150, 0, 0)
pdf.cell(45, 4, f'Attendu : {test["expected"]}', ln=False)
pdf.set_text_color(200, 50, 0)
pdf.cell(45, 4, f'Obtenu : {test["obtained"]}', ln=False)
if test.get('bug'):
pdf.set_text_color(180, 0, 0)
pdf.set_font('Helvetica', 'B', 7)
pdf.set_xy(22, y0 + 19)
pdf.cell(0, 4, f'Cause : {test["bug"]}', ln=True)
pdf.set_text_color(0, 0, 0)
pdf.set_xy(10, y0 + 28)
pdf.output(PDF_PATH)
print(f'PDF genere : {PDF_PATH}')
return PDF_PATH
if __name__ == '__main__':
tests, scores = parse_report(REPORT_PATH)
print(f'Tests charges : {len(tests)}')
print(f'Categories : {list(scores.keys())}')
generate_pdf(tests, scores)