← Files VeraARCHIVED FILE

modules/fusione-guidata/scripts/fusione_dossier.py

6.75 KB · Oct 5, 2026 · 18:29 UTC

↓ Download file

"""Readable, escaped P1 workpapers and dossiers from scoped persisted case records."""

from __future__ import annotations

import html
import json
from pathlib import Path
from typing import Any

__all__ = ["write_workpapers"]

LABELS = {
    "BranchDecision": "Perimetro dell'incorporazione",
    "Valuation": "Valutazione e ponte all'equity",
    "ExchangeModel": "Concambio e assegnazioni",
    "BookBridge": "Ponte contabile e valori fiscali",
    "Deadline": "Calendario per eventi",
    "LegalDocument": "Dossier di revisione",
}


def cell(value: Any) -> str:
    if isinstance(value, (dict, list)):
        return json.dumps(value, ensure_ascii=False)
    return "—" if value is None else str(value)


def render(value: Any, depth: int = 3) -> tuple[str, str]:
    """Render nested evidence without executing model-authored HTML or Markdown."""
    if isinstance(value, dict):
        md, markup = [], []
        for key, entry in value.items():
            title = key.replace("_", " ")
            inner_md, inner_html = render(entry, min(depth + 1, 6))
            md.extend(["#" * depth + " " + title, "", inner_md, ""])
            markup.append(f"<h{depth}>{html.escape(title)}</h{depth}>{inner_html}")
        return "\n".join(md), "".join(markup)
    if (
        isinstance(value, list)
        and value
        and all(isinstance(row, dict) for row in value)
    ):
        keys = list(dict.fromkeys(key for row in value for key in row))
        md = [
            "| " + " | ".join(keys) + " |",
            "| " + " | ".join("---" for _ in keys) + " |",
        ]
        rows = []
        for row in value:
            values = [cell(row.get(key)) for key in keys]
            md.append(
                "| "
                + " | ".join(v.replace("|", "\\|").replace("\n", " ") for v in values)
                + " |"
            )
            rows.append(
                "<tr>"
                + "".join("<td>" + html.escape(v) + "</td>" for v in values)
                + "</tr>"
            )
        table = (
            "<div class='table'><table><thead><tr>"
            + "".join("<th>" + html.escape(k.replace("_", " ")) + "</th>" for k in keys)
            + "</tr></thead><tbody>"
            + "".join(rows)
            + "</tbody></table></div>"
        )
        return "\n".join(md), table
    text = cell(value)
    return text, "<p>" + html.escape(text).replace("\n", "<br>") + "</p>"


def write_workpapers(report: dict[str, Any], destination: Path) -> dict[str, str]:
    """Write both human-readable formats; never treat an exported draft as a filing."""
    items = [
        row
        for row in report["records"]
        if row["record"]["data"].get("engine_version") == "fusione.p1.v1"
    ]
    if not items:
        return {}
    title = "Fusione guidata · P1 — fascicolo di revisione"
    boundary = (
        "DATI INTERAMENTE SINTETICI"
        if report["synthetic"]
        else "Bozza per revisione professionale"
    )
    md = [
        "# " + title,
        "",
        boundary,
        "",
        "Le conferme valgono per la versione e l'ambito registrati. Nessuna firma, deposito o efficacia giuridica è attestata.",
        "",
    ]
    sections = []
    for item in items:
        row, status = item["record"], item["status"]
        heading = LABELS[row["kind"]] + " · " + row["id"]
        state = f"Versione {row['version']} · {status['review_state']}"
        issues = (
            "; ".join(status["issues"])
            or "Nessuna eccezione strutturale rilevata; revisione professionale secondo lo stato indicato."
        )
        body_md, body_html = render(
            row["data"]["result"]
            if row["data"]["result"] is not None
            else {"risultato": "Non calcolato", "problemi": row["data"]["issues"]}
        )
        md.extend(
            [
                "## " + heading,
                "",
                state,
                "",
                issues,
                "",
                body_md,
                "",
                "Riferimenti esatti: "
                + json.dumps(row["dependencies"], ensure_ascii=False),
                "",
            ]
        )
        sections.append(
            "<section><h2>"
            + html.escape(heading)
            + "</h2><p class='state'>"
            + html.escape(state)
            + "</p><p>"
            + html.escape(issues)
            + "</p>"
            + body_html
            + "<details><summary>Versioni e impronte delle fonti</summary><pre>"
            + html.escape(json.dumps(row["dependencies"], indent=2))
            + "</pre></details></section>"
        )
    disclosure = "Il runtime Codex o Cowork può leggere identità, documenti selezionati, saldi, valori, soci, bozze, fonti e conferme necessari al lavoro. Il programma non osserva il contesto del modello: per i casi reali l'esposizione resta non misurabile. Non è applicata anonimizzazione automatica. Il report dati locale è disponibile accanto al fascicolo."
    md += ["## Quali dati arrivano al modello", "", disclosure]
    markdown = destination / "p1-workpapers.md"
    markdown.write_text("\n".join(md) + "\n", encoding="utf-8")
    stylesheet = "body{font:17px/1.6 system-ui,sans-serif;color:#16233b;background:#fff;margin:0}main{max-width:1100px;margin:auto;padding:36px 24px}h1,h2{color:#002060;line-height:1.2}h1{font-size:36px}h2{font-size:27px}h3,h4,h5,h6{font-size:18px;margin:24px 0 8px}section{padding:28px 0;border-top:1px solid #dbe2ed}.state{color:#0070c0}p,td{overflow-wrap:anywhere}.table{overflow:auto}table{border-collapse:collapse;width:100%;font-size:14px}th,td{text-align:left;vertical-align:top;padding:10px;border-bottom:1px solid #dbe2ed}th{color:#002060}pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:12px}details{margin-top:20px}a{color:#0070c0}@media print{main{max-width:none;padding:0}details{display:none}.table{overflow:visible}section{break-inside:auto}}"
    page = (
        "<!doctype html><html lang='it'><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><meta http-equiv='Content-Security-Policy' content=\"default-src 'none'; style-src 'unsafe-inline'; img-src 'none'\"><title>"
        + title
        + "</title><style>"
        + stylesheet
        + "</style><main><h1>"
        + title
        + "</h1><p>"
        + boundary
        + "</p><p>Le conferme valgono per la versione e l'ambito registrati. Nessuna firma, deposito o efficacia giuridica è attestata.</p>"
        + "".join(sections)
        + "<section><h2>Quali dati arrivano al modello</h2><p>"
        + disclosure
        + "</p><a href='model_data_report.md'>Report dati locale</a></section></main></html>"
    )
    target = destination / "p1-workpapers.html"
    target.write_text(page, encoding="utf-8")
    return {"workpapers": str(markdown), "review_html": str(target)}

SHA-256: 816642abf56afa89878239166cba3dd4a16296f9ebbe95efbe7f894c5ebeb7b0