medizininformatik-initiative / fdpg-plus

2 stars 0 forks source link

Improved Ontology Search #11

Open juliangruendner opened 8 months ago

juliangruendner commented 8 months ago

Overview

Searching through the ontology shall become more convenient. This requires changes to the frontend and the backend as well. The frontend part (except the API to it) is not the scope of this issue, however, the input of the frontend team is much appreciated (as well as from others).

For the backend, the plan is to add an instance of elastic search that will be populated with data generated by the fhir ontology generator and queried by the backend application.

This issue is meant to collect and discuss ideas first.

Current State

The documents below are the place to track changes for this. If it is not reflected here - it is not happening ;) (for now)

Click to expand OpenAPI (copy to https://editor-next.swagger.io/ for your convenience) ```yml openapi: 3.0.3 info: title: MII Feasibility Backend REST API - Terminology Search Draft description: todo contact: email: noreply@todo.de license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend servers: - url: https://to.be.defined variables: basePath: default: / tags: - name: terminology description: operations on the terminology - name: codeable concepts description: operations on codeable concepts paths: /terminology/search/filter: get: tags: - terminology summary: "Get the list of available filters" operationId: "getFilters" responses: 200: description: Ok, return the list of available filters content: application/json: schema: type: "array" items: $ref: "#/components/schemas/Filter" /terminology/entry/search: get: tags: - terminology summary: "Parametrized search in the configured elastic search service." operationId: "search" parameters: - name: searchterm in: query description: The term to search for. In case of an empty searchterm, return the first page of the whole index, sorted alphabetically schema: type: "string" example: Diabetes Mellitus - name: contexts in: query description: Limit the search to any of the given contexts schema: type: array items: type: string example: Diagnosis style: form explode: false - name: terminologies in: query description: Limit the search to a any of the given terminologies schema: type: array items: type: string example: ICD10 style: form explode: false - name: kds-modules in: query description: Limit the search to any of the given KDS modules schema: type: array items: type: string example: Condition style: form explode: false - name: criteria-sets in: query description: Limit the search to certain criteria sets (specified via url) schema: type: array items: type: string example: http://fhir.de/CodeSystem/bfarm/icd-10-gm style: form explode: false - name: availability in: query description: Limit the search to available items? (Maybe better invert this one to make this the default?) schema: type: boolean example: true - name: page-size in: query description: How many results shall be returned per page schema: type: integer example: 10 - name: page in: query description: Which page shall be returned schema: type: integer example: 42 responses: 200: description: Ok, return the list of results for the search content: application/json: schema: $ref: "#/components/schemas/ElasticSearchResult" /terminology/entry/{id}/relations: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" operationId: "getEntryById" parameters: - name: id in: path description: The id (contextualized termcode hash) of the entry that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntryWithRelations' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found /terminology/entry/{id}: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" description: "This should be in the getEntryById call, but for better distinction between the return values it is listed seperately here" operationId: "getEntriesById" parameters: - name: id in: path description: The ids (contextualized termcode hash) of the entries that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntry' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found /terminology/criteria-profile-data: get: tags: - terminology summary: "Get the profile data for criteria, containing uiProfile context and termCodes" description: "This should return all the information needed to build criteria in the frontend." operationId: "getEntriesByIdsWithDetail" parameters: - name: ids in: query style: form explode: false description: "A comma-separated list of IDs (contextualized termcode hashes) of the entries that shall be retrieved." required: true schema: type: string example: "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8,203e04cd-4f0a-321b-b1ad-9ec6d211e0a9" responses: 200: description: "Entries found. May contain empty entries if some were not found." content: application/json: schema: type: array items: $ref: '#/components/schemas/CriteriaProfileData' 401: description: "Unauthorized - please login first." 403: description: "Forbidden - insufficient access rights." /codeable-concept/entry/search: get: tags: - codeable concepts summary: Search for codeable concepts by code or display. Optionally filter by value sets operationId: searchCodeableConcepts parameters: - name: searchterm in: query description: The term to search for. In case of an empty searchterm, return the first page of the whole index, sorted alphabetically schema: type: "string" example: Diabetes Mellitus - name: value-sets in: query description: Limit the search to certain value sets (specified via url) schema: type: array items: type: string example: http://fhir.de/CodeSystem/bfarm/icd-10-gm style: form explode: false - name: page-size in: query description: How many results shall be returned per page schema: type: integer example: 10 - name: page in: query description: Which page shall be returned schema: type: integer example: 42 responses: 200: description: Ok, return the page of results content: application/json: schema: $ref: "#/components/schemas/CodeableConceptSearchResult" 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights /codeable-concept/entry/{id}: get: tags: - codeable concepts summary: Search for codeable concepts by code or display. Optionally filter by value sets operationId: getCodeableConceptByCode parameters: - name: id in: path description: The id (contextualized termcode hash) of the entry that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Ok, return the codeable concept content: application/json: schema: $ref: "#/components/schemas/TermCode" 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found components: schemas: CodeableConceptSearchResult: type: object properties: totalHits: type: integer example: 42 results: type: array items: $ref: "#/components/schemas/TermCode" ElasticSearchResult: type: object properties: totalHits: type: integer example: 42 results: type: array items: $ref: "#/components/schemas/ElasticSearchResultEntry" ElasticSearchResultEntryWithRelations: type: object properties: translations: type: array items: $ref: "#/components/schemas/ElasticSearchTranslationEntry" parents: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" children: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" relatedTerms: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" ElasticSearchResultEntry: type: object properties: name: type: string example: Diabetes Mellitus id: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 example: 119578 context: type: string example: Diagnosis terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition selectable: type: boolean example: true ElasticSearchTranslationEntry: type: object properties: lang: type: string example: en value: type: string example: Diabetes Mellitus ElasticSearchRelationEntry: type: object properties: name: type: string example: Endokrine, Ernährungs- und Stoffwechselerkrankungen contextualizedTermcodeHash: type: string example: c55d0d62-6c47-30b0-94b2-afa383ce35f7 CriteriaProfileData: type: object properties: id: type: string format: uuid uiprofile: type: array items: $ref: "#/components/schemas/UiProfileEntry" termCodes: type: array items: $ref: "#/components/schemas/TermCode" context: $ref: "#/components/schemas/TermCode" Filter: type: object properties: name: type: string example: terminology type: type: string example: selectable-concept values: type: array items: $ref: "#/components/schemas/FilterValue" FilterValue: type: object properties: label: type: string example: http://fhir.de/CodeSystem/bfarm/ops count: type: integer example: 42 TermCode: type: object properties: code: type: string example: "E14.0" system: type: string example: "http://fhir.de/CodeSystem/bfarm/icd-10-gm" version: type: string example: "2023" display: type: string example: "Diabetes mellitus" UiProfileEntry: type: object properties: attributeDefinitions: type: array items: $ref: "#/components/schemas/AttributeDefinition" name: type: string example: "Diagnose" timeRestrictionAllowed: type: boolean example: false valueDefinition: type: string Unit: type: object properties: code: type: string display: type: string AttributeDefinition: type: object properties: allowedUnits: type: array items: $ref: "#/components/schemas/Unit" attributeCode: $ref: "#/components/schemas/TermCode" max: type: number example: null min: type: number example: null optional: type: boolean example: true precision: type: number example: 1 referencedCriteriaSet: type: string example: "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm" referencedValueSet: type: string example: "http://hl7.org/fhir/sid/icd-o-3" type: type: string example: "reference" ```
Click to expand Elastic Search index for ontology ```json { "settings": { "analysis": { "tokenizer": { "edge_ngram_tokenizer": { "type": "edge_ngram", "min_gram": 1, "max_gram": 20, "token_chars": [ "letter", "digit" ] } }, "analyzer": { "edge_ngram_analyzer": { "type": "custom", "tokenizer": "edge_ngram_tokenizer", "filter": [ "lowercase" ] }, "lowercase_analyzer": { "type": "custom", "tokenizer": "standard", "filter": [ "lowercase" ] } } } }, "mappings": { "properties": { "availability": { "type": "long", "index": false }, "children": { "properties": { "contextualized_termcode_hash": { "type": "text", "index": false }, "name": { "type": "text", "index": false } } }, "context": { "properties": { "code": { "type": "keyword" }, "display": { "type": "keyword" }, "system": { "type": "keyword" }, "version": { "type": "keyword" } } }, "criteria_sets": { "type": "keyword" }, "kds_module": { "type": "keyword" }, "name": { "type": "text", "analyzer": "edge_ngram_analyzer", "search_analyzer": "lowercase_analyzer" }, "parents": { "properties": { "contextualized_termcode_hash": { "type": "text", "index": false }, "name": { "type": "text", "index": false } } }, "related_terms": { "properties": { "contextualized_termcode_hash": { "type": "text", "index": false }, "name": { "type": "text", "index": false } } }, "selectable": { "type": "boolean" }, "termcode": { "type": "text", "analyzer": "edge_ngram_analyzer", "search_analyzer": "lowercase_analyzer" }, "termcodes": { "properties": { "code": { "type": "text", "index": false }, "display": { "type": "text", "index": false }, "system": { "type": "text", "index": false }, "version": { "type": "text", "index": false } } }, "terminology": { "type": "keyword" }, "translations": { "properties": { "lang": { "type": "text", "index": false }, "value": { "type": "text", "index": false } } } } } } ```
Click to expand Elastic Search index for codeable concepts ```json { "settings": { "analysis": { "tokenizer": { "edge_ngram_tokenizer": { "type": "edge_ngram", "min_gram": 1, "max_gram": 20, "token_chars": [ "letter", "digit" ] } }, "analyzer": { "edge_ngram_analyzer": { "type": "custom", "tokenizer": "edge_ngram_tokenizer", "filter": [ "lowercase" ] }, "lowercase_analyzer": { "type": "custom", "tokenizer": "standard", "filter": [ "lowercase" ] } } } }, "mappings": { "properties": { "termcode": { "properties": { "code": { "type": "text", "analyzer": "edge_ngram_analyzer", "search_analyzer": "lowercase_analyzer" }, "display": { "type": "text", "analyzer": "edge_ngram_analyzer", "search_analyzer": "lowercase_analyzer" }, "system": { "type": "text", "index": false }, "version": { "type": "long", "index": false } } }, "value_sets": { "type": "keyword" } } } } ```
michael-82 commented 7 months ago

The first 3-ish ideas for json objects to store in elastic (which would have to be generated by the ontology generator or by another tool in cooperation with the ontology generator for that matter) are as follows.

Please consider this as some sort of "mix and match" - I didn't want to include each and every permutation of the options.

The "contextualized-termcode-hash" in each of those examples would be removed from the json and used as id in elastic search (or maybe it can also stay in the json, but I don't see any benefit from it)

Option 1

This is pretty verbose and contains the full information needed to a) display and b) load any relatives. Availability is a numeric value (either absolute or relative?) - not sure if this is a) possible b) allowed c) desired

