# termbank.pdhc — User manual

The read API and the seven import CLIs are shipped and in use. This is
the current operator + integrator surface.

## Who this is for

Platform operators and developers of other PDHC services (`plan.pdhc`,
`rosetta.pdhc`, `cdr.pdhc`, `forms.pdhc`, etc.) that need to resolve
canonical terminology URIs.

End users never reach `termbank.pdhc` directly — they interact through
`plan.pdhc`'s curation UI, which browses `termbank.pdhc` on their behalf.

## What it is

A bulk canonical terminology store. It imports upstream vocabularies
as-is and serves them read-only over HTTP. It owns the canonical URI
namespace `https://termbank.pdhc.se/CodeSystem/<system>/<code>` — no
other service mints these URIs. `plan.pdhc` mirrors the ones it curates,
`rosetta.pdhc` stores mappings that point to them.

The read API is **public** (no auth, Rule 24). Every mutating HTTP
method returns `405` — the only way data changes is the import CLIs,
run by an operator.

## Quick start

```bash
# FHIR R5 CodeSystem $lookup — the primary, spec-shaped operation
curl 'https://termbank.pdhc.se/fhir/CodeSystem/$lookup?system=loinc&code=4548-4'

# Same, asking for the Swedish display on the top line
curl 'https://termbank.pdhc.se/fhir/CodeSystem/$lookup?system=snomed&code=73211009&displayLanguage=sv'

# Convenience shortcut: look up by system + code in the path
curl https://termbank.pdhc.se/CodeSystem/loinc/4548-4

# Look up by canonical URI (query parameter, not a path segment)
curl 'https://termbank.pdhc.se/Concept?uri=https://termbank.pdhc.se/CodeSystem/loinc/4548-4'

# Fuzzy search across display + designations
curl 'https://termbank.pdhc.se/search?q=HbA1c&system=loinc&limit=20'

# CapabilityStatement (what the service advertises)
curl https://termbank.pdhc.se/fhir/metadata

# Health (DB reachability + per-source version/staleness)
curl https://termbank.pdhc.se/health
```

`$lookup`, `/CodeSystem/<system>/<code>` and `/Concept?uri=` all return
the same FHIR R5 `Parameters` resource (name, version, display,
`property` entries, `designation` entries). `/search` returns a plain
JSON list (not a FHIR Bundle). Errors come back as FHIR
`OperationOutcome` with an appropriate HTTP status (400 missing args,
404 not found, 405 mutation attempt).

### `$lookup` via POST

`$lookup` also accepts `POST` with a FHIR `Parameters` body (the URL-escaped
`/fhir/CodeSystem/%24lookup` form works too). This is a read, not a
mutation, so it is exempt from the 405 rule:

```bash
curl -X POST 'https://termbank.pdhc.se/fhir/CodeSystem/$lookup' \
  -H 'content-type: application/json' \
  -d '{"resourceType":"Parameters","parameter":[
        {"name":"system","valueUri":"loinc"},
        {"name":"code","valueCode":"4548-4"}]}'
```

### `displayLanguage`

`$lookup`, `/CodeSystem/<system>/<code>` and `/Concept?uri=` accept an
optional `displayLanguage` (e.g. `sv`). When supplied, the top-line
`display` is replaced with that language's `preferred` designation if
one exists (then `fsn`, then any designation in that language, then the
default display). The full `designation` list still carries every
language.

### Search parameters

- `q` — required. Ranked across `Concept.display` and designation values
  (exact match wins, then prefix, then contains).
- `system` — optional. Repeat it (`?system=loinc&system=snomed`) or pass
  one comma-separated value (`?system=loinc,snomed`) to restrict.
- `limit` — optional, default 20, capped at 200.
- `displayLanguage` — optional.

## Vocabularies (seven importers)

| System (`system=`) | Importer | Source |
|--------------------|----------|--------|
| `loinc`            | `import_loinc.py`   | LOINC release table |
| `snomed`           | `import_snomed.py`  | SNOMED CT Managed Service SE |
| `icd10`            | `import_icd10.py`   | ICD-10-SE (Socialstyrelsen) |
| `atc`              | `import_atc.py`     | ATC (Läkemedelsverket / SeNSL) |
| `kva`              | `import_kva.py`     | KVA — medical (KMA) + surgical (KKA) procedures |
| `socialstyrelsen`  | `import_sosstyr.py` | Socialstyrelsen termbank (full XML) |
| `icf`              | `import_icf.py`     | ICF (Socialstyrelsen) |

There is no ICD-11 importer.

## Import (operator only)

All imports are CLI-only. Every importer **requires `--source-url` and
`--version`** (source URL is recorded for provenance even when a local
file is used). Re-running with an already-imported `--version` is a
no-op; a new `--version` updates concepts in place while preserving each
`canonical_uri`. Optional on every importer: `--operator` (defaults to
`$USER@$HOST`) and `--batch-size` (default 2000).

Run from the repo root with the venv active:

```bash
# LOINC — requires --local-file (the LoincTable CSV)
python scripts/import_loinc.py \
  --source-url https://loinc.org/file/loinc-2.82 \
  --version 2.82 \
  --local-file ./Loinc_2.82/LoincTable/Loinc.csv

# SNOMED CT SE — requires --release-dir (the RF2 release folder)
python scripts/import_snomed.py \
  --source-url https://mlds.ihtsdotools.org/.../SE \
  --version SE1000052_20251130 \
  --release-dir ./SnomedCT_ManagedServiceSE_PRODUCTION_SE1000052_20251130T120000Z

# ICD-10-SE — requires --local-file (the TSV)
python scripts/import_icd10.py \
  --source-url https://www.socialstyrelsen.se/.../icd-10-se \
  --version 2024 \
  --local-file ./icd-10-se.tsv

# ATC — requires --local-file (the SeNSL ATC XML)
python scripts/import_atc.py \
  --source-url https://www.lakemedelsverket.se/sv/sensl \
  --version v2 \
  --local-file 'sensl-v2/codesystems/atc-code.xml'

# KVA — requires --kma-file and/or --kka-file (at least one)
python scripts/import_kva.py \
  --source-url https://www.socialstyrelsen.se/.../kva \
  --version 2026 \
  --kma-file ./kva-medicinska-atgarder-kma.tsv \
  --kka-file ./kva-kirurgiska-atgarder-kka.tsv

# Socialstyrelsen termbank — --local-file is optional (downloads otherwise)
python scripts/import_sosstyr.py \
  --source-url https://www.socialstyrelsen.se/.../socialstyrelsenstermbank_komplett.xml \
  --version 4.3.6-2025-12-17

# ICF — requires --local-file (the TSV)
python scripts/import_icf.py \
  --source-url https://www.socialstyrelsen.se/.../icf \
  --version 2024 \
  --local-file ./icf.tsv
```

There are no `--release-csv` / `--release-tsv` flags — the single-file
importers all use `--local-file`, SNOMED uses `--release-dir`, and KVA
uses its two `--kma-file` / `--kka-file` inputs.

## Health & monitoring

`GET /health` returns `200` with `status: "ok"` when the database is
reachable, `503` with `status: "degraded"` when it is not. It also lists
every imported source with its version, concept count, the pinned
`<SOURCE>_VERSION` env value (if set), and a `stale` flag when the two
disagree — so you can spot when the running data is behind the pinned
release.
