avaliev commited on
Commit
9cd0080
·
verified ·
1 Parent(s): 7d2791c

Upload 2 files

Browse files
Files changed (2) hide show
  1. metrics.py +211 -83
  2. shared.py +1 -1
metrics.py CHANGED
@@ -10,93 +10,166 @@ import json
10
 
11
  class MetricsTracker:
12
  """Tracks productivity metrics and focus history."""
13
-
14
- def __init__(self, db_path: str = "focusflow.db"):
15
- """Initialize metrics tracker with SQLite database."""
 
 
 
 
 
 
16
  self.db_path = db_path
17
- self._init_db()
18
-
 
 
 
 
 
 
 
 
 
19
  def _init_db(self):
20
  """Create metrics tables if they don't exist."""
21
- conn = sqlite3.connect(self.db_path)
22
- cursor = conn.cursor()
23
-
24
- # Focus check history
25
- cursor.execute("""
26
- CREATE TABLE IF NOT EXISTS focus_history (
27
- id INTEGER PRIMARY KEY AUTOINCREMENT,
28
- task_id INTEGER,
29
- task_title TEXT,
30
- verdict TEXT,
31
- message TEXT,
32
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
33
- )
34
- """)
35
-
36
- # Daily streaks
37
- cursor.execute("""
38
- CREATE TABLE IF NOT EXISTS streaks (
39
- id INTEGER PRIMARY KEY AUTOINCREMENT,
40
- date DATE UNIQUE,
41
- on_track_count INTEGER DEFAULT 0,
42
- distracted_count INTEGER DEFAULT 0,
43
- idle_count INTEGER DEFAULT 0,
44
- max_consecutive_on_track INTEGER DEFAULT 0,
45
- focus_score REAL DEFAULT 0
46
- )
47
- """)
48
-
49
- conn.commit()
50
- conn.close()
51
-
 
 
 
 
 
 
52
  def log_focus_check(self, task_id: int, task_title: str, verdict: str, message: str):
53
  """Log a focus check result."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  conn = sqlite3.connect(self.db_path)
55
  cursor = conn.cursor()
56
-
57
  cursor.execute("""
58
  INSERT INTO focus_history (task_id, task_title, verdict, message, timestamp)
59
  VALUES (?, ?, ?, ?, ?)
60
- """, (task_id, task_title, verdict, message, datetime.now()))
61
-
62
  # Update today's streak data
63
- today = datetime.now().date()
64
-
65
  # Get or create today's streak record
66
  cursor.execute("SELECT * FROM streaks WHERE date = ?", (today,))
67
  row = cursor.fetchone()
68
-
69
  if row:
70
  # Update existing record
71
  on_track = row[2] + (1 if verdict == "On Track" else 0)
72
  distracted = row[3] + (1 if verdict == "Distracted" else 0)
73
  idle = row[4] + (1 if verdict == "Idle" else 0)
74
-
75
  # Calculate consecutive on-track streak
76
  cursor.execute("""
77
- SELECT verdict FROM focus_history
78
- WHERE DATE(timestamp) = ?
79
  ORDER BY timestamp DESC LIMIT 20
80
  """, (today,))
81
  recent_verdicts = [r[0] for r in cursor.fetchall()]
82
  recent_verdicts.reverse()
83
-
84
  consecutive = 0
85
  for v in reversed(recent_verdicts):
86
  if v == "On Track":
87
  consecutive += 1
88
  else:
89
  break
90
-
91
  max_consecutive = max(row[5], consecutive)
92
-
93
  # Calculate focus score (0-100)
94
  total_checks = on_track + distracted + idle
95
  focus_score = (on_track / total_checks * 100) if total_checks > 0 else 0
96
-
97
  cursor.execute("""
98
- UPDATE streaks
99
- SET on_track_count = ?, distracted_count = ?, idle_count = ?,
100
  max_consecutive_on_track = ?, focus_score = ?
101
  WHERE date = ?
102
  """, (on_track, distracted, idle, max_consecutive, focus_score, today))
@@ -106,27 +179,44 @@ class MetricsTracker:
106
  distracted = 1 if verdict == "Distracted" else 0
107
  idle = 1 if verdict == "Idle" else 0
108
  focus_score = (on_track / 1 * 100) if (on_track + distracted + idle) > 0 else 0
109
-
110
  cursor.execute("""
111
- INSERT INTO streaks (date, on_track_count, distracted_count, idle_count,
112
  max_consecutive_on_track, focus_score)
