Initial commit
This commit is contained in:
commit
da5c4ce2a9
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ketotrack
|
||||||
|
.idea/
|
24
README.md
Normal file
24
README.md
Normal file
@ -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
|
||||||
|
```
|
179
main.go
Normal file
179
main.go
Normal file
@ -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
|
||||||
|
}
|
20
main_test.go
Normal file
20
main_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user