API Reference Go package dosing — github.com/Open-Nucleus/open-pharm-dosing

Import path:
import dosing "github.com/Open-Nucleus/open-pharm-dosing"

Types

FrequencyCode

The primary data type. Represents a single dosing frequency with all its metadata.

type FrequencyCode struct {
    Code            string            // Canonical code: "BD", "TDS", etc.
    Aliases         []string          // All accepted input forms
    LocalePreferred map[string]string // Locale → preferred display code
    Display         map[string]string // Locale → human-readable text
    Category        Category          // regular, interval, time_of_day, ...
    Frequency       int               // Times per period (2 for BD)
    Period          int               // Period count (1 for "per day")
    PeriodUnit      PeriodUnit        // d, h, wk, mo
    IntervalHours   float64           // Hours between doses (12 for BD)
    DefaultTimes    []string          // HH:MM default admin times
    WakingOnly      bool              // Only during waking hours
    AsNeeded        bool              // PRN flag
    MaxPerDay       int               // 0 = no limit
    MinInterval     *float64          // Min hours between PRN doses
    MealRelation    MealRelation      // none, before, after, with
    Latin           string            // Latin origin
    FhirCode        string            // FHIR TimingAbbreviation code
    FhirSystem      string            // FHIR coding system URI
    SortOrder       int               // For UI display ordering
}

DosingInstruction

A complete prescription line combining frequency with dose, route, duration, and modifiers.

type DosingInstruction struct {
    Frequency    *FrequencyCode // Required: the dosing frequency
    MealModifier *FrequencyCode // Optional: AC, PC, CC modifier
    Dose         *Dose          // Optional: amount + unit
    Route        string         // PO, IV, IM, SC, etc.
    Duration     *Duration      // Optional: 7 days, 2 weeks, etc.
    MaxDose      *MaxDose       // Optional: max per day / per dose
    Instructions []string       // Additional: "with plenty of water"
}

Dose

type Dose struct {
    Value     float64  // e.g. 500
    Unit      string   // mg, ml, mcg, units, tablets, etc.
    LowValue  *float64 // For range doses: "1-2 tablets"
    HighValue *float64
}

Duration

type Duration struct {
    Value int
    Unit  PeriodUnit // d, wk, mo
}

MaxDose

type MaxDose struct {
    MaxPerDose     *float64
    MaxPerDay      *float64
    MaxPerDoseUnit string
    MaxPerDayUnit  string
}

ValidationWarning

type ValidationWarning struct {
    Field   string // Which field triggered the finding
    Message string // Human-readable description
    Level   string // "error", "warning", "info"
}

Constants

// Categories
const (
    CategoryRegular      Category = "regular"
    CategoryInterval     Category = "interval"
    CategoryTimeOfDay    Category = "time_of_day"
    CategoryMealRelative Category = "meal_relative"
    CategoryPRN          Category = "prn"
    CategoryOneOff       Category = "one_off"
    CategoryExtended     Category = "extended"
)

// Period units
const (
    PeriodHour  PeriodUnit = "h"
    PeriodDay   PeriodUnit = "d"
    PeriodWeek  PeriodUnit = "wk"
    PeriodMonth PeriodUnit = "mo"
)

// Meal relations
const (
    MealNone   MealRelation = "none"
    MealBefore MealRelation = "before"
    MealAfter  MealRelation = "after"
    MealWith   MealRelation = "with"
)

// Locales
const (
    LocaleEnGB = "en-GB"
    LocaleEnUS = "en-US"
)

Registry Functions

Get

func Get(code string) (*FrequencyCode, error)

Returns the FrequencyCode for the given canonical code. Returns ErrCodeNotFound if the code is not in the registry, ErrEmptyInput if empty.

Example
fc, err := dosing.Get("TDS")
fmt.Println(fc.Frequency)     // 3
fmt.Println(fc.IntervalHours) // 8
fmt.Println(fc.DefaultTimes)  // ["08:00", "14:00", "20:00"]
fmt.Println(fc.Latin)         // "ter die sumendus"

List

