Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - pacharanero/sct: SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready · GitHub
Skip to content

Latest commit

History

563 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

sct logo

A local-first SNOMED CT toolchain that's 10-100x faster than IHTSDO Snowstorm. One binary - from raw RF2 release to NDJSON, then SQL, Parquet, Markdown, TUI, GUI, graphs and MCP/LLM tool use. All on your machine, no network calls, REST APIs, or external servers required.

This is very much a work in progress, but it's ready to use and I would very much like feedback on how it performs for you.

flowchart TD
TRUD(["NHS TRUD API"]) -->|"sct trud download"| RF2["RF2 Snapshot release"]
RF2 -->|"sct ndjson"| N[("canonical NDJSON artefact")]
N -->|"sct sqlite"| DB[("snomed.db · SQL + FTS5<br/>+ transitive closure (sct tct)")]
N -->|"sct parquet"| PQ[("snomed.parquet")]
N -->|"sct markdown"| MD["snomed-concepts/"]
N -->|"sct embed"| AR[("snomed-embeddings.arrow")]
DB --> QUERY["sct lexical · lookup · ecl<br/>refset · map · diagram · codelist"]
N -->|"sct fst"| FST[("snomed.fst · FST index")]
FST --> SAYT["sct sayt · search-as-you-type"]
DB --> SERVE["sct serve · FHIR R4 server"]
DB --> MCP["sct mcp · LLM tool use"]
AR --> SEM["sct semantic · vector search"]
PQ --> DUCK["DuckDB / pandas / Polars"]
MD --> RAG["RAG / LLM file reading"]
Loading

Plus sct diff (compare two NDJSON releases), sct info (inspect any artefact), and sct gui / sct tui for visual, point-and-click exploration.

The canonical NDJSON artefact at the centre is stable, versionable, and greppable. The main file contains concepts; --refsets all adds provenance-declared payload-refset and history companion streams so non-concept RF2 rows remain lossless. All other outputs are derived from this NDJSON bundle and can be regenerated at any time.


Why is this needed?

sct joins the relatively incomprehensible RF2 files into a canonical concept NDJSON stream, with companion NDJSON only when payload or history refsets are requested. For the UK Monolith Edition the concept file is over 1 GB but it was still possible to load into VSCode to get a feel for the data structure, which is something that is impossible with the original RF2 files. This also means you can use standard tools like jq or ripgrep to query the data without needing a custom server or API.

SNOMED CT is distributed as RF2 - a set of tab-separated files that require joining across multiple tables to get anything useful. The entire healthcare industry relies on remote terminology servers for this, with the overhead of network calls and REST APIs. sct performs the join once creating an NDJSON artefact, and produces standard files you can query locally with sqlite3, duckdb, jq, ripgrep, or an LLM. No server, no API key, no network.

Speed comparison

Operationsct + SQLiteSnowstorm Litesct speedup
Import - Clinical Edition22s209s~10x faster
Import - Full UK Monolith~57sFailed (OOM)*
Single concept lookup (SCTID)6ms491ms~80x faster
Free-text search (10 results)2ms202ms~100x faster
  • Snowstorm Lite running in Docker with 24Gb of Java heap allocation ran out of memory on the full UK Monolith, which has 837,930 concepts. sct handled it in under a minute.

These comparison numbers predate the current 837,930-concept release and haven't been re-run against a live Snowstorm Lite instance since - treat them as indicative rather than current. For up-to-date sct-only timings, see docs/benchmarks.md. Feel free to run the benchmarks yourself (including a fresh Snowstorm Lite comparison) and share your results, perhaps as an Issue.


Installation

Prebuilt binaries are published for Linux (x86_64, aarch64), macOS (Apple Silicon, Intel), and Windows (x86_64) on every release, with SHA-256 checksums you can verify against the SHA256SUMS file on the Releases page.

Shell one-liners

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/pacharanero/sct/main/install.sh | sh

Windows (PowerShell):

iwr -useb https://raw.githubusercontent.com/pacharanero/sct/main/install.ps1 | iex

Both installers auto-detect your OS and architecture, download the matching binary, verify its SHA-256 checksum against the published SHA256SUMS, and install to ~/.local/bin (macOS / Linux) or %LOCALAPPDATA%\sct\bin (Windows). Override the destination with SCT_INSTALL_DIR, or pin a specific version with SCT_VERSION=v0.3.9.

Homebrew (macOS and Linux)

brew tap pacharanero/tap
brew install sct

Arch Linux (AUR)

yay -S sct-rs-bin

Scoop (Windows)

scoop bucket add pacharanero https://github.com/pacharanero/scoop
scoop install sct

Cargo

If you already have a Rust toolchain (via rustup, stable 1.88+):

# Compile from crates.io
cargo install sct-rs

Or, if you have the cargo-binstall plugin installed (it is not bundled with cargo itself), grab a prebuilt binary instead of compiling from source:

# One-time: install the binstall plugin (or follow the one-liner installers in its README)
cargo install cargo-binstall
# Then install sct without compilation
cargo binstall sct-rs

Nix

With Nix and flakes enabled, run sct straight from the repository without installing it, add it to your profile, or drop into a dev shell:

# Run without installing anything
nix run github:pacharanero/sct -- lookup 22298006
# Install into your profile
nix profile install github:pacharanero/sct
# Dev shell with the Rust toolchain, for hacking on sct
nix develop github:pacharanero/sct

Build from source

git clone https://github.com/pacharanero/sct
cd sct
cargo install --path .# default build: core commands + sct serve + sct tui
cargo install --path . --features gui # add the browser UI (sct gui)
cargo install --path . --features dmwb # add the NHS DMWB .mdb reader (sct dmwb)
cargo install --path . --features full # everything: serve + tui + gui + dmwb + diagram SVG
FeatureDefault?What it addsExtra dependencies
serveyesFHIR R4 terminology server (sct serve)axum, tokio
tuiyesInteractive terminal UI - powers both sct tui and the live sct sayt viewratatui, crossterm
guiopt-inBrowser-based graph UI (sct gui)axum, tokio, open
dmwbopt-inRead NHS Data Migration Workbench .mdb files (sct dmwb)jetdb
diagram-svgopt-inBuilt-in SVG output for sct diagramlayout-rs
fullopt-inEverything: serve + tui + gui + dmwb + diagram-svgall of the above

Every other subcommand (RF2 conversion, SQLite/Parquet/Markdown/Arrow, search, ECL, maps, codelists, MCP, diff, info…) is always compiled in. Only a --no-default-features build - such as the headless Docker server image - drops serve and tui.

Manual download

Grab the appropriate archive from the Releases page, verify its SHA-256 against SHA256SUMS, extract, and drop sct somewhere on your PATH.


Quick start

# 1. Download a distribution of SNOMED CT# UK: https://isd.digital.nhs.uk/ → Monolith Edition, RF2: Snapshot# (free under NHS England national licence - access is immediate)# NB: You need to Subscribe to a release before you can see the Download option 🤯# International: https://mlds.ihtsdotools.org/ (allow up to a week for approval)# 2. Convert RF2 → NDJSON (~52s for 837,930 concepts)# Pass the .zip directly - no manual extraction needed
sct ndjson --rf2 SnomedCT_MonolithRF2_PRODUCTION_20260311T120000Z.zip
# ✓ 837,930 concepts written → snomedct-monolithrf2-production-20260311t120000z.ndjson# 3. Load into SQLite with FTS5# The release name carries through - every build command names its output# after its input, and prints the name it chose
sct sqlite --ndjson snomedct-monolithrf2-production-20260311t120000z.ndjson
# ✓ Output: snomedct-monolithrf2-production-20260311t120000z.db# 4. Query with standard tools - no custom binary needed
sqlite3 snomedct-monolithrf2-production-20260311t120000z.db \
"SELECT id, preferred_term FROM concepts_fts WHERE concepts_fts MATCH 'heart attack' LIMIT 5"# 5. Start the MCP server for Claude Desktop# No --db needed: sct discovers the database you just built
sct mcp

UK users can automate steps 1–3 with a single command once the TRUD API integration is set up. Store your TRUD API key once, then download and build in one go:

sct trud auth < my-trud-key.txt # one-time: verifies and saves the key
sct trud download --edition uk_monolith --pipeline

Or, on a fresh VPS with Docker installed, run the FHIR terminology server from this checkout - sct plus a Caddy reverse proxy for automatic HTTPS:

cp .env.example .env
$EDITOR .env # set TRUD_API_KEY, and DOMAIN for real HTTPS
docker compose up -d --build

The first boot downloads the configured TRUD edition, builds snomed.db into a persistent Docker volume, and serves FHIR at https://$DOMAIN/fhir (or http://localhost/fhir if DOMAIN is left unset). See Get Your Own Terminology Server for the full walkthrough, including optional basic auth and a no-clone route using the published Docker Hub image.

Documentation

For all further information see the full documentation by either exploring the docs/ directory or running the docs site locally with s/docs, or visit the docs on the GitHub Pages site: https://pacharanero.github.io/sct/


