""" Biblioteca de contenido CURADO (la fuente de la verdad, no el LLM). Cargás JSON validado por ustedes. El motor pide content.pick(intent, edad) y recibe un 'script' que el LLM presenta con calidez, sin inventar. """ import json import os import random class ContentLibrary: def __init__(self, content_dir: str): self.items = {} # intent -> list[dict] self.titles = {} # content_id -> title (para armar bloques de contexto/memoria) for name in ("activities", "stories", "songs", "learning"): path = os.path.join(content_dir, f"{name}.json") if os.path.exists(path): with open(path, encoding="utf-8") as f: for item in json.load(f): self.items.setdefault(item["intent"], []).append(item) self.titles[item["id"]] = item["title"] self.colors = [] # paletas para "Sofía, cambiate a " (ver colors.json) colors_path = os.path.join(content_dir, "colors.json") if os.path.exists(colors_path): with open(colors_path, encoding="utf-8") as f: self.colors = json.load(f) def pick(self, intent: str, child_age: int, exclude_ids: set | None = None): """Elige un ítem curado para la edad. Si hay `exclude_ids` (contenido ya visto recientemente, ver ParentalStore.seen_ids), preferimos algo nuevo; si ya vio todo lo disponible, repetimos antes que no ofrecer nada.""" candidates = [ it for it in self.items.get(intent, []) if it.get("min_age", 0) <= child_age <= it.get("max_age", 99) ] if not candidates: return None if exclude_ids: fresh = [it for it in candidates if it["id"] not in exclude_ids] if fresh: candidates = fresh return random.choice(candidates) def match_color(self, message_lower: str) -> dict | None: """Si el mensaje menciona uno de los colores simples de colors.json, devuelve su entrada (script + palette curados). None si no hay match.""" for color in self.colors: if any(word in message_lower for word in color["match"]): return color return None