Update snomed display names xquery

For updating displayName attributes of terminologyAssociations, valueSet concepts and template vocabulary items with the latest available description of a particular type from SNOMED CT (or country extension). This is useful because SNOMED CT updates in a different pace from projects and especially translations could occur after initial project setup. Operates on 1 project at a time.

Prerequisite: have SNOMED CT data installed at your site

Note: once applied to your project, there is no undo unless you keep a copy of the project as backup.

This xquery will perform the following actions:

  • Based on all elements with @code, @codeSystem = SNOMED CT, and @displayName pick the currently available fsn from the requested refset $refsetlanguage, and the US English fallback fsn
  • If applying the $update is requested apply update, and leave a version statement in the project part
  • Return a report of what was done

To run the xquery below on the server:

  • Open a webbrowser, surf to: http://<server>:8877
  • Click on ‘dashboard’.
  • Log in as ‘admin’.
  • Open eXide.
  • Click: ‘New XQuery’ (paste the xquery in the box below to the browserscreen)
  • Click: ‘eval’ to run the xquery.
import module namespace get         = "http://art-decor.org/ns/art-decor-settings" at "/db/apps/art/modules/art-decor-settings.xqm";
import module namespace art         = "http://art-decor.org/ns/art" at "/db/apps/art/modules/art-decor.xqm";
import module namespace snomed      = "http://art-decor.org/ns/terminology/snomed" at "/db/apps/terminology/snomed/api/api-snomed.xqm";

declare variable $codeSystemSNOMED      := '2.16.840.1.113883.6.96';
(:31000146106 - Nederlandse taalreferentieset (foundation metadata concept):)
declare variable $refsetlanguage        := '31000146106';
(:900000000000509007 - United States of America English language reference set:)
declare variable $refsetlanguage-us     := '900000000000509007';

