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:
Michele
2026-03-08 01:54:58 +01:00
parent 50d5708016
commit 209d8962f7
8 changed files with 635 additions and 2 deletions

194
backend/constants.py Normal file
View File

@@ -0,0 +1,194 @@
"""Costanti di dominio LOCKED per PostGenerator.
ATTENZIONE: Queste costanti sono fondamentali per la coerenza del sistema.
Non modificare CANVA_FIELDS, PERSUASION_DISTRIBUTION o SCHWARTZ_DISTRIBUTION
senza aggiornare anche il CSV builder e tutti i prompt LLM.
"""
# ---------------------------------------------------------------------------
# Configurazione ciclo editoriale
# ---------------------------------------------------------------------------
POST_PER_CICLO: int = 13
"""Numero fisso di post per ciclo editoriale completo."""
# ---------------------------------------------------------------------------
# Distribuzione Persuasion Nurturing (PN) — 13 slot per ciclo
# ---------------------------------------------------------------------------
PERSUASION_DISTRIBUTION: dict[str, int] = {
"valore": 4,
"storytelling": 2,
"news": 2,
"riprova_sociale": 3,
"coinvolgimento": 1,
"promozione": 1,
}
"""Distribuzione tipo_contenuto per ciclo di 13 post.
Totale: sum(values) == 13
"""
# Verifica a load-time
assert sum(PERSUASION_DISTRIBUTION.values()) == POST_PER_CICLO, (
f"PERSUASION_DISTRIBUTION deve sommare a {POST_PER_CICLO}, "
f"ora somma a {sum(PERSUASION_DISTRIBUTION.values())}"
)
# ---------------------------------------------------------------------------
# Distribuzione livelli Schwartz — 13 slot per ciclo
# ---------------------------------------------------------------------------
SCHWARTZ_DISTRIBUTION: dict[str, int] = {
"L5": 3, # Inconsapevole del problema — storytelling + news
"L4": 3, # Consapevole del problema — valore + news
"L3": 4, # Consapevole della soluzione — valore + riprova_sociale
"L2": 2, # Consapevole del prodotto — riprova_sociale + coinvolgimento
"L1": 1, # Pronto all'acquisto — promozione
}
"""Distribuzione livelli consapevolezza Schwartz per ciclo di 13 post.
L5+L4 = 6 (fase Attira/Cattura — top of funnel)
L3 = 4 (fase Coinvolgi — middle of funnel)
L2+L1 = 3 (fase Converti — bottom of funnel)
Totale: sum(values) == 13
"""
# Verifica a load-time
assert sum(SCHWARTZ_DISTRIBUTION.values()) == POST_PER_CICLO, (
f"SCHWARTZ_DISTRIBUTION deve sommare a {POST_PER_CICLO}, "
f"ora somma a {sum(SCHWARTZ_DISTRIBUTION.values())}"
)
# ---------------------------------------------------------------------------
# Colonne CSV per Canva Bulk Create — LOCKED
# ---------------------------------------------------------------------------
CANVA_FIELDS: list[str] = [
# --- Metadati slot (8 colonne) ---
"campagna",
"fase_campagna",
"tipo_contenuto",
"formato_narrativo",
"funzione",
"livello_schwartz",
"target_nicchia",
"data_pub_suggerita",
# --- Cover slide (3 colonne) ---
"cover_title",
"cover_subtitle",
"cover_image_keyword",
# --- Slide 2 (3 colonne) ---
"s2_headline",
"s2_body",
"s2_image_keyword",
# --- Slide 3 (3 colonne) ---
"s3_headline",
"s3_body",
"s3_image_keyword",
# --- Slide 4 (3 colonne) ---
"s4_headline",
"s4_body",
"s4_image_keyword",
# --- Slide 5 (3 colonne) ---
"s5_headline",
"s5_body",
"s5_image_keyword",
# --- Slide 6 (3 colonne) ---
"s6_headline",
"s6_body",
"s6_image_keyword",
# --- Slide 7 (3 colonne) ---
"s7_headline",
"s7_body",
"s7_image_keyword",
# --- CTA slide (3 colonne) ---
"cta_text",
"cta_subtext",
"cta_image_keyword",
# --- Extra (1 colonna) ---
"caption_instagram",
]
"""Lista ORDINATA di tutte le colonne del CSV per Canva Bulk Create.
Struttura:
- 8 metadati slot
- 24 campi slide (8 slide x 3 campi: headline/title, body/subtitle, image_keyword)
Nota: image_keyword contiene parole chiave testuali (NON URL).
Gli URL Unsplash verranno aggiunti in Phase 4.
- 1 caption Instagram
Totale: 33 colonne
LOCKED: Non aggiungere/rimuovere colonne senza aggiornare tutti i prompt LLM
e il CSV builder.
"""
# Verifica a load-time
_expected_count = 8 + 24 + 1 # 33
assert len(CANVA_FIELDS) == _expected_count, (
f"CANVA_FIELDS deve avere {_expected_count} elementi, "
f"ne ha {len(CANVA_FIELDS)}"
)
# ---------------------------------------------------------------------------
# Formati narrativi disponibili (7 formati)
# ---------------------------------------------------------------------------
FORMATI_NARRATIVI: list[str] = [
"PAS", # Problema → Agitazione → Soluzione
"AIDA", # Attenzione → Interesse → Desiderio → Azione
"BAB", # Before → After → Bridge
"Listicle", # Lista numerata di punti/consigli
"Storytelling", # Narrativa emotiva di trasformazione
"Dato_Implicazione", # Dato/statistica → Implicazione → Azione
"Obiezione_Risposta", # Obiezione comune → Confutazione → Soluzione
]
"""I 7 formati narrativi supportati per i caroselli Instagram."""
# ---------------------------------------------------------------------------
# Funzioni contenuto (4 macro-funzioni editoriali)
# ---------------------------------------------------------------------------
FUNZIONI_CONTENUTO: list[str] = [
"Intrattenere",
"Educare",
"Persuadere",
"Convertire",
]
"""Le 4 macro-funzioni editoriali di ogni post."""
# ---------------------------------------------------------------------------
# Fasi campagna (funnel AIDA semplificato)
# ---------------------------------------------------------------------------
FASI_CAMPAGNA: list[str] = [
"Attira", # Top of funnel — inconsapevoli (L5)
"Cattura", # Upper middle — consapevoli del problema (L4+L3)
"Coinvolgi", # Lower middle — consapevoli della soluzione (L3+L2)
"Converti", # Bottom of funnel — pronti all'acquisto (L1+L2)
]
"""Le 4 fasi del funnel di acquisizione clienti."""
# ---------------------------------------------------------------------------
# Nicchie target predefinite
# ---------------------------------------------------------------------------
NICCHIE_DEFAULT: list[str] = [
"generico",
"dentisti",
"avvocati",
"ecommerce",
"local_business",
"agenzie",
]
"""Lista di nicchie target predefinite.
"generico" è sempre incluso e viene usato per il 50% degli slot.
Le altre nicchie vengono ruotate per il restante 50%.
"""