113
  VALUES (?, ?, ?, ?, ?, ?)
114
  """, (today, on_track, distracted, idle, on_track, focus_score))
115
-
116
  conn.commit()
117
  conn.close()
118
-
119
  def get_today_stats(self) -> Dict:
120
  """Get today's productivity statistics."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  conn = sqlite3.connect(self.db_path)
122
  cursor = conn.cursor()
123
-
124
- today = datetime.now().date()
125
  cursor.execute("SELECT * FROM streaks WHERE date = ?", (today,))
126
  row = cursor.fetchone()
127
-
128
  conn.close()
129
-
130
  if not row:
131
  return {
132
  "on_track": 0,
@@ -136,7 +226,7 @@ class MetricsTracker:
136
  "focus_score": 0,
137
  "total_checks": 0
138
  }
139
-
140
  return {
141
  "on_track": row[2],
142
  "distracted": row[3],
@@ -145,88 +235,126 @@ class MetricsTracker:
145
  "focus_score": round(row[6], 1),
146
  "total_checks": row[2] + row[3] + row[4]
147
  }
148
-
149
  def get_weekly_stats(self) -> List[Dict]:
150
  """Get last 7 days of statistics."""
 
 
 
 
 
 
 
 
151
  conn = sqlite3.connect(self.db_path)
152
  conn.row_factory = sqlite3.Row
153
  cursor = conn.cursor()
154
-
155
  seven_days_ago = datetime.now().date() - timedelta(days=6)
156
-
157
  cursor.execute("""
158
  SELECT date, on_track_count, distracted_count, idle_count, focus_score
159
  FROM streaks
160
  WHERE date >= ?
161
  ORDER BY date DESC
162
  """, (seven_days_ago,))
163
-
164
  rows = cursor.fetchall()
165
  conn.close()
166
-
167
  return [dict(row) for row in rows]
168
-
169
  def get_focus_history(self, limit: int = 20) -> List[Dict]:
170
  """Get recent focus check history."""
 
 
 
 
171
  conn = sqlite3.connect(self.db_path)
172
  conn.row_factory = sqlite3.Row
173
  cursor = conn.cursor()
174
-
175
  cursor.execute("""
176
  SELECT task_title, verdict, message, timestamp
177
  FROM focus_history
178
  ORDER BY timestamp DESC
179
  LIMIT ?
180
  """, (limit,))
181
-
182
  rows = cursor.fetchall()
183
  conn.close()
184
-
185
  return [dict(row) for row in rows]
186
-
187
  def get_current_streak(self) -> int:
188
  """Get current consecutive 'On Track' streak."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  conn = sqlite3.connect(self.db_path)
190
  cursor = conn.cursor()
191
-
192
  today = datetime.now().date()
193
  cursor.execute("""
194
- SELECT verdict FROM focus_history
195
- WHERE DATE(timestamp) = ?
196
  ORDER BY timestamp DESC LIMIT 50
197
  """, (today,))
198
-
199
  verdicts = [r[0] for r in cursor.fetchall()]
200
  conn.close()
201
-
202
  streak = 0
203
  for verdict in verdicts:
204
  if verdict == "On Track":
205
  streak += 1
206
  else:
207
  break
208
-
209
  return streak
210
-
211
  def get_chart_data(self) -> Dict:
212
  """Get data formatted for charts."""
213
  weekly = self.get_weekly_stats()
214
-
215
  # Prepare data for charts
216
  dates = []
217
  focus_scores = []
218
  on_track_counts = []
219
  distracted_counts = []
220
  idle_counts = []
221
-
222
  # Fill in missing days with zeros
223
  for i in range(7):
224
  date = datetime.now().date() - timedelta(days=6-i)
225
  dates.append(date.strftime("%m/%d"))
226
-
227
  # Find matching data
228
- day_data = next((d for d in weekly if str(d['date']) == str(date)), None)
229
-
 
 
 
 
 
 
 
 
 
 
230
  if day_data:
231
  focus_scores.append(day_data['focus_score'])
232
  on_track_counts.append(day_data['on_track_count'])
@@ -237,7 +365,7 @@ class MetricsTracker:
237
  on_track_counts.append(0)
238
  distracted_counts.append(0)
239
  idle_counts.append(0)
240
-
241
  return {
242
  "dates": dates,
243
  "focus_scores": focus_scores,
 
10
 
11
  class MetricsTracker:
12
  """Tracks productivity metrics and focus history."""
13
+
14
+ def __init__(self, db_path: str = "focusflow.db", use_memory: bool = False):
15
+ """
16
+ Initialize metrics tracker.
17
+
18
+ Args:
19
+ db_path: Path to SQLite database file
20
+ use_memory: If True, use in-memory list instead of SQLite (for Demo/HF Spaces)
21
+ """
22
  self.db_path = db_path
23
+ self.use_memory = use_memory
24
+
25
+ # In-memory storage
26
+ self.memory_history = [] # List of dicts for focus history
27
+ self.memory_streaks = {} # Dict of date -> dict for streaks
28
+
29
+ if not self.use_memory:
30
+ self._init_db()
31
+ else:
32
+ print("ℹ️ MetricsTracker initialized in IN-MEMORY mode (non-persistent)")
33
+
34
  def _init_db(self):
35
  """Create metrics tables if they don't exist."""
