openapi: 3.1.0
info:
  title: Vietnam EMR Terminology Gateway
  version: 0.1.0-design
  description: >-
    Read-only runtime contract. Requests are served only from a published release.
    This service assists terminology lookup and validation; it does not diagnose or determine reimbursement.
    Runtime clients select system and published version; internal Snowstorm branch paths are never accepted here.
    Vietnamese terms are a versioned overlay and do not modify or repackage canonical SNOMED CT content as RF2.
servers:
  - url: https://terminology.example.vn/fhir
paths:
  /CodeSystem/$lookup:
    get:
      operationId: lookupCode
      summary: Resolve a code and its Vietnamese/English designations
      parameters:
        - $ref: '#/components/parameters/System'
        - $ref: '#/components/parameters/Code'
        - $ref: '#/components/parameters/Version'
        - $ref: '#/components/parameters/Language'
      responses:
        '200': {description: Lookup result, content: {application/fhir+json: {schema: {$ref: '#/components/schemas/Parameters'}}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
        '404': {description: Code or published release not found}
  /CodeSystem/$validate-code:
    get:
      operationId: validateCode
      summary: Validate code existence/status in a pinned (or latest published) release
      description: >-
        Always returns HTTP 200, even when the code does not exist, is inactive, or `system` is not
        SNOMED CT — the outcome is carried in `result` (boolean), never as an HTTP error. `version`
        defaults to the latest published release when omitted.
      parameters:
        - $ref: '#/components/parameters/System'
        - $ref: '#/components/parameters/Code'
        - $ref: '#/components/parameters/OptionalVersion'
        - $ref: '#/components/parameters/Language'
      responses:
        '200':
          description: >-
            Parameters with `result` (boolean). When true it also carries `display` and `version`;
            when false it carries `message` explaining why (not found, inactive, unsupported system).
          content: {application/fhir+json: {schema: {$ref: '#/components/schemas/Parameters'}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
  /ValueSet/$expand:
    get:
      operationId: expandValueSet
      summary: Expand an ECL expression as an implicit ValueSet, with Vietnamese display
      description: >-
        The ECL expression travels inside the implicit ValueSet URI, exactly as SNOMED CT and
        standard FHIR tooling send it:
        `http://snomed.info/sct/900000000000207008/version/20260801?fhir_vs=ecl/<< 22298006`.
        The expression is passed verbatim to the terminology server on the branch pinned to the
        published release; this service never parses ECL and never exposes an authoring branch.
        Concepts without a published Vietnamese translation are still returned, carrying the
        English term, so an EMR never sees a value set that silently lost codes;
        `expansion.parameter.translatedCount` reports how many entries in this page are translated.
        Inactive concepts are excluded so that a retired code cannot reach a patient record.
      parameters:
        - name: url
          in: query
          required: true
          description: >-
            Implicit ValueSet URI. Must start with http://snomed.info/sct and contain
            `?fhir_vs=ecl/<expression>`. If it carries `/version/<YYYYMMDD>` that version wins;
            otherwise the `version` parameter is used, and failing that the latest published release.
          schema: {type: string, format: uri}
          examples:
            allMyocardialInfarction:
              summary: A concept and all its descendants
              value: 'http://snomed.info/sct/900000000000207008/version/20260801?fhir_vs=ecl/<< 22298006'
            diseaseOfHeart:
              summary: Attribute-constrained — diseases with a finding site in the heart
              value: 'http://snomed.info/sct?fhir_vs=ecl/< 404684003 : 363698007 = << 80891009'
        - $ref: '#/components/parameters/ValueSetVersion'
        - name: filter
          in: query
          description: >-
            Free-text filter applied inside the ECL scope. Vietnamese text is matched against the
            published translation overlay first, then intersected with the ECL scope on the
            terminology server; English text is matched by the terminology server itself. This is
            what lets a mapping tool search in Vietnamese against SNOMED CT.
          schema: {type: string, maxLength: 200}
          example: 'màng tim'
        - $ref: '#/components/parameters/Language'
        - name: offset
          in: query
          schema: {type: integer, minimum: 0, default: 0}
        - name: count
          in: query
          schema: {type: integer, minimum: 1, maximum: 200, default: 50}
      responses:
        '200': {description: ValueSet expansion, content: {application/fhir+json: {schema: {$ref: '#/components/schemas/ValueSet'}}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
    post:
      operationId: expandValueSetInline
      summary: Expand a ValueSet sent inline in the request body (SNOMED CT only)
      description: >-
        For a compose too large or complex to fit in a query string. The body is a FHIR Parameters
        resource carrying one `valueSet` part with an inline ValueSet resource. Every entry in
        `compose.include` must be `system: http://snomed.info/sct`; each include carries either an
        ECL expression as a `filter` (`property: concept, op: =, value: <ECL>`) or a `concept[]`
        list of codes — mixing SNOMED with other systems, or omitting both filter and concept, is
        rejected. Reuses the same expansion engine as the published-ValueSet path, so results carry
        the same bilingual `display`/`designation` as the GET form.
      parameters:
        - $ref: '#/components/parameters/ValueSetVersion'
        - $ref: '#/components/parameters/Language'
        - name: offset
          in: query
          schema: {type: integer, minimum: 0, default: 0}
        - name: count
          in: query
          schema: {type: integer, minimum: 1, default: 50}
      requestBody:
        required: true
        content:
          application/fhir+json:
            schema: {$ref: '#/components/schemas/Parameters'}
            example:
              resourceType: Parameters
              parameter:
                - name: valueSet
                  resource:
                    resourceType: ValueSet
                    status: active
                    compose:
                      include:
                        - system: 'http://snomed.info/sct'
                          filter:
                            - {property: concept, op: '=', value: '<< 22298006'}
      responses:
        '200': {description: ValueSet expansion (no `url`, since the ValueSet was inline), content: {application/fhir+json: {schema: {$ref: '#/components/schemas/ValueSet'}}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
  /ValueSet/$validate-code:
    get:
      operationId: validateCodeInValueSet
      summary: Check whether a code falls inside an implicit ECL scope or a published official ValueSet, and is still active
      parameters:
        - name: url
          in: query
          required: true
          description: >-
            Either an implicit ValueSet URI (same shape as $expand), OR the canonical `url` of an
            official ValueSet that has a published version — in which case the code is checked
            against that ValueSet's frozen expansion snapshot instead of an ECL scope. A canonical
            url with no published version returns 404.
          schema: {type: string, format: uri}
        - $ref: '#/components/parameters/Code'
        - $ref: '#/components/parameters/OptionalSystem'
        - $ref: '#/components/parameters/OptionalVersion'
        - $ref: '#/components/parameters/ValueSetVersion'
        - $ref: '#/components/parameters/Language'
      responses:
        '200':
          description: >-
            Parameters with `result` (boolean). When true it also carries `display` in the requested
            language and `version`; when false it carries `message` explaining that the code is
            outside the scope or inactive.
          content: {application/fhir+json: {schema: {$ref: '#/components/schemas/Parameters'}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
        '404': {description: Canonical (official) ValueSet url has no published version}
  /CodeSystem:
    get:
      operationId: listCodeSystemVersions
      summary: List published SNOMED CT versions this gateway serves
      description: >-
        Returns a Bundle of CodeSystem resources, one per published release. Each `version` is a
        full SNOMED CT version URI that can be sent straight back inside an implicit ValueSet URI.
      parameters:
        - name: url
          in: query
          schema: {type: string, format: uri}
          example: 'http://snomed.info/sct'
      responses:
        '200': {description: Searchset bundle, content: {application/fhir+json: {schema: {type: object}}}}
  /ConceptMap/$translate:
    post:
      operationId: translateCode
      summary: Follow a SNOMED CT historical association to its replacement concept
      description: >-
        Body is a FHIR Parameters resource with `url` set to the implicit ConceptMap URI
        (`http://snomed.info/sct/...?fhir_cm=<refsetId>`) and `coding` set to the concept being
        translated. SNOMED CT publishes historical associations as reference sets rather than as
        stand-alone ConceptMap resources, so this reads the pinned branch's refset members.
        Use it to warn a user that a code has been retired and replaced instead of letting a dead
        code stay in a patient record.
      requestBody:
        required: true
        content:
          application/fhir+json:
            schema: {$ref: '#/components/schemas/Parameters'}
            example:
              resourceType: Parameters
              parameter:
                - name: url
                  valueUri: 'http://snomed.info/sct/900000000000207008/version/20260801?fhir_cm=900000000000530003'
                - name: coding
                  valueCoding: {system: 'http://snomed.info/sct', code: '1234567890'}
      responses:
        '200': {description: Mapping candidates and relationships, content: {application/fhir+json: {schema: {$ref: '#/components/schemas/Parameters'}}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
  /CodeSystem/$subsumes:
    get:
      operationId: subsumes
      summary: Test hierarchy relation between two SNOMED CT concepts
      description: >-
        Answers via ECL (`<< codeA` / `<< codeB`) against the pinned or latest published release —
        it does not walk the hierarchy itself, it asks the terminology server. `version` defaults
        to the latest published release when omitted.
      parameters:
        - $ref: '#/components/parameters/System'
        - $ref: '#/components/parameters/OptionalVersion'
        - name: codeA
          in: query
          required: true
          schema: {type: string}
        - name: codeB
          in: query
          required: true
          schema: {type: string}
      responses:
        '200':
          description: 'Parameters with `outcome` (`equivalent`|`subsumes`|`subsumed-by`|`not-subsumed`), `system`, and `version`.'
          content: {application/fhir+json: {schema: {$ref: '#/components/schemas/Parameters'}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
  /metadata:
    get:
      operationId: capabilityStatement
      summary: FHIR discovery document — operations served and SNOMED CT versions published
      description: >-
        Standard FHIR discovery endpoint. Lists the CodeSystem/ValueSet/ConceptMap operations this
        gateway serves and the SNOMED CT versions currently published (`snomedVersions`), so a
        client can check capability and pick a version before calling any operation.
      responses:
        '200': {description: CapabilityStatement, content: {application/fhir+json: {schema: {$ref: '#/components/schemas/CapabilityStatement'}}}}
  /search:
    get:
      operationId: searchTerms
      summary: Search terms with explicit ranking and match reason
      parameters:
        - name: q
          in: query
          required: true
          schema: {type: string, minLength: 1, maxLength: 200}
        - $ref: '#/components/parameters/System'
        - $ref: '#/components/parameters/Version'
        - $ref: '#/components/parameters/Language'
        - name: valueSetUrl
          in: query
          schema: {type: string, format: uri}
        - $ref: '#/components/parameters/ValueSetVersion'
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 100, default: 20}
      responses:
        '200':
          description: Search results; fuzzy matches are never implicit selections.
          content: {application/json: {schema: {$ref: '#/components/schemas/SearchResponse'}}}
        '400': {$ref: '#/components/responses/InvalidRequest'}
components:
  parameters:
    System: {name: system, in: query, required: true, schema: {type: string, format: uri}}
    OptionalSystem: {name: system, in: query, schema: {type: string, format: uri}}
    Code: {name: code, in: query, required: true, schema: {type: string}}
    Version: {name: version, in: query, required: true, schema: {type: string}}
    OptionalVersion: {name: version, in: query, description: Defaults to the latest published release when omitted., schema: {type: string}}
    Language: {name: displayLanguage, in: query, schema: {type: string, default: vi}}
    Filter: {name: filter, in: query, schema: {type: string, maxLength: 200}}
    ValueSetVersion: {name: valueSetVersion, in: query, schema: {type: string}}
  responses:
    InvalidRequest: {description: Missing version, invalid parameters, or non-published artifact}
  schemas:
    Parameters: {type: object, required: [resourceType], properties: {resourceType: {const: Parameters}, parameter: {type: array, items: {type: object}}}}
    ValueSet: {type: object, required: [resourceType, status], properties: {resourceType: {const: ValueSet}, url: {type: string}, status: {type: string}, compose: {type: object}, expansion: {type: object}}}
    CapabilityStatement:
      type: object
      required: [resourceType, status, fhirVersion, rest]
      properties:
        resourceType: {const: CapabilityStatement}
        status: {type: string}
        kind: {type: string}
        fhirVersion: {type: string}
        format: {type: array, items: {type: string}}
        software: {type: object}
        implementation: {type: object}
        rest: {type: array, items: {type: object}}
        instantiates: {type: array, items: {type: string, format: uri}}
        snomedVersions:
          type: array
          description: Published SNOMED CT versions this gateway currently serves.
          items: {type: object, properties: {code: {type: string}}}
    SearchResponse:
      type: object
      required: [system, version, query, displayLanguage, results]
      properties:
        system: {type: string, format: uri}
        version: {type: string}
        query: {type: string}
        displayLanguage: {type: string}
        results:
          type: array
          items:
            type: object
            required: [code, display, language, matchReason, score, translationVersion]
            properties:
              code: {type: string}
              display: {type: string}
              language: {type: string}
              translationVersion: {type: string}
              matchReason: {type: string, enum: [EXACT_PREFERRED, PREFIX_PREFERRED, SYNONYM, UNACCENTED]}
              score: {type: number}