{
  "name": "Diabetes Mellitus",
  "contextualized-termcode-hash": "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8",
  "availability": "96",
  "domain": "Diagnosis",
  "terminology": "icd-10",
  "termcode": "E10-E14",
  "kds-module": "Condition",
  "translations" : [
    {
      "lang": "de",
      "value": "Diabetes Mellitus"
    },
    {
      "lang": "en-UK",
      "value": "Diabetes Mellitus"
    }
  ],
  "parents" : [
    {
      "name": "Endokrine, Ernährungs- und Stoffwechselerkrankungen",
      "contextualized-termcode-hash": "c55d0d62-6c47-30b0-94b2-afa383ce35f7"
    }
  ],
  "children": [
    {
      "name": "Diabetes Mellitus, Typ 1",
      "contextualized-termcode-hash": "b303cc1c-0776-3d78-9f18-98847173a73d"
    },
    {
      "name": "Diabetes Mellitus, Typ 2",
      "contextualized-termcode-hash": "29451c0c-e26d-3d43-9b74-6e43230ac9f5"
    }
  ]
}

Option 2

This limits the information about the relatives to their ids. It also groups the terminology parts to an object (good ideas for a name are welcome). The translations part is also a bit condensed, but contain the information about which of those are verified. Availability is restricted to a boolean

{
  "name": "Diabetes Mellitus",
  "contextualized-termcode-hash": "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8",
  "availability": true,
  "to-be-named": {
    "domain": "Diagnosis",
    "terminology": "icd-10",
    "termcode": "E10-E14",
    "kds-module": "Condition"
  },
  "translations" : [
    {
      "de": "Diabetes Mellitus",
      "verified": false
    },
    {
      "en-UK": "Diabetes Mellitus",
      "verified": true
    }
  ],
  "parents" : [
      "c55d0d62-6c47-30b0-94b2-afa383ce35f7"
  ],
  "children": [
      "b303cc1c-0776-3d78-9f18-98847173a73d",
      "29451c0c-e26d-3d43-9b74-6e43230ac9f5"
  ]
}

Option 3

This removes all information about relatives. This information would have to be gathered from the backend and injected before sending back to the gui (or who/whatever uses the rest api). The language contains information about a preferred translation (not really sure if this should be in elastic search). The "name" part is lacking, because it can be retrieved from the translations array and it would be up to the gui which one to display (based on browser language or user setting?)

{
  "contextualized-termcode-hash": "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8",
  "availability": true,
  "to-be-named": {
    "domain": "Diagnosis",
    "terminology": "icd-10",
    "termcode": "E10-E14",
    "kds-module": "Condition"
  },
  "translations" : [
    {
      "lang": "de",
      "value": "Diabetes Mellitus",
      "verified": true,
      "preferred": true
    },
    {
      "lang": "en-UK",
      "value": "Diabetes Mellitus",
      "verified": false
    }
  ]
}
michael-82 commented 7 months ago

As a proposal for the API, ideally just paste the following to https://editor-next.swagger.io . This also is one example, which would be mutable, depending on what we choose to store in elastic.

There is once more a placeholder for parameters we might want to add, like limiting to a certain amount of results, or giving an offset to enable pagination, or maybe a minimum required score for a result to be included.

It would also be an option to use a json request body for the query instead of query parameters.

Click to expand OpenAPI code ```yaml openapi: 3.0.3 info: title: MII Feasibility Backend REST API - Terminology Search Draft description: todo contact: email: noreply@todo.de license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend servers: - url: https://to.be.defined variables: basePath: default: / tags: - name: terminology description: operations on the terminology paths: /terminology/search: get: tags: - terminology summary: "Parametrized search in the configured elastic search service." operationId: "search" parameters: - name: query in: query description: The term to search for schema: type: "string" example: Diabetes Mellitus - name: placeholder in: query description: Maybe include limit/offset/min-score? schema: type: "string" example: example - name: domain in: query description: Limit the search to a given domain (or domains?) schema: type: "string" example: Diagnosis - name: terminology in: query description: Limit the search to a given terminology (or terminologies?) schema: type: "string" example: ICD10 - name: kds in: query description: Limit the search to a given KDS module (or modules?) schema: type: "string" example: Condition - name: availability in: query description: Limit the search to available items? (Maybe better invert this one to make this the default?) schema: type: boolean example: true responses: 200: description: Ok, return the list of results (maybe limited/offset - to be decided) for the search content: application/json: schema: type: "array" items: $ref: "#/components/schemas/ElasticSearchResult" components: schemas: ElasticSearchResult: type: object properties: totalHits: type: integer example: 42 results: type: array items: $ref: "#/components/schemas/ElasticSearchResultEntry" ElasticSearchResultEntry: type: object properties: name: type: string example: Diabetes Mellitus contextualizedTermcodeHash: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 maximum: 100 example: 94 domain: type: string example: Diagnosis terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition translations: type: array items: $ref: "#/components/schemas/ElasticSearchTranslationEntry" parents: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" children: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" ElasticSearchTranslationEntry: type: object properties: lang: type: string example: en value: type: string example: Diabetes Mellitus ElasticSearchRelationEntry: type: object properties: name: type: string example: Endokrine, Ernährungs- und Stoffwechselerkrankungen contextualizedTermcodeHash: type: string example: c55d0d62-6c47-30b0-94b2-afa383ce35f7 ```
michael-82 commented 4 months ago

