services/api/src/modules/match/match-response.js

Overview

Este archivo define el mapeo público de Match para candidatos internos. Su trabajo es traducir un candidato ya calculado a una metadata segura para cruzar el límite HTTP, sin exponer la estructura interna completa del motor.

La lógica del módulo es deliberadamente restrictiva. Solo publica presentationState, warningCode, matchedCriteria, differenceCriteria y pendingCriteria. No reexpone signals, blockers completos, ni valores internos como score, porcentaje o presentationPosition.

[!IMPORTANT] Este archivo no decide presentabilidad ni orden por sí mismo. Solo serializa información ya resuelta por otras partes del flujo, y reusa candidate-presentation.js como fuente de verdad para el estado y la advertencia.

How it works

flowchart TB
    Candidate[Candidato interno de Match]
    Presentation[candidate-presentation.js]
    WarningMap[resolveWarningCode]
    CriteriaMap[mapSignalsToCriteriaCodes]
    PublicMeta[toPublicMatchMetadata]
    PublicOut[Metadata publica]

    Candidate --> Presentation
    Candidate --> WarningMap
    Candidate --> CriteriaMap
    Presentation --> PublicMeta
    WarningMap --> PublicMeta
    CriteriaMap --> PublicMeta
    PublicMeta --> PublicOut

El flujo parte de un candidato interno producido por Match. Primero, toPublicMatchMetadata() obtiene el presentationState con getCandidatePresentationState(). Luego evalúa si aplica un warningCode con isApprovedBudgetWarningCandidate() y, si corresponde, lo resuelve con resolveWarningCode().

Después, el módulo traduce señales internas a códigos públicos de criterio. Lo hace con listas de outcomes permitidos y con allowlists explícitas de campos. Si un campo o un reason interno no está mapeado, el módulo lo omite o falla de forma ruidosa, según el caso.

[!NOTE] La conversión es por allowlist, no por redacción sustractiva. El módulo construye cada campo público de forma explícita.

Key components

{
    "title": "resolveWarningCode",
    "content": "Funci\u00f3n interna que traduce el reason interno de un blocker de price a un c\u00f3digo p\u00fablico de warning. Solo se usa cuando el candidato ya est\u00e1 confirmado como budget_warning.",
    "type": "function",
    "filePath": "services/api/src/modules/match/match-response.js",
    "badges": []
}
{
    "title": "mapSignalsToCriteriaCodes",
    "content": "Funci\u00f3n interna que filtra signals por outcomes permitidos y traduce sus fields a c\u00f3digos p\u00fablicos de criterio. Ignora outcomes y fields no permitidos.",
    "type": "function",
    "filePath": "services/api/src/modules/match/match-response.js",
    "badges": []
}
{
    "title": "toPublicMatchMetadata",
    "content": "Funci\u00f3n p\u00fablica que convierte un candidato interno en la metadata p\u00fablica de Match. Devuelve el estado de presentaci\u00f3n, el warningCode y las listas p\u00fablicas de criterios.",
    "type": "function",
    "filePath": "services/api/src/modules/match/match-response.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "candidate",
        "type": "object",
        "required": true,
        "description": "Candidato interno de Match ya generado por evaluateCandidatePair() y generateCandidatesForRequirement().",
        "children": [
            {
                "name": "signals",
                "type": "array<object>",
                "description": "Se usa para construir matchedCriteria y pendingCriteria."
            },
            {
                "name": "blockers",
                "type": "array<object>",
                "description": "Se usa para construir differenceCriteria y warningCode cuando el estado lo permite.",
                "children": [
                    {
                        "name": "field",
                        "type": "string",
                        "description": "Nombre interno del campo bloqueante."
                    },
                    {
                        "name": "outcome",
                        "type": "string",
                        "description": "Outcome interno del blocker."
                    },
                    {
                        "name": "reason",
                        "type": "string",
                        "description": "Reason interno usado solo para el warning de presupuesto."
                    }
                ]
            }
        ]
    },
    {
        "name": "presentationState",
        "type": "string",
        "required": true,
        "description": "Estado público devuelto por getCandidatePresentationState()."
    },
    {
        "name": "warningCode",
        "type": "string|null",
        "description": "Código público de warning. Solo existe para candidatos aprobados como budget_warning."
    },
    {
        "name": "matchedCriteria",
        "type": "array<object>",
        "required": true,
        "description": "Criterios públicos construidos a partir de signals con outcome pass.",
        "children": [
            {
                "name": "code",
                "type": "string",
                "required": true,
                "description": "Código público del criterio."
            }
        ]
    },
    {
        "name": "differenceCriteria",
        "type": "array<object>",
        "required": true,
        "description": "Criterios públicos que explican una diferencia conocida y aprobada. Solo aplica a budget_warning y alternative.",
        "children": [
            {
                "name": "code",
                "type": "string",
                "required": true,
                "description": "Código público del criterio diferencial."
            }
        ]
    },
    {
        "name": "pendingCriteria",
        "type": "array<object>",
        "required": true,
        "description": "Criterios públicos construidos a partir de signals con outcome pending_unknown o pending_gate.",
        "children": [
            {
                "name": "code",
                "type": "string",
                "required": true,
                "description": "Código público del criterio pendiente."
            }
        ]
    }
]

Códigos de warning

El módulo traduce reason internos a códigos públicos mediante una allowlist fija. Solo reconoce dos razones internas para budget_warning.

  • ABOVE_BUDGET_MAX → BUDGET_ABOVE_DECLARED_MAX
  • BELOW_BUDGET_MIN → BUDGET_BELOW_DECLARED_MIN

Códigos de criterio

El módulo también traduce field internos a códigos públicos de criterio.

  • propertyType → PROPERTY_TYPE
  • price → PRICE
  • term → RENTAL_TERM
  • location → GEOGRAPHY
  • bedrooms → BEDROOMS
  • bathrooms → BATHROOMS
  • halfBathrooms → HALF_BATHROOMS
  • parkingSpots → PARKING_SPOTS
  • constructionArea → CONSTRUCTION_AREA
  • landArea → LAND_AREA
  • frontage → FRONTAGE
  • depth → DEPTH
  • water → WATER_INFRASTRUCTURE
  • electricity → ELECTRICITY_INFRASTRUCTURE
  • drainage → DRAINAGE_INFRASTRUCTURE

Outcomes admitidos

  • pass → entra en matchedCriteria
  • pending_unknown → entra en pendingCriteria
  • pending_gate → entra en pendingCriteria

Estados con diferencia conocida

  • budget_warning
  • alternative

El archivo documenta, pero no expone, varios datos internos de Match.

  • signals crudos no se publican.
  • blockers crudos no se publican.
  • pendingRequired no se publica.
  • pendingGates no se publica.
  • unknowns no se publica.
  • notApplicable no se publica.
  • evidence no se publica.
  • requirementValue no se publica.
  • offerValue no se publica.
  • hardFail no se publica.
  • contributesToRanking no se publica.
  • score no se publica.
  • porcentaje no se publica.
  • presentationPosition no se publica.

Behaviors

  • resolveWarningCode() — Busca el blocker de price, lee su reason interno y devuelve el código público correspondiente.
  • mapSignalsToCriteriaCodes() — Recorre una lista de signals, filtra por outcomes permitidos y devuelve códigos públicos de criterio.
  • toPublicMatchMetadata() — Construye el objeto público final con estado, warning y listas de criterios.

Workflows

  • El candidato llega desde la lógica interna de Match.
  • getCandidatePresentationState() determina el presentationState.
  • isApprovedBudgetWarningCandidate() valida si existe un warning público.
  • resolveWarningCode() traduce el reason interno de presupuesto, si aplica.
  • mapSignalsToCriteriaCodes() convierte señales internas en criterios públicos.
  • toPublicMatchMetadata() devuelve la metadata pública final.

[!WARNING] resolveWarningCode() lanza un error si aparece un reason no mapeado. El módulo no inventa un código público nuevo.

[!IMPORTANT] toPublicMatchMetadata() no filtra candidatos hidden. El filtrado previo pertenece al caller, tal como indica el código.

Gotchas & edge cases

[!WARNING] Si candidate.blockers contiene un reason desconocido para budget_warning, el módulo falla con un Error. Esto evita exponer un reason interno sin contrato público.

[!NOTE] differenceCriteria solo se calcula para budget_warning y alternative. En otros estados, devuelve una lista vacía.

[!NOTE] El criterio amenities queda fuera del mapeo público. El archivo asume que su resultado no produce pass ni pending_*.

[!CAUTION] mapSignalsToCriteriaCodes() ignora cualquier field no incluido en la allowlist. El campo no se publica, aunque exista dentro del candidato interno.

Este módulo depende directamente de ./candidate-presentation.js. Allí se define la fuente de verdad para getCandidatePresentationState() e isApprovedBudgetWarningCandidate(). El comentario del archivo también lo conecta con evaluateCandidatePair() y generateCandidatesForRequirement(), que producen el candidato interno que luego se traduce aquí.

También está relacionado con la capa de comparación y generación de Match, porque los campos públicos salen de los signals que esas piezas producen. En la superficie pública, este archivo actúa como frontera de serialización entre el motor interno y la respuesta HTTP.