Skip to content

Commit 0721b06

Browse files
committed
fix: correct session calcul
1 parent f29d9b3 commit 0721b06

File tree

4 files changed

+121
-82
lines changed

4 files changed

+121
-82
lines changed

core/stats/bridge.go

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,25 @@ type APIStats struct {
2424

2525
// APIPeriodStats is simplified version of PeriodStats for API responses
2626
type APIPeriodStats struct {
27-
Period string `json:"period"`
28-
StartDate time.Time `json:"start_date"`
29-
EndDate time.Time `json:"end_date"`
30-
TotalTime float64 `json:"total_time"`
31-
TotalLines int `json:"total_lines"`
32-
Languages []APILanguageRanking `json:"languages"`
33-
Projects []APIProjectRanking `json:"projects"`
34-
Editors []APIEditorRanking `json:"editors"`
35-
Files []APIFileRanking `json:"top_files"`
36-
HourlyActivity []HourlyActivity `json:"hourly_activity"`
37-
PeakHour int `json:"peak_hour"`
38-
Sessions []APISession `json:"sessions"`
39-
SessionCount int `json:"session_count"`
40-
FocusScore int `json:"focus_score"`
41-
DailyGoals DailyGoals `json:"daily_goals,omitempty"`
27+
Period string `json:"period"`
28+
StartDate time.Time `json:"start_date"`
29+
EndDate time.Time `json:"end_date"`
30+
TotalTime float64 `json:"total_time"`
31+
TotalLines int `json:"total_lines"`
32+
Languages []APILanguageStats `json:"languages"`
33+
Projects []APIProjectStats `json:"projects"`
34+
Editors []APIEditorStats `json:"editors"`
35+
Files []APIFileStats `json:"top_files"`
36+
HourlyActivity []HourlyActivity `json:"hourly_activity"`
37+
PeakHour int `json:"peak_hour"`
38+
Sessions []APISession `json:"sessions"`
39+
SessionCount int `json:"session_count"`
40+
FocusScore int `json:"focus_score"`
41+
DailyGoals DailyGoals `json:"daily_goals,omitempty"`
4242
}
4343

44-
// APILanguageRanking - simplified language stats
45-
type APILanguageRanking struct {
44+
// APILanguageStats - simplified language stats
45+
type APILanguageStats struct {
4646
Name string `json:"name"`
4747
Time float64 `json:"time"`
4848
Lines int `json:"lines"`
@@ -53,8 +53,8 @@ type APILanguageRanking struct {
5353
Trending bool `json:"trending"`
5454
}
5555

56-
// APIProjectRanking - simplified project stats
57-
type APIProjectRanking struct {
56+
// APIProjectStats - simplified project stats
57+
type APIProjectStats struct {
5858
Name string `json:"name"`
5959
Time float64 `json:"time"`
6060
Lines int `json:"lines"`
@@ -65,15 +65,15 @@ type APIProjectRanking struct {
6565
LastActive time.Time `json:"last_active"`
6666
}
6767

68-
// APIEditorRanking - simplified editor stats
69-
type APIEditorRanking struct {
68+
// APIEditorStats - simplified editor stats
69+
type APIEditorStats struct {
7070
Name string `json:"name"`
7171
Time float64 `json:"time"`
7272
PercentTotal float64 `json:"percent_total"`
7373
}
7474

75-
// APIFileRanking - simplified file stats
76-
type APIFileRanking struct {
75+
// APIFileStats - simplified file stats
76+
type APIFileStats struct {
7777
Name string `json:"name"`
7878
Time float64 `json:"time"`
7979
Lines int `json:"lines"`
@@ -83,13 +83,14 @@ type APIFileRanking struct {
8383

8484
// APISession - simplified session without full activities array
8585
type APISession struct {
86-
ID string `json:"id"`
87-
StartTime time.Time `json:"start_time"`
88-
EndTime time.Time `json:"end_time"`
89-
Duration float64 `json:"duration"`
90-
Projects []string `json:"projects"`
91-
Languages []string `json:"languages,omitempty"` // Top languages used in session
92-
IsActive bool `json:"is_active"`
86+
ID string `json:"id"`
87+
StartTime time.Time `json:"start_time"`
88+
EndTime time.Time `json:"end_time"`
89+
Duration float64 `json:"duration"`
90+
Projects []string `json:"projects"`
91+
Languages []string `json:"languages,omitempty"` // Top languages used in session
92+
IsActive bool `json:"is_active"`
93+
BreakAfter float64 `json:"break_after,omitempty"` // Duration until next session
9394
}
9495

9596
type APIComparisonResult struct {
@@ -173,10 +174,10 @@ func convertPeriodToAPILight(p PeriodStats) APIPeriodStats {
173174
}
174175

175176
// convertLanguagesToAPI simplifies language stats
176-
func convertLanguagesToAPI(langs []LanguageStats) []APILanguageRanking {
177-
result := make([]APILanguageRanking, len(langs))
177+
func convertLanguagesToAPI(langs []LanguageStats) []APILanguageStats {
178+
result := make([]APILanguageStats, len(langs))
178179
for i, l := range langs {
179-
result[i] = APILanguageRanking{
180+
result[i] = APILanguageStats{
180181
Name: l.Name,
181182
Time: l.Time,
182183
Lines: l.Lines,
@@ -190,10 +191,10 @@ func convertLanguagesToAPI(langs []LanguageStats) []APILanguageRanking {
190191
}
191192

192193
// convertProjectsToAPI simplifies project stats
193-
func convertProjectsToAPI(projs []ProjectStats) []APIProjectRanking {
194-
result := make([]APIProjectRanking, len(projs))
194+
func convertProjectsToAPI(projs []ProjectStats) []APIProjectStats {
195+
result := make([]APIProjectStats, len(projs))
195196
for i, p := range projs {
196-
result[i] = APIProjectRanking{
197+
result[i] = APIProjectStats{
197198
Name: p.Name,
198199
Time: p.Time,
199200
Lines: p.Lines,
@@ -207,10 +208,10 @@ func convertProjectsToAPI(projs []ProjectStats) []APIProjectRanking {
207208
}
208209

209210
// convertEditorsToAPI simplifies editor stats
210-
func convertEditorsToAPI(eds []EditorStats) []APIEditorRanking {
211-
result := make([]APIEditorRanking, len(eds))
211+
func convertEditorsToAPI(eds []EditorStats) []APIEditorStats {
212+
result := make([]APIEditorStats, len(eds))
212213
for i, e := range eds {
213-
result[i] = APIEditorRanking{
214+
result[i] = APIEditorStats{
214215
Name: e.Name,
215216
Time: e.Time,
216217
}
@@ -219,10 +220,10 @@ func convertEditorsToAPI(eds []EditorStats) []APIEditorRanking {
219220
}
220221

221222
// convertFilesToAPI simplifies file stats
222-
func convertFilesToAPI(files []FileStats) []APIFileRanking {
223-
result := make([]APIFileRanking, len(files))
223+
func convertFilesToAPI(files []FileStats) []APIFileStats {
224+
result := make([]APIFileStats, len(files))
224225
for i, f := range files {
225-
result[i] = APIFileRanking{
226+
result[i] = APIFileStats{
226227
Name: f.Name,
227228
Time: f.Time,
228229
Lines: f.Lines,
@@ -237,13 +238,14 @@ func convertSessionsToAPI(sessions []Session) []APISession {
237238

238239
for i, s := range sessions {
239240
result[i] = APISession{
240-
ID: s.ID,
241-
StartTime: s.StartTime,
242-
EndTime: s.EndTime,
243-
Duration: s.Duration,
244-
Projects: append([]string(nil), s.Projects...),
245-
Languages: append([]string(nil), s.Languages...),
246-
IsActive: s.IsActive,
241+
ID: s.ID,
242+
StartTime: s.StartTime,
243+
EndTime: s.EndTime,
244+
Duration: s.Duration,
245+
Projects: append([]string(nil), s.Projects...),
246+
Languages: append([]string(nil), s.Languages...),
247+
IsActive: s.IsActive,
248+
BreakAfter: s.BreakAfter,
247249
}
248250
}
249251

core/stats/session.go

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// core/stats/session.go
12
package stats
23

34
import (
@@ -37,6 +38,7 @@ func (sm *SessionManager) IsValidSession(session Session) bool {
3738
return session.Duration >= sm.minDuration.Seconds()
3839
}
3940

41+
// GroupActivitiesIntoSessions groups activities into sessions
4042
func (sm *SessionManager) GroupActivitiesIntoSessions(activities []Activity) []Session {
4143
if len(activities) == 0 {
4244
return nil
@@ -56,7 +58,6 @@ func (sm *SessionManager) GroupActivitiesIntoSessions(activities []Activity) []S
5658
start := func(a Activity) {
5759
projects = util.NewStringSet()
5860
languages = util.NewStringSet()
59-
6061
projects.Add(a.Project)
6162
languages.Add(a.Language)
6263

@@ -69,14 +70,20 @@ func (sm *SessionManager) GroupActivitiesIntoSessions(activities []Activity) []S
6970
}
7071
}
7172

72-
finalize := func(lastIdx int) {
73-
current.EndTime = current.EndTime.Add(
74-
sm.getActivityDuration(sorted, lastIdx),
75-
)
76-
current.Duration = current.EndTime.Sub(
77-
current.StartTime,
78-
).Seconds()
73+
finalize := func() {
74+
// CRITICAL: Session duration = sum of activity durations (not time span!)
75+
sessionDuration := 0.0
76+
for _, act := range current.Activities {
77+
sessionDuration += act.Duration
78+
}
79+
current.Duration = sessionDuration
80+
81+
// Set end time to last activity timestamp (not extended)
82+
if len(current.Activities) > 0 {
83+
current.EndTime = current.Activities[len(current.Activities)-1].Timestamp
84+
}
7985

86+
// Populate projects and languages
8087
current.Projects = projects.ToSortedSlice()
8188
current.Languages = languages.ToSortedSlice()
8289

@@ -85,29 +92,32 @@ func (sm *SessionManager) GroupActivitiesIntoSessions(activities []Activity) []S
8592
}
8693
}
8794

88-
for i, a := range sorted {
95+
for _, a := range sorted {
8996
if current == nil {
9097
start(a)
9198
continue
9299
}
93100

94101
if a.Timestamp.Sub(current.EndTime) > sm.timeout {
95-
finalize(i - 1)
102+
// Gap too large - finalize current session and start new one
103+
finalize()
96104
start(a)
97105
continue
98106
}
99107

108+
// Continue current session
100109
current.Activities = append(current.Activities, a)
101110
current.EndTime = a.Timestamp
102-
103111
projects.Add(a.Project)
104112
languages.Add(a.Language)
105113
}
106114

115+
// Finalize last session
107116
if current != nil {
108-
finalize(len(sorted) - 1)
117+
finalize()
109118
}
110119

120+
// Calculate breaks between sessions
111121
for i := 0; i < len(sessions)-1; i++ {
112122
sessions[i].BreakAfter = sessions[i+1].StartTime.Sub(
113123
sessions[i].EndTime,
@@ -116,16 +126,3 @@ func (sm *SessionManager) GroupActivitiesIntoSessions(activities []Activity) []S
116126

117127
return sessions
118128
}
119-
120-
func (sm *SessionManager) getActivityDuration(activities []Activity, currentIndex int) time.Duration {
121-
// If there's a next activity, use the time between them (capped at 2 minutes)
122-
if currentIndex < len(activities)-1 {
123-
gap := activities[currentIndex+1].Timestamp.Sub(activities[currentIndex].Timestamp)
124-
if gap > 2*time.Minute {
125-
return 2 * time.Minute
126-
}
127-
return gap
128-
}
129-
// For the last activity, assume 2 minutes of work
130-
return 2 * time.Minute
131-
}

core/storage.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package core
33

44
import (
55
"database/sql"
6+
"encoding/json"
67
"fmt"
78
"os"
89
"path/filepath"
@@ -102,20 +103,33 @@ func (s *SQLiteStorage) SaveActivity(activity stats.Activity) error {
102103

103104
// SaveSession persists a session to the database
104105
func (s *SQLiteStorage) SaveSession(session stats.Session) error {
106+
// Convert string arrays to JSON for storage
107+
projectsJSON, err := json.Marshal(session.Projects)
108+
if err != nil {
109+
return fmt.Errorf("failed to marshal projects: %w", err)
110+
}
111+
112+
languagesJSON, err := json.Marshal(session.Languages)
113+
if err != nil {
114+
return fmt.Errorf("failed to marshal languages: %w", err)
115+
}
116+
105117
query := `
106118
INSERT OR REPLACE INTO sessions
107-
(id, start_time, end_time, projects, languages, is_active)
108-
VALUES (?, ?, ?, ?, ?, ?)
119+
(id, start_time, end_time, duration, projects, languages, is_active, break_after)
120+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
109121
`
110122

111-
_, err := s.db.Exec(
123+
_, err = s.db.Exec(
112124
query,
113125
session.ID,
114126
session.StartTime.Unix(),
115127
session.EndTime.Unix(),
116-
strings.Join(session.Projects, ","),
117-
strings.Join(session.Languages, ","),
128+
session.Duration,
129+
string(projectsJSON),
130+
string(languagesJSON),
118131
boolToInt(session.IsActive),
132+
session.BreakAfter,
119133
)
120134

121135
if err != nil {
@@ -251,14 +265,16 @@ func createSchema(db *sql.DB) error {
251265
id TEXT PRIMARY KEY,
252266
start_time INTEGER NOT NULL,
253267
end_time INTEGER NOT NULL,
268+
duration REAL NOT NULL DEFAULT 0,
254269
projects TEXT,
255270
languages TEXT,
256271
is_active INTEGER DEFAULT 0,
272+
break_after REAL DEFAULT 0,
257273
created_at INTEGER DEFAULT (strftime('%s', 'now'))
258274
);
259275
260276
CREATE INDEX IF NOT EXISTS idx_sessions_start_time ON sessions(start_time);
261-
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project);
277+
CREATE INDEX IF NOT EXISTS idx_sessions_end_time ON sessions(end_time);
262278
`
263279

264280
statements := strings.Split(schema, ";")

core/tracker.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,26 @@ func (t *Tracker) TrackFileActivity(filePath, language, editor string, linesChan
5757
// Detect project
5858
project := DetectProject(filePath)
5959

60-
// Create activity
60+
// Calculate duration for this activity
61+
var duration float64
62+
if !t.lastActivityTime.IsZero() {
63+
gap := now.Sub(t.lastActivityTime).Seconds()
64+
// Cap at 2 minutes
65+
if gap > 120 {
66+
duration = 120
67+
} else {
68+
duration = gap
69+
}
70+
} else {
71+
// First activity - assume 2 minutes
72+
duration = 120
73+
}
74+
75+
// Create activity WITH duration
6176
activity := stats.Activity{
6277
ID: uuid.New().String(),
6378
Timestamp: now,
79+
Duration: duration, // ← ADD THIS
6480
Lines: linesChanged,
6581
Language: language,
6682
Project: project,
@@ -79,6 +95,7 @@ func (t *Tracker) TrackFileActivity(filePath, language, editor string, linesChan
7995
ID: uuid.New().String(),
8096
StartTime: now,
8197
EndTime: now,
98+
Duration: 0, // ← CHANGE THIS
8299
Projects: []string{},
83100
Languages: []string{},
84101
IsActive: true,
@@ -89,7 +106,14 @@ func (t *Tracker) TrackFileActivity(filePath, language, editor string, linesChan
89106
// Add activity
90107
t.activeSession.Activities = append(t.activeSession.Activities, activity)
91108
t.activeSession.EndTime = now
92-
t.activeSession.Duration = now.Sub(t.activeSession.StartTime).Seconds()
109+
110+
// CRITICAL FIX: Duration = sum of activity durations, not time span!
111+
sessionDuration := 0.0
112+
for _, act := range t.activeSession.Activities {
113+
sessionDuration += act.Duration
114+
}
115+
t.activeSession.Duration = sessionDuration // ← REPLACE LINE 74 WITH THIS
116+
93117
t.lastActivityTime = now
94118

95119
// Update Projects and Languages using StringSet

0 commit comments

Comments
 (0)