From da5c4ce2a97de3f14ccbcfe3f8cf44fe81258930 Mon Sep 17 00:00:00 2001 From: agatha Date: Sat, 11 May 2024 22:06:45 -0400 Subject: [PATCH] Initial commit --- .gitignore | 2 + README.md | 24 +++++++ go.mod | 3 + main.go | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++ main_test.go | 20 ++++++ 5 files changed, 228 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 go.mod create mode 100644 main.go create mode 100644 main_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab58259 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +ketotrack +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea5341e --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# ketotrack +Simple GKI calculator written as an exercise in learning Go. + +Currently, this only calculates your GKI and stores the results into a JSON file. At some point I'll get around to +implementing note-taking and time-series display. + +## Usage +After measuring your blood glucose and blood ketone levels, run `ketotrack`. GKI will be displayed along with +the level of ketosis you are in according to [Keto-Mojo](https://keto-mojo.com/glucose-ketone-index-gki/): +``` +Enter glucose reading (mg/dL): 77 +Enter ketone reading (mmol/L): 1.1 + +Your GKI is: 3.89 +You're in a moderate level of ketosis. +``` + +Reading data will be saved to `$HOME/.ketotrack/readings.json`. + +## Building and Installing +```shell +go build +mv ketotrack $HOME/.local/bin +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cf5a0f8 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module ketotrack + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..b0859c0 --- /dev/null +++ b/main.go @@ -0,0 +1,179 @@ +package main + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +var readingsFilename string + +func init() { + // Get user's homeDir directory + homeDir, err := os.UserHomeDir() + if err != nil { + fmt.Println("Error fetching user's home directory:", err) + os.Exit(1) + } + + // Define the path to the application data directory + ketotrackDir := filepath.Join(homeDir, ".ketotrack") + + // Create the directory if it does not exist + err = os.MkdirAll(ketotrackDir, 0770) + if err != nil { + fmt.Println("Error creating .ketotrack directory:", err) + os.Exit(1) + } + + // Set data file path for readings + readingsFilename = filepath.Join(ketotrackDir, "readings.json") +} + +// Reading holds the glucose and ketone level measurements along with the time the measurements were taken. +type Reading struct { + Time time.Time `json:"time"` + Glucose float64 `json:"glucose"` + Ketone float64 `json:"ketone"` +} + +// NewReading creates and returns a new Reading instance with the provided glucose and ketone levels, and records +// the current time. The glucose value should be provided in mg/dL, while the ketone level should be in mmol/L. +func NewReading(glucose, ketone float64) Reading { + return Reading{ + Time: time.Now(), + Glucose: glucose, + Ketone: ketone, + } +} + +// GKI calculates and returns the Glucose Ketone Index of the reading. +// The Glucose Ketone Index helps determine the efficiency of glucose and ketone levels within the body. It is +// calculated as (glucose level in mg/dL / 18) divided by the ketone level. +func (r Reading) GKI() (float64, error) { + if r.Ketone == 0 { + return 0, errors.New("ketone level must not be zero to calculate GKI") + } + return (r.Glucose / 18) / r.Ketone, nil +} + +func main() { + // Take a new reading from the user + reading, err := getReading() + if err != nil { + fmt.Println(err.Error()) + return + } + + // Calculate GKI + gki, err := reading.GKI() + if err != nil { + fmt.Println(err.Error()) + fmt.Println("You are not in ketosis.") + return + } + fmt.Printf("\nYour GKI is: %0.2f\n", gki) + + // Display level of ketosis + switch { + case gki <= 1: + fmt.Println("You're in the highest level of ketosis.") + case gki < 3: + fmt.Println("You're in a high therapeutic level of ketosis.") + case gki < 6: + fmt.Println("You're in a moderate level of ketosis.") + case gki <= 9: + fmt.Println("You're in a low level of ketosis.") + default: + fmt.Println("You are not in ketosis.") + } + + // Save latest reading to readings file + readings, _ := loadReadingsFromFile(readingsFilename) + readings = append(readings, reading) + err = saveReadingsToFile(readingsFilename, readings) + if err != nil { + fmt.Printf("error saving data: %s\n", err.Error()) + } +} + +func saveReadingsToFile(filename string, data []Reading) error { + jsonData, err := json.Marshal(data) + if err != nil { + return err + } + return os.WriteFile(filename, jsonData, 0660) +} + +func loadReadingsFromFile(filename string) ([]Reading, error) { + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + + var readings []Reading + err = json.Unmarshal(data, &readings) + if err != nil { + return nil, err + } + + return readings, nil +} + +func getReading() (Reading, error) { + // Get glucose reading + glucose, err := getUserFloat("Enter glucose reading (mg/dL)") + if err != nil { + return Reading{}, fmt.Errorf("error getting glucose reading: %w", err) + } + + // Validate glucose reading + if glucose <= 0 { + return Reading{}, errors.New("glucose reading cannot be less than or equal to 0") + } + + // Get ketone reading + ketone, err := getUserFloat("Enter ketone reading (mmol/L)") + if err != nil { + return Reading{}, fmt.Errorf("error getting ketone reading: %w", err) + } + + // Validate ketone reading + if ketone < 0 { + fmt.Println("") + return Reading{}, errors.New("ketone reading cannot be less than 0") + } + + return NewReading(glucose, ketone), nil +} + +func getUserInput(prompt string) (string, error) { + fmt.Printf("%s: ", prompt) + reader := bufio.NewReader(os.Stdin) + input, err := reader.ReadString('\n') + if err != nil { + return "", err + } + return strings.TrimSpace(input), nil +} + +func getUserFloat(prompt string) (float64, error) { + // Get input as a string + s, err := getUserInput(prompt) + if err != nil { + return 0, err + } + + // Convert string to float + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, err + } + return f, nil +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..b2fc703 --- /dev/null +++ b/main_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "math" + "testing" +) + +func TestGKI(t *testing.T) { + r := Reading{ + Glucose: 77, + Ketone: 1.1, + } + expected := (77.0 / 18) / 1.1 + epsilon := 0.0001 // Tolerance level + gki, _ := r.GKI() + + if math.Abs(gki-expected) > epsilon { + t.Errorf("expected %v, got %v", expected, gki) + } +}