diff --git a/modules/frontend/src/lib/fhir.ts b/modules/frontend/src/lib/fhir.ts index 949a1ddb5..b036ad5a7 100644 --- a/modules/frontend/src/lib/fhir.ts +++ b/modules/frontend/src/lib/fhir.ts @@ -43,6 +43,25 @@ export function parameter(concept: Parameters, name: string): ParametersParamete return concept.parameter?.filter((c) => c.name == name)[0]; } +export function parameterParts( + concept: Parameters, + name: string +): ParametersParameter[] | undefined { + return concept.parameter?.filter((c) => c.name == name); +} + +export function parameterValue( + parameter: ParametersParameter +): Coding | string | number | boolean | undefined { + if ('valueCode' in parameter) return parameter.valueCode; + if ('valueCoding' in parameter) return parameter.valueCoding; + if ('valueString' in parameter) return parameter.valueString; + if ('valueInteger' in parameter) return parameter.valueInteger; + if ('valueBoolean' in parameter) return parameter.valueBoolean; + if ('valueDateTime' in parameter) return parameter.valueDateTime; + if ('valueDecimal' in parameter) return parameter.valueDecimal; +} + export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; export type SearchMode = 'match' | 'include' | 'outcome'; diff --git a/modules/frontend/src/routes/CodeSystem/$lookup/+page.server.ts b/modules/frontend/src/routes/CodeSystem/$lookup/+page.server.ts new file mode 100644 index 000000000..d3b7d3bf7 --- /dev/null +++ b/modules/frontend/src/routes/CodeSystem/$lookup/+page.server.ts @@ -0,0 +1,78 @@ +import type { Actions } from './$types'; +import type { OperationOutcome, Parameters, ParametersParameter } from 'fhir/r4'; +import { resolve } from '$app/paths'; +import { fail } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request, fetch }) => { + const data = await request.formData(); + const system = data.get('system') as string; + const version = data.get('version') as string; + const code = data.get('code') as string; + const display = data.get('display') as string; + const displayLanguage = data.get('displayLanguage') as string; + + const parameters: ParametersParameter[] = [ + { + name: 'system', + valueUri: system + }, + { + name: 'code', + valueCode: code + } + ]; + + if (version !== '') { + parameters.push({ + name: 'version', + valueString: version + }); + } + + if (display !== '') { + parameters.push({ + name: 'display', + valueString: display + }); + } + + if (displayLanguage !== '') { + parameters.push({ + name: 'displayLanguage', + valueCode: displayLanguage + }); + } + + const res = await fetch(resolve('/CodeSystem/$lookup'), { + method: 'POST', + headers: { 'Content-Type': 'application/fhir+json', Accept: 'application/fhir+json' }, + body: JSON.stringify({ + resourceType: 'Parameters', + parameter: parameters + }) + }); + + if (!res.ok) { + const error: OperationOutcome = await res.json(); + return fail(400, { + system, + version, + code, + display, + displayLanguage, + incorrect: true, + msg: error.issue[0]?.diagnostics ?? error.issue[0]?.details?.text + }); + } + + return { + system, + version, + code, + display, + displayLanguage, + result: (await res.json()) as Parameters + }; + } +} satisfies Actions; diff --git a/modules/frontend/src/routes/CodeSystem/$lookup/+page.svelte b/modules/frontend/src/routes/CodeSystem/$lookup/+page.svelte new file mode 100644 index 000000000..6a291484b --- /dev/null +++ b/modules/frontend/src/routes/CodeSystem/$lookup/+page.svelte @@ -0,0 +1,52 @@ + + + + $lookup - CodeSystem - Blaze + + +
+ + + + + $lookup + + +
+ +
+
+
+ + + + + +
+ {#snippet buttons()} + + {/snippet} + + + {#if form?.incorrect} +

{form.msg}

+ {/if} + + {#if form?.result} + + {/if} +
diff --git a/modules/frontend/src/routes/CodeSystem/$lookup/result-list.svelte b/modules/frontend/src/routes/CodeSystem/$lookup/result-list.svelte new file mode 100644 index 000000000..48f3cbeef --- /dev/null +++ b/modules/frontend/src/routes/CodeSystem/$lookup/result-list.svelte @@ -0,0 +1,61 @@ + + + + + {parameter(parameters, 'name')?.valueString} + + {@const version = parameter(parameters, 'version')?.valueString} + {#if version} + + {version} + + {/if} + {@const display = parameter(parameters, 'display')?.valueString} + {#if display} + + {display} + + {/if} + {@const definition = parameter(parameters, 'definition')?.valueString} + {#if definition} + + {definition} + + {/if} + {@const properties = parameterParts(parameters, 'property')} + {#if properties} + {#each properties as property, i} + + + {#each property.part as part} + {parameterValue(part)} + {/each} + + + {/each} + {/if} + {@const designations = parameterParts(parameters, 'designation')} + {#if designations} + {#each designations as designation, i} + + + {#each designation.part as part} + {parameterValue(part)} + {/each} + + + {/each} + {/if} + diff --git a/modules/frontend/src/routes/CodeSystem/$validate-code/+page.svelte b/modules/frontend/src/routes/CodeSystem/$validate-code/+page.svelte index 6562f712e..e27f3db79 100644 --- a/modules/frontend/src/routes/CodeSystem/$validate-code/+page.svelte +++ b/modules/frontend/src/routes/CodeSystem/$validate-code/+page.svelte @@ -9,7 +9,7 @@ import Section from '$lib/tailwind/form/section.svelte'; import TextField from '$lib/tailwind/form/text-field.svelte'; import SubmitButton from '$lib/tailwind/form/button-submit.svelte'; - import ResultList from '../result-list.svelte'; + import ResultList from './result-list.svelte'; let { form }: PageProps = $props(); diff --git a/modules/frontend/src/routes/CodeSystem/result-list.svelte b/modules/frontend/src/routes/CodeSystem/$validate-code/result-list.svelte similarity index 100% rename from modules/frontend/src/routes/CodeSystem/result-list.svelte rename to modules/frontend/src/routes/CodeSystem/$validate-code/result-list.svelte diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.server.ts b/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.server.ts new file mode 100644 index 000000000..ea37bbf98 --- /dev/null +++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.server.ts @@ -0,0 +1,56 @@ +import type { Actions } from './$types'; +import type { OperationOutcome, Parameters, ParametersParameter } from 'fhir/r4'; +import { resolve } from '$app/paths'; +import { fail } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request, fetch, params }) => { + const data = await request.formData(); + const code = data.get('code') as string; + const display = data.get('display') as string; + const displayLanguage = data.get('displayLanguage') as string; + + const parameters: ParametersParameter[] = [ + { + name: 'code', + valueCode: code + } + ]; + + if (display !== '') { + parameters.push({ + name: 'display', + valueString: display + }); + } + + if (displayLanguage !== '') { + parameters.push({ + name: 'displayLanguage', + valueCode: displayLanguage + }); + } + + const res = await fetch(resolve('/CodeSystem/[id=id]/$lookup', params), { + method: 'POST', + headers: { 'Content-Type': 'application/fhir+json', Accept: 'application/fhir+json' }, + body: JSON.stringify({ + resourceType: 'Parameters', + parameter: parameters + }) + }); + + if (!res.ok) { + const error: OperationOutcome = await res.json(); + return fail(400, { + code, + display, + displayLanguage, + incorrect: true, + msg: error.issue[0]?.diagnostics ?? error.issue[0]?.details?.text + }); + } + + return { code, display, displayLanguage, result: (await res.json()) as Parameters }; + } +} satisfies Actions; diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.svelte b/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.svelte new file mode 100644 index 000000000..c47bfdef3 --- /dev/null +++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.svelte @@ -0,0 +1,61 @@ + + + + $lookup - {title(data.codeSystem)} - Blaze + + +
+ + + + + + $lookup + + +
+ +
+

+ {title(data.codeSystem)} +

+ {#if data.codeSystem.description} +

{data.codeSystem.description}

+ {/if} + +
+
+ + + +
+ {#snippet buttons()} + + {/snippet} + + + {#if form?.incorrect} +

{form.msg}

+ {/if} + + {#if form?.result} + + {/if} +
diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.ts b/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.ts new file mode 100644 index 000000000..e87b8ee7b --- /dev/null +++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$lookup/+page.ts @@ -0,0 +1,34 @@ +import type { PageLoad } from './$types'; +import type { Bundle, CodeSystem } from 'fhir/r4'; + +import { resolve } from '$app/paths'; +import { error, type NumericRange } from '@sveltejs/kit'; + +export const load: PageLoad = async ({ fetch, params }) => { + const res = await fetch( + `${resolve('/CodeSystem')}?_id=${params.id}&_elements=version,title,description`, + { + headers: { + Accept: 'application/fhir+json' + } + } + ); + + if (!res.ok) { + error(res.status as NumericRange<400, 599>, { + short: res.status == 404 ? 'Not Found' : res.status == 410 ? 'Gone' : undefined, + message: + res.status == 404 + ? `The CodeSystem with ID ${params.id} was not found.` + : res.status == 410 + ? `The CodeSystem with ID ${params.id} was deleted. Please look into the history.` + : `An error happened while loading the CodeSystem with ID ${params.id}. Please try again later.` + }); + } + + const bundle: Bundle = await res.json(); + + return { + codeSystem: bundle.entry?.[0].resource as CodeSystem + }; +}; diff --git a/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte index 3a00a2a79..f75d8d505 100644 --- a/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte +++ b/modules/frontend/src/routes/CodeSystem/[id=id]/$validate-code/+page.svelte @@ -10,7 +10,7 @@ import Section from '$lib/tailwind/form/section.svelte'; import TextField from '$lib/tailwind/form/text-field.svelte'; import SubmitButton from '$lib/tailwind/form/button-submit.svelte'; - import ResultList from '../../result-list.svelte'; + import ResultList from '../../$validate-code/result-list.svelte'; import { title } from '$lib/resource.js'; diff --git a/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte b/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte index dde012936..b9a8ba356 100644 --- a/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte +++ b/modules/frontend/src/routes/CodeSystem/operation-dropdown.svelte @@ -6,5 +6,6 @@ + diff --git a/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte b/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte index 1ae020599..1c8936cf1 100644 --- a/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte +++ b/modules/frontend/src/routes/ValueSet/$validate-code/+page.svelte @@ -11,7 +11,7 @@ import CheckBoxes from '$lib/tailwind/form/check-boxes.svelte'; import CheckBox from '$lib/tailwind/form/check-box.svelte'; import SubmitButton from '$lib/tailwind/form/button-submit.svelte'; - import ResultList from '../result-list.svelte'; + import ResultList from './result-list.svelte'; let { form }: PageProps = $props(); diff --git a/modules/frontend/src/routes/ValueSet/result-list.svelte b/modules/frontend/src/routes/ValueSet/$validate-code/result-list.svelte similarity index 100% rename from modules/frontend/src/routes/ValueSet/result-list.svelte rename to modules/frontend/src/routes/ValueSet/$validate-code/result-list.svelte diff --git a/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte index d1bd4511f..543ed6197 100644 --- a/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte +++ b/modules/frontend/src/routes/ValueSet/[id=id]/$validate-code/+page.svelte @@ -7,7 +7,7 @@ import BreadcrumbEntryResource from '$lib/breadcrumb/resource.svelte'; import BreadcrumbEntry from '$lib/breadcrumb/entry.svelte'; import CheckBoxes from '$lib/tailwind/form/check-boxes.svelte'; - import ResultList from '../../result-list.svelte'; + import ResultList from '../../$validate-code/result-list.svelte'; import SubmitButton from '$lib/tailwind/form/button-submit.svelte'; import CheckBox from '$lib/tailwind/form/check-box.svelte'; import Section from '$lib/tailwind/form/section.svelte'; diff --git a/modules/frontend/src/routes/[type=type]/[id=id]/operation-dropdown.svelte b/modules/frontend/src/routes/[type=type]/[id=id]/operation-dropdown.svelte index ebd8b6a40..250b6b195 100644 --- a/modules/frontend/src/routes/[type=type]/[id=id]/operation-dropdown.svelte +++ b/modules/frontend/src/routes/[type=type]/[id=id]/operation-dropdown.svelte @@ -12,6 +12,7 @@ {#if ['CodeSystem', 'ValueSet', 'Patient'].includes(params.type)} {#if params.type === 'CodeSystem'} + (get-in concept-properties [property-code :description]) type/string)) + +(defn- property-description* [code-system property-code] + (some #(when (= property-code (:value (:code %))) (:description %)) (:property code-system))) + +(defn- property-description [code-system property-code] + (or (property-description* code-system property-code) + (standard-property-description property-code))) + +(defn- property-param [code-system {:keys [code value source]}] + (let [description (property-description code-system (:value code))] + (cond-> + ["code" code + "value" value + "source" source] + description (conj "description" description)))) (defn parameters-from-concept {:arglists '([code-system concept])} - [{:keys [name version]} + [{:keys [name version] :as code-system} {:keys [display definition designation property]}] (fu/parameters @@ -26,4 +79,4 @@ "display" (if (nil? display) nil (type/string display)) "definition" definition "designation" (map designation-param designation) - "property" (map property-param property))) + "property" (map (partial property-param code-system) property))) diff --git a/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj b/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj index 35afcf0c9..81cf473eb 100644 --- a/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj +++ b/modules/terminology-service-local/test/blaze/terminology_service/local_test.clj @@ -4,7 +4,7 @@ [blaze.db.api-stub :as api-stub :refer [with-system with-system-data]] [blaze.fhir.spec :as fhir-spec] [blaze.fhir.spec.type :as type] - [blaze.fhir.test-util :refer [parameter structure-definition-repo]] + [blaze.fhir.test-util :refer [parameter parameter-part structure-definition-repo]] [blaze.fhir.util :as fu] [blaze.fhir.util-spec] [blaze.module.test-util :as mtu :refer [given-failed-future given-failed-system]] @@ -657,7 +657,85 @@ :version #fhir/string "version-124939" :code #fhir/code "code-115927"}) ::anom/category := ::anom/not-found - ::anom/message := "Unknown code `code-115927` was not found in the provided code system."))))) + ::anom/message := "Unknown code `code-115927` was not found in the provided code system.")))) + + (testing "description in property part" + (testing "is filled from property defined in code system" + (with-system-data [{ts ::ts/local} config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :name #fhir/string "name-134300" + :url #fhir/uri "system-115910" + :content #fhir/code "complete" + :property + [{:fhir/type :fhir.CodeSystem/property + :code #fhir/code "prop-code-091200" + :uri #fhir/uri "prop-uri-091200" + :description #fhir/string "prop-description-091200"}] + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-154735" + :property + [{:fhir/type :fhir.CodeSystem.concept/property + :code #fhir/code "prop-code-091200" + :value #fhir/string "prop-value-091200"}]}]}]]] + + (given @(code-system-lookup ts + "system" #fhir/uri "system-115910" + "code" #fhir/code "code-154735") + :fhir/type := :fhir/Parameters + [(parameter "name") 0 :value] := #fhir/string "name-134300" + [(parameter "property") count] := 1 + [(parameter "property") 0 (parameter-part "code") 0 :value] := #fhir/code "prop-code-091200" + [(parameter "property") 0 (parameter-part "value") 0 :value] := #fhir/string "prop-value-091200" + [(parameter "property") 0 (parameter-part "description") 0 :value] := #fhir/string "prop-description-091200"))) + + (testing "is filled from property defined default concept-properties" + (with-system-data [{ts ::ts/local} config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :name #fhir/string "name-134300" + :url #fhir/uri "system-115910" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-154735" + :property + [{:fhir/type :fhir.CodeSystem.concept/property + :code #fhir/code "inactive" + :value #fhir/boolean true}]}]}]]] + + (given @(code-system-lookup ts + "system" #fhir/uri "system-115910" + "code" #fhir/code "code-154735") + :fhir/type := :fhir/Parameters + [(parameter "name") 0 :value] := #fhir/string "name-134300" + [(parameter "property") count] := 1 + [(parameter "property") 0 (parameter-part "code") 0 :value] := #fhir/code "inactive" + [(parameter "property") 0 (parameter-part "value") 0 :value] := #fhir/boolean true + [(parameter "property") 0 (parameter-part "description") 0 :value] := #fhir/string "True if the concept is not considered active - e.g. not a valid concept any more. Property type is boolean, default value is false. Note that the status property may also be used to indicate that a concept is inactive"))) + + (testing "is not filled when property does not exist" + (with-system-data [{ts ::ts/local} config] + [[[:put {:fhir/type :fhir/CodeSystem :id "0" + :name #fhir/string "name-134300" + :url #fhir/uri "system-115910" + :content #fhir/code "complete" + :concept + [{:fhir/type :fhir.CodeSystem/concept + :code #fhir/code "code-154735" + :property + [{:fhir/type :fhir.CodeSystem.concept/property + :code #fhir/code "prop-code-091200" + :value #fhir/boolean true}]}]}]]] + + (given @(code-system-lookup ts + "system" #fhir/uri "system-115910" + "code" #fhir/code "code-154735") + :fhir/type := :fhir/Parameters + [(parameter "name") 0 :value] := #fhir/string "name-134300" + [(parameter "property") count] := 1 + [(parameter "property") 0 (parameter-part "code") 0 :value] := #fhir/code "prop-code-091200" + [(parameter "property") 0 (parameter-part "value") 0 :value] := #fhir/boolean true + [(parameter "property") 0 (parameter-part "description") count] := 0))))) (deftest code-system-lookup-bcp-13-test (with-system [{ts ::ts/local} bcp-13-config]