package health import ( "encoding/json" "math" "sort" "time" "github.com/eternal-flame-AD/yoake/internal/util" "github.com/google/uuid" ) type ComplianceLog struct { UUID string `json:"uuid,omitempty"` MedKeyname string `json:"med_keyname"` Expected ComplianceDoseInfo `json:"expected"` Actual ComplianceDoseInfo `json:"actual"` // 0 = closest to expected time +1 = closest to next expected dose // get a cumsum of this to get a compliance stat DoseOffset f64OrNan `json:"dose_offset"` EffectiveLastDose *ComplianceLog `json:"effective_last_dose,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type ComplianceDoseInfo struct { Time time.Time `json:"time"` Dose int `json:"dose"` } type f64OrNan float64 func (n *f64OrNan) UnmarshalJSON(b []byte) error { if string(b) == "null" { *n = f64OrNan(math.NaN()) return nil } var f float64 if err := json.Unmarshal(b, &f); err != nil { return err } *n = f64OrNan(f) return nil } func (n f64OrNan) MarshalJSON() ([]byte, error) { if math.IsNaN(float64(n)) { return []byte("null"), nil } return json.Marshal(float64(n)) } func doseOffset(dir Direction, this ComplianceLog, last ComplianceLog) float64 { if last.UUID == "" { return math.NaN() } offset := float64(this.Actual.Time.Sub(last.Actual.Time))/ float64(time.Duration(dir.PeriodHours)*time.Hour) - 1 // for prn ignore positive offsets if util.Contain(dir.Flags, DirectionFlagPRN) { if offset > 0 { return 0 } } // ad lib ignore negative offsets if util.Contain(dir.Flags, DirectionFlagAdLib) { if offset < 0 { return 0 } } return offset } type ComplianceLogList []ComplianceLog func (c ComplianceLogList) findEffectiveLastDose(dir Direction, this ComplianceLog) ComplianceLog { // for ad lib directions, this finds the last dose // for default scheduling, this find the earliest dose that does not cumulatively exceed a whole dose var lastDose ComplianceLog var cumDosage int for ptr := 0; ptr < len(c); ptr++ { if c[ptr].MedKeyname == dir.KeyName() && c[ptr].Actual.Time.Before(this.Actual.Time) { if dir.OptSchedule == OptScheduleWholeDose { return c[ptr] } cumDosage += c[ptr].Actual.Dose if cumDosage > dir.Dosage { return lastDose } else if cumDosage == dir.Dosage { return c[ptr] } lastDose = c[ptr] } } return lastDose } func (c ComplianceLogList) ProjectNextDose(dir Direction) (nextDose ComplianceLog) { tmpUUID := uuid.New().String() nextDose = ComplianceLog{ UUID: tmpUUID, MedKeyname: dir.KeyName(), Expected: ComplianceDoseInfo{ Time: time.Now(), Dose: dir.Dosage, }, Actual: ComplianceDoseInfo{ Time: time.Now(), Dose: dir.Dosage, }, DoseOffset: 0, CreatedAt: time.Now(), UpdatedAt: time.Now(), } lastDose := c.findEffectiveLastDose(dir, nextDose) if lastDose.UUID == "" /* not found */ { return } nextDose.EffectiveLastDose = &lastDose nextDose.Expected.Time = lastDose.Actual.Time.Add(time.Duration(dir.PeriodHours) * time.Hour) nextDose.DoseOffset = f64OrNan(doseOffset(dir, nextDose, lastDose)) return } func (c ComplianceLogList) UpdateDoseOffset(dir Direction) { sort.Sort(c) for i := range c { if c[i].MedKeyname == dir.KeyName() { lastDose, thisDose := c.findEffectiveLastDose(dir, c[i]), c[i] if lastDose.UUID == "" /* not found */ { return } c[i].DoseOffset = f64OrNan(doseOffset(dir, thisDose, lastDose)) } } } func (c ComplianceLogList) Len() int { return len(c) } func (c ComplianceLogList) Less(i, j int) bool { return c[i].Actual.Time.After(c[j].Actual.Time) } func (c ComplianceLogList) Swap(i, j int) { c[i], c[j] = c[j], c[i] }