Case Study 2: Maya Reyes and the Client Portfolio Tracker
Character: Maya Reyes (freelance business consultant) Chapter: 7 — Data Structures Difficulty: Intermediate–Advanced
The Situation
Maya Reyes runs a one-person consulting practice. She has twelve active clients, ranging from a small bakery chain that pays her a retainer to an enterprise software company that calls her in for quarterly strategy sessions. She has been tracking everything in a spreadsheet, but the spreadsheet is getting unwieldy — it can't answer questions like "which client hasn't heard from me in over 90 days?" or "what percentage of my revenue comes from my top two clients?"
It's a Sunday afternoon. Maya brews coffee, opens her laptop, and decides she's going to build a client portfolio tracker in Python. She knows the techniques from this chapter; now she wants to put them to real use.
Her goals: 1. Represent her client portfolio as a structured in-memory data store 2. Find her most valuable client 3. Identify at-risk relationships (clients who haven't been invoiced in over 60 days) 4. Calculate her revenue concentration — how dependent is she on her top clients? 5. Generate a one-page portfolio summary report she can review each Monday morning
Step 1: Designing the Client Record
Maya thinks about what she needs to know about each client. She sketches the fields:
- client_id: unique identifier
- name: client company or person name
- industry: helps her spot patterns across sectors
- relationship_start: when she started working with them (year)
- status: active, paused, or closed
- monthly_retainer_usd: recurring monthly revenue (0 if project-based)
- ytd_revenue_usd: year-to-date revenue billed to this client
- last_invoice_date: date of most recent invoice (as a string "YYYY-MM-DD")
- primary_contact: contact name
- services: list of services she provides to this client
- notes: a brief free-text note
Each client is a dict. All clients together are a list of dicts. The services field is itself a list, making this a nested structure.
Step 2: Building the Portfolio
"""
Maya Reyes Consulting — Client Portfolio Tracker
Case Study 2: Chapter 7
"""
from __future__ import annotations
from datetime import date, datetime
from typing import Any
# Type alias for readability
ClientRecord = dict[str, Any]
# -----------------------------------------------------------------------
# PORTFOLIO DATA
# Represents Maya's current client roster as of early February.
# -----------------------------------------------------------------------
def load_portfolio() -> list[ClientRecord]:
"""
Return Maya's current client portfolio as a list of dicts.
Each record represents one client relationship. The 'services' field
is a list because a client can receive multiple service types.
"""
return [
{
"client_id": "CLI-001",
"name": "Brightstone Bakery Group",
"industry": "Food & Beverage",
"relationship_start": 2021,
"status": "active",
"monthly_retainer_usd": 3_500.00,
"ytd_revenue_usd": 7_000.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Ellen Cho",
"services": ["operations consulting", "supply chain review"],
"notes": "Expanding to 3 new locations this spring.",
},
{
"client_id": "CLI-002",
"name": "Vertex Software Inc.",
"industry": "Technology",
"relationship_start": 2019,
"status": "active",
"monthly_retainer_usd": 0.00,
"ytd_revenue_usd": 22_500.00,
"last_invoice_date": "2026-01-15",
"primary_contact": "Raj Mehta",
"services": ["go-to-market strategy", "pricing analysis", "executive coaching"],
"notes": "Q1 strategy session completed. Next engagement in April.",
},
{
"client_id": "CLI-003",
"name": "Harborlight Realty",
"industry": "Real Estate",
"relationship_start": 2023,
"status": "active",
"monthly_retainer_usd": 2_000.00,
"ytd_revenue_usd": 4_000.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Victor Osei",
"services": ["market analysis", "investor reporting"],
"notes": "Steady relationship. Low maintenance.",
},
{
"client_id": "CLI-004",
"name": "Cascadia Health Partners",
"industry": "Healthcare",
"relationship_start": 2022,
"status": "active",
"monthly_retainer_usd": 5_000.00,
"ytd_revenue_usd": 10_000.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Dr. Amara Diallo",
"services": ["operational efficiency", "staff training", "compliance review"],
"notes": "Large account. Renewal discussion in March.",
},
{
"client_id": "CLI-005",
"name": "Thornfield Logistics",
"industry": "Logistics",
"relationship_start": 2020,
"status": "active",
"monthly_retainer_usd": 0.00,
"ytd_revenue_usd": 8_750.00,
"last_invoice_date": "2025-11-20",
"primary_contact": "Rachel Torres",
"services": ["route optimization", "vendor negotiation"],
"notes": "Last project wrapped November. No new work yet.",
},
{
"client_id": "CLI-006",
"name": "Northgate Nonprofit Alliance",
"industry": "Nonprofit",
"relationship_start": 2024,
"status": "active",
"monthly_retainer_usd": 1_200.00,
"ytd_revenue_usd": 2_400.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Fatima Ali",
"services": ["fundraising strategy", "board governance"],
"notes": "Pro-bono rate applied. Strong relationship.",
},
{
"client_id": "CLI-007",
"name": "Meridian Capital Group",
"industry": "Finance",
"relationship_start": 2021,
"status": "active",
"monthly_retainer_usd": 4_500.00,
"ytd_revenue_usd": 9_000.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Nathan Park",
"services": ["investment strategy", "due diligence support"],
"notes": "Consistent, reliable. Referred CLI-010.",
},
{
"client_id": "CLI-008",
"name": "Summit Ridge Hospitality",
"industry": "Hospitality",
"relationship_start": 2023,
"status": "paused",
"monthly_retainer_usd": 0.00,
"ytd_revenue_usd": 0.00,
"last_invoice_date": "2025-09-15",
"primary_contact": "Chris Balcom",
"services": ["revenue management", "brand positioning"],
"notes": "On hold since October — client budget freeze.",
},
{
"client_id": "CLI-009",
"name": "Blue Horizon Retail",
"industry": "Retail",
"relationship_start": 2022,
"status": "active",
"monthly_retainer_usd": 0.00,
"ytd_revenue_usd": 3_200.00,
"last_invoice_date": "2025-12-10",
"primary_contact": "Lisa Fontaine",
"services": ["merchandising strategy"],
"notes": "Holiday season project completed. Follow up in Q2.",
},
{
"client_id": "CLI-010",
"name": "Ironclad Legal Services",
"industry": "Legal",
"relationship_start": 2024,
"status": "active",
"monthly_retainer_usd": 2_800.00,
"ytd_revenue_usd": 5_600.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Diane Okafor",
"services": ["business development", "client intake process"],
"notes": "New client (referred by Meridian). High potential.",
},
{
"client_id": "CLI-011",
"name": "Pacific Grove Charter School",
"industry": "Education",
"relationship_start": 2023,
"status": "active",
"monthly_retainer_usd": 800.00,
"ytd_revenue_usd": 1_600.00,
"last_invoice_date": "2026-01-31",
"primary_contact": "Mark Ingram",
"services": ["strategic planning"],
"notes": "Small account. Low revenue but high satisfaction.",
},
{
"client_id": "CLI-012",
"name": "Redwood Valley Winery",
"industry": "Food & Beverage",
"relationship_start": 2020,
"status": "active",
"monthly_retainer_usd": 0.00,
"ytd_revenue_usd": 4_100.00,
"last_invoice_date": "2025-10-05",
"primary_contact": "Sophie Durand",
"services": ["direct-to-consumer strategy", "e-commerce setup"],
"notes": "Project-based. Annual review engagement due soon.",
},
]
Step 3: Finding the Most Valuable Client
"Most valuable" can mean different things. Maya decides she wants to know two things: 1. Highest year-to-date revenue (who has paid the most so far this year) 2. Highest monthly retainer (who provides the most predictable recurring income)
def most_valuable_client_ytd(portfolio: list[ClientRecord]) -> ClientRecord | None:
"""
Return the client with the highest year-to-date revenue billed.
Only considers active clients.
Args:
portfolio: List of client dicts.
Returns:
The client record with the highest ytd_revenue_usd, or None if empty.
"""
active_clients = [c for c in portfolio if c["status"] == "active"]
if not active_clients:
return None
return max(active_clients, key=lambda c: c["ytd_revenue_usd"])
def highest_retainer_client(portfolio: list[ClientRecord]) -> ClientRecord | None:
"""
Return the active client with the highest monthly retainer.
Args:
portfolio: List of client dicts.
Returns:
The client record with the highest monthly_retainer_usd, or None.
"""
retainer_clients = [
c for c in portfolio
if c["status"] == "active" and c["monthly_retainer_usd"] > 0
]
if not retainer_clients:
return None
return max(retainer_clients, key=lambda c: c["monthly_retainer_usd"])
Running these:
portfolio = load_portfolio()
top_ytd = most_valuable_client_ytd(portfolio)
print(f"Highest YTD revenue: {top_ytd['name']} (${top_ytd['ytd_revenue_usd']:,.2f})")
top_retainer = highest_retainer_client(portfolio)
print(f"Highest retainer: {top_retainer['name']} (${top_retainer['monthly_retainer_usd']:,.2f}/mo)")
Output:
Highest YTD revenue: Vertex Software Inc. ($22,500.00)
Highest retainer: Cascadia Health Partners ($5,000.00/mo)
Vertex Software is her biggest year-to-date earner, but they're project-based — unpredictable. Cascadia Health is her most reliable monthly income. Maya makes a mental note: she should nurture both relationships equally.
Step 4: Identifying At-Risk Relationships
An at-risk client is one she hasn't invoiced in a long time. For active clients, Maya's rule of thumb is: - Yellow flag: no invoice in the last 60 days - Red flag: no invoice in the last 90 days
She needs to calculate the number of days since each client's last invoice. The datetime module handles the date math.
def days_since_invoice(last_invoice_date_str: str, reference_date: date | None = None) -> int:
"""
Calculate the number of calendar days since an invoice date.
Args:
last_invoice_date_str: Date string in 'YYYY-MM-DD' format.
reference_date: The date to measure from (defaults to today).
Returns:
Number of days since the invoice date (integer).
"""
if reference_date is None:
reference_date = date.today()
invoice_date = datetime.strptime(last_invoice_date_str, "%Y-%m-%d").date()
return (reference_date - invoice_date).days
def find_at_risk_clients(
portfolio: list[ClientRecord],
yellow_threshold: int = 60,
red_threshold: int = 90,
reference_date: date | None = None,
) -> dict[str, list[dict]]:
"""
Identify active clients who haven't been invoiced recently.
Args:
portfolio: List of client dicts.
yellow_threshold: Days without invoice to trigger yellow flag (default 60).
red_threshold: Days without invoice to trigger red flag (default 90).
reference_date: Date to measure from (defaults to today).
Returns:
Dict with keys 'red' and 'yellow', each mapping to a list of dicts:
[{"client": record, "days_since_invoice": int}]
"""
if reference_date is None:
reference_date = date.today()
at_risk: dict[str, list[dict]] = {"red": [], "yellow": []}
for client in portfolio:
if client["status"] != "active":
continue
days = days_since_invoice(client["last_invoice_date"], reference_date)
entry = {"client": client, "days_since_invoice": days}
if days >= red_threshold:
at_risk["red"].append(entry)
elif days >= yellow_threshold:
at_risk["yellow"].append(entry)
# Sort each list by days (most overdue first)
at_risk["red"].sort(key=lambda e: e["days_since_invoice"], reverse=True)
at_risk["yellow"].sort(key=lambda e: e["days_since_invoice"], reverse=True)
return at_risk
Maya runs the report using February 24, 2026 as her reference date:
from datetime import date
ref = date(2026, 2, 24)
at_risk = find_at_risk_clients(portfolio, reference_date=ref)
print("\nAt-Risk Client Relationships")
print("-" * 55)
if at_risk["red"]:
print("\n RED FLAG (90+ days since last invoice):")
for entry in at_risk["red"]:
c = entry["client"]
print(f" {c['name']:<35} {entry['days_since_invoice']:>4} days (last: {c['last_invoice_date']})")
else:
print("\n No red-flag clients.")
if at_risk["yellow"]:
print("\n YELLOW FLAG (60-89 days since last invoice):")
for entry in at_risk["yellow"]:
c = entry["client"]
print(f" {c['name']:<35} {entry['days_since_invoice']:>4} days (last: {c['last_invoice_date']})")
else:
print("\n No yellow-flag clients.")
Output (with reference date 2026-02-24):
At-Risk Client Relationships
-------------------------------------------------------
RED FLAG (90+ days since last invoice):
Redwood Valley Winery 142 days (last: 2025-10-05)
Summit Ridge Hospitality 162 days (last: 2025-09-15) [paused]
YELLOW FLAG (60-89 days since last invoice):
Thornfield Logistics 96 days (last: 2025-11-20)
Blue Horizon Retail 76 days (last: 2025-12-10)
Maya stares at Thornfield Logistics. Rachel Torres was a great client — she needs to reach out this week. And the Redwood Valley Winery annual review is overdue. She adds two items to her calendar before she even finishes her coffee.
Step 5: Revenue Concentration Analysis
Revenue concentration tells Maya how much of her income depends on just one or two clients. This is a key business risk metric for any sole proprietor. If her top client disappears, what percentage of her income does she lose?
def revenue_concentration(
portfolio: list[ClientRecord],
top_n: int = 3,
) -> dict[str, Any]:
"""
Calculate revenue concentration — how dependent Maya is on her top clients.
Computes total YTD revenue, top-N share, and the Herfindahl-Hirschman
approximation (sum of squared revenue shares), which is a standard measure
of concentration risk.
Args:
portfolio: List of client dicts.
top_n: Number of top clients to analyze (default 3).
Returns:
Dict with total_revenue, top_n_revenue, top_n_share_pct,
top_clients (list of dicts), and concentration_score.
"""
active = [c for c in portfolio if c["status"] == "active" and c["ytd_revenue_usd"] > 0]
if not active:
return {}
total_revenue = sum(c["ytd_revenue_usd"] for c in active)
ranked = sorted(active, key=lambda c: c["ytd_revenue_usd"], reverse=True)
top_clients_list = []
for client in ranked[:top_n]:
share = client["ytd_revenue_usd"] / total_revenue * 100
top_clients_list.append({
"name": client["name"],
"ytd_revenue": client["ytd_revenue_usd"],
"share_pct": share,
})
top_n_revenue = sum(item["ytd_revenue"] for item in top_clients_list)
top_n_share = top_n_revenue / total_revenue * 100
# Herfindahl index: sum of squared market shares (in decimal form)
# Score of 1.0 = one client is 100% of revenue (maximum concentration)
# Score near 0 = perfectly diversified
hhi = sum((c["ytd_revenue_usd"] / total_revenue) ** 2 for c in active)
return {
"total_revenue": total_revenue,
"client_count": len(active),
"top_n": top_n,
"top_n_revenue": top_n_revenue,
"top_n_share_pct": top_n_share,
"top_clients": top_clients_list,
"concentration_score": hhi,
}
Running it:
concentration = revenue_concentration(portfolio, top_n=3)
print("\nRevenue Concentration Analysis")
print("-" * 50)
print(f" Total YTD revenue: ${concentration['total_revenue']:>10,.2f}")
print(f" Active clients: {concentration['client_count']}")
print(f" Top {concentration['top_n']} share: {concentration['top_n_share_pct']:>6.1f}%")
print(f" Concentration score: {concentration['concentration_score']:.3f} (0=diversified, 1=one client)")
print()
print(f" Top {concentration['top_n']} Clients:")
for i, client in enumerate(concentration["top_clients"], start=1):
print(f" #{i}: {client['name']:<35} ${client['ytd_revenue']:>8,.2f} ({client['share_pct']:.1f}%)")
Output:
Revenue Concentration Analysis
--------------------------------------------------
Total YTD revenue: $ 78,150.00
Active clients: 11
Top 3 share: 54.7%
Concentration score: 0.141 (0=diversified, 1=one client)
Top 3 Clients:
#1: Vertex Software Inc. $22,500.00 (28.8%)
#2: Cascadia Health Partners $10,000.00 (12.8%)
#3: Thornfield Logistics $ 8,750.00 (11.2%)
Maya's top 3 clients represent 54.7% of her YTD revenue. That's actually not bad for a solo practitioner — the rule of thumb is that over 60% in one client is a serious risk. But Vertex Software alone at 28.8% gives her pause. She makes a note to actively develop two smaller clients this quarter to reduce that dependency.
Step 6: Generating the Weekly Portfolio Summary
Maya pulls it all together into a generate_portfolio_report() function she can run every Monday morning. It gives her a complete read of her business in under five seconds.
def portfolio_summary(portfolio: list[ClientRecord], reference_date: date | None = None) -> None:
"""
Print a complete portfolio summary report.
Args:
portfolio: List of client dicts.
reference_date: Date to use for 'days since invoice' calculations.
"""
if reference_date is None:
reference_date = date.today()
active = [c for c in portfolio if c["status"] == "active"]
paused = [c for c in portfolio if c["status"] == "paused"]
total_ytd = sum(c["ytd_revenue_usd"] for c in active)
monthly_retainer_total = sum(c["monthly_retainer_usd"] for c in active)
print("=" * 65)
print(" MAYA REYES CONSULTING — CLIENT PORTFOLIO REPORT")
print(f" As of: {reference_date.strftime('%B %d, %Y')}")
print("=" * 65)
# ---- Overview ----
print(f"\n Portfolio Overview")
print(f" {'Active clients:':<30} {len(active)}")
print(f" {'Paused clients:':<30} {len(paused)}")
print(f" {'YTD total revenue:':<30} ${total_ytd:>10,.2f}")
print(f" {'Monthly recurring revenue:':<30} ${monthly_retainer_total:>10,.2f}")
# ---- All clients ranked by YTD revenue ----
ranked = sorted(active, key=lambda c: c["ytd_revenue_usd"], reverse=True)
print(f"\n Client Roster (ranked by YTD revenue)")
print(f" {'#':<3} {'Client':<30} {'Industry':<18} {'YTD Rev':>10} {'Retainer/mo':>12} {'Days Since Inv':>14}")
print(f" {'-'*3} {'-'*30} {'-'*18} {'-'*10} {'-'*12} {'-'*14}")
for i, client in enumerate(ranked, start=1):
days = days_since_invoice(client["last_invoice_date"], reference_date)
flag = " [RED]" if days >= 90 else (" [YLW]" if days >= 60 else "")
print(
f" {i:<3} {client['name']:<30} {client['industry']:<18} "
f"${client['ytd_revenue_usd']:>9,.2f} "
f"${client['monthly_retainer_usd']:>11,.2f} "
f"{days:>12} d{flag}"
)
# ---- At-risk clients ----
at_risk = find_at_risk_clients(portfolio, reference_date=reference_date)
total_at_risk = len(at_risk["red"]) + len(at_risk["yellow"])
if total_at_risk:
print(f"\n Relationship Alerts ({total_at_risk} clients need attention)")
for entry in at_risk["red"]:
c = entry["client"]
print(f" [RED ] {c['name']:<30} {entry['days_since_invoice']} days — {c['notes'][:50]}")
for entry in at_risk["yellow"]:
c = entry["client"]
print(f" [YELLOW] {c['name']:<30} {entry['days_since_invoice']} days — {c['notes'][:50]}")
else:
print("\n No relationship alerts.")
# ---- Revenue concentration ----
conc = revenue_concentration(portfolio, top_n=3)
print(f"\n Revenue Concentration")
print(f" Top 3 clients = {conc['top_n_share_pct']:.1f}% of YTD revenue")
print(f" Concentration score: {conc['concentration_score']:.3f}")
for item in conc["top_clients"]:
print(f" {item['name']:<30} ${item['ytd_revenue']:>9,.2f} ({item['share_pct']:.1f}%)")
# ---- Services inventory ----
all_services: set[str] = set()
service_client_count: dict[str, int] = {}
for client in active:
for svc in client["services"]:
all_services.add(svc)
service_client_count[svc] = service_client_count.get(svc, 0) + 1
print(f"\n Services Offered ({len(all_services)} distinct services across {len(active)} active clients)")
ranked_services = sorted(service_client_count.items(), key=lambda x: x[1], reverse=True)
for svc, count in ranked_services:
print(f" {svc:<35} {count} client{'s' if count != 1 else ''}")
# ---- Industry diversity ----
industries: dict[str, int] = {}
for client in active:
ind = client["industry"]
industries[ind] = industries.get(ind, 0) + 1
print(f"\n Industry Distribution")
for industry in sorted(industries, key=lambda i: industries[i], reverse=True):
bar = "#" * industries[industry]
print(f" {industry:<22} {bar} ({industries[industry]})")
print("\n" + "=" * 65)
Running the full report:
portfolio = load_portfolio()
portfolio_summary(portfolio, reference_date=date(2026, 2, 24))
What the Output Tells Maya
When the report finishes printing, Maya reads it carefully. A few things stand out:
Relationship Alerts: Thornfield Logistics and Redwood Valley Winery both need outreach this week. She schedules two 15-minute calls.
Revenue concentration: Vertex Software is 28.8% of her income — higher than she'd like. Her goal for Q1 is to bring two paused or new clients back to active status.
Services: "Operations consulting" appears with five clients — that's her core competency. "E-commerce setup" appears with only one client (Redwood Valley), which flags as a niche service that isn't scaling.
Industry diversity: She has two Food & Beverage clients (Brightstone Bakery and Redwood Valley Winery). That's a positive pattern — she has industry expertise to leverage.
None of this analysis required a database. It required choosing the right data structure and writing clean, focused functions.
Step 7: Bonus — Updating the Portfolio
The portfolio isn't static. Maya writes simple update functions:
def add_client(portfolio: list[ClientRecord], new_client: ClientRecord) -> bool:
"""
Add a new client record to the portfolio.
Validates that client_id does not already exist.
Args:
portfolio: The current portfolio list.
new_client: Dict containing all required client fields.
Returns:
True if added, False if client_id is a duplicate.
"""
existing_ids = {c["client_id"] for c in portfolio}
if new_client["client_id"] in existing_ids:
print(f" [ERROR] Client ID {new_client['client_id']} already exists.")
return False
portfolio.append(new_client)
return True
def record_invoice(
portfolio: list[ClientRecord],
client_id: str,
amount: float,
invoice_date: str,
) -> bool:
"""
Record a new invoice: update last_invoice_date and add to ytd_revenue.
Args:
portfolio: The current portfolio list.
client_id: ID of the client to update.
amount: Invoice amount in USD.
invoice_date: Invoice date string in 'YYYY-MM-DD' format.
Returns:
True if updated, False if client not found.
"""
for client in portfolio:
if client["client_id"] == client_id:
client["ytd_revenue_usd"] += amount
client["last_invoice_date"] = invoice_date
print(f" Invoice recorded: {client['name']} — ${amount:,.2f} on {invoice_date}")
return True
print(f" [ERROR] Client {client_id} not found.")
return False
When Maya wins a new engagement with Thornfield Logistics:
record_invoice(portfolio, "CLI-005", 6_500.00, "2026-02-24")
Output:
Invoice recorded: Thornfield Logistics — $6,500.00 on 2026-02-24
And the next Monday's portfolio report will show Thornfield off the at-risk list, with updated YTD revenue.
What Maya Learned
Maya saves the file as portfolio_tracker.py and closes her laptop. The whole thing took about two hours.
The insight she keeps coming back to is this: the hardest part was deciding what each client record should look like. Once she had a clear, consistent structure — every client a dict, every portfolio a list — the analysis functions practically wrote themselves. Each function takes the list, iterates, and accumulates. Same pattern, different fields.
She also appreciated the set pattern for unique services. Using a set to collect all unique service types from across all clients — {svc for client in active for svc in client["services"]} — was three characters of syntax for something that would have taken a ten-line loop with duplicate checking just a chapter ago.
Data structures, she concludes, are not a technical concept. They are a business concept. Choosing the right one is an act of clarity — about what your data is, what questions you need to ask, and what operations need to be fast.
Key Concepts Demonstrated
| Concept | Where Used |
|---|---|
| List of dicts as portfolio | load_portfolio() |
.get() with default |
revenue_concentration(), all aggregation |
| max() with key function | most_valuable_client_ytd(), highest_retainer_client() |
| datetime and date arithmetic | days_since_invoice() |
| Filtering with list comprehension | At-risk detection, active client filter |
| Nested list in dict | services field; iterating with nested loop |
| Set comprehension for unique values | Services inventory |
| dict.get() aggregation pattern | revenue_by_product(), industry count |
| Sorting with key | All ranked outputs |
| In-place dict mutation | record_invoice() |
| Set for duplicate prevention | add_client() — check existing IDs |
Continue to the Exercises to practice these patterns yourself.