Skip to content

Filtro de políticas (Bhutan GNH + NZ LSF + Humphrey) ​

Rúbrica publicada del filtro determinista que alimenta recommend_action (server/chat/policy-screen.ts, 213 líneas, sin red y sin LLM). Que sea determinista es el punto: la misma propuesta produce el mismo veredicto siempre, y el veredicto es auditable leyendo listas de palabras clave versionadas en el repositorio, no interrogando a un modelo.

Sirve al Pilar 3 — Gobernanza y Democracia: un regulador puede reproducir el resultado sin depender de nosotros.

Qué hace ​

Puntúa un texto de propuesta (o el pitch de un piloto) contra tres lentes:

  1. Los 10 ejes del atlas (salud … gobernanza-democrática).
  2. Los seis capitales intergeneracionales estilo NZ Living Standards Framework: natural, human, social, financial, physical, cultural.
  3. Una lectura Humphrey de "¿qué se mueve y para quién?", hoy: tono positive / negative / mixed / neutral por eje, sin inventar cifras.

Cómo puntúa ​

Cada lente se resuelve con scoreKeywords() sobre listas positive / negative propias, y el tono sale de qué polaridades encontraron coincidencia:

CoincidenciasTono
positivas y negativasmixed
solo positivaspositive
solo negativasnegative
ningunaneutral

El tono global (overall) agrega los tonos de ejes y capitales descartando los neutral: si no queda ninguno → neutral; si queda más de uno distinto → mixed; si queda exactamente uno → ese. Una propuesta con un eje positivo y otro negativo es mixed, nunca un promedio que borre la tensión.

Reglas ​

  • La coincidencia es por palabra completa (desempleo no coincide con empleo).
  • Keywords por eje: no se infiere el tono de las celdas ambientales del país. El filtro lee la propuesta, no el corpus del país, así el resultado no depende de qué tan bien cubierto esté un país.
  • Tokens de producto (cribado, latamgpt, futuros, odisea) no se tratan como geografía (Costa Rica / CRI).
  • human_override_required: true y export_allowed: false siempre, como literales de tipo: una persona debe autorizar cualquier exportación del resultado. Están tipados como constantes, así que el compilador rechaza cualquier código que intente emitir un resultado auto-exportable.
  • Determinista y aceptable; no es un LLM.

Forma del resultado ​

jsonc
{
  "rubric": "bhutan-gnh+nz-lsf+humphrey/v1",  // versionada: un cambio de rúbrica cambia el id
  "proposal": "…",
  "ejes":     [{ "eje": "salud", "tone": "positive", "hits": ["…"], "note": "…" }],
  "capitals": [{ "capital": "natural", "tone": "mixed", "hits": ["…"] }],
  "overall": "mixed",
  "human_override_required": true,
  "export_allowed": false,
  "warnings": [],
  "truncated": true                           // solo presente cuando la propuesta superó los 8.000 caracteres
}

Dónde vive ​

  • Código: server/chat/policy-screen.ts (+ tests).
  • Consumo: el campo policy_screen en la respuesta de recommend_action (herramienta disponible tanto en el asistente como en el servidor MCP).
  • API: POST /api/policy-screen con { "proposal": "…" }. Cuerpo limitado a 16 KB (413 body_too_large por encima); JSON inválido → 400 parse_error; maxDuration de 10 s. proposal debe ser una cadena no vacía. Cualquier otro tipo JSON (objeto, arreglo, número, booleano) devuelve 400 proposal_required: no se convierte a texto ("[object Object]") ni se analiza como si fuera una propuesta real. La propuesta se limita a 8.000 caracteres. Por encima se analizan los primeros 8.000 y la respuesta lleva truncated: true, para que quien llama sepa que el veredicto cubre solo parte de su documento; truncar en silencio un documento de política cambiaría el resultado sin avisar a nadie, y por eso existe la bandera.
  • Superficie humana: /gobernanza/cribado (demo con ejes mixtos + autorización humana para exportar).

Cada cifra con su fuente — la trazabilidad es el contrato.