Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import re | |
| import textwrap | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Dict, Iterable, List, Tuple | |
| import numpy as np | |
| import pandas as pd | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| from docx import Document | |
| from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT | |
| from docx.enum.text import WD_ALIGN_PARAGRAPH | |
| from docx.oxml import OxmlElement | |
| from docx.oxml.ns import qn | |
| from docx.shared import Cm, Pt | |
| SCORE_MAP: Dict[str, int] = { | |
| "Absent": 1, | |
| "Minimum": 2, | |
| "Sufficient": 3, | |
| "Good": 4, | |
| "Excellent": 5, | |
| "Top": 6, | |
| } | |
| def to_score(x) -> float: | |
| if pd.isna(x): | |
| return float("nan") | |
| if isinstance(x, (int, float, np.integer, np.floating)): | |
| return float(x) | |
| s = str(x).strip() | |
| return float(SCORE_MAP.get(s, np.nan)) | |
| def label_color(label) -> str: | |
| """Return hex fill for a verbal label (no '#').""" | |
| if pd.isna(label): | |
| return "FFFFFF" | |
| s = str(label).strip() | |
| if s in ("Top", "Excellent"): | |
| return "C6EFCE" # light green | |
| if s in ("Good", "Sufficient"): | |
| return "FFEB9C" # light yellow | |
| if s in ("Minimum", "Absent"): | |
| return "FFC7CE" # light red | |
| return "FFFFFF" | |
| def extract_competence_blocks(columns: Iterable[str]) -> List[dict]: | |
| """Infer competences from 'Commento qualitativo - ...' blocks. | |
| For each competence, we assume exactly 4 indicator columns immediately | |
| before the comment column. | |
| """ | |
| cols = list(columns) | |
| comment_cols = [ | |
| c | |
| for c in cols | |
| if isinstance(c, str) and c.strip().lower().startswith("commento qualitativo -") | |
| ] | |
| blocks = [] | |
| for c in comment_cols: | |
| idx = cols.index(c) | |
| indicator_cols = cols[idx - 4 : idx] | |
| name = c.split("-", 1)[1].strip() | |
| blocks.append({"name": name, "indicator_cols": indicator_cols, "comment_col": c}) | |
| return blocks | |
| def wrap_label(s: str, width: int = 14) -> str: | |
| return "\n".join(textwrap.wrap(str(s), width=width, break_long_words=False)) | |
| def radar_chart(names: List[str], auto_vals: List[float], valut_vals: List[float], out_png: Path) -> None: | |
| """Radar con 2 sole serie (AUTO vs VALUT). | |
| Nota estetica: niente aree piene (o riempimento quasi trasparente) per evitare l'effetto | |
| "troppe aree" con 11 competenze; legenda grande e fuori dal grafico. | |
| """ | |
| labels = [wrap_label(n, 18) for n in names] | |
| n = len(labels) | |
| angles = np.linspace(0, 2 * np.pi, n, endpoint=False).tolist() | |
| angles += angles[:1] | |
| a = list(auto_vals) + [auto_vals[0]] | |
| v = list(valut_vals) + [valut_vals[0]] | |
| # Figura più larga per ospitare la legenda fuori dal grafico | |
| fig = plt.figure(figsize=(9.0, 7.2), dpi=220) | |
| ax = plt.subplot(111, polar=True) | |
| ax.set_theta_offset(np.pi / 2) | |
| ax.set_theta_direction(-1) | |
| ax.set_thetagrids(np.degrees(angles[:-1]), labels, fontsize=9) | |
| ax.tick_params(axis='x', pad=28) | |
| ax.set_ylim(0, 6) | |
| ax.set_yticks([1, 2, 3, 4, 5, 6]) | |
| ax.set_yticklabels(["1", "2", "3", "4", "5", "6"], fontsize=8) | |
| # Linee (niente riempimento) per una lettura più pulita | |
| ax.plot(angles, v, linewidth=2.4, color="#1f77b4", label="Valutazione") | |
| ax.plot(angles, a, linewidth=2.4, color="#ff7f0e", label="Autovalutazione") | |
| # Griglia un filo più leggera | |
| ax.grid(alpha=0.35) | |
| # Legenda: grande e fuori, dentro la figura (a destra) | |
| ax.legend( | |
| loc="center left", | |
| bbox_to_anchor=(1.04, 0.5), | |
| frameon=False, | |
| fontsize=12, | |
| ) | |
| # Lascia spazio a destra per la legenda | |
| fig.subplots_adjust(left=0.05, right=0.80, top=0.95, bottom=0.07) | |
| fig.savefig(out_png, transparent=True, bbox_inches="tight", pad_inches=0.25) | |
| plt.close(fig) | |
| def bar_chart(auto_mean: float, valut_mean: float, out_png: Path) -> None: | |
| """Barre AUTO vs VALUT con legenda grande fuori dal grafico.""" | |
| fig = plt.figure(figsize=(7.2, 3.4), dpi=220) | |
| ax = plt.gca() | |
| ax.bar([0], [valut_mean], width=0.42, color="#1f77b4", label="Valutazione") | |
| ax.bar([0.5], [auto_mean], width=0.42, color="#ff7f0e", label="Autovalutazione") | |
| ax.set_ylim(0, 6) | |
| ax.set_xticks([0.25]) | |
| ax.set_xticklabels([""], fontsize=10) | |
| ax.set_yticks([1, 2, 3, 4, 5, 6]) | |
| ax.grid(axis="y", alpha=0.28) | |
| for x, y in [(0, valut_mean), (0.5, auto_mean)]: | |
| ax.text(x, y + 0.12, f"{y:.2f}", ha="center", va="bottom", fontsize=10) | |
| # Legenda fuori (a destra), più grande | |
| ax.legend( | |
| loc="center left", | |
| bbox_to_anchor=(1.01, 0.8), | |
| frameon=False, | |
| fontsize=11, | |
| ) | |
| fig.subplots_adjust(left=0.08, right=0.80, top=0.92, bottom=0.18) | |
| fig.savefig(out_png, transparent=True, bbox_inches="tight", pad_inches=0.18) | |
| plt.close(fig) | |
| def _set_cell_shading(cell, fill: str) -> None: | |
| tcPr = cell._tc.get_or_add_tcPr() | |
| shd = OxmlElement("w:shd") | |
| shd.set(qn("w:val"), "clear") | |
| shd.set(qn("w:color"), "auto") | |
| shd.set(qn("w:fill"), fill) | |
| tcPr.append(shd) | |
| def _set_cell_text(cell, text, *, bold=False, align="left", font_size=9) -> None: | |
| cell.text = "" | |
| p = cell.paragraphs[0] | |
| run = p.add_run(str(text) if text is not None else "") | |
| run.bold = bold | |
| run.font.size = Pt(font_size) | |
| if align == "center": | |
| p.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| elif align == "right": | |
| p.alignment = WD_ALIGN_PARAGRAPH.RIGHT | |
| else: | |
| p.alignment = WD_ALIGN_PARAGRAPH.LEFT | |
| cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER | |
| def _insert_table_after(paragraph, rows: int, cols: int, width_cm: float = 17.0): | |
| tbl = paragraph._parent.add_table(rows=rows, cols=cols, width=Cm(width_cm)) | |
| paragraph._p.addnext(tbl._tbl) | |
| return tbl | |
| def _delete_paragraph(paragraph) -> None: | |
| p = paragraph._element | |
| p.getparent().remove(p) | |
| paragraph._p = paragraph._element = None | |
| def _clear_paragraph(paragraph) -> None: | |
| for r in paragraph.runs: | |
| r.text = "" | |
| def _replace_paragraph_with_picture(paragraph, image_path: Path, *, width_cm: float) -> None: | |
| _clear_paragraph(paragraph) | |
| run = paragraph.add_run() | |
| run.add_picture(str(image_path), width=Cm(width_cm)) | |
| paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| def _table_header(tbl, headers: List[str]) -> None: | |
| for j, h in enumerate(headers): | |
| c = tbl.cell(0, j) | |
| _set_cell_text(c, h, bold=True, align="center", font_size=9) | |
| _set_cell_shading(c, "D9D9D9") | |
| def _build_table_3_2(paragraph, comp_df: pd.DataFrame) -> None: | |
| tbl = _insert_table_after(paragraph, rows=len(comp_df) + 1, cols=3) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Competenza", "Autovalutazione", "Valutazione"]) | |
| for i, (_, row) in enumerate(comp_df.iterrows(), start=1): | |
| _set_cell_text(tbl.cell(i, 0), row["competenza"], align="left", font_size=9) | |
| _set_cell_text(tbl.cell(i, 1), f"{row['auto']:.2f}", align="center") | |
| _set_cell_text(tbl.cell(i, 2), f"{row['valut']:.2f}", align="center") | |
| tbl.columns[0].width = Cm(12.5) | |
| tbl.columns[1].width = Cm(2.5) | |
| tbl.columns[2].width = Cm(2.5) | |
| _delete_paragraph(paragraph) | |
| def _build_table_gap_4_1(paragraph, df: pd.DataFrame) -> None: | |
| tbl = _insert_table_after(paragraph, rows=len(df) + 1, cols=5) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Competenza", "Autoval.", "Valut.", "Gap", "Trend"]) | |
| for i, (_, r) in enumerate(df.iterrows(), start=1): | |
| _set_cell_text(tbl.cell(i, 0), r["competenza"], align="left", font_size=9) | |
| _set_cell_text(tbl.cell(i, 1), f"{r['auto']:.2f}", align="center") | |
| _set_cell_text(tbl.cell(i, 2), f"{r['valut']:.2f}", align="center") | |
| _set_cell_text(tbl.cell(i, 3), f"{r['diff']:+.2f}", align="center") | |
| _set_cell_text(tbl.cell(i, 4), r["trend"], align="center", font_size=11) | |
| tbl.columns[0].width = Cm(10.8) | |
| for j in range(1, 5): | |
| tbl.columns[j].width = Cm(1.9) | |
| _delete_paragraph(paragraph) | |
| def _build_table_gap_4_2(paragraph, df: pd.DataFrame) -> None: | |
| tbl = _insert_table_after(paragraph, rows=len(df) + 1, cols=3) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Competenza", "Valut.", "Gap da Top"]) | |
| for i, (_, r) in enumerate(df.iterrows(), start=1): | |
| _set_cell_text(tbl.cell(i, 0), r["competenza"], align="left", font_size=9) | |
| _set_cell_text(tbl.cell(i, 1), f"{r['valut']:.2f}", align="center") | |
| _set_cell_text(tbl.cell(i, 2), f"{r['gap_top']:.2f}", align="center") | |
| tbl.columns[0].width = Cm(12.5) | |
| tbl.columns[1].width = Cm(2.5) | |
| tbl.columns[2].width = Cm(2.5) | |
| _delete_paragraph(paragraph) | |
| def _build_table_indicators(paragraph, indicators: List[dict]) -> None: | |
| tbl = _insert_table_after(paragraph, rows=len(indicators) + 1, cols=3) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Comportamento osservabile", "Autovalutazione", "Valutazione"]) | |
| for i, ind in enumerate(indicators, start=1): | |
| _set_cell_text(tbl.cell(i, 0), ind["text"], align="left", font_size=8.5) | |
| cA = tbl.cell(i, 1) | |
| _set_cell_text(cA, ind["auto_label"], align="center") | |
| _set_cell_shading(cA, label_color(ind["auto_label"])) | |
| cV = tbl.cell(i, 2) | |
| _set_cell_text(cV, ind["valut_label"], align="center") | |
| _set_cell_shading(cV, label_color(ind["valut_label"])) | |
| tbl.columns[0].width = Cm(12.0) | |
| tbl.columns[1].width = Cm(2.6) | |
| tbl.columns[2].width = Cm(2.6) | |
| _delete_paragraph(paragraph) | |
| def _build_table_comments(paragraph, auto_comment, valut_comment) -> None: | |
| tbl = _insert_table_after(paragraph, rows=3, cols=2) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Fonte", "Commento qualitativo"]) | |
| _set_cell_text(tbl.cell(1, 0), "Autovalutazione", bold=True, align="left") | |
| _set_cell_text(tbl.cell(1, 1), auto_comment if pd.notna(auto_comment) else "", align="left") | |
| _set_cell_text(tbl.cell(2, 0), "Valutazione", bold=True, align="left") | |
| _set_cell_text(tbl.cell(2, 1), valut_comment if pd.notna(valut_comment) else "", align="left") | |
| tbl.columns[0].width = Cm(4.0) | |
| tbl.columns[1].width = Cm(13.4) | |
| _delete_paragraph(paragraph) | |
| def _build_table_behaviors(paragraph, rows: List[dict]) -> None: | |
| tbl = _insert_table_after(paragraph, rows=len(rows) + 1, cols=3) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Comportamento osservabile", "Competenza", "Valutazione"]) | |
| for i, r in enumerate(rows, start=1): | |
| _set_cell_text(tbl.cell(i, 0), r["indicator"], align="left", font_size=8.5) | |
| _set_cell_text(tbl.cell(i, 1), r["competenza"], align="left", font_size=8.5) | |
| c = tbl.cell(i, 2) | |
| _set_cell_text(c, r["label"], align="center") | |
| _set_cell_shading(c, label_color(r["label"])) | |
| tbl.columns[0].width = Cm(9.5) | |
| tbl.columns[1].width = Cm(5.8) | |
| tbl.columns[2].width = Cm(2.8) | |
| _delete_paragraph(paragraph) | |
| def _build_table_tech(paragraph, auto_text, valut_text) -> None: | |
| tbl = _insert_table_after(paragraph, rows=2, cols=2) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Autovalutazione", "Valutazione manager"]) | |
| _set_cell_text(tbl.cell(1, 0), auto_text if pd.notna(auto_text) else "", align="left") | |
| _set_cell_text(tbl.cell(1, 1), valut_text if pd.notna(valut_text) else "", align="left") | |
| tbl.columns[0].width = Cm(8.6) | |
| tbl.columns[1].width = Cm(8.6) | |
| _delete_paragraph(paragraph) | |
| def _build_table_feedback(paragraph, qas: List[Tuple[str, str]]) -> None: | |
| tbl = _insert_table_after(paragraph, rows=len(qas) + 1, cols=2) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Domanda", "Risposta"]) | |
| for i, (q, a) in enumerate(qas, start=1): | |
| _set_cell_text(tbl.cell(i, 0), q, align="left", font_size=8.5) | |
| _set_cell_text(tbl.cell(i, 1), a if pd.notna(a) else "", align="left") | |
| tbl.columns[0].width = Cm(6.5) | |
| tbl.columns[1].width = Cm(10.7) | |
| _delete_paragraph(paragraph) | |
| def _build_table_priority(paragraph, priorities: List[str], valut_by_comp: Dict[str, float]) -> None: | |
| rows = [] | |
| for rank, comp in enumerate(priorities, start=1): | |
| key = comp.lower() | |
| if key in valut_by_comp: | |
| rows.append((rank, comp, valut_by_comp[key])) | |
| tbl = _insert_table_after(paragraph, rows=len(rows) + 1, cols=3) | |
| tbl.alignment = WD_TABLE_ALIGNMENT.CENTER | |
| tbl.style = "Table Grid" | |
| _table_header(tbl, ["Priorità", "Competenza", "Valutazione"]) | |
| for i, (rank, comp, val) in enumerate(rows, start=1): | |
| _set_cell_text(tbl.cell(i, 0), str(rank), align="center") | |
| _set_cell_text(tbl.cell(i, 1), comp, align="left") | |
| _set_cell_text(tbl.cell(i, 2), f"{val:.2f}", align="center") | |
| tbl.columns[0].width = Cm(2.0) | |
| tbl.columns[1].width = Cm(12.5) | |
| tbl.columns[2].width = Cm(2.5) | |
| _delete_paragraph(paragraph) | |
| class PersonData: | |
| name: str | |
| comps: List[dict] | |
| auto_row: pd.Series | |
| valut_row: pd.Series | |
| def build_person_data(df_auto: pd.DataFrame, df_valut: pd.DataFrame, name: str) -> PersonData: | |
| # Robust selection: if a row is missing in AUTO or VALUT, we keep NaN/empty values. | |
| if "Nome e cognome" not in df_auto.columns: | |
| raise ValueError("Colonna 'Nome e cognome' non trovata nel file AUTO.") | |
| if "Nome e cognome" not in df_valut.columns: | |
| raise ValueError("Colonna 'Nome e cognome' non trovata nel file VALUT.") | |
| auto_match = df_auto[df_auto["Nome e cognome"] == name] | |
| valut_match = df_valut[df_valut["Nome e cognome"] == name] | |
| auto_row = auto_match.iloc[-1] if len(auto_match) else pd.Series({c: np.nan for c in df_auto.columns}) | |
| valut_row = valut_match.iloc[-1] if len(valut_match) else pd.Series({c: np.nan for c in df_valut.columns}) | |
| blocks = extract_competence_blocks(df_auto.columns) | |
| comps = [] | |
| for b in blocks: | |
| auto_labels = [auto_row[c] for c in b["indicator_cols"]] | |
| valut_labels = [valut_row.get(c, np.nan) for c in b["indicator_cols"]] | |
| auto_scores = [to_score(x) for x in auto_labels] | |
| valut_scores = [to_score(x) for x in valut_labels] | |
| comps.append( | |
| { | |
| "name": b["name"], | |
| "indicator_texts": b["indicator_cols"], | |
| "auto_labels": auto_labels, | |
| "valut_labels": valut_labels, | |
| "auto_scores": auto_scores, | |
| "valut_scores": valut_scores, | |
| "auto_mean": float(np.nanmean(auto_scores)), | |
| "valut_mean": float(np.nanmean(valut_scores)), | |
| "auto_comment": auto_row[b["comment_col"]], | |
| "valut_comment": valut_row.get(b["comment_col"], np.nan), | |
| } | |
| ) | |
| return PersonData(name=name, comps=comps, auto_row=auto_row, valut_row=valut_row) | |
| def fill_template( | |
| template_path: Path, | |
| out_docx: Path, | |
| df_auto: pd.DataFrame, | |
| df_valut: pd.DataFrame, | |
| person_name: str, | |
| kind: str, | |
| *, | |
| workdir: Path, | |
| ) -> Path: | |
| """Fill a Word template replacing only placeholders (template formatting stays intact).""" | |
| doc = Document(str(template_path)) | |
| pdata = build_person_data(df_auto, df_valut, person_name) | |
| comps = pdata.comps | |
| comp_df = pd.DataFrame( | |
| [{"competenza": c["name"], "auto": c["auto_mean"], "valut": c["valut_mean"]} for c in comps] | |
| ) | |
| comp_df_sorted = comp_df.sort_values("valut", ascending=False).reset_index(drop=True) | |
| gap_df = comp_df.copy() | |
| gap_df["diff"] = gap_df["valut"] - gap_df["auto"] | |
| def trend(diff: float) -> str: | |
| if -0.5 <= diff <= 0.5: | |
| return "↔" | |
| if diff < -0.5: | |
| return "↑" | |
| return "↓" | |
| gap_df["trend"] = gap_df["diff"].apply(trend) | |
| gap_df["abs"] = gap_df["diff"].abs() | |
| gap_df = gap_df.sort_values(["abs", "diff"], ascending=[False, False]).drop(columns=["abs"]).reset_index(drop=True) | |
| gtop = comp_df.copy() | |
| gtop["gap_top"] = 6 - gtop["valut"] | |
| gtop = gtop.sort_values("gap_top", ascending=False).reset_index(drop=True) | |
| behaviors = [] | |
| for c in comps: | |
| for txt, label, score in zip(c["indicator_texts"], c["valut_labels"], c["valut_scores"]): | |
| if pd.notna(score): | |
| behaviors.append({"indicator": txt, "competenza": c["name"], "label": label, "score": float(score)}) | |
| beh_df = pd.DataFrame(behaviors) | |
| beh_top = beh_df.sort_values("score", ascending=False).head(10).to_dict("records") | |
| beh_bot = beh_df.sort_values("score", ascending=True).head(10).to_dict("records") | |
| # Charts | |
| img_dir = workdir / re.sub(r"[^A-Za-z0-9_-]+", "_", person_name) | |
| img_dir.mkdir(parents=True, exist_ok=True) | |
| radar_png = img_dir / "radar.png" | |
| radar_chart([c["name"] for c in comps], [c["auto_mean"] for c in comps], [c["valut_mean"] for c in comps], radar_png) | |
| comp_bar: Dict[int, Path] = {} | |
| for idx, c in enumerate(comps, start=1): | |
| png = img_dir / f"bar_{idx}.png" | |
| bar_chart(c["auto_mean"], c["valut_mean"], png) | |
| comp_bar[idx] = png | |
| # Qualitative | |
| fb_qs = [ | |
| "Quale comportamento/atteggiamento dovrebbe continuare ad agire il mio responsabile?", | |
| "Quale comportamento/atteggiamento dovrebbe iniziare ad agire?", | |
| "Quale comportamento/atteggiamento suggerisco di smettere di agire?", | |
| ] | |
| qas = [(q, pdata.auto_row.get(q, "")) for q in fb_qs] | |
| auto_tech = "" | |
| val_tech = "" | |
| if kind == "collaboratori": | |
| auto_tech_q = [ | |
| c for c in df_auto.columns if isinstance(c, str) and c.strip().lower().startswith("indica 1 competenza tecnica") | |
| ] | |
| val_tech_q = [ | |
| c for c in df_valut.columns if isinstance(c, str) and c.strip().lower().startswith("indica 1 competenza tecnica") | |
| ] | |
| if auto_tech_q: | |
| auto_tech = pdata.auto_row.get(auto_tech_q[0], "") | |
| if val_tech_q: | |
| val_tech = pdata.valut_row.get(val_tech_q[0], "") | |
| priorities = [ | |
| "Attenzione alla qualità", | |
| "Capacità di comunicazione efficace e ascolto attivo", | |
| "Spirito di iniziativa e orientamento al risultato", | |
| "Proporre decisioni e lavorare con senso di responsabilità", | |
| "Orientamento al cliente (interno/esterno)", | |
| ] | |
| valut_by_comp = {c["name"].lower(): float(c["valut_mean"]) for c in comps} | |
| # Replace placeholders | |
| done_radar = False | |
| for p in list(doc.paragraphs): | |
| t = p.text.strip().replace("\t", "") | |
| if t == "[@NomeCognome]": | |
| # Mantieni lo stile del template: sostituisci solo il placeholder. | |
| _clear_paragraph(p) | |
| p.add_run(person_name) | |
| elif t == "[@GraficoSezione3.2]": | |
| if kind == "manager" and done_radar: | |
| _delete_paragraph(p) | |
| else: | |
| _replace_paragraph_with_picture(p, radar_png, width_cm=16.2) | |
| done_radar = True | |
| elif t == "[@TabellaSezione3.2]": | |
| _build_table_3_2(p, comp_df_sorted) | |
| elif t == "[@TabellaSezione4.1]": | |
| _build_table_gap_4_1(p, gap_df) | |
| elif t == "[@TabellaSezione4.2]": | |
| _build_table_gap_4_2(p, gtop) | |
| else: | |
| m = re.fullmatch(r"\[@GraficoSezione5\.(\d+)\]", t) | |
| if m: | |
| idx = int(m.group(1)) | |
| if idx in comp_bar: | |
| _replace_paragraph_with_picture(p, comp_bar[idx], width_cm=15.6) | |
| continue | |
| m = re.fullmatch(r"\[@Tabella1Sezione5\.(\d+)\]", t) | |
| if m: | |
| idx = int(m.group(1)) | |
| if 1 <= idx <= len(comps): | |
| c = comps[idx - 1] | |
| indicators = [ | |
| {"text": txt, "auto_label": al, "valut_label": vl} | |
| for txt, al, vl in zip(c["indicator_texts"], c["auto_labels"], c["valut_labels"]) | |
| ] | |
| _build_table_indicators(p, indicators) | |
| continue | |
| m = re.fullmatch(r"\[@Tabella2Sezione5\.(\d+)\]", t) | |
| if m: | |
| idx = int(m.group(1)) | |
| if 1 <= idx <= len(comps): | |
| c = comps[idx - 1] | |
| _build_table_comments(p, c["auto_comment"], c["valut_comment"]) | |
| continue | |
| if t == "[@TabellaSezione6.1]": | |
| _build_table_behaviors(p, beh_top) | |
| elif t == "[@TabellaSezione6.2]": | |
| _build_table_behaviors(p, beh_bot) | |
| elif t == "[@TabellaSezione7.1]": | |
| if kind == "collaboratori": | |
| _build_table_tech(p, auto_tech, val_tech) | |
| else: | |
| _build_table_feedback(p, qas) | |
| elif t == "[@TabellaSezione7.2]": | |
| _build_table_feedback(p, qas) | |
| elif t == "[@TabellaSezione8.1]": | |
| if kind == "collaboratori": | |
| _build_table_priority(p, priorities, valut_by_comp) | |
| else: | |
| _delete_paragraph(p) | |
| doc.save(str(out_docx)) | |
| return out_docx | |