FHIR Mapping Bidirectional conversion between clinical shorthand and FHIR R4 Timing / Dosage structures.
Key invariant:
FromFhirTiming(ToFhirTiming(code)) == code for all 33 supported codes.
Three documented ambiguities resolve to equivalent codes.
Frequency → FHIR Timing
How each frequency code maps to Timing.repeat and Timing.code fields.
| Code | frequency | period | periodUnit | timeOfDay | when | count | asNeeded | FHIR code |
|---|---|---|---|---|---|---|---|---|
OD | 1 | 1 | d | ["08:00"] | QD | |||
BD | 2 | 1 | d | ["08:00","20:00"] | BID | |||
TDS | 3 | 1 | d | ["08:00","14:00","20:00"] | TID | |||
QDS | 4 | 1 | d | ["06:00","12:00","18:00","22:00"] | QID | |||
5X_DAILY | 5 | 1 | d | ["06:00","10:00","14:00","18:00","22:00"] | — | |||
Q1H | 1 | 1 | h | Q1H | ||||
Q2H | 1 | 2 | h | — | ||||
Q4H | 1 | 4 | h | Q4H | ||||
Q6H | 1 | 6 | h | Q6H | ||||
Q8H | 1 | 8 | h | — | ||||
Q12H | 1 | 12 | h | — | ||||
Q24H | 1 | 24 | h | — | ||||
Q36H | 1 | 36 | h | — | ||||
Q48H | 1 | 48 | h | — | ||||
Q72H | 1 | 72 | h | — | ||||
MANE | 1 | 1 | d | ["08:00"] | AM | |||
NOCTE | 1 | 1 | d | NIGHT | — | |||
MIDI | 1 | 1 | d | ["12:00"] | — | |||
AM_PM | 2 | 1 | d | ["08:00","20:00"] | — | |||
AC | AC | — | ||||||
PC | PC | — | ||||||
CC | C | — | ||||||
AC_HS | 4 | 1 | d | AC, HS | — | |||
PRN | true | — | ||||||
PRN_Q4H | 1 | 4 | h | true | — | |||
PRN_Q6H | 1 | 6 | h | true | — | |||
SOS | true | — | ||||||
STAT | 1 | — | ||||||
ONCE | 1 | — | ||||||
QOD | 1 | 2 | d | QOD | ||||
WEEKLY | 1 | 1 | wk | — | ||||
BIWEEKLY | 1 | 2 | wk | — | ||||
MONTHLY | 1 | 1 | mo | — |
FHIR EventTiming Codes
How Timing.repeat.when values map to open-pharma-dosing codes.
| FHIR when | Mapping |
|---|---|
MORN | MANE |
MORN.early | Early morning (06:00) |
MORN.late | Late morning (10:00) |
NOON | MIDI |
AFT | Afternoon |
AFT.early | Early afternoon (14:00) |
AFT.late | Late afternoon (16:00) |
EVE | Evening |
EVE.early | Early evening (18:00) |
EVE.late | Late evening (20:00) |
NIGHT | NOCTE |
PHS | After sleep |
HS | At bedtime (equivalent to NOCTE) |
AC | Before meal (AC modifier) |
ACM | Before breakfast |
ACD | Before lunch |
ACV | Before dinner |
PC | After meal (PC modifier) |
PCM | After breakfast |
PCD | After lunch |
PCV | After dinner |
C | With meal (CC modifier) |
CM | With breakfast |
CD | With lunch |
CV | With dinner |
Worked Example: BD → FHIR Timing
Converting “BD” (twice daily) to a FHIR R4 Timing resource:
Go
timingJSON, _ := dosing.ToFhirTiming("BD")
Output JSON
{
"repeat": {
"frequency": 2,
"period": 1,
"periodUnit": "d",
"timeOfDay": ["08:00", "20:00"]
},
"code": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-GTSAbbreviation",
"code": "BID"
}
]
}
}
The reverse conversion recovers the original code:
Go
fc, _ := dosing.FromFhirTiming(timingJSON)
fmt.Println(fc.Code) // "BD"
FromFhirTiming uses three strategies in order:
- FHIR code match — if
Timing.code.codingcontains a recognised GTS code (e.g.BID), return the corresponding frequency immediately. - When-based match — if
repeat.whenis set (e.g.NIGHT,AC), map to the appropriate code. - Structure match — match by
frequency/period/periodUnit/count/asNeededcombination.
Worked Example: DosingInstruction → FHIR Dosage
Converting a complete dosing instruction to a FHIR R4 Dosage resource:
Go
fc, _ := dosing.Parse("BD")
instruction := &dosing.DosingInstruction{
Frequency: fc,
Dose: &dosing.Dose{Value: 500, Unit: "mg"},
Route: "PO",
}
dosageJSON, _ := dosing.ToFhirDosage(instruction)
Output JSON
{
"timing": {
"repeat": {
"frequency": 2,
"period": 1,
"periodUnit": "d",
"timeOfDay": ["08:00", "20:00"]
},
"code": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/v3-GTSAbbreviation",
"code": "BID"
}
]
}
},
"route": {
"text": "PO"
},
"doseAndRate": [
{
"doseQuantity": {
"value": 500,
"unit": "mg"
}
}
]
}
The reverse conversion recovers the instruction:
Go
result, _ := dosing.FromFhirDosage(dosageJSON)
fmt.Println(result.Frequency.Code) // "BD"
fmt.Println(result.Dose.Value) // 500
fmt.Println(result.Dose.Unit) // "mg"
fmt.Println(result.Route) // "PO"
Roundtrip Invariant
FHIR roundtrip fidelity:
FromFhirTiming(ToFhirTiming(code)) == code holds for all 33 supported frequency codes.
Three codes have documented structural ambiguities where the roundtrip resolves to an equivalent code. These are structurally indistinguishable in FHIR and resolve to the more common code:
| Input | Roundtrip Result | Reason |
|---|---|---|
ONCE |
STAT |
Both produce count: 1; STAT is the default for single-count timings |
AM_PM |
BD |
Both produce frequency: 2, period: 1/d, timeOfDay: ["08:00", "20:00"] |
SOS |
PRN |
Both produce asNeeded: true without period constraints |
Why these ambiguities exist:
FHIR’s
Timing.repeat structure does not have enough fields to distinguish these pairs.
For example, both ONCE and STAT produce {"repeat": {"count": 1}} — there is no FHIR field
that captures “immediate” vs “scheduled single dose.” The library resolves to the more
commonly used code (STAT over ONCE, BD over AM_PM, PRN over SOS).
HL7 v3-GTSAbbreviation Coverage
The library supports all medication-relevant codes from the HL7 v3-GTSAbbreviation CodeSystem:
| GTS Code | GTS Display | Library Code | Status |
|---|---|---|---|
QD | Every day | OD (alias: QD) | Supported |
BID | Twice a day | BD (alias: BID) | Supported |
TID | Three times a day | TDS (alias: TID) | Supported |
QID | Four times a day | QDS (alias: QID) | Supported |
Q1H | Every hour | Q1H | Supported |
Q2H | Every 2 hours | Q2H | Supported |
Q3H | Every 3 hours | — | Not yet included (rare) |
Q4H | Every 4 hours | Q4H | Supported |
Q6H | Every 6 hours | Q6H | Supported |
Q8H | Every 8 hours | Q8H | Supported |
QOD | Every other day | QOD | Supported |
AM | Every morning | MANE (FHIR code: AM) | Supported |
PM | Every afternoon | — | Mapped via EventTiming |
BED | At bedtime | NOCTE (alias: HS) | Supported |
WK | Weekly | WEEKLY | Supported |
MO | Monthly | MONTHLY | Supported |
Note: The GTS CodeSystem also includes non-dosing codes (JB, JE, JH and holiday sub-codes) which are not relevant to medication dosing and are excluded.