--- phase: 01-core-generation-pipeline plan: 02 type: execute wave: 1 depends_on: [] files_modified: - backend/services/__init__.py - backend/services/calendar_service.py - backend/services/format_selector.py - backend/services/prompt_service.py - backend/schemas/__init__.py - backend/schemas/calendar.py - backend/schemas/generate.py - backend/data/format_mapping.json - backend/data/prompts/system_prompt.txt - backend/data/prompts/pas_valore.txt - backend/data/prompts/listicle_valore.txt - backend/data/prompts/bab_storytelling.txt - backend/data/prompts/aida_promozione.txt - backend/data/prompts/dato_news.txt - backend/constants.py autonomous: true must_haves: truths: - "CalendarService genera esattamente 13 slot con distribuzione PN corretta (4 valore, 2 storytelling, 2 news, 3 riprova, 1 coinvolgimento, 1 promo)" - "Ogni slot ha livello Schwartz assegnato con distribuzione corretta (L5+L4=6, L3=4, L2=2, L1=1)" - "FormatSelector mappa ogni combinazione tipo_contenuto x livello_schwartz a un formato narrativo" - "PromptService carica, lista e compila prompt .txt con sostituzione variabili" - "CANVA_FIELDS e' definito come costante locked e contiene tutti i nomi colonna CSV" - "Almeno 5 prompt base esistono come file .txt scritti IN italiano" artifacts: - path: "backend/services/calendar_service.py" provides: "CalendarService con generate_calendar() che produce 13 slot PN + Schwartz + nicchie + date" contains: "class CalendarService" - path: "backend/services/format_selector.py" provides: "FormatSelector con select_format(tipo, livello) -> formato narrativo" contains: "class FormatSelector" - path: "backend/services/prompt_service.py" provides: "PromptService con load, list, compile con variabili" contains: "class PromptService" - path: "backend/constants.py" provides: "CANVA_FIELDS, PERSUASION_DISTRIBUTION, SCHWARTZ_DISTRIBUTION costanti locked" contains: "CANVA_FIELDS" - path: "backend/schemas/calendar.py" provides: "CalendarSlot, CalendarRequest, CalendarResponse Pydantic models" contains: "class CalendarSlot" - path: "backend/schemas/generate.py" provides: "SlideContent, GeneratedPost Pydantic models per output LLM e CSV" contains: "class GeneratedPost" - path: "backend/data/format_mapping.json" provides: "Tabella mapping tipo_contenuto x livello_schwartz -> formato narrativo" - path: "backend/data/prompts/system_prompt.txt" provides: "System prompt scritto IN italiano per generazione caroselli" key_links: - from: "backend/services/calendar_service.py" to: "backend/constants.py" via: "Importa PERSUASION_DISTRIBUTION e SCHWARTZ_DISTRIBUTION" pattern: "from.*constants.*import" - from: "backend/services/format_selector.py" to: "backend/data/format_mapping.json" via: "Carica mapping da file JSON" pattern: "format_mapping" - from: "backend/services/prompt_service.py" to: "backend/config.py" via: "Usa PROMPTS_PATH per localizzare file .txt" pattern: "PROMPTS_PATH" --- Creare i servizi core del dominio: CalendarService (distribuzione 13 post PN + Schwartz + nicchie + date), FormatSelector (mapping tipo x livello -> formato), PromptService (carica/compila prompt .txt), e definire le costanti fondamentali (CANVA_FIELDS, distribuzioni). Purpose: Costruire la logica di dominio pura (zero dipendenza LLM) che orchestra il calendario editoriale e prepara i prompt per la generazione. Queste sono le fondamenta su cui LLMService e CSVBuilder si appoggeranno. Output: Servizi Python testabili indipendentemente, 5 prompt .txt in italiano, schema Pydantic per slot calendario e post generato, costante CANVA_FIELDS locked. @C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md @C:\Users\miche\.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/research/ARCHITECTURE.md @.planning/research/PITFALLS.md @.planning/phases/01-core-generation-pipeline/01-CONTEXT.md Task 1: Costanti di dominio, Pydantic schemas, FormatSelector backend/constants.py backend/schemas/__init__.py backend/schemas/calendar.py backend/schemas/generate.py backend/services/__init__.py backend/services/format_selector.py backend/data/format_mapping.json 1. Creare backend/constants.py con le costanti LOCKED del progetto: PERSUASION_DISTRIBUTION — distribuzione per ciclo di 13 post: - "valore": 4 - "storytelling": 2 - "news": 2 - "riprova_sociale": 3 - "coinvolgimento": 1 - "promozione": 1 SCHWARTZ_DISTRIBUTION — distribuzione livelli per 13 post: - L5: 3 post (storytelling + news) - L4: 3 post (valore + storytelling) - L3: 4 post (valore + riprova) - L2: 2 post (riprova + coinvolgimento) - L1: 1 post (promozione) Nota: L5+L4=6, L3=4, L2=2, L1=1 come da requirement CAL-02. CANVA_FIELDS — lista ORDINATA dei nomi colonna CSV per Canva Bulk Create: Metadati: campagna, fase_campagna, tipo_contenuto, formato_narrativo, funzione, livello_schwartz, target_nicchia, data_pub_suggerita Slide (8 slide x 3 campi = 24): cover_title, cover_subtitle, cover_image_keyword s2_headline, s2_body, s2_image_keyword s3_headline, s3_body, s3_image_keyword s4_headline, s4_body, s4_image_keyword s5_headline, s5_body, s5_image_keyword s6_headline, s6_body, s6_image_keyword s7_headline, s7_body, s7_image_keyword cta_text, cta_subtext, cta_image_keyword Extra: caption_instagram Totale: 8 metadati + 24 slide + 1 caption = 33 colonne FORMATI_NARRATIVI — lista dei 7 formati: PAS, AIDA, BAB, Listicle, Storytelling, Dato_Implicazione, Obiezione_Risposta FUNZIONI_CONTENUTO — le 4 funzioni: Intrattenere, Educare, Persuadere, Convertire FASI_CAMPAGNA — le 4 fasi: Attira, Cattura, Coinvolgi, Converti NICCHIE_DEFAULT — lista default: ["generico", "dentisti", "avvocati", "ecommerce", "local_business", "agenzie"] POST_PER_CICLO = 13 Nota: usare _image_keyword (non _image_url) per le colonne immagine — l'URL verra' solo con Unsplash in Phase 4. Per ora il CSV contiene keyword testuali. 2. Creare backend/schemas/__init__.py (vuoto). 3. Creare backend/schemas/calendar.py con Pydantic models: - CalendarSlot: indice (int), tipo_contenuto (str), livello_schwartz (str), formato_narrativo (str), funzione (str), fase_campagna (str), target_nicchia (str), data_pub_suggerita (str, formato YYYY-MM-DD), topic (Optional[str], default None — verra' generato dall'LLM o overridden dall'utente) - CalendarRequest: obiettivo_campagna (str), settimane (int, default 2), nicchie (Optional[list[str]]), frequenza_post (int, default 3 — post a settimana), data_inizio (Optional[str]) - CalendarResponse: campagna (str), slots (list[CalendarSlot]), totale_post (int) 4. Creare backend/schemas/generate.py con Pydantic models: - SlideContent: headline (str), body (str), image_keyword (str) - GeneratedPost: cover_title (str), cover_subtitle (str), cover_image_keyword (str), slides (list[SlideContent] — 6 slide centrali s2-s7), cta_text (str), cta_subtext (str), cta_image_keyword (str), caption_instagram (str) - GenerateRequest: slot (CalendarSlot), obiettivo_campagna (str), brand_name (Optional[str]), tono (Optional[str]) - PostResult: slot_index (int), status (Literal["success", "failed", "pending"]), post (Optional[GeneratedPost]), error (Optional[str]) - GenerateResponse: campagna (str), results (list[PostResult]), total (int), success_count (int), failed_count (int) 5. Creare backend/data/format_mapping.json: Matrice tipo_contenuto x livello_schwartz -> formato_narrativo. Struttura: { "valore": { "L5": "Listicle", "L4": "PAS", "L3": "PAS", "L2": "Obiezione_Risposta", "L1": "AIDA" }, ... } Coprire tutte le 6 combinazioni tipo x 5 livelli con i 7 formati disponibili, scegliendo il formato piu' efficace per ogni combinazione. 6. Creare backend/services/__init__.py (vuoto). 7. Creare backend/services/format_selector.py: - class FormatSelector con __init__ che carica format_mapping.json - Metodo select_format(tipo_contenuto: str, livello_schwartz: str) -> str che ritorna il formato narrativo - Fallback a "PAS" se combinazione non trovata - Metodo get_mapping() -> dict per esporre la tabella completa - backend/constants.py: CANVA_FIELDS ha esattamente 33 elementi, PERSUASION_DISTRIBUTION somma a 13, SCHWARTZ_DISTRIBUTION somma a 13 - backend/schemas/calendar.py: CalendarSlot importabile, CalendarRequest ha campo obiettivo_campagna - backend/schemas/generate.py: GeneratedPost ha slides come list[SlideContent], PostResult ha campo status - backend/data/format_mapping.json: contiene tutte le 6 chiavi tipo_contenuto, ciascuna con 5 livelli - backend/services/format_selector.py: FormatSelector ha metodo select_format Costanti di dominio locked (CANVA_FIELDS, PERSUASION_DISTRIBUTION, SCHWARTZ_DISTRIBUTION). Pydantic schemas per calendario e generazione definiti. FormatSelector carica mapping da JSON e mappa tipo x livello -> formato. Tutto il dominio ha tipi espliciti, nessuna stringa magica. Task 2: CalendarService, PromptService, e prompt .txt in italiano backend/services/calendar_service.py backend/services/prompt_service.py backend/data/prompts/system_prompt.txt backend/data/prompts/pas_valore.txt backend/data/prompts/listicle_valore.txt backend/data/prompts/bab_storytelling.txt backend/data/prompts/aida_promozione.txt backend/data/prompts/dato_news.txt 1. Creare backend/services/calendar_service.py: - class CalendarService - Metodo generate_calendar(request: CalendarRequest) -> CalendarResponse: a. Genera 13 slot con distribuzione PERSUASION_DISTRIBUTION (import da constants) b. Assegna livelli Schwartz secondo la logica naturale: - valore: L4 (2), L3 (2) — educare chi e' consapevole del problema/soluzioni - storytelling: L5 (2) — attirare inconsapevoli con storie - news: L5 (1), L4 (1) — intrattenere/educare - riprova_sociale: L3 (2), L2 (1) — persuadere chi conosce soluzioni/prodotto - coinvolgimento: L2 (1) — interagire con chi conosce il prodotto - promozione: L1 (1) — convertire chi e' pronto Verifica che totali siano: L5=3, L4=3, L3=4, L2=2, L1=1 c. Assegna funzioni contenuto: Intrattenere (storytelling, news, coinvolgimento), Educare (valore), Persuadere (riprova_sociale), Convertire (promozione) d. Distribuisce nelle 4 fasi campagna: Attira (L5), Cattura (L4+L3), Coinvolgi (L3+L2), Converti (L1+L2) e. Ordina gli slot nella sequenza campagna (Attira -> Cattura -> Coinvolgi -> Converti) f. Calcola date_pub_suggerita dalla data_inizio con la frequenza specificata (default: 3 post/settimana, lun-mer-ven) g. Assegna nicchie con rotazione: 50% generico, 50% verticali in rotazione dalla lista nicchie - Usa FormatSelector internamente per assegnare formato_narrativo a ogni slot - Metodo statico _distribute_niches(slots, nicchie) per la logica rotazione 2. Creare backend/services/prompt_service.py: - class PromptService(__init__ riceve prompts_dir: Path) - list_prompts() -> list[str]: elenca tutti i .txt nella directory - load_prompt(name: str) -> str: carica contenuto file .txt - compile_prompt(name: str, variables: dict[str, str]) -> str: carica e sostituisce {{variabile}} con valori dal dict - Usa doppia graffa {{variabile}} come delimitatore (Pitfall 7) - Solleva ValueError se una variabile nel template non ha corrispondenza nel dict - save_prompt(name: str, content: str) -> None: salva contenuto (per Phase 2 editor) - get_required_variables(name: str) -> list[str]: parse il template e ritorna lista variabili {{...}} trovate 3. Creare i 5 prompt base + system prompt, TUTTI scritti IN italiano (Pitfall 8): backend/data/prompts/system_prompt.txt: - Ruolo: esperto di content marketing B2B per PMI italiane - Tono: diretto, provocatorio ma costruttivo, usa il "tu" - Target: imprenditori e manager italiani - Regole: "cosa fare" mai "come farlo", benefici concreti, evitare jargon - Lingua: italiano naturale, NON tradotto dall'inglese - Output: JSON strutturato con i campi specificati nello schema backend/data/prompts/pas_valore.txt (formato PAS per post valore): - Sezioni: SYSTEM (ref system_prompt), USER, OUTPUT_SCHEMA - Variabili: {{obiettivo_campagna}}, {{target_nicchia}}, {{livello_schwartz}}, {{topic}}, {{brand_name}} - Istruzioni PAS: Problema -> Agitazione -> Soluzione - Output schema JSON esplicito con campi GeneratedPost backend/data/prompts/listicle_valore.txt (formato Listicle per post valore): - Variabili: stesse di PAS - Istruzioni Listicle: lista numerata di consigli pratici - Enfasi su valore educativo backend/data/prompts/bab_storytelling.txt (formato BAB per storytelling): - Variabili: stesse + enfasi emotiva - Istruzioni BAB: Before -> After -> Bridge - Tono narrativo, storia di trasformazione backend/data/prompts/aida_promozione.txt (formato AIDA per promo): - Variabili: stesse + {{call_to_action}} - Istruzioni AIDA: Attenzione -> Interesse -> Desiderio -> Azione - Focus su conversione backend/data/prompts/dato_news.txt (formato Dato+Implicazione per news): - Variabili: stesse - Istruzioni: dato/statistica -> cosa significa -> come agire - Focus su urgenza informata TUTTI i prompt devono: - Essere scritti interamente in italiano (istruzioni E esempi) - Usare {{variabile}} per tutti i parametri dinamici - Specificare l'output JSON schema esplicito con i campi di GeneratedPost - Includere istruzioni sul tono (tu, diretto, concreto) - Specificare il numero di slide e la struttura (cover, 6 slide centrali, CTA) - CalendarService.generate_calendar() con CalendarRequest(obiettivo_campagna="test", settimane=2) produce CalendarResponse con esattamente 13 slot - Distribuzione PN: contare tipi -> 4 valore, 2 storytelling, 2 news, 3 riprova, 1 coinvolgimento, 1 promo - Distribuzione Schwartz: contare livelli -> L5=3, L4=3, L3=4, L2=2, L1=1 - PromptService.list_prompts() ritorna almeno 6 file (system + 5 base) - PromptService.compile_prompt("pas_valore", {"obiettivo_campagna": "test", ...}) sostituisce tutte le variabili senza errori - Tutti i prompt .txt contengono SOLO testo italiano, nessuna istruzione in inglese - Nessun prompt contiene numeri hardcoded per slide count — usano {{num_slides}} o la struttura e' definita nell'output schema CalendarService genera 13 slot con distribuzione PN e Schwartz corretta, assegna fasi campagna, calcola date, ruota nicchie. PromptService carica e compila prompt con variabili {{...}}. 5 prompt base + system prompt scritti IN italiano, con output JSON schema esplicito. Nessun valore hardcoded nei template. 1. CANVA_FIELDS ha 33 elementi e corrisponde allo schema in PROJECT.md (+ caption_instagram) 2. CalendarService produce 13 slot con distribuzione verificabile contando tipi e livelli 3. Ogni prompt .txt usa solo {{variabile}} e non contiene numeri/strutture hardcoded 4. FormatSelector mappa tutte le 30 combinazioni (6 tipi x 5 livelli) 5. Pydantic schemas validano correttamente un esempio di GeneratedPost - CalendarService genera calendario con distribuzione PN e Schwartz corretta - FormatSelector mappa ogni combinazione tipo x livello a un formato - PromptService carica, lista, compila prompt con sostituzione variabili - 5 prompt base + system prompt esistono come file .txt in italiano - CANVA_FIELDS locked come costante con tutti i nomi colonna - Pydantic schemas per calendario, generazione, e risultati definiti After completion, create `.planning/phases/01-core-generation-pipeline/01-02-SUMMARY.md`