deterministic-interpreter.js — intérprete determinista NLU-02A

Overview

Este módulo convierte una entrada de InterpretationResult.input en un InterpretationResult válido en ejecución. Lo hace sin E/S, sin reloj, sin aleatoriedad, sin estado de módulo y sin proveedor de IA. También mantiene la evidencia textual que no puede representar, en lugar de descartarla.

Su papel es muy concreto dentro de services/api/src/modules/interpretation: usa el contrato de interpretación, el léxico inmobiliario, la normalización de texto, los montos deterministas y la ubicación determinista. Esa combinación le permite producir resultados reproducibles y validados. fileciteturn0file0turn0file9

[!IMPORTANT] Este intérprete nunca ejecuta acciones de dominio. Como mucho, propone publish_wizard.open para una oferta.

[!NOTE] La validación final pasa por validateRuntimeInterpretation. El resultado también se congela en profundidad antes de devolverse. fileciteturn0file9turn0file13

How it works

flowchart TB
    A[interpret] --> B[snapshotInput]
    B --> C[interpretText]
    C --> D[analyze]
    D --> E[decideIntent]

    E -->|discover.search| F[buildSearch]
    E -->|publish.start_offer| G[buildOffer]
    E -->|ambiguous| H[buildAmbiguous]
    E -->|unsupported| I[buildUnsupported]
    E -->|unrecognized| J[buildUnrecognized]

    F --> K[validateRuntimeInterpretation]
    G --> K
    H --> K
    I --> K
    J --> K

    K --> L[deepFreeze]

El flujo empieza con snapshotInput. Esa función valida que la entrada sea un objeto plano y serializable. Después, interpretText analiza solo text. Si el texto es inválido o demasiado largo, el intérprete usa una cadena vacía.

analyze combina varias señales. Extrae coincidencias del léxico, ubicaciones, negaciones y montos. Después, decideIntent selecciona entre búsqueda, oferta, ambigüedad, no soportado o no reconocido. Finalmente, el builder correspondiente arma el resultado y validateRuntimeInterpretation verifica que todo cumpla el contrato. fileciteturn0file9turn0file17

Key components

