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
This commit is contained in:
1
backend/services/__init__.py
Normal file
1
backend/services/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Servizi di dominio per PostGenerator."""
|
||||
91
backend/services/format_selector.py
Normal file
91
backend/services/format_selector.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user