sostituisce pylatex con reportlab: genera PDF senza LaTeX di sistema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 4.6 committed 2026-05-14 10:15:14 +02:00
1 parent ec799466cf
commit 777b3de338
2 files changed
+140 -121

No files matched your search

+139 -120
View File
@@ -1,152 +1,171 @@
from pathlib import Path
from pylatex import (Document, Section, Subsection, Command, Figure,
Tabular, Math, NoEscape, Package)
from pylatex.utils import bold
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table,
TableStyle, Image, HRFlowable, PageBreak)
BASE = Path(__file__).parent
INDUTTORI = [
{
"nome": "Induttore 1",
"nome": "Induttore 1",
"intervallo": "110.81 – 111.56 s",
"t0": "110.81",
"t_end": "111.56",
"n_punti": "5",
"zona_exc": None,
"A": "87.3140",
"A_err": "0.0260",
"tau": "15.7231",
"tau_err": "0.2021",
"r2": "0.999505",
"png": BASE / "induttore1" / "induttore1.png",
"t0": "110.81",
"t_end": "111.56",
"n_punti": "5",
"zona_exc": None,
"A": "87.3140",
"A_err": "0.0260",
"tau": "15.7231",
"tau_err": "0.2021",
"r2": "0.999505",
"png": BASE / "induttore1" / "induttore1.png",
},
{
"nome": "Induttore 2",
"nome": "Induttore 2",
"intervallo": "112.02 – 113.95 s",
"t0": "112.02",
"t_end": "113.95",
"n_punti": "13",
"zona_exc": None,
"A": "158.3430",
"A_err": "0.2088",
"tau": "13.2844",
"tau_err": "0.2183",
"r2": "0.997314",
"png": BASE / "induttore2" / "induttore2.png",
"t0": "112.02",
"t_end": "113.95",
"n_punti": "13",
"zona_exc": None,
"A": "158.3430",
"A_err": "0.2088",
"tau": "13.2844",
"tau_err": "0.2183",
"r2": "0.997314",
"png": BASE / "induttore2" / "induttore2.png",
},
{
"nome": "Induttore 3",
"nome": "Induttore 3",
"intervallo": "115.0 – 133.7 s",
"t0": "115.0",
"t_end": "133.7",
"n_punti": "125",
"zona_exc": "115.9 – 117.2 s",
"A": "185.1847",
"A_err": "0.2706",
"tau": "16.2654",
"tau_err": "0.0464",
"r2": "0.993827",
"png": BASE / "induttore3" / "induttore3.png",
"t0": "115.0",
"t_end": "133.7",
"n_punti": "125",
"zona_exc": "115.9 – 117.2 s",
"A": "185.1847",
"A_err": "0.2706",
"tau": "16.2654",
"tau_err": "0.0464",
"r2": "0.993827",
"png": BASE / "induttore3" / "induttore3.png",
},
]
HEADER_COLOR = colors.HexColor("#2c5f8a")
LIGHT_GRAY = colors.HexColor("#f2f2f2")
TABLE_STYLE = TableStyle([
("BACKGROUND", (0, 0), (-1, 0), HEADER_COLOR),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
("FONTSIZE", (0, 0), (-1, 0), 10),
("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT_GRAY]),
("FONTNAME", (0, 1), (-1, -1), "Helvetica"),
("FONTSIZE", (0, 1), (-1, -1), 9),
("GRID", (0, 0), (-1, -1), 0.5, colors.HexColor("#cccccc")),
("TOPPADDING", (0, 0), (-1, -1), 5),
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
("LEFTPADDING", (0, 0), (-1, -1), 8),
])
def build_doc():
doc = Document(geometry_options={"margin": "2.5cm"})
out = str(BASE / "report_completo.pdf")
doc = SimpleDocTemplate(out, pagesize=A4,
leftMargin=2.5*cm, rightMargin=2.5*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm)
doc.packages.append(Package("booktabs"))
doc.packages.append(Package("graphicx"))
doc.packages.append(Package("amsmath"))
doc.packages.append(Package("float"))
doc.packages.append(Package("caption"))
doc.packages.append(Package("inputenc", options="utf8"))
styles = getSampleStyleSheet()
style_title = styles["Title"]
style_h1 = styles["Heading1"]
style_h2 = styles["Heading2"]
style_body = styles["BodyText"]
style_eq = ParagraphStyle("Equation", parent=styles["BodyText"],
fontName="Courier", fontSize=10,
leftIndent=20, spaceAfter=6)
# Titolo
doc.preamble.append(Command("title", "Report Analisi Termica Forno"))
doc.preamble.append(Command("author", "Analisi IR -- Cevolani"))
doc.preamble.append(Command("date", NoEscape(r"\today")))
doc.append(NoEscape(r"\maketitle"))
story = []
# Introduzione
with doc.create(Section("Modello di fitting")):
doc.append(
"Ogni intervallo è interpolato con il modello di raffreddamento di Newton:"
)
doc.append(Math(data=[NoEscape(
r"T(t) = T_\infty + A \cdot e^{-(t - t_0)/\tau}"
)], escape=False))
doc.append(NoEscape(
r"dove $T_\infty = 22.99\,^\circ\mathrm{C}$ è la temperatura ambiente media ponderata. "
r"La regressione non lineare usa il metodo \textit{Trust Region Reflective} (TRF) "
r"di \texttt{scipy.optimize.curve\_fit}."
))
# --- Titolo ---
story.append(Paragraph("Report Analisi Termica Forno", style_title))
story.append(Paragraph("Analisi IR – Cevolani", styles["Normal"]))
story.append(Spacer(1, 0.5*cm))
story.append(HRFlowable(width="100%", thickness=1, color=HEADER_COLOR))
story.append(Spacer(1, 0.5*cm))
# Sezione per ogni induttore
# --- Modello ---
story.append(Paragraph("Modello di fitting", style_h1))
story.append(Paragraph(
"Ogni intervallo è interpolato con il modello di raffreddamento di Newton:",
style_body))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph("T(t) = T_inf + A · exp( -(t - t0) / τ )", style_eq))
story.append(Paragraph(
"dove <b>T_inf = 22.99 °C</b> è la temperatura ambiente media ponderata. "
"La regressione non lineare usa il metodo <i>Trust Region Reflective</i> (TRF) "
"di <font name='Courier'>scipy.optimize.curve_fit</font>.",
style_body))
story.append(Spacer(1, 0.4*cm))
# --- Sezione per ogni induttore ---
for ind in INDUTTORI:
with doc.create(Section(ind["nome"])):
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.lightgrey))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(ind["nome"], style_h1))
with doc.create(Subsection("Intervallo di campionamento")):
with doc.create(Tabular("|l|l|")) as tab:
tab.add_hline()
tab.add_row([bold("Parametro"), bold("Valore")])
tab.add_hline()
tab.add_row(["Inizio", f"{ind['t0']} s"])
tab.add_row(["Fine", f"{ind['t_end']} s"])
tab.add_row(["Numero di punti", ind["n_punti"]])
if ind["zona_exc"]:
tab.add_row(["Zona esclusa (peso = 0)", ind["zona_exc"]])
tab.add_hline()
# Tabella intervallo
story.append(Paragraph("Intervallo di campionamento", style_h2))
rows = [["Parametro", "Valore"],
["Inizio", f"{ind['t0']} s"],
["Fine", f"{ind['t_end']} s"],
["Numero di punti", ind["n_punti"]]]
if ind["zona_exc"]:
rows.append(["Zona esclusa (peso = 0)", ind["zona_exc"]])
t = Table(rows, colWidths=[8*cm, 6*cm])
t.setStyle(TABLE_STYLE)
story.append(t)
story.append(Spacer(1, 0.3*cm))
with doc.create(Subsection("Equazione trovata")):
t0 = ind["t0"]
A = ind["A"]
tau = ind["tau"]
doc.append(Math(data=[NoEscape(
rf"T(t) = 22.99 + {A} \cdot e^{{-(t - {t0}) / {tau}}}"
)], escape=False))
# Equazione
story.append(Paragraph("Equazione trovata", style_h2))
eq = (f"T(t) = 22.99 + {ind['A']} · exp( -(t - {ind['t0']}) / {ind['tau']} )")
story.append(Paragraph(eq, style_eq))
story.append(Spacer(1, 0.2*cm))
doc.append(NoEscape(r"\vspace{4pt}"))
with doc.create(Tabular("|l|l|l|")) as tab:
tab.add_hline()
tab.add_row([bold("Parametro"), bold("Valore"), bold("Incertezza (1σ)")])
tab.add_hline()
tab.add_row(["A",
NoEscape(rf"${ind['A']}\,^\circ\mathrm{{C}}$"),
NoEscape(rf"$\pm\,{ind['A_err']}\,^\circ\mathrm{{C}}$")])
tab.add_row([NoEscape(r"$\tau$"),
NoEscape(rf"${ind['tau']}\,\mathrm{{s}}$"),
NoEscape(rf"$\pm\,{ind['tau_err']}\,\mathrm{{s}}$")])
tab.add_hline()
# Tabella parametri
param_rows = [
["Parametro", "Valore", "Incertezza (1σ)"],
["A", f"{ind['A']} °C", f"± {ind['A_err']} °C"],
["τ", f"{ind['tau']} s", f"± {ind['tau_err']} s"],
["R²", ind["r2"], ""],
]
t2 = Table(param_rows, colWidths=[4*cm, 6*cm, 5*cm])
t2.setStyle(TABLE_STYLE)
story.append(t2)
story.append(Spacer(1, 0.3*cm))
doc.append(NoEscape(r"\vspace{4pt}"))
doc.append(NoEscape(rf"$R^2 = {ind['r2']}$"))
# Plot
story.append(Paragraph("Grafico", style_h2))
img = Image(str(ind["png"]), width=14*cm, height=8.4*cm)
story.append(img)
story.append(Spacer(1, 0.5*cm))
with doc.create(Subsection("Grafico")):
with doc.create(Figure(position="H")) as fig:
fig.add_image(str(ind["png"]), width=NoEscape(r"0.9\textwidth"))
fig.add_caption(f"Fit esponenziale — {ind['nome']} [{ind['intervallo']}]")
# --- Tabella riepilogativa ---
story.append(PageBreak())
story.append(Paragraph("Riepilogo", style_h1))
summary = [["Induttore", "Intervallo [s]", "A [°C]", "τ [s]", "R²"]]
for ind in INDUTTORI:
summary.append([ind["nome"], ind["intervallo"],
ind["A"], ind["tau"], ind["r2"]])
t3 = Table(summary, colWidths=[3.5*cm, 4.5*cm, 3*cm, 3*cm, 3*cm])
t3.setStyle(TABLE_STYLE)
story.append(t3)
# Tabella riepilogativa
with doc.create(Section("Riepilogo")):
with doc.create(Tabular("|l|l|l|l|l|")) as tab:
tab.add_hline()
tab.add_row([bold("Induttore"), bold("Intervallo [s]"),
bold("A [°C]"), bold(NoEscape(r"$\tau$ [s]")), bold(NoEscape(r"$R^2$"))])
tab.add_hline()
for ind in INDUTTORI:
tab.add_row([ind["nome"], ind["intervallo"],
ind["A"], ind["tau"], ind["r2"]])
tab.add_hline()
doc.build(story)
print(f"report_completo.pdf generato.")
out = BASE / "report_completo"
doc.generate_tex(str(out))
print(f"report_completo.tex generato.")
try:
doc.generate_pdf(str(out), clean_tex=False, compiler="pdflatex")
print("report_completo.pdf generato.")
except Exception as e:
print(f"Compilazione PDF non disponibile ({e}).")
print("Installa MikTeX o TeX Live per generare il PDF da report_completo.tex.")
if __name__ == "__main__":
build_doc()
+1 -1
View File
@@ -1,4 +1,4 @@
pandas
matplotlib
scipy
pylatex
reportlab