{
    "title": "InterpretationError",
    "content": "Clase de error del m\u00f3dulo. Representa fallos de entrada o de resultado con el c\u00f3digo INVALID_INPUT o INVALID_RESULT.",
    "type": "class",
    "filePath": "services/api/src/modules/interpretation/deterministic-interpreter.js",
    "badges": [
        "public"
    ]
}
{
    "title": "interpret",
    "content": "Funci\u00f3n p\u00fablica principal. Recibe la entrada NLU-01, ejecuta el int\u00e9rprete determinista, valida el resultado y lo devuelve congelado.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/deterministic-interpreter.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "contractVersion",
        "type": "string",
        "required": true,
        "description": "Versión del contrato devuelta por INTERPRETATION_CONTRACT_VERSION."
    },
    {
        "name": "interpreter",
        "type": "object",
        "required": true,
        "description": "Metadata del intérprete determinista.",
        "children": [
            {
                "name": "kind",
                "type": "string",
                "required": true,
                "description": "Siempre es deterministic."
            },
            {
                "name": "id",
                "type": "string",
                "required": true,
                "description": "Identificador exportado por DETERMINISTIC_INTERPRETER_ID."
            },
            {
                "name": "version",
                "type": "string",
                "required": true,
                "description": "Versión semántica del intérprete."
            },
            {
                "name": "capabilityRegistryVersion",
                "type": "string",
                "required": true,
                "description": "Versión del registro de capacidades vigente."
            },
            {
                "name": "lexiconVersion",
                "type": "string",
                "required": true,
                "description": "Versión del léxico inmobiliario vigente."
            }
        ]
    },
    {
        "name": "input",
        "type": "object",
        "required": true,
        "description": "Copia profunda de la entrada recibida. El intérprete solo lee text y conserva el resto."
    },
    {
        "name": "status",
        "type": "string",
        "required": true,
        "description": "Estado runtime. Puede ser recognized, needs_clarification, ambiguous_intent, unsupported o unrecognized."
    },
    {
        "name": "intent",
        "type": "string | null",
        "required": true,
        "description": "Intento resuelto. Usa discover.search, publish.start_offer o null."
    },
    {
        "name": "params",
        "type": "object",
        "required": true,
        "description": "Parámetros extraídos con procedencia textual exacta.",
        "children": [
            {
                "name": "propertyType",
                "type": "object",
                "description": "Tipo de inmueble extraído del léxico.",
                "children": [
                    {
                        "name": "value",
                        "type": "string",
                        "required": true,
                        "description": "Valor canónico del tipo de inmueble."
                    },
                    {
                        "name": "source",
                        "type": "object",
                        "required": true,
                        "description": "Procedencia exacta del fragmento.",
                        "children": [
                            {
                                "name": "kind",
                                "type": "string",
                                "required": true,
                                "description": "Siempre text."
                            },
                            {
                                "name": "span",
                                "type": "number[]",
                                "required": true,
                                "description": "Rango UTF-16 del fragmento original."
                            },
                            {
                                "name": "matched",
                                "type": "string",
                                "required": true,
                                "description": "Fragmento exacto encontrado."
                            },
                            {
                                "name": "rule",
                                "type": "string",
                                "required": true,
                                "description": "Regla o identificador de origen."
                            }
                        ]
                    }
                ]
            },
            {
                "name": "entityType",
                "type": "object",
                "description": "Se fija en property cuando existe propertyType."
            },
            {
                "name": "operationType",
                "type": "object",
                "description": "Operación inmobiliaria reconocida, como rent o sell."
            },
            {
                "name": "locationQuery",
                "type": "object",
                "description": "Ubicación textual extraída sin geocodificación."
            },
            {
                "name": "budgetMax",
                "type": "object",
                "description": "Cota superior de presupuesto.",
                "children": [
                    {
                        "name": "value",
                        "type": "number",
                        "required": true,
                        "description": "Monto máximo normalizado."
                    },
                    {
                        "name": "source",
                        "type": "object",
                        "required": true,
                        "description": "Fragmento de origen del monto."
                    }
                ]
            },
            {
                "name": "budgetCurrency",
                "type": "object",
                "description": "Moneda explícita o resuelta por contexto."
            },
            {
                "name": "budgetPeriodicity",
                "type": "object",
                "description": "Periodicidad monetaria reconocida."
            }
        ]
    },
    {
        "name": "missing",
        "type": "array",
        "required": true,
        "description": "Campos que faltan para completar la interpretación.",
        "children": [
            {
                "name": "param",
                "type": "string",
                "required": true,
                "description": "Nombre del parámetro faltante."
            },
            {
                "name": "required",
                "type": "boolean",
                "required": true,
                "description": "Indica si el faltante bloquea la interpretación."
            },
            {
                "name": "control",
                "type": "string",
                "required": true,
                "description": "Tipo de control de aclaración."
            }
        ]
    },
    {
        "name": "ambiguities",
        "type": "array",
        "required": true,
        "description": "Ambigüedades que requieren elección explícita.",
        "children": [
            {
                "name": "source",
                "type": "object",
                "required": true,
                "description": "Fragmento que originó la ambigüedad."
            },
            {
                "name": "control",
                "type": "string",
                "required": true,
                "description": "Tipo de control que resuelve la ambigüedad."
            },
            {
                "name": "options",
                "type": "array",
                "required": true,
                "description": "Opciones disponibles para resolverla."
            }
        ]
    },
    {
        "name": "unparsed",
        "type": "array",
        "required": true,
        "description": "Evidencia reconocida pero no representable.",
        "children": [
            {
                "name": "span",
                "type": "number[]",
                "required": true,
                "description": "Rango UTF-16 exacto."
            },
            {
                "name": "matched",
                "type": "string",
                "required": true,
                "description": "Fragmento exacto conservado."
            },
            {
                "name": "reason",
                "type": "string",
                "required": true,
                "description": "Motivo de la conservación."
            }
        ]
    },
    {
        "name": "capability",
        "type": "object",
        "description": "Capacidad declarada para la acción propuesta o planificada."
    },
    {
        "name": "proposedAction",
        "type": "object | null",
        "description": "Acción sugerida por el intérprete. En esta versión solo aparece para publish.start_offer."
    },
    {
        "name": "confirmation",
        "type": "string",
        "required": true,
        "description": "Nivel de confirmación asociado a la acción o al resultado."
    }
]

