Contrato de interpretación de lenguaje natural

Panorama general

Este archivo define el contrato InterpretationResult versión "1" para todo intérprete de lenguaje natural de Plano. Su función es validar salidas de interpretación sin ejecutar acciones, sin escribir datos y sin tocar persistencia.

El módulo también fija los catálogos, los esquemas cerrados, las reglas de procedencia y la coherencia entre intención, capacidad, confirmación y dominio. Eso permite que el sistema siga siendo determinista, incluso cuando exista un intérprete basado en modelo.

[!IMPORTANT] Un intérprete solo propone. Nunca ejecuta ni completa acciones por su cuenta.

La ejecución real revalida cada argumento con los validadores de dominio existentes.

[!WARNING] El contrato rechaza cualquier score, confidence, probability o likelihood en cualquier nivel del resultado.

Cómo funciona

flowchart TD
    RT[validateRuntimeInterpretation] --> VR[validateInterpretationResult]
    VR --> V1[Versiones y esquema superior]
    VR --> I1[validateInput]
    I1 --> P1[params]
    I1 --> C1[changes]
    I1 --> M1[missing]
    I1 --> A1[ambiguities]
    I1 --> U1[unparsed]
    P1 --> L1[location]
    C1 --> L1
    L1 --> D1[domain]
    D1 --> C2[capability]
    C2 --> A2[proposedAction]
    A2 --> R1[refinementScope]
    R1 --> O1[valid or errors]

El flujo comienza con validateInterpretationResult(). Primero verifica el contenedor superior, luego valida input, después revisa parámetros, cambios, faltantes, ambigüedades y fragmentos no parseados. Al final comprueba la coherencia de ubicación, dominio, capacidad, acción propuesta y confirmación.

validateRuntimeInterpretation() actúa como envoltura estricta para ejecución. Exige expectedInput, prohíbe intérpretes de tipo reference y nunca permite acciones planificadas.

Componentes clave

{
    "title": "validateInterpretationResult",
    "content": "Valida un InterpretationResult completo y devuelve { valid, errors }. Revisa esquema cerrado, evidencia, procedencia, dominio, capacidad, confirmaci\u00f3n y coherencia de estado.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/interpretation-contract.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateRuntimeInterpretation",
    "content": "Validador obligatorio para ejecuci\u00f3n. Exige expectedInput, rechaza etiquetas de referencia y desactiva acciones planificadas.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/interpretation-contract.js",
    "badges": [
        "public"
    ]
}
{
    "title": "resolveRefinedCriteria",
    "content": "Aplica un refinamiento ya validado sobre criterios activos. Conserva lo no mencionado y reemplaza la ubicaci\u00f3n completa cuando cambia locationQuery o searchZones.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/interpretation-contract.js",
    "badges": [
        "public"
    ]
}
{
    "title": "checkDomainCombination",
    "content": "Comprueba la compatibilidad entre entityType, operationType y subtipo de listado. Devuelve errores, missing y unrepresentable.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/interpretation-contract.js",
    "badges": [
        "public"
    ]
}
{
    "title": "validateParamValue",
    "content": "Valida un valor contra la especificaci\u00f3n de su par\u00e1metro. Soporta enums, listas, zonas, n\u00fameros, enteros, booleanos, cadenas, ordinales y referencias activas.",
    "type": "function",
    "filePath": "services/api/src/modules/interpretation/interpretation-contract.js",
    "badges": [
        "public"
    ]
}

Datos y campos

El contrato usa esquemas cerrados. Cada objeto anidado rechaza llaves desconocidas. La entrada input la construye quien llama, no el intérprete.

[
    {
        "name": "contractVersion",
        "type": "string",
        "required": true,
        "description": "Versión del contrato. Debe ser \"1\"."
    },
    {
        "name": "interpreter",
        "type": "object",
        "required": true,
        "description": "Identifica el intérprete que produjo el resultado.",
        "children": [
            {
                "name": "kind",
                "type": "deterministic | model | reference",
                "required": true,
                "description": "Tipo de intérprete."
            },
            {
                "name": "id",
                "type": "string",
                "required": true,
                "description": "Identificador no vacío del intérprete."
            },
            {
                "name": "version",
                "type": "string",
                "required": true,
                "description": "Versión no vacía del intérprete."
            },
            {
                "name": "capabilityRegistryVersion",
                "type": "string",
                "description": "Si existe, debe coincidir con CAPABILITY_REGISTRY_VERSION."
            },
            {
                "name": "lexiconVersion",
                "type": "string",
                "description": "Si existe, debe coincidir con LEXICON_VERSION."
            }
        ]
    },
    {
        "name": "input",
        "type": "object",
        "required": true,
        "description": "Entrada original que recibió el intérprete.",
        "children": [
            {
                "name": "text",
                "type": "string",
                "required": true,
                "description": "Texto fuente. Máximo 2000 caracteres."
            },
            {
                "name": "locale",
                "type": "string",
                "required": true,
                "description": "Localidad admitida. Solo \"es-MX\"."
            },
            {
                "name": "context",
                "type": "object",
                "required": true,
                "description": "Contexto que aporta evidencia verificable.",
                "children": [
                    {
                        "name": "surface",
                        "type": "wall | conversation | temporary",
                        "required": true,
                        "description": "Superficie de interacción actual."
                    },
                    {
                        "name": "conversationId",
                        "type": "string",
                        "description": "Identificador de conversación, cuando existe."
                    },
                    {
                        "name": "activeSearch",
                        "type": "object",
                        "description": "Búsqueda activa que puede ser temporary o saved.",
                        "children": [
                            {
                                "name": "kind",
                                "type": "temporary | saved",
                                "required": true,
                                "description": "Tipo de búsqueda activa."
                            },
                            {
                                "name": "requirementId",
                                "type": "string",
                                "description": "Obligatorio cuando kind es saved."
                            },
                            {
                                "name": "criteria",
                                "type": "object",
                                "description": "Criterios activos verificables."
                            }
                        ]
                    },
                    {
                        "name": "visibleResults",
                        "type": "object",
                        "description": "Resumen de resultados visibles.",
                        "children": [
                            {
                                "name": "count",
                                "type": "integer",
                                "required": true,
                                "description": "Cantidad visible de resultados."
                            }
                        ]
                    },
                    {
                        "name": "locationDefaults",
                        "type": "object",
                        "description": "Valores predeterminados de ubicación.",
                        "children": [
                            {
                                "name": "current",
                                "type": "object",
                                "description": "Ubicación actual autorizada.",
                                "children": [
                                    {
                                        "name": "consent",
                                        "type": "\"granted\"",
                                        "required": true,
                                        "description": "Debe declarar consentimiento explícito."
                                    },
                                    {
                                        "name": "searchZones",
                                        "type": "zone list",
                                        "required": true,
                                        "description": "Zonas geocodificadas de la ubicación actual."
                                    }
                                ]
                            },
                            {
                                "name": "profile",
                                "type": "object",
                                "description": "Preferencias de ubicación del perfil.",
                                "children": [
                                    {
                                        "name": "locationQuery",
                                        "type": "string",
                                        "description": "Texto de ubicación del perfil."
                                    },
                                    {
                                        "name": "searchZones",
                                        "type": "zone list",
                                        "description": "Zonas geocodificadas del perfil."
                                    }
                                ]
                            }
                        ]
                    }
                ]
            },
            {
                "name": "controlSelections",
                "type": "array of controlSelection",
                "description": "Selecciones hechas por controles de aclaración."
            }
        ]
    },
    {
        "name": "status",
        "type": "recognized | needs_clarification | ambiguous_intent | unsupported | unrecognized",
        "required": true,
        "description": "Estado final de la interpretación."
    },
    {
        "name": "intent",
        "type": "string | null",
        "required": true,
        "description": "IntentId del catálogo, o null cuando el estado lo exige."
    },
    {
        "name": "intentOptions",
        "type": "array of string",
        "description": "Opciones de intención solo para ambiguous_intent."
    },
    {
        "name": "params",
        "type": "object",
        "required": true,
        "description": "Parámetros interpretados. Cada entrada usa { value, source }."
    },
    {
        "name": "changes",
        "type": "array",
        "description": "Cambios sobre criterios activos. Solo aplica a discover.refine."
    },
    {
        "name": "missing",
        "type": "array",
        "description": "Parámetros obligatorios faltantes, con su control esperado."
    },
    {
        "name": "ambiguities",
        "type": "array",
        "description": "Lecturas ambiguas con dos o más opciones válidas."
    },
    {
        "name": "unparsed",
        "type": "array",
        "description": "Fragmentos no interpretados del texto."
    },
    {
        "name": "location",
        "type": "object",
        "description": "Resolución de ubicación y su editabilidad."
    },
    {
        "name": "capability",
        "type": "object",
        "description": "Capacidad declarada por el registro de acciones."
    },
    {
        "name": "proposedAction",
        "type": "object | null",
        "required": true,
        "description": "Acción propuesta. args debe ser {}."
    },
    {
        "name": "confirmation",
        "type": "none | required | required_public_visibility",
        "required": true,
        "description": "Nivel de confirmación requerido para la acción propuesta."
    }
]