36
+ try:
37
+ conn = sqlite3.connect(self.db_path)
38
+ cursor = conn.cursor()
39
+
40
+ # Focus check history
41
+ cursor.execute("""
42
+ CREATE TABLE IF NOT EXISTS focus_history (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ task_id INTEGER,
45
+ task_title TEXT,
46
+ verdict TEXT,
47
+ message TEXT,
48
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
49
+ )
50
+ """)
51
+
52
+ # Daily streaks
53
+ cursor.execute("""
54
+ CREATE TABLE IF NOT EXISTS streaks (
55
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
56
+ date DATE UNIQUE,
57
+ on_track_count INTEGER DEFAULT 0,
58
+ distracted_count INTEGER DEFAULT 0,
59
+ idle_count INTEGER DEFAULT 0,
60
+ max_consecutive_on_track INTEGER DEFAULT 0,
61
+ focus_score REAL DEFAULT 0
62
+ )
63
+ """)
64
+
65
+ conn.commit()
66
+ conn.close()
67
+ except Exception as e:
68
+ print(f"⚠️ Metrics DB initialization failed: {e}. Falling back to in-memory mode.")
69
+ self.use_memory = True
70
+ self.memory_history = []
71
+ self.memory_streaks = {}
72
+
73
  def log_focus_check(self, task_id: int, task_title: str, verdict: str, message: str):
74
  """Log a focus check result."""
75
+ timestamp = datetime.now()
76
+ today = timestamp.date()
77
+
78
+ if self.use_memory:
79
+ # Log history
80
+ self.memory_history.append({
81
+ "task_id": task_id,
82
+ "task_title": task_title,
83
+ "verdict": verdict,
84
+ "message": message,
85
+ "timestamp": timestamp
86
+ })
87
+
88
+ # Update streaks
89
+ if today not in self.memory_streaks:
90
+ self.memory_streaks[today] = {
91
+ "date": today,
92
+ "on_track_count": 0,
93
+ "distracted_count": 0,
94
+ "idle_count": 0,
95
+ "max_consecutive_on_track": 0,
96
+ "focus_score": 0
97
+ }
98
+
99
+ streak_data = self.memory_streaks[today]
100
+
101
+ if verdict == "On Track":
102
+ streak_data["on_track_count"] += 1
103
+ elif verdict == "Distracted":
104
+ streak_data["distracted_count"] += 1
105
+ elif verdict == "Idle":
106
+ streak_data["idle_count"] += 1
107
+
108
+ # Calculate consecutive
109
+ recent_verdicts = [h['verdict'] for h in self.memory_history
110
+ if h['timestamp'].date() == today]
111
+
112
+ consecutive = 0
113
+ for v in reversed(recent_verdicts):
114
+ if v == "On Track":
115
+ consecutive += 1
116
+ else:
117
+ break
118
+
119
+ streak_data["max_consecutive_on_track"] = max(streak_data["max_consecutive_on_track"], consecutive)
120
+
121
+ # Calculate score
122
+ total = streak_data["on_track_count"] + streak_data["distracted_count"] + streak_data["idle_count"]
123
+ if total > 0:
124
+ streak_data["focus_score"] = (streak_data["on_track_count"] / total) * 100
125
+
126
+ return
127
+
128
  conn = sqlite3.connect(self.db_path)
129
  cursor = conn.cursor()
130
+
131
  cursor.execute("""
132
  INSERT INTO focus_history (task_id, task_title, verdict, message, timestamp)
133
  VALUES (?, ?, ?, ?, ?)
134
+ """, (task_id, task_title, verdict, message, timestamp))
135
+
136
  # Update today's streak data
