bizy-ai 1.1.0__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {bizy_ai-1.1.0/bizy_ai.egg-info → bizy_ai-1.1.1}/PKG-INFO +1 -1
  2. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/cli.py +18 -5
  3. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/core.py +4 -4
  4. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/models.py +2 -2
  5. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/tasks.py +26 -13
  6. {bizy_ai-1.1.0 → bizy_ai-1.1.1/bizy_ai.egg-info}/PKG-INFO +1 -1
  7. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/pyproject.toml +1 -1
  8. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/setup.py +1 -1
  9. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_tasks.py +140 -0
  10. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/.env.example +0 -0
  11. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/LICENSE +0 -0
  12. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/MANIFEST.in +0 -0
  13. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/README.md +0 -0
  14. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/__init__.py +0 -0
  15. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/evening_review.py +0 -0
  16. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/morning_brief.py +0 -0
  17. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/plan_manager.py +0 -0
  18. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/planner.py +0 -0
  19. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/research.py +0 -0
  20. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/weekly_review.py +0 -0
  21. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/SOURCES.txt +0 -0
  22. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/dependency_links.txt +0 -0
  23. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/entry_points.txt +0 -0
  24. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/requires.txt +0 -0
  25. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/top_level.txt +0 -0
  26. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/requirements.txt +0 -0
  27. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/init_db.py +0 -0
  28. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/load_business_plan.py +0 -0
  29. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/migrate_db.py +0 -0
  30. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/scheduler.py +0 -0
  31. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/setup.cfg +0 -0
  32. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_models.py +0 -0
  33. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_plan_manager.py +0 -0
  34. {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_planner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bizy-ai
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: AI-powered business planning and execution agent
5
5
  Author: Reid Chatham
6
6
  License: MIT
@@ -362,23 +362,36 @@ def stats():
362
362
  """Show statistics"""
363
363
  task_mgr = TaskManager()
364
364
  weekly_stats = task_mgr.get_weekly_task_stats()
365
- velocity = task_mgr.get_task_velocity()
365
+ today_summary = task_mgr.get_daily_summary()
366
+ yesterday_summary = task_mgr.get_yesterday_summary()
367
+ velocity = task_mgr.get_task_velocity(days=7) # Use 7-day velocity to match weekly context
366
368
  today_tasks = task_mgr.get_tasks_for_today()
367
369
 
368
370
  console.print("\n[bold cyan]📊 Your Statistics[/bold cyan]\n")
369
- console.print("[bold]This Week:[/bold]")
371
+
372
+ # Today's stats
373
+ console.print("[bold]Today:[/bold]")
374
+ console.print(f" • Tasks Completed: {today_summary['tasks_completed']}")
375
+ console.print(f" • Tasks Scheduled: {len(today_tasks)}")
376
+
377
+ # Yesterday's stats
378
+ console.print(f"\n[bold]Yesterday:[/bold]")
379
+ console.print(f" • Tasks Completed: {yesterday_summary['tasks_completed']}")
380
+
381
+ # Weekly stats
382
+ console.print(f"\n[bold]This Week:[/bold]")
370
383
  console.print(f" • Tasks Completed: {weekly_stats['tasks_completed_this_week']}")
371
384
  console.print(f" • Tasks Created: {weekly_stats['tasks_created_this_week']}")
372
385
  console.print(f" • Completion Rate: {weekly_stats['completion_rate']:.1f}%")
373
386
  console.print(f" • Total Hours (Estimated): {weekly_stats['total_estimated_hours']:.1f}h")
374
387
  if weekly_stats['total_actual_hours'] > 0:
375
388
  console.print(f" • Total Hours (Actual): {weekly_stats['total_actual_hours']:.1f}h")
376
- console.print(f"\n[bold]Velocity:[/bold] {velocity:.1f} tasks/day")
377
- console.print(f"\n[bold]Today:[/bold] {len(today_tasks)} tasks scheduled\n")
389
+
390
+ console.print(f"\n[bold]Velocity:[/bold] {velocity:.1f} tasks/day\n")
378
391
 
379
392
  # Show breakdown by category if available
380
393
  if weekly_stats['categories']:
381
- console.print("[bold]By Category:[/bold]")
394
+ console.print("[bold]By Category (This Week):[/bold]")
382
395
  for category, count in sorted(weekly_stats['categories'].items(), key=lambda x: x[1], reverse=True):
383
396
  console.print(f" • {category}: {count} task(s)")
384
397
  console.print()
@@ -127,10 +127,10 @@ Be supportive but honest. Keep it concise."""
127
127
  WEEK ENDING: {datetime.now().strftime('%B %d, %Y')}
128
128
 
129
129
  STATS:
130
- - Total Tasks Completed: {weekly_stats.get('total_tasks_completed', 0)}
131
- - Total Tasks Planned: {weekly_stats.get('total_tasks_planned', 0)}
132
- - Average Completion Rate: {weekly_stats.get('average_completion_rate', 0):.0%}
133
- - Days Logged: {weekly_stats.get('days_logged', 0)}
130
+ - Total Tasks Completed: {weekly_stats.get('tasks_completed_this_week', 0)}
131
+ - Total Tasks Created: {weekly_stats.get('tasks_created_this_week', 0)}
132
+ - Completion Rate: {weekly_stats.get('completion_rate', 0):.1f}%
133
+ - Total Hours (Estimated): {weekly_stats.get('total_estimated_hours', 0):.1f}h
134
134
 
135
135
  GOAL PROGRESS:
136
136
  {goals_progress}
@@ -18,8 +18,8 @@ class Task(Base):
18
18
  estimated_hours = Column(Float)
19
19
  actual_hours = Column(Float)
20
20
  due_date = Column(DateTime)
21
- created_at = Column(DateTime, default=datetime.utcnow)
22
- completed_at = Column(DateTime)
21
+ created_at = Column(DateTime, default=datetime.now) # LOCAL time for consistency
22
+ completed_at = Column(DateTime) # LOCAL time, set by complete_task()
23
23
  parent_goal_id = Column(Integer) # Links to goals
24
24
  dependencies = Column(JSON) # List of task IDs this depends on
25
25
  notes = Column(Text)
@@ -118,16 +118,21 @@ class TaskManager:
118
118
  ).order_by(Task.due_date, Task.priority).all()
119
119
 
120
120
  def get_daily_summary(self, date_obj=None):
121
- """Get summary of tasks for a specific day"""
121
+ """
122
+ Get summary of tasks for a specific day.
123
+ Shows tasks completed on this day regardless of due date.
124
+ All timestamps are in LOCAL time.
125
+ """
122
126
  if date_obj is None:
123
127
  date_obj = datetime.now()
124
-
128
+
125
129
  if isinstance(date_obj, date) and not isinstance(date_obj, datetime):
126
130
  date_obj = datetime.combine(date_obj, datetime.min.time())
127
-
131
+
132
+ # Use LOCAL time boundaries (midnight to midnight)
128
133
  day_start = date_obj.replace(hour=0, minute=0, second=0, microsecond=0)
129
134
  day_end = day_start + timedelta(days=1)
130
-
135
+
131
136
  # Get tasks due on this day
132
137
  tasks_due = self.session.query(Task).filter(
133
138
  and_(
@@ -135,20 +140,28 @@ class TaskManager:
135
140
  Task.due_date < day_end
136
141
  )
137
142
  ).all()
138
-
139
- # Get tasks completed on this day
143
+
144
+ # Get tasks completed on this day (using LOCAL time)
140
145
  tasks_completed = self.session.query(Task).filter(
141
146
  and_(
142
147
  Task.completed_at >= day_start,
143
148
  Task.completed_at < day_end
144
149
  )
145
150
  ).all()
146
-
151
+
152
+ # Calculate completion rate based on tasks that were due
153
+ # If no tasks were due, show N/A but still show completed count
154
+ if tasks_due:
155
+ completed_count = len([t for t in tasks_due if t.status == 'completed'])
156
+ completion_rate = completed_count / len(tasks_due)
157
+ else:
158
+ completion_rate = 0
159
+
147
160
  return {
148
161
  'date': date_obj.strftime('%Y-%m-%d'),
149
162
  'tasks_due': len(tasks_due),
150
- 'tasks_completed': len(tasks_completed),
151
- 'completion_rate': len(tasks_completed) / len(tasks_due) if tasks_due else 0,
163
+ 'tasks_completed': len(tasks_completed), # All tasks completed today
164
+ 'completion_rate': completion_rate, # Based on tasks that were due
152
165
  'completed_tasks': [t.to_dict() for t in tasks_completed],
153
166
  'pending_tasks': [t.to_dict() for t in tasks_due if t.status != 'completed']
154
167
  }
@@ -249,8 +262,8 @@ class TaskManager:
249
262
 
250
263
  def get_completed_tasks_this_week(self, days=7):
251
264
  """Get tasks completed in the last N days based on completed_at timestamp"""
252
- # Use UTC time to match database timestamps
253
- now = datetime.utcnow()
265
+ # Use LOCAL time to match database timestamps
266
+ now = datetime.now()
254
267
  start_date = now - timedelta(days=days)
255
268
 
256
269
  completed_tasks = self.session.query(Task).filter(
@@ -265,8 +278,8 @@ class TaskManager:
265
278
 
266
279
  def get_created_tasks_this_week(self, days=7):
267
280
  """Get tasks created in the last N days based on created_at timestamp"""
268
- # Use UTC time to match database timestamps
269
- now = datetime.utcnow()
281
+ # Use LOCAL time to match database timestamps
282
+ now = datetime.now()
270
283
  start_date = now - timedelta(days=days)
271
284
 
272
285
  created_tasks = self.session.query(Task).filter(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bizy-ai
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: AI-powered business planning and execution agent
5
5
  Author: Reid Chatham
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bizy-ai"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "AI-powered business planning and execution agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
5
5
 
6
6
  setup(
7
7
  name='bizy-ai',
8
- version='1.0.2',
8
+ version='1.1.1',
9
9
  description='AI-powered business planning and execution agent',
10
10
  author='Reid Chatham',
11
11
  packages=find_packages(),
@@ -203,3 +203,143 @@ class TestTaskManager:
203
203
  assert stats['tasks_created_this_week'] == 0
204
204
  assert stats['completion_rate'] == 0
205
205
  assert len(stats['completed_tasks']) == 0
206
+
207
+ def test_get_daily_summary_today(self, test_session):
208
+ """Test getting summary for today's completed tasks"""
209
+ task_mgr = TaskManager()
210
+ task_mgr.session = test_session
211
+
212
+ # Complete 3 tasks today
213
+ for i in range(3):
214
+ task = task_mgr.create_task(title=f"Today Task {i+1}")
215
+ task_mgr.complete_task(task.id)
216
+
217
+ # Get today's summary
218
+ summary = task_mgr.get_daily_summary()
219
+
220
+ # Should show 3 tasks completed today
221
+ assert summary['tasks_completed'] == 3
222
+ assert len(summary['completed_tasks']) == 3
223
+
224
+ def test_get_daily_summary_yesterday(self, test_session):
225
+ """Test getting summary for yesterday's completed tasks"""
226
+ task_mgr = TaskManager()
227
+ task_mgr.session = test_session
228
+
229
+ # Create tasks completed yesterday
230
+ from agent.models import Task
231
+ yesterday = datetime.now() - timedelta(days=1)
232
+
233
+ for i in range(2):
234
+ task = Task(title=f"Yesterday Task {i+1}", status="completed")
235
+ task.completed_at = yesterday
236
+ test_session.add(task)
237
+ test_session.commit()
238
+
239
+ # Get yesterday's summary
240
+ summary = task_mgr.get_yesterday_summary()
241
+
242
+ # Should show 2 tasks completed yesterday
243
+ assert summary['tasks_completed'] == 2
244
+ assert len(summary['completed_tasks']) == 2
245
+
246
+ def test_get_daily_summary_with_due_date(self, test_session):
247
+ """Test daily summary shows tasks completed vs tasks due"""
248
+ task_mgr = TaskManager()
249
+ task_mgr.session = test_session
250
+
251
+ today = datetime.now()
252
+
253
+ # Create 5 tasks due today
254
+ for i in range(5):
255
+ task_mgr.create_task(title=f"Due Today {i+1}", due_date=today)
256
+
257
+ # Complete 3 of them
258
+ tasks = task_mgr.get_tasks_for_today()
259
+ for task in tasks[:3]:
260
+ task_mgr.complete_task(task.id)
261
+
262
+ # Get today's summary
263
+ summary = task_mgr.get_daily_summary()
264
+
265
+ assert summary['tasks_due'] == 5
266
+ assert summary['tasks_completed'] == 3
267
+ assert summary['completion_rate'] == 0.6 # 3/5
268
+
269
+ def test_timestamps_use_local_time(self, test_session):
270
+ """Test that both created_at and completed_at use LOCAL time consistently"""
271
+ from agent.models import Task
272
+ import time
273
+
274
+ task_mgr = TaskManager()
275
+ task_mgr.session = test_session
276
+
277
+ # Record current times
278
+ before_local = datetime.now()
279
+ time.sleep(0.01) # Small delay
280
+
281
+ # Create and complete a task
282
+ task = task_mgr.create_task(title="Timestamp Test Task")
283
+ task_id = task.id
284
+ task_mgr.complete_task(task_id)
285
+
286
+ time.sleep(0.01)
287
+ after_local = datetime.now()
288
+
289
+ # Refresh to get latest data
290
+ test_session.refresh(task)
291
+
292
+ # Both timestamps should be in local time (between before and after)
293
+ assert before_local <= task.created_at <= after_local, \
294
+ f"created_at {task.created_at} not in range [{before_local}, {after_local}]"
295
+ assert before_local <= task.completed_at <= after_local, \
296
+ f"completed_at {task.completed_at} not in range [{before_local}, {after_local}]"
297
+
298
+ # Timestamps should NOT be 7 hours apart (which would indicate UTC vs local mix)
299
+ time_diff = abs((task.created_at - task.completed_at).total_seconds())
300
+ assert time_diff < 60, \
301
+ f"created_at and completed_at differ by {time_diff}s - possible UTC/local mix"
302
+
303
+ def test_daily_summary_early_morning(self, test_session):
304
+ """Test daily summary works for tasks completed early morning (before 7 AM)"""
305
+ from agent.models import Task
306
+
307
+ task_mgr = TaskManager()
308
+ task_mgr.session = test_session
309
+
310
+ # Create a task completed at 5 AM today
311
+ today = datetime.now().replace(hour=5, minute=0, second=0, microsecond=0)
312
+ task = Task(title="Early Morning Task", status="completed")
313
+ task.completed_at = today
314
+ task.created_at = today - timedelta(hours=1)
315
+ test_session.add(task)
316
+ test_session.commit()
317
+
318
+ # Get today's summary
319
+ summary = task_mgr.get_daily_summary(today)
320
+
321
+ # Should include the 5 AM task
322
+ assert summary['tasks_completed'] >= 1, \
323
+ "Early morning task (5 AM) should be included in today's summary"
324
+
325
+ def test_daily_summary_late_night(self, test_session):
326
+ """Test daily summary works for tasks completed late at night (after 11 PM)"""
327
+ from agent.models import Task
328
+
329
+ task_mgr = TaskManager()
330
+ task_mgr.session = test_session
331
+
332
+ # Create a task completed at 11:30 PM today
333
+ today = datetime.now().replace(hour=23, minute=30, second=0, microsecond=0)
334
+ task = Task(title="Late Night Task", status="completed")
335
+ task.completed_at = today
336
+ task.created_at = today - timedelta(hours=1)
337
+ test_session.add(task)
338
+ test_session.commit()
339
+
340
+ # Get today's summary
341
+ summary = task_mgr.get_daily_summary(today)
342
+
343
+ # Should include the 11:30 PM task
344
+ assert summary['tasks_completed'] >= 1, \
345
+ "Late night task (11:30 PM) should be included in today's summary"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes