more solid task updating, no need to stop the main loop to avoid race conditions
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
from collections import Counter, defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from pymongo import MongoClient
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
timezone = ZoneInfo("America/Argentina/Buenos_Aires")
|
||||
utctz = ZoneInfo("UTC")
|
||||
@@ -38,8 +38,8 @@ def parse_task_line(line):
|
||||
|
||||
|
||||
def load_task_from_files(task_id):
|
||||
"""Search task directory files for a task ID and load it into task_history."""
|
||||
for task_filepath in task_dir.glob("*"):
|
||||
"""Search task directory files (recursively) for a task ID and load it into task_history."""
|
||||
for task_filepath in task_dir.glob("**/*"):
|
||||
if not task_filepath.is_file():
|
||||
continue
|
||||
|
||||
@@ -65,12 +65,14 @@ def load_task_from_files(task_id):
|
||||
# Found it! Insert into task_history
|
||||
task_history.update_one(
|
||||
{"task_id": task_id},
|
||||
{"$set": {
|
||||
"path": full_path,
|
||||
"task_id": task_id,
|
||||
"source_file": task_filepath.name
|
||||
}},
|
||||
upsert=True
|
||||
{
|
||||
"$set": {
|
||||
"path": full_path,
|
||||
"task_id": task_id,
|
||||
"source_file": task_filepath.name,
|
||||
}
|
||||
},
|
||||
upsert=True,
|
||||
)
|
||||
return full_path
|
||||
except:
|
||||
@@ -134,15 +136,10 @@ def get_task_time_seconds(start, end, task_id, workspaces=None):
|
||||
"$match": {
|
||||
"date": {"$gte": start, "$lte": end},
|
||||
"task": task_id,
|
||||
"workspace": {"$in": workspaces}
|
||||
"workspace": {"$in": workspaces},
|
||||
}
|
||||
},
|
||||
{
|
||||
"$group": {
|
||||
"_id": None,
|
||||
"total_seconds": {"$sum": "$delta"}
|
||||
}
|
||||
}
|
||||
{"$group": {"_id": None, "total_seconds": {"$sum": "$delta"}}},
|
||||
]
|
||||
|
||||
result = list(switches.aggregate(pipeline))
|
||||
@@ -154,12 +151,8 @@ def get_task_time_seconds(start, end, task_id, workspaces=None):
|
||||
|
||||
|
||||
def task_or_none(task=None):
|
||||
if not task:
|
||||
task = read_and_extract(task_file)
|
||||
|
||||
if task == "all":
|
||||
task = None
|
||||
|
||||
return task
|
||||
|
||||
|
||||
@@ -180,24 +173,6 @@ def convert_seconds(seconds, use_days=False):
|
||||
return "{:02d}:{:02d}:{:02d}".format(hours + days * 24, minutes, remaining_seconds)
|
||||
|
||||
|
||||
def extract(line):
|
||||
if line.rstrip().endswith("*"):
|
||||
pipe_index = line.find("|")
|
||||
if pipe_index != -1 and len(line) > pipe_index + 8:
|
||||
value = line[pipe_index + 1 : pipe_index + 9]
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def read_and_extract(file_path):
|
||||
with open(file_path, "r") as file:
|
||||
for line in file:
|
||||
value = extract(line)
|
||||
if value:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def get_work_period_totals(start, end):
|
||||
"""Get period totals grouped by task with full path."""
|
||||
# Get all tasks with time in the period
|
||||
@@ -206,15 +181,10 @@ def get_work_period_totals(start, end):
|
||||
"$match": {
|
||||
"date": {"$gte": start, "$lte": end},
|
||||
"workspace": {"$in": ["Plan", "Think", "Work"]},
|
||||
"task": {"$exists": True, "$ne": None}
|
||||
"task": {"$exists": True, "$ne": None},
|
||||
}
|
||||
},
|
||||
{
|
||||
"$group": {
|
||||
"_id": "$task",
|
||||
"total_seconds": {"$sum": "$delta"}
|
||||
}
|
||||
}
|
||||
{"$group": {"_id": "$task", "total_seconds": {"$sum": "$delta"}}},
|
||||
]
|
||||
|
||||
results = list(switches.aggregate(pipeline))
|
||||
@@ -228,17 +198,18 @@ def get_work_period_totals(start, end):
|
||||
# Get task path with history fallback
|
||||
task_path = get_task_path(task_id)
|
||||
|
||||
combined_rows.append({
|
||||
"ws": task_path,
|
||||
"total": convert_seconds(total_seconds)
|
||||
})
|
||||
combined_rows.append(
|
||||
{"ws": task_path, "total": convert_seconds(total_seconds)}
|
||||
)
|
||||
|
||||
# Sort by path for consistency
|
||||
combined_rows.sort(key=lambda x: x["ws"])
|
||||
return combined_rows
|
||||
|
||||
|
||||
def get_task_blocks_calendar(start, end, task=None, min_block_seconds=300, grid_hours=1):
|
||||
def get_task_blocks_calendar(
|
||||
start, end, task=None, min_block_seconds=300, grid_hours=1
|
||||
):
|
||||
"""
|
||||
Get task blocks for calendar-style visualization, aggregated by time grid.
|
||||
Shows all tasks worked on during each grid period, with overlapping blocks.
|
||||
@@ -263,7 +234,7 @@ def get_task_blocks_calendar(start, end, task=None, min_block_seconds=300, grid_
|
||||
|
||||
match_query = {
|
||||
"date": {"$gte": start, "$lte": end},
|
||||
"workspace": {"$in": ["Plan", "Think", "Work"]} # Only active workspaces
|
||||
"workspace": {"$in": ["Plan", "Think", "Work"]}, # Only active workspaces
|
||||
}
|
||||
if task_query:
|
||||
match_query["task"] = task_query
|
||||
@@ -291,13 +262,14 @@ def get_task_blocks_calendar(start, end, task=None, min_block_seconds=300, grid_
|
||||
while remaining_duration > 0 and current_time < switch_end:
|
||||
# Calculate grid period start (hour rounded down to grid_hours)
|
||||
grid_hour = (current_time.hour // grid_hours) * grid_hours
|
||||
grid_start = current_time.replace(hour=grid_hour, minute=0, second=0, microsecond=0)
|
||||
grid_start = current_time.replace(
|
||||
hour=grid_hour, minute=0, second=0, microsecond=0
|
||||
)
|
||||
grid_end = grid_start + timedelta(hours=grid_hours)
|
||||
|
||||
# Time in this grid period
|
||||
time_in_grid = min(
|
||||
(grid_end - current_time).total_seconds(),
|
||||
remaining_duration
|
||||
(grid_end - current_time).total_seconds(), remaining_duration
|
||||
)
|
||||
|
||||
key = (current_time.date(), grid_hour, task_id)
|
||||
@@ -316,19 +288,23 @@ def get_task_blocks_calendar(start, end, task=None, min_block_seconds=300, grid_
|
||||
blocks = []
|
||||
for (date, grid_hour, task_id), data in grid_task_time.items():
|
||||
if data["duration"] >= min_block_seconds:
|
||||
grid_start = datetime(date.year, date.month, date.day, grid_hour, 0, 0, tzinfo=timezone)
|
||||
grid_start = datetime(
|
||||
date.year, date.month, date.day, grid_hour, 0, 0, tzinfo=timezone
|
||||
)
|
||||
|
||||
blocks.append({
|
||||
"task_id": task_id,
|
||||
"task_path": data["task_path"],
|
||||
"start": grid_start,
|
||||
"end": grid_start + timedelta(seconds=data["duration"]),
|
||||
"hour": grid_hour,
|
||||
"duration": int(data["duration"]),
|
||||
"active_seconds": int(data["duration"]),
|
||||
"idle_seconds": 0,
|
||||
"active_ratio": 1.0
|
||||
})
|
||||
blocks.append(
|
||||
{
|
||||
"task_id": task_id,
|
||||
"task_path": data["task_path"],
|
||||
"start": grid_start,
|
||||
"end": grid_start + timedelta(seconds=data["duration"]),
|
||||
"hour": grid_hour,
|
||||
"duration": int(data["duration"]),
|
||||
"active_seconds": int(data["duration"]),
|
||||
"idle_seconds": 0,
|
||||
"active_ratio": 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
return sorted(blocks, key=lambda x: (x["start"], x["task_path"]))
|
||||
|
||||
@@ -360,13 +336,15 @@ def get_raw_switches(start, end, task=None):
|
||||
# Get task path with history fallback
|
||||
task_path = get_task_path(task_id) or "No Task"
|
||||
|
||||
result.append({
|
||||
"workspace": switch["workspace"],
|
||||
"task_id": task_id,
|
||||
"task_path": task_path,
|
||||
"date": switch["date"].replace(tzinfo=utctz).astimezone(timezone),
|
||||
"delta": switch["delta"]
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"workspace": switch["workspace"],
|
||||
"task_id": task_id,
|
||||
"task_path": task_path,
|
||||
"date": switch["date"].replace(tzinfo=utctz).astimezone(timezone),
|
||||
"delta": switch["delta"],
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
Reference in New Issue
Block a user