feat(status): add status subcommand with day and week metrics
- Add status.py with compute_day_status() and compute_week_status() - Add projected_hours_for_day(): for each entry, use its duration if closed, or the next entry's start time as the close time if open - Open entries (start present, end absent) are preserved by the parser instead of being skipped; aggregate_rows() skips them for summaries - Expected end is computed by filling remaining hours into available time slots from the open entry's start; clamped to latest_end if a pre-logged entry ends later, with a note explaining why - Projected week total sums projected_hours_for_day() across all days - Add status subcommand to cli.py with shared source/day arguments - Add [work] daily_hours / weekly_hours config keys (default 8 / 40) - Add timesheets.example.toml [work] section - Add tests for projected_hours_for_day, compute_day_status, compute_week_status and all DayStatus/WeekStatus fields
This commit is contained in:
parent
d5dbe8791b
commit
f372a691d4
8 changed files with 799 additions and 7 deletions
|
|
@ -177,12 +177,16 @@ class TestParseTable:
|
|||
assert parse_table(lines) == []
|
||||
|
||||
def test_empty_end_time_row_skipped(self):
|
||||
# Open entry (no end, no duration) is now preserved with duration_hours=None
|
||||
lines = [
|
||||
"| Start | End | Project | Story | Note |",
|
||||
"|-------|-------|---------|-------|------|",
|
||||
"|-------|-------|---------|-------|------||",
|
||||
"| 09:55 | | bugs | | |",
|
||||
]
|
||||
assert parse_table(lines, has_duration_col=False) == []
|
||||
rows = parse_table(lines, has_duration_col=False)
|
||||
assert len(rows) == 1
|
||||
assert rows[0]["duration_hours"] is None
|
||||
assert rows[0]["end"] is None
|
||||
|
||||
def test_empty_input(self):
|
||||
assert parse_table([]) == []
|
||||
|
|
@ -231,15 +235,18 @@ class TestParseDocument:
|
|||
# File has 5 daily tables; expect a healthy number of rows
|
||||
assert len(rows) > 20
|
||||
# All rows must have expected keys
|
||||
# All closed rows must have positive duration; open entries have None
|
||||
for row in rows:
|
||||
assert "project" in row
|
||||
assert "duration_hours" in row
|
||||
assert row["duration_hours"] > 0
|
||||
# The incomplete row (09:55 | empty end) must have been skipped
|
||||
if row["duration_hours"] is not None:
|
||||
assert row["duration_hours"] > 0
|
||||
# The open entry (09:55, no end) must be preserved with duration_hours=None
|
||||
incomplete = [
|
||||
r for r in rows if r["start"] == "09:55" and r["project"] == "bugs"
|
||||
]
|
||||
assert all(r["duration_hours"] > 0 for r in incomplete)
|
||||
assert len(incomplete) == 1
|
||||
assert incomplete[0]["duration_hours"] is None
|
||||
|
||||
def test_week_file_no_markdown_links_in_stories(self):
|
||||
"""Markdown link syntax must be stripped from story/note fields."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue