diff --git a/internal/client/eightsleep.go b/internal/client/eightsleep.go index 55fad67..4fe1c54 100644 --- a/internal/client/eightsleep.go +++ b/internal/client/eightsleep.go @@ -376,20 +376,35 @@ type SleepDay struct { Date string `json:"day"` Score float64 `json:"score"` Tnt int `json:"tnt"` - Respiratory float64 `json:"respiratoryRate"` - HeartRate float64 `json:"heartRate"` - LatencyAsleep float64 `json:"latencyAsleepSeconds"` - LatencyOut float64 `json:"latencyOutSeconds"` - Duration float64 `json:"sleepDurationSeconds"` - Stages []Stage `json:"stages"` - SleepQuality struct { - HRV struct { - Score float64 `json:"score"` - } `json:"hrv"` - Resp struct { - Score float64 `json:"score"` - } `json:"respiratoryRate"` - } `json:"sleepQualityScore"` + Duration float64 `json:"sleepDuration"` + DeepDuration float64 `json:"deepDuration"` + RemDuration float64 `json:"remDuration"` + LightDuration float64 `json:"lightDuration"` + DeepPercent float64 `json:"deepPercent"` + RemPercent float64 `json:"remPercent"` + PresenceStart string `json:"presenceStart"` + PresenceEnd string `json:"presenceEnd"` + SleepStart string `json:"sleepStart"` + SleepEnd string `json:"sleepEnd"` + SleepQuality SleepQualityScore `json:"sleepQualityScore"` +} + +// SleepQualityScore contains detailed sleep quality metrics from the API. +type SleepQualityScore struct { + Total float64 `json:"total"` + HRV SleepMetric `json:"hrv"` + HeartRate SleepMetric `json:"heartRate"` + Respiratory SleepMetric `json:"respiratoryRate"` + Deep SleepMetric `json:"deep"` + Rem SleepMetric `json:"rem"` + Waso SleepMetric `json:"waso"` +} + +// SleepMetric represents a single sleep metric with current value and statistics. +type SleepMetric struct { + Current float64 `json:"current"` + Average float64 `json:"average"` + Score float64 `json:"score"` } // Stage represents sleep stage duration. diff --git a/internal/cmd/sleep.go b/internal/cmd/sleep.go index 8a22ff1..bc3d182 100644 --- a/internal/cmd/sleep.go +++ b/internal/cmd/sleep.go @@ -37,21 +37,30 @@ var sleepDayCmd = &cobra.Command{ if err != nil { return err } + // Convert durations from seconds to hours for readability + durationHrs := day.Duration / 3600 + deepHrs := day.DeepDuration / 3600 + remHrs := day.RemDuration / 3600 + lightHrs := day.LightDuration / 3600 + rows := []map[string]any{ { - "date": day.Date, - "score": day.Score, - "tnt": day.Tnt, - "resp_rate": day.Respiratory, - "heart_rate": day.HeartRate, - "duration": day.Duration, - "latency_asleep": day.LatencyAsleep, - "latency_out": day.LatencyOut, - "hrv_score": day.SleepQuality.HRV.Score, + "date": day.Date, + "score": day.Score, + "duration_hrs": float64(int(durationHrs*10)) / 10, + "deep_hrs": float64(int(deepHrs*10)) / 10, + "rem_hrs": float64(int(remHrs*10)) / 10, + "light_hrs": float64(int(lightHrs*10)) / 10, + "sleep_start": day.SleepStart, + "sleep_end": day.SleepEnd, + "tnt": day.Tnt, + "rhr": day.SleepQuality.HeartRate.Current, + "hrv": day.SleepQuality.HRV.Current, + "resp_rate": day.SleepQuality.Respiratory.Current, }, } rows = output.FilterFields(rows, viper.GetStringSlice("fields")) - return output.Print(output.Format(viper.GetString("output")), []string{"date", "score", "duration", "latency_asleep", "latency_out", "tnt", "resp_rate", "heart_rate", "hrv_score"}, rows) + return output.Print(output.Format(viper.GetString("output")), []string{"date", "score", "duration_hrs", "deep_hrs", "rem_hrs", "light_hrs", "rhr", "hrv", "resp_rate", "tnt", "sleep_start", "sleep_end"}, rows) }, } diff --git a/internal/cmd/sleep_range.go b/internal/cmd/sleep_range.go index d31f1ec..bb7e93a 100644 --- a/internal/cmd/sleep_range.go +++ b/internal/cmd/sleep_range.go @@ -19,8 +19,8 @@ var sleepRangeCmd = &cobra.Command{ if err := requireAuthFields(); err != nil { return err } - from := viper.GetString("from") - to := viper.GetString("to") + from, _ := cmd.Flags().GetString("from") + to, _ := cmd.Flags().GetString("to") if from == "" || to == "" { return fmt.Errorf("--from and --to are required") } @@ -47,18 +47,24 @@ var sleepRangeCmd = &cobra.Command{ if err != nil { return err } + // Convert durations from seconds to hours for readability + durationHrs := day.Duration / 3600 + deepHrs := day.DeepDuration / 3600 + remHrs := day.RemDuration / 3600 + rows = append(rows, map[string]any{ - "date": day.Date, - "score": day.Score, - "duration": day.Duration, - "tnt": day.Tnt, - "resp_rate": day.Respiratory, - "heart_rate": day.HeartRate, - "hrv_score": day.SleepQuality.HRV.Score, + "date": day.Date, + "score": day.Score, + "duration_hrs": float64(int(durationHrs*10)) / 10, + "deep_hrs": float64(int(deepHrs*10)) / 10, + "rem_hrs": float64(int(remHrs*10)) / 10, + "tnt": day.Tnt, + "rhr": day.SleepQuality.HeartRate.Current, + "hrv": day.SleepQuality.HRV.Current, }) } rows = output.FilterFields(rows, viper.GetStringSlice("fields")) - headers := []string{"date", "score", "duration", "tnt", "resp_rate", "heart_rate", "hrv_score"} + headers := []string{"date", "score", "duration_hrs", "deep_hrs", "rem_hrs", "rhr", "hrv", "tnt"} if len(viper.GetStringSlice("fields")) > 0 { headers = viper.GetStringSlice("fields") }