Documentation / Exploitation / Guides pratiques / Configure terminology routing

Configure terminology routing

Coded ports ({ kind: "coded", valueSet }) are validated against a TerminologyPort. The instance's terminology: map routes each code system to the resolver that owns it — a plugin-contributed adapter (e.g. SNOMED CT via Hermes) — and lets every other system fall through to the built-in registry. This recipe adds one routing entry and verifies it resolves.

For what a coded type is and how value-sets fit the type system, see The type system. For adding a value-set or plugging a new resolver plugin, see Add vocabulary and types.

1. Identify the adapter to route to

Terminology resolvers are contributed by plugins as terminology adapters — the same mechanism as any other port adapter (libs/shared/src/plugin/manifest.ts, AdapterContribSchema). The bundled @posos/snomed plugin is the concrete example (external-plugins/snomed/plugin.yaml):

contributes:
  adapters:
    - port: terminology
      name: hermes
      module: ./adapters/hermes.ts
      systems: [snomed-ct]   # informational only — not used for routing

The name (hermes) is what you reference from the instance manifest. systems documents which code systems the adapter is meant for, but it does not drive routing by itself — routing is entirely decided by the instance's terminology: keys (next step). Make sure the plugin is loaded first: see Declare and install plugins.

2. Add a routing entry to the instance manifest

In instance.yaml (or the file pointed to by INSTANCE_CONFIG), add one entry per code system under terminology:, keyed by the code system identifier (the value-set id, e.g. snomed-ct) and pointing at an adapter name plus its config:

terminology:
  # routing by code system; systems not listed fall back to internal resolution.
  snomed-ct: { adapter: hermes, config: { baseUrl: http://localhost:8081 } }

config is passed verbatim to the adapter's factory (apps/api/src/instance.ts, buildTerminology):

for (const [system, binding] of Object.entries(instance.terminology)) {
  const ad = findAdapter(plugins, "terminology", binding.adapter);
  if (!ad) { log.warn({ system, adapter: binding.adapter }, "résolveur terminology introuvable"); continue; }
  providers[system] = ad.factory(binding.config) as TerminologyPort;
}

For the Hermes adapter, config.baseUrl is read by external-plugins/snomed/adapters/hermes.ts (falling back to the HERMES_BASE_URL env var, then http://localhost:8081 if neither is set). If binding.adapter doesn't match any loaded adapter's name, the system is logged as a warning at boot and silently gets no dedicated resolver — it falls through to the same internal default as an unlisted system (see next step).

3. Leave every other system unrouted

You do not need an entry for systems that should resolve internally. The resulting RoutingTerminology (libs/shared/src/domain/terminology.ts) tries providers[system] first and falls back to InternalTerminology — the in-repo value-set registry (libs/shared/src/domain/value-sets.ts) — for anything not in the map:

private route(valueSet: string): TerminologyPort {
  return this.providers[valueSet] ?? this.fallback; // fallback = new InternalTerminology()
}

This fallback is fixed to the internal registry — there is no manifest option to point unrouted systems at a generic external server instead. If you need a system resolved externally, contribute (or select) a terminology adapter for it explicitly, as in step 1.

Be aware of one asymmetry: a value-set contributed with external: true (like snomed-ct itself — its codes aren't enumerated in-repo) has no concepts for InternalTerminology to check against, so its internal validateCode fails open (accepts any code) rather than rejecting it (libs/shared/src/domain/terminology.ts, InternalTerminology.validateCode). In practice this means: route every external value-set you actually want validated, and treat "unrouted + external" as unchecked, not as "denied".

4. Verify the routing

Run the instance-selection test, which asserts both branches — a routed system resolving through the adapter, and an unlisted system falling back internally (apps/api/tests/test-instance.ts):

cd apps/api
pnpm run test:instance

It exercises:

const okSnomed = await ports.terminology!.validateCode("snomed-ct", "24700007");
// → true, routed to Hermes
const okGender = await ports.terminology!.validateCode("administrative-gender", "male");
// → true, unlisted system, resolved by the internal fallback

test:instance passes even without Hermes reachable — the adapter fails open and logs a warning (hermes.ts: "serveur injoignable … validation SNOMED en mode fail-open"). To exercise a real Hermes round-trip (start it first on http://localhost:8081), run the dedicated end-to-end test instead:

pnpm run test:snomed

5. The deployed instance

Staging runs Hermes in-cluster as applications/apps/meridian-hermes (posos-tech/vanilla-argonaute) — a StatefulSet with a PVC, reachable on ClusterIP only, and the routing entry from step 2 is already in the meridian-instance ConfigMap pointing at http://hermes.ps-staging-apps.svc.cluster.local:8080. Two things differ from a Hermes you run locally:

  • The database is built on first boot, by two initContainers, from the SNOMED CT édition nationale française RF2 held in a private GCS bucket. That edition is a full edition, not a language extension — it carries the international content it depends on, so it is a single import. The ANS download is authenticated (SMT account + French NRC affiliate number), so the zip is uploaded by hand, once per annual (June) release.

  • French display is selected by the adapter, not by the server. Hermes resolves locales through a hardcoded table in which fr-fr means the international French language reference set, 722131000. The ANS national edition ships its own, 10031000315102, which that table does not know — so --locale fr makes the server refuse to start, and Hermes's documented escape hatch (a BCP 47 private-use tag, fr-x-10031000315102) fails too, because the match is then filtered against the recognised locales only. The server therefore runs on en-US, and the routing entry carries languageRefsetId: 10031000315102 so the adapter picks the term preferred in the French refset off /extended.

    The translation is delivered incrementally, so display() returns French where translated and falls back to the server's preferred term elsewhere.

  • --bind-address 0.0.0.0 is required. Hermes binds to loopback by default, which is invisible to both the Service and the kubelet's readiness probe.

75 documents6 sectionssource : /docs · généré au build