refactor: hourlog prints a day-by-project table
One row per day, one column per project, hours in the cell. The per-day blocks repeated the project name on every line and buried the totals. Share, active minutes, hour ranges, and the outside-working-hours flags move to --json, which is where the skill reads them anyway. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -242,29 +242,41 @@ def main():
|
||||
print()
|
||||
return 0
|
||||
|
||||
print(f"{since} .. {until} ({nfiles} sessions, {mins}-min slots, "
|
||||
f"{cfg['timezone']}, {cfg['nominal_day_hours']}h day)")
|
||||
print("hours are a suggested split of a full day, not measured time\n")
|
||||
label_w = max([12] + [len(p["project"]) for e in report
|
||||
for p in e["projects"]])
|
||||
cols = [p for p in dict.fromkeys(
|
||||
x["project"] for e in report for x in e["projects"])
|
||||
if not p.startswith("unmapped:")]
|
||||
widths = [max(len(c), 6) for c in cols]
|
||||
head = "day " + " ".join(
|
||||
f"{c:>{w}}" for c, w in zip(cols, widths)) + " total"
|
||||
print(head)
|
||||
print("-" * len(head))
|
||||
|
||||
totals = collections.Counter()
|
||||
flagged = False
|
||||
for e in report:
|
||||
if not e["projects"]:
|
||||
continue
|
||||
note = " — under 30min, treat as empty" if e["thin"] else ""
|
||||
print(f"{e['date']} {e['weekday']} "
|
||||
f"{e['total_active_minutes']}min active{note}")
|
||||
for p in e["projects"]:
|
||||
out = ""
|
||||
if p["outside_workday"]:
|
||||
out = " outside " + ",".join(
|
||||
f"{h:02d}h" for h in p["outside_workday"])
|
||||
print(f" {p['project']:<{label_w}} "
|
||||
f"{fmt_hours(p['suggested_hours']):>6} "
|
||||
f"{p['share']*100:3.0f}% {p['active_minutes']:>4}min "
|
||||
f"{p['first_hour']:02d}-{p['last_hour']:02d}h{out}")
|
||||
print()
|
||||
hours = {p["project"]: p["suggested_hours"] for p in e["projects"]}
|
||||
day_sum = sum(hours.get(c, 0) for c in cols)
|
||||
cells = " ".join(
|
||||
f"{fmt_hours(hours[c]) if hours.get(c) else '—':>{w}}"
|
||||
for c, w in zip(cols, widths))
|
||||
mark = " *" if e["thin"] and e["total_active_minutes"] else ""
|
||||
flagged = flagged or bool(mark)
|
||||
print(f"{e['date']} {e['weekday']} {cells} "
|
||||
f"{fmt_hours(day_sum) if day_sum else '—':>5}{mark}")
|
||||
for c in cols:
|
||||
totals[c] += hours.get(c, 0)
|
||||
|
||||
print("-" * len(head))
|
||||
print("total " + " ".join(
|
||||
f"{fmt_hours(totals[c]):>{w}}" for c, w in zip(cols, widths)) +
|
||||
f" {fmt_hours(sum(totals.values())):>5}")
|
||||
|
||||
if flagged:
|
||||
print("\n* under 30min of activity — treat as empty unless you know "
|
||||
"otherwise")
|
||||
if unmapped:
|
||||
print("unmapped paths — add them to the config or the exclude list:")
|
||||
print("\nunmapped paths, excluded from the split — add them to the "
|
||||
"config or the exclude list:")
|
||||
for u in unmapped:
|
||||
print(" ", u)
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user