Terminology Service — Application Logic
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
1. Commands
| Command | Handler | Source FR | Notes |
|---|---|---|---|
CreateConcept | ConceptCommandHandler | FR-TERM-008 | Platform admin (global) or tenant admin (tenant-scoped) |
UpdateConcept | ConceptCommandHandler | FR-TERM-008 | Updates display or definition only; code/system immutable |
DeactivateConcept | ConceptCommandHandler | FR-TERM-008 | One-way; publishes TerminologyConcept.Deactivated |
ImportConceptBatch | ImportCommandHandler | FR-TERM-009 | Admin CSV import; may be disabled via env flag; publishes TerminologyDataset.Updated |
2. Queries
| Query | Handler | Source FR | Cache | Notes |
|---|---|---|---|---|
SearchConcepts | ConceptQueryHandler | FR-TERM-001 | No (dynamic) | Full-text search; active concepts only; respects tenantId scope |
LookupConcept | ConceptQueryHandler | FR-TERM-002 | Redis 1h | Exact system+code lookup |
ValidateConcept | ConceptQueryHandler | FR-TERM-003 | Redis 1h | Returns {valid: boolean, active: boolean} |
ExpandValueSet | ValueSetQueryHandler | FR-TERM-004 | Redis 5min | Returns active concepts for a ValueSet URL |
GetDrugClasses | DrugClassQueryHandler | FR-TERM-005 | Redis 1h | Returns class(es) for an RxNorm code |
CheckDrugInteractions | DrugInteractionQueryHandler | FR-TERM-006 | No (dynamic) | Returns pairwise interactions for supplied RxNorm codes |
CheckDuplicateTherapy | DrugInteractionQueryHandler | FR-TERM-011 | No (dynamic) | Returns drug pairs sharing a drug class |
CheckContraindications | DrugInteractionQueryHandler | FR-TERM-012 | No (dynamic) | Returns drug-condition contraindication matches |
TranslateConcept | ConceptMapQueryHandler | TERM-ENH-EPIC-01 | Redis 1h | Maps code from source to target system via ConceptMap |
3. Ports (Hexagonal Architecture)
| Port | Direction | Adapter |
|---|---|---|
IConceptRepository | Out | Drizzle ORM → PostgreSQL |
IDrugInteractionRepository | Out | Drizzle ORM → PostgreSQL |
IDrugClassRepository | Out | Drizzle ORM → PostgreSQL |
IDrugContraindicationRepository | Out | Drizzle ORM → PostgreSQL |
IValueSetRepository | Out | Drizzle ORM → PostgreSQL |
IConceptCache | Out | Redis (ioredis) |
IEventPublisher | Out | NATS JetStream via @ghasi/nats-client |
IImportFileReader | Out | MinIO or local filesystem (CSV) |
4. Sequence: Concept Lookup (with Cache)
5. Sequence: Drug Interaction Check
6. Sequence: ValueSet Expansion
7. Sequence: Bulk Concept Import
8. Business Rules (applied in application layer)
| Rule | Enforcement point |
|---|---|
| Only active concepts returned by public queries | ConceptQueryHandler — appends AND active = true to all read queries |
| Global concepts visible to all tenants; tenant concepts visible only to owner | ConceptQueryHandler — WHERE tenant_id IS NULL OR tenant_id = :tenantId |
| Code and system are immutable after creation | UpdateConcept command rejects changes to code/system fields |
| Deactivation is one-way | DeactivateConcept command checks active = true before proceeding |
| Only platform admin may manage global concepts | ConceptCommandHandler — checks hasRole('platform:admin') when tenantId = null |
| Drug interactions checked symmetrically | Query builds both (A,B) and (B,A) pairs before DB lookup |
Duplicate therapy: two drugs sharing at least one className | DrugInteractionQueryHandler — joins drug_classes on rxnormCode IN (codes) and groups by className |
Import endpoint disabled if TERMINOLOGY_CONCEPT_IMPORT_ENABLED=false | ImportCommandHandler — returns 403 at guard level when flag is off |