Files
postgenerator/backend/services/format_selector.py
Michele 209d8962f7 feat(01-02): costanti dominio, schemas Pydantic, FormatSelector
- backend/constants.py: CANVA_FIELDS (33 col locked), PERSUASION_DISTRIBUTION (13), SCHWARTZ_DISTRIBUTION (13)
- backend/schemas/calendar.py: CalendarSlot, CalendarRequest, CalendarResponse
- backend/schemas/generate.py: SlideContent, GeneratedPost, TopicResult, GenerateRequest, PostResult, GenerateResponse
- backend/data/format_mapping.json: matrice 6 tipi x 5 livelli (30 combinazioni)
- backend/services/format_selector.py: FormatSelector con select_format e fallback PAS
- fix .gitignore: backend/data/prompts/ e format_mapping.json non erano ignorabili
2026-03-08 01:54:58 +01:00

92 lines
3.2 KiB
Python

"""FormatSelector — mappa tipo_contenuto x livello_schwartz -> formato_narrativo.
Carica la matrice di mapping da format_mapping.json e seleziona il formato
narrativo più efficace per ogni combinazione di tipo e livello.
"""
from __future__ import annotations
import json
from pathlib import Path
# Percorso default al file JSON (relativo a questo modulo)
_DEFAULT_MAPPING_PATH = Path(__file__).parent.parent / "data" / "format_mapping.json"
# Fallback se la combinazione non è presente nella matrice
_FALLBACK_FORMAT = "PAS"
class FormatSelector:
"""Seleziona il formato narrativo ottimale per un dato tipo_contenuto e livello_schwartz.
Carica la matrice di mapping da un file JSON e la mantiene in memoria.
La selezione è deterministica e basata sulla tabella (nessuna logica LLM).
Esempio:
selector = FormatSelector()
formato = selector.select_format("valore", "L4") # -> "PAS"
formato = selector.select_format("storytelling", "L5") # -> "BAB"
"""
def __init__(self, mapping_path: Path | None = None) -> None:
"""Carica il mapping da file JSON.
Args:
mapping_path: Percorso al file format_mapping.json.
Default: backend/data/format_mapping.json
"""
path = mapping_path or _DEFAULT_MAPPING_PATH
if not path.exists():
raise FileNotFoundError(
f"File format_mapping.json non trovato: {path}. "
"Assicurati che backend/data/format_mapping.json esista."
)
with path.open(encoding="utf-8") as f:
raw = json.load(f)
# Filtra i commenti (chiavi che iniziano con "_")
self._mapping: dict[str, dict[str, str]] = {
k: v for k, v in raw.items() if not k.startswith("_")
}
def select_format(self, tipo_contenuto: str, livello_schwartz: str) -> str:
"""Ritorna il formato narrativo per la combinazione data.
Args:
tipo_contenuto: Tipo PN (es. "valore", "storytelling", "promozione")
livello_schwartz: Livello consapevolezza (es. "L1", "L3", "L5")
Returns:
Nome del formato narrativo (es. "PAS", "BAB", "AIDA").
Ritorna "PAS" come fallback se la combinazione non è nella matrice.
"""
tipo_map = self._mapping.get(tipo_contenuto)
if tipo_map is None:
return _FALLBACK_FORMAT
return tipo_map.get(livello_schwartz, _FALLBACK_FORMAT)
def get_mapping(self) -> dict[str, dict[str, str]]:
"""Ritorna la tabella di mapping completa.
Returns:
Dizionario { tipo_contenuto: { livello_schwartz: formato_narrativo } }
"""
return dict(self._mapping)
def get_supported_types(self) -> list[str]:
"""Ritorna la lista dei tipi_contenuto supportati dalla matrice."""
return list(self._mapping.keys())
def get_supported_levels(self) -> list[str]:
"""Ritorna la lista dei livelli_schwartz supportati dalla matrice.
Inferisce i livelli dal primo tipo disponibile (la matrice è consistente).
"""
if not self._mapping:
return []
first_type = next(iter(self._mapping.values()))
return sorted(first_type.keys())