Vocabulario y catálogos de estado

Estos valores se usan para cerrar el contrato y limitar salidas válidas. Muchos de ellos dependen de validadores externos o de catálogos del backend.

  • INTERPRETATION_CONTRACT_VERSION — versión del contrato, fija en "1".
  • CAPABILITY_REGISTRY_VERSION — versión del registro de capacidades, fija en "1".
  • INTERPRETATION_STATUSES — recognized, needs_clarification, ambiguous_intent, unsupported, unrecognized.
  • INTERPRETER_KINDS — deterministic, model, reference.
  • INTERPRETATION_LOCALES — solo es-MX.
  • INTERACTION_SURFACES — wall, conversation, temporary.
  • ACTIVE_SEARCH_KINDS — temporary, saved.
  • CONTEXT_REFS — activeSearch, visibleResults, conversation, locationDefaults.
  • INTENT_IDS — catálogo cerrado de intenciones.
  • CONFIRMATION_LEVELS — none, required, required_public_visibility.
  • CLARIFICATION_CONTROLS — choice, number, range, toggle, text, place_search, requirement_picker, listing_picker, conversation_picker.
  • UNPARSED_REASONS — UNKNOWN_TERM, UNSUPPORTED_FIELD, NEGATION_NOT_REPRESENTABLE, OUT_OF_SCOPE_REQUEST.
  • PARAM_SOURCE_KINDS — text, context, control.
  • CHANGE_OPS — set, clear, remove.
  • CAPABILITY_STATUSES — available, planned, none.
  • AUTHORIZATION_REQUIREMENTS — authenticated, owner, verified_email, publish_entitlement.
  • LOCATION_RESOLUTIONS — explicit, conversation, current_location, profile, unresolved.

Vocabulario de dominio

Este bloque copia vocabulario controlado del backend. Los comentarios del archivo lo vinculan con models/Listing.js y modules/listings/listings.validators.js.

  • ENTITY_TYPE_VALUES — property, service, investment.
  • OPERATION_TYPE_VALUES — sale, rent, temporary_rent, presale, investment_offer, service_offer, exchange, auction, transfer, service_request, investment_request.
  • PROPERTY_TYPE_VALUES — house, apartment, land, office, warehouse, commercial, industrial, building, other.
  • SERVICE_TYPE_VALUES — legal, notarial, architecture, construction, structural_steel, construction_engineering, insurance, mortgage, financial_advisory, valuation, property_management, photography, referral, other_service.
  • INVESTMENT_TYPE_VALUES — fibra, fractional, development.
  • LISTING_INTENTS — offer, requirement.
  • AMENITY_VALUES — catálogo cerrado de amenidades.
  • PERIODICITY_VALUES — unspecified, day, week, month.
  • CURRENCY_VALUES — MXN, USD, EUR.

[!NOTE] CURRENCY_VALUES es un vocabulario controlado del formulario.

El archivo aclara que el backend guarda la moneda como texto libre.

Especificación de parámetros

PARAM_SPECS define el tipo, límites y targetPath de cada parámetro. PARAM_KEYS es la lista derivada de esas claves. CRITERIA_PARAMS marca qué campos forman parte de una búsqueda activa.

Parámetros de dominio

  • entityType — enum, meta.entityType.
  • operationType — enum, meta.operationType.
  • propertyType — enum, meta.propertyType.
  • serviceType — enum, meta.serviceType.
  • investmentType — enum, meta.investmentType.

Parámetros económicos

  • budgetMin — number, mínimo 0, requirement.budget.min.
  • budgetMax — number, mínimo 0, requirement.budget.max.
  • budgetCurrency — enum, requirement.budget.currency.
  • budgetPeriodicity — enum, requirement.budget.periodicity.
  • priceAmount — number, mínimo 0, price.amount.
  • priceCurrency — enum, price.currency.
  • pricePeriodicity — enum, price.periodicity.

Parámetros físicos

  • bedrooms — integer, mínimo 0, meta.bedrooms.
  • bathrooms — integer, mínimo 0, meta.bathrooms.
  • halfBathrooms — integer, mínimo 0, meta.halfBathrooms.
  • parkingSpots — integer, mínimo 0, meta.parkingSpots.
  • constructionArea — number, mínimo 0, unidad m2, meta.constructionArea.
  • landArea — number, mínimo 0, unidad m2, meta.landArea.

Preferencias y ubicación

  • desiredAmenities — enum list, requirement.desiredAmenities.
  • hasPets — boolean, requirement.hasPets.
  • petFriendlyRequired — boolean, requirement.petFriendlyRequired.
  • locationQuery — string, targetPath: null.
  • searchZones — zone list, máximo 3, requirement.searchZones.
  • titleQuery — string, targetPath: null.