func List(opts ...ListOption) []*FrequencyCode

Returns all registered frequencies sorted by SortOrder. Accepts optional filters.

Example
// All codes
all := dosing.List()

// Filter by category
prn := dosing.List(dosing.WithCategory(dosing.CategoryPRN))

Search

func Search(query string) []*FrequencyCode

Returns frequency codes matching the query as a case-insensitive substring of the code, any alias, or any display text.

Example
results := dosing.Search("daily")   // matches OD, BD, TDS, QDS, 5X_DAILY
results = dosing.Search("morning")  // matches MANE, AM_PM

WithCategory

func WithCategory(c Category) ListOption

Filters List() results to a specific category.

Parser

Parse

func Parse(input string) (*FrequencyCode, error)

Converts a free-text dosing frequency string into a FrequencyCode. Handles canonical codes, aliases, mixed case, punctuation variants, and “every N hours” patterns.

Example
fc, _ := dosing.Parse("BD")           // → FrequencyCode{Code: "BD"}
fc, _ = dosing.Parse("b.i.d.")        // → FrequencyCode{Code: "BD"}
fc, _ = dosing.Parse("twice daily")   // → FrequencyCode{Code: "BD"}
fc, _ = dosing.Parse("every 4 hours") // → FrequencyCode{Code: "Q4H"}
fc, _ = dosing.Parse("TID")           // → FrequencyCode{Code: "TDS"}
fc, _ = dosing.Parse("p.r.n.")        // → FrequencyCode{Code: "PRN"}

ParseInstruction

func ParseInstruction(input string) (*DosingInstruction, error)

Parses a full dosing instruction string. Stub — returns ErrNotImplemented. Full implementation deferred to Phase 4.

FHIR Conversion

ToFhirTiming

func ToFhirTiming(code string) ([]byte, error)

Converts a canonical frequency code (or alias) to a FHIR R4 Timing JSON object.

Example
timingJSON, _ := dosing.ToFhirTiming("BD")
// Output:
// {
//   "repeat": {
//     "frequency": 2,
//     "period": 1,
//     "periodUnit": "d",
//     "timeOfDay": ["08:00", "20:00"]
//   },
//   "code": {
//     "coding": [{"system": "...v3-GTSAbbreviation", "code": "BID"}]
//   }
// }

FromFhirTiming

func FromFhirTiming(timing []byte) (*FrequencyCode, error)

Converts FHIR R4 Timing JSON to a FrequencyCode. Uses three strategies in order: FHIR code match, when-based match, structure match.

Example
fc, _ := dosing.FromFhirTiming(timingJSON)
fmt.Println(fc.Code) // "BD"

ToFhirDosage

func ToFhirDosage(instruction *DosingInstruction) ([]byte, error)

Converts a DosingInstruction to a FHIR R4 Dosage JSON object. Includes timing, route, dose quantity, and max dose per period.

Example
fc, _ := dosing.Parse("BD")
instruction := &dosing.DosingInstruction{
    Frequency: fc,
    Dose:      &dosing.Dose{Value: 500, Unit: "mg"},
    Route:     "PO",
}
dosageJSON, _ := dosing.ToFhirDosage(instruction)

FromFhirDosage

func FromFhirDosage(dosage []byte) (*DosingInstruction, error)

Converts FHIR R4 Dosage JSON to a DosingInstruction. Extracts frequency, dose, route, and max dose.

Example
result, _ := dosing.FromFhirDosage(dosageJSON)
fmt.Println(result.Frequency.Code) // "BD"
fmt.Println(result.Dose.Value)     // 500
fmt.Println(result.Route)          // "PO"

Text Generation

ToText

func ToText(code string, locale string) (string, error)

Returns the human-readable display text for a frequency code. Falls back to en-GB for unsupported locales.

Example
text, _ := dosing.ToText("BD", dosing.LocaleEnGB)  // "Twice daily"
text, _ = dosing.ToText("PRN", dosing.LocaleEnGB)  // "As needed"

ToLabel

func ToLabel(code string, locale string) (string, error)