Constants and rule sets

  • DETERMINISTIC_INTERPRETER_ID fija el identificador público del intérprete como plano-deterministic.
  • DETERMINISTIC_INTERPRETER_VERSION fija la versión del intérprete como 0.1.0.
  • INTERPRETATION_ERROR_CODES expone INVALID_INPUT e INVALID_RESULT.
  • AMBIGUOUS_INTENT_OPTIONS contiene discover.search y publish.start_offer.
  • DOLLAR_OPTIONS modela la ambigüedad de $ como MXN o USD.
  • NARROW_NEGATIONS y NARROW_SKIP_WORDS definen el alcance de negaciones estrechas.
  • OBJECT_BOUNDARY_WORDS y DETERMINER_WORDS ayudan a cortar objetos negados.
  • CONFIDENT_SCOPE_WORDS marca el umbral de incertidumbre para cláusulas largas.
  • CONJUNCTION_BREAKS y VERB_BREAKS cierran el alcance de una cláusula.
  • POSSESSIVE_BEFORE detecta posesivos como evidencia de oferta.
  • SCOPE_WORD y CLAUSE_BREAK sostienen la segmentación textual.

Behaviors

  • isPlainObject — Comprueba si un valor es un objeto plano.
  • facetsOf — Devuelve facetas solo cuando la coincidencia no es ambigua.
  • facetValue — Lee el valor de una faceta por categoría.
  • textSource — Construye procedencia textual con span, matched y rule.
  • unparsedItem — Crea un registro de evidencia no representable.
  • deepFreeze — Congela recursivamente objetos y sus hijos.
  • negatedClause — Detecta el final de una cláusula negada.
  • unrepresentableObjectEnd — Calcula el alcance de una negación sin criterios representables.
  • resolveNegations — Marca coincidencias y lugares negados, y conserva evidencia.
  • single — Resuelve un único valor de faceta o detecta conflicto.
  • analyze — Extrae señales de léxico, lugares, negaciones y montos.
  • decideIntent — Decide el intent final con evidencia textual.
  • propertyParams — Construye propertyType, entityType, operationType y locationQuery.
  • distinctPlaces — Deduplica lugares por valor.
  • sharedUnparsed — Reúne evidencia no representable compartida.
  • orderedUnparsed — Elimina duplicados y ordena evidencia de forma determinista.
  • capabilityFor — Proyecta una acción del registro a su capacidad.
  • buildSearch — Construye una búsqueda reconocida o con aclaraciones.
  • buildOffer — Construye una oferta reconocida y propone abrir el wizard.
  • buildAmbiguous — Devuelve un resultado con ambiguous_intent.
  • buildUnsupported — Devuelve un resultado unsupported.
  • buildUnrecognized — Devuelve un resultado unrecognized.
  • interpretText — Despacha al builder correcto según el intent.
  • snapshotInput — Copia y valida la entrada como datos serializables.
  • interpret — Orquesta snapshot, interpretación, validación y congelado final. fileciteturn0file9turn0file13

Gotchas & edge cases

[!WARNING] Si input no es un objeto plano, interpret lanza INVALID_INPUT. Un Proxy revocado también cuenta como entrada inválida.

[!WARNING] Si input no es serializable, snapshotInput falla con INVALID_INPUT. El módulo no continúa con una copia parcial.

[!CAUTION] Un texto ausente o demasiado largo se reemplaza por "". El intérprete no procesa ese contenido.

[!IMPORTANT] La negación nunca activa un criterio positivo. Si algo no puede representarse, queda en unparsed con su fragmento exacto. fileciteturn0file0turn0file9

[!NOTE] "$" por sí solo no fija MXN. El módulo deja esa ambigüedad en ambiguities.

[!NOTE] 1.500 sin magnitud no se interpreta como monto válido. La evidencia se conserva.

[!NOTE] La ubicación se extrae por estructura, no por mayúsculas. El módulo puede aceptar texto como puebla. fileciteturn0file0turn0file1

deterministic-interpreter.js depende de interpretation-contract.js, real-estate-lexicon.js, text-normalization.js, deterministic-amounts.js y deterministic-location.js. Esa relación se ve directamente en sus imports. fileciteturn0file0turn0file13

Los tests de services/api/test/interpretation-deterministic.test.js, services/api/test/interpretation-deterministic-units.test.js y services/api/test/interpretation-deterministic-dev.test.js validan versiones, procedencia exacta, determinismo y acuerdo con el corpus. El contrato también se cubre en services/api/test/interpretation-contract.test.js. fileciteturn0file2turn0file4turn0file5turn0file8turn0file14

El contexto del repositorio describe este módulo como NLU-02A, un primer corte vertical de interpretación determinista. También señala que su salida sigue el contrato InterpretationResult de NLU-01. fileciteturn0file1turn0file6