diff --git a/report_completo.py b/report_completo.py
index f663300..55883d7 100644
--- a/report_completo.py
+++ b/report_completo.py
@@ -1,119 +1,255 @@
+"""
+report_completo.py
+Genera report_completo.tex e report_completo.pdf leggendo dinamicamente
+tutti i report*.md presenti nelle cartelle induttore*/.
+"""
+
import re
+import tempfile
+import shutil
from pathlib import Path
+from datetime import date
+
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+
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.lib.enums import TA_CENTER, TA_LEFT
-from reportlab.platypus import (BaseDocTemplate, PageTemplate, Frame,
- Paragraph, Spacer, Table, TableStyle,
- Image, HRFlowable, PageBreak, NextPageTemplate,
- KeepTogether)
+from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
+from reportlab.platypus import (
+ BaseDocTemplate, PageTemplate, Frame,
+ Paragraph, Spacer, Table, TableStyle,
+ Image, HRFlowable, PageBreak, NextPageTemplate, KeepTogether,
+)
+from reportlab.lib.utils import ImageReader
from reportlab.pdfgen import canvas as rl_canvas
-from datetime import date
-BASE = Path(__file__).parent
+# ── Identità aziendale ────────────────────────────────────────────────────────
+SOCIETA = "Cevolani Italia s.r.l."
+DIPARTIMENTO = "Ufficio Tecnico Meccanico"
+DOC_NUM = "RT-TERM-001"
+REVISIONE = "Rev. 00"
+STATO = "Definitivo"
+CLASSIFICAZIONE = "USO INTERNO"
+TODAY = date.today()
-AUTORE = "Ufficio Tecnico – Cevolani S.p.A."
-VERSIONE = "1.0"
-STATO = "Definitivo"
-
-# ─── Palette ──────────────────────────────────────────────────────────────────
-C_DARK = colors.HexColor("#0f2236")
-C_BLUE = colors.HexColor("#1a3a5c")
-C_MID = colors.HexColor("#2c5f8a")
-C_LIGHT = colors.HexColor("#4a8fc0")
-C_ACCENT = colors.HexColor("#e8f2fa")
-C_GRAY = colors.HexColor("#f6f7f8")
-C_BORDER = colors.HexColor("#b8c8d8")
-C_TEXT = colors.HexColor("#1e1e1e")
-C_WHITE = colors.white
-
-W, H = A4
+BASE = Path(__file__).parent
+W, H = A4
MARGIN = 2.5 * cm
-CONTENT_W = W - 2 * MARGIN
+CW = W - 2 * MARGIN
-# ─── TableStyle ───────────────────────────────────────────────────────────────
-def make_table_style(header_color=None):
- hc = header_color or C_MID
- return TableStyle([
- ("BACKGROUND", (0, 0), (-1, 0), hc),
- ("TEXTCOLOR", (0, 0), (-1, 0), C_WHITE),
- ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
- ("FONTSIZE", (0, 0), (-1, 0), 9),
- ("ROWBACKGROUNDS", (0, 1), (-1, -1), [C_WHITE, C_GRAY]),
- ("FONTNAME", (0, 1), (-1, -1), "Helvetica"),
- ("FONTSIZE", (0, 1), (-1, -1), 9),
- ("GRID", (0, 0), (-1, -1), 0.4, C_BORDER),
- ("TOPPADDING", (0, 0), (-1, -1), 5),
- ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
- ("LEFTPADDING", (0, 0), (-1, -1), 8),
- ("RIGHTPADDING", (0, 0), (-1, -1), 8),
- ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
- ])
+# ── Palette ───────────────────────────────────────────────────────────────────
+C_DARK = colors.HexColor("#0f2236")
+C_BLUE = colors.HexColor("#1a3a5c")
+C_MID = colors.HexColor("#2c5f8a")
+C_LIGHT = colors.HexColor("#4a8fc0")
+C_GRAY = colors.HexColor("#f6f7f8")
+C_BORDER = colors.HexColor("#b8c8d8")
+C_WHITE = colors.white
+C_TEXT = colors.HexColor("#1e1e1e")
-TABLE_STD = make_table_style()
-TABLE_DARK = make_table_style(C_DARK)
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# 1. PARSING DEI REPORT MARKDOWN
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-COVER_INFO_STYLE = TableStyle([
- ("FONTNAME", (0, 0), (-1, -1), "Helvetica"),
- ("FONTSIZE", (0, 0), (-1, -1), 10),
- ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
- ("TEXTCOLOR", (0, 0), (0, -1), C_BLUE),
- ("TEXTCOLOR", (1, 0), (1, -1), C_TEXT),
- ("ROWBACKGROUNDS",(0, 0), (-1, -1), [C_WHITE, C_GRAY]),
- ("GRID", (0, 0), (-1, -1), 0.4, C_BORDER),
- ("TOPPADDING", (0, 0), (-1, -1), 7),
- ("BOTTOMPADDING", (0, 0), (-1, -1), 7),
- ("LEFTPADDING", (0, 0), (-1, -1), 12),
-])
+def _strip(val: str, *suffixes) -> str:
+ for s in suffixes:
+ val = val.replace(s, "")
+ return val.strip()
-# ─── Parser markdown ──────────────────────────────────────────────────────────
-def parse_md(md_path: Path) -> dict:
+def parse_report(md_path: Path) -> dict:
text = md_path.read_text(encoding="utf-8")
- data = {}
- m = re.search(r"^# (.+)$", text, re.MULTILINE)
- data["nome"] = m.group(1).strip() if m else md_path.parent.name
+ title = re.search(r"^# (.+)$", text, re.MULTILINE)
+ kv2 = {k.strip(): v.strip()
+ for k, v in re.findall(r"^\| (.+?) \| (.+?) \|$", text, re.MULTILINE)
+ if k.strip() != "---"}
+ kv3 = {r[0].strip(): (r[1].strip(), r[2].strip())
+ for r in re.findall(r"^\| (.+?) \| (.+?) \| (.+?) \|$", text, re.MULTILINE)
+ if r[0].strip() not in ("Parametro", "---")}
+ eq = re.search(r"\$\$(.+?)\$\$", text, re.DOTALL)
- kv = dict(re.findall(r"^\| (.+?) \| (.+?) \|$", text, re.MULTILINE))
- kv = {k.strip(): v.strip() for k, v in kv.items() if k.strip() != "---"}
+ t0 = _strip(kv2.get("Inizio", ""), " s")
+ tend = _strip(kv2.get("Fine", ""), " s")
- data["t0"] = kv.get("Inizio", "").replace(" s", "")
- data["t_end"] = kv.get("Fine", "").replace(" s", "")
- data["n_punti"] = kv.get("Numero di punti", "")
- data["zona_exc"] = kv.get("Zona esclusa (peso = 0)", None)
- data["r2"] = kv.get("R²", "")
- data["intervallo"] = f"{data['t0']} – {data['t_end']} s"
+ def param(key):
+ v, e = kv3.get(key, ("", ""))
+ return (_strip(v, " °C", " s"),
+ _strip(e, "± ", "+/- ", " °C", " s"))
- m = re.search(r"\$\$(.+?)\$\$", text, re.DOTALL)
- data["equazione_latex"] = m.group(1).strip() if m else ""
- data["equazione"] = (data["equazione_latex"]
- .replace(r"\cdot", "·").replace(r"\infty", "∞"))
+ A, A_err = param("A")
+ tau, tau_err = param("τ")
- rows3 = re.findall(r"^\| (.+?) \| (.+?) \| (.+?) \|$", text, re.MULTILINE)
- params = {r[0].strip(): (r[1].strip(), r[2].strip())
- for r in rows3 if r[0].strip() not in ("Parametro", "---")}
- data["A"] = params.get("A", ("", ""))[0].replace(" °C", "")
- data["A_err"] = (params.get("A", ("", ""))[1]
- .replace("± ", "").replace("+/- ", "").replace(" °C", ""))
- data["tau"] = params.get("τ", ("", ""))[0].replace(" s", "")
- data["tau_err"] = (params.get("τ", ("", ""))[1]
- .replace("± ", "").replace("+/- ", "").replace(" s", ""))
-
- pngs = sorted(md_path.parent.glob("*.png"))
- data["png"] = pngs[0] if pngs else None
- return data
+ return {
+ "nome": title.group(1).strip() if title else md_path.parent.name,
+ "t0": t0,
+ "t_end": tend,
+ "intervallo": f"{t0} – {tend} s",
+ "n_punti": kv2.get("Numero di punti", ""),
+ "zona_exc": kv2.get("Zona esclusa (peso = 0)"),
+ "eq_latex": eq.group(1).strip() if eq else "",
+ "A": A, "A_err": A_err,
+ "tau": tau, "tau_err": tau_err,
+ "r2": kv2.get("R²", ""),
+ "png": next(iter(sorted(md_path.parent.glob("*.png"))), None),
+ }
-def collect_induttori() -> list:
- return [parse_md(md)
- for folder in sorted(BASE.glob("induttore*/"))
- for md in sorted(folder.glob("report*.md"))]
+def collect_induttori() -> list[dict]:
+ return [
+ parse_report(md)
+ for folder in sorted(BASE.glob("induttore*/"))
+ for md in sorted(folder.glob("report*.md"))
+ ]
-# ─── Canvas con header/footer ─────────────────────────────────────────────────
+def read_t_inf() -> str:
+ """Legge T∞ da start/report.md generato da calcola_t_amb.py."""
+ report = BASE / "start" / "report.md"
+ if not report.exists():
+ return "N/D"
+ m = re.search(r"media ponderata\*\*\s*\|\s*\*\*([0-9.]+)\s*°C",
+ report.read_text(encoding="utf-8"))
+ return m.group(1) if m else "N/D"
+
+
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# 2. RENDERING EQUAZIONI CON MATPLOTLIB
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+def _save_math_png(latex: str, tmpdir: Path, prefix: str,
+ figsize, fontsize: int, dpi: int, pad: float) -> Path:
+ """Renderizza una stringa LaTeX math come PNG trasparente."""
+ fig = plt.figure(figsize=figsize)
+ ax = fig.add_axes([0, 0, 1, 1])
+ ax.text(0.5, 0.5, f"${latex}$",
+ ha="center", va="center", fontsize=fontsize,
+ transform=ax.transAxes, color="#0f2236")
+ ax.axis("off")
+ out = tmpdir / f"{prefix}_{abs(hash(latex))}.png"
+ fig.savefig(out, dpi=dpi, bbox_inches="tight",
+ transparent=True, pad_inches=pad)
+ plt.close(fig)
+ return out
+
+
+def render_eq(latex: str, tmpdir: Path,
+ width_cm: float = 13, fontsize: int = 16) -> Image:
+ """Equazione a blocco, larghezza piena, altezza proporzionale al PNG."""
+ out = _save_math_png(latex, tmpdir, "eq",
+ figsize=(width_cm / 2.54, 1.5),
+ fontsize=fontsize, dpi=220, pad=0.25)
+ iw, ih = ImageReader(str(out)).getSize()
+ w_pt = width_cm * cm
+ return Image(str(out), width=w_pt, height=w_pt * ih / iw)
+
+
+def render_sym(latex: str, tmpdir: Path, width_cm: float = 1.3) -> Image:
+ """Simbolo matematico per cella tabella — stesso stile di render_eq."""
+ out = _save_math_png(latex, tmpdir, "sym",
+ figsize=(width_cm / 2.54, 0.6),
+ fontsize=13, dpi=220, pad=0.12)
+ iw, ih = ImageReader(str(out)).getSize()
+ w_pt = width_cm * cm
+ return Image(str(out), width=w_pt, height=w_pt * ih / iw)
+
+
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# 3. GENERAZIONE .TEX
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+def generate_tex(induttori: list[dict], t_inf: str):
+ today_str = TODAY.strftime("%d/%m/%Y")
+ lines = [
+ r"\documentclass[11pt,a4paper]{article}",
+ r"\usepackage[utf8]{inputenc}",
+ r"\usepackage[T1]{fontenc}",
+ r"\usepackage[margin=2.5cm]{geometry}",
+ r"\usepackage{amsmath,amssymb,booktabs,graphicx,float}",
+ r"\usepackage[table]{xcolor}",
+ r"\usepackage[colorlinks,linkcolor=blue,urlcolor=blue]{hyperref}",
+ r"\usepackage{fancyhdr}",
+ r"\definecolor{darkblue}{HTML}{1a3a5c}",
+ r"\pagestyle{fancy}\fancyhf{}",
+ r"\lhead{\textcolor{darkblue}{\small\textbf{" + SOCIETA + r"}}}",
+ r"\chead{\textcolor{darkblue}{\small Analisi Termica Forno}}",
+ r"\rhead{\textcolor{darkblue}{\small " + DOC_NUM + r"\ --\ " + REVISIONE + r"}}",
+ r"\lfoot{\textcolor{gray}{\tiny " + CLASSIFICAZIONE + r"}}",
+ r"\cfoot{\textcolor{gray}{\tiny " + DOC_NUM + r"\ |\ " + REVISIONE + r"\ |\ " + today_str + r"}}",
+ r"\rfoot{\thepage}",
+ rf"\title{{\textbf{{Report Analisi Termica Forno}}\\[0.4em]"
+ rf"\large Caratterizzazione termica degli induttori}}",
+ rf"\author{{{DIPARTIMENTO} \\ {SOCIETA}}}",
+ r"\date{\today}",
+ r"\begin{document}",
+ r"\maketitle\tableofcontents\newpage",
+ "",
+ r"\section{Modello di fitting}",
+ r"Ogni intervallo è interpolato con la legge di raffreddamento di Newton:",
+ r"\begin{equation}",
+ r" T(t) = T_{\infty} + A\,e^{-(t-t_0)/\tau}",
+ r"\end{equation}",
+ rf"con $T_{{\infty}}={t_inf.replace('.', '{,}')}\,^{{\circ}}\mathrm{{C}}$ fissa (media ponderata),"
+ r" parametri $A$ e $\tau$ liberi stimati con TRF (\texttt{scipy.optimize.curve\_fit}).",
+ "",
+ ]
+
+ for ind in induttori:
+ lines += [
+ rf"\section{{{ind['nome']}}}",
+ r"\subsection{Intervallo di campionamento}",
+ r"\begin{tabular}{ll}\toprule",
+ r"\textbf{Parametro}&\textbf{Valore}\\\midrule",
+ rf"Inizio&${ind['t0']}$ s\\",
+ rf"Fine&${ind['t_end']}$ s\\",
+ rf"N.\,punti&{ind['n_punti']}\\",
+ ]
+ if ind["zona_exc"]:
+ lines.append(rf"Zona esclusa&{ind['zona_exc']}\\")
+ lines += [
+ r"\bottomrule\end{tabular}",
+ r"\subsection{Equazione trovata}",
+ r"\begin{equation}", f" {ind['eq_latex']}", r"\end{equation}",
+ r"\begin{tabular}{lll}\toprule",
+ r"\textbf{Param.}&\textbf{Valore}&\textbf{Incert.\,($1\sigma$)}\\\midrule",
+ rf"$A$&${ind['A']}\,^{{\circ}}\mathrm{{C}}$&$\pm\,{ind['A_err']}\,^{{\circ}}\mathrm{{C}}$\\",
+ rf"$\tau$&${ind['tau']}\,\mathrm{{s}}$&$\pm\,{ind['tau_err']}\,\mathrm{{s}}$\\",
+ rf"$R^2$&\multicolumn{{2}}{{l}}{{{ind['r2']}}}\\",
+ r"\bottomrule\end{tabular}",
+ r"\subsection{Grafico}",
+ r"\begin{figure}[H]\centering",
+ rf"\includegraphics[width=.9\textwidth]{{{ind['png']}}}",
+ rf"\caption{{Fit esponenziale — {ind['nome']} [{ind['intervallo']}]}}",
+ r"\end{figure}\newpage",
+ ]
+
+ lines += [
+ r"\section{Riepilogo}",
+ r"\begin{tabular}{lllll}\toprule",
+ r"\textbf{Induttore}&\textbf{Intervallo [s]}&\textbf{$A$ [°C]}"
+ r"&\textbf{$\tau$ [s]}&\textbf{$R^2$}\\\midrule",
+ ]
+ for ind in induttori:
+ lines.append(
+ rf"{ind['nome']}&{ind['intervallo']}&{ind['A']}&{ind['tau']}&{ind['r2']}\\"
+ )
+ lines += [r"\bottomrule\end{tabular}", r"\end{document}"]
+
+ (BASE / "report_completo.tex").write_text("\n".join(lines), encoding="utf-8")
+ print("report_completo.tex generato.")
+
+
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# 4. CANVAS CON HEADER / FOOTER
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+# Altezza header: riga 1 (azienda / titolo / docnum) + riga 2 (dip. / data)
+HDR_H = 1.6 * cm
+
class ProfessionalCanvas(rl_canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -135,160 +271,198 @@ class ProfessionalCanvas(rl_canvas.Canvas):
def _draw_header(self):
self.saveState()
- # Barra colorata in cima
+ # Sfondo intero header
self.setFillColor(C_DARK)
- self.rect(0, H - 1.4*cm, W, 1.4*cm, fill=1, stroke=0)
+ self.rect(0, H - HDR_H, W, HDR_H, fill=1, stroke=0)
+ # Striscia laterale colorata
self.setFillColor(C_LIGHT)
- self.rect(0, H - 1.4*cm, 0.5*cm, 1.4*cm, fill=1, stroke=0)
+ self.rect(0, H - HDR_H, 0.4 * cm, HDR_H, fill=1, stroke=0)
+ # Linea separatrice tra riga 1 e riga 2
+ self.setStrokeColor(colors.HexColor("#1e4a72"))
+ self.setLineWidth(0.4)
+ self.line(0.6 * cm, H - HDR_H * 0.55, W - 0.5 * cm, H - HDR_H * 0.55)
+
+ # -- Riga 1: azienda | titolo documento | codice + revisione --
+ y1 = H - HDR_H * 0.38
self.setFillColor(C_WHITE)
- self.setFont("Helvetica-Bold", 9)
- self.drawString(0.8*cm, H - 0.9*cm, "ANALISI TERMICA FORNO")
- self.setFont("Helvetica", 8)
- self.drawRightString(W - 0.6*cm, H - 0.9*cm, "Cevolani S.p.A. — Uso interno")
+ self.setFont("Helvetica-Bold", 8)
+ self.drawString(0.65 * cm, y1, SOCIETA.upper())
+ self.setFont("Helvetica", 7.5)
+ self.drawCentredString(W / 2, y1, "ANALISI TERMICA FORNO")
+ self.setFont("Helvetica-Bold", 7.5)
+ self.drawRightString(W - 0.5 * cm, y1, f"{DOC_NUM} | {REVISIONE}")
+
+ # -- Riga 2: dipartimento | data --
+ y2 = H - HDR_H * 0.75
+ self.setFillColor(colors.HexColor("#8ab4d0"))
+ self.setFont("Helvetica", 6.5)
+ self.drawString(0.65 * cm, y2, DIPARTIMENTO)
+ self.drawRightString(W - 0.5 * cm, y2, TODAY.strftime("%d/%m/%Y"))
self.restoreState()
def _draw_footer(self, total):
self.saveState()
+ # Linea superiore footer
self.setStrokeColor(C_MID)
- self.setLineWidth(0.8)
- self.line(MARGIN, 1.9*cm, W - MARGIN, 1.9*cm)
+ self.setLineWidth(0.6)
+ self.line(MARGIN, 1.9 * cm, W - MARGIN, 1.9 * cm)
+ # Classificazione (sinistra)
+ self.setFillColor(C_BLUE)
+ self.setFont("Helvetica-Bold", 7)
+ self.drawString(MARGIN, 1.5 * cm, CLASSIFICAZIONE)
+ # Riferimento documento (centro)
+ self.setFillColor(C_MID)
+ self.setFont("Helvetica", 7)
+ self.drawCentredString(
+ W / 2, 1.5 * cm,
+ f"{DOC_NUM} | {REVISIONE} | {STATO} | {TODAY.strftime('%d/%m/%Y')}"
+ )
+ # Numero pagina (destra)
self.setFillColor(C_BLUE)
- self.setFont("Helvetica", 7.5)
- self.drawString(MARGIN, 1.4*cm,
- f"Documento: report_completo | Ver. {VERSIONE} | {STATO}")
self.setFont("Helvetica-Bold", 8)
- self.drawRightString(W - MARGIN, 1.4*cm,
+ self.drawRightString(W - MARGIN, 1.5 * cm,
f"Pag. {self._pageNumber} / {total}")
self.restoreState()
-# ─── Stili testo ──────────────────────────────────────────────────────────────
-def make_styles():
- styles = getSampleStyleSheet()
- s = {}
- s["body"] = ParagraphStyle("body", parent=styles["BodyText"],
- fontSize=9.5, leading=14, textColor=C_TEXT,
- spaceAfter=6)
- s["h1"] = ParagraphStyle("h1", fontName="Helvetica-Bold", fontSize=14,
- textColor=C_WHITE, spaceBefore=16, spaceAfter=2,
- leftIndent=-0.3*cm, leading=18)
- s["h2"] = ParagraphStyle("h2", fontName="Helvetica-Bold", fontSize=10.5,
- textColor=C_BLUE, spaceBefore=10, spaceAfter=4,
- leading=14)
- s["eq"] = ParagraphStyle("eq", fontName="Courier", fontSize=9.5,
- leftIndent=18, spaceBefore=4, spaceAfter=6,
- backColor=C_ACCENT, borderPad=7, leading=14)
- s["caption"] = ParagraphStyle("caption", fontName="Helvetica-Oblique",
- fontSize=8.5, textColor=colors.HexColor("#555555"),
- alignment=TA_CENTER, spaceAfter=4)
- return s
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# 5. COMPONENTI PDF
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+def _styles() -> dict:
+ base = getSampleStyleSheet()
+ mono = ParagraphStyle("mono", fontName="Courier", fontSize=9, textColor=C_TEXT)
+ return {
+ "body": ParagraphStyle("body", parent=base["BodyText"],
+ fontSize=9.5, leading=14.5, textColor=C_TEXT,
+ spaceAfter=6),
+ "mono": mono,
+ "h1": ParagraphStyle("h1", fontName="Helvetica-Bold", fontSize=12,
+ textColor=C_WHITE, leading=16, spaceAfter=2),
+ "h2": ParagraphStyle("h2", fontName="Helvetica-Bold", fontSize=10,
+ textColor=C_BLUE, spaceBefore=10, spaceAfter=4,
+ leading=13),
+ "cap": ParagraphStyle("cap", fontName="Helvetica-Oblique", fontSize=8,
+ textColor=colors.HexColor("#555555"),
+ alignment=TA_CENTER, spaceBefore=2),
+ "cov_title": ParagraphStyle("cov_title", fontName="Helvetica-Bold",
+ fontSize=24, textColor=C_BLUE,
+ alignment=TA_CENTER, leading=30),
+ "cov_sub": ParagraphStyle("cov_sub", fontName="Helvetica",
+ fontSize=12, textColor=C_MID,
+ alignment=TA_CENTER, leading=17),
+ }
-def section_header(title: str, s_h1) -> list:
- """Barra blu con titolo sezione."""
- tbl = Table([[Paragraph(title, s_h1)]], colWidths=[CONTENT_W])
+def _ts(hdr_color=None) -> TableStyle:
+ hc = hdr_color or C_MID
+ return TableStyle([
+ ("BACKGROUND", (0, 0), (-1, 0), hc),
+ ("TEXTCOLOR", (0, 0), (-1, 0), C_WHITE),
+ ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
+ ("FONTSIZE", (0, 0), (-1, 0), 9),
+ ("ROWBACKGROUNDS", (0, 1), (-1, -1), [C_WHITE, C_GRAY]),
+ ("FONTNAME", (0, 1), (-1, -1), "Helvetica"),
+ ("FONTSIZE", (0, 1), (-1, -1), 9),
+ ("GRID", (0, 0), (-1, -1), 0.4, C_BORDER),
+ ("TOPPADDING", (0, 0), (-1, -1), 3),
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
+ ("LEFTPADDING", (0, 0), (-1, -1), 7),
+ ("RIGHTPADDING", (0, 0), (-1, -1), 7),
+ ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
+ ])
+
+
+def _ts_cover() -> TableStyle:
+ """Stile per la scheda documento in copertina."""
+ return TableStyle([
+ ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
+ ("FONTNAME", (1, 0), (1, -1), "Helvetica"),
+ ("FONTSIZE", (0, 0), (-1, -1), 10),
+ ("TEXTCOLOR", (0, 0), (0, -1), C_BLUE),
+ ("TEXTCOLOR", (1, 0), (1, -1), C_TEXT),
+ ("ROWBACKGROUNDS",(0, 0), (-1, -1), [C_WHITE, C_GRAY]),
+ ("GRID", (0, 0), (-1, -1), 0.5, C_BORDER),
+ ("TOPPADDING", (0, 0), (-1, -1), 8),
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 8),
+ ("LEFTPADDING", (0, 0), (-1, -1), 14),
+ ("RIGHTPADDING", (0, 0), (-1, -1), 14),
+ ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
+ # Riga classificazione in evidenza
+ ("BACKGROUND", (0, 5), (-1, 5), colors.HexColor("#fff3cd")),
+ ("TEXTCOLOR", (1, 5), (1, 5), colors.HexColor("#7a4f00")),
+ ("FONTNAME", (1, 5), (1, 5), "Helvetica-Bold"),
+ ])
+
+
+def _section_banner(title: str, s_h1) -> list:
+ tbl = Table([[Paragraph(title, s_h1)]], colWidths=[CW])
tbl.setStyle(TableStyle([
- ("BACKGROUND", (0, 0), (-1, -1), C_BLUE),
- ("LEFTPADDING", (0, 0), (-1, -1), 10),
- ("RIGHTPADDING", (0, 0), (-1, -1), 10),
- ("TOPPADDING", (0, 0), (-1, -1), 6),
- ("BOTTOMPADDING",(0, 0), (-1, -1), 6),
+ ("BACKGROUND", (0, 0), (-1, -1), C_BLUE),
+ ("LEFTPADDING", (0, 0), (-1, -1), 12),
+ ("RIGHTPADDING", (0, 0), (-1, -1), 12),
+ ("TOPPADDING", (0, 0), (-1, -1), 7),
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 7),
]))
- return [tbl, Spacer(1, 0.25*cm)]
+ return [tbl, Spacer(1, 0.2 * cm)]
-# ─── Genera .tex ──────────────────────────────────────────────────────────────
-def generate_tex(induttori: list):
- today = date.today().strftime("%d/%m/%Y")
- lines = [
- r"\documentclass[11pt,a4paper]{article}",
- r"\usepackage[utf8]{inputenc}",
- r"\usepackage[T1]{fontenc}",
- r"\usepackage[margin=2.5cm]{geometry}",
- r"\usepackage{amsmath,amssymb}",
- r"\usepackage{booktabs}",
- r"\usepackage{graphicx}",
- r"\usepackage{float}",
- r"\usepackage[table]{xcolor}",
- r"\usepackage{hyperref}",
- r"\usepackage{fancyhdr}",
- r"\definecolor{darkblue}{HTML}{1a3a5c}",
- r"\pagestyle{fancy}\fancyhf{}",
- r"\rhead{\textcolor{darkblue}{\small Cevolani S.p.A.}}",
- r"\lhead{\textcolor{darkblue}{\small Analisi Termica Forno}}",
- r"\rfoot{\thepage}",
- r"\lfoot{\textcolor{gray}{\tiny Ver. " + VERSIONE + r" -- " + today + r"}}",
- rf"\title{{\textbf{{Report Analisi Termica Forno}}\\[0.5em]\large Caratterizzazione termica degli induttori}}",
- rf"\author{{{AUTORE}}}",
- r"\date{\today}",
- r"\begin{document}",
- r"\maketitle",
- r"\tableofcontents",
- r"\newpage",
- "",
- r"\section{Modello di fitting}",
- r"Ogni intervallo è interpolato con il modello di raffreddamento di Newton:",
- r"\begin{equation}",
- r" T(t) = T_\infty + A \cdot e^{-(t-t_0)/\tau}",
- r"\end{equation}",
- 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}.",
- "",
+def _cover(s: dict, n: int) -> list:
+ # Intestazione aziendale
+ banner = Table([[
+ Paragraph(
+ f"{SOCIETA.upper()}",
+ ParagraphStyle("_bl", alignment=TA_LEFT)),
+ Paragraph(
+ f"{DIPARTIMENTO}",
+ ParagraphStyle("_br", alignment=TA_RIGHT)),
+ ]], colWidths=[CW * 0.6, CW * 0.4],
+ style=TableStyle([
+ ("BACKGROUND", (0, 0), (-1, -1), C_DARK),
+ ("TOPPADDING", (0, 0), (-1, -1), 14),
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 14),
+ ("LEFTPADDING", (0, 0), (-1, -1), 16),
+ ("RIGHTPADDING", (0, 0), (-1, -1), 16),
+ ]))
+
+ # Scheda documento
+ doc_table = Table([
+ ["Codice documento", DOC_NUM],
+ ["Revisione", REVISIONE],
+ ["Data di emissione", TODAY.strftime("%d %B %Y")],
+ ["Stato", STATO],
+ ["Redatto da", DIPARTIMENTO],
+ ["Classificazione", CLASSIFICAZIONE],
+ ["Induttori analizzati", str(n)],
+ ], colWidths=[6 * cm, CW - 6 * cm], style=_ts_cover())
+
+ return [
+ banner,
+ Spacer(1, 3.8 * cm),
+ HRFlowable(width="100%", thickness=5, color=C_BLUE),
+ Spacer(1, 0.45 * cm),
+ Paragraph("Report Analisi Termica Forno", s["cov_title"]),
+ Spacer(1, 0.25 * cm),
+ Paragraph("Caratterizzazione termica degli induttori", s["cov_sub"]),
+ Spacer(1, 0.45 * cm),
+ HRFlowable(width="100%", thickness=5, color=C_BLUE),
+ Spacer(1, 1.8 * cm),
+ doc_table,
+ NextPageTemplate("normal"),
+ PageBreak(),
]
- for ind in induttori:
- lines += [
- rf"\section{{{ind['nome']}}}",
- r"\subsection{Intervallo di campionamento}",
- r"\begin{tabular}{ll}\toprule",
- r"\textbf{Parametro} & \textbf{Valore} \\\midrule",
- rf"Inizio & ${ind['t0']}$ s \\",
- rf"Fine & ${ind['t_end']}$ s \\",
- rf"Numero di punti & {ind['n_punti']} \\",
- ]
- if ind["zona_exc"]:
- lines.append(rf"Zona esclusa (peso = 0) & {ind['zona_exc']} \\")
- lines += [r"\bottomrule\end{tabular}", "",
- r"\subsection{Equazione trovata}",
- r"\begin{equation}",
- f" {ind['equazione_latex']}",
- r"\end{equation}",
- r"\begin{tabular}{lll}\toprule",
- r"\textbf{Param.} & \textbf{Valore} & \textbf{Incertezza ($1\sigma$)} \\\midrule",
- rf"$A$ & ${ind['A']}\,^\circ\mathrm{{C}}$ & $\pm\,{ind['A_err']}\,^\circ\mathrm{{C}}$ \\",
- rf"$\tau$ & ${ind['tau']}\,\mathrm{{s}}$ & $\pm\,{ind['tau_err']}\,\mathrm{{s}}$ \\",
- rf"$R^2$ & \multicolumn{{2}}{{l}}{{{ind['r2']}}} \\",
- r"\bottomrule\end{tabular}", "",
- r"\subsection{Grafico}",
- r"\begin{figure}[H]\centering",
- rf"\includegraphics[width=0.9\textwidth]{{{ind['png']}}}",
- rf"\caption{{Fit esponenziale -- {ind['nome']} [{ind['intervallo']}]}}",
- r"\end{figure}\newpage", "",
- ]
- lines += [
- r"\section{Riepilogo}",
- r"\begin{tabular}{lllll}\toprule",
- r"\textbf{Induttore} & \textbf{Intervallo [s]} & \textbf{A [°C]} & \textbf{$\tau$ [s]} & \textbf{$R^2$} \\\midrule",
- ]
- for ind in induttori:
- lines.append(
- rf"{ind['nome']} & {ind['intervallo']} & {ind['A']} & {ind['tau']} & {ind['r2']} \\"
- )
- lines += [r"\bottomrule\end{tabular}", "", r"\end{document}"]
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# 6. GENERAZIONE PDF
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
- (BASE / "report_completo.tex").write_text("\n".join(lines), encoding="utf-8")
- print("report_completo.tex generato.")
+def generate_pdf(induttori: list[dict], t_inf: str):
+ s = _styles()
+ tmpdir = Path(tempfile.mkdtemp())
-
-# ─── Genera PDF ───────────────────────────────────────────────────────────────
-def generate_pdf(induttori: list):
- s = make_styles()
- today_str = date.today().strftime("%d %B %Y")
-
- frame_cover = Frame(MARGIN, MARGIN, CONTENT_W, H - 2*MARGIN, id="cover")
- frame_normal = Frame(MARGIN, 2.5*cm, CONTENT_W, H - 2.5*cm - 1.6*cm,
+ frame_cover = Frame(MARGIN, MARGIN, CW, H - 2 * MARGIN, id="cover")
+ frame_normal = Frame(MARGIN, 2.6 * cm, CW, H - HDR_H - 2.6 * cm - 0.3 * cm,
id="normal", topPadding=0)
doc = BaseDocTemplate(str(BASE / "report_completo.pdf"), pagesize=A4)
@@ -297,79 +471,39 @@ def generate_pdf(induttori: list):
PageTemplate(id="normal", frames=[frame_normal]),
])
- story = []
+ story: list = []
- # ── COVER PAGE ──────────────────────────────────────────────────────────────
- # Barra superiore scura
- story.append(Table(
- [[Paragraph("CEVOLANI S.p.A.",
- ParagraphStyle("ch", alignment=TA_LEFT)),
- Paragraph("Analisi Termica — Uso Interno",
- ParagraphStyle("cr", alignment=TA_LEFT))]],
- colWidths=[CONTENT_W * 0.55, CONTENT_W * 0.45],
- style=TableStyle([
- ("BACKGROUND", (0, 0), (-1, -1), C_DARK),
- ("TOPPADDING", (0, 0), (-1, -1), 10),
- ("BOTTOMPADDING", (0, 0), (-1, -1), 10),
- ("LEFTPADDING", (0, 0), (-1, -1), 14),
- ])
- ))
- story.append(Spacer(1, 4*cm))
+ # ── Copertina ──
+ story += _cover(s, len(induttori))
- # Titolo principale
- story.append(HRFlowable(width="100%", thickness=4, color=C_BLUE))
- story.append(Spacer(1, 0.4*cm))
+ # ── 1. Modello di fitting ──
+ story += _section_banner("1. Modello di fitting", s["h1"])
story.append(Paragraph(
- "Report Analisi Termica Forno",
- ParagraphStyle("ct", alignment=TA_CENTER, leading=32)))
- story.append(Spacer(1, 0.3*cm))
- story.append(Paragraph(
- "Caratterizzazione termica degli induttori",
- ParagraphStyle("cs", alignment=TA_CENTER, leading=18)))
- story.append(Spacer(1, 0.4*cm))
- story.append(HRFlowable(width="100%", thickness=4, color=C_BLUE))
- story.append(Spacer(1, 2*cm))
-
- # Scheda documento
- doc_info = [
- ["Documento", "report_completo"],
- ["Autore", AUTORE],
- ["Data di emissione", today_str],
- ["Versione", VERSIONE],
- ["Stato", STATO],
- ["Induttori analizzati", str(len(induttori))],
- ]
- story.append(Table(doc_info, colWidths=[5.5*cm, CONTENT_W - 5.5*cm],
- style=COVER_INFO_STYLE))
- story.append(NextPageTemplate("normal"))
- story.append(PageBreak())
-
- # ── MODELLO ─────────────────────────────────────────────────────────────────
- story += section_header("1. Modello di fitting", s["h1"])
- story.append(Paragraph(
- "Ogni intervallo di acquisizione è interpolato con la legge di "
- "raffreddamento di Newton, che descrive lo scambio termico convettivo "
- "tra il corpo e l'ambiente:", s["body"]))
- story.append(Paragraph("T(t) = T_inf + A · exp( -(t - t₀) / τ )", s["eq"]))
+ "Ogni tratto di acquisizione è interpolato con la legge di "
+ "raffreddamento di Newton (convezione termica):", s["body"]))
+ story.append(Spacer(1, 0.1 * cm))
+ story.append(render_eq(r"T(t) = T_{\infty} + A\,e^{-(t-t_0)/\tau}", tmpdir))
+ story.append(Spacer(1, 0.15 * cm))
story.append(Table([
- ["Simbolo", "Significato", "Unità"],
- ["T_inf", "Temperatura ambiente (fissa)", "°C"],
- ["A", "Ampiezza iniziale (libera)", "°C"],
- ["t₀", "Istante di riferimento (fisso)", "s"],
- ["τ", "Costante di tempo (libera)", "s"],
- ], colWidths=[3*cm, 9*cm, 3*cm], style=TABLE_STD))
- story.append(Spacer(1, 0.3*cm))
+ ["Simbolo", "Significato", "Unità"],
+ [render_sym(r"T_{\infty}", tmpdir), "Temperatura ambiente — valore fisso", "°C"],
+ [render_sym(r"A", tmpdir), "Ampiezza iniziale — parametro libero", "°C"],
+ [render_sym(r"t_0", tmpdir), "Istante di riferimento — valore fisso", "s"],
+ [render_sym(r"\tau", tmpdir), "Costante di tempo — parametro libero", "s"],
+ ], colWidths=[2.5 * cm, 9.5 * cm, CW - 12 * cm], style=_ts()))
+ story.append(Spacer(1, 0.3 * cm))
story.append(Paragraph(
- "La regressione non lineare è eseguita con il metodo "
- "Trust Region Reflective (TRF) tramite "
- "scipy.optimize.curve_fit. "
- f"T_inf = 22.99 °C è la media ponderata sul tempo dell'intera acquisizione "
- "(calcolata con la regola dei trapezi).", s["body"]))
- story.append(Spacer(1, 0.4*cm))
+ "La regressione usa il metodo Trust Region Reflective (TRF) "
+ "(scipy.optimize.curve_fit). "
+ f"T∞ = {t_inf} °C è la media ponderata sul tempo dell'intera "
+ "acquisizione (regola dei trapezi, "
+ "calcola_t_amb.py).",
+ s["body"]))
+ story.append(Spacer(1, 0.4 * cm))
- # ── SEZIONI INDUTTORI ────────────────────────────────────────────────────────
- for i, ind in enumerate(induttori, 1):
- story += section_header(f"{i + 1}. {ind['nome']}", s["h1"])
+ # ── Sezioni induttori ──
+ for i, ind in enumerate(induttori, 2):
+ story += _section_banner(f"{i}. {ind['nome']}", s["h1"])
# Campionamento
story.append(Paragraph("Intervallo di campionamento", s["h2"]))
@@ -378,65 +512,63 @@ def generate_pdf(induttori: list):
["Fine intervallo", f"{ind['t_end']} s"],
["Numero di punti", ind["n_punti"]]]
if ind["zona_exc"]:
- rows.append(["Zona esclusa dal fit (σ → ∞)", ind["zona_exc"]])
- story.append(Table(rows, colWidths=[8*cm, CONTENT_W - 8*cm],
- style=TABLE_STD))
- story.append(Spacer(1, 0.3*cm))
+ rows.append(["Zona esclusa (σ → ∞)", ind["zona_exc"]])
+ story.append(Table(rows, colWidths=[7 * cm, CW - 7 * cm], style=_ts()))
+ story.append(Spacer(1, 0.3 * cm))
# Equazione
story.append(Paragraph("Equazione del modello", s["h2"]))
- story.append(Paragraph(ind["equazione"], s["eq"]))
- story.append(Spacer(1, 0.2*cm))
- story.append(Paragraph("Parametri stimati dalla regressione:", s["body"]))
+ story.append(render_eq(ind["eq_latex"], tmpdir))
+ story.append(Spacer(1, 0.2 * cm))
story.append(Table([
- ["Parametro", "Valore stimato", "Incertezza (1σ)", "Note"],
- ["A", f"{ind['A']} °C", f"± {ind['A_err']} °C", "Ampiezza"],
- ["τ", f"{ind['tau']} s", f"± {ind['tau_err']} s","Costante di tempo"],
- ["R²", ind["r2"], "—", "Qualità del fit"],
- ], colWidths=[2.5*cm, 4.5*cm, 4*cm, CONTENT_W - 11*cm],
- style=TABLE_STD))
- story.append(Spacer(1, 0.3*cm))
+ ["Parametro", "Valore stimato", "Incertezza (1σ)", "Descrizione"],
+ [render_sym(r"A", tmpdir), f"{ind['A']} °C", f"± {ind['A_err']} °C", "Ampiezza"],
+ [render_sym(r"\tau", tmpdir), f"{ind['tau']} s", f"± {ind['tau_err']} s", "Costante di tempo"],
+ [render_sym(r"R^2", tmpdir), ind["r2"], "—", "Qualità del fit"],
+ ], colWidths=[2.2 * cm, 4.2 * cm, 4 * cm, CW - 10.4 * cm], style=_ts()))
+ story.append(Spacer(1, 0.3 * cm))
# Grafico
if ind["png"] and Path(ind["png"]).exists():
story.append(Paragraph("Grafico del fit", s["h2"]))
story.append(KeepTogether([
- Image(str(ind["png"]), width=CONTENT_W, height=CONTENT_W * 0.6),
+ Image(str(ind["png"]), width=CW, height=CW * 0.6),
Paragraph(
- f"Figura {i}: fit esponenziale — {ind['nome']} "
- f"[{ind['intervallo']}]", s["caption"]),
+ f"Fig. {i - 1} — Fit esponenziale: {ind['nome']} "
+ f"[{ind['intervallo']}]", s["cap"]),
]))
- story.append(Spacer(1, 0.5*cm))
+ story.append(Spacer(1, 0.5 * cm))
- # ── RIEPILOGO ────────────────────────────────────────────────────────────────
+ # ── Riepilogo ──
story.append(PageBreak())
- story += section_header(f"{len(induttori) + 2}. Riepilogo comparativo", s["h1"])
+ n_sec = len(induttori) + 2
+ story += _section_banner(f"{n_sec}. Riepilogo comparativo", s["h1"])
story.append(Paragraph(
- "Tabella comparativa dei parametri ottimali ottenuti dalla regressione "
- "non lineare per ciascun induttore analizzato.", s["body"]))
- story.append(Spacer(1, 0.2*cm))
-
+ "Parametri ottimali dalla regressione non lineare per ciascun induttore. "
+ "R² calcolato escludendo i punti con peso nullo.", s["body"]))
+ story.append(Spacer(1, 0.2 * cm))
summary = [["Induttore", "Intervallo [s]", "A [°C]", "τ [s]", "R²"]]
for ind in induttori:
summary.append([ind["nome"], ind["intervallo"],
ind["A"], ind["tau"], ind["r2"]])
story.append(Table(summary,
- colWidths=[3.5*cm, 4.5*cm, 3*cm, 3*cm, CONTENT_W - 14*cm],
- style=TABLE_DARK))
- story.append(Spacer(1, 0.5*cm))
- story.append(Paragraph(
- "Note: tutti i fit usano T_inf = 22.99 °C fissa. "
- "R² calcolato escludendo i punti con peso nullo.", s["body"]))
+ colWidths=[3.5 * cm, 4.5 * cm, 3 * cm, 3 * cm, CW - 14 * cm],
+ style=_ts(C_DARK)))
doc.build(story, canvasmaker=ProfessionalCanvas)
+ shutil.rmtree(tmpdir, ignore_errors=True)
print(f"report_completo.pdf generato ({len(induttori)} induttori).")
-# ─── Entry point ──────────────────────────────────────────────────────────────
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# ENTRY POINT
+# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
if __name__ == "__main__":
induttori = collect_induttori()
if not induttori:
print("Nessun report*.md trovato nelle cartelle induttore*/.")
else:
- generate_tex(induttori)
- generate_pdf(induttori)
+ t_inf = read_t_inf()
+ generate_tex(induttori, t_inf)
+ generate_pdf(induttori, t_inf)
diff --git a/report_completo.tex b/report_completo.tex
index 702f4a7..22e4502 100644
--- a/report_completo.tex
+++ b/report_completo.tex
@@ -2,120 +2,105 @@
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[margin=2.5cm]{geometry}
-\usepackage{amsmath,amssymb}
-\usepackage{booktabs}
-\usepackage{graphicx}
-\usepackage{float}
+\usepackage{amsmath,amssymb,booktabs,graphicx,float}
\usepackage[table]{xcolor}
-\usepackage{hyperref}
+\usepackage[colorlinks,linkcolor=blue,urlcolor=blue]{hyperref}
\usepackage{fancyhdr}
\definecolor{darkblue}{HTML}{1a3a5c}
\pagestyle{fancy}\fancyhf{}
-\rhead{\textcolor{darkblue}{\small Cevolani S.p.A.}}
-\lhead{\textcolor{darkblue}{\small Analisi Termica Forno}}
+\lhead{\textcolor{darkblue}{\small\textbf{Cevolani Italia s.r.l.}}}
+\chead{\textcolor{darkblue}{\small Analisi Termica Forno}}
+\rhead{\textcolor{darkblue}{\small RT-TERM-001\ --\ Rev. 00}}
+\lfoot{\textcolor{gray}{\tiny USO INTERNO}}
+\cfoot{\textcolor{gray}{\tiny RT-TERM-001\ |\ Rev. 00\ |\ 14/05/2026}}
\rfoot{\thepage}
-\lfoot{\textcolor{gray}{\tiny Ver. 1.0 -- 14/05/2026}}
-\title{\textbf{Report Analisi Termica Forno}\\[0.5em]\large Caratterizzazione termica degli induttori}
-\author{Ufficio Tecnico – Cevolani S.p.A.}
+\title{\textbf{Report Analisi Termica Forno}\\[0.4em]\large Caratterizzazione termica degli induttori}
+\author{Ufficio Tecnico Meccanico \\ Cevolani Italia s.r.l.}
\date{\today}
\begin{document}
-\maketitle
-\tableofcontents
-\newpage
+\maketitle\tableofcontents\newpage
\section{Modello di fitting}
-Ogni intervallo è interpolato con il modello di raffreddamento di Newton:
+Ogni intervallo è interpolato con la legge di raffreddamento di Newton:
\begin{equation}
- T(t) = T_\infty + A \cdot e^{-(t-t_0)/\tau}
+ T(t) = T_{\infty} + A\,e^{-(t-t_0)/\tau}
\end{equation}
-dove $T_\infty = 22{,}99\,^\circ\mathrm{C}$ è la temperatura ambiente media ponderata.
-La regressione non lineare usa il metodo \textit{Trust Region Reflective} (TRF)
-di \texttt{scipy.optimize.curve\_fit}.
+con $T_{\infty}=22{,}99\,^{\circ}\mathrm{C}$ fissa (media ponderata), parametri $A$ e $\tau$ liberi stimati con TRF (\texttt{scipy.optimize.curve\_fit}).
\section{Report Induttore 1}
\subsection{Intervallo di campionamento}
\begin{tabular}{ll}\toprule
-\textbf{Parametro} & \textbf{Valore} \\\midrule
-Inizio & $110.81$ s \\
-Fine & $111.56$ s \\
-Numero di punti & 5 \\
+\textbf{Parametro}&\textbf{Valore}\\\midrule
+Inizio&$110.81$ s\\
+Fine&$111.56$ s\\
+N.\,punti&5\\
\bottomrule\end{tabular}
-
\subsection{Equazione trovata}
\begin{equation}
T(t) = 22.99 + 87.3140 \cdot e^{-(t - 110.81) / 15.7231}
\end{equation}
\begin{tabular}{lll}\toprule
-\textbf{Param.} & \textbf{Valore} & \textbf{Incertezza ($1\sigma$)} \\\midrule
-$A$ & $87.3140\,^\circ\mathrm{C}$ & $\pm\,0.0260\,^\circ\mathrm{C}$ \\
-$\tau$ & $15.7231\,\mathrm{s}$ & $\pm\,0.2021\,\mathrm{s}$ \\
-$R^2$ & \multicolumn{2}{l}{0.999505} \\
+\textbf{Param.}&\textbf{Valore}&\textbf{Incert.\,($1\sigma$)}\\\midrule
+$A$&$87.3140\,^{\circ}\mathrm{C}$&$\pm\,0.0260\,^{\circ}\mathrm{C}$\\
+$\tau$&$15.7231\,\mathrm{s}$&$\pm\,0.2021\,\mathrm{s}$\\
+$R^2$&\multicolumn{2}{l}{0.999505}\\
\bottomrule\end{tabular}
-
\subsection{Grafico}
\begin{figure}[H]\centering
-\includegraphics[width=0.9\textwidth]{C:\Users\davide.grilli\Desktop\vs\temperatura\induttore1\induttore1.png}
-\caption{Fit esponenziale -- Report Induttore 1 [110.81 – 111.56 s]}
+\includegraphics[width=.9\textwidth]{C:\Users\davide.grilli\Desktop\vs\temperatura\induttore1\induttore1.png}
+\caption{Fit esponenziale — Report Induttore 1 [110.81 – 111.56 s]}
\end{figure}\newpage
-
\section{Report Induttore 2}
\subsection{Intervallo di campionamento}
\begin{tabular}{ll}\toprule
-\textbf{Parametro} & \textbf{Valore} \\\midrule
-Inizio & $112.02$ s \\
-Fine & $113.95$ s \\
-Numero di punti & 12 \\
+\textbf{Parametro}&\textbf{Valore}\\\midrule
+Inizio&$112.02$ s\\
+Fine&$113.95$ s\\
+N.\,punti&12\\
\bottomrule\end{tabular}
-
\subsection{Equazione trovata}
\begin{equation}
T(t) = 22.99 + 158.3430 \cdot e^{-(t - 112.02) / 13.2844}
\end{equation}
\begin{tabular}{lll}\toprule
-\textbf{Param.} & \textbf{Valore} & \textbf{Incertezza ($1\sigma$)} \\\midrule
-$A$ & $158.3430\,^\circ\mathrm{C}$ & $\pm\,0.2088\,^\circ\mathrm{C}$ \\
-$\tau$ & $13.2844\,\mathrm{s}$ & $\pm\,0.2183\,\mathrm{s}$ \\
-$R^2$ & \multicolumn{2}{l}{0.997314} \\
+\textbf{Param.}&\textbf{Valore}&\textbf{Incert.\,($1\sigma$)}\\\midrule
+$A$&$158.3430\,^{\circ}\mathrm{C}$&$\pm\,0.2088\,^{\circ}\mathrm{C}$\\
+$\tau$&$13.2844\,\mathrm{s}$&$\pm\,0.2183\,\mathrm{s}$\\
+$R^2$&\multicolumn{2}{l}{0.997314}\\
\bottomrule\end{tabular}
-
\subsection{Grafico}
\begin{figure}[H]\centering
-\includegraphics[width=0.9\textwidth]{C:\Users\davide.grilli\Desktop\vs\temperatura\induttore2\induttore2.png}
-\caption{Fit esponenziale -- Report Induttore 2 [112.02 – 113.95 s]}
+\includegraphics[width=.9\textwidth]{C:\Users\davide.grilli\Desktop\vs\temperatura\induttore2\induttore2.png}
+\caption{Fit esponenziale — Report Induttore 2 [112.02 – 113.95 s]}
\end{figure}\newpage
-
\section{Report Induttore 3}
\subsection{Intervallo di campionamento}
\begin{tabular}{ll}\toprule
-\textbf{Parametro} & \textbf{Valore} \\\midrule
-Inizio & $115.0$ s \\
-Fine & $133.7$ s \\
-Numero di punti & 125 \\
+\textbf{Parametro}&\textbf{Valore}\\\midrule
+Inizio&$115.0$ s\\
+Fine&$133.7$ s\\
+N.\,punti&125\\
\bottomrule\end{tabular}
-
\subsection{Equazione trovata}
\begin{equation}
T(t) = 22.99 + 185.1847 \cdot e^{-(t - 115.0) / 16.2654}
\end{equation}
\begin{tabular}{lll}\toprule
-\textbf{Param.} & \textbf{Valore} & \textbf{Incertezza ($1\sigma$)} \\\midrule
-$A$ & $185.1847\,^\circ\mathrm{C}$ & $\pm\,0.2706\,^\circ\mathrm{C}$ \\
-$\tau$ & $16.2654\,\mathrm{s}$ & $\pm\,0.0464\,\mathrm{s}$ \\
-$R^2$ & \multicolumn{2}{l}{0.993827} \\
+\textbf{Param.}&\textbf{Valore}&\textbf{Incert.\,($1\sigma$)}\\\midrule
+$A$&$185.1847\,^{\circ}\mathrm{C}$&$\pm\,0.2706\,^{\circ}\mathrm{C}$\\
+$\tau$&$16.2654\,\mathrm{s}$&$\pm\,0.0464\,\mathrm{s}$\\
+$R^2$&\multicolumn{2}{l}{0.993827}\\
\bottomrule\end{tabular}
-
\subsection{Grafico}
\begin{figure}[H]\centering
-\includegraphics[width=0.9\textwidth]{C:\Users\davide.grilli\Desktop\vs\temperatura\induttore3\induttore3.png}
-\caption{Fit esponenziale -- Report Induttore 3 [115.0 – 133.7 s]}
+\includegraphics[width=.9\textwidth]{C:\Users\davide.grilli\Desktop\vs\temperatura\induttore3\induttore3.png}
+\caption{Fit esponenziale — Report Induttore 3 [115.0 – 133.7 s]}
\end{figure}\newpage
-
\section{Riepilogo}
\begin{tabular}{lllll}\toprule
-\textbf{Induttore} & \textbf{Intervallo [s]} & \textbf{A [°C]} & \textbf{$\tau$ [s]} & \textbf{$R^2$} \\\midrule
-Report Induttore 1 & 110.81 – 111.56 s & 87.3140 & 15.7231 & 0.999505 \\
-Report Induttore 2 & 112.02 – 113.95 s & 158.3430 & 13.2844 & 0.997314 \\
-Report Induttore 3 & 115.0 – 133.7 s & 185.1847 & 16.2654 & 0.993827 \\
+\textbf{Induttore}&\textbf{Intervallo [s]}&\textbf{$A$ [°C]}&\textbf{$\tau$ [s]}&\textbf{$R^2$}\\\midrule
+Report Induttore 1&110.81 – 111.56 s&87.3140&15.7231&0.999505\\
+Report Induttore 2&112.02 – 113.95 s&158.3430&13.2844&0.997314\\
+Report Induttore 3&115.0 – 133.7 s&185.1847&16.2654&0.993827\\
\bottomrule\end{tabular}
-
\end{document}
\ No newline at end of file