Skip to main content

Terminology Service — User Stories

Service: terminology-service Story prefix: TERM-US Last updated: 2026-04-18 Source: migrated and formatted from _sources/terminology/backlog/USER_STORIES.md

Stories

TERM-US-001 — Search coded concepts by text

FieldValue
Issue typeStory
SummaryFull-text concept search returns ranked, tenant-scoped results
Epic linkTERM-EPIC-01
StatusDone
PriorityMust
Story points5
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentsconcept-query-api
FR referencesFR-TERM-001
Legacy FR refsFR-TERM-001
Dependencies(none)

User story: As a clinical documentation user, when I search coded terms, I want relevant ranked concept results so that coding is fast and accurate.

Acceptance criteria (Gherkin):

  • Given an authenticated tenant user, when a search is run with limit <= 100, then only active global plus tenant concepts are returned within 200ms p95 under warm-index conditions.
  • Given an unknown system URI, when search executes, then response is an empty array with HTTP 200.
  • Given duplicate term candidates, when response is returned, then deterministic ranking order is preserved for the same input query.

Technical notes:

  • PostgreSQL GIN full-text index on display || ' ' || code.
  • Tenant scope enforced via RLS + app.tenant_id.

Definition of Done: Standard DoD plus p95 latency test ≤ 200ms.


TERM-US-002 — Exact concept lookup by code

FieldValue
Issue typeStory
SummaryExact system+code lookup returns display and active status
Epic linkTERM-EPIC-01
StatusDone
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentsconcept-query-api, redis-cache
FR referencesFR-TERM-002
Legacy FR refsFR-TERM-002
Dependencies(none)

User story: As a service consumer, when I resolve a known code, I want exact code lookup so that downstream displays are consistent.

Acceptance criteria (Gherkin):

  • Given active concept exists, when lookup is called with exact system+code, then response returns system/code/display with HTTP 200.
  • Given concept is missing, when lookup is called, then HTTP 404 is returned with NOT_FOUND semantics.
  • Given inactive concept, when lookup is called, then no active payload is returned.

Technical notes:

  • Redis cache key: term:lookup:{system}:{code}:{tenantId}; TTL 1h.

Definition of Done: Standard DoD plus cache hit/miss integration test green.


TERM-US-003 — Validate a code's active status

FieldValue
Issue typeStory
SummaryValidation endpoint returns boolean active/valid for a given code
Epic linkTERM-EPIC-01
StatusDone
PriorityMust
Story points2
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentsconcept-query-api
FR referencesFR-TERM-003
Legacy FR refsFR-TERM-003
DependenciesTERM-US-002

User story: As a validation workflow, when I submit a code candidate, I want validity responses so that invalid codes are blocked.

Acceptance criteria (Gherkin):

  • Given active code exists, when validate is called, then result.valid=true is returned.
  • Given missing or inactive code, when validate is called, then result.valid=false is returned.
  • Given malformed request missing required fields, when validate is called, then HTTP 400 is returned.

Definition of Done: Standard DoD.


TERM-US-004 — Expand a ValueSet to its active concepts

FieldValue
Issue typeStory
SummaryValueSet $expand returns bounded list of active member concepts
Epic linkTERM-EPIC-02
StatusDone
PriorityMust
Story points5
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentsvalue-set-api, redis-cache
FR referencesFR-TERM-004
Legacy FR refsFR-TERM-004
DependenciesTERM-US-001

User story: As an interoperability consumer, when I expand a ValueSet URL, I want a consistent list of active concepts so that decision logic is reproducible.

Acceptance criteria (Gherkin):

  • Given mapped ValueSet URL, when expand is called, then active concepts are returned with HTTP 200.
  • Given unmapped ValueSet URL, when expand is called, then empty array is returned.
  • Given expansion exceeds cap, when response is produced, then returned concepts are limited to configured maximum (500).

Definition of Done: Standard DoD.


TERM-US-005 — Drug class lookup by RxNorm code

FieldValue
Issue typeStory
SummaryReturn drug class(es) for a supplied RxNorm code
Epic linkTERM-EPIC-03
StatusDone
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentscds-api
FR referencesFR-TERM-005
Legacy FR refsFR-TERM-005
DependenciesDrug class data loaded via ETL

User story: As a CDS engine, when I evaluate medication class risk, I want drug class lookup so that class-based checks are possible.

