{"id":26956294,"url":"https://github.com/wardle/hermes","last_synced_at":"2025-05-16T04:03:58.440Z","repository":{"id":43815933,"uuid":"293230222","full_name":"wardle/hermes","owner":"wardle","description":"A library and microservice implementing the health and care terminology SNOMED CT with support for cross-maps, inference, fast full-text search, autocompletion, compositional grammar and the expression constraint language.","archived":false,"fork":false,"pushed_at":"2025-03-30T14:19:13.000Z","size":1549,"stargazers_count":186,"open_issues_count":0,"forks_count":24,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-05-14T10:02:05.965Z","etag":null,"topics":["clojure","diagnoses","drugs","health","healthcare","icd-10","lmdb","lucene","medications","snomed","snomed-ct","terminology","terminology-server"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wardle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-09-06T07:59:43.000Z","updated_at":"2025-05-08T02:25:46.000Z","dependencies_parsed_at":"2024-02-18T10:32:36.070Z","dependency_job_id":"863d99b5-1ea0-49f1-adfd-1f2ab36f49a9","html_url":"https://github.com/wardle/hermes","commit_stats":{"total_commits":956,"total_committers":3,"mean_commits":318.6666666666667,"dds":"0.0020920502092049986","last_synced_commit":"1800b8f72a97d7ac30fca3b0dcf8d9dbdeec46f3"},"previous_names":[],"tags_count":129,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardle%2Fhermes","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardle%2Fhermes/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardle%2Fhermes/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wardle%2Fhermes/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wardle","download_url":"https://codeload.github.com/wardle/hermes/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254464891,"owners_count":22075570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","diagnoses","drugs","health","healthcare","icd-10","lmdb","lucene","medications","snomed","snomed-ct","terminology","terminology-server"],"created_at":"2025-04-03T03:21:11.426Z","updated_at":"2025-05-16T04:03:58.409Z","avatar_url":"https://github.com/wardle.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hermes : terminology tools, library and microservice.\n\n[![Scc Count Badge](https://sloc.xyz/github/wardle/hermes)](https://github.com/wardle/hermes/)\n[![Scc Cocomo Badge](https://sloc.xyz/github/wardle/hermes?category=cocomo\u0026avg-wage=100000)](https://github.com/wardle/hermes/)\n[![Test](https://github.com/wardle/hermes/actions/workflows/test.yml/badge.svg)](https://github.com/wardle/hermes/actions/workflows/test.yml)\n[![DOI](https://zenodo.org/badge/293230222.svg)](https://zenodo.org/badge/latestdoi/293230222)\n\u003cbr/\u003e\n[![GitHub release (latest by date)](https://img.shields.io/github/v/release/wardle/hermes)](https://github.com/wardle/hermes/releases/latest)\n[![Clojars Project](https://img.shields.io/clojars/v/com.eldrix/hermes.svg)](https://clojars.org/com.eldrix/hermes)\n[![cljdoc badge](https://cljdoc.org/badge/com.eldrix/hermes)](https://cljdoc.org/d/com.eldrix/hermes)\n\nHermes provides a set of terminology tools built around SNOMED CT including:\n\n* a fast terminology service with full-text search functionality; ideal for driving autocompletion in user interfaces\n* an inference engine in order to analyse SNOMED CT expressions and concepts and derive meaning\n* cross-mapping to and from other code systems including ICD-10, Read codes and OPCS\n* support for SNOMED CT compositional grammar (cg) and expression constraint language (ECL) v2.2.\n* optional HL7 FHIR terminology server via [hades](https://github.com/wardle/hades)\n\nIt is designed as both a library for embedding into larger applications and as a standalone microservice.\n\nIt is fast, both for import and for use. It imports and indexes the International\nand UK editions of SNOMED CT in less than 5 minutes; you can have a server\nrunning seconds after that.\n\nIt replaces previous similar tools I wrote in Java and Go and is designed to fit into a wider architecture with\nidentifier resolution, mapping and semantics as first-class abstractions.\n\nRather than a single monolithic terminology server, it is entirely reasonable to build\nmultiple services, each providing an API around a specific edition or version of SNOMED CT,\nand to use an API gateway to manage client access. `Hermes` is lightweight and designed\nto be composed with other services.\n\nIt is part of my PatientCare v4 development; previous versions have been operational within NHS Wales\nsince 2007.\n\nYou can have a working terminology server running by typing only a few lines at a terminal. There's no need\nfor any special hardware, or any special dependencies such as setting up your own elasticsearch or solr cluster.\nYou just need a filesystem! Many other tools take hours to import the SNOMED data; you'll be finished in less than\n10 minutes!\n\nA HL7 FHIR terminology facade is under development : [hades](https://github.com/wardle/hades).\nThis exposes the functionality available in `hermes` via a FHIR terminology API. This already\nsupports search and autocompletion using the $expand operation.\n\n# Table of contents\n\n- [Quickstart](#quickstart)\n- [Common questions](#common-questions)\n  - [What can I do with `hermes`?](#what-can-i-do-with-hermes)\n  - [Difference to a 'national terminology server'?](#how-is-this-different-to-a-national-terminology-service)\n  - [Localisation](#localisation)\n  - [Can I get support?](#can-i-get-support)\n  - [Why are you building so many repositories?](#why-are-you-building-so-many-repositories)\n  - [What are you using `hermes` for?](#what-are-you-using-hermes-for)\n  - [What is this graph stuff you're doing?](#what-is-this-graph-stuff-youre-doing)\n  - [Is `hermes` fast?](#is-hermes-fast)\n  - [Can I use `hermes` with containers?](#can-i-use-hermes-with-containers)\n  - [Can I use `hermes` on Apple Silicon?](#can-i-use-hermes-on-apple-silicon)\n  - [Can I use `hermes` on other architectures or operating systems such as FreeBSD?](#can-i-use-hermes-on-other-architectures-or-operating-systems-such-as-freebsd)\n- [Documentation](#documentation)\n  - [A. Download and build a terminology service](#a-how-to-download-and-build-a-terminology-service)\n    - [1. Download and install at least one distribution](#1-download-and-install-at-least-one-distribution)\n    - [2. Index](#2-index)\n    - [3. Compact (optional)](#3-compact-database-optional)\n    - [4. Run a REPL (optional)](#4-run-a-repl-optional)\n    - [5. Get status (optional)](#5-get-the-status-of-your-installed-index)\n    - [6. Run a HTTP terminology server](#6-run-a-terminology-web-service)\n    - [7. Run a FHIR terminology server](#7-run-a-hl7-fhir-terminology-web-service)\n  - [B. Endpoints for the HTTP server](#b-endpoints-for-the-http-terminology-server)\n    - [Get a single concept](#get-a-single-concept-)\n    - [Get extended information about a single concept](#get-extended-information-about-a-single-concept)\n    - [Get properties for a single concept](#get-properties-for-a-single-concept)\n    - [Search](#search)\n    - [Expanding SNOMED ECL (Expression Constraint Language)](#expanding-ecl-without-search)\n    - [Crossmap to and from SNOMED CT - e.g. ICD-10](#crossmap-to-and-from-snomed-ct)\n    - [Map a concept into a reference set](#map-a-concept-into-a-reference-set)\n  - [C. Embed into another application as a JVM library](#c-embed-into-another-application)\n  - [D. Development notes](#d-development)\n  - [E. Backwards compatibility and versioning](#e-backwards-compatibility-and-versioning)\n\n# Quickstart\n\nYou can have a terminology server running in minutes.\nFull documentation is below, but here is a quickstart.\n\nBefore you begin, you will need to have Java version 11 or greater installed.\n\n### 1. Download hermes\n\nYou can choose to run a jar file by downloading a release and running using Java, \nor run from source code using Clojure:\n\n##### Download a release and run using Java\n\nDownload the latest release from https://github.com/wardle/hermes/releases\nFor simplicity, I've renamed the download jar file to 'hermes.jar' for these\nexamples\n\nRun the jar file using:\n\n```shell\njava -jar hermes.jar\n```\n\nWhen run without parameters, you will be given help text.\n\nIn all examples below, `java -jar hermes.jar` is equivalent to `clj -M:run` and vice versa.\n\n##### Run from source code using Clojure\n\nInstall clojure. e.g on Mac OS X:\n\n```shell\nbrew install clojure\n```\n\nThen clone the repository, change directory and run:\n\n```shell\ngit clone https://github.com/wardle/hermes\ncd hermes\nclj -M:run\n```\n\nWhen run without parameters, you will be given help text.\n\nIn all examples below, `java -jar hermes.jar` is equivalent to `clj -M:run` and vice versa.\n\n### 2. Download and install one or more distributions\n\nYou will need to download distributions from a National Release Centre.\n\nHow to do this will principally depend on your location. \n\nFor more information, see [https://www.snomed.org/snomed-ct/get-snomed](https://www.snomed.org/snomed-ct/get-snomed).\nSNOMED provide a [Member Licensing and Distribution Centre](https://mlds.ihtsdotools.org/#/landing).\n\nIn the United States, the National Library of Medicine (NLM) has [more information](https://www.nlm.nih.gov/healthit/snomedct/snomed_licensing.html). For example, the SNOMED USA edition is available from [https://www.nlm.nih.gov/healthit/snomedct/us_edition.html](https://www.nlm.nih.gov/healthit/snomedct/us_edition.html).\n\nIn the United Kingdom, you can download a distribution from NHS Digital using the [TRUD service](https://isd.digital.nhs.uk).\n\n`hermes` also provides automated downloads for a range of distributions worldwide using the [MLDS](https://mlds.ihtsdotools.org).\n\nIf you've downloaded a distribution manually, unzip and import using one of these commands:\n\n```shell\njava -jar hermes.jar --db snomed.db import ~/Downloads/snomed-2021/\n```\nor\n```shell\nclj -M:run --db snomed.db import ~/Downloads/snomed-2021/\n```\n\nIf you're a UK user and want to use automatic downloads, you can do this\n\n```shell\njava -jar hermes.jar --db snomed.db install --dist uk.nhs/sct-clinical --dist uk.nhs/sct-drug-ext --api-key trud-api-key.txt --cache-dir /tmp/trud\n```\n```shell\nclj -M:run --db snomed.db install --dist uk.nhs/sct-clinical --dist uk.nhs/sct-drug-ext --api-key trud-api-key.txt --cache-dir /tmp/trud\n```\n\nEnsure you have a [TRUD API key](https://isd.digital.nhs.uk/trud3/user/guest/group/0/home).\n\nThis will download both the UK clinical edition and the UK drug extension. If you're a UK user, I'd recommend installing both.\n\nWhen running interactively at the command-line, you can use `--progress` to turn on \nprogress reporting when downloading items.\n\ne.g.\n```shell\njava -jar hermes.jar --progress --db snomed.db install --dist uk.nhs/sct-clinical --dist uk.nhs/sct-drug-ext --api-key trud-api-key.txt --cache-dir /tmp/trud\n```\n```shell\nclj -M:run --progress --db snomed.db install --dist uk.nhs/sct-clinical --dist uk.nhs/sct-drug-ext --api-key trud-api-key.txt --cache-dir /tmp/trud\n```\n\n\nYou can download a specific edition using an ISO 6801 formatted date:\n\n\n```shell\njava -jar hermes.jar --db snomed.db install --dist uk.nhs/sct-clinical --api-key trud-api-key.txt --cache-dir /tmp/trud --release-date 2021-03-24\njava -jar hermes.jar --db snomed.db install --dist uk.nhs/sct-drug-ext --api-key trud-api-key.txt --cache-dir /tmp/trud --release-date 2021-03-24\n```\nor\n```shell\nclj -M:run --db snomed.db install --dist uk.nhs/sct-clinical --api-key trud-api-key.txt --cache-dir /tmp/trud --release-date 2021-03-24\nclj -M:run --db snomed.db install --dist uk.nhs/sct-drug-ext --api-key trud-api-key.txt --cache-dir /tmp/trud --release-date 2021-03-24\n```\n\nThese are most useful for building reproducible container images.\nYou can get a list of available UK versions by simply looking at the TRUD website, or using:\n\n```shell\njava -jar hermes.jar available --dist uk.nhs/sct-clinical --api-key trud-api-key.txt --cache-dir /tmp/trud\n```\nor\n```shell\nclj -M:run available --dist uk.nhs/sct-clinical --api-key trud-api-key.txt --cache-dir /tmp/trud\n```\n\n\nMy tiny i5 'NUC' machine takes 1 minute to import the UK edition of SNOMED CT and a further minute to import the UK\ndictionary\nof medicines and devices.\n\nIf you have an account with the [MLDS](https://mlds.ihtsdotools.org), then you can use that website to download\na distribution manually, or `hermes` can do it for you. \n\n```shell\njava -jar hermes.jar available\n```\nor\n```shell\nclj -M:run available\n```\n\nFor example, to install the Irish distribution:\n\n```shell\njava -jar hermes.jar --db snomed.db install --dist ie.mlds/285520 --username xxxx --password password.txt\n```\nor \n```shell\nclj -M:run --db snomed.db install --dist ie.mlds/285520 --username xxxx --password password.txt\n```\n\nYou can request a specific version by providing `--release-date` as an option.\nYou will need to have a licence for the distribution you are trying to download,\nor you will get an 'invalid credentials' error. \n\n\n### 3. Index and compact\n\nYou must index. Compaction is not mandatory, but advisable.\n\n```shell\njava -jar hermes.jar --db snomed.db index compact\n```\nor\n```shell\nclj -M:run --db snomed.db index compact\n```\n\nMy machine takes 6 minutes to build the search indices and 20 seconds to compact the database.\n\n### 4. Run a server!\n\n```shell\njava -jar hermes.jar --db snomed.db --port 8080 --bind-address 0.0.0.0 serve\n```\nor\n```shell\nclj -M:run --db snomed.db --port 8080 serve\n```\n\nYou can use [hades](https://github.com/wardle/hades) with the 'snomed.db' index\nto give you a FHIR terminology server.\n\nMore detailed documentation is included below.\n\nYou can use multiple commands at the same time.\n\nFor example:\n```shell\njava -jar hermes.jar --api-key trud-api-key.txt --db snomed.db install uk.nhs/sct-clinical index compact serve \n```\nWill download, extract, import, index and compact a database, and then run a server.\n\n\n# Common questions\n\n### What can I do with `hermes`?\n\n`hermes` provides a simple library, and optionally a microservice, to help\nyou make use of SNOMED CT.\n\nA library can be embedded into your application; this is easy using Clojure or\nJava or any other language running on the JVM. You make calls using the API just \nas you'd use any regular library. \n\nA microservice runs independently and you make use of the data and software\nby making an API call over the network. This makes the functionality available\nto any software code that can use HTTP and JSON, such as C#, Python or R.\n\nLike all `PatientCare` components, you can use `hermes` in either way.\nSometimes, when you're starting out, it's best to use as a library but larger\nprojects and larger installations will want to run their software components\nindependently, optimising for usage patterns, resilience, reliability and\nrate of change.\n\nMost people who use a terminology run a server and make calls over the network.\n\n### How is this different to a national terminology service?\n\nPreviously, I implemented SNOMED CT within an EPR.\nLater I realised how important it was to build it as a separate module;\nI created terminology servers in Java, and then later in Go;\n`hermes` is written in Clojure. In the UK,  the different health services in England\nand Wales have procured a centralised national terminology server. \nWhile I support the provision of a national terminology server for convenience, \nI think it's important to recognise that it is the *data* that matters most. We \nneed to cooperate and collaborate on semantic interoperability, but the software \nservices that make use of those data can be centralised or distributed; when I \ndo analytics, I can't see me making server round-trips for every check of \nsubsumption! That would be silly; I've been using SNOMED for analytics for \nlonger than most; you need flexibility in provisioning terminology services. I \nwant tooling that can both provide services at scale, while is capable of \nrunning on my personal computers as well. \n\nUnlike other available terminology servers, `hermes` is lightweight and has no \nother dependencies except a filesystem, which can be read-only when in \noperation. This makes it ideal for use in situations such as a data pipeline, \nperhaps built upon Apache Kafka - with `hermes`, SNOMED analytics capability can \nbe embedded anywhere. \n\nI don't believe in the idea of uploading codesystems and value sets in place.\nMy approach to versioning is to run different services; I simply deploy new\nservices and switch at the API gateway level.\n\n### Localisation\n\nSNOMED CT is distributed across the world. The base distribution is the\nInternational release, but your local distribution will include this together\nwith local data. Local data will include region-specific language reference\nsets.\n\nThe core SNOMED API relating to concepts and their meaning is not affected\nby issues of locale. Locale is used to derive the synonyms for any given\nconcept. There should be a single preferred synonym for every concept in a\ngiven language reference set. \n\nWhen you build a database, the search index caches the preferred synonyms using\nthe installed locales.\n\n### Can I get support?\n\nYes. Raise an issue, or more formal support options are available on request,\nincluding a fully-managed service.\n\n### Why are you building so many repositories?\n\nYes, I have a lot of repositories at [https://github.com/wardle](https://github.com/wardle),\nproviding functionality such as:\n\n- Integration with UK NHS services via [concierge](https://github.com/wardle/concierge)\n- UK dictionary of medicines and devices via [dmd](https://github.com/wardle/dmd)\n- socioeconomic deprivation data via [deprivare](https://github.com/wardle/deprivare)\n- UK reference data updates via [trud](https://github.com/wardle/trud)\n- UK organisational data via [clods](https://github.com/wardle/clods)\n- UK geographical data via the NHS postcode directory [nhspd](https://github.com/wardle/nhspd)\n\n- I previously built an electronic patient record as a monolithic application with\nmany of these subsystems as modules of that larger system. Over time, I'm\nsplitting them out into their own more independent modules. \n\nI see the future of building health and care applications as simply composing\ntogether different modules of core well-tested functionality to solve user \nproblems. \n\nSmall modules of functionality are easier to develop, easier to understand,\neasier to test and easier to maintain. I design modules to be composable so that I can\nstitch different components together in order to solve problems.\n\nIn larger systems, it is easy to see code rotting. Dependencies become outdated and\nthe software becomes difficult to change easily because of software that depend\non it. Small, well-defined modules are much easier to build and are less likely\nto need ongoing changes over time; my goal is to need to update modules only\nin response to changes in *domain* not software itself.\nI aim for an accretion of functionality.\n\nIt is very difficult to 'prove' software is working as designed when there are\nlots of moving parts.\n\n### What are you using `hermes` for?\n\nI have embedded it into clinical systems; I use it for a fast autocompletion\nservice so users start typing and the diagnosis, or procedure, or occupation,\nor ethnicity, or whatever, pops up. Users don't generally know they're using\nSNOMED CT. I use it to populate pop-ups and drop-down controls, and I use it\nfor decision support to switch functionality on and off in my user interface -\ne.g. does this patient have a type of 'x' such as motor neurone disease - as well\nas analytics. A large number of my academic publications are as a result of\nusing SNOMED in analytics.\n\n### What is this graph stuff you're doing?\n\nI think health and care data are and always will be heterogeneous, incomplete and difficult to process.\nI do not think trying to build entities or classes representing our domain works at scale; it is\nfine for toy applications and trivial data modelling such as e-observations, but classes and\nobject-orientation cannot scale across such a complete and disparate environment. Instead, I\nfind it much easier to think about first-class properties - entity - attribute - value - and\nuse such triples as a way of building and navigating a complex, hierarchical graph.\n\nI am using a graph API in order to decouple subsystems and can now navigate from clinical data\ninto different types of reference data seamlessly. For example, with the same backend data, I can view\nan x.500 representation of a practitioner, or a FHIR R4 Practitioner resource model.\nThe key is to recognise that identifier resolution and mapping are first class problems within\nthe health and care domain. Similarly, I think the semantics of reading data are very different to\none of writing data. I cannot shoehorn health and care data into a REST model in which we read and write\nto resources representing the type. Instead, just as in real-life, we record event data which can effect change.\nIn the end, it is all data.\n\n### Is `hermes` fast?\n\nHermes benefits from the speed of the libraries it uses, particularly [Apache Lucene](https://lucene.apache.org)\nand [lmdb](https://www.symas.com/lmdb), and from some fundamental design\ndecisions including read-only operation and memory-mapped data files. It provides a\nHTTP server using the lightweight and reliable [jetty web server](https://www.eclipse.org/jetty/).\n\nI have a small i3 NUC server on my local wifi network, and here is an example of\nload testing, in which users are typing 'mnd' and expecting an autocompletion:\n\n```shell\nmark@jupiter classes % wrk -c300 -t12 -d30s --latency  'http://nuc:8080/v1/snomed/search?s=mnd'\nRunning 30s test @ http://nuc:8080/v1/snomed/search?s=mnd\n  12 threads and 300 connections\n  Thread Stats   Avg      Stdev     Max   +/- Stdev\n    Latency    40.36ms   19.97ms 565.73ms   92.08%\n    Req/Sec   632.19     66.79     0.85k    68.70%\n  Latency Distribution\n     50%   38.76ms\n     75%   45.93ms\n     90%   54.09ms\n     99%   79.31ms\n  226942 requests in 30.09s, 125.75MB read\nRequests/sec:   7540.91\nTransfer/sec:      4.18MB\n```\n\nThis uses 12 threads to make 300 concurrent HTTP connections.\nOn 99% of occasions, that would provide a fast enough response for\nautocompletion (\u003c79ms). Of course, that is users typing at exactly the same time,\nso a single instance could support more concurrent users than that. Given its design, Hermes is designed to easily scale\nhorizontally, because you can simply run more servers and load balance across\nthem. Of course, these data are fairly crude, because in real-life you'll be\ndoing more complex concurrent calls. In real deployments, I've only needed one instance for hundreds of concurrent\nusers, but it is nice to know I can scale easily.\n\n### Can I use `hermes` with containers?\n\nYes. It is designed to be containerised, although I have a mixture of different\napproaches in production, including running from source code directly. I would\nusually advise creating a volume and populating that with data, and then\npermitting read-only access to your service containers. A shared volume can be\nmemory mapped by multiple running instances and provide high scalability.\n\nThere are some examples of [different configurations available](https://github.com/wardle/hermes-docker).\n\n### Can I use `hermes` on Apple Silicon? \n\nYes. There are three options. \n\nThe first is to use Rosetta and run an x86 Java SDK and this will look for an x86 LMDB library already bundled with `hermes`.\n\nThe other two options install a native aarch64 LMDB library, and make it available to\n`hermes`. The best performance will be gained from using a native library.\n\nThe next version of `lmdbjava` will include a pre-built lmdb binary for ARM on\nMac OS X, so these steps will become unnecessary and `hermes` will work on \nmultiple architectures and operating systems without needing these steps.  \n\n##### Option 1. Install an x86 Java SDK and run using that (Rosetta). \n\nFor example, you can get a list of installed JDKs:\n```shell\n$ /usr/libexec/java_home -V\n\n    11.0.17 (arm64) \"Amazon.com Inc.\" - \"Amazon Corretto 11\" /Users/mark/Library/Java/JavaVirtualMachines/corretto-11.0.17/Contents/Home\n    11.0.14.1 (x86_64) \"Azul Systems, Inc.\" - \"Zulu 11.54.25\" /Users/mark/Library/Java/JavaVirtualMachines/azul-11.0.14.1-1--x86/Contents/Home\n```\n\nChoose an SDK and check what we are using\n```shell\n$ export JAVA_HOME=$(/usr/libexec/java_home -v 11.0.14.1)\n$ clj -M -e '(System/getProperty \"os.arch\")'\n\n\"x86_64\"\n\n```\n##### Option 2. Install the lmdb library for your architecture\n\nHere I use homebrew on my mac:\n\n```shell\nbrew install lmdb\nbrew list lmdb\n```\n\nOnce you have a native LMDB installed on your machine, you can reference it \nfrom the command line:\n\n```shell\njava -Dlmdbjava.native.lib=/opt/homebrew/Cellar/lmdb/0.9.30/lib/liblmdb.dylib -jar target/hermes-1.2.1151.jar --db snomed.db status\n```\nor\n```shell\nclj -J-Dlmdbjava.native.lib=/opt/homebrew/Cellar/lmdb/0.9.30/lib/liblmdb.dylib -M:run --db snomed.db status\n```\n\n##### Option 3. Build the lmdb library for your architecture (ie arm64).\n\nInstall the xcode command line tools, if they are not already installed\n```shell\nxcode-select --install\n```\n\nAnd then download lmdb and build:\n```shell\ngit clone --depth 1 https://git.openldap.org/openldap/openldap.git\ncd openldap/libraries/liblmdb\nmake -e SOEXT=.dylib\nmkdir -p ~/Library/Java/Extensions\ncp liblmdb.dylib ~/Library/Java/Extensions\n```\n\nIn this example, rather than specifying the location of the library at the \ncommand line, I'm just copying the library to a well known location.\n\nOnce this native library is copied, you can use `hermes` natively using an arm64 based JDK.\n\n```shell\n$ export JAVA_HOME=$(/usr/libexec/java_home -v 11.0.17)\n$ clj -M -e '(System/getProperty \"os.arch\")'\n\n\"aarch64\"\n```\n\n### Can I use `hermes` on other architectures or operating systems such as FreeBSD?\n\nIf `hermes` does not already contain a pre-built binary for your operating system and architecture,\nyou simply need to install lmdb yourself. You may need to also tell `hermes` where to \nfind the native library.\n\ne.g. on FreeBSD:\n\n```shell\n$ pkg info -lx lmdb | grep liblmdb\n\n\t/usr/local/lib/liblmdb.a\n\t/usr/local/lib/liblmdb.so\n\t/usr/local/lib/liblmdb.so.0\n```\n\n```shell\njava -Dlmdbjava.native.lib=/usr/local/lib/liblmdb.so -jar target/hermes-1.2.1151.jar --db snomed.db status\n```\nor\n```shell\nclj -J-Dlmdbjava.native.lib=/usr/local/lib/liblmdb.so -M:run --db snomed.db status\n```\n\n\n# Documentation\n\n### A. How to download and build a terminology service\n\nEnsure you have a pre-built jar file, or the source code checked out from github. See below for build instructions.\n\nI'd recommend installing clojure and running using source code but use the pre-built jar file if you prefer.\n\n#### 1. Download and install at least one distribution.\n\nIf your local distributor is supported, `hermes` can do this automatically for you.\nOtherwise, you will need to download your local distribution(s) manually.\n\n##### i) Use a registered SNOMED CT distributor to automatically download and import\n\nYou can see distributions that are available for automatic installation:\n\n```shell\njava -jar hermes.jar available\n```\n\n```shell\nclj -M:run available\n```\n\nThe basic command is:\n\n```shell\nclj -M:run --db snomed.db install --dist \u003cdistribution-identifier\u003e [properties] \n```\n\nor if you are using a precompiled jar:\n\n```shell\njava -jar hermes.jar --db snomed.db install --dist \u003cdistribution-identifier\u003e [properties]\n```\n\nThe distribution, as defined by `distribution-identifier`, will be downloaded\nand imported to the file-based database `snomed.db`.\n\n| Distribution-identifier | Description                                        |\n|-------------------------|----------------------------------------------------|\n| uk.nhs/sct-clinical     | UK SNOMED CT clinical - incl international release |\n| uk.nhs/sct-drug-ext     | UK SNOMED CT drug extension - incl dm+d            |\n| uk.nhs/sct-monolith     | UK SNOMED CT monolith edition: includes everything |\n\nAt the time of writing, the UK monolith edition is labelled as `Draft for Trial Use`.\n\nEach distribution might require custom configuration options. \n\nFor example, the UK releases use the NHS Digital TRUD API, and so you need to\npass in the following parameters:\n\n- `--api-key`   : path to a file containing your NHS Digital TRUD api key\n- `--cache-dir` : directory to use for downloading and caching releases\n\nFor example, these commands will download, cache and install the International\nrelease, the UK clinical edition and the UK drug extension:\n\n```shell\nclj -M:run --db snomed.db install uk.nhs/sct-monolith --api-key=trud-api-key.txt --cache-dir=/tmp/trud\n```\n\n`hermes` will tell you what configuration parameters are required:\n\n```shell\njava -jar hermes.jar install --dist uk.nhs/sct-clinical --help\n```\nor\n```shell\nclj -M:run install --dist uk.nhs/sct-clinical --help\n```\n\nFor the UK, TRUD requires an `--api-key`, which should be a path to a file \ncontaining your API key for that service.\n\nYou will need to provide different configuration options if `hermes`\nis using the MLDS to download distributions:\n\n```shell\njava -jar hermes.jar install --dist nl.mlds/128785 --help\n```\nor\n```shell\nclj -M:run install --dist nl.mlds/128785 --help\n```\n\nFor MLDS downloads, you will need to provide `--username` and `--password` options. \nThe password should be the path to a file containing your password. This makes\nit safer to use in automated pipelines and less likely to be accidentally logged.\n\n##### ii) Download and install SNOMED CT distribution file(s) manually\n\nDepending on where you live in the World, download the most appropriate\ndistribution(s) for your needs.\n\nIn the UK, we can obtain these from [TRUD](https://isd.digital.nhs.uk).\n\nFor example, you can download the UK \"Clinical Edition\", containing the International and UK clinical distributions\nas part of TRUD pack 26/subpack 101.\n\n* [https://isd.digital.nhs.uk/trud3/user/authenticated/group/0/pack/26/subpack/101/releases](https://isd.digital.nhs.uk/trud3/user/authenticated/group/0/pack/26/subpack/101/releases)\n\nOptionally, you can also download the UK SNOMED CT drug extension, that contains the dictionary of medicines and\ndevices (dm+d) is available\nas part of TRUD pack 26/subpack 105.\n\n* [https://isd.digital.nhs.uk/trud3/user/authenticated/group/0/pack/26/subpack/105/releases](https://isd.digital.nhs.uk/trud3/user/authenticated/group/0/pack/26/subpack/105/releases)\n\nOnce you have downloaded what you need, unzip them to a common directory and\nthen you can use `hermes` to create a file-based database.\n\nIf you are running using the jar file:\n\n```shell\njava -jar hermes.jar --db snomed.db import ~/Downloads/snomed-2020\n```\n\nIf you are running from source code:\n\n```shell\nclj -M:run --db snomed.db import ~/Downloads/snomed-2020/\n```\n\nThe import of both International and UK distribution files takes\na total of less than 3 minutes on my machine.\n\n\n#### 2. Index\n\nFor correct operation, indices are needed for components, search and reference\nset membership.\n\nRun\n\n```shell\njava -jar hermes.jar --db snomed.db index\n```\n\nor\n\n```shell\nclj -M:run --db snomed.db index\n```\n\nThis will build the indices; it takes about 6 minutes on my machine.\n\n#### 3. Compact database (optional).\n\nThis reduces the file size and takes 20 seconds.\nThis is an optional step, but recommended.\n\n```shell\njava -jar hermes.jar --db snomed.db compact\n```\n\nor\n\n```shell\nclj -M:run --db snomed.db compact\n```\n\n#### 4. Run a REPL (optional)\n\nWhen I first built terminology tools, either in Java or in Go, I needed to\nalso build a custom command-line interface in order to explore the ontology.\nThis is not necessary as most developers using Clojure quickly learn the value\nof the REPL; a read-evaluate-print-loop in which one can issue arbitrary\ncommands to execute. As such, one has a full Turing-complete language (a lisp)\nin which to explore the domain.\n\nI usually use a REPL from within my IDE so run a REPL from there.\nYou can run an nREPL server, which makes it easy to connect from other editors, such as emacs or neovim:\n\n```\nclj -M:dev:nrepl-server\n```\n\nYou can run a REPL and use the terminology services interactively at the command-line, but I\nwould not advise this. It is much better to use a REPL within your editor.\n\n```\nclj -M:dev\n```\n\n#### 5. Get the status of your installed index\n\nYou can obtain status information about any index by using:\n\n```shell\nclj -M:run --db snomed.db status --format json\n```\n\nResult:\n\n```json\n{\"releases\":\n[\"SNOMED Clinical Terms version: 20220731 [R] (July 2022 Release)\",\n  \"35.6.0_20230315000001 UK drug extension\",\n  \"35.6.0_20230315000001 UK clinical extension\"],\n  \"locales\":[\"en-GB\", \"en-US\"],\n  \"components\":\n  {\"concepts\":1068735,\n    \"descriptions\":3050621,\n    \"relationships\":7956235,\n    \"concrete-values\":33349,\n    \"refsets\":541,\n    \"refset-items\":13349472,\n    \"indices\":\n    {\"descriptions-concept\":3050621,\n      \"concept-parent-relationships\":4737884,\n      \"concept-child-relationships\":4737884,\n      \"component-refsets\":10595249,\n      \"associations\":1254384,\n      \"descriptions-search\":3050621,\n      \"members-search\":13349472}}}\n```\nIn this example, you can see I have the July 2022 International release, with the \nUK clinical and drug extensions from March 2023.\nGiven that these releases have been imported, hermes recognises it can support\nthe locales en-GB and en-US. For completeness, detailed statistics on \ncomponents and indices are also provided. Additional options are available:\n\n```shell\njava -jar hermes.jar --db snomed.db status --help\n```\nor\n```shell\nclj -M:run --db snomed.db status --help\n```\n\n#### 6. Run a terminology web service\n\nBy default, data are returned using json, but you can\nrequest [edn](https://github.com/edn-format/edn) by simply adding \"Accept:application/edn\" in the request header.\n\n```\njava -jar hermes.jar --db snomed.db --port 8080 --bind-address 0.0.0.0 serve \n```\n\nor\n\n```\nclj -M:run --db snomed.db --port 8080 --bind-address 0.0.0.0 serve\n```\n\nThere are a number of configuration options for `serve`:\n\n```shell\njava -jar hermes.jar serve --help\n```\nor\n```shell\nclj -M:run serve --help\n```\n\n```shell\nUsage: hermes [options] serve\n\nStart a terminology server\n\nOptions:\n      --allowed-origin \"*\" or ORIGIN    []     Set CORS policy, with \"*\" or hostname\n      --allowed-origins \"*\" or ORIGINS         Set CORS policy, with \"*\" or comma-delimited hostnames\n  -a, --bind-address BIND_ADDRESS              Address to bind\n  -d, --db PATH                                Path to database directory\n  -h, --help\n      --locale LOCALE                   en-GB  Set default / fallback locale\n  -p, --port PORT                       8080   Port number\n```\n\n* `--bind-address` is optional. You may want to use `--bind-address 0.0.0.0` particularly if you are running in a container.\n* `--allowed-origins` is optional. You could use `--allowed-origins \"*\"` or `--allowed-origins example.com,example.net`\n* `--allowed-origin example.com --allowed-origin example.net` is equivalent to `--allowed-origins example.com,example.net`.\n* `--allowed-origin \"*\"` is equivalent to `--allowed-origins \"*\"`\n* `--locale` sets the default locale. This is used as a default if clients do not specify their preference. e.g. `--locale=en-GB`\n\nBy default, the default locale will be determined by looking at which language reference sets are installed.\n\n#### 7. Run a HL7 FHIR terminology web service\n\nYou can use [`hades`](https://github.com/wardle/hades) together with the \nfiles you have just created to run a FHIR R4 terminology server.\n\n### B. Endpoints for the HTTP terminology server\n\nThere are a range of endpoints. \n\nI have a very small, low-powered server (\u003c$3/mo) available for demonstration purposes. It is not intended for production use. \n\nHere are some examples:\n\n* [/v1/snomed/concepts/24700007](http://128.140.5.148:8080/v1/snomed/concepts/24700007) - basic data about a single concept\n* [/v1/snomed/concepts/24700007/descriptions](http://128.140.5.148:8080/v1/snomed/concepts/24700007/descriptions) - all descriptions for concept\n* [/v1/snomed/concepts/24700007/preferred](http://128.140.5.148:8080/v1/snomed/concepts/24700007/preferred) : preferred description for concept. Use an `Accept-Language` header to choose your locale (see below).\n* [/v1/snomed/concepts/24700007/extended](http://128.140.5.148:8080/v1/snomed/concepts/24700007/extended) : an extended concept \n* [/v1/snomed/concepts/1231295007/properties?expand=1](http://128.140.5.148:8080/v1/snomed/concepts/24700007/properties?expand=1) : properties for a concept\n* [/v1/snomed/concepts/586591000000100/historical](http://128.140.5.148:8080/v1/snomed/concepts/586591000000100/historical) - historical associations for this concept\n* [/v1/snomed/concepts/24700007/refsets](http://128.140.5.148:8080/v1/snomed/concepts/24700007/refsets) - refsets to which this concept is a member\n* [/v1/snomed/concepts/24700007/map/999002271000000101](http://128.140.5.148:8080/v1/snomed/concepts/24700007/map/999002271000000101) - crossmap to alternate codesystem (ICD-10 in this example)\n* [/v1/snomed/concepts/24700007/map/991411000000109](http://128.140.5.148:8080/v1/snomed/concepts/24700007/map/991411000000109) - map into a SNOMED subset (the UK emergency unit refset in this example)\n* [/v1/snomed/crossmap/999002271000000101/G35X](http://128.140.5.148:8080/v1/snomed/crossmap/999002271000000101/G35X) - cross from an alternate codesystem (ICD-10 in this example)\n* [/v1/snomed/search?s=mnd\\\u0026constraint=\u003c64572001\u0026maxHits=5](http://128.140.5.148:8080/v1/snomed/search?s=mnd\\\u0026constraint=\u003c64572001\u0026maxHits=5) - search for a term, constrained by SNOMED ECL expression\n* [/v1/snomed/expand?ecl= \u003c19829001 AND \u003c301867009\u0026includeHistoric=true](http://128.140.5.148:8080/v1/snomed/expand?ecl=%20%3C19829001%20AND%20%3C301867009\u0026includeHistoric=true) - expand SNOMED ECL expression\n\n**WARNING**\n\nThe HTTP API returns data formatted as either JSON or EDN. Identifiers, such as concept or description identifiers, \nin SNOMED CT are [64-bit positive integers](https://confluence.ihtsdotools.org/display/DOCRELFMT/6+SNOMED+CT+Identifiers).\nThe JSON specification does not limit the size of numeric types, but some implementations struggle to properly manage\nvery large numbers and can silently truncate numbers. Most implementations have no such difficulty; if your client\nlibrary or platform does not properly handle large numbers in JSON, there is usually a way to configure your parser\nto work correctly. For example, in JavaScript, you can use a [reviver parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#using_the_reviver_parameter).\n\nHermes could offer a per-server, or per-request configuration to stringify identifiers when output to JSON to help\nbroken client implementations. If this applies to you, please join the [discussion](https://github.com/wardle/hermes/issues/50).\n\n##### Get a single concept \n\n```shell\nhttp '127.0.0.1:8080/v1/snomed/concepts/24700007'\n```\n\n```json\n{\n  \"active\": true,\n  \"definitionStatusId\": 900000000000074008,\n  \"effectiveTime\": \"2002-01-31\",\n  \"id\": 24700007,\n  \"moduleId\": 900000000000207008\n}\n```\n\n```edn\n\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/concepts/24700007](http://128.140.5.148:8080/v1/snomed/concepts/24700007)\n\nYou'll want to use the other endpoints much more frequently.\n\n##### Get extended information about a single concept\n\n\n```shell\nhttp 127.0.0.1:8080/v1/snomed/concepts/24700007/extended\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/concepts/24700007/extended](http://128.140.5.148:8080/v1/snomed/concepts/24700007/extended)\n\nThe result is an extended concept definition - all the information\nneeded for inference, logic and display. For example, at the client\nlevel, we can then check whether this is a type of demyelinating disease\nor is a disease affecting the central nervous system without further\nserver round-trips. Each relationship also includes the transitive closure tables for that\nrelationship, making it easier to execute logical inference.\nNote how the list of descriptions includes a convenient\n`acceptableIn` and `preferredIn` so you can easily display the preferred\nterm for your locale. If you provide an Accept-Language header, then\nyou will also get a preferredDescription that is the best choice for those\nlanguage preferences given what is installed.\n\n```shell\nHTTP/1.1 200 OK\nContent-Type: application/json\nDate: Mon, 08 Mar 2021 22:01:13 GMT\n\n{\n    \"concept\": {\n        \"active\": true,\n        \"definitionStatusId\": 900000000000074008,\n        \"effectiveTime\": \"2002-01-31\",\n        \"id\": 24700007,\n        \"moduleId\": 900000000000207008\n    },\n    \"descriptions\": [\n        {\n            \"acceptableIn\": [],\n            \"active\": true,\n            \"caseSignificanceId\": 900000000000448009,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2017-07-31\",\n            \"id\": 41398015,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"refsets\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"term\": \"Multiple sclerosis\",\n            \"typeId\": 900000000000013009\n        },\n        {\n            \"acceptableIn\": [],\n            \"active\": false,\n            \"caseSignificanceId\": 900000000000020002,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2002-01-31\",\n            \"id\": 41399011,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [],\n            \"refsets\": [],\n            \"term\": \"Multiple sclerosis, NOS\",\n            \"typeId\": 900000000000013009\n        },\n        {\n            \"acceptableIn\": [],\n            \"active\": false,\n            \"caseSignificanceId\": 900000000000020002,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2015-01-31\",\n            \"id\": 41400016,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [],\n            \"refsets\": [],\n            \"term\": \"Generalized multiple sclerosis\",\n            \"typeId\": 900000000000013009\n        },\n        {\n            \"acceptableIn\": [],\n            \"active\": false,\n            \"caseSignificanceId\": 900000000000020002,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2015-01-31\",\n            \"id\": 481990016,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [],\n            \"refsets\": [],\n            \"term\": \"Generalised multiple sclerosis\",\n            \"typeId\": 900000000000013009\n        },\n        {\n            \"acceptableIn\": [],\n            \"active\": true,\n            \"caseSignificanceId\": 900000000000448009,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2017-07-31\",\n            \"id\": 754365011,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"refsets\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"term\": \"Multiple sclerosis (disorder)\",\n            \"typeId\": 900000000000003001\n        },\n        {\n            \"acceptableIn\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"active\": true,\n            \"caseSignificanceId\": 900000000000448009,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2017-07-31\",\n            \"id\": 1223979019,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [],\n            \"refsets\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"term\": \"Disseminated sclerosis\",\n            \"typeId\": 900000000000013009\n        },\n        {\n            \"acceptableIn\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"active\": true,\n            \"caseSignificanceId\": 900000000000017005,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2003-07-31\",\n            \"id\": 1223980016,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [],\n            \"refsets\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"term\": \"MS - Multiple sclerosis\",\n            \"typeId\": 900000000000013009\n        },\n        {\n            \"acceptableIn\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"active\": true,\n            \"caseSignificanceId\": 900000000000017005,\n            \"conceptId\": 24700007,\n            \"effectiveTime\": \"2003-07-31\",\n            \"id\": 1223981017,\n            \"languageCode\": \"en\",\n            \"moduleId\": 900000000000207008,\n            \"preferredIn\": [],\n            \"refsets\": [\n                900000000000509007,\n                900000000000508004,\n                999001261000000100\n            ],\n            \"term\": \"DS - Disseminated sclerosis\",\n            \"typeId\": 900000000000013009\n        }\n    ],\n    \"directParentRelationships\": {\n        \"116676008\": [\n            409774005,\n            32693004\n        ],\n        \"116680003\": [\n            6118003,\n            414029004,\n            39367000\n        ],\n        \"363698007\": [\n            21483005\n        ],\n        \"370135005\": [\n            769247005\n        ]\n    },\n    \"parentRelationships\": {\n        \"116676008\": [\n            138875005,\n            107669003,\n            123037004,\n            409774005,\n            32693004,\n            49755003,\n            118956008\n        ],\n        \"116680003\": [\n            6118003,\n            138875005,\n            404684003,\n            123946008,\n            118234003,\n            128139000,\n            23853001,\n            246556002,\n            363170005,\n            64572001,\n            118940003,\n            414029004,\n            362975008,\n            363171009,\n            39367000,\n            80690008,\n            362965005\n        ],\n        \"363698007\": [\n            138875005,\n            21483005,\n            442083009,\n            123037004,\n            25087005,\n            91689009,\n            91723000\n        ],\n        \"370135005\": [\n            138875005,\n            769247005,\n            308489006,\n            303102005,\n            281586009,\n            362981000,\n            719982003\n        ]\n    },\n    \"refsets\": [\n        991381000000107,\n        999002271000000101,\n        991411000000109,\n        1127581000000103,\n        1127601000000107,\n        900000000000497000,\n        447562003\n    ]\n}\n\n```\n\n##### Get properties for a single concept\n\nEach concept within SNOMED CT is associated with relationships. You can use \n`hermes` to return these as groups of properties, including concrete values\nwhen available.\n\nHere we look at properties for the concept representing the anti-convulsant\nlamotrigine:\n\n```shell\nhttp 'http://127.0.0.1:8080/v1/snomed/concepts/1231295007/properties'\n```\n\nTry it live [http://128.140.5.148:8080/v1/snomed/concepts/1231295007/properties](http://128.140.5.148:8080/v1/snomed/concepts/1231295007/properties)\n\nNote that when results are not expanded, the metadata model is used to fix \nthe cardinality of the values for the relationship in the context of the concept.\n\n```json\n{\n    \"0\": {\n        \"1142139005\": \"#1\",\n        \"116680003\": [\n            779653004\n        ],\n        \"411116001\": 385060002,\n        \"763032000\": 732936001,\n        \"766939001\": [\n            773862006\n        ]\n    },\n    \"1\": {\n        \"1142135004\": \"#250\",\n        \"1142136003\": \"#1\",\n        \"732943007\": 387562000,\n        \"732945000\": 258684004,\n        \"732947008\": 732936001,\n        \"762949000\": 387562000\n    }\n}\n```\n\nAvailable parameters\n\n- `expand` - expand results to include transitive relationships (`true`/`false`/`1`/`0`)\n- `format` - format results \n- `key-format` - format keys\n- `value-format` - format values\n\nFor machine-interpretation, it is best to simply use `?expand=1` and process\nidentifiers appropriately. For human consumption, and for interactive use, \nproperties can be pretty-printed using a variety of formatting options:\n\nEach format can be one of \n- `id` the identifier\n- `syn` synonym (language determined by Accept-Language header or system/index defaults)\n- `id:syn` a string of identifier and synonym\n- `[id:syn]` a vector of identifier and synonym\n- `{id:syn}` a map of identifier to synonym\n\nExample:\n\n```shell\nhttp 'http://127.0.0.1:8080/v1/snomed/concepts/1231295007/properties?expand=0\u0026format=id:syn'\n```\n\nTry it live [http://128.140.5.148:8080/v1/snomed/concepts/1231295007/properties?expand=1\u0026format=id:syn](http://128.140.5.148:8080/v1/snomed/concepts/1231295007/properties?expand=0\u0026format=id:syn)\n\nNote again how the models within SNOMED CT are used to determine the cardinality\nof the returned relationships. A drug can have multiple roles, but has only\nsingle 'count of base of active ingredient and 'manufactured dose form' \nproperties.\n\n```json\n{\n    \"0\": {\n        \"1142139005:Count of base of active ingredient\": \"#1\",\n        \"116680003:Is a\": [\n            \"779653004:Lamotrigine only product in oral dose form\"\n        ],\n        \"411116001:Has manufactured dose form\": \"385060002:Prolonged-release oral tablet\",\n        \"763032000:Has unit of presentation\": \"732936001:Tablet\",\n        \"766939001:Plays role\": [\n            \"773862006:Anticonvulsant therapeutic role\"\n        ]\n    },\n    \"1\": {\n        \"1142135004:Has presentation strength numerator value\": \"#250\",\n        \"1142136003:Has presentation strength denominator value\": \"#1\",\n        \"732943007:Has BoSS\": \"387562000:Lamotrigine\",\n        \"732945000:Has presentation strength numerator unit\": \"258684004:mg\",\n        \"732947008:Has presentation strength denominator unit\": \"732936001:Tablet\",\n        \"762949000:Has precise active ingredient\": \"387562000:Lamotrigine\"\n    }\n}\n\n```\n\n\n##### Search\n\nExample usage of search endpoint.\n\n```shell\nhttp '127.0.0.1:8080/v1/snomed/search?s=mnd\\\u0026constraint=\u003c64572001\u0026maxHits=5'\n````\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/search?s=mnd\\\u0026constraint=\u003c64572001\u0026maxHits=5](http://128.140.5.148:8080/v1/snomed/search?s=mnd\\\u0026constraint=\u003c64572001\u0026maxHits=5)\n\n```json\n[\n  {\n    \"id\": 486696014,\n    \"conceptId\": 37340000,\n    \"term\": \"MND - Motor neurone disease\",\n    \"preferredTerm\": \"Motor neuron disease\"\n  }\n]\n\n```\n\nThis searches only active concepts, but both active and inactive descriptions, by default. This can be changed\nper request. The defaults are sensible, because a user trying to find something with a now inactive synonym\nsuch as 'Wegener's Granulomatosis' will be suprised that their search fails to return any results.\n\nSearch parameters:\n\n* `s` - the text to search\n* `constraint` - an ECL expression to constrain the search; I never use search without this\n* `maxHits` - maximum number of hits\n* `inactiveConcepts` - whether to search inactive concepts (default, `false`)\n* `inactiveDescriptions` - whether to search inactive descriptions (default, `true`)\n* `fuzzy` - whether to use fuzziness for search (default, `false`)\n* `fallbackFuzzy` - whether to retry using a fuzziness factor if initial search returns no results (default, `false`)\n* `removeDuplicates` - whether to remove consecutive results with the same conceptId and text (default, `false`)\n\nFor autocompletion, in a typical type-ahead user interface control, you might use `fallbackFuzzy=1` (or\n`fallbackFuzzy=true`) and `removeDuplicates=1` (or `removeDuplicates=true`).\nThat will mean that if a user mistypes one or two characters, they should still get some sensible results.\n\n`removeDuplicates` is designed to create a better user experience when searching SNOMED CT. In general, during search,\nyou will want to show to the user the multiple synonyms for a given concept. Recently however, and particularly if you\nare using multiple SNOMED CT distributions (e.g. both the UK clinical and drug extensions), then a single concept may\nhave multiple synonyms with the same textual content. This can be disconcerting for end-users as it looks as if there\nare duplicates in the autocompletion list. Each, of course, has a different description id, but we do not show identifiers\nto end-users. To improve the user experience, I advise using `removeDuplicates` to remove consecutive results with the \nsame conceptId and text.\n\nHere I search for all UK medicinal products with the name amlodipine and populate my autocompletion control using the\nresults:\n\n```shell\nhttp '127.0.0.1:8080/v1/snomed/search?s=amlodipine\\\u0026constraint=\u003c10363601000001109\u0026fallbackFuzzy=true\u0026removeDuplicates=true\u0026maxHits=500'\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/search?s=amlodipine\\\u0026constraint=\u003c10363601000001109\u0026fallbackFuzzy=true\u0026removeDuplicates=true\u0026maxHits=500](http://128.140.5.148:8080/v1/snomed/search?s=amlodipine\\\u0026constraint=\u003c10363601000001109\u0026fallbackFuzzy=true\u0026removeDuplicates=true\u0026maxHits=500)\n\nMore complex expressions are supported, and no search term is actually needed.\n\nLet's get all drugs with exactly three active ingredients:\n\n```shell\nhttp '127.0.0.1:8080/v1/snomed/search?constraint=\u003c373873005|Pharmaceutical / biologic product| : [3..3]  127489000 |Has active ingredient|  = \u003c  105590001 |Substance|'\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/search?constraint=\u003c373873005|Pharmaceutical / biologic product| : [3..3]  127489000 |Has active ingredient|  = \u003c  105590001 |Substance|](http://128.140.5.148:8080/v1/snomed/search?constraint=%3C373873005%7CPharmaceutical%20/%20biologic%20product%7C%20:%20%5B3..3%5D%20%20127489000%20%7CHas%20active%20ingredient%7C%20%20=%20%3C%20%20105590001%20%7CSubstance%7C)\n\nOr, what about all disorders of the lung that are associated with oedema?\n\n```shell\nhttp -j '127.0.0.1:8080/v1/snomed/search?constraint= \u003c  19829001 |Disorder of lung|  AND \u003c  301867009 |Edema of trunk|'\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/search?constraint=/v1/snomed/search?constraint= \u003c  19829001 |Disorder of lung|  AND \u003c  301867009 |Edema of trunk|](http://128.140.5.148:8080/v1/snomed/search?constraint=%3C19829001%20AND%20%3C301867009)\n\nThe ECL can be written more concisely:\n\n```shell\nhttp -j '127.0.0.1:8080/v1/snomed/search?constraint= \u003c19829001 AND \u003c301867009'\n```\n\n##### Expanding ECL without search\n\nSNOMED CT provides the [Expression Constraint Language (ECL)](http://snomed.org/ecl) \nto declaratively define constraints for expressions. `hermes` provides support \nfor the latest version of ECL. If you are simply expanding an ECL expression \nwithout search terms, you can use the `expand` endpoint.\n\n```shell\nhttp -j '127.0.0.1:8080/v1/snomed/expand?ecl= \u003c19829001 AND \u003c301867009\u0026includeHistoric=true'\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/expand?ecl=\u003c19829001 AND \u003c301867009\u0026includeHistoric=true](http://128.140.5.148:8080/v1/snomed/expand?ecl=%20%3C19829001%20AND%20%3C301867009\u0026includeHistoric=true)\n\nThis has an optional parameter `includeHistoric` which can expand the expansion\nto include historical associations. This is very useful in analytics. SNOMED \nintroduced dedicated historic functionality in ECL v2.0, allowing you to choose\nto include historic associations as part of your ECL. You can use either approach\nin `hermes`.\n\nFor example,\n\n```\n\u003c195967001 |Asthma| {{ +HISTORY-MOD }}\n```\n\nis an ECL expression that will return Asthma, and all subtypes, including \nthose now considered inactive or duplicate. You can read more about the new\n[history supplement functionality](https://confluence.ihtsdotools.org/display/DOCECL/6.11+History+Supplements) in ECL2.0 in the [formal documentation](http://snomed.org/ecl).\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/expand?ecl=\u003c\u003c195967001 {{ +HISTORY-MOD }}](http://128.140.5.148:8080/v1/snomed/expand?ecl=%3C%3C%20195967001%20%7CAsthma%7C%20%7B%7B%20%2BHISTORY-MOD%20%7D%7D)\n\nAs a concept identifier is actually a valid SNOMED ECL expression, you can do this:\n\n```shell\nhttp -j '127.0.0.1:8080/v1/snomed/expand?ecl=24700007\u0026includeHistoric=true'\n```\nTry it live: [http://128.140.5.148:8080/v1/snomed/expand?ecl=24700007\u0026includeHistoric=true](http://128.140.5.148:8080/v1/snomed/expand?ecl=24700007\u0026includeHistoric=true)\n\n\n```json\n\n[\n    {\n        \"conceptId\": 586591000000100,\n        \"id\": 1301271000000113,\n        \"preferredTerm\": \"Multiple sclerosis NOS\",\n        \"term\": \"Multiple sclerosis NOS\"\n    },\n    {\n        \"conceptId\": 192930001,\n        \"id\": 297181019,\n        \"preferredTerm\": \"Multiple sclerosis NOS\",\n        \"term\": \"Multiple sclerosis NOS\"\n    },\n    {\n        \"conceptId\": 24700007,\n        \"id\": 41398015,\n        \"preferredTerm\": \"Multiple sclerosis\",\n        \"term\": \"Multiple sclerosis\"\n    }\n    ...\n]\n```\n\nYou can search using concrete values. \n\nHere is SNOMED ECL that will return all products containing 250mg of amoxicillin that \nhave an oral dose form:\n```\n\u003c 763158003 |Medicinal product (product)| :\n     411116001 |Has manufactured dose form (attribute)|  = \u003c\u003c  385268001 |Oral dose form (dose form)| ,\n    {    \u003c\u003c  127489000 |Has active ingredient (attribute)|  = \u003c\u003c  372687004 |Amoxicillin (substance)| ,\n          1142135004 |Has presentation strength numerator value (attribute)|  = #250,\n         732945000 |Has presentation strength numerator unit (attribute)|  =  258684004 |milligram (qualifier value)|}\n```\n\nYou can use `hermes` to expand this:\n\nTry it live:  [http://128.140.5.148:8080/v1/snomed/expand?ecl=\u003c7631580003...](http://128.140.5.148:8080/v1/snomed/expand?ecl=%3C%20763158003%20%7CMedicinal%20product%20%28product%29%7C%20%3A%0A%20%20%20%20%20411116001%20%7CHas%20manufactured%20dose%20form%20%28attribute%29%7C%20%20%3D%20%3C%3C%20%20385268001%20%7COral%20dose%20form%20%28dose%20form%29%7C%20%2C%0A%20%20%20%20%7B%20%20%20%20%3C%3C%20%20127489000%20%7CHas%20active%20ingredient%20%28attribute%29%7C%20%20%3D%20%3C%3C%20%20372687004%20%7CAmoxicillin%20%28substance%29%7C%20%2C%0A%20%20%20%20%20%20%20%20%20%201142135004%20%7CHas%20presentation%20strength%20numerator%20value%20%28attribute%29%7C%20%20%3D%20%23250%2C%0A%20%20%20%20%20%20%20%20%20732945000%20%7CHas%20presentation%20strength%20numerator%20unit%20%28attribute%29%7C%20%20%3D%20%20258684004%20%7Cmilligram%20%28qualifier%20value%29%7C%7D)\n\nUnfortunately, at the time of writing, the UK SNOMED drug extension doesn't \ncurrently publish concrete values data for products in the UK dictionary of \nmedicines and devices, but this is on their roadmap.\n\n##### Crossmap to and from SNOMED CT\n\nThere are endpoints for crossmapping to and from SNOMED.\n\nLet's map one of our diagnostic terms into ICD-10:\n\n- `24700007` is multiple sclerosis.\n- `999002271000000101` is the ICD-10 UK complex map reference set.\n\n```shell\nhttp -j 127.0.0.1:8080/v1/snomed/concepts/24700007/map/999002271000000101\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/concepts/24700007/map/999002271000000101](http://128.140.5.148:8080/v1/snomed/concepts/24700007/map/999002271000000101)\n\nResult:\n\n```\n[\n    {\n        \"active\": true,\n        \"correlationId\": 447561005,\n        \"effectiveTime\": \"2020-08-05\",\n        \"id\": \"57433204-2371-5c6f-855f-94ff9dad7ba6\",\n        \"mapAdvice\": \"ALWAYS G35.X\",\n        \"mapCategoryId\": 1,\n        \"mapGroup\": 1,\n        \"mapPriority\": 1,\n        \"mapRule\": \"\",\n        \"mapTarget\": \"G35X\",\n        \"moduleId\": 999000031000000106,\n        \"referencedComponentId\": 24700007,\n        \"refsetId\": 999002271000000101\n    }\n]\n```\n\nAnd of course, we can crossmap back to SNOMED as well:\n\n```shell\nhttp -j 127.0.0.1:8080/v1/snomed/crossmap/999002271000000101/G35X\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/crossmap/999002271000000101/G35X](http://128.140.5.148:8080/v1/snomed/crossmap/999002271000000101/G35X)\n\nIf you map a concept into a reference set that doesn't contain that concept, you'll\nautomatically get the best parent matches instead.\n\n##### Map a concept into a reference set\n\nYou will usually crossmap using a SNOMED CT crossmap reference set, such as those for ICD-10 or OPCS. However, `Hermes` supports\ncrossmapping a concept into any reference set. You can use this feature in data analytics in order to reduce the dimensionality of your dataset. \n\nHere we have multiple sclerosis (`24700007`), and we're mapping into the UK emergency unit\nreference set (`991411000000109`):\n\n```shell\nhttp -j 127.0.0.1:8080/v1/snomed/concepts/24700007/map/991411000000109\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/concepts/24700007/map/991411000000109](http://128.140.5.148:8080/v1/snomed/concepts/24700007/map/991411000000109)\n\nThe UK emergency unit reference set gives a subset of concepts used for central reporting problems and diagnoses in UK emergency units. \n\nAs multiple sclerosis in that reference set, you'll simply get:\n\n```json\n[\n  {\n    \"active\": true,\n    \"effectiveTime\": \"2015-10-01\",\n    \"id\": \"d55ce305-3dcc-5723-8814-cd26486c37f7\",\n    \"moduleId\": 999000021000000109,\n    \"referencedComponentId\": 24700007,\n    \"refsetId\": 991411000000109\n  }\n]\n```\n\nBut what happens if we try something that isn't in that emergency reference set?\n\nHere is 'limbic encephalitis with LGI1 antibodies' (`763794005`). It isn't in\nthat UK emergency unit reference set:\n\n```shell\nhttp -j 127.0.0.1:8080/v1/snomed/concepts/763794005/map/991411000000109\n```\n\nTry it live: [http://128.140.5.148:8080/v1/snomed/concepts/763794005/map/991411000000109](http://128.140.5.148:8080/v1/snomed/concepts/763794005/map/991411000000109)\n\n\nResult:\n\n```json\n[\n  {\n    \"active\": true,\n    \"effectiveTime\": \"2015-10-01\",\n    \"id\": \"5b3b8cdd-dd02-50e3-b207-bf4a3aa17694\",\n    \"moduleId\": 999000021000000109,\n    \"referencedComponentId\": 45170000,\n    \"refsetId\": 991411000000109\n  }\n]\n```\n\nYou get a more general concept - 'encephalitis' (`45170000`) that is in the\nemergency unit reference set. This makes it straightforward to map concepts\ninto subsets of terms as defined by a reference set for analytics.\n\nYou could limit users to only entering the terms in a subset, but much better to allow clinicians to regard highly-specific granular terms and be able to map to less granular terms on demand.\n\n### C. Embed into another application\n\nYou can use git coordinates in a deps.edn file, or use maven:\n\nIn your `deps.edn` file (make sure you change the commit-id):\n\n```\n[com.eldrix.hermes {:git/url \"https://github.com/wardle/hermes.git\"\n                    :sha     \"097e3094070587dc9362ca4564401a924bea952c\"}\n``` \n\nIn your pom.xml:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.eldrix\u003c/groupId\u003e\n  \u003cartifactId\u003ehermes\u003c/artifactId\u003e\n  \u003cversion\u003e1.0.960\u003c/version\u003e\n\u003c/dependency\u003e\n```\nRemember to use the latest version.\n\nYou may need to add Clojars as a repository in your build tool. Here for maven:\n\n```xml\n\u003crepositories\u003e\n    \u003crepository\u003e\n        \u003cid\u003eclojars.org\u003c/id\u003e\n        \u003curl\u003ehttps://clojars.org/repo\u003c/url\u003e\n    \u003c/repository\u003e\n\u003c/repositories\u003e\n```\n\n### D. Development\n\nSee [/doc/development](/doc/development.md) on how to develop, test, lint, deploy and release `hermes`. \n\n### E. Backwards compatibility and versioning\n\n`Hermes` uses versions of form `major.minor.commit`\n\n`Hermes` builds a file-based database made up of a store and indices\nand each database is also versioned. `Hermes` of a specified major/minor\nversion is compatible with databases created by the same major/minor version. \nFor example, a database was created with `Hermes v1.4.1265` can be read\nby `Hermes v1.4.1320`, but one created with `Hermes v1.3.1262` cannot\n\nIf backwards compatibility can easily be preserved, the major/minor version is \nkept the same. For example, when support for concrete values was added, this was \nan additive change so that newer versions of `Hermes` would simply degrade\ngracefully, but throw a warning to say concrete values were not supported for \nthis database.\n\nOn some occasions, compatibility is broken even when there is only a minor \nchange to database format to prevent user inconvenience, error or confusion. For\nexample, in the change from 1.3 series to 1.4, the search index changed to use\nnormalised (folded) text according to term locale. This was a small change\nand degradation could have occurred gracefully, but such a fallback would lead\nto varying behaviour depending on which database was used and potentially \nconfuse users. \n\nIn general therefore, the policy for versioning is to enforce exact version \nmatching for a given `Hermes` and database version with a bias towards bumping \nversions when backwards compatibility or fallback modes of operation could \nresult in confusing or unexpected behaviour.\n\n*Mark*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwardle%2Fhermes","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwardle%2Fhermes","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwardle%2Fhermes/lists"}