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