Acceptance criteria (Gherkin):

  • Given known RxNorm code, when drug-class endpoint is called, then one or more class names are returned.
  • Given unknown RxNorm code, when endpoint is called, then empty result is returned with HTTP 200.
  • Given inactive class rows, when query executes, then inactive rows are excluded.

Definition of Done: Standard DoD.


TERM-US-006 — Drug-drug interaction check

FieldValue
Issue typeStory
SummaryReturn pairwise interactions with severity for a list of RxNorm codes
Epic linkTERM-EPIC-03
StatusDone
PriorityMust
Story points5
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentscds-api
FR referencesFR-TERM-006
Legacy FR refsFR-TERM-006
DependenciesDrug interaction data loaded via ETL

User story: As a prescribing workflow, when multiple medications are present, I want interaction checks so that unsafe combinations are identified.

Acceptance criteria (Gherkin):

  • Given two or more RxNorm codes, when interactions endpoint is called, then matching active interactions are returned with severity and description.
  • Given fewer than two codes, when endpoint is called, then HTTP 400 BAD_REQUEST is returned.
  • Given no interactions found, when endpoint is called, then empty array is returned.

Definition of Done: Standard DoD plus known CONTRAINDICATED pair verified in integration test.


TERM-US-007 — Duplicate therapy detection

FieldValue
Issue typeStory
SummaryIdentify drug pairs sharing a drug class as potential duplicate therapies
Epic linkTERM-EPIC-03
StatusDone
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentscds-api
FR referencesFR-TERM-011
Legacy FR refsFR-TERM-011
DependenciesTERM-US-005

User story: As a clinical safety workflow, when duplicate-therapy screening runs, I want shared-class pair detection so that duplicate therapies are flagged.

Acceptance criteria (Gherkin):

  • Given at least two RxNorm codes, when duplicate-therapy endpoint is called, then pairs with sharedClasses are returned.
  • Given no shared class exists, when endpoint is called, then empty array is returned.
  • Given duplicate input codes only, when endpoint is called, then output excludes same-code pairs.

Definition of Done: Standard DoD.


TERM-US-008 — Drug-condition contraindication check

FieldValue
Issue typeStory
SummaryReturn contraindication alerts for a drug given a patient condition list
Epic linkTERM-EPIC-03
StatusDone
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentscds-api
FR referencesFR-TERM-012
Legacy FR refsFR-TERM-012
DependenciesDrug contraindication data loaded via ETL

User story: As a prescribing workflow, when patient conditions are known, I want contraindication checks so that drug-condition risks are surfaced.

Acceptance criteria (Gherkin):

  • Given rxnormCode and icd10Codes list, when contraindications endpoint is called, then matching alerts include severity and description.
  • Given no matching contraindications, when endpoint is called, then empty array is returned.
  • Given invalid request missing required params, when endpoint is called, then HTTP 400 is returned.

Definition of Done: Standard DoD.


TERM-US-009 — Tenant-scoped visibility enforcement

FieldValue
Issue typeStory
SummaryTenant JWT queries return only global + own-tenant concepts
Epic linkTERM-EPIC-04
StatusDone
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, slice:S0
Componentsconcept-query-api, rls-policy
FR referencesFR-TERM-007
Legacy FR refsFR-TERM-007
DependenciesTERM-US-001

User story: As a tenant user, when querying terminology, I want tenant-scoped visibility so that cross-tenant leakage is prevented.

Acceptance criteria (Gherkin):

  • Given JWT request with tenantId, when search/lookup executes, then only global plus same-tenant concepts are returned.
  • Given internal request without tenantId, when query executes, then only global concepts are returned.
  • Given request for other-tenant data, when query executes, then data is not returned.

Technical notes: Enforced by PostgreSQL RLS + app.tenant_id session variable set from JWT.

Definition of Done: Standard DoD plus tenant-isolation.spec.ts green.


TERM-US-010 — Secure concept CRUD for administrators

FieldValue
Issue typeStory
SummaryPlatform and tenant admins can create, update, and deactivate concepts per their scope
Epic linkTERM-EPIC-04
StatusDone
PriorityMust
Story points5
Labelsservice:terminology-service, type:backend, type:api, slice:S0
Componentsadmin-api
FR referencesFR-TERM-008
Legacy FR refsFR-TERM-008
DependenciesTERM-US-009

User story: As an administrator, when governing terminology data, I want secure concept CRUD so that quality and scope are controlled.

