- Backend FastAPI con multi-LLM (Claude/OpenAI/Gemini) - Publishing su Facebook, Instagram, YouTube, TikTok - Calendario editoriale con awareness levels (PAS, AIDA, BAB...) - Design system Editorial Fresh (Fraunces + DM Sans) - Scheduler automatico, gestione commenti AI, affiliate links Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
169 lines
5.4 KiB
Python
169 lines
5.4 KiB
Python
"""CalendarService — genera il calendario editoriale con awareness levels (Schwartz).
|
|
|
|
Versione adattata per Leopost Full (standalone, senza dipendenze da postgenerator).
|
|
Genera un piano di pubblicazione con:
|
|
- Formati narrativi: PAS, AIDA, BAB, Storytelling, Listicle, Dato_Implicazione
|
|
- Awareness levels (Schwartz): 1-Unaware, 2-Problem Aware, 3-Solution Aware,
|
|
4-Product Aware, 5-Most Aware
|
|
- Date di pubblicazione suggerite
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, timedelta
|
|
from typing import Optional
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Costanti
|
|
# ---------------------------------------------------------------------------
|
|
|
|
FORMATI_NARRATIVI = [
|
|
"PAS",
|
|
"AIDA",
|
|
"BAB",
|
|
"Storytelling",
|
|
"Listicle",
|
|
"Dato_Implicazione",
|
|
]
|
|
|
|
AWARENESS_LEVELS = {
|
|
1: "Unaware",
|
|
2: "Problem Aware",
|
|
3: "Solution Aware",
|
|
4: "Product Aware",
|
|
5: "Most Aware",
|
|
}
|
|
|
|
# Mapping formato narrativo -> awareness levels consigliati
|
|
_FORMATO_TO_LEVELS: dict[str, list[int]] = {
|
|
"PAS": [2, 3],
|
|
"AIDA": [3, 4, 5],
|
|
"BAB": [2, 3],
|
|
"Storytelling": [1, 2],
|
|
"Listicle": [2, 3],
|
|
"Dato_Implicazione": [1, 2, 3],
|
|
}
|
|
|
|
# Distribuzione default per generazione automatica
|
|
_DEFAULT_DISTRIBUTION = [
|
|
("Storytelling", 1),
|
|
("Dato_Implicazione", 2),
|
|
("PAS", 2),
|
|
("Listicle", 3),
|
|
("AIDA", 4),
|
|
("BAB", 3),
|
|
("AIDA", 5),
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CalendarService
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class CalendarService:
|
|
"""Genera il calendario editoriale con awareness levels e formati narrativi."""
|
|
|
|
def generate_calendar(
|
|
self,
|
|
topics: list[str],
|
|
num_posts: int = 7,
|
|
format_narrativo: Optional[str] = None,
|
|
awareness_level: Optional[int] = None,
|
|
start_date: Optional[str] = None,
|
|
) -> list[dict]:
|
|
"""Genera un calendario editoriale."""
|
|
if start_date:
|
|
try:
|
|
data_inizio = date.fromisoformat(start_date)
|
|
except ValueError:
|
|
data_inizio = date.today()
|
|
else:
|
|
data_inizio = date.today()
|
|
|
|
dates = self._generate_dates(data_inizio, num_posts)
|
|
|
|
if format_narrativo and awareness_level:
|
|
distribution = [(format_narrativo, awareness_level)] * num_posts
|
|
elif format_narrativo:
|
|
levels = _FORMATO_TO_LEVELS.get(format_narrativo, [2, 3, 4])
|
|
distribution = [
|
|
(format_narrativo, levels[i % len(levels)])
|
|
for i in range(num_posts)
|
|
]
|
|
elif awareness_level:
|
|
compatible_formats = [
|
|
fmt for fmt, levels in _FORMATO_TO_LEVELS.items()
|
|
if awareness_level in levels
|
|
]
|
|
if not compatible_formats:
|
|
compatible_formats = FORMATI_NARRATIVI
|
|
distribution = [
|
|
(compatible_formats[i % len(compatible_formats)], awareness_level)
|
|
for i in range(num_posts)
|
|
]
|
|
else:
|
|
distribution = [
|
|
_DEFAULT_DISTRIBUTION[i % len(_DEFAULT_DISTRIBUTION)]
|
|
for i in range(num_posts)
|
|
]
|
|
|
|
slots = []
|
|
for i in range(num_posts):
|
|
topic = topics[i % len(topics)] if topics else f"Topic {i + 1}"
|
|
fmt, level = distribution[i]
|
|
|
|
slots.append({
|
|
"indice": i,
|
|
"topic": topic,
|
|
"formato_narrativo": fmt,
|
|
"awareness_level": level,
|
|
"awareness_label": AWARENESS_LEVELS.get(level, f"Level {level}"),
|
|
"data_pubblicazione": dates[i].isoformat(),
|
|
"note": self._generate_note(fmt, level),
|
|
})
|
|
|
|
return slots
|
|
|
|
def get_formats(self) -> list[dict]:
|
|
"""Ritorna la lista dei formati narrativi disponibili."""
|
|
return [
|
|
{
|
|
"value": fmt,
|
|
"label": fmt.replace("_", " "),
|
|
"awareness_levels": _FORMATO_TO_LEVELS.get(fmt, [2, 3]),
|
|
}
|
|
for fmt in FORMATI_NARRATIVI
|
|
]
|
|
|
|
@staticmethod
|
|
def _generate_dates(start: date, count: int) -> list[date]:
|
|
"""Genera date di pubblicazione (lun, mer, ven)."""
|
|
publish_days = [0, 2, 4]
|
|
dates = []
|
|
current = start
|
|
|
|
while current.weekday() not in publish_days:
|
|
current += timedelta(days=1)
|
|
|
|
while len(dates) < count:
|
|
if current.weekday() in publish_days:
|
|
dates.append(current)
|
|
current += timedelta(days=1)
|
|
|
|
return dates
|
|
|
|
@staticmethod
|
|
def _generate_note(formato: str, level: int) -> str:
|
|
"""Genera una nota descrittiva per lo slot."""
|
|
level_label = AWARENESS_LEVELS.get(level, f"L{level}")
|
|
notes = {
|
|
"PAS": f"Problema-Agitazione-Soluzione. Target: {level_label}",
|
|
"AIDA": f"Attenzione-Interesse-Desiderio-Azione. Target: {level_label}",
|
|
"BAB": f"Before-After-Bridge. Target: {level_label}",
|
|
"Storytelling": f"Racconta una storia autentica. Target: {level_label}",
|
|
"Listicle": f"Lista di punti pratici. Target: {level_label}",
|
|
"Dato_Implicazione": f"Dato sorprendente + implicazione. Target: {level_label}",
|
|
}
|
|
return notes.get(formato, f"Formato {formato}. Target: {level_label}")
|