From 19efc7053917b276b807ad3f47ad2f1b075a5e19 Mon Sep 17 00:00:00 2001 From: ChemaVX Date: Fri, 3 Jul 2026 07:52:26 +0000 Subject: [PATCH] feat(seo): fijar vocabulario de tags ES en el autofill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cierra el TODO de ALLOWED_TAGS: el allow-list ES son los 6 tags del núcleo vivo de zonadeexclusion (Admin API, 2026-07-03), espejo de la lista EN confirmada: uap, desclasificados, casos-militares, casos-clasicos, investigacion, casos-espana. Quedan fuera la cola de tags de 1 post que el modelo inventó antes de constreñir y el duplicado investigacion-2 (colisión de mayúsculas en Ghost). La regla anti-legacy 'never use "investigacion"' del prompt pasa a ser solo-EN: en ES ese slug es el tag canónico del allow-list. Tests de las funciones puras (_coerce, _system_prompt) por idioma. Co-Authored-By: Claude Fable 5 --- src/seo/autofill.py | 17 +++++++++++++---- tests/test_seo_autofill.py | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/test_seo_autofill.py diff --git a/src/seo/autofill.py b/src/seo/autofill.py index 8eee0b6..3b95d43 100644 --- a/src/seo/autofill.py +++ b/src/seo/autofill.py @@ -37,14 +37,19 @@ SITE_BY_LANG = { # Tag allow-list — the model may ONLY pick from these; invented tags are dropped # deterministically after the call (never trust the LLM to self-limit). EN is the -# live vocabulary Jose confirmed; ES is not yet constrained, so we don't enforce -# it (model picks freely there until an ES vocab is pinned down). +# live vocabulary Jose confirmed; ES is the live core taxonomy on zonadeexclusion +# (fetched 2026-07-03), mirroring EN. Deliberately excluded from ES: the one-post +# long-tail tags the model invented before constraining, and "investigacion-2" +# (a Ghost case-collision duplicate of "investigacion"). ALLOWED_TAGS = { "en": [ "uap", "declassified", "military-cases", "classic-cases", "investigation", "spain-cases", "latin-america", ], - # "es": [...] # TODO: pin the live ES tag vocabulary before constraining. + "es": [ + "uap", "desclasificados", "casos-militares", "casos-clasicos", + "investigacion", "casos-espana", + ], } # Language-aware default tag when nothing from the allow-list fits / the model @@ -116,9 +121,13 @@ async def fetch_published_menu(lang: str) -> list[dict]: def _system_prompt(lang: str) -> str: allow = ", ".join(ALLOWED_TAGS.get(lang, [])) out_lang = "SPANISH" if lang == "es" else "ENGLISH" + # The anti-legacy "never use investigacion" rule is EN-only: on the ES site + # "investigacion" IS the canonical tag (and the allow-list already excludes + # the "investigacion-2" duplicate). + legacy_clause = ", never use \"investigacion\"" if lang == "en" else "" tag_clause = ( f"TAGS: choose 2-4 tags ONLY from this exact list (never invent a tag, " - f"never translate it, never use \"investigacion\"): {allow}.\n" + f"never translate it{legacy_clause}): {allow}.\n" if allow else "TAGS: 2-4 lowercase-hyphenated topical tags appropriate to the article.\n" ) diff --git a/tests/test_seo_autofill.py b/tests/test_seo_autofill.py new file mode 100644 index 0000000..cff00b4 --- /dev/null +++ b/tests/test_seo_autofill.py @@ -0,0 +1,39 @@ +from src.seo.autofill import ALLOWED_TAGS, DEFAULT_TAG, _coerce, _system_prompt + +BASE = { + "meta_title": "t", + "meta_description": "d", + "custom_excerpt": "e", + "image_query": "q", + "image_context": "c", +} + + +def test_coerce_es_drops_invented_tags(): + obj = dict(BASE, tags=["uap", "humanoides", "Desclasificados", "investigacion-2"]) + out = _coerce(obj, "es") + assert out["tags"] == ["uap", "desclasificados"] + + +def test_coerce_es_falls_back_to_default(): + obj = dict(BASE, tags=["pentagono", "encuentros-cercanos"]) + out = _coerce(obj, "es") + assert out["tags"] == [DEFAULT_TAG["es"]] + + +def test_coerce_en_still_constrained(): + obj = dict(BASE, tags=["uap", "investigacion"]) + out = _coerce(obj, "en") + assert out["tags"] == ["uap"] + + +def test_system_prompt_lists_allowed_tags_per_lang(): + es = _system_prompt("es") + en = _system_prompt("en") + for tag in ALLOWED_TAGS["es"]: + assert tag in es + # La regla anti-legacy 'never use "investigacion"' es solo para EN: en ES + # "investigacion" es el tag canónico del allow-list. + assert 'never use "investigacion"' in en + assert 'never use "investigacion"' not in es + assert "ONLY from this exact list" in es