Acceptance criteria (Gherkin):

  • Given SUPER_ADMIN and tenantId=null, when create concept is requested, then global concept is created.
  • Given TENANT_ADMIN for own tenant, when create concept is requested, then tenant concept is created and global create is rejected with 403.
  • Given duplicate key in same scope, when create is requested, then HTTP 409 CONFLICT is returned.

Definition of Done: Standard DoD.


TERM-US-011 — Bulk CSV concept import

FieldValue
Issue typeStory
SummaryPlatform operator loads licensed terminology data via optional CSV import endpoint
Epic linkTERM-EPIC-04
StatusDone
PriorityMust
Story points8
Labelsservice:terminology-service, type:backend, slice:S0
Componentsimport-pipeline
FR referencesFR-TERM-009
Legacy FR refsFR-TERM-009
DependenciesTERM-US-010

User story: As a platform operator, when loading licensed data, I want optional CSV import controls so that loading policy is enforceable.

Acceptance criteria (Gherkin):

  • Given import is enabled, when valid CSV is posted by authorized actor, then response includes imported/skipped/errors counts.
  • Given import is disabled by env flag, when endpoint is called, then request is rejected consistently.
  • Given malformed CSV rows, when import runs, then row-level errors are reported without crashing service.

Definition of Done: Standard DoD.


TERM-US-015 — JWT authentication on public routes

FieldValue
Issue typeStory
SummaryAll public terminology routes require valid JWT
Epic linkTERM-EPIC-05
StatusDone
PriorityMust
Story points2
Labelsservice:terminology-service, type:backend, slice:S0
Componentsauth-guards
FR referencesFR-TERM-010
Legacy FR refsFR-TERM-010
DependenciesKeycloak realm

User story: As a security owner, when public APIs are accessed, I want JWT auth and entitlement checks so that unauthorized traffic is blocked.

Acceptance criteria (Gherkin):

  • Given missing or invalid JWT, when /v1 or /fhir routes are called, then HTTP 401 is returned.
  • Given valid JWT without required entitlement, when route is called, then access is denied.
  • Given valid JWT and entitlement, when route is called, then request proceeds.

Definition of Done: Standard DoD.


TERM-US-016 — Internal route shared-secret enforcement

FieldValue
Issue typeStory
SummaryInternal routes optionally enforce a shared-secret token
Epic linkTERM-EPIC-05
StatusDone
PriorityMust
Story points2
Labelsservice:terminology-service, type:backend, slice:S0
Componentsauth-guards
FR referencesFR-TERM-010
Legacy FR refsFR-TERM-010
DependenciesTERM-US-015

User story: As a platform operator, when internal routes are used, I want optional shared-secret enforcement so that trust boundaries remain controlled.

Acceptance criteria (Gherkin):

  • Given internal token env unset, when internal endpoint is called, then request is allowed per network policy.
  • Given token env set and wrong token provided, when endpoint is called, then HTTP 401 is returned.
  • Given token env set and valid token provided, when endpoint is called, then request succeeds.

Definition of Done: Standard DoD.


TERM-US-017 — SLO latency evidence for search endpoint

FieldValue
Issue typeStory
SummaryMeasurable p95 latency evidence produced for NFR compliance
Epic linkTERM-EPIC-05
StatusIn Progress
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, slice:S0
Componentsobservability, performance-tests
FR references(NFR)
Legacy FR refsNFR-TERM-001
DependenciesTERM-US-001

User story: As an operations owner, when monitoring terminology latency, I want measurable p95 evidence so that SLO compliance is provable.

Acceptance criteria (Gherkin):

  • Given load test execution, when search endpoint is measured, then p95 latency result is recorded against ≤ 200ms target.
  • Given dashboard ingestion, when metrics are published, then latency trend is retained for at least 30 days.
  • Given threshold breach, when latency exceeds configured limit, then alert rule can be triggered.

Definition of Done: k6 performance test added to CI (scheduled); Grafana dashboard showing latency trend.


TERM-US-018 — Terminology mutation events via NATS

FieldValue
Issue typeStory
SummaryConcept create, deactivate, and dataset update mutations publish events
Epic linkTERM-EPIC-04
StatusDone
PriorityMust
Story points3
Labelsservice:terminology-service, type:backend, slice:S0
Componentsoutbox-relay
FR references(EVENT_MODEL)
Legacy FR refsEVENT_MODEL
DependenciesNATS JetStream

