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.
- {bizy_ai-1.1.0/bizy_ai.egg-info → bizy_ai-1.1.1}/PKG-INFO +1 -1
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/cli.py +18 -5
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/core.py +4 -4
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/models.py +2 -2
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/tasks.py +26 -13
- {bizy_ai-1.1.0 → bizy_ai-1.1.1/bizy_ai.egg-info}/PKG-INFO +1 -1
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/pyproject.toml +1 -1
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/setup.py +1 -1
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_tasks.py +140 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/.env.example +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/LICENSE +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/MANIFEST.in +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/README.md +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/__init__.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/evening_review.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/morning_brief.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/plan_manager.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/planner.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/research.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/agent/weekly_review.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/SOURCES.txt +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/dependency_links.txt +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/entry_points.txt +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/requires.txt +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/bizy_ai.egg-info/top_level.txt +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/requirements.txt +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/init_db.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/load_business_plan.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/migrate_db.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/scripts/scheduler.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/setup.cfg +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_models.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_plan_manager.py +0 -0
- {bizy_ai-1.1.0 → bizy_ai-1.1.1}/tests/test_planner.py +0 -0
@@ -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
|
-
|
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
|
-
|
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
|
-
|
377
|
-
console.print(f"\n[bold]
|
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('
|
131
|
-
- Total Tasks
|
132
|
-
-
|
133
|
-
-
|
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.
|
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
|
-
"""
|
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':
|
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
|
253
|
-
now = datetime.
|
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
|
269
|
-
now = datetime.
|
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(
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|