Subcommands

  • sct trud - download SNOMED CT RF2 releases via the NHS TRUD API
  • sct ndjson - convert an RF2 Snapshot directory to a canonical NDJSON artefact
  • sct sqlite - load NDJSON into a SQLite database with FTS5
  • sct tct - build a transitive closure table over the IS-A hierarchy for subsumption-heavy workloads
  • sct parquet - export NDJSON to a Parquet file for DuckDB / analytics
  • sct markdown - export NDJSON to per-concept Markdown files (or per-hierarchy with --mode hierarchy)
  • sct mcp - start a local MCP server over stdio backed by the SQLite database
  • sct serve - FHIR R4 terminology server ($lookup/$validate-code/$subsumes/$expand with ECL)
  • sct read2 - import final Read v2 maps from NHS Data Migration TRUD item 9
  • sct embed - generate Ollama vector embeddings and write an Arrow IPC file
  • sct lexical - keyword (FTS5) search over the SQLite database
  • sct fst - mmap'd FST index for exact, prefix, and typo-tolerant fuzzy search
  • sct sayt - search-as-you-type: instant offline autocomplete over 800k+ concepts, as an interactive TUI, a --stdio line protocol, or an HTTP /autocomplete endpoint on sct serve
  • sct semantic - semantic similarity search over the Arrow IPC embeddings file (requires Ollama) - experimental, see the docs for known limitations
  • sct ecl - evaluate an ECL expression and emit matching concept SCTIDs (pipe-friendly)
  • sct lookup <code> - look up a concept by SCTID, or reverse-resolve a CTV3 code
  • sct diagram - draw a concept's definition, ancestors, or descendants as a tree, DOT, or Mermaid diagram
  • sct proximal-primitives - compute a concept's most specific primitive ancestors, for classification and post-coordination QA
  • sct refset - inspect SNOMED CT simple reference sets loaded into a SQLite database
  • sct map - map codes between SNOMED CT, Read v2, CTV3, ICD-10, and OPCS-4: sct map <code> shows all cross-terminology equivalents of a single code, sct map --from read2 --to snomed maps a stream (sct trud download --multi-terminology builds the full workspace). Aliases: sct transcode, sct crosswalk
  • sct codelist - build, compose, import, validate, and export clinical code lists; add --ecl "<<73211009" populates from an ECL query
  • sct info <file> - inspect any .ndjson, .db, or .arrow artefact and print a summary
  • sct diff --old <file> --new <file> - compare two NDJSON releases and report what changed
  • sct paths - show where sct looks for databases, embeddings, and config files
  • sct bench - benchmark SDK/CLI performance or semantic retrieval quality against fixed local scenarios, with reproducible machine-readable reports
  • sct completions - print shell completion scripts (bash, zsh, fish, powershell, elvish)
  • sct tui - keyboard-driven terminal UI for interactive SNOMED CT exploration (in the default build)
  • sct gui - browser-based UI served over localhost for point-and-click exploration (optional feature)

The natural single-value readers lookup, lexical, semantic, and refset info|members|profile also accept - for ordered stdin batches. Use --format json or --format yaml when a caller needs each input paired with its result set; lookup, lexical, semantic, and refset members additionally offer mutually exclusive --ids output for a flat stream into another command.

Run any subcommand with --help for full option reference.


Which output do I want?

GoalCommand
Query with SQL / keyword searchsct sqlite then sct lexical
Analytics / DuckDBsct parquet
RAG / LLM file ingestionsct markdown
Semantic / meaning-based searchsct embed then sct semantic
Claude Desktop or Claude Codesct sqlite then sct mcp

Getting SNOMED CT

SNOMED CT is licensed. Download the RF2 Snapshot for your region:

  • UK:NHS Digital TRUDSNOMED CT Monolith Edition, RF2: Snapshot. Covered by the NHS England national licence.
  • International:MLDS or NLM.

Download the Monolith Snapshot if available - it bundles the international base, clinical extension, and drug extension in one directory.


Feedback

Please try it out and let me know how it performs for you, especially if you have a use case that isn't well supported by the current subcommands. Open an Issue for anything you want to report, from bugs to feature requests to general feedback.

Development

A devcontainer configuration is included in .devcontainer/. Open the project in VS Code and select "Reopen in Container" to get a ready-to-go environment with Rust, sqlite3, duckdb, jq, and ripgrep pre-installed. Also included is python3 and Ollama, for working with the embeddings and semantic search features.

Store SNOMED data files (zips, NDJSON, databases) in the data-volume/ directory inside the container - it's backed by a Docker volume for faster I/O than the default bind mount.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute, report issues, or request features.

Roadmap

See the ROADMAP for planned features, improvements, and long-term vision for the project.

Trademarks and Copyright

SNOMED CT®

SNOMED CT® is a registered trademark of SNOMED International. This project is an independent implementation and is not affiliated with SNOMED International. All SNOMED CT data is sourced from the official RF2 releases and remains copyright of SNOMED International. Please refer to the license terms for your use of SNOMED CT data. You must ensure you have an appropriate license to use SNOMED CT data in your jurisdiction.

sct

sct is not trademarked. The source code and binaries are copyright Marcus Baw and Baw Medical Ltd, and provided to you under the terms of the AGPL-3.0 license.

About

SNOMED-CT tooling, brought into the 21stC. RF2 → NDJSON → SQLite | Vector | .arrow, | .parquet | .md | refsets | .codelist | MCP | CTV3 & ReadV2 - ready

Topics

Resources

Code of conduct

Contributing

Stars

26 stars

Watchers

5 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages