| """ |
| 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 = {} |
| self.titles = {} |
| 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 = [] |
| 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 |
|
|