To FHIR or not to FHIR
Background
HL7 FHIR is the recommended format for sharing health data digitally in Norway, as defined by direktoratet for E-helse (read more). However, as the first version of the DHG-API (Digitalt helsekort for gravide-API) is a trial to get started with sharing maternity-data digitally between different actors in Norway, other factors also need to be considered when choosing the data model. To make sure the project is a success, we want to make sure we choose the format that is easiest for EPJ-suppliers to get started with. We would therefore like your input before making a decision on the format. Below we will present examples of what a Maternity health record might look like using FHIR, and also in a format designed spesifically for DHG in Norway.
This document is intended for EPJ-providers who are interested in having a say in how we build the API. It is intended for people having a fairly high technical knowledge with building and using APIs, typically software developers and/or architects. Some knowledge of FHIR is useful, but not required.
Any feedback or questions can be sent via email to the product owner, Asefeh Johnsen, before Monday October 7th - or even better via our slack-channel to make the discussion as transparent as possible (reach out to Asefeh via email to be invited to our open slack channel).
Important note: If we decide to use a custom format for the first version of the API, that does not mean that we will never provide a FHIR-version of the API in the future. It is possible we might want to support several formats if that's what's best for the sector.
Feedback
The deadline for feedback is Monday October 7th.
We will publish feedback received on this discussion continuously on a separate feedback page.
The data model
The functional description of the API is outlined in detail in "Høringsnotat - Utkast til nasjonal informasjonsmodell for elektronisk helsekort for gravide". The "høringsnotat" describes what data will be available in the first version of the API, so here we will only discuss the format of the data, not the content. There are a lot of data points planning to be included in the API, so in the examples we will only be including some of them to show what the format might look like. The fields we will provide examples of are the following (in norwegian):
Important note: The examples provided is a suggestion of what the data model might look like, but it's not a fully thought out API model. The FHIR profiles used, the names of the fields, values used and so on can change once we land on FHIR or not FHIR. The examples are intended to give an idea of the general differences between FHIR and a custom format - it's not the finished data model.
Mor |
---|
NIN |
Navn |
Adr. |
Sivilstatus |
Høyeste utdanning |
Yrkesaktiv siste 6 mnd. |
Prosent yrkesaktiv |
Yrke og bransje |
Yrke og Bransje tekslig |
Bostedsadresse |
språk |
Far eller medmor |
---|
Far eller medmor |
Yrke og bransje |
Yrke og Bransje tekslig |
språk |
Aktuelt svangerskap |
---|
Dato for siste mens |
Siste menstruasjon sikker |
Termin |
Dato terminberegning |
Svangerskapskonsultasjoner, Opplysninger per foster |
---|
Presentasjons/leie |
fosterlyd/min |
kjenner liv |
merknader |
Example Custom DHG Format
On create, fill out wanted fields (in this example "mor",
"far-eller-medmor" and "aktuelt svangerskap") in JSON, and POST
/POST/DHG
Body:
{
"mother": {
"name": "string",
"nin": "fnr/dnr",
"address": "string",
"residentialAddress": "string",
"maritalStatus": "Kodeverk 3103 Sivilstand",
"highestEducation": "Kodeverk 8707 Høyeste fullførte utdanning",
"employedLast6Months": "true/false",
"employmentPercentage": "number",
"occupationAndIndustryCode": ["list of job-codes", "Kodeverk 8646"],
"occupationAndIndustry": "free text string",
"language": "Kodeverk 3303 Språk"
},
"coParent": {
"name": "string",
"nin": "fnr/dnr",
"occupationAndIndustryCode": ["list of job-codes", "Kodeverk 8646"],
"occupationAndIndustry": "free text string",
"language": "Kodeverk 3303 Språk"
},
"currentPregnancy": {
"lastMenstruationDate": "date",
"lastMenstruationDateConfirmed": "true/false",
"dueDate": "date",
"dueDateLastCalculated": "date"
}
}
Get current DGH, then create list of prenatalConsultations, or
append to list if it already exists
/GET/DHG
Response:
{
"mother": { ... },
"coParent": { ... },
"currentPregnancy": { ... }
}
/POST/DHG
{
"mother": { ... },
"coParent": { ... },
"currentPregnancy": { ... },
"prenatalConsultations": [
{
"date": "dateTime",
"fetusVitalSigns": [
{
"position": "Hodeleie | Seteleie | Tverrleie",
"fetalHeartRatePerMin": "number",
"fetalMovementFelt": "true/false",
"notes": "string"
}
]
}
]
}
Simple GET with nin as param
/GET/DHG
{
"metadata": {
"version": "number",
"lastUpdated": "dateTime",
"lastUpdatedBy": {
"orgNr": "number",
"orgNrDisplay": "string",
"hprNr": "number",
"hprRole": "Volven 9060",
"name": "string"
}
},
"mother": {
"name": "string",
"nin": "fnr/dnr",
"address": "string",
"residentialAddress": "string",
"maritalStatus": "Kodeverk 3103 Sivilstand",
"highestEducation": "Kodeverk 8707 Høyeste fullførte utdanning",
"employedLast6Months": "true/false",
"employmentPercentage": "number",
"occupationAndIndustryCode": ["list of job-codes", "Kodeverk 8646"],
"occupationAndIndustry": "free text string",
"language": "Kodeverk 3303 Språk"
},
"coParent": {
"name": "string",
"nin": "fnr/dnr",
"occupationAndIndustryCode": ["list of job-codes", "Kodeverk 8646"],
"occupationAndIndustry": "free text string",
"language": "Kodeverk 3303 Språk"
},
"currentPregnancy": {
"lastMenstruationDate": "date",
"lastMenstruationDateConfirmed": "true/false",
"dueDate": "date",
"dueDateLastCalculated": "date"
},
"prenatalConsultations": [
{
"date": "dateTime",
"fetusVitalSigns": [
{
"position": "Hodeleie | Seteleie | Tverrleie",
"fetalHeartRatePerMin": "number",
"fetalMovementFelt": "true/false",
"notes": "string"
}
]
}
]
}
Example FHIR Format
FHIR JSON:
curl --location --request POST '/api/patient/Patient'
--header 'Content-Type: application/json'
--data-raw :
{
"resourceType": "Patient",
"id": "138afd87-9d10-4698-aa04-2446b472a3dd",
"extension": [
{
// Høyeste utdannin
"url": "http://nhn.no/fhir/dhg/patient-educationLevel",
"valueCodeableConcept": {
"coding": [
{
"system": "urn:oid:2.16.578.1.12.4.1.1..8707",
"code": "5",
"display": "Treårig høgskole/universitet"
}
]
}
},
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-citizenship",
"valueCodeableConcept": {
"coding": [
{
"system": "urn:iso:std:iso:3166",
"code": "FI",
"display": "Finland"
}
]
}
}
],
"identifier": [
{
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "31129914400"
}
],
"name": [
{
"family": "GravidHelle",
"given": [
"AnneMor"
]
}
],
"telecom": [
{
"system": "phone",
"value": "+4790255014",
"use": "mobile"
}
],
"gender": "female",
"address": [
{
"line": [
"Strandveien 12"
],
"city": "Asker",
"postalCode": "1386"
}
],
"maritalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",
"code": "M",
"display": "Married"
}
]
},
"communication": [
{
"language": {
"coding": [
{
"system": "urn:oid:2.16.578.1.12.4.1.1.3303",
"code": "FI",
"display": "FINSK"
}
]
}
},
{
// Behov for tolk
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired",
"valueBoolean": false
}
],
"language": {
"text": "Findlands svensk"
},
"preferred": true
}
]
}
curl --location '/dhg/Condition' --data
{
"resourceType": "Condition",
"id": "9e23bf85-bb0a-4a3b-8950-8a8d15cc4803",
"extension": [
{
"url": "http://nhn.no/fhir/dhg/type-terminberegning",
"valueCodeableConcept": {
"text": "eSnurra"
}
}
],
"clinicalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "ACTIVE",
"display": "Active"
}
]
},
"verificationStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-ver-status",
"code": "CONFIRMED",
"display": "Confirmed"
}
]
},
"category": [
{
"coding": [
{
"system": "http://snomed.info/sct",
"code": "439401001",
"display": "diagnosis"
}
]
}
],
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "77386006",
"display": "gravid"
}
]
},
"subject": {
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "31129914400"
}
},
"onsetPeriod": {
"start": "2024-08-13T13:06:01+00:00",
"end": "2025-05-10T13:06:01+00:00"
}
}
curl --location '/dhg/Observation' --data
{
"resourceType": "Observation",
"id": "9541db45-a20c-4ce2-b78a-977551a4c006",
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "278084002",
"display": "Normal baseline fetal heart rate"
}
]
},
"subject": {
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
},
"encounter": {
"reference": "Encounter/67890"
},
"effectiveInstant": "2024-09-20T10:18:13.198+00:00",
"performer": [
{
"type": "PractitionerRole",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.4",
"value": "46546"
},
"display": "Lege Legesen"
}
],
"valueQuantity": {
"value": 140,
"unit": "beats/minute",
"system": "http://unitsofmeasure.org",
"code": "/min"
}
}
curl --location 'http://localhost:8080/api/careplan/dhg/_search' \
--data-urlencode 'subject%3APatient.identifier=99116900216' \
--data-urlencode '_include=*' \
--data-urlencode 'status=active'
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "c9c2d18c-6d6b-440b-a6ce-4f3d8d42207a",
"meta": {
"versionId": "1",
"lastUpdated": "2024-09-20T09:31:10.506+00:00"
},
"extension": [
{
// Høyeste utdanning
"url": "http://nhn.no/fhir/dhg/patient-educationLevel",
"valueCodeableConcept": {
"coding": [
{
"system": "urn:oid:2.16.578.1.12.4.1.1..8707",
"code": "5",
"display": "Treårig høgskole/universitet"
}
]
}
},
{
// Mor landbakgrunn
"url": "http://hl7.org/fhir/StructureDefinition/patient-citizenship",
"valueCodeableConcept": {
"coding": [
{
"system": "urn:iso:std:iso:3166",
"code": "FI",
"display": "Finland"
}
]
}
}
],
"identifier": [
{
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
],
"name": [
{
"family": "GravidHelle",
"given": [
"AnneMor"
]
}
],
"telecom": [
{
"system": "phone",
"value": "+4790255014",
"use": "mobile"
}
],
"gender": "female",
"address": [
{
"line": [
"Strandveien 12"
],
"city": "Asker",
"postalCode": "1386"
}
],
"maritalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-MaritalStatus",
"code": "M",
"display": "Married"
}
]
},
"communication": [
{
// Mor språk
"language": {
"coding": [
{
"system": "urn:oid:2.16.578.1.12.4.1.1.3303",
"code": "FI",
"display": "FINSK"
}
]
}
},
{
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired",
"valueBoolean": false
}
],
"language": {
"text": "Findlands svensk"
},
"preferred": true
}
]
},
"search": {
"mode": "match"
}
},
{
"resource": {
// Svangerskap
"resourceType": "Condition",
"id": "267936b2-a199-4fc9-8cde-12571b9767c6",
"meta": {
"versionId": "1",
"lastUpdated": "2024-09-20T12:06:42.345+00:00"
},
// Type terminberegning
"extension": [
{
"url": "http://nhn.no/fhir/dhg/type-terminberegning",
"valueCodeableConcept": {
"text": "eSnurra"
}
}
],
"clinicalStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "ACTIVE",
"display": "Active"
}
]
},
"verificationStatus": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/condition-ver-status",
"code": "CONFIRMED",
"display": "Confirmed"
}
]
},
"category": [
{
"coding": [
{
"system": "http://snomed.info/sct",
"code": "439401001",
"display": "diagnosis"
}
]
}
],
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "77386006",
"display": "gravid"
}
]
},
"subject": {
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
},
"onsetPeriod": {
"start": "2024-08-18T15:02:04+00:00",
"end": "2025-05-15T15:02:04+00:00"
}
},
"search": {
"mode": "include"
}
},
{
"resource": {
// Mors yrke
"resourceType": "Observation",
"id": "f94ca844-d422-4a06-99cf-1417ed5eff8f",
"meta": {
"versionId": "1",
"lastUpdated": "2024-09-20T10:19:33.586+00:00"
},
// Yrkesaktiv siste 6 mnd
"extension": [
{
"url": "http://nhn.no/fhir/dhg/occupationally_active_last_6_mnd",
"valueBoolean": true
}
],
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "social-history",
"display": "Social History"
}
]
}
],
"code": {
"coding": [
{
"system": "urn:oid:2.16.578.1.12.4.1.1..8646",
"code": "2551127",
"display": "KUNSTMALER"
}
]
},
"subject": {
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
},
"effectiveDateTime": "2024-09-17T16:18:27+00:00",
"performer": [
{
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
}
],
// Prosent yrkesaktiv
"valueQuantity": {
"value": 60.0,
"unit": "%",
"system": "http://unitsofmeasure.org"
}
},
"search": {
"mode": "include"
}
},
{
"resource": {
// Måling under svangerskapskonsultasjon
"resourceType": "Observation",
"id": "564583f8-d295-497a-a8f6-4cdc641769de",
"meta": {
"versionId": "1",
"lastUpdated": "2024-09-20T10:19:22.083+00:00",
"tag": [
{
"system": "urn:oid:2.16.578.1.12.4.1.4.4",
"code": "222200063",
"display": "Rolf Mock Lillehagen"
},
{
"system": "urn:oid:2.16.578.1.12.4.1.4.101",
"code": "993187178"
},
{
"system": "urn:oid:2.16.578.1.12.4.1.1.9060",
"code": "LE",
"display": "Lege"
}
]
},
"status": "final",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "278084002",
"display": "Normal baseline fetal heart rate"
}
]
},
"subject": {
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
},
"encounter": {
"reference": "Encounter/67890"
},
"effectiveInstant": "2024-09-20T10:18:13.198+00:00",
"performer": [
{
"type": "PractitionerRole",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.4",
"value": "46546"
},
"display": "Lege Legesen"
}
],
"valueQuantity": {
"value": 140,
"unit": "beats/minute",
"system": "http://unitsofmeasure.org",
"code": "/min"
}
},
"search": {
"mode": "include"
}
},
{
"resource": {
// Siste mens
"resourceType": "Observation",
"id": "3f892848-e82e-4620-a9a5-c6838e82833b",
"meta": {
"versionId": "1",
"lastUpdated": "2024-09-20T14:25:01.593+00:00"
},
"status": "registered",
"category": [
{
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}
]
}
],
"code": {
"coding": [ {
"system": "http://snomed.info/sct",
"code": "21840007",
"display": "Date of last menstrual period"
} ]
},
"subject": {
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
},
"effectiveDateTime": "2024-04-25",
"performer": [
{
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
}
]
},
"search": {
"mode": "include"
}
},
{
"resource": {
// Svangerskapskonsultasjon
"resourceType": "Encounter",
"id": "a7866fec-8bad-42e3-942a-e6d30549894a",
"meta": {
"versionId": "1",
"lastUpdated": "2024-09-20T09:30:35.893+00:00",
},
"status": "finished",
"class": {
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "AMB",
"display": "ambulatory"
},
"type": [
{
"coding": [
{
"system": "http://snomed.info/sct",
"code": "424441002",
"display": "prenatal visit"
}
]
}
],
"subject": {
"type": "Patient",
"identifier": {
"system": "urn:oid:2.16.578.1.12.4.1.4.1",
"value": "99116900216"
}
},
"period": {
"start": "2024-09-20T09:30:17+00:00",
"end": "2024-09-20T09:50:17+00:00"
}
},
"search": {
"mode": "include"
}
}
]
}
FHIR code example (Kotlin):
fun createMotherPatient(): Patient {
val mother = Patient()
mother.setId(UUID.randomUUID().toString())
val motherNin = "31129914400"
mother.addIdentifier(Identifier().setSystem("2.16.578.1.12.4.1.4.1").setValue(motherNin))
//Navn
mother.addName(HumanName().setFamily("GravidHelle").addGiven("AnneMor"))
//Adresse
mother.addAddress(Address().addLine("Strandveien 12").setPostalCode("1386").setCity("Asker"))
//Telefon
mother.addTelecom(
ContactPoint().setSystem(ContactPoint.ContactPointSystem.PHONE)
.setValue("+4790255014")
.setUse(ContactPoint.ContactPointUse.MOBILE)
)
mother.setGender(AdministrativeGender.FEMALE)
//Sivilstatus
mother.setMaritalStatus(
CodeableConcept().addCoding(
Coding().setSystem("http://terminology.hl7.org/CodeSystem/v3-MaritalStatus")
.setCode("M")
.setDisplay("Married")
)
)
//Høyeste utdanning -NB Extension
mother.addExtension(
Extension().setUrl("http://nhn.no/fhir/dhg/patient-educationLevel")
.setValue(
CodeableConcept().addCoding(
Coding().apply {
system = "urn:oid:2.16.578.1.12.4.1.1.8707"
code = "5"
display = "Treårig høgskole/universitet"
})
)
)
//Språk 1
mother.addCommunication(
Patient.PatientCommunicationComponent()
.setLanguage(
CodeableConcept().addCoding(
Coding()
.setSystem("urn:oid:2.16.578.1.12.4.1.1.3303").setCode("FI").setDisplay("FINSK")
)
)
)
//Språk 2 foretrukket
val communication2 = Patient.PatientCommunicationComponent()
communication2.setLanguage(CodeableConcept().setText("Findlands svensk")) //Mangler kode i Volven 3303
communication2.setPreferred(true)
//Behov for tolk NB! extension
communication2.addExtension(
Extension()
.setUrl("http://hl7.org/fhir/StructureDefinition/patient-interpreterRequired")
.setValue(BooleanType(false))
)
mother.addCommunication(communication2)
//Mor landbakgrunn NB! Extension
mother.addExtension(Extension().setUrl("http://hl7.org/fhir/StructureDefinition/patient-citizenship")
.setValue(CodeableConcept()
.addCoding(
Coding().apply {
system = "urn:iso:std:iso:3166"
code = "FI"
display = "Finland"
}
)))
return mother
}
fun createPregnancyCondition(nin: String): Condition {
val condition = Condition()
condition.id = UUID.randomUUID().toString()
condition.setSubject(Reference().setIdentifier(Identifier().setSystem(OID_NIN).setValue(nin)))
condition.setClinicalStatus(
CodeableConcept().addCoding(
Coding()
.setSystem(ConditionClinical.ACTIVE.system)
.setCode(ConditionClinical.ACTIVE.name)
.setDisplay(ConditionClinical.ACTIVE.display)
)
)
condition.setVerificationStatus(
CodeableConcept().addCoding(
Coding()
.setSystem(ConditionVerStatus.CONFIRMED.system)
.setCode(ConditionVerStatus.CONFIRMED.name)
.setDisplay(ConditionVerStatus.CONFIRMED.display)
)
)
condition.addCategory(
CodeableConcept().addCoding(
Coding()
.setSystem("http://snomed.info/sct")
.setCode("439401001")
.setDisplay("diagnosis")
)
)
condition.setCode(
CodeableConcept().addCoding(
Coding()
.setSystem("http://snomed.info/sct")
.setCode("77386006")
.setDisplay("gravid")
)
)
val period = Period()
period.start = Date.from(Instant.now().minus(30, ChronoUnit.DAYS))
period.end = Date.from(Instant.now().plus(240, ChronoUnit.DAYS))
condition.onset = period
//Terminberegnings metode NB! Extension
condition.addExtension(
Extension().setUrl("http://nhn.no/fhir/dhg/type-terminberegning")
.setValue(CodeableConcept().setText("eSnurra"))
)
}
fun createPrenatalConsultation(motherNin: String): Encounter {
val encounter = Encounter()
encounter.id = UUID.randomUUID().toString()
encounter.setSubject(createSubjectRef(motherNin))
encounter.setClass_( Coding("http://terminology.hl7.org/CodeSystem/v3-ActCode", "AMB", "ambulatory"))
val type = CodeableConcept(
Coding("http://snomed.info/sct", "424441002", "prenatal visit")
)
encounter.addType(type)
val period = Period()
period.start = Date.from(Instant.now())
period.end = Date.from(Instant.now().plus(20, ChronoUnit.MINUTES))
encounter.period = period
encounter.setStatus(Encounter.EncounterStatus.FINISHED)
return encounter
}
fun createFetusObservation(motherNin: String): Observation {
val observation = Observation()
observation.id = UUID.randomUUID().toString()
observation.setSubject(createSubjectRef(motherNin))
observation.setStatus(Observation.ObservationStatus.FINAL)
val category = CodeableConcept(
Coding("http://terminology.hl7.org/CodeSystem/observation-category", "vital-signs", "Vital Signs")
)
observation.addCategory(category)
observation.setEncounter(Reference("Encounter/67890"))
observation.code = CodeableConcept().addCoding(
Coding().apply {
system = "http://snomed.info/sct"
code = "278084002"
display = "Normal baseline fetal heart rate"
}
)
observation.value =
Quantity().setValue(140).setUnit("beats/minute").setSystem("http://unitsofmeasure.org").setCode("/min")
observation.effective = InstantType.now()
val practitionerRoleRef = Reference()
practitionerRoleRef.setType("PractitionerRole")
practitionerRoleRef.setIdentifier(Identifier().setSystem("2.16.578.1.12.4.1.4.4").setValue("46546"))
practitionerRoleRef.setDisplay("Lege Legesen")
observation.addPerformer(practitionerRoleRef)
return observation
}