group_by_status from yesterday gives you {"on track": [...], "critical": [...]}. But a report needs numbers, not just lists. Total hours, total revenue, average rate — per status group. That is a list of dicts inside a dict.
So I need to loop over the groups dict, and then loop over the clients inside each group. A loop inside a loop.
Exactly. Outer loop over .items() to get the status and the client list. Inner loop over the client list to accumulate totals. The pattern is the same as last week's single-client computation, now applied to each group:
for status, group in groups.items():
total_hours = sum(c.get("hours", 0) for c in group)
total_revenue = sum(c.get("rate", 0) * c.get("hours", 0) for c in group)
avg_rate = total_revenue / total_hours if total_hours > 0 else 0.0What is that sum(... for c in group) syntax? I have not seen a loop inside a function call before.
It is a generator expression — a compact way to compute a sum without building an intermediate list. sum(c.get("hours", 0) for c in group) is equivalent to a for loop that adds to a counter. Readable once you see it a few times.
So the output is a nested dict: {"on track": {"total_hours": ..., "total_revenue": ..., "avg_rate": ...}}.
Your monthly review spreadsheet, as a Python dict. JSON-serialisable, ready to email, ready to format:
def status_summary(clients: list) -> dict:
groups = group_by_status(clients)
summary = {}
for status, group in groups.items():
total_hours = sum(c.get("hours", 0) for c in group)
total_revenue = sum(c.get("rate", 0) * c.get("hours", 0) for c in group)
avg_rate = round(total_revenue / total_hours, 2) if total_hours > 0 else 0.0
summary[status] = {"total_hours": total_hours, "total_revenue": round(total_revenue, 2), "avg_rate": avg_rate}
print(f"Summary groups: {list(summary.keys())}")
return summarySix tabs, two hours, gone. One function, nested dict out.
And every downstream function — Week 3 and Week 4 — takes this summary as input. The data structure you build today is the backbone of the capstone.
status_summary builds on group_by_status to add computed stats per group:
groups = group_by_status(clients)
for status, group in groups.items():
total_hours = sum(c['hours'] for c in group)
total_revenue = sum(c['rate'] * c['hours'] for c in group)
avg_rate = total_revenue / total_hours if total_hours > 0 else 0sum() with a generator expression is the idiomatic way to add up a field across all dicts in a list — cleaner than initialising a counter variable and looping.
Guard against zero division: total_hours could be 0 if a group has no hours. The if total_hours > 0 else 0 guard prevents a ZeroDivisionError.
group_by_status from yesterday gives you {"on track": [...], "critical": [...]}. But a report needs numbers, not just lists. Total hours, total revenue, average rate — per status group. That is a list of dicts inside a dict.
So I need to loop over the groups dict, and then loop over the clients inside each group. A loop inside a loop.
Exactly. Outer loop over .items() to get the status and the client list. Inner loop over the client list to accumulate totals. The pattern is the same as last week's single-client computation, now applied to each group:
for status, group in groups.items():
total_hours = sum(c.get("hours", 0) for c in group)
total_revenue = sum(c.get("rate", 0) * c.get("hours", 0) for c in group)
avg_rate = total_revenue / total_hours if total_hours > 0 else 0.0What is that sum(... for c in group) syntax? I have not seen a loop inside a function call before.
It is a generator expression — a compact way to compute a sum without building an intermediate list. sum(c.get("hours", 0) for c in group) is equivalent to a for loop that adds to a counter. Readable once you see it a few times.
So the output is a nested dict: {"on track": {"total_hours": ..., "total_revenue": ..., "avg_rate": ...}}.
Your monthly review spreadsheet, as a Python dict. JSON-serialisable, ready to email, ready to format:
def status_summary(clients: list) -> dict:
groups = group_by_status(clients)
summary = {}
for status, group in groups.items():
total_hours = sum(c.get("hours", 0) for c in group)
total_revenue = sum(c.get("rate", 0) * c.get("hours", 0) for c in group)
avg_rate = round(total_revenue / total_hours, 2) if total_hours > 0 else 0.0
summary[status] = {"total_hours": total_hours, "total_revenue": round(total_revenue, 2), "avg_rate": avg_rate}
print(f"Summary groups: {list(summary.keys())}")
return summarySix tabs, two hours, gone. One function, nested dict out.
And every downstream function — Week 3 and Week 4 — takes this summary as input. The data structure you build today is the backbone of the capstone.
status_summary builds on group_by_status to add computed stats per group:
groups = group_by_status(clients)
for status, group in groups.items():
total_hours = sum(c['hours'] for c in group)
total_revenue = sum(c['rate'] * c['hours'] for c in group)
avg_rate = total_revenue / total_hours if total_hours > 0 else 0sum() with a generator expression is the idiomatic way to add up a field across all dicts in a list — cleaner than initialising a counter variable and looping.
Guard against zero division: total_hours could be 0 if a group has no hours. The if total_hours > 0 else 0 guard prevents a ZeroDivisionError.
Remy needs a nested summary of his client portfolio — total hours, total revenue, and average rate per project status group — for the monthly board report. Write `status_summary(clients)` that calls `group_by_status` on the clients list and returns a dict mapping each status to `{"total_hours", "total_revenue", "avg_rate"}`. Each input client dict has `name`, `rate`, `hours`, and `status` keys.
Tap each step for scaffolded hints.
No blank-editor panic.