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:
naps62
2026-08-14 18:38:45 +00:00
parent 95e98a1e77
commit cd63a4c18e
2 changed files with 59 additions and 43 deletions
+33 -21
View File
@@ -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