Referencias e identificadores

  • listingReference — ordinal, targetPath: null.
  • requirementId — string, targetPath: null.
  • listingId — string, targetPath: null.
  • conversationId — string, targetPath: null.
  • requirementReference — referencia activa, usa evidencia de activeSearch.

[!IMPORTANT] searchZones nunca sale del texto.

Solo puede venir de place_search o de evidencia previa en input.context.

Procedencia permitida

La procedencia está cerrada por tipo. Cada valor debe demostrar de dónde salió.

  • text
    • Requiere span.
    • Requiere matched.
    • Requiere rule.
    • matched debe ser exactamente el fragmento de input.text.
  • context
    • Requiere ref.
    • Requiere path cuando la evidencia debe apuntar a un valor.
    • Solo acepta activeSearch, visibleResults, conversation, locationDefaults.
  • control
    • Requiere control.
    • Requiere selectionId.
    • Debe coincidir con input.controlSelections.

Cambios sobre criterios activos

changes solo se permite con discover.refine. El archivo define tres operaciones:

  • set — establece o reemplaza un criterio.
  • clear — elimina un criterio.
  • remove — elimina un elemento de una lista.

resolveRefinedCriteria() aplica esos cambios sobre input.context.activeSearch.criteria. Cuando cambia locationQuery o searchZones, la ubicación completa se reemplaza.

Registro de acciones

ACTION_TYPES relaciona intención, permisos, confirmación, persistencia y disponibilidad. Cada acción incluye intents, writes, minimumConfirmation, available, authorization y otros requisitos.

  • match.preview_criteria

    • Intenciones: discover.search, discover.refine, discover.show_matches.
    • No escribe.
    • Requiere confirmación none.
    • Está registrada como available: false.
    • Se relaciona con una operación planificada sobre generateCandidatesForRequirement.
  • match.requirement_candidates

    • Intenciones: discover.show_matches, discover.search.
    • No escribe.
    • Requiere authenticated y owner.
    • Solo admite saved.
    • Se vincula con GET /api/match/requirements/:requirementId/candidates.
  • requirement.refine

    • Solo discover.refine.
    • Escribe.
    • Requiere authenticated, owner, verified_email.
    • Requiere requirementReference.
    • Usa persistent sobre una búsqueda guardada.
  • requirement.save_private

    • Solo discover.save.
    • Escribe.
    • Está planificada, no disponible.
    • Se vincula con una visibilidad privada de requirement.
  • requirement.publish

    • discover.allow_recommendations.
    • Escribe.
    • Requiere authenticated, owner, verified_email, publish_entitlement.
    • Hace pública información de la persona.
  • conversation.attach_requirement

    • Solo discover.select_existing.
    • Escribe.
    • Requiere requirementId y conversationId.
    • Se vincula con PATCH /api/conversations/:id.
  • conversation.open

    • discover.recover.
    • No escribe.
    • Requiere authenticated y owner.
    • Se vincula con consultas de conversaciones.
  • listing.open

    • listing.inspect.
    • No escribe.
    • No requiere autenticación.
    • Se vincula con GET /api/listings/:id.
  • publish_wizard.open

    • publish.start_offer.
    • No escribe.
    • Requiere authenticated.
    • Se vincula con la ruta del asistente de publicación.
  • publish_wizard.continue

    • publish.continue.
    • No escribe.
    • Requiere authenticated y owner.
    • Se vincula con listado propio y edición del asistente.
  • navigate.leads

    • attend.leads.
    • No escribe.
    • Requiere authenticated.
    • Se vincula con la ruta de atención y GET /api/leads.
  • navigate.activity

    • attend.activity.
    • No escribe.
    • Requiere authenticated.
    • Se vincula con la cuenta y GET /api/listing-events.
  • navigate.wall

    • nav.wall.
    • No escribe.
    • Requiere authenticated.
    • Se vincula con la ruta del feed.
  • help.show

    • system.help.
    • No escribe.
    • No requiere autenticación.
    • Usa una plantilla determinista sin backend.

Esquemas cerrados