declare function local:getDisplayNameAndStatus($code as xs:string, $codeSystem as xs:string, $defaultLanguage as xs:string) as element()* {
    if ($codeSystem = $codeSystemSNOMED) then
        (:let $service-uri        := concat($artDeepLinkTerminology,'snomed/getConcept/',encode-for-uri($code))
        let $server-response    := httpclient:get(xs:anyURI($service-uri),false(),$requestHeaders)
        
        let $concept        := $server-response/httpclient:body//concept[@conceptId = $code]:)
        let $concept        := $snomed:colDataBase//@conceptId[. = $code]/parent::concept
        let $displayNames   := $concept/desc[@active]
        let $fsn            := $concept/desc[@type = 'fsn'][@active][@languageRefsetId = $refsetlanguage-us]
        let $langfsn        := ($concept/desc[@type = 'fsn'][@active][@languageRefsetId = $refsetlanguage], $concept/desc[@type = 'pref'][@active][@languageRefsetId = $refsetlanguage])[1]
        let $statusCode     := if ($concept[@active]) then 'active' else if ($concept) then 'deprecated' else ('')
        let $statusText     := $statusCode
        return
            <concept code="{$code}" codeSystem="{$codeSystem}" displayName="{$fsn}" statusCode="{$statusCode}" statusText="{$statusText}">
            {
                if (empty($langfsn)) then () else attribute langfsn {$langfsn}
            }
            </concept>
    else ()
};

declare function local:getReturnLines($terms as element()*, $namemap as item()) as element()* {
    for $term in $terms[not(ancestor::valueSet)]
    let $replacementTerm    := map:get($namemap, $term/@code)/@langfsn
    return
        element {name($term)} {
            $term/@*,
            if (empty($replacementTerm)) then () else attribute replaceBy {$replacementTerm},
            art:getConcept($term/@conceptId, $term/@conceptFlexibility)
        }
    ,
    for $termsByVS in $terms[ancestor::valueSet]
    let $vsid   := $termsByVS/ancestor::valueSet/@id
    let $vsed   := $termsByVS/ancestor::valueSet/@effectiveDate
    group by $vsid, $vsed
    return
        <valueSet>
        {
            $termsByVS/ancestor::valueSet/@*,
            for $term in $termsByVS
            let $replacementTerm    := map:get($namemap, $term/@code)/@langfsn
            return
                element {name($term)} {
                    $term/@*,
                    if (empty($replacementTerm)) then () else attribute replaceBy {$replacementTerm},
                    $term/node()
                }
        }
        </valueSet>
};
declare function local:getReleaseNote($snomedConcepts as element()*, $resultConcepts as element()*) as element() {
    if ($refsetlanguage = '31000146106') then
    <version date="{substring(string(current-dateTime()), 1, 19)}" by="{get:strCurrentUserName()}">
        <desc language="nl-NL">SNOMED CT concept weergavenamen bijgewerkt met Nederlandse termen voor zover beschikbaar. Totaal: {count($resultConcepts[self::terminologyAssociation] | $resultConcepts[self::valueSet]/(concept | exception | include))}
            <ul>
                <li>Terminologiekoppelingen: {count($resultConcepts[self::terminologyAssociation])}</li>
                <li>Waardelijstconcepten {count($resultConcepts[self::valueSet]/(concept | exception | include))} in {count($resultConcepts[self::valueSet])} waardelijsten</li>
                {
                    let $others := $resultConcepts[not(self::valueSet | self::terminologyAssociation)]
                    return
                        if (empty($others)) then () else <li>Anders: {count($others)}</li>
                }
            </ul>
        </desc>
    </version>
    else (
    <version date="{substring(string(current-dateTime()), 1, 19)}" by="{get:strCurrentUserName()}">
        <desc language="nl-NL">SNOMED CT concept display names updated with currently available description. Total: {count($resultConcepts[self::terminologyAssociation] | $resultConcepts[self::valueSet]/(concept | exception | include))}
            <ul>
                <li>Terminology associations: {count($resultConcepts[self::terminologyAssociation])}</li>
                <li>Vaslue set concepts {count($resultConcepts[self::valueSet]/(concept | exception | include))} in {count($resultConcepts[self::valueSet])} value sets</li>
                {
                    let $others := $resultConcepts[not(self::valueSet | self::terminologyAssociation)]
                    return
                        if (empty($others)) then () else <li>Other: {count($others)}</li>
                }
            </ul>
        </desc>
    </version>
    )
};

let $projectPrefix              := if (request:exists()) then request:get-parameter('prefix', ())               else 'demo1-'
let $update                     := if (request:exists()) then request:get-parameter('update', 'false') = 'true' else false()

let $decor                      := if (empty($projectPrefix)) then () else collection('/db/apps/decor/data')/decor[project/@prefix = $projectPrefix]
let $snomedConcepts             := if (empty($projectPrefix)) then () else collection('/db/apps/decor/data')/decor[project/@prefix = $projectPrefix]//*[@code][@displayName][@codeSystem = $codeSystemSNOMED][not(ancestor::template)]
let $namemap                    := map:new(
        for $term in $snomedConcepts
        let $conceptId  := $term/@code
        group by $conceptId
        return
            map:entry($conceptId, local:getDisplayNameAndStatus($conceptId, $term[1]/@codeSystem, 'nl-NL'))
    )
let $resultConcepts             := if (empty($snomedConcepts)) then () else local:getReturnLines($snomedConcepts, $namemap)
let $versionOrRelease           := if (empty($snomedConcepts)) then () else local:getReleaseNote($snomedConcepts, $resultConcepts)

let $updateLines                := 
    if ($update) then 
        for $term in $snomedConcepts[@displayName]
        let $replacementTerm    := map:get($namemap, $term/@code)/@langfsn
        return
            if (empty($replacementTerm))    then () else 
            if ($term[@displayName = $replacementTerm]) then () else (
                update value $term/@displayName with $replacementTerm
            )
    else ()
let $updateVersion              := 
    if ($update) then 
        if (empty($versionOrRelease))       then ( (: don't have anything to write:) )          else 
        if (empty($decor))                  then ( (: don't have a project to write into :) )   else
        if ($decor/project[version | release]) then
            update insert $versionOrRelease preceding $decor/project/(release | version)[1]
        else (
            update insert $versionOrRelease into $decor/project
        )
    else ()

return (
    <project prefix="{$projectPrefix}" update="{$update}">
        {$versionOrRelease}
        <x>{$resultConcepts}</x>
    </project>
)