package logbook import ( "encoding/json" "errors" "fmt" "time" bolt "go.etcd.io/bbolt" "git.wow.st/gmp/logbook/parser" ) // --- Measurement Data Structure --- type Measurement struct { ID string // Unique identifier Date time.Time // When the measurement was taken Variable string // e.g. "Bodyweight", "Waist", "Biceps" Value float64 // The actual measurement value Note string // Optional notes } // --- BoltDB Bucket Name --- var bucketMeasurements = []byte("measurements") // --- MeasurementRepository --- type MeasurementRepository struct { db *bolt.DB } func NewMeasurementRepository(dbpath string) (*MeasurementRepository, error) { db, err := bolt.Open(dbpath, 0600, nil) if err != nil { return nil, err } err = db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists(bucketMeasurements) return err }) if err != nil { return nil, err } return &MeasurementRepository{db: db}, nil } func (mr *MeasurementRepository) Close() { mr.db.Close() } func (r *MeasurementRepository) Save(m *Measurement) error { if m.ID == "" { m.ID = generateDateID(m.Date, "measurement-" + "m.Variable") } return r.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucketMeasurements) data, err := json.Marshal(m) if err != nil { return err } return b.Put([]byte(m.ID), data) }) } func (r *MeasurementRepository) GetByID(id string) (*Measurement, error) { var m Measurement err := r.db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucketMeasurements) v := b.Get([]byte(id)) if v == nil { return errors.New("measurement not found") } return json.Unmarshal(v, &m) }) if err != nil { return nil, err } return &m, nil } func (r *MeasurementRepository) FindAll() ([]*Measurement, error) { var measurements []*Measurement err := r.db.View(func(tx *bolt.Tx) error { b := tx.Bucket(bucketMeasurements) return b.ForEach(func(k, v []byte) error { var m Measurement if err := json.Unmarshal(v, &m); err != nil { return err } measurements = append(measurements, &m) return nil }) }) return measurements, err } func (r *MeasurementRepository) Delete(id string) error { return r.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(bucketMeasurements) return b.Delete([]byte(id)) }) } // --- AddMeasurementsFromIR (with bucket creation) --- func (r *MeasurementRepository) AddMeasurementsFromIR(irMeasurements []parser.Measurement) error { db := r.db if db == nil { return fmt.Errorf("Measurement Repository is not initialized.") } for _, m := range(irMeasurements) { measurementID := generateDateID(m.Date, m.Variable) mm := &Measurement{ ID: measurementID, Date: m.Date, Variable: m.Variable, Value: m.Value, Note: m.Note, } err := r.Save(mm) if err != nil { return err } } return nil } func (r *MeasurementRepository) GetOrPredict(variable string, date time.Time) (float64, error) { var ret float64 id := date.UTC().Format("2006-01-02") err := r.db.View(func(tx *bolt.Tx) error { c := tx.Bucket(bucketMeasurements).Cursor() k,v := c.Seek([]byte(id)) if k != nil { var m Measurement if err := json.Unmarshal(v, &m); err != nil { return err } ret = m.Value } else { x, err := r.Predict(variable, date, 0.75) if err != nil { return err } ret = x } return nil }) if err != nil { return 0, err } return ret, nil }