Files
bd-fhir-national/ops/adding-additional-igs.md
2026-03-16 00:02:58 +06:00

262 lines
11 KiB
Markdown

# Adding Additional Implementation Guides
**Audience:** DGHS FHIR development and operations team
**Applies to:** Any IG added after BD Core FHIR IG v0.2.1
**Current IGs:** BD Core (`https://fhir.dghs.gov.bd/core`)
**Planned IGs:** MCCoD (`https://fhir.dghs.gov.bd/mccod`), IMCI (`https://fhir.dghs.gov.bd/imci`)
---
## Overview
Each DGHS Implementation Guide has its own canonical URL namespace:
| IG | Canonical base | Package naming convention |
|----|---------------|--------------------------|
| BD Core | `https://fhir.dghs.gov.bd/core` | `bd.gov.dghs.core-{version}.tgz` |
| MCCoD | `https://fhir.dghs.gov.bd/mccod` | `bd.gov.dghs.mccod-{version}.tgz` |
| IMCI | `https://fhir.dghs.gov.bd/imci` | `bd.gov.dghs.imci-{version}.tgz` |
Separate canonical namespaces mean profiles from different IGs never collide regardless of resource type overlap. A `Composition` profiled in MCCoD at `https://fhir.dghs.gov.bd/mccod/StructureDefinition/mccod-composition` and a `Composition` profiled in a future Core IG extension are completely independent. HAPI validates a resource against whichever profile URL it declares in `meta.profile`.
All packages are loaded into a single `NpmPackageValidationSupport` instance. HAPI merges them into one validation context at startup. There is no performance penalty for multiple IGs — profiles are loaded once into memory and reused across all validation calls.
---
## What changes when adding a new IG
### 1. `packages/` directory
Place the new IG `.tgz` alongside the existing core IG package:
```
hapi-overlay/src/main/resources/packages/
├── bd.gov.dghs.core-0.2.1.tgz ← existing
├── bd.gov.dghs.mccod-1.0.0.tgz ← new
└── bd.gov.dghs.imci-1.0.0.tgz ← new
```
Only one version of each IG per image. If you are upgrading an existing IG, remove the old `.tgz` and place the new one.
### 2. `FhirServerConfig.java` — load the new package
Find the `npmPackageValidationSupport()` bean and add a `loadPackageFromClasspath()` call for each new IG:
```java
@Bean
public NpmPackageValidationSupport npmPackageValidationSupport(FhirContext fhirContext) {
NpmPackageValidationSupport support = new NpmPackageValidationSupport(fhirContext);
// BD Core IG — always present
support.loadPackageFromClasspath(
"classpath:packages/bd.gov.dghs.core-0.2.1.tgz");
// MCCoD IG — add when deploying
support.loadPackageFromClasspath(
"classpath:packages/bd.gov.dghs.mccod-1.0.0.tgz");
// IMCI IG — add when deploying
support.loadPackageFromClasspath(
"classpath:packages/bd.gov.dghs.imci-1.0.0.tgz");
return support;
}
```
### 3. `FhirServerConfig.java` — register new resource types
The `BD_CORE_PROFILE_RESOURCE_TYPES` set determines which resource types receive full profile validation versus the `unvalidated-profile` tag. Add every resource type that any of your IGs profiles:
```java
private static final Set<String> BD_CORE_PROFILE_RESOURCE_TYPES = Set.of(
// BD Core IG
"Patient", "Condition", "Encounter", "Observation",
"Practitioner", "Organization", "Location",
"Medication", "MedicationRequest", "Immunization",
// MCCoD IG — add the resource types your MCCoD IG profiles
"Composition", "MedicationStatement",
// IMCI IG — add the resource types your IMCI IG profiles
"QuestionnaireResponse", "ClinicalImpression"
);
```
If a resource type appears in multiple IGs (e.g., `Composition` in both MCCoD and a future Core extension), add it once. HAPI validates against whichever profile URL the submitted resource declares — it does not matter that multiple profiles for that type are loaded.
### 4. `IgPackageInitializer.java` — load metadata for each new package
The initialiser currently loads one package under an advisory lock. Extend it to load each package. The advisory lock pattern remains the same — one lock per package, identified by package ID:
```java
@Override
public void afterPropertiesSet() throws Exception {
loadIgPackage(
"classpath:packages/bd.gov.dghs.core-0.2.1.tgz",
"bd.gov.dghs.core", "0.2.1");
loadIgPackage(
"classpath:packages/bd.gov.dghs.mccod-1.0.0.tgz",
"bd.gov.dghs.mccod", "1.0.0");
loadIgPackage(
"classpath:packages/bd.gov.dghs.imci-1.0.0.tgz",
"bd.gov.dghs.imci", "1.0.0");
}
private void loadIgPackage(
String classpathPath,
String packageId,
String version) throws Exception {
long lockKey = deriveLockKey(packageId);
// ... same advisory lock acquisition logic as current implementation
// ... same performIgLoad() call
// Each package gets its own independent advisory lock key
// so packages load concurrently across replicas without blocking each other
}
```
### 5. `application.yaml` — add new IG configuration entries
Under the `bd.fhir.ig` section, add entries for the new packages. This makes IG paths configurable without recompiling:
```yaml
bd:
fhir:
ig:
packages:
- classpath: classpath:packages/bd.gov.dghs.core-0.2.1.tgz
id: bd.gov.dghs.core
version: 0.2.1
- classpath: classpath:packages/bd.gov.dghs.mccod-1.0.0.tgz
id: bd.gov.dghs.mccod
version: 1.0.0
- classpath: classpath:packages/bd.gov.dghs.imci-1.0.0.tgz
id: bd.gov.dghs.imci
version: 1.0.0
```
Update `FhirServerConfig.java` to read this list and loop over it rather than having hardcoded paths. This means adding a new IG in future requires only a config change and new `.tgz` — no Java code change.
### 6. `.env` — add new IG version variables
Add version tracking variables for operational visibility and for the `/actuator/info` endpoint:
```bash
# BD Core IG
HAPI_IG_CORE_VERSION=0.2.1
# MCCoD IG
HAPI_IG_MCCOD_VERSION=1.0.0
# IMCI IG
HAPI_IG_IMCI_VERSION=1.0.0
```
### 7. Gitea workflow — add new IG package secrets
For each new IG, add a Gitea secret and decode it in the build step:
**Gitea → Repository → Settings → Secrets — add:**
| Secret | Value |
|--------|-------|
| `MCCOD_PACKAGE_B64` | `base64 -w 0 bd.gov.dghs.mccod-1.0.0.tgz` |
| `IMCI_PACKAGE_B64` | `base64 -w 0 bd.gov.dghs.imci-1.0.0.tgz` |
**Gitea → Repository → Settings → Variables — add:**
| Variable | Value |
|----------|-------|
| `MCCOD_PACKAGE_FILENAME` | `bd.gov.dghs.mccod-1.0.0.tgz` |
| `IMCI_PACKAGE_FILENAME` | `bd.gov.dghs.imci-1.0.0.tgz` |
**In `.gitea/workflows/build.yml` — extend the IG placement step:**
```yaml
- name: Place IG packages for build
run: |
echo "${{ secrets.IG_PACKAGE_B64 }}" | base64 -d > \
hapi-overlay/src/main/resources/packages/${{ vars.IG_PACKAGE_FILENAME }}
echo "${{ secrets.MCCOD_PACKAGE_B64 }}" | base64 -d > \
hapi-overlay/src/main/resources/packages/${{ vars.MCCOD_PACKAGE_FILENAME }}
echo "${{ secrets.IMCI_PACKAGE_B64 }}" | base64 -d > \
hapi-overlay/src/main/resources/packages/${{ vars.IMCI_PACKAGE_FILENAME }}
echo "Packages placed:"
ls -lh hapi-overlay/src/main/resources/packages/
```
**And extend the cleanup step:**
```yaml
- name: Clean up IG packages from workspace
if: always()
run: rm -f hapi-overlay/src/main/resources/packages/*.tgz
```
---
## What does not change
| Component | Reason |
|-----------|--------|
| Validation chain order | `NpmPackageValidationSupport` handles all loaded IGs transparently |
| OCL integration | `BdTerminologyValidationSupport` intercepts only `http://id.who.int/icd/release/11/mms` — other systems in any IG route through normally |
| Cluster expression validator | ICD-11 specific — unaffected by other IGs |
| Keycloak auth | No change — all vendors use `mci-api` role regardless of which IG they submit against |
| Audit tables | Schema is resource-type agnostic — new resource types are captured automatically |
| PostgreSQL schema | No migration needed — HAPI JPA stores all FHIR R4 resource types in the same tables |
| pgBouncer, nginx proxy config | Infrastructure is IG-agnostic |
---
## Terminology considerations for new IGs
If MCCoD or IMCI IGs introduce coded elements using systems **other than ICD-11** that are already in your OCL instance (e.g., LOINC, drug ValueSets), no additional configuration is needed. `BdTerminologyValidationSupport` only handles ICD-11. All other systems fall through to HAPI's standard remote terminology mechanism which already calls OCL.
If a new IG introduces a **new terminology system** not currently in OCL:
1. Import the new system into OCL first.
2. Verify OCL `$validate-code` works for the new system: `curl "https://tr.ocl.dghs.gov.bd/api/fhir/CodeSystem/$validate-code?system={new-system-url}&code={test-code}"`
3. No HAPI code changes needed — HAPI's remote terminology support handles any system OCL knows about.
If a new IG introduces a terminology system that will **never be in OCL** (e.g., a purely local ValueSet defined within the IG itself), HAPI will validate it using `InMemoryTerminologyServerValidationSupport` from the concepts loaded with the IG package. No external call is made.
---
## Upgrade procedure for an existing specialised IG
When MCCoD advances from v1.0.0 to v1.1.0:
1. Place `bd.gov.dghs.mccod-1.1.0.tgz` in `packages/`, remove `bd.gov.dghs.mccod-1.0.0.tgz`.
2. Update the package path in `FhirServerConfig.java` (or in `application.yaml` if you implemented the config-driven approach from Step 5 above).
3. Update `MCCOD_PACKAGE_FILENAME` Gitea variable to `bd.gov.dghs.mccod-1.1.0.tgz`.
4. Update `MCCOD_PACKAGE_B64` Gitea secret with the new package base64.
5. Tag and push — CI builds and pushes the new image.
6. Deploy the new image on the production server.
If the IG upgrade changes terminology ValueSets in OCL (new codes, reclassified codes), follow the cache flush procedure in `ops/version-upgrade-integration.md` after deployment.
---
## Deployment checklist for a new IG
- [ ] New IG `.tgz` placed in `packages/`, filename follows naming convention
- [ ] `FhirServerConfig.java``npmPackageValidationSupport()` loads new package
- [ ] `FhirServerConfig.java``BD_CORE_PROFILE_RESOURCE_TYPES` updated with new resource types
- [ ] `IgPackageInitializer.java` — new package included in initialisation loop
- [ ] `application.yaml` — new IG entry added under `bd.fhir.ig.packages`
- [ ] `.env` — new IG version variable added
- [ ] Gitea secrets — new `*_PACKAGE_B64` secret created
- [ ] Gitea variables — new `*_PACKAGE_FILENAME` variable created
- [ ] Gitea workflow — new package decode and cleanup steps added
- [ ] New image built, pushed, deployed
- [ ] Acceptance test: submit a resource claiming the new IG profile → 201 accepted
- [ ] Acceptance test: submit a resource violating the new IG profile → 422 rejected
- [ ] Acceptance test: existing Core IG submissions still work → 201 accepted
- [ ] Vendors notified of new IG availability and profile URLs