230 lines
5.8 KiB
Go
230 lines
5.8 KiB
Go
package health
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/eternal-flame-AD/yoake/internal/util"
|
|
)
|
|
|
|
type Direction struct {
|
|
Name string `json:"name"`
|
|
|
|
PeriodHours int `json:"period_hours"`
|
|
|
|
Dosage int `json:"dosage"`
|
|
DosageUnit string `json:"dosage_unit"`
|
|
DosageRoute string `json:"dosage_route"`
|
|
|
|
Flags []DirectionFlag `json:"flags"`
|
|
|
|
DirectionShorthand string `json:"shorthand"`
|
|
|
|
OptSchedule OptSchedule `json:"schedule"`
|
|
|
|
Disclaimer string `json:"__disclaimer"`
|
|
}
|
|
|
|
const DirectionDisclaimer = "For personal use only. No warranty of accuracy."
|
|
|
|
type DirectionFlag string
|
|
type OptSchedule string
|
|
|
|
const (
|
|
DirectionFlagAM DirectionFlag = "qam"
|
|
DirectionFlagHS DirectionFlag = "qhs"
|
|
DirectionFlagPRN DirectionFlag = "prn"
|
|
OptScheduleDefault OptSchedule = "default"
|
|
OptScheduleWholeDose OptSchedule = "whole"
|
|
)
|
|
|
|
func ParseShorthand(shorthand string) (*Direction, error) {
|
|
res := new(Direction)
|
|
res.Disclaimer = DirectionDisclaimer
|
|
res.Flags = make([]DirectionFlag, 0)
|
|
words := strings.Split(shorthand, " ")
|
|
|
|
optionsRegex := regexp.MustCompile(`^([a-zA-Z]+)\((\w+)\)$`)
|
|
for i := len(words) - 1; i >= 0; i-- {
|
|
if match := optionsRegex.FindStringSubmatch(words[i]); match != nil {
|
|
name, value := match[1], match[2]
|
|
switch strings.ToLower(name) {
|
|
case "sched":
|
|
fallthrough
|
|
case "schedule":
|
|
if res.OptSchedule != "" {
|
|
return nil, fmt.Errorf("duplicate schedule option")
|
|
}
|
|
switch strings.ToLower(value) {
|
|
case "default":
|
|
fallthrough
|
|
case "":
|
|
res.OptSchedule = OptScheduleDefault
|
|
case "whole":
|
|
res.OptSchedule = OptScheduleWholeDose
|
|
default:
|
|
return nil, fmt.Errorf("invalid schedule option: %s", value)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unknown option %s", name)
|
|
}
|
|
|
|
words = words[:i]
|
|
}
|
|
}
|
|
if res.OptSchedule == "" {
|
|
res.OptSchedule = OptScheduleDefault
|
|
}
|
|
|
|
// combined numbers and units
|
|
for i := range words {
|
|
digits := regexp.MustCompile(`^\d+$`)
|
|
if digits.MatchString(words[i]) {
|
|
words[i] = words[i] + words[i+1]
|
|
words[i+1] = ""
|
|
}
|
|
}
|
|
words = util.AntiJoin(words, []string{""})
|
|
|
|
// find prn keyword
|
|
for i := len(words) - 1; i >= 0; i-- {
|
|
if strings.EqualFold(words[i], "prn") {
|
|
res.Flags = append(res.Flags, DirectionFlagPRN)
|
|
words = append(words[:i], words[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
freqIdx := len(words) - 1
|
|
if lastWord := strings.ToLower(words[len(words)-1]); lastWord == "bid" {
|
|
res.PeriodHours = 12
|
|
words = words[:len(words)-1]
|
|
} else if lastWord == "tid" {
|
|
res.PeriodHours = 8
|
|
words = words[:len(words)-1]
|
|
} else if lastWord == "qid" {
|
|
res.PeriodHours = 6
|
|
words = words[:len(words)-1]
|
|
} else {
|
|
for i := len(words) - 1; i >= 0; i-- {
|
|
if strings.HasPrefix(strings.ToLower(words[i]), "q") {
|
|
freqIdx = i
|
|
break
|
|
}
|
|
}
|
|
|
|
freqStr := strings.ToLower(strings.Join(words[freqIdx:], ""))[1:]
|
|
if freqStr == "am" {
|
|
res.Flags = append(res.Flags, DirectionFlagAM)
|
|
res.PeriodHours = 24
|
|
} else if freqStr == "hs" {
|
|
res.Flags = append(res.Flags, DirectionFlagHS)
|
|
res.PeriodHours = 24
|
|
} else {
|
|
if !(freqStr[0] >= '0' && freqStr[0] <= '9') {
|
|
freqStr = "1" + freqStr
|
|
}
|
|
freqRegexp := regexp.MustCompile(`^([0-9]+)([a-z]+)$`)
|
|
freqMatch := freqRegexp.FindStringSubmatch(freqStr)
|
|
if freqMatch == nil {
|
|
return nil, fmt.Errorf("invalid frequency: %s", freqStr)
|
|
}
|
|
freq, err := strconv.Atoi(freqMatch[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid frequency number : %s", freqMatch[1])
|
|
}
|
|
if freqMatch[2] == "d" {
|
|
res.PeriodHours = freq * 24
|
|
} else if freqMatch[2] == "h" {
|
|
res.PeriodHours = freq
|
|
} else {
|
|
return nil, fmt.Errorf("invalid frequency unit: %s", freqMatch[2])
|
|
}
|
|
}
|
|
}
|
|
|
|
words = words[:freqIdx]
|
|
|
|
dosageRegexp := regexp.MustCompile(`^([0-9]+)([a-z]*)$`)
|
|
var dosageMatch []string
|
|
if dosageMatch = dosageRegexp.FindStringSubmatch(words[len(words)-1]); dosageMatch == nil {
|
|
if dosageMatch = dosageRegexp.FindStringSubmatch(words[len(words)-2]); dosageMatch == nil {
|
|
return nil, fmt.Errorf("invalid dosage: %s", words[len(words)-2:])
|
|
} else {
|
|
res.DosageRoute = words[len(words)-1]
|
|
words = words[:len(words)-2]
|
|
}
|
|
} else {
|
|
words = words[:len(words)-1]
|
|
}
|
|
dosage, err := strconv.Atoi(dosageMatch[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid dosage number: %s", dosageMatch[1])
|
|
}
|
|
res.Dosage = dosage
|
|
res.DosageUnit = dosageMatch[2]
|
|
|
|
res.Name = strings.Join(words, " ")
|
|
|
|
s1, s2 := res.ShortHand()
|
|
res.DirectionShorthand = s1 + " " + s2
|
|
return res, nil
|
|
}
|
|
|
|
func (d Direction) KeyName() string {
|
|
return strings.ToLower(strings.SplitN(d.Name, " ", 2)[0])
|
|
}
|
|
func (d *Direction) ShortHand() (name string, direction string) {
|
|
builder := new(strings.Builder)
|
|
builder.WriteString(strconv.Itoa(d.Dosage))
|
|
if d.DosageUnit != "" {
|
|
builder.WriteString(" ")
|
|
builder.WriteString(d.DosageUnit)
|
|
}
|
|
if d.DosageRoute != "" {
|
|
builder.WriteString(" ")
|
|
builder.WriteString(d.DosageRoute)
|
|
}
|
|
if d.PeriodHours%24 == 0 {
|
|
qNd := d.PeriodHours / 24
|
|
qNdS := strconv.Itoa(qNd)
|
|
if qNd == 1 {
|
|
if util.Contain(d.Flags, DirectionFlagAM) {
|
|
qNdS = "AM"
|
|
} else if util.Contain(d.Flags, DirectionFlagHS) {
|
|
qNdS = "HS"
|
|
} else {
|
|
qNdS = "d"
|
|
}
|
|
} else {
|
|
qNdS += "d"
|
|
}
|
|
fmt.Fprintf(builder, " q%s", qNdS)
|
|
} else if d.PeriodHours == 12 {
|
|
builder.WriteString(" bid")
|
|
} else if d.PeriodHours == 8 {
|
|
builder.WriteString(" tid")
|
|
} else if d.PeriodHours == 6 {
|
|
builder.WriteString(" qid")
|
|
} else {
|
|
fmt.Fprintf(builder, " q%sh", strconv.Itoa(d.PeriodHours))
|
|
}
|
|
if util.Contain(d.Flags, DirectionFlagPRN) {
|
|
builder.WriteString(" PRN")
|
|
}
|
|
if d.OptSchedule != "" && d.OptSchedule != OptScheduleDefault {
|
|
if d.OptSchedule == OptScheduleWholeDose {
|
|
builder.WriteString(" sched(whole)")
|
|
}
|
|
}
|
|
|
|
return d.Name, builder.String()
|
|
}
|
|
|
|
type Dose struct {
|
|
Time time.Time
|
|
Dose int
|
|
}
|