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:
2026-04-25 12:19:29 +02:00
parent b4c2935c53
commit 419c1acc7a
8 changed files with 625 additions and 0 deletions
@@ -15,6 +15,7 @@ class Settings(BaseSettings):
llm_model_default: str = "anthropic/claude-sonnet-4"
public_base_url: str = Field(...)
data_dir: Path = Path("/data")
templates_seed_dir: Path = Path("/app/services/mcp-docugen/templates_seed")
asset_ttl_days: int = 30
max_image_size_mb: int = 10
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.mcp_tools import build_mcp_server
from mcp_docugen.renderer import Renderer
from mcp_docugen.template_seed import seed_templates
from mcp_docugen.template_store import TemplateStore
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"
template_store = TemplateStore(base_dir=templates_dir)
seed_templates(settings.templates_seed_dir, templates_dir)
generation_store = GenerationStore(
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