137
+
 
138
  # Get or create today's streak record
139
  cursor.execute("SELECT * FROM streaks WHERE date = ?", (today,))
140
  row = cursor.fetchone()
141
+
142
  if row:
143
  # Update existing record
144
  on_track = row[2] + (1 if verdict == "On Track" else 0)
145
  distracted = row[3] + (1 if verdict == "Distracted" else 0)
146
  idle = row[4] + (1 if verdict == "Idle" else 0)
147
+
148
  # Calculate consecutive on-track streak
149
  cursor.execute("""
150
+ SELECT verdict FROM focus_history
151
+ WHERE DATE(timestamp) = ?
152
  ORDER BY timestamp DESC LIMIT 20
153
  """, (today,))
154
  recent_verdicts = [r[0] for r in cursor.fetchall()]
155
  recent_verdicts.reverse()
156
+
157
  consecutive = 0
158
  for v in reversed(recent_verdicts):
159
  if v == "On Track":
160
  consecutive += 1
161
  else:
162
  break
163
+
164
  max_consecutive = max(row[5], consecutive)
165
+
166
  # Calculate focus score (0-100)
167
  total_checks = on_track + distracted + idle
168
  focus_score = (on_track / total_checks * 100) if total_checks > 0 else 0
169
+
170
  cursor.execute("""
171
+ UPDATE streaks
172
+ SET on_track_count = ?, distracted_count = ?, idle_count = ?,
173
  max_consecutive_on_track = ?, focus_score = ?
174
  WHERE date = ?
175
  """, (on_track, distracted, idle, max_consecutive, focus_score, today))
 
179
  distracted = 1 if verdict == "Distracted" else 0
180
  idle = 1 if verdict == "Idle" else 0
181
  focus_score = (on_track / 1 * 100) if (on_track + distracted + idle) > 0 else 0
182
+
183
  cursor.execute("""
184
+ INSERT INTO streaks (date, on_track_count, distracted_count, idle_count,
185
  max_consecutive_on_track, focus_score)
186
  VALUES (?, ?, ?, ?, ?, ?)
187
  """, (today, on_track, distracted, idle, on_track, focus_score))
188
+
189
  conn.commit()
190
  conn.close()
191
+
192
  def get_today_stats(self) -> Dict:
193
  """Get today's productivity statistics."""
194
+ today = datetime.now().date()
195
+
196
+ if self.use_memory:
197
+ if today in self.memory_streaks:
198
+ data = self.memory_streaks[today]
199
+ return {
200
+ "on_track": data["on_track_count"],
201
+ "distracted": data["distracted_count"],
202
+ "idle": data["idle_count"],
203
+ "max_streak": data["max_consecutive_on_track"],
204
+ "focus_score": round(data["focus_score"], 1),
205
+ "total_checks": data["on_track_count"] + data["distracted_count"] + data["idle_count"]
206
+ }
207
+ return {
208
+ "on_track": 0, "distracted": 0, "idle": 0,
209
+ "max_streak": 0, "focus_score": 0, "total_checks": 0
210
+ }
211
+
212
  conn = sqlite3.connect(self.db_path)
213
  cursor = conn.cursor()
214
+
 
215
  cursor.execute("SELECT * FROM streaks WHERE date = ?", (today,))
216
  row = cursor.fetchone()
217
+
218
  conn.close()
219
+
220
  if not row:
221
  return {
222
  "on_track": 0,
 
226
  "focus_score": 0,
227
  "total_checks": 0
228
  }
229
+
230
  return {
231
  "on_track": row[2],
232
  "distracted": row[3],
 
235
  "focus_score": round(row[6], 1),
236
  "total_checks": row[2] + row[3] + row[4]
237
  }
238
+
239
  def get_weekly_stats(self) -> List[Dict]:
240
  """Get last 7 days of statistics."""
241
+ if self.use_memory:
242
+ stats = []
243
+ for i in range(7):
244
+ date = datetime.now().date() - timedelta(days=i)
245
+ if date in self.memory_streaks:
246
+ stats.append(self.memory_streaks[date])
247
+ return stats
248
+
249
  conn = sqlite3.connect(self.db_path)
250
  conn.row_factory = sqlite3.Row
251
  cursor = conn.cursor()
252
+
253
  seven_days_ago = datetime.now().date() - timedelta(days=6)