Returns the locale-preferred short code label. For example, “BD” in en-GB vs “BID” in en-US.

Example
label, _ := dosing.ToLabel("BD", dosing.LocaleEnGB)  // "BD"
label, _ = dosing.ToLabel("BD", dosing.LocaleEnUS)  // "BID"
label, _ = dosing.ToLabel("TDS", dosing.LocaleEnUS) // "TID"

InstructionToText

func InstructionToText(instruction *DosingInstruction, locale string) (string, error)

Converts a DosingInstruction to a human-readable string. Output pattern: {dose} {frequency} {route} for {duration}, {meal modifier} (max {max}).

Example
fc, _ := dosing.Parse("BD")
ac, _ := dosing.Parse("AC")
text, _ := dosing.InstructionToText(&dosing.DosingInstruction{
    Frequency:    fc,
    MealModifier: ac,
    Dose:         &dosing.Dose{Value: 500, Unit: "mg"},
    Route:        "PO",
    Duration:     &dosing.Duration{Value: 7, Unit: dosing.PeriodDay},
}, dosing.LocaleEnGB)
// "500mg twice daily by mouth for 7 days, before meals"

Schedule Generation

Schedule

func Schedule(code string, start time.Time, days int) ([]time.Time, error)

Generates concrete administration times for the given frequency code. Day 1 skips times that have already passed relative to start. PRN and pure meal modifier codes return errors.

Example
start := time.Date(2025, 1, 15, 9, 30, 0, 0, time.UTC)

// Fixed-time schedule (skips already-passed times on day 1)
times, _ := dosing.Schedule("BD", start, 3)
// Day 1: 20:00 (08:00 skipped — before 09:30)
// Day 2: 08:00, 20:00
// Day 3: 08:00, 20:00

// Rolling interval
times, _ = dosing.Schedule("Q1H", start, 1)
// 24 times: 09:30, 10:30, 11:30, ...

// One-off
times, _ = dosing.Schedule("STAT", start, 1)
// [2025-01-15 09:30]

// PRN returns error
_, err := dosing.Schedule("PRN", start, 1)
// ErrPRNNoSchedule

ScheduleWithTimes

func ScheduleWithTimes(code string, start time.Time, days int, customTimes []string) ([]time.Time, error)

Like Schedule but uses custom HH:MM times instead of the code’s DefaultTimes. Rolling-interval codes ignore custom times.

Example
times, _ := dosing.ScheduleWithTimes("BD", start, 1, []string{"07:00", "19:00"})

Validation

Validate

func Validate(code string) error

Checks whether the given code resolves to a known frequency code.

Example
err := dosing.Validate("BD")    // nil
err = dosing.Validate("XYZZY") // error

ValidateInstruction

func ValidateInstruction(instruction *DosingInstruction) []ValidationWarning

Performs clinical sense-checking on a DosingInstruction, returning multi-level findings. Checks include:

Example
fc, _ := dosing.Parse("PRN")
warnings := dosing.ValidateInstruction(&dosing.DosingInstruction{
    Frequency: fc,
    Dose:      &dosing.Dose{Value: 500, Unit: "mg"},
})
// warnings includes:
//   {Field: "max_dose", Level: "warning", Message: "PRN frequency without maximum dose limit"}
//   {Field: "route",    Level: "info",    Message: "route not specified"}

Errors

VariableValueReturned By
ErrCodeNotFoundfrequency code not foundGet, Parse
ErrEmptyInputempty inputGet, Parse
ErrFhirConversionFHIR conversion errorToFhirTiming, ToFhirDosage
ErrFhirNoMatchno matching frequency code for FHIR timingFromFhirTiming
ErrPRNNoSchedulePRN codes have no fixed scheduleSchedule, ScheduleWithTimes
ErrMealRelNoSchedulemeal-relative codes require a base frequencySchedule, ScheduleWithTimes
ErrInvalidDaysdays must be positiveSchedule, ScheduleWithTimes
ErrInvalidTimeFormatcustom time must be HH:MM formatScheduleWithTimes
ErrNotImplementednot implementedParseInstruction