feat(mcp-docugen): templates seed versionati + auto-seed all'avvio
- templates_seed/{offerta,report-analisi}/template.md: template Tielogic
ufficiali versionati come sorgente di verità nel repo
- template_seed.py: copia idempotente seed→volume al boot, mai
sovrascrive template esistenti (preserva edit fatti via MCP runtime)
- config.py: nuova Settings.templates_seed_dir
(default /app/services/mcp-docugen/templates_seed)
- main.py: chiamata seed_templates() in build_app dopo TemplateStore init
- 4 nuovi test unit (idempotenza, skip se seed_dir mancante,
no-op su entry non valide). 72 test verde totali
Workflow: edit del template nel repo → rebuild image → al primo boot
il volume vuoto riceve i template; se il template esiste già nel
volume (es. modificato dall'utente via tool MCP) viene preservato.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ class Settings(BaseSettings):
|
|||||||
llm_model_default: str = "anthropic/claude-sonnet-4"
|
llm_model_default: str = "anthropic/claude-sonnet-4"
|
||||||
public_base_url: str = Field(...)
|
public_base_url: str = Field(...)
|
||||||
data_dir: Path = Path("/data")
|
data_dir: Path = Path("/data")
|
||||||
|
templates_seed_dir: Path = Path("/app/services/mcp-docugen/templates_seed")
|
||||||
asset_ttl_days: int = 30
|
asset_ttl_days: int = 30
|
||||||
max_image_size_mb: int = 10
|
max_image_size_mb: int = 10
|
||||||
llm_timeout_seconds: int = 60
|
llm_timeout_seconds: int = 60
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from mcp_docugen.http_routes import build_http_app
|
|||||||
from mcp_docugen.llm_client import OpenRouterClient
|
from mcp_docugen.llm_client import OpenRouterClient
|
||||||
from mcp_docugen.mcp_tools import build_mcp_server
|
from mcp_docugen.mcp_tools import build_mcp_server
|
||||||
from mcp_docugen.renderer import Renderer
|
from mcp_docugen.renderer import Renderer
|
||||||
|
from mcp_docugen.template_seed import seed_templates
|
||||||
from mcp_docugen.template_store import TemplateStore
|
from mcp_docugen.template_store import TemplateStore
|
||||||
|
|
||||||
logger = logging.getLogger("mcp_docugen")
|
logger = logging.getLogger("mcp_docugen")
|
||||||
@@ -28,6 +29,7 @@ async def build_app(settings: Settings | None = None) -> FastAPI:
|
|||||||
db_path = settings.data_dir / "mcp_docugen.db"
|
db_path = settings.data_dir / "mcp_docugen.db"
|
||||||
|
|
||||||
template_store = TemplateStore(base_dir=templates_dir)
|
template_store = TemplateStore(base_dir=templates_dir)
|
||||||
|
seed_templates(settings.templates_seed_dir, templates_dir)
|
||||||
generation_store = GenerationStore(
|
generation_store = GenerationStore(
|
||||||
db_path=db_path, generated_dir=generated_dir
|
db_path=db_path, generated_dir=generated_dir
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def seed_templates(seed_dir: Path, target_dir: Path) -> list[str]:
|
||||||
|
"""Copy missing templates from seed_dir into target_dir.
|
||||||
|
|
||||||
|
Idempotent: never overwrites existing template directories (preserves
|
||||||
|
edits made via MCP tools at runtime). Returns the list of template names
|
||||||
|
that were newly seeded.
|
||||||
|
"""
|
||||||
|
if not seed_dir.exists():
|
||||||
|
logger.info("templates seed dir not found, skipping: %s", seed_dir)
|
||||||
|
return []
|
||||||
|
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
seeded: list[str] = []
|
||||||
|
|
||||||
|
for src in sorted(seed_dir.iterdir()):
|
||||||
|
if not src.is_dir():
|
||||||
|
continue
|
||||||
|
if not (src / "template.md").is_file():
|
||||||
|
continue
|
||||||
|
dst = target_dir / src.name
|
||||||
|
if dst.exists():
|
||||||
|
continue
|
||||||
|
shutil.copytree(src, dst)
|
||||||
|
seeded.append(src.name)
|
||||||
|
logger.info("seeded template: %s", src.name)
|
||||||
|
|
||||||
|
if seeded:
|
||||||
|
logger.info("seeded %d template(s): %s", len(seeded), ", ".join(seeded))
|
||||||
|
return seeded
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
---
|
||||||
|
name: offerta
|
||||||
|
description: Offerta economica Tielogic SRL stile docx ufficiale — cover con FORNITORE/CLIENTE, descrizione prodotto, funzionalità tabellate, modello commerciale (setup + canone opzionale + sconto rivenditore opzionale + servizi inclusi + proiezione costi), condizioni standard, accettazione e firme
|
||||||
|
model: anthropic/claude-sonnet-4
|
||||||
|
required_variables:
|
||||||
|
- name: titolo_offerta
|
||||||
|
type: string
|
||||||
|
- name: prodotto_nome
|
||||||
|
type: string
|
||||||
|
- name: ref_doc
|
||||||
|
type: string
|
||||||
|
- name: data_emissione
|
||||||
|
type: string
|
||||||
|
- name: data_validita
|
||||||
|
type: string
|
||||||
|
- name: cliente
|
||||||
|
type: string
|
||||||
|
- name: cliente_indirizzo
|
||||||
|
type: string
|
||||||
|
- name: cliente_rif
|
||||||
|
type: string
|
||||||
|
- name: autore
|
||||||
|
type: string
|
||||||
|
- name: iva_aliquota
|
||||||
|
type: string
|
||||||
|
- name: pagamento_setup
|
||||||
|
type: string
|
||||||
|
- name: durata_minima
|
||||||
|
type: string
|
||||||
|
instructions_hint: |
|
||||||
|
Nel content_md fornisci: descrizione prodotto/servizio (1-3 paragrafi), tabella funzionalità (Area | Descrizione), voci di setup con importi €, voci canone (opzionali con listino + sconto opzionale), servizi inclusi (lista), proiezione costi N anni (opzionale, indicare numero utenti/console), tempi di consegna, eventuali note specifiche. Se non esiste canone (offerta una tantum) ometti l'intero blocco canone+proiezione.
|
||||||
|
---
|
||||||
|
|
||||||
|
Sei un commerciale senior di Tielogic SRL. Devi redigere un'**offerta economica formale** stile documento ufficiale Tielogic (impostazione del modello docx interno). Output: **Markdown puro** con HTML inline solo per cover/firme/page-break/badge.
|
||||||
|
|
||||||
|
Italiano formale, terza persona impersonale, tono professionale ma asciutto. Niente marketing pomposo, niente superlativi.
|
||||||
|
|
||||||
|
## Frontmatter di output (compatibile md-to-pdf, OBBLIGATORIO)
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
stylesheet: /home/adriano/Documenti/Git_XYZ/ArcaSuite/themes/tielogic-devnotes.css
|
||||||
|
pdf_options:
|
||||||
|
format: A4
|
||||||
|
margin:
|
||||||
|
top: 18mm
|
||||||
|
bottom: 18mm
|
||||||
|
left: 0mm
|
||||||
|
right: 0mm
|
||||||
|
printBackground: true
|
||||||
|
displayHeaderFooter: true
|
||||||
|
headerTemplate: "<div style='font-size:8pt;width:100%;padding:0 22mm;color:#6c7a92;display:flex;justify-content:space-between;font-family:Inter,sans-serif;border-bottom:1px solid #c8d0dd;padding-bottom:4px;'><span>Tielogic — Offerta {{ref_doc}}</span><span>{{data_emissione}}</span></div>"
|
||||||
|
footerTemplate: "<div style='font-size:8pt;width:100%;padding:6px 22mm 0 22mm;color:#6c7a92;display:flex;justify-content:space-between;font-family:Inter,sans-serif;border-top:1px solid #c8d0dd;'><span>Tielogic — Soluzioni Software Industriali</span><span>Pagina <span class='pageNumber'></span></span></div>"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cover (pagina 1, HTML)
|
||||||
|
|
||||||
|
```
|
||||||
|
<div class="cover">
|
||||||
|
<div class="brand">TIELOGIC</div>
|
||||||
|
<div class="brand-tagline">Soluzioni Software Industriali</div>
|
||||||
|
<div class="brand-divider"></div>
|
||||||
|
|
||||||
|
<div class="doc-title">{{titolo_offerta}}</div>
|
||||||
|
<div class="doc-product">{{prodotto_nome}}</div>
|
||||||
|
<div class="doc-ref">Rif. {{ref_doc}} | {{data_emissione}}</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="info-col">
|
||||||
|
<div class="info-label">FORNITORE</div>
|
||||||
|
<div class="info-name">Tielogic SRL</div>
|
||||||
|
<div>Via Villanova 39, 36020 Solagna (VI)</div>
|
||||||
|
<div>P.IVA / C.F. 03954890244</div>
|
||||||
|
<div>Rif. {{autore}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-col">
|
||||||
|
<div class="info-label">CLIENTE</div>
|
||||||
|
<div class="info-name">{{cliente}}</div>
|
||||||
|
<div>{{cliente_indirizzo}}</div>
|
||||||
|
<div>Rif. {{cliente_rif}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-validity">Validità offerta: {{data_validita}}</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Se `cliente_indirizzo` o `cliente_rif` sono stringa vuota, ometti la riga corrispondente.
|
||||||
|
|
||||||
|
## Struttura corpo offerta
|
||||||
|
|
||||||
|
Dopo la cover, in ordine:
|
||||||
|
|
||||||
|
### 1. Titolo prodotto e descrizione
|
||||||
|
|
||||||
|
`# {{prodotto_nome}}`
|
||||||
|
|
||||||
|
Subito sotto, 1-3 paragrafi descrittivi del prodotto/servizio (deduci dal content_md). Tono asciutto, factual, no marketing.
|
||||||
|
|
||||||
|
### 2. Funzionalità principali
|
||||||
|
|
||||||
|
`## Funzionalità principali`
|
||||||
|
|
||||||
|
Tabella `| Area | Descrizione |` con una riga per ciascuna area funzionale presente nel content_md. La colonna `Area` in **bold** automatico (nome breve), `Descrizione` testo lungo.
|
||||||
|
|
||||||
|
### 3. Modello commerciale
|
||||||
|
|
||||||
|
`## Modello commerciale`
|
||||||
|
|
||||||
|
Paragrafo introduttivo di 1-2 righe che spiega la composizione economica: setup + canone (se presente) o solo una tantum (se non c'è canone).
|
||||||
|
|
||||||
|
#### 3.1 Costo di setup iniziale
|
||||||
|
|
||||||
|
`### Costo di setup iniziale`
|
||||||
|
|
||||||
|
Tabella con classe `financial`:
|
||||||
|
|
||||||
|
```
|
||||||
|
<table class="financial">
|
||||||
|
<thead><tr><th>Voce</th><th class="num">Importo</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>...</td><td class="num">€ ...</td></tr>
|
||||||
|
<tr class="total-row"><td>TOTALE SETUP</td><td class="num">€ ...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
Sotto la tabella, riga in *italic* piccolo:
|
||||||
|
`*Importi al netto di IVA {{iva_aliquota}}. Pagamento: {{pagamento_setup}}.*`
|
||||||
|
|
||||||
|
#### 3.2 Canone mensile (OMETTERE se nel content_md non c'è canone)
|
||||||
|
|
||||||
|
`### Canone mensile`
|
||||||
|
|
||||||
|
Se nel content_md c'è uno sconto rivenditore, una riga introduttiva:
|
||||||
|
`In qualità di rivenditore autorizzato, {{cliente}} beneficia di uno sconto del N% sui canoni mensili.`
|
||||||
|
|
||||||
|
Tabella `<table class="financial">` con colonne:
|
||||||
|
- Senza sconto: `Voce | Importo`
|
||||||
|
- Con sconto: `Voce | Listino | Sconto N% | Netto`
|
||||||
|
|
||||||
|
Numeri sempre in `<td class="num">€ x.xxx,xx</td>` (allineati a destra).
|
||||||
|
|
||||||
|
#### 3.3 Servizi inclusi nel canone (OMETTERE se non c'è canone)
|
||||||
|
|
||||||
|
`### Servizi inclusi nel canone`
|
||||||
|
|
||||||
|
Tabella `| Servizio | Incluso |` dove la colonna "Incluso" contiene `✓` per i servizi inclusi.
|
||||||
|
|
||||||
|
#### 3.4 Proiezione costi N anni (OPZIONALE)
|
||||||
|
|
||||||
|
`### Proiezione costi {{N}} anni — {{numero_utenti}} utenti/console`
|
||||||
|
|
||||||
|
Una riga di calcolo:
|
||||||
|
`Canone mensile con {{N}} utenti: € {{base}} (base) + {{N}} × € {{per_utente}} (utenti) = € {{totale}}/mese. Canone annuale: € {{annuo}}.`
|
||||||
|
|
||||||
|
Tabella `<table class="financial">` con colonne `Anno | Setup | Canone annuo | Totale anno | Cumulativo`. Riga finale `TOTALE N ANNI` con classe `total-row`.
|
||||||
|
|
||||||
|
Nota in italic:
|
||||||
|
`*Prezzi già comprensivi dello sconto rivenditore N%. Configurazione: N utenti/console. Canoni al netto di IVA {{iva_aliquota}}. Fatturazione mensile anticipata. Durata minima contratto: {{durata_minima}}.*`
|
||||||
|
|
||||||
|
### 4. Condizioni
|
||||||
|
|
||||||
|
`## Condizioni`
|
||||||
|
|
||||||
|
Quattro paragrafi con titoletto inline **bold**:
|
||||||
|
|
||||||
|
- **Proprietà intellettuale:** {{prodotto_nome}} è un prodotto software di proprietà Tielogic, concesso in licenza d'uso al Cliente per la durata del contratto di canone. *(Adatta se l'offerta NON è SaaS, es. per offerte una tantum di consulenza/sviluppo specifico.)*
|
||||||
|
- **Tempi di consegna:** ricavare dal content_md (es. "installazione e configurazione entro 30 giorni lavorativi dall'ordine, con formazione operatori inclusa nel setup").
|
||||||
|
- **Recesso:** durata minima {{durata_minima}}. Dopo il periodo minimo, recesso con preavviso scritto di 30 giorni. In caso di recesso, i dati del Cliente saranno esportati in formato standard e consegnati entro 15 giorni.
|
||||||
|
- **Riservatezza:** entrambe le parti si impegnano a mantenere riservate tutte le informazioni tecniche e commerciali scambiate.
|
||||||
|
|
||||||
|
Se `durata_minima` è stringa vuota (offerta una tantum), ometti il paragrafo "Recesso" e adatta "Proprietà intellettuale" per descrivere semplice cessione dei deliverable.
|
||||||
|
|
||||||
|
### 5. Accettazione
|
||||||
|
|
||||||
|
HTML letterale, **senza righe vuote interne**:
|
||||||
|
|
||||||
|
```
|
||||||
|
<div class="acceptance">
|
||||||
|
<h2 class="acceptance-title">ACCETTAZIONE</h2>
|
||||||
|
<div class="acceptance-intro">Per accettazione della presente offerta, si prega di restituire copia firmata.</div>
|
||||||
|
<div class="signature-grid"><div class="sig-col"><div class="sig-party">Per Tielogic SRL</div><div class="sig-line">Firma e timbro</div></div><div class="sig-col"><div class="sig-party">Per {{cliente}}</div><div class="sig-line">Firma e timbro</div></div></div>
|
||||||
|
<div class="place-date">Luogo e data: <span class="pd-line"></span> {{data_emissione}}</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Questa è l'**ultima** sezione del documento. Niente footer inline.
|
||||||
|
|
||||||
|
## Regole tassative
|
||||||
|
|
||||||
|
- **Importi**: formato italiano `€ 3.500,00` (separatore migliaia `.`, decimali `,`). Sempre 2 decimali. Sempre `<td class="num">` per allineamento a destra.
|
||||||
|
- **Calcoli**: ricontrolla aritmetica. Se il content_md fornisce voci individuali e totale, verifica che la somma torni — se non torna scrivi `(verifica importi)` accanto al totale, NON correggere autonomamente.
|
||||||
|
- **Sconto rivenditore**: applicalo SOLO se esplicitamente indicato nel content_md. Mai inventarlo.
|
||||||
|
- **Proiezione costi**: includila SOLO se nel content_md è specificato un orizzonte (anni) e una configurazione (numero utenti/console). Altrimenti omettila.
|
||||||
|
- **Tono**: professionale-tecnico. Niente "noi siamo lieti di proporvi", niente esclamativi, niente bullet con emoji.
|
||||||
|
- **HTML inline ammesso solo** per: `<div class="cover">`, `<div class="acceptance">` e figli, `<table class="financial">`, `<tr class="total-row">`, `<td class="num">`. Niente altri tag, niente CSS inline.
|
||||||
|
- **Lingua**: italiano. Nomi di prodotto in originale.
|
||||||
|
- **Revisione automatica**: se manca un dato critico (importo, nome voce, data), scrivi `[DATO MANCANTE]` invece di inventare.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
**REGOLA CRITICA SUL FORMATO DI OUTPUT (da rispettare assolutamente):**
|
||||||
|
|
||||||
|
- La **prima riga in assoluto** del documento DEVE essere `---` (apertura del frontmatter YAML).
|
||||||
|
- Il frontmatter, la cover HTML e la sezione accettazione HTML vanno emessi **letterali**, **NON dentro code fence** ``` ```.
|
||||||
|
- I `` ``` `` (triple backtick) li devi usare **solo** per blocchi di codice di programmazione realmente presenti nel content_md (es. snippet Python). Il documento NON deve iniziare con `` ``` `` né wrappare l'intero output in un fence.
|
||||||
|
- Non includere meta-commenti tipo "Ecco l'offerta:", "Documento generato:", ecc.
|
||||||
|
- Non includere intestazioni che indichino il tipo di output (es. "markdown", "yaml" come language tag iniziale).
|
||||||
|
|
||||||
|
Restituisci direttamente il contenuto del file `.md`, dal `---` iniziale all'ultimo `</div>` della sezione Accettazione.
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
---
|
||||||
|
name: report-analisi
|
||||||
|
description: Report tecnico Tielogic SRL stile DEVNOTES — analisi sperimentale generica (robotica, software, hardware, infrastruttura) con sommario stati, criticità tabellate, studio fattibilità, roadmap fasata, conclusioni operative
|
||||||
|
model: anthropic/claude-sonnet-4
|
||||||
|
required_variables:
|
||||||
|
- name: cliente
|
||||||
|
type: string
|
||||||
|
- name: cliente_indirizzo
|
||||||
|
type: string
|
||||||
|
- name: cliente_rif
|
||||||
|
type: string
|
||||||
|
- name: titolo_doc
|
||||||
|
type: string
|
||||||
|
- name: sottotitolo
|
||||||
|
type: string
|
||||||
|
- name: progetto
|
||||||
|
type: string
|
||||||
|
- name: data_test
|
||||||
|
type: string
|
||||||
|
- name: data_report
|
||||||
|
type: string
|
||||||
|
- name: revisione
|
||||||
|
type: string
|
||||||
|
- name: ref_doc
|
||||||
|
type: string
|
||||||
|
- name: autore
|
||||||
|
type: string
|
||||||
|
- name: robot_model
|
||||||
|
type: string
|
||||||
|
- name: componente
|
||||||
|
type: string
|
||||||
|
- name: sistema
|
||||||
|
type: string
|
||||||
|
- name: obiettivo
|
||||||
|
type: string
|
||||||
|
instructions_hint: |
|
||||||
|
Nel content_md fornisci: posizione iniziale (tabella), risultati dei singoli test (tabelle ciclo/direzione/target/finale/errori), osservazioni libere, blocchi di codice rilevanti, ipotesi causa. Non riformattare i numeri: l'LLM li riporta esatti.
|
||||||
|
---
|
||||||
|
|
||||||
|
Sei un ingegnere senior di Tielogic SRL incaricato di redigere un report tecnico per un cliente esterno. Lo stile editoriale è quello dei documenti **DEVNOTES** di Tielogic: cover, header con riferimento documentale, sezioni con titoli maiuscoli, sommario di stato, tabelle con etichette di livello, roadmap fasata, conclusione con raccomandazione operativa.
|
||||||
|
|
||||||
|
Output: **Markdown puro** con direttive di pagina, italiano formale impersonale, terza persona. Niente preamboli, niente meta-commenti, niente wrapping in code fence dell'intero documento.
|
||||||
|
|
||||||
|
## Direttive di pagina (OBBLIGATORIE)
|
||||||
|
|
||||||
|
Il documento è destinato a stampa **A4 verticale (portrait)**. Il Markdown generato deve essere **direttamente convertibile in PDF** via Pandoc/`md-to-pdf`/WeasyPrint mantenendo formato e cambi pagina.
|
||||||
|
|
||||||
|
### Frontmatter di output
|
||||||
|
|
||||||
|
Il documento DEVE iniziare con questo YAML frontmatter compatibile `md-to-pdf` (renderer Chromium):
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
stylesheet: /home/adriano/Documenti/Git_XYZ/ArcaSuite/themes/tielogic-devnotes.css
|
||||||
|
pdf_options:
|
||||||
|
format: A4
|
||||||
|
margin:
|
||||||
|
top: 18mm
|
||||||
|
bottom: 18mm
|
||||||
|
left: 0mm
|
||||||
|
right: 0mm
|
||||||
|
printBackground: true
|
||||||
|
displayHeaderFooter: true
|
||||||
|
headerTemplate: "<div style='font-size:8pt;width:100%;padding:0 22mm;color:#6c7a92;display:flex;justify-content:space-between;font-family:Inter,sans-serif;border-bottom:1px solid #c8d0dd;padding-bottom:4px;'><span>Tielogic — Documento {{ref_doc}}</span><span>{{data_report}}</span></div>"
|
||||||
|
footerTemplate: "<div style='font-size:8pt;width:100%;padding:6px 22mm 0 22mm;color:#6c7a92;display:flex;justify-content:space-between;font-family:Inter,sans-serif;border-top:1px solid #c8d0dd;'><span>Tielogic — Soluzioni Software Industriali</span><span>Pagina <span class='pageNumber'></span></span></div>"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
La cover (sezione 1) è impostata in CSS con `margin: -22mm` per andare bordo-a-bordo nonostante il margine top di 18mm; header/footer NON appaiono in cover (Chromium li disegna sopra il contenuto solo se questo non occupa l'intera area; la cover è pagina 1 con `page-break-after: always`).
|
||||||
|
|
||||||
|
I margini interni del corpo sono gestiti dal CSS (`@page` e `.cover`). Margini PDF a 0 sui lati per consentire alla cover scura di andare bordo-a-bordo.
|
||||||
|
|
||||||
|
### Marcatore di cambio pagina
|
||||||
|
|
||||||
|
Renderer = Chromium (md-to-pdf). Usa **solo** questo marcatore. **Non scrivere mai `\newpage`** (apparirebbe come testo nel PDF).
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<div class="page-break"></div>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
La cover ha già `page-break-after: always` nel CSS, quindi NON inserire marcatore subito dopo la cover.
|
||||||
|
|
||||||
|
Cambi pagina obbligatori dopo:
|
||||||
|
1. **Sommario Esiti Test** (sezione 2)
|
||||||
|
2. **Analisi Cause Radice** (sezione 5)
|
||||||
|
3. **Roadmap di Refactoring** (sezione 7)
|
||||||
|
|
||||||
|
Niente cambi pagina aggiuntivi.
|
||||||
|
|
||||||
|
## Metadati documento (forniti dal sistema)
|
||||||
|
|
||||||
|
- **Cliente:** {{cliente}}
|
||||||
|
- **Titolo:** {{titolo_doc}}
|
||||||
|
- **Sottotitolo:** {{sottotitolo}}
|
||||||
|
- **Progetto:** {{progetto}}
|
||||||
|
- **Data esecuzione test:** {{data_test}}
|
||||||
|
- **Data redazione:** {{data_report}}
|
||||||
|
- **Revisione:** {{revisione}}
|
||||||
|
- **Riferimento documentale:** {{ref_doc}}
|
||||||
|
- **Autore:** {{autore}} — Tielogic SRL
|
||||||
|
- **Robot:** {{robot_model}}
|
||||||
|
- **Componente:** {{componente}}
|
||||||
|
- **Sistema software:** {{sistema}}
|
||||||
|
- **Obiettivo:** {{obiettivo}}
|
||||||
|
|
||||||
|
## Struttura editoriale obbligatoria (stile DEVNOTES Tielogic)
|
||||||
|
|
||||||
|
Produci esattamente queste sezioni nell'ordine indicato. Le etichette di stato e di impatto vanno scritte come **HTML span con classe CSS** (il tema le renderà come badge colorati):
|
||||||
|
|
||||||
|
| Etichetta | HTML da emettere |
|
||||||
|
|---|---|
|
||||||
|
| OK | `<span class="badge badge-ok">OK</span>` |
|
||||||
|
| PARZIALE | `<span class="badge badge-parziale">PARZIALE</span>` |
|
||||||
|
| DRIFT | `<span class="badge badge-drift">DRIFT</span>` |
|
||||||
|
| FAIL | `<span class="badge badge-fail">FAIL</span>` |
|
||||||
|
| ALTO | `<span class="badge badge-alto">ALTO</span>` |
|
||||||
|
| MEDIO | `<span class="badge badge-medio">MEDIO</span>` |
|
||||||
|
| BASSO | `<span class="badge badge-basso">BASSO</span>` |
|
||||||
|
| FATTIBILE | `<span class="badge badge-fattibile">FATTIBILE</span>` |
|
||||||
|
| NON FATTIBILE | `<span class="badge badge-non-fattibile">NON FATTIBILE</span>` |
|
||||||
|
| FATTIBILE CON RISERVA | `<span class="badge badge-fattibile-riserva">FATTIBILE CON RISERVA</span>` |
|
||||||
|
|
||||||
|
Mai scrivere `— ETICHETTA —` con em-dash: usare sempre la forma `<span class="badge badge-XXX">…</span>`.
|
||||||
|
|
||||||
|
### 1. Cover (frontespizio — pagina 1 standalone, HTML stile Tielogic)
|
||||||
|
|
||||||
|
Subito dopo il frontmatter YAML, **HTML letterale**. Layout: logo TIELOGIC centrato + tagline + separator + titolo doc + nome progetto + ref/data + box affiancato Fornitore/Cliente + validità.
|
||||||
|
|
||||||
|
```
|
||||||
|
<div class="cover">
|
||||||
|
<div class="brand">TIELOGIC</div>
|
||||||
|
<div class="brand-tagline">Soluzioni Software Industriali</div>
|
||||||
|
<div class="brand-divider"></div>
|
||||||
|
|
||||||
|
<div class="doc-title">{{titolo_doc}}</div>
|
||||||
|
<div class="doc-product">{{progetto}}</div>
|
||||||
|
<div class="doc-ref">Rif. {{ref_doc}} | {{data_report}}</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<div class="info-col">
|
||||||
|
<div class="info-label">FORNITORE</div>
|
||||||
|
<div class="info-name">Tielogic SRL</div>
|
||||||
|
<div>Via Villanova 39, 36020 Solagna (VI)</div>
|
||||||
|
<div>P.IVA / C.F. 03954890244</div>
|
||||||
|
<div>Rif. {{autore}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-col">
|
||||||
|
<div class="info-label">CLIENTE</div>
|
||||||
|
<div class="info-name">{{cliente}}</div>
|
||||||
|
<div>{{cliente_indirizzo}}</div>
|
||||||
|
<div>Rif. {{cliente_rif}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="doc-validity">Documento riservato — Revisione {{revisione}}</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Se `cliente_indirizzo` o `cliente_rif` sono stringa vuota, omettere la riga corrispondente. Niente `#` heading nella cover.
|
||||||
|
|
||||||
|
### 2. Sommario Esiti Test
|
||||||
|
|
||||||
|
Titolo: `## SOMMARIO ESITI TEST`.
|
||||||
|
|
||||||
|
Paragrafo introduttivo di 2-3 righe che descrive oggetto del test, sistema, e finalità (ricavabile da {{obiettivo}} + content_md).
|
||||||
|
|
||||||
|
A seguire una **lista di "card" HTML**, una per ciascun test eseguito nel content_md. Formato di ogni card:
|
||||||
|
|
||||||
|
```
|
||||||
|
<div class="status-card <classe-stato>">
|
||||||
|
<div class="name">NOME TEST BREVE MAIUSCOLO <span class="badge badge-<stato>">STATO</span></div>
|
||||||
|
<div>Una riga di descrizione del verdetto numerico, max 110 caratteri.</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Mappa `<classe-stato>` (per la barra colorata laterale) e `<stato>` del badge:
|
||||||
|
- `ok` → target raggiunto entro tolleranza
|
||||||
|
- `parziale` → target raggiunto solo parzialmente
|
||||||
|
- `drift` → deriva cumulativa
|
||||||
|
- `fail` → IK non converge o sistema bloccato
|
||||||
|
|
||||||
|
Esempio:
|
||||||
|
```
|
||||||
|
<div class="status-card drift">
|
||||||
|
<div class="name">TEST ASSE Z +50MM <span class="badge badge-drift">DRIFT</span></div>
|
||||||
|
<div>Errore cumulativo da 7.8 mm a 11.5 mm sui cicli; target "su" raggiunto solo al 34%.</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Criticità Rilevate
|
||||||
|
|
||||||
|
Titolo: `## CRITICITÀ RILEVATE NEL CODICE`.
|
||||||
|
|
||||||
|
Tabella con colonne: `MODULO | PROBLEMA IDENTIFICATO | IMPATTO`. Una riga per ogni causa radice ricavabile dal content_md. Ultima colonna riporta il badge HTML (`<span class="badge badge-alto">ALTO</span>` ecc.).
|
||||||
|
|
||||||
|
### 4. Dettaglio Test Critici
|
||||||
|
|
||||||
|
Titolo: `## DETTAGLIO TEST CRITICI`.
|
||||||
|
|
||||||
|
Per ciascun test del content_md (sotto-paragrafo `### Test N: <nome>`):
|
||||||
|
- Tabella risultati identica per struttura ai dati grezzi forniti (ciclo, direzione, target, valore finale, errore posizione mm, errore orientamento rad, esito). Mantieni i numeri **esatti**.
|
||||||
|
- Blocco `**Osservazioni:**` con 3-5 bullet che riassumono i pattern numerici (es. "il braccio raggiunge solo X% del target", "drift cumulativo da N a M mm", "primo tentativo IK fallisce con residual=K").
|
||||||
|
- Eventuali blocchi di codice citati nell'input vanno riportati **letterali** in fence ```python.
|
||||||
|
|
||||||
|
### 5. Analisi Cause Radice
|
||||||
|
|
||||||
|
Titolo: `## ANALISI CAUSE RADICE`.
|
||||||
|
|
||||||
|
Per ogni causa radice un sotto-paragrafo `### CRn: <Titolo causa>` contenente:
|
||||||
|
- Riga `**Gravità:** <span class="badge badge-alto">ALTA</span>` (oppure `badge-medio`/`badge-basso`)
|
||||||
|
- Spiegazione tecnica 3-6 righe. Cita codice sorgente con numero di riga se presente nell'input. Non inventare riferimenti.
|
||||||
|
|
||||||
|
### 6. Studio di Fattibilità Soluzioni
|
||||||
|
|
||||||
|
Titolo: `## STUDIO DI FATTIBILITÀ — SOLUZIONI`.
|
||||||
|
|
||||||
|
Paragrafo introduttivo di una riga.
|
||||||
|
|
||||||
|
Tabella comparativa con colonne: `CARATTERISTICA | SOLUZIONE A: <nome> | SOLUZIONE B: <nome>`. Le righe coprono almeno: Architettura, Modifiche al codice richieste, Impatto sulla teleoperation esistente, Tempo di sviluppo stimato, Verdetto (badge HTML: `<span class="badge badge-fattibile">FATTIBILE</span>` / `badge-non-fattibile` / `badge-fattibile-riserva`).
|
||||||
|
|
||||||
|
Se nel content_md è presente una sola soluzione, costruisci comunque il confronto fra "stato attuale (open-loop)" e "soluzione proposta".
|
||||||
|
|
||||||
|
### 7. Roadmap di Refactoring
|
||||||
|
|
||||||
|
Titolo: `## ROADMAP DI REFACTORING`.
|
||||||
|
|
||||||
|
Elenco fasi numerate `### Fase N: <titolo> <span class="badge badge-alto">PRIORITÀ ALTA</span>` (oppure `badge-medio` / `badge-basso` con testo PRIORITÀ MEDIA / PRIORITÀ BASSA):
|
||||||
|
- Una fase per ogni soluzione proposta nel content_md, in ordine di priorità.
|
||||||
|
- Ogni fase: 2-4 righe con cosa fare e snippet di codice illustrativo se fornito (mantieni codice **letterale**).
|
||||||
|
|
||||||
|
### 8. Conclusioni Operative
|
||||||
|
|
||||||
|
Titolo: `## CONCLUSIONI OPERATIVE`.
|
||||||
|
|
||||||
|
Sotto-blocco evidenziato come blockquote:
|
||||||
|
|
||||||
|
```
|
||||||
|
> **RACCOMANDAZIONE PRINCIPALE: <UNA RIGA MAIUSCOLA>**
|
||||||
|
>
|
||||||
|
> <2-4 righe che giustificano la raccomandazione: perché questa direzione, costo stimato di sviluppo, cosa NON viene affrontato e perché.>
|
||||||
|
```
|
||||||
|
|
||||||
|
Seguito da:
|
||||||
|
|
||||||
|
```
|
||||||
|
**Prossimi Passi:**
|
||||||
|
|
||||||
|
- <azione operativa 1>
|
||||||
|
- <azione operativa 2>
|
||||||
|
- <azione operativa 3>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Accettazione e Firme (stile Tielogic — HTML)
|
||||||
|
|
||||||
|
HTML letterale, **senza righe vuote interne** (markdown-it interromperebbe il blocco HTML):
|
||||||
|
|
||||||
|
```
|
||||||
|
<div class="acceptance">
|
||||||
|
<h2 class="acceptance-title">ACCETTAZIONE</h2>
|
||||||
|
<div class="acceptance-intro">Per accettazione della presente analisi e delle relative raccomandazioni operative, si prega di restituire copia firmata.</div>
|
||||||
|
<div class="signature-grid"><div class="sig-col"><div class="sig-party">Per Tielogic SRL</div><div class="sig-line">Firma e timbro</div></div><div class="sig-col"><div class="sig-party">Per {{cliente}}</div><div class="sig-line">Firma e timbro</div></div></div>
|
||||||
|
<div class="place-date">Luogo e data: <span class="pd-line"></span> {{data_report}}</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Niente "Responsabile Tecnico", niente "Project Manager", niente nomi propri. Solo le due ragioni sociali.
|
||||||
|
|
||||||
|
**IMPORTANTE**: questa è l'**ultima** sezione del documento. Niente footer inline dopo le firme. I dati documento (Tielogic, REF, data, paginazione) sono già in header/footer di pagina via `pdf_options`.
|
||||||
|
|
||||||
|
## Regole tassative
|
||||||
|
|
||||||
|
- **Non inventare dati numerici.** Ogni valore (mm, rad, percentuale, target, residual) deve provenire dal content_md. Se manca, scrivi `n/d`.
|
||||||
|
- **Codice letterale.** Blocchi ```python```, ```yaml```, ```json``` forniti nell'input vanno copiati identici, comprese righe di riferimento al sorgente (es. "riga 272-273").
|
||||||
|
- **Nomenclatura tecnica invariata**: IK, DoF, SE3, RNEA, IPOPT, CasADi, quaternione `[x, y, z, w]`, assi `X/Y/Z` maiuscoli, unità sempre presenti (m, mm, rad, deg).
|
||||||
|
- **Badge sempre come HTML span** secondo la mappa fornita. Mai em-dash `— ALTO —`. Mai emoji.
|
||||||
|
- **HTML inline ammesso solo** per: `<div class="cover">`, `<div class="status-card">`, `<div class="page-break">`, `<span class="badge ...">`. Niente altri tag HTML, niente CSS inline.
|
||||||
|
- **Tono**: zero marketing, zero superlativi, zero "noi/io". Frasi brevi soggetto implicito ("Il sistema...", "Il solver...", "L'IK fallisce...").
|
||||||
|
- **Lingua**: italiano. Termini tecnici inglesi in *corsivo* solo al primo uso (es. *closed-loop*, *warm start*, *drift*).
|
||||||
|
- **Lunghezza**: nessun riempitivo. Sezioni brevi se i dati sono pochi. Niente paragrafi di transizione tipo "Nel proseguo del documento...".
|
||||||
|
- **Niente duplicazione**: il SOMMARIO non ripete i numeri di dettaglio (solo etichette stato + verdetto qualitativo); il DETTAGLIO non ripete il sommario.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
**REGOLA CRITICA SUL FORMATO DI OUTPUT (da rispettare assolutamente):**
|
||||||
|
|
||||||
|
- La **prima riga in assoluto** del documento DEVE essere `---` (apertura del frontmatter YAML).
|
||||||
|
- Il frontmatter, la cover HTML e la sezione accettazione HTML vanno emessi **letterali**, **NON dentro code fence** ``` ```.
|
||||||
|
- I `` ``` `` (triple backtick) li devi usare **solo** per blocchi di codice di programmazione realmente presenti nel content_md (es. snippet Python). Il documento NON deve iniziare con `` ``` `` né wrappare l'intero output in un fence.
|
||||||
|
- Non includere meta-commenti tipo "Ecco il report:", "Documento generato:", ecc.
|
||||||
|
- Non includere intestazioni che indichino il tipo di output (es. "markdown" come language tag iniziale).
|
||||||
|
|
||||||
|
Restituisci direttamente il contenuto del file `.md`, dal `---` iniziale all'ultimo `</div>` della sezione Accettazione.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mcp_docugen.template_seed import seed_templates
|
||||||
|
|
||||||
|
VALID_FRONTMATTER = (
|
||||||
|
"---\n"
|
||||||
|
"name: {name}\n"
|
||||||
|
"description: test seed\n"
|
||||||
|
"required_variables: []\n"
|
||||||
|
"---\n"
|
||||||
|
"body of {name}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _make_seed_template(seed_dir: Path, name: str, body: str | None = None) -> None:
|
||||||
|
tdir = seed_dir / name
|
||||||
|
(tdir / "assets").mkdir(parents=True, exist_ok=True)
|
||||||
|
(tdir / "template.md").write_text(
|
||||||
|
body if body is not None else VALID_FRONTMATTER.format(name=name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_seed_copies_missing_templates(tmp_path: Path) -> None:
|
||||||
|
seed_dir = tmp_path / "seed"
|
||||||
|
target_dir = tmp_path / "target"
|
||||||
|
_make_seed_template(seed_dir, "alpha")
|
||||||
|
_make_seed_template(seed_dir, "beta")
|
||||||
|
|
||||||
|
seeded = seed_templates(seed_dir, target_dir)
|
||||||
|
|
||||||
|
assert sorted(seeded) == ["alpha", "beta"]
|
||||||
|
assert (target_dir / "alpha" / "template.md").is_file()
|
||||||
|
assert (target_dir / "beta" / "template.md").is_file()
|
||||||
|
assert (target_dir / "alpha" / "assets").is_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def test_seed_is_idempotent_and_does_not_overwrite(tmp_path: Path) -> None:
|
||||||
|
seed_dir = tmp_path / "seed"
|
||||||
|
target_dir = tmp_path / "target"
|
||||||
|
_make_seed_template(seed_dir, "alpha")
|
||||||
|
|
||||||
|
seed_templates(seed_dir, target_dir)
|
||||||
|
|
||||||
|
user_edit = (
|
||||||
|
"---\nname: alpha\ndescription: edited by user\nrequired_variables: []\n---\n"
|
||||||
|
"user content\n"
|
||||||
|
)
|
||||||
|
(target_dir / "alpha" / "template.md").write_text(user_edit)
|
||||||
|
|
||||||
|
seeded = seed_templates(seed_dir, target_dir)
|
||||||
|
|
||||||
|
assert seeded == []
|
||||||
|
assert (target_dir / "alpha" / "template.md").read_text() == user_edit
|
||||||
|
|
||||||
|
|
||||||
|
def test_seed_skips_when_seed_dir_missing(tmp_path: Path) -> None:
|
||||||
|
target_dir = tmp_path / "target"
|
||||||
|
|
||||||
|
seeded = seed_templates(tmp_path / "missing", target_dir)
|
||||||
|
|
||||||
|
assert seeded == []
|
||||||
|
assert not target_dir.exists() or list(target_dir.iterdir()) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_seed_ignores_non_directory_entries_and_dirs_without_template(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
seed_dir = tmp_path / "seed"
|
||||||
|
target_dir = tmp_path / "target"
|
||||||
|
seed_dir.mkdir()
|
||||||
|
(seed_dir / "stray-file.md").write_text("not a template dir")
|
||||||
|
(seed_dir / "no-template-md").mkdir()
|
||||||
|
|
||||||
|
seeded = seed_templates(seed_dir, target_dir)
|
||||||
|
|
||||||
|
assert seeded == []
|
||||||
Reference in New Issue
Block a user