""" Génération de PDFs propres à partir des données OGC extraites Reproduit fidèlement le formulaire "Fiche médicale de recueil du praticien conseil" """ import os import json import glob from reportlab.lib import colors from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm, cm from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.platypus import ( SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, KeepTogether ) from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT # ============================================================ # Styles # ============================================================ PAGE_W, PAGE_H = A4 # 210 x 297 mm styles = getSampleStyleSheet() STYLE_TITLE = ParagraphStyle( "title_ogc", parent=styles["Normal"], fontSize=9, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11, ) STYLE_SUBTITLE = ParagraphStyle( "subtitle_ogc", parent=styles["Normal"], fontSize=6.5, fontName="Helvetica-Oblique", alignment=TA_CENTER, leading=8, ) STYLE_HEADER = ParagraphStyle( "header_ogc", parent=styles["Normal"], fontSize=7.5, fontName="Helvetica-Bold", alignment=TA_LEFT, leading=9, ) STYLE_CELL = ParagraphStyle( "cell_ogc", parent=styles["Normal"], fontSize=7, fontName="Helvetica", alignment=TA_CENTER, leading=9, ) STYLE_CELL_LEFT = ParagraphStyle( "cell_left_ogc", parent=styles["Normal"], fontSize=7, fontName="Helvetica", alignment=TA_LEFT, leading=9, ) STYLE_CELL_BOLD = ParagraphStyle( "cell_bold_ogc", parent=styles["Normal"], fontSize=7, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=9, ) STYLE_CELL_SMALL = ParagraphStyle( "cell_small_ogc", parent=styles["Normal"], fontSize=6, fontName="Helvetica", alignment=TA_CENTER, leading=7.5, ) STYLE_SECTION = ParagraphStyle( "section_ogc", parent=styles["Normal"], fontSize=7.5, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=9, backColor=colors.Color(0.85, 0.85, 0.85), ) STYLE_BODY = ParagraphStyle( "body_ogc", parent=styles["Normal"], fontSize=7, fontName="Helvetica", alignment=TA_LEFT, leading=9, spaceBefore=2, spaceAfter=2, ) STYLE_FOOTER = ParagraphStyle( "footer_ogc", parent=styles["Normal"], fontSize=5.5, fontName="Helvetica-Oblique", alignment=TA_LEFT, leading=7, ) # Couleurs GREY_BG = colors.Color(0.90, 0.90, 0.90) DARK_HEADER = colors.Color(0.18, 0.31, 0.59) WHITE = colors.white BLACK = colors.black def P(text, style=STYLE_CELL): """Raccourci pour créer un Paragraph""" return Paragraph(str(text) if text else "", style) def checkbox(checked=False): """Retourne un caractère checkbox via symbole textuel""" return "[X]" if checked else "[ ]" DECISION_LABELS = { "maintien_avis_controleur": "Maintien de l'avis initial du médecin contrôleur", "retour_groupage_dim": "Retour groupage initial DIM", "autre_groupage": "Autre groupage", } # ============================================================ # Page Recueil # ============================================================ def build_page_recueil(p): """Construit la page recueil à partir des données parsed""" elements = [] # --- Titre --- elements.append(P("FICHE MÉDICALE DE RECUEIL DU PRATICIEN CONSEIL (une fiche par RUM)", STYLE_TITLE)) elements.append(P("(à transmettre au médecin du DIM avant concertation)
Seul le recodage impactant la facturation est renseigné", STYLE_SUBTITLE)) elements.append(Spacer(1, 3 * mm)) # --- En-tête établissement --- header_data = [ [ P(f"Établissement : {p.get('etablissement', '')}", STYLE_CELL_LEFT), P(f"FINESS : {p.get('finess', '')}", STYLE_CELL_LEFT), P(f"Date début contrôle : {p.get('date_debut_controle', '')}", STYLE_CELL_LEFT), ], ] t = Table(header_data, colWidths=[85 * mm, 50 * mm, 55 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.5, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2), ])) elements.append(t) # --- N° champ + libellé + N° OGC --- row2_data = [ [ P(f"N° champ : {p.get('n_champ', '')}", STYLE_CELL_LEFT), P(f"Libellé champ de contrôle : Champ 1 : séjours ayant des caractéristiques communes
séjours de niveau 2 3 et 4 mono RUM", STYLE_CELL_LEFT), P(f"N° OGC : {p.get('n_ogc', '')}", STYLE_CELL_LEFT), ], ] t = Table(row2_data, colWidths=[30 * mm, 130 * mm, 30 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.5, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2), ])) elements.append(t) # --- Dates du séjour --- elements.append(Spacer(1, 1 * mm)) dates_data = [[ P(f"Dossier manquant : 0", STYLE_CELL_LEFT), P(f"Dates du séjour : {p.get('dates_sejour', '')}", STYLE_CELL_LEFT), ]] t = Table(dates_data, colWidths=[50 * mm, 140 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.5, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2), ])) elements.append(t) elements.append(Spacer(1, 2 * mm)) # --- Données du séjour --- se = p.get("sejour_etab", {}) sr = p.get("sejour_reco", {}) col_hdrs = ["", "Âge", "Sexe", "Date\nnais.", "Durée\nséjour", "Mode\nentrée", "Prove-\nnance", "Mode\nsortie", "Desti-\nnation", "Nb RUM", "Nb DPI", "Nb\nséances", "Psy\nE&B", "Psy\nD.S"] col_widths = [22 * mm] + [12 * mm] * 13 sejour_data = [ [P("Données du\nséjour", STYLE_CELL_SMALL)] + [P(h, STYLE_CELL_SMALL) for h in col_hdrs[1:]], [P("Établissement", STYLE_CELL_SMALL), P(se.get("age", ""), STYLE_CELL), P(se.get("sexe", ""), STYLE_CELL), P("", STYLE_CELL), P(se.get("duree_sejour", ""), STYLE_CELL), P(se.get("mode_entree", ""), STYLE_CELL), P(se.get("provenance", ""), STYLE_CELL), P(se.get("mode_sortie", ""), STYLE_CELL), P(se.get("destination", ""), STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL)], [P("Recodage", STYLE_CELL_SMALL), P(sr.get("age", ""), STYLE_CELL), P(sr.get("sexe", ""), STYLE_CELL), P("", STYLE_CELL), P(sr.get("duree_sejour", ""), STYLE_CELL), P(sr.get("mode_entree", ""), STYLE_CELL), P(sr.get("provenance", ""), STYLE_CELL), P(sr.get("mode_sortie", ""), STYLE_CELL), P(sr.get("destination", ""), STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL), P("", STYLE_CELL)], ] t = Table(sejour_data, colWidths=col_widths) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("BACKGROUND", (0, 0), (-1, 0), GREY_BG), ("BACKGROUND", (0, 1), (0, 2), GREY_BG), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ])) elements.append(t) elements.append(Spacer(1, 2 * mm)) # --- Données du RUM --- rum = p.get("rum_etab", {}) rum_header = [ [P("Données du RUM", STYLE_CELL_SMALL), P("Éts détails SP", STYLE_CELL_SMALL), P("UM", STYLE_CELL_SMALL), P("IGS II", STYLE_CELL_SMALL), P("Durée RUM", STYLE_CELL_SMALL), P("Nature\nsuppl.", STYLE_CELL_SMALL), P("Nb\nsuppl.", STYLE_CELL_SMALL)], ] t = Table(rum_header, colWidths=[30 * mm, 30 * mm, 25 * mm, 25 * mm, 30 * mm, 25 * mm, 25 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("BACKGROUND", (0, 0), (-1, 0), GREY_BG), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ])) elements.append(t) # Lignes RUM Établissement et Recodage rum_lines = [ [P(f"N° RUM Établissement : 1/1", STYLE_CELL_LEFT), P("0", STYLE_CELL), P(rum.get("um", ""), STYLE_CELL), P(rum.get("igs", ""), STYLE_CELL), P(rum.get("duree", ""), STYLE_CELL), P(f"{rum.get('dates', '')}", STYLE_CELL_LEFT)], [P(f"N° RUM Recodage : 1/1", STYLE_CELL_LEFT), P("0", STYLE_CELL), P(rum.get("um", ""), STYLE_CELL), P("", STYLE_CELL), P(rum.get("duree", ""), STYLE_CELL), P(f"{rum.get('dates', '')}", STYLE_CELL_LEFT)], ] t = Table(rum_lines, colWidths=[45 * mm, 15 * mm, 25 * mm, 25 * mm, 25 * mm, 55 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ])) elements.append(t) elements.append(Spacer(1, 2 * mm)) # --- Codage --- ce = p.get("codage_etab", {}) cr = p.get("codage_reco", {}) # Section header codage_header = [[ P("Codage de l'Établissement", STYLE_SECTION), P("Recodage", STYLE_SECTION), ]] t = Table(codage_header, colWidths=[150 * mm, 40 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.5, BLACK), ("BACKGROUND", (0, 0), (-1, 0), GREY_BG), ])) elements.append(t) # DP dp_data = [ [P("DP", STYLE_CELL_BOLD), P(ce.get("dp", ""), STYLE_CELL), P(ce.get("dp_libelle", ""), STYLE_CELL_LEFT), P(cr.get("dp", ""), STYLE_CELL)], ] t = Table(dp_data, colWidths=[15 * mm, 25 * mm, 110 * mm, 40 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ])) elements.append(t) # DR dr_data = [ [P("DR", STYLE_CELL_BOLD), P(ce.get("dr", ""), STYLE_CELL), P("", STYLE_CELL_LEFT), P(cr.get("dr", ""), STYLE_CELL)], ] t = Table(dr_data, colWidths=[15 * mm, 25 * mm, 110 * mm, 40 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ])) elements.append(t) # DAS das_etab = ce.get("das", []) das_reco = cr.get("das", []) max_das = max(len(das_etab), len(das_reco), 1) das_rows = [[P("DAS", STYLE_CELL_BOLD), P("Code", STYLE_CELL_SMALL), P("Pos", STYLE_CELL_SMALL), P("Libellé", STYLE_CELL_SMALL), P("Code", STYLE_CELL_SMALL), P("Pos", STYLE_CELL_SMALL)]] for i in range(max_das): de = das_etab[i] if i < len(das_etab) else {} dr_item = das_reco[i] if i < len(das_reco) else {} if isinstance(de, str): de = {"code": de} if isinstance(dr_item, str): dr_item = {"code": dr_item} das_rows.append([ P("", STYLE_CELL), P(de.get("code", ""), STYLE_CELL), P(de.get("position", ""), STYLE_CELL), P(de.get("libelle", ""), STYLE_CELL_LEFT), P(dr_item.get("code", ""), STYLE_CELL), P(dr_item.get("position", ""), STYLE_CELL), ]) # Lignes vides si moins de 6 DAS for _ in range(max(0, 6 - max_das)): das_rows.append([P("", STYLE_CELL)] * 6) t = Table(das_rows, colWidths=[15 * mm, 25 * mm, 12 * mm, 98 * mm, 25 * mm, 15 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("BACKGROUND", (0, 0), (-1, 0), GREY_BG), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ("SPAN", (0, 0), (0, 0)), ])) elements.append(t) elements.append(Spacer(1, 2 * mm)) # --- Actes --- actes_etab = p.get("actes_etab", []) actes_reco = p.get("actes_reco", []) max_actes = max(len(actes_etab), len(actes_reco), 1) actes_rows = [[P("Actes", STYLE_CELL_BOLD), P("Code", STYLE_CELL_SMALL), P("Pos", STYLE_CELL_SMALL), P("Libellé", STYLE_CELL_SMALL), P("Code", STYLE_CELL_SMALL), P("Pos", STYLE_CELL_SMALL)]] for i in range(max_actes): ae = actes_etab[i] if i < len(actes_etab) else {} ar = actes_reco[i] if i < len(actes_reco) else {} if isinstance(ae, str): ae = {"code": ae} if isinstance(ar, str): ar = {"code": ar} actes_rows.append([ P("", STYLE_CELL), P(ae.get("code", ""), STYLE_CELL), P(ae.get("position", ae.get("quantite", "")), STYLE_CELL), P(ae.get("libelle", ""), STYLE_CELL_LEFT), P(ar.get("code", ""), STYLE_CELL), P(ar.get("position", ar.get("quantite", "")), STYLE_CELL), ]) # Lignes vides for _ in range(max(0, 4 - max_actes)): actes_rows.append([P("", STYLE_CELL)] * 6) t = Table(actes_rows, colWidths=[15 * mm, 25 * mm, 12 * mm, 98 * mm, 25 * mm, 15 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("BACKGROUND", (0, 0), (-1, 0), GREY_BG), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 1), ("BOTTOMPADDING", (0, 0), (-1, -1), 1), ])) elements.append(t) elements.append(Spacer(1, 2 * mm)) # --- GHM / GHS --- ghm_data = [[ P(f"GHM établissement : {p.get('ghm_etab', '')}", STYLE_CELL_LEFT), P(f"GHS établissement : {p.get('ghs_etab', '')}", STYLE_CELL_LEFT), P(f"GHM après recodage : {p.get('ghm_reco', '')}", STYLE_CELL_LEFT), P(f"GHS après recodage : {p.get('ghs_reco', '')}", STYLE_CELL_LEFT), ]] t = Table(ghm_data, colWidths=[50 * mm, 45 * mm, 50 * mm, 45 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2), ])) elements.append(t) # --- Praticien conseil --- elements.append(Spacer(1, 1 * mm)) prat_data = [[ P("Praticien conseil", STYLE_CELL_LEFT), P("", STYLE_CELL), P("Médecin DIM", STYLE_CELL_LEFT), ]] t = Table(prat_data, colWidths=[50 * mm, 90 * mm, 50 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ])) elements.append(t) # --- Décisions --- elements.append(Spacer(1, 1 * mm)) is_accord = p.get("accord_desaccord", "") == "accord" is_desaccord = p.get("accord_desaccord", "") == "désaccord" reco_imp = p.get("recodage_impactant", "") ghs_inj = p.get("ghs_injustifie", "") decisions_data = [ [P(f"Recodage impactant la facturation : {reco_imp}", STYLE_CELL_LEFT), P(f"Accord {checkbox(is_accord)} Désaccord {checkbox(is_desaccord)}", STYLE_CELL_LEFT)], [P(f"GHS injustifié : {ghs_inj}", STYLE_CELL_LEFT), P("", STYLE_CELL)], ] t = Table(decisions_data, colWidths=[110 * mm, 80 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.3, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2), ])) elements.append(t) # --- Nom praticien --- elements.append(Spacer(1, 1 * mm)) prat_nom_data = [[ P(f"Nom du praticien conseil responsable du codage :", STYLE_CELL_LEFT), ]] t = Table(prat_nom_data, colWidths=[190 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ])) elements.append(t) prat_val_data = [[P(f"{p.get('praticien_conseil', '')}", STYLE_CELL_LEFT)]] t = Table(prat_val_data, colWidths=[190 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3), ])) elements.append(t) # --- Footer --- elements.append(Spacer(1, 2 * mm)) elements.append(P( "En fonction des DR/DR et actes retenus par le PC, seul le recodage d'une des CMA les plus élevées ayant une incidence " "sur le GHM est sur la facturation des suppléments
" "sera renseigné. Hors RCI injustifié avec actes externes, seuls les actes classants seront recueillis.", STYLE_FOOTER )) return elements # ============================================================ # Page Concertation 2/2 # ============================================================ def build_page_concertation_2(p2): """Page décision finale concertation""" elements = [] elements.append(P("CONCERTATION 2/2 — Décision finale", STYLE_TITLE)) elements.append(Spacer(1, 5 * mm)) # GHS ghs_data = [[ P(f"GHS initial : {p2.get('ghs_initial', '')}", STYLE_CELL_LEFT), P(f"GHS avant concertation : {p2.get('ghs_avant_concertation', '')}", STYLE_CELL_LEFT), P(f"GHS final après concertation : {p2.get('ghs_final', '')}", STYLE_CELL_LEFT), ]] t = Table(ghs_data, colWidths=[63 * mm, 63 * mm, 64 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("INNERGRID", (0, 0), (-1, -1), 0.5, BLACK), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3), ])) elements.append(t) elements.append(Spacer(1, 3 * mm)) # Décision decision_raw = p2.get("decision", "") decision = DECISION_LABELS.get(decision_raw, decision_raw) dec_data = [[ P(f"Décision : {decision}", STYLE_CELL_LEFT), ]] t = Table(dec_data, colWidths=[190 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("TOPPADDING", (0, 0), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 3), ])) elements.append(t) elements.append(Spacer(1, 3 * mm)) # Date date_conc = p2.get("date_concertation", "") if date_conc: elements.append(P(f"Date de concertation : {date_conc}", STYLE_CELL_LEFT)) return elements # ============================================================ # Page Concertation 1/2 (Argumentaire) # ============================================================ def build_page_concertation_1(p1): """Page argumentaire""" elements = [] elements.append(P("CONCERTATION 1/2 — Argumentaire du médecin contrôleur", STYLE_TITLE)) elements.append(Spacer(1, 5 * mm)) date_conc = p1.get("date_concertation", "") if date_conc: elements.append(P(f"Date : {date_conc}", STYLE_CELL_LEFT)) elements.append(Spacer(1, 3 * mm)) arg = p1.get("argumentaire", "") if arg: # Nettoyer les séparateurs | et formater arg_clean = arg.replace(" | ", "\n").replace("| ", "\n") elements.append(P(f"Argumentaire :", STYLE_CELL_LEFT)) elements.append(Spacer(1, 2 * mm)) arg_data = [[P(arg_clean, STYLE_BODY)]] t = Table(arg_data, colWidths=[190 * mm]) t.setStyle(TableStyle([ ("BOX", (0, 0), (-1, -1), 0.5, BLACK), ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), ("LEFTPADDING", (0, 0), (-1, -1), 5), ("RIGHTPADDING", (0, 0), (-1, -1), 5), ])) elements.append(t) return elements # ============================================================ # Génération du PDF complet # ============================================================ def generate_ogc_pdf(result, output_path): """Génère un PDF propre à partir des données extraites""" doc = SimpleDocTemplate( output_path, pagesize=A4, leftMargin=10 * mm, rightMargin=10 * mm, topMargin=10 * mm, bottomMargin=10 * mm, ) elements = [] # Page recueil rec = result.get("recueil") if rec: p = rec.get("parsed", {}) elements.extend(build_page_recueil(p)) # Page concertation (argumentaire + décision sur une seule page) c1 = result.get("concertation_1") c2 = result.get("concertation_2") if c1 or c2: from reportlab.platypus import PageBreak elements.append(PageBreak()) if c1: p1 = c1.get("parsed", {}) elements.extend(build_page_concertation_1(p1)) if c2: elements.append(Spacer(1, 5 * mm)) p2 = c2.get("parsed", {}) elements.extend(build_page_concertation_2(p2)) doc.build(elements) # ============================================================ # Main # ============================================================ if __name__ == "__main__": output_dir = "/home/dom/ai/Aivanov_scan_ogc/output" pdf_output_dir = os.path.join(output_dir, "pdf_propres") os.makedirs(pdf_output_dir, exist_ok=True) json_files = sorted(glob.glob(os.path.join(output_dir, "OGC *.json"))) print(f"Génération de {len(json_files)} PDFs propres...") for json_path in json_files: basename = os.path.splitext(os.path.basename(json_path))[0] with open(json_path, "r", encoding="utf-8") as f: result = json.load(f) pdf_path = os.path.join(pdf_output_dir, f"{basename} - propre.pdf") try: generate_ogc_pdf(result, pdf_path) print(f" ✓ {basename}") except Exception as e: print(f" ✗ {basename}: {e}") import traceback traceback.print_exc() print(f"\nPDFs générés dans: {pdf_output_dir}")