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:
1 parent
ec799466cf
commit
777b3de338
2 files changed
+140
-121
No files matched your search
+139
-120
@@ -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
@@ -1,4 +1,4 @@
|
||||
pandas
|
||||
matplotlib
|
||||
scipy
|
||||
pylatex
|
||||
reportlab
|
||||
Reference in new issue
Block a user