156 lines
3.7 KiB
Go
156 lines
3.7 KiB
Go
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
|
|
}
|
|
|