254
+
255
  cursor.execute("""
256
  SELECT date, on_track_count, distracted_count, idle_count, focus_score
257
  FROM streaks
258
  WHERE date >= ?
259
  ORDER BY date DESC
260
  """, (seven_days_ago,))
261
+
262
  rows = cursor.fetchall()
263
  conn.close()
264
+
265
  return [dict(row) for row in rows]
266
+
267
  def get_focus_history(self, limit: int = 20) -> List[Dict]:
268
  """Get recent focus check history."""
269
+ if self.use_memory:
270
+ # Return last N items, reversed
271
+ return sorted(self.memory_history, key=lambda x: x['timestamp'], reverse=True)[:limit]
272
+
273
  conn = sqlite3.connect(self.db_path)
274
  conn.row_factory = sqlite3.Row
275
  cursor = conn.cursor()
276
+
277
  cursor.execute("""
278
  SELECT task_title, verdict, message, timestamp
279
  FROM focus_history
280
  ORDER BY timestamp DESC
281
  LIMIT ?
282
  """, (limit,))
283
+
284
  rows = cursor.fetchall()
285
  conn.close()
286
+
287
  return [dict(row) for row in rows]
288
+
289
  def get_current_streak(self) -> int:
290
  """Get current consecutive 'On Track' streak."""
291
+ if self.use_memory:
292
+ today = datetime.now().date()
293
+ if today in self.memory_streaks:
294
+ # Recalculate from history just to be safe, or use cached max
295
+ # But "current streak" implies from NOW backwards
296
+ recent = sorted([h for h in self.memory_history if h['timestamp'].date() == today],
297
+ key=lambda x: x['timestamp'], reverse=True)
298
+ streak = 0
299
+ for h in recent:
300
+ if h['verdict'] == "On Track":
301
+ streak += 1
302
+ else:
303
+ break
304
+ return streak
305
+ return 0
306
+
307
  conn = sqlite3.connect(self.db_path)
308
  cursor = conn.cursor()
309
+
310
  today = datetime.now().date()
311
  cursor.execute("""
312
+ SELECT verdict FROM focus_history
313
+ WHERE DATE(timestamp) = ?
314
  ORDER BY timestamp DESC LIMIT 50
315
  """, (today,))
316
+
317
  verdicts = [r[0] for r in cursor.fetchall()]
318
  conn.close()
319
+
320
  streak = 0
321
  for verdict in verdicts:
322
  if verdict == "On Track":
323
  streak += 1
324
  else:
325
  break
326
+
327
  return streak
328
+
329
  def get_chart_data(self) -> Dict:
330
  """Get data formatted for charts."""
331
  weekly = self.get_weekly_stats()
332
+
333
  # Prepare data for charts
334
  dates = []
335
  focus_scores = []
336
  on_track_counts = []
337
  distracted_counts = []
338
  idle_counts = []
339
+
340
  # Fill in missing days with zeros
341
  for i in range(7):
342
  date = datetime.now().date() - timedelta(days=6-i)
343
  dates.append(date.strftime("%m/%d"))
344
+
345
  # Find matching data
346
+ # Handle both dict access (memory) and sqlite.Row (db)
347
+ day_data = None
348
+ for d in weekly:
349
+ d_date = d['date']
350
+ # Convert string date from DB to object if needed
351
+ if isinstance(d_date, str):
352
+ d_date = datetime.strptime(d_date, "%Y-%m-%d").date()
353
+
354
+ if d_date == date:
355
+ day_data = d
356
+ break
357
+
358
  if day_data:
359
  focus_scores.append(day_data['focus_score'])
360
  on_track_counts.append(day_data['on_track_count'])
 
365
  on_track_counts.append(0)
366
  distracted_counts.append(0)
367
  idle_counts.append(0)
368
+
369
  return {
370
  "dates": dates,
371
  "focus_scores": focus_scores,
shared.py CHANGED
@@ -14,4 +14,4 @@ LAUNCH_MODE = os.getenv("LAUNCH_MODE", "demo").lower()
14
  use_memory = (LAUNCH_MODE == "demo")
15
 
16
  task_manager = TaskManager(use_memory=use_memory)
17
- metrics_tracker = MetricsTracker()
 
14
  use_memory = (LAUNCH_MODE == "demo")
15
 
16
  task_manager = TaskManager(use_memory=use_memory)
17
+ metrics_tracker = MetricsTracker(use_memory=use_memory)