SCHEMA_KEYS enumera las llaves permitidas por objeto. Cualquier llave fuera de estas listas provoca error.

  • result — llaves superiores del resultado.
  • interpreter — llaves del bloque del intérprete.
  • input — llaves de la entrada.
  • context — llaves de contexto.
  • activeSearch — llaves de búsqueda activa.
  • visibleResults — llaves de resultados visibles.
  • locationDefaults — llaves de ubicación predeterminada.
  • currentLocation — llaves de locationDefaults.current.
  • profileLocation — llaves de locationDefaults.profile.
  • controlSelection — llaves de una selección de control.
  • paramEntry — llaves de una entrada de parámetro.
  • source — llaves permitidas según kind.
  • zone — llaves de una zona geocodificada.
  • zoneCenter — llaves del centro geográfico.
  • change — llaves de un cambio.
  • missing — llaves de un faltante.
  • ambiguity — llaves de una ambigüedad.
  • ambiguityOption — llaves de una opción ambigua.
  • unparsed — llaves de un fragmento no parseado.
  • location — llaves del estado de ubicación.
  • capability — llaves del bloque de capacidad.
  • proposedAction — llaves de la acción propuesta.
  • ordinalValue — llaves de un valor ordinal.
  • activeRequirementValue — llaves de una referencia activa.

Comportamientos

Estas funciones son el núcleo operativo del archivo. Cada una devuelve errores estructurados, no lanza excepciones de dominio.

  • validateInterpretationResult() — valida el resultado completo y devuelve { valid, errors }.
  • validateRuntimeInterpretation() — valida en ejecución y exige expectedInput.
  • resolveRefinedCriteria() — calcula los criterios resultantes tras aplicar params y changes.
  • checkDomainCombination() — verifica compatibilidad entre entidad, operación y subtipo.
  • validateParamValue() — valida un valor individual contra PARAM_SPECS.

Funciones internas relevantes:

  • validateZone() — valida zonas con centro geográfico y radio.
  • validateSource() — despacha la validación según text, context o control.
  • validateContextSource() — comprueba evidencia de contexto.
  • validateControlSource() — comprueba selecciones de controles.
  • validateChanges() — valida y resume cambios sobre criterios activos.
  • validateLocation() — aplica precedencia y coherencia de ubicación.
  • validateDomain() — enlaza intención con reglas de creación de listings.
  • validateCapability() — compara capability con ACTION_TYPES.
  • validateProposedAction() — valida la acción propuesta y sus requisitos.
  • validateRefinementScope() — restringe refinamientos persistentes.

Riesgos y casos límite

[!WARNING] input debe coincidir con la entrada original en ejecución.

validateRuntimeInterpretation() rechaza cualquier resultado que no use expectedInput.

[!CAUTION] interpreter.kind = "reference" no se admite en ejecución.

Ese valor existe para corpus de evaluación, no para producción.

[!WARNING] proposedAction.args siempre debe ser {}.

Los argumentos se derivan de params ya verificados, no se reciben libres.

[!IMPORTANT] changes solo es válido con discover.refine.

Además, change.source.kind no puede ser context cuando la operación es set.

[!WARNING] recognized exige intent, capability y una acción coherente.

También prohíbe faltantes obligatorios y ambigüedades.

[!WARNING] ambiguous_intent exige intent = null y al menos dos intentOptions distintas.

También prohíbe changes y proposedAction.

[!WARNING] unsupported requiere un fragmento unparsed con reason = OUT_OF_SCOPE_REQUEST.

No admite params, changes ni proposedAction.

[!NOTE] location.resolution = "conversation" solo se hereda en discover.refine.

Si el refinamiento toca la ubicación, esa herencia deja de aplicar.

[!NOTE] searchZones solo puede venir de place_search o de contexto previo.

El contrato rechaza cualquier procedencia textual para ese campo.

Relacionado

Este archivo se conecta con varios elementos citados explícitamente en sus comentarios y validaciones. También fija la base para el uso determinista de interpretación en Plano.

  • services/api/src/modules/interpretation/real-estate-lexicon.js — aporta LEXICON_VERSION.
  • models/Listing.js — fuente del vocabulario de entidad y operación.
  • modules/listings/listings.validators.js — fuente de reglas y catálogos reflejados aquí.
  • test/interpretation-enum-sync.test.js — verifica sincronía entre enums del contrato y el backend.
  • test/interpretation-versioning.test.js — registra la huella de versiones.
  • tools/nlp/versioning-policy.md — política de versionado citada por el módulo.
  • match/comparison-contract.js — aparece en comentarios como referencia para comparePhysicalMinimum.
  • validateListingPayload — referencia funcional usada para compatibilidad de dominio.
  • applyRequirementRefinement() — referencia funcional usada para refinamientos persistentes.
  • generateCandidatesForRequirement — referencia interna citada en la descripción de una acción planificada.

[!NOTE] El archivo no define endpoints HTTP reales.

Solo registra relaciones textuales hacia rutas y flujos ya existentes o planificados.