Following a discussion between @juliangruendner @Shayan1375 @thkoehler11 and myself from today (2024-05-15), the following has been decided:

Elasticsearch

Data Storage

Please note that the object below is NOT necessarily what will be returned to the frontend. For that part, look below in the OpenAPI code.

Objects will be stored in elastic search as suggested in option 1 above, with the minor change that domain will be renamed to context, resulting in:

Click to expand JSON ```json { "name": "Diabetes Mellitus", "contextualized-termcode-hash": "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8", "availability": "96", "context": "Diagnosis", "terminology": "icd-10", "termcode": "E10-E14", "kds-module": "Condition", "translations" : [ { "lang": "de", "value": "Diabetes Mellitus" }, { "lang": "en-UK", "value": "Diabetes Mellitus" } ], "parents" : [ { "name": "Endokrine, Ernährungs- und Stoffwechselerkrankungen", "contextualized-termcode-hash": "c55d0d62-6c47-30b0-94b2-afa383ce35f7" } ], "children": [ { "name": "Diabetes Mellitus, Typ 1", "contextualized-termcode-hash": "b303cc1c-0776-3d78-9f18-98847173a73d" }, { "name": "Diabetes Mellitus, Typ 2", "contextualized-termcode-hash": "29451c0c-e26d-3d43-9b74-6e43230ac9f5" } ], "relatedTerms": [ { "name": "foo", "contextualized-termcode-hash": "81e98694-bb61-4e1f-844e-399806b67b08" }, { "name": "bar", "contextualized-termcode-hash": "644de523-636a-4afe-9ade-997db8487f81" } ] } ```

For now, availability will be stored as an absolute numeric value. There is some optimism that this will be allowed to display, since it will have no site-correlation as it is only an aggregated number over all sites (however, there is always a chance that it will have to be removed later)

Search behaviour

For now, a search query will only check the fields termcode and name. In further iterations, the translations might be included (maybe prioritized by accept-language priority?)

API frontend <-> backend

The API will be modified from the former suggestion, following a refined proposal for the frontend.

TL;DR:

Possible additions to do later:

OpenAPI

With the aforementioned changes...just copy the following to https://editor-next.swagger.io/

Click to expand OpenAPI code ```yaml openapi: 3.0.3 info: title: MII Feasibility Backend REST API - Terminology Search Draft description: todo contact: email: noreply@todo.de license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend servers: - url: https://to.be.defined variables: basePath: default: / tags: - name: terminology description: operations on the terminology paths: /terminology/search/filter: get: tags: - terminology summary: "Get the list of available filters" operationId: "getFilters" responses: 200: description: Ok, return the list of available filters content: application/json: schema: type: "array" items: $ref: "#/components/schemas/Filter" /terminology/search: get: tags: - terminology summary: "Parametrized search in the configured elastic search service." operationId: "search" parameters: - name: searchterm in: query description: The term to search for schema: type: "string" example: Diabetes Mellitus - name: context in: query description: Limit the search to a given context (or contexts?) schema: type: "string" example: Diagnosis - name: terminology in: query description: Limit the search to a given terminology (or terminologies?) schema: type: "string" example: ICD10 - name: kds in: query description: Limit the search to a given KDS module (or modules?) schema: type: "string" example: Condition - name: availability in: query description: Limit the search to available items? (Maybe better invert this one to make this the default?) schema: type: boolean example: true - name: limit in: query description: How many results shall be returned schema: type: integer example: 10 - name: offset in: query description: What should be the index of the first item to be displayed schema: type: integer example: 100 responses: 200: description: Ok, return the list of results (maybe limited/offset - to be decided) for the search content: application/json: schema: $ref: "#/components/schemas/ElasticSearchResult" /terminology/search/{id}: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" operationId: "getEntryById" parameters: - name: id in: path description: The id (contextualized termcode hash) of the entry that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntryWithRelations' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found components: schemas: ElasticSearchResult: type: object properties: totalHits: type: integer example: 42 results: type: array items: $ref: "#/components/schemas/ElasticSearchResultEntry" ElasticSearchResultEntryWithRelations: type: object properties: name: type: string example: Diabetes Mellitus contextualizedTermcodeHash: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 maximum: 100 example: 94 context: type: string example: Diagnosis terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition translations: type: array items: $ref: "#/components/schemas/ElasticSearchTranslationEntry" parents: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" children: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" relatedTerms: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" ElasticSearchResultEntry: type: object properties: name: type: string example: Diabetes Mellitus contextualizedTermcodeHash: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 example: 119578 context: type: string example: Diagnosis terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition ElasticSearchTranslationEntry: type: object properties: lang: type: string example: en value: type: string example: Diabetes Mellitus ElasticSearchRelationEntry: type: object properties: name: type: string example: Endokrine, Ernährungs- und Stoffwechselerkrankungen contextualizedTermcodeHash: type: string example: c55d0d62-6c47-30b0-94b2-afa383ce35f7 Filter: type: object properties: name: type: string example: Terminology values: type: array items: $ref: "#/components/schemas/FilterValue" example: - ICD10 - SNOMED - LOINC FilterValue: type: string example: "icd10" ```
michael-82 commented 4 months ago

