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
OD11d["08:00"]QD
BD21d["08:00","20:00"]BID
TDS31d["08:00","14:00","20:00"]TID
QDS41d["06:00","12:00","18:00","22:00"]QID
5X_DAILY51d["06:00","10:00","14:00","18:00","22:00"]—
Q1H11hQ1H
Q2H12h—
Q4H14hQ4H
Q6H16hQ6H
Q8H18h—
Q12H112h—
Q24H124h—
Q36H136h—
Q48H148h—
Q72H172h—
MANE11d["08:00"]AM
NOCTE11dNIGHT—
MIDI11d["12:00"]—
AM_PM21d["08:00","20:00"]—
ACAC—
PCPC—
CCC—
AC_HS41dAC, HS—
PRNtrue—
PRN_Q4H14htrue—
PRN_Q6H16htrue—
SOStrue—
STAT1—
ONCE1—
QOD12dQOD
WEEKLY11wk—
BIWEEKLY12wk—
MONTHLY11mo—

FHIR EventTiming Codes

How Timing.repeat.when values map to open-pharma-dosing codes.

FHIR whenMapping
MORNMANE
MORN.earlyEarly morning (06:00)
MORN.lateLate morning (10:00)
NOONMIDI
AFTAfternoon
AFT.earlyEarly afternoon (14:00)
AFT.lateLate afternoon (16:00)
EVEEvening
EVE.earlyEarly evening (18:00)
EVE.lateLate evening (20:00)
NIGHTNOCTE
PHSAfter sleep
HSAt bedtime (equivalent to NOCTE)
ACBefore meal (AC modifier)
ACMBefore breakfast
ACDBefore lunch
ACVBefore dinner
PCAfter meal (PC modifier)
PCMAfter breakfast
PCDAfter lunch
PCVAfter dinner
CWith meal (CC modifier)
CMWith breakfast
CDWith lunch
CVWith 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:

  1. FHIR code match — if Timing.code.coding contains a recognised GTS code (e.g. BID), return the corresponding frequency immediately.
  2. When-based match — if repeat.when is set (e.g. NIGHT, AC), map to the appropriate code.
  3. Structure match — match by frequency/period/periodUnit/count/asNeeded combination.

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:

InputRoundtrip ResultReason
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 CodeGTS DisplayLibrary CodeStatus
QDEvery dayOD (alias: QD)Supported
BIDTwice a dayBD (alias: BID)Supported
TIDThree times a dayTDS (alias: TID)Supported
QIDFour times a dayQDS (alias: QID)Supported
Q1HEvery hourQ1HSupported
Q2HEvery 2 hoursQ2HSupported
Q3HEvery 3 hours—Not yet included (rare)
Q4HEvery 4 hoursQ4HSupported
Q6HEvery 6 hoursQ6HSupported
Q8HEvery 8 hoursQ8HSupported
QODEvery other dayQODSupported
AMEvery morningMANE (FHIR code: AM)Supported
PMEvery afternoon—Mapped via EventTiming
BEDAt bedtimeNOCTE (alias: HS)Supported
WKWeeklyWEEKLYSupported
MOMonthlyMONTHLYSupported
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.