User story: As an integration consumer, when terminology data changes, I want mutation events so that dependent caches and workflows stay synchronized.

Acceptance criteria (Gherkin):

  • Given concept create succeeds, when transaction completes, then TERMINOLOGY.concept.created event is published once.
  • Given concept deactivate succeeds, when transaction completes, then TERMINOLOGY.concept.deactivated event is published once.
  • Given import completes, when run finishes, then TERMINOLOGY.dataset.updated event is published with run metadata.

Definition of Done: Standard DoD plus outbox.spec.ts green.


TERM-US-019 — Capacity and licensing-safe import posture

FieldValue
Issue typeStory
SummaryService operates within capacity SLOs with licensed data; import posture is compliant
Epic linkTERM-EPIC-05
StatusIn Progress
PriorityMust
Story points5
Labelsservice:terminology-service, type:backend, slice:S0
Componentsobservability, etl
FR references(NFR)
Legacy FR refsNFR-TERM-002, NFR-TERM-003
DependenciesTERM-US-017

User story: As a platform owner, when scaling terminology operations, I want capacity and licensing-safe import posture so that service growth remains compliant.

Acceptance criteria (Gherkin):

  • Given production-like dataset ≥ 300,000 active concepts, when benchmark runs, then query error rate remains within agreed SLO budget.
  • Given licensed terminology assets, when imports are performed, then data is loaded without bundling licensed datasets into source repo.
  • Given capacity test report generation, when run completes, then measured throughput and resource metrics are stored for audit.

Definition of Done: k6 capacity test passing; .gitignore audit clean; MIGRATION_PLAN.md ETL instructions confirmed.


TERM-ENH-US-001 — TerminologyCapabilities metadata endpoint

FieldValue
Issue typeStory
SummaryFHIR $metadata returns TerminologyCapabilities for discovery
Epic linkTERM-ENH-EPIC-01
StatusTo Do
PriorityShould
Story points3
Labelsservice:terminology-service, type:backend, type:api, slice:S1
Componentsfhir-operations
FR referencesFR-TERM-ENH-001
Legacy FR refsFR-TERM-ENH-001
DependenciesTERM-EPIC-01

User story: As an interoperability consumer, when integrating terminology services, I want TerminologyCapabilities metadata so that supported operations are machine-discoverable.

Acceptance criteria (Gherkin):

  • Given /fhir/R4/metadata is called, when service responds, then TerminologyCapabilities resource includes implemented operations.
  • Given unsupported operations exist, when metadata is generated, then unsupported capabilities are absent.

Definition of Done: Standard DoD.


TERM-ENH-US-002 — ConceptMap $translate operation

FieldValue
Issue typeStory
SummaryFHIR $translate maps codes between systems via ConceptMap
Epic linkTERM-ENH-EPIC-01
StatusTo Do
PriorityShould
Story points5
Labelsservice:terminology-service, type:backend, type:api, slice:S1
Componentsconcept-map-api
FR referencesFR-TERM-ENH-002
Legacy FR refsFR-TERM-ENH-002
DependenciesTERM-ENH-US-001

User story: As an integration workflow, when translating between code systems, I want ConceptMap translation support so that mapping can be automated.

Acceptance criteria (Gherkin):

  • Given valid ConceptMap mapping exists, when $translate is called, then mapped target code is returned with match details.
  • Given no mapping exists, when request is processed, then no-match response is returned without server error.

Definition of Done: Standard DoD.


TERM-ENH-US-007 — Per-tenant rate limiting

FieldValue
Issue typeStory
SummaryPer-tenant API quota prevents noisy-tenant impact
Epic linkTERM-ENH-EPIC-03
StatusTo Do
PriorityCould
Story points5
Labelsservice:terminology-service, type:backend, slice:S2
Componentsrate-limiting
FR references(NFR)
Legacy FR refsNFR-TERM-ENH-001
DependenciesTERM-EPIC-05

User story: As a platform operator, when tenants consume terminology APIs, I want per-tenant rate limits so that noisy-tenant impact is contained.

Acceptance criteria (Gherkin):

  • Given per-tenant quota is configured, when request volume exceeds limit, then excess requests receive HTTP 429.
  • Given request is within quota, when endpoint is called, then request succeeds without throttle.

Definition of Done: Standard DoD plus rate-limit integration test green.