Following another discussion on 2024-05-29, we decided to combine some of the former endpoints in order to reduce the amount of calls. The legacy calls to the backend to get entries from the ui tree files will be removed. The ui profiles will be included in the regular call to retrieve an entry. This means an additional call to the database when retrieving an entry, but is more efficient than having two seperate rest calls for that. Other notable changes are the addition of the selectable attribute and the addition of the complete context and term codes. This is all reflected in the swagger file below. The stored objects in elastic search will also change accordingly.

Click to expand JSON ```json { "name": "Diabetes Mellitus", "contextualized-termcode-hash": "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8", "availability": 96, "terminology": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "termCode": "E10-E14", "kds-module": "Condition", "selectable": true, "context": { "code": "Diagnose", "display": "Diagnose", "system": "fdpg.mii.cds", "version": "1.0.0" }, "termCodes": [ { "code": "E10-E14", "display": "Diabetes mellitus", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023" } ], "translations" : [ { "lang": "de", "value": "Diabetes Mellitus" }, { "lang": "en-UK", "value": "Diabetes Mellitus" } ], "parents" : [ { "name": "Endokrine, Ernährungs- und Stoffwechselerkrankungen", "contextualized-termcode-hash": "c55d0d62-6c47-30b0-94b2-afa383ce35f7" } ], "children": [ { "name": "Diabetes Mellitus, Typ 1", "contextualized-termcode-hash": "b303cc1c-0776-3d78-9f18-98847173a73d" }, { "name": "Diabetes Mellitus, Typ 2", "contextualized-termcode-hash": "29451c0c-e26d-3d43-9b74-6e43230ac9f5" } ], "relatedTerms": [ { "name": "foo", "contextualized-termcode-hash": "81e98694-bb61-4e1f-844e-399806b67b08" }, { "name": "bar", "contextualized-termcode-hash": "644de523-636a-4afe-9ade-997db8487f81" } ] } ```
Click to expand OpenAPI ```yml openapi: 3.0.3 info: title: MII Feasibility Backend REST API - Terminology Search Draft description: todo contact: email: noreply@todo.de license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend servers: - url: https://to.be.defined variables: basePath: default: / tags: - name: terminology description: operations on the terminology paths: /terminology/search/filter: get: tags: - terminology summary: "Get the list of available filters" operationId: "getFilters" responses: 200: description: Ok, return the list of available filters content: application/json: schema: type: "array" items: $ref: "#/components/schemas/Filter" /terminology/search: get: tags: - terminology summary: "Parametrized search in the configured elastic search service." operationId: "search" parameters: - name: searchterm in: query description: The term to search for schema: type: "string" example: Diabetes Mellitus - name: context in: query description: Limit the search to a given context (or contexts?) schema: type: "string" example: Diagnosis - name: terminology in: query description: Limit the search to a given terminology (or terminologies?) schema: type: "string" example: ICD10 - name: kds in: query description: Limit the search to a given KDS module (or modules?) schema: type: "string" example: Condition - name: availability in: query description: Limit the search to available items? (Maybe better invert this one to make this the default?) schema: type: boolean example: true - name: pageSize in: query description: How many results shall be returned per page schema: type: integer example: 10 - name: page in: query description: Which page shall be returned schema: type: integer example: 42 responses: 200: description: Ok, return the list of results (maybe limited/offset - to be decided) for the search content: application/json: schema: type: "array" items: $ref: "#/components/schemas/ElasticSearchResult" /terminology/entry/{id}: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" operationId: "getEntryById" parameters: - name: id in: path description: The id (contextualized termcode hash) of the entry that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntry' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found /terminology/entry/{id}?detail=true: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" description: "This should be in the getEntryById call, but for better distinction between the return values it is listed seperately here" operationId: "getEntryByIdWithDetail" parameters: - name: id in: path description: The id (contextualized termcode hash) of the entry that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntryWithRelations' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found components: schemas: ElasticSearchResult: type: object properties: totalHits: type: integer example: 42 results: type: array items: $ref: "#/components/schemas/ElasticSearchResultEntry" ElasticSearchResultEntryWithRelations: type: object properties: name: type: string example: Diabetes Mellitus id: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 maximum: 100 example: 94 terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition selectable: type: boolean example: false context: $ref: "#/components/schemas/TermCode" termCodes: type: array items: $ref: "#components/schemas/TermCode" translations: type: array items: $ref: "#/components/schemas/ElasticSearchTranslationEntry" parents: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" children: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" relatedTerms: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" uiprofile: $ref: "#/components/schemas/UiProfileEntry" ElasticSearchResultEntry: type: object properties: name: type: string example: Diabetes Mellitus id: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 example: 119578 context: type: string example: Diagnosis terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition selectable: type: boolean example: true ElasticSearchTranslationEntry: type: object properties: lang: type: string example: en value: type: string example: Diabetes Mellitus ElasticSearchRelationEntry: type: object properties: name: type: string example: Endokrine, Ernährungs- und Stoffwechselerkrankungen contextualizedTermcodeHash: type: string example: c55d0d62-6c47-30b0-94b2-afa383ce35f7 Filter: type: object properties: name: type: string example: Terminology values: type: array items: $ref: "#/components/schemas/FilterValue" example: - ICD10 - SNOMED - LOINC FilterValue: type: string example: "icd10" TermCode: type: object properties: code: type: string example: "E14.0" system: type: string example: "http://fhir.de/CodeSystem/bfarm/icd-10-gm" version: type: string example: "2023" display: type: string example: "Diabetes mellitus" UiProfileEntry: type: object properties: attributeDefinitions: type: array items: $ref: "#/components/schemas/AttributeDefinition" name: type: string example: "Diagnose" timeRestrictionAllowed: type: boolean example: false valueDefinition: type: string Unit: type: object properties: code: type: string display: type: string AttributeDefinition: type: object properties: allowedUnits: type: array items: $ref: "#/components/schemas/Unit" attributeCode: $ref: "#/components/schemas/TermCode" max: type: number example: null min: type: number example: null optional: type: boolean example: true precision: type: number example: 1 referenceCriteriaSet: type: string example: "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm" selectableConcepts: type: array items: $ref: "#/components/schemas/TermCode" type: type: string example: "reference" ```
Shayan1375 commented 3 months ago

Additional Changes: New GET Endpoint for UI Profiles

After further discussion, we have decided to make additional changes. We will introduce a new GET endpoint: GET /terminology/ui-profile?id=afd0b036-625a-3aa8-b639-9dc8c8fff0ff,9c45c2f1-1761-3daa-ad31-1ff8703ae846

Details:

Example Response: The response of the GET request is a list of objects containing an ID and the corresponding UI profile. This response is also just an example:

Click to expand JSON ```json { [ { "id": "afd0b036-625a-3aa8-b639-9dc8c8fff0ff", "uiProfile": { "attributeDefinitions": [ { "allowedUnits": [ { "code": "string", "display": "string" } ], "attributeCode": { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" }, "max": null, "min": null, "optional": true, "precision": 1, "referenceCriteriaSet": "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm", "selectableConcepts": [ { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" } ], "type": "reference" } ], "name": "Diagnose", "timeRestrictionAllowed": false, "valueDefinition": "string" } }, { "id": "9c45c2f1-1761-3daa-ad31-1ff8703ae846", "uiProfile": { "attributeDefinitions": [ { "allowedUnits": [ { "code": "string", "display": "string" } ], "attributeCode": { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" }, "max": null, "min": null, "optional": true, "precision": 1, "referenceCriteriaSet": "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm", "selectableConcepts": [ { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" } ], "type": "reference" } ], "name": "Diagnose", "timeRestrictionAllowed": false, "valueDefinition": "string" } } ] } ```

This change means that when retrieving data from http://localhost:8090/api/v3/terminology/entries/5dfe270a-d520-1de8-24eb-19452f270561?detail=true, a UI profile will no longer be required in the response.

Click to expand JSON ```json { "id": "5dfe270a-d520-1de8-24eb-19452f270561", "name": "Angeborene Torsion des Ovars", "availability": 2915, "terminology": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "termCode": "Q50.2", "kdsModule": null, "translations": [], "parents": [ { "name": "Angeborene Fehlbildungen der Ovarien, der Tubae uterinae und der Ligg. lata uteri", "id": "8b4035b0-a6d1-dadf-a423-d4af8383ae0b" } ], "children": [], "relatedTerms": [], } ```
michael-82 commented 3 months ago

The response containing the list of ui profiles will be the array itself, not a json object containing the array as in the JSON posted above.

Click to expand JSON ```json [ { "id": "afd0b036-625a-3aa8-b639-9dc8c8fff0ff", "uiProfile": { "attributeDefinitions": [ { "allowedUnits": [ { "code": "string", "display": "string" } ], "attributeCode": { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" }, "max": null, "min": null, "optional": true, "precision": 1, "referenceCriteriaSet": "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm", "selectableConcepts": [ { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" } ], "type": "reference" } ], "name": "Diagnose", "timeRestrictionAllowed": false, "valueDefinition": "string" } }, { "id": "9c45c2f1-1761-3daa-ad31-1ff8703ae846", "uiProfile": { "attributeDefinitions": [ { "allowedUnits": [ { "code": "string", "display": "string" } ], "attributeCode": { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" }, "max": null, "min": null, "optional": true, "precision": 1, "referenceCriteriaSet": "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm", "selectableConcepts": [ { "code": "E14.0", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Diabetes mellitus" } ], "type": "reference" } ], "name": "Diagnose", "timeRestrictionAllowed": false, "valueDefinition": "string" } } ] ```

