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"
|
||||
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
|
||||
Reference in New Issue
Block a user