Open juliangruendner opened 10 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)
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"
}
]
}
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"
]
}
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
}
]
}
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.
Following a discussion between @juliangruendner @Shayan1375 @thkoehler11 and myself from today (2024-05-15), the following has been decided:
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:
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)
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?)
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/
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.
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:
The IDs of the selected elements will be sent to the backend to retrieve the UI profiles.
The IDs in the GET URL are just an example and will be sent separated by commas.
We have determined that the URL can have a maximum length of 2,048 characters, which corresponds to approximately 50 IDs that can be sent at one time.
If there are more IDs, the frontend must split them into multiple GET requests and wait for the results.
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:
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.
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.
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:
(Also, terminology will no longer be a URL in the future, but this is not relevant for this discussion.)
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}/relations
which returns the parents
, children
, relatedTerms
, and translations
for a list entry. The id
availability
name
terminology
termCode
kdsModule
UiProfile
context
and termCodes
have 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.
We discussed the new endpoints for searching in profiles. The first draft is below. Please comment @juliangruendner @Shayan1375 @thkoehler11
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" } } } } ```