Also, the response to the entry with details will still contain the context and termcodes as objects like decided earlier. Only the uiprofile will be removed, resulting in the following:

Click to expand JSON ```json { "id": "5dfe270a-d520-1de8-24eb-19452f270561", "name": "Angeborene Torsion des Ovars", "availability": 2915, "terminology": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "termCode": "Q50.2", "kdsModule": null, "translations": [], "parents": [ { "name": "Angeborene Fehlbildungen der Ovarien, der Tubae uterinae und der Ligg. lata uteri", "id": "8b4035b0-a6d1-dadf-a423-d4af8383ae0b" } ], "children": [], "relatedTerms": [], "context": { "code": "Diagnose", "system": "fdpg.mii.cds", "version": "1.0.0", "display": "Diagnose" }, "termCodes": [ { "code": "Q50.2", "system": "http://fhir.de/CodeSystem/bfarm/icd-10-gm", "version": "2023", "display": "Angeborene Torsion des Ovars" } ] } ```

(Also, terminology will no longer be a URL in the future, but this is not relevant for this discussion.)

Shayan1375 commented 3 months ago

We've made updates to the endpoints and responses in the Swagger documentation to better align them with their names and expected responses.

We have introduced a new GET endpoint /terminology/criteria-profile-data/{ids}?=which returns the UiProfile, termCode, and context for a comma-separated list of IDs.

Additionally, we have established the endpoint /terminology/entry/{id}/relationswhich returns the parents, children, relatedTerms, and translationsfor a list entry. The id availability name terminology termCode kdsModule UiProfile context and termCodeshave been removed from this endpoint. Therefore the endpoint /terminology/entry/{id}?detail=true becomes obselete.

The endpoint /terminology/search has been renamend to /terminology/entry/search The revised Swagger documentation reflecting these changes can be found below.

Click to expand OpenAPI ```yml openapi: 3.0.3 info: title: MII Feasibility Backend REST API - Terminology Search Draft description: todo contact: email: noreply@todo.de license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend servers: - url: https://to.be.defined variables: basePath: default: / tags: - name: terminology description: operations on the terminology paths: /terminology/search/filter: get: tags: - terminology summary: "Get the list of available filters" operationId: "getFilters" responses: 200: description: Ok, return the list of available filters content: application/json: schema: type: "array" items: $ref: "#/components/schemas/Filter" /terminology/entry/search: get: tags: - terminology summary: "Parametrized search in the configured elastic search service." operationId: "search" parameters: - name: searchterm in: query description: The term to search for schema: type: "string" example: Diabetes Mellitus - name: context in: query description: Limit the search to a given context (or contexts?) schema: type: "string" example: Diagnosis - name: terminology in: query description: Limit the search to a given terminology (or terminologies?) schema: type: "string" example: ICD10 - name: kds in: query description: Limit the search to a given KDS module (or modules?) schema: type: "string" example: Condition - name: availability in: query description: Limit the search to available items? (Maybe better invert this one to make this the default?) schema: type: boolean example: true - name: pageSize in: query description: How many results shall be returned per page schema: type: integer example: 10 - name: page in: query description: Which page shall be returned schema: type: integer example: 42 responses: 200: description: Ok, return the list of results (maybe limited/offset - to be decided) for the search content: application/json: schema: $ref: "#/components/schemas/ElasticSearchResult" /terminology/entry/{id}/relations: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" operationId: "getEntryById" parameters: - name: id in: path description: The id (contextualized termcode hash) of the entry that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntryWithRelations' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found /terminology/entry/{id}: get: tags: - terminology summary: "Get the detailed entry for a criterion, containing children and translations" description: "This should be in the getEntryById call, but for better distinction between the return values it is listed seperately here" operationId: "getEntriesById" parameters: - name: id in: path description: The ids (contextualized termcode hash) of the entries that shall be retrieved required: true schema: type: string example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 responses: 200: description: Entry found content: application/json: schema: $ref: '#/components/schemas/ElasticSearchResultEntry' 401: description: Unauthorized - please login first 403: description: Forbidden - insufficient access rights 404: description: Entry not found /terminology/criteria-profile-data: get: tags: - terminology summary: "Get the profile data for criteria, containing uiProfile context and termCodes" description: "This should return all the information needed to build criteria in the frontend." operationId: "getEntriesByIdsWithDetail" parameters: - name: ids in: query style: form explode: false description: "A comma-separated list of IDs (contextualized termcode hashes) of the entries that shall be retrieved." required: true schema: type: string example: "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8,203e04cd-4f0a-321b-b1ad-9ec6d211e0a9" responses: 200: description: "Entries found. May contain empty entries if some were not found." content: application/json: schema: type: array items: $ref: '#/components/schemas/CriteriaProfileData' 401: description: "Unauthorized - please login first." 403: description: "Forbidden - insufficient access rights." components: schemas: ElasticSearchResult: type: object properties: totalHits: type: integer example: 42 results: type: array items: $ref: "#/components/schemas/ElasticSearchResultEntry" ElasticSearchResultEntryWithRelations: type: object properties: translations: type: array items: $ref: "#/components/schemas/ElasticSearchTranslationEntry" parents: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" children: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" relatedTerms: type: array items: $ref: "#/components/schemas/ElasticSearchRelationEntry" ElasticSearchResultEntry: type: object properties: name: type: string example: Diabetes Mellitus id: type: string format: uuid example: 203e04cd-4f0a-321b-b1ad-9ec6d211e0a8 availability: description: Not sure if we want this as numeric value, percentage or just boolean? type: integer minimum: 0 example: 119578 context: type: string example: Diagnosis terminology: type: string example: icd-10 termcode: type: string example: E10-E14 kdsModule: type: string example: Condition selectable: type: boolean example: true ElasticSearchTranslationEntry: type: object properties: lang: type: string example: en value: type: string example: Diabetes Mellitus ElasticSearchRelationEntry: type: object properties: name: type: string example: Endokrine, Ernährungs- und Stoffwechselerkrankungen contextualizedTermcodeHash: type: string example: c55d0d62-6c47-30b0-94b2-afa383ce35f7 CriteriaProfileData: type: object properties: id: type: string format: uuid uiprofile: type: array items: $ref: "#/components/schemas/UiProfileEntry" termCodes: type: array items: $ref: "#/components/schemas/TermCode" context: $ref: "#/components/schemas/TermCode" Filter: type: object properties: name: type: string example: Terminology values: type: array items: $ref: "#/components/schemas/FilterValue" example: - ICD10 - SNOMED - LOINC FilterValue: type: string example: "icd10" TermCode: type: object properties: code: type: string example: "E14.0" system: type: string example: "http://fhir.de/CodeSystem/bfarm/icd-10-gm" version: type: string example: "2023" display: type: string example: "Diabetes mellitus" UiProfileEntry: type: object properties: attributeDefinitions: type: array items: $ref: "#/components/schemas/AttributeDefinition" name: type: string example: "Diagnose" timeRestrictionAllowed: type: boolean example: false valueDefinition: type: string Unit: type: object properties: code: type: string display: type: string AttributeDefinition: type: object properties: allowedUnits: type: array items: $ref: "#/components/schemas/Unit" attributeCode: $ref: "#/components/schemas/TermCode" max: type: number example: null min: type: number example: null optional: type: boolean example: true precision: type: number example: 1 referenceCriteriaSet: type: string example: "http://fdpg.mii.cds/CriteriaSet/Diagnose/icd-10-gm" selectableConcepts: type: array items: $ref: "#/components/schemas/TermCode" type: type: string example: "reference" ```
michael-82 commented 2 months ago

We discussed the new endpoints for searching in profiles. The first draft is below. Please comment @juliangruendner @Shayan1375 @thkoehler11

Click to expand OpenAPI fragment ```yml openapi: 3.0.3 info: title: MII Feasibility Backend REST API - Terminology Search Draft description: todo contact: email: noreply@todo.de license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 0.0.1 externalDocs: description: Check out the github repository url: https://github.com/medizininformatik-initiative/feasibility-backend servers: - url: https://to.be.defined variables: basePath: default: / tags: - name: tdb description: to be decided paths: /terminology/healthrecord/tree: get: tags: - tbd responses: 200: description: "Tree found and loaded. Will be delivered." content: application/json: schema: type: array items: $ref: '#/components/schemas/HealthRecordTree' 401: description: "Unauthorized - please login first." 403: description: "Forbidden - insufficient access rights." /terminology/healthrecord/profile: get: tags: - tbd parameters: - name: ids in: query style: form explode: false description: "A comma-separated list of IDs of the profiles that shall be retrieved." required: true schema: type: string example: "203e04cd-4f0a-321b-b1ad-9ec6d211e0a8,203e04cd-4f0a-321b-b1ad-9ec6d211e0a9" responses: 200: description: "HealthRecord Profiles found - may contain empty entries" content: application/json: schema: type: array items: $ref: '#/components/schemas/HealthRecordProfileData' 401: description: "Unauthorized - please login first." 403: description: "Forbidden - insufficient access rights." /terminology/url-mapping: get: tags: - tbd responses: 200: description: ok content: application/json: schema: type: array items: $ref: '#/components/schemas/UrlMapping' components: schemas: UrlMapping: type: object properties: url: type: string format: url example: http://hl7.org/fhir/sid/icd-o-3 display: type: string example: icd-o3 HealthRecordProfileData: type: object properties: id: type: string format: uuid profiles: type: array items: $ref: '#/components/schemas/HealthRecordProfileEntry' HealthRecordProfileEntry: type: object properties: entry: description: this is to be decided by julian type: string format: json HealthRecordTree: type: object properties: entry: description: full json tree containing all profiles for now. might change if lazy loading or server-side filtering is required type: string format: json ```