feat: extraction OGC et génération de PDFs propres
Pipeline complet pour extraire les données structurées des fiches OGC scannées (recueil praticien conseil + concertation) et générer des PDFs propres et lisibles à partir des JSON extraits. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
615
generate_pdf.py
Normal file
615
generate_pdf.py
Normal file
@@ -0,0 +1,615 @@
|
||||
"""
|
||||
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 "<b>[X]</b>" 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)<br/>Seul le recodage impactant la facturation est renseigné", STYLE_SUBTITLE))
|
||||
elements.append(Spacer(1, 3 * mm))
|
||||
|
||||
# --- En-tête établissement ---
|
||||
header_data = [
|
||||
[
|
||||
P(f"<b>Établissement :</b> {p.get('etablissement', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>FINESS :</b> {p.get('finess', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>Date début contrôle :</b> {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"<b>N° champ :</b> {p.get('n_champ', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>Libellé champ de contrôle :</b> Champ 1 : séjours ayant des caractéristiques communes<br/>séjours de niveau 2 3 et 4 mono RUM", STYLE_CELL_LEFT),
|
||||
P(f"<b>N° OGC :</b> {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"<b>Dossier manquant :</b> 0", STYLE_CELL_LEFT),
|
||||
P(f"<b>Dates du séjour :</b> {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("<b>Données du\nséjour</b>", STYLE_CELL_SMALL)] + [P(h, STYLE_CELL_SMALL) for h in col_hdrs[1:]],
|
||||
[P("<b>Établissement</b>", 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("<b>Recodage</b>", 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("<b>Données du RUM</b>", STYLE_CELL_SMALL),
|
||||
P("Éts détails SP", STYLE_CELL_SMALL),
|
||||
P("<b>UM</b>", STYLE_CELL_SMALL),
|
||||
P("<b>IGS II</b>", STYLE_CELL_SMALL),
|
||||
P("<b>Durée RUM</b>", 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"<b>N° RUM Établissement : 1/1</b>", 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"<b>N° RUM Recodage : 1/1</b>", 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("<b>Codage de l'Établissement</b>", STYLE_SECTION),
|
||||
P("<b>Recodage</b>", 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("<b>DP</b>", 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("<b>DR</b>", 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("<b>DAS</b>", STYLE_CELL_BOLD),
|
||||
P("<b>Code</b>", STYLE_CELL_SMALL), P("<b>Pos</b>", STYLE_CELL_SMALL),
|
||||
P("<b>Libellé</b>", STYLE_CELL_SMALL),
|
||||
P("<b>Code</b>", STYLE_CELL_SMALL), P("<b>Pos</b>", 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("<b>Actes</b>", STYLE_CELL_BOLD),
|
||||
P("<b>Code</b>", STYLE_CELL_SMALL), P("<b>Pos</b>", STYLE_CELL_SMALL),
|
||||
P("<b>Libellé</b>", STYLE_CELL_SMALL),
|
||||
P("<b>Code</b>", STYLE_CELL_SMALL), P("<b>Pos</b>", 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"<b>GHM établissement :</b> {p.get('ghm_etab', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>GHS établissement :</b> {p.get('ghs_etab', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>GHM après recodage :</b> {p.get('ghm_reco', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>GHS après recodage :</b> {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("<b>Praticien conseil</b>", STYLE_CELL_LEFT),
|
||||
P("", STYLE_CELL),
|
||||
P("<b>Médecin DIM</b>", 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"<b>Recodage impactant la facturation :</b> {reco_imp}", STYLE_CELL_LEFT),
|
||||
P(f"<b>Accord</b> {checkbox(is_accord)} <b>Désaccord</b> {checkbox(is_desaccord)}", STYLE_CELL_LEFT)],
|
||||
[P(f"<b>GHS injustifié :</b> {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"<b>Nom du praticien conseil responsable du codage :</b>", 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"<b>{p.get('praticien_conseil', '')}</b>", 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<br/>"
|
||||
"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("<b>CONCERTATION 2/2 — Décision finale</b>", STYLE_TITLE))
|
||||
elements.append(Spacer(1, 5 * mm))
|
||||
|
||||
# GHS
|
||||
ghs_data = [[
|
||||
P(f"<b>GHS initial :</b> {p2.get('ghs_initial', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>GHS avant concertation :</b> {p2.get('ghs_avant_concertation', '')}", STYLE_CELL_LEFT),
|
||||
P(f"<b>GHS final après concertation :</b> {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"<b>Décision :</b> {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"<b>Date de concertation :</b> {date_conc}", STYLE_CELL_LEFT))
|
||||
|
||||
return elements
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Page Concertation 1/2 (Argumentaire)
|
||||
# ============================================================
|
||||
|
||||
def build_page_concertation_1(p1):
|
||||
"""Page argumentaire"""
|
||||
elements = []
|
||||
elements.append(P("<b>CONCERTATION 1/2 — Argumentaire du médecin contrôleur</b>", STYLE_TITLE))
|
||||
elements.append(Spacer(1, 5 * mm))
|
||||
|
||||
date_conc = p1.get("date_concertation", "")
|
||||
if date_conc:
|
||||
elements.append(P(f"<b>Date :</b> {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"<b>Argumentaire :</b>", 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}")
|
||||
Reference in New Issue
Block a user