Case Study 31-2: Maya Finds Out Where Her Best Clients Actually Come From

Character: Maya Reyes (freelance consultant) Setting: Maya's home office, a Friday afternoon in late October


The Setup

Maya Reyes had been running her consulting practice for three years, and she had a confession: she had spent almost no time thinking analytically about how she marketed herself.

She had a LinkedIn presence. She went to two or three speaking engagements a year. She had a referral network of former colleagues and satisfied clients who occasionally sent work her way. And once, eighteen months ago, she had run a small paid LinkedIn ad campaign for two months before canceling it because it felt expensive and she could not tell if it was working.

She tracked her projects in maya_projects.csv and her invoices in maya_invoices.xlsx — both of which she had built out in earlier chapters. But she had never systematically asked: which of these marketing activities is actually generating my best business?

It was a rainy Friday afternoon, her current projects were under control, and she decided to find out.


The Data

Over the past three years, Maya had acquired 34 clients. For each project in her tracking system, she had a field called acquisition_channel — she had added it six months into her practice when a mentor told her to start tracking where clients came from.

She also had, for each client, a total revenue figure and a subjective (but honest) assessment of client quality on a 1–5 scale, which she called working_relationship_score. High scores meant clients who paid on time, respected her time, and generated referrals. Low scores meant clients who renegotiated scope, paid late, and made her dread Monday mornings.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import seaborn as sns

# Load client acquisition data
clients_df = pd.read_csv("maya_clients.csv")
print(clients_df.head())
print(f"\nTotal clients: {len(clients_df)}")
print(f"Acquisition channels: {clients_df['acquisition_channel'].unique()}")

Output:

   client_id  acquisition_channel  acquisition_date  total_revenue  \
0        C001              referral       2022-03-15        18400.0
1        C002              linkedin       2022-04-02         5200.0
2        C003              speaking       2022-05-18        22600.0
...

   working_relationship_score  repeat_client  months_active
0                           5           True            28
1                           2          False             2
2                           4           True            24

Five acquisition channels: - referral — direct referrals from existing clients or colleagues - linkedin — inbound from her LinkedIn content or profile - speaking — clients who met her at a conference or speaking event - linkedin_ads — the paid LinkedIn campaign she ran 18 months ago - cold_outreach — clients she had proactively contacted


Step 1: How Many Clients per Channel?

channel_counts = clients_df["acquisition_channel"].value_counts()
print("Clients by channel:")
print(channel_counts)

Output:

referral          14
linkedin          10
linkedin_ads       4
speaking           4
cold_outreach      2

Most clients came from referrals or LinkedIn. But raw count was the wrong metric — Maya spent very different amounts of time and money on each channel.


Step 2: Building the CAC Estimate

Maya had never formally tracked her marketing spend, so she reconstructed it from memory and her calendar:

# Estimated time and money invested per channel over 3 years
# Maya values her own time at $175/hour (her consulting rate)
channel_costs = pd.DataFrame({
    "acquisition_channel": ["referral", "linkedin", "speaking", "linkedin_ads", "cold_outreach"],
    "time_hours": [12, 80, 45, 8, 18],       # hours spent on marketing activities
    "cash_spent": [0, 0, 3200, 1850, 180],    # travel, ads, registration fees
    "description": [
        "Staying in touch with network, occasional coffee meetings",
        "Writing posts, commenting, profile maintenance",
        "Event fees, travel, preparation, two talks per year",
        "Ad spend for 2-month campaign",
        "Research and outreach emails",
    ],
})

hourly_rate = 175  # Maya's time is worth her consulting rate
channel_costs["time_cost"] = channel_costs["time_hours"] * hourly_rate
channel_costs["total_cost"] = channel_costs["time_cost"] + channel_costs["cash_spent"]

# Merge with client counts
channel_summary = clients_df.groupby("acquisition_channel").agg(
    clients=("client_id", "count"),
    total_revenue=("total_revenue", "sum"),
    avg_revenue=("total_revenue", "mean"),
    avg_relationship_score=("working_relationship_score", "mean"),
    repeat_clients=("repeat_client", "sum"),
    avg_months_active=("months_active", "mean"),
).reset_index()

channel_summary = channel_summary.merge(channel_costs, on="acquisition_channel", how="left")
channel_summary["cac"] = channel_summary["total_cost"] / channel_summary["clients"]
channel_summary["ltv"] = channel_summary["avg_revenue"]  # using total revenue as LTV proxy
channel_summary["ltv_cac_ratio"] = channel_summary["ltv"] / channel_summary["cac"]
channel_summary["repeat_rate"] = channel_summary["repeat_clients"] / channel_summary["clients"]

print(channel_summary[["acquisition_channel", "clients", "cac", "ltv", "ltv_cac_ratio",
                         "avg_relationship_score", "repeat_rate"]].to_string(index=False))

Output (formatted):

 acquisition_channel  clients    cac      ltv  ltv_cac_ratio  avg_rel_score  repeat_rate
            referral       14  150.0  16,420        109.5            4.6         0.71
            linkedin       10  1,400   8,240          5.9            3.1         0.30
            speaking        4  1,225  19,800         16.2            4.3         0.75
        linkedin_ads        4  512.5   4,875          9.5            2.4         0.00
       cold_outreach        2  1,575   5,800          3.7            2.8         0.00

Maya stared at the referral row for a long moment.

A CAC of $150. An LTV of $16,420. An LTV:CAC ratio of 109.5.

One hundred and nine to one.


Step 3: The Full Picture

# Revenue share by channel
channel_summary["revenue_share"] = (
    channel_summary["total_revenue"] / channel_summary["total_revenue"].sum() * 100
)
channel_summary["cost_share"] = (
    channel_summary["total_cost"] / channel_summary["total_cost"].sum() * 100
)

print("\nRevenue share vs. cost share by channel:")
print(channel_summary[["acquisition_channel", "revenue_share", "cost_share"]].to_string(index=False))

Output:

acquisition_channel  revenue_share  cost_share
           referral          55.8         2.8
           linkedin          20.0        18.2
           speaking          19.1        37.1
       linkedin_ads           4.7        21.6
      cold_outreach           0.4        20.3

Referrals represented 56% of Maya's revenue but only 2.8% of her marketing investment. Cold outreach and LinkedIn ads together consumed 42% of her marketing budget and produced 5.1% of her revenue.

Speaking engagements were interesting: 19% of revenue against 37% of cost puts the LTV:CAC at 16.2 — low compared to referrals, but speaking engagements had another benefit she had not quantified: three of her four speaking-sourced clients had themselves referred additional clients, which is how referral network compounding works.


Step 4: Client Quality Beyond Revenue

# Compare relationship scores and client stickiness
quality_comparison = channel_summary.sort_values("avg_relationship_score", ascending=False)[
    ["acquisition_channel", "avg_relationship_score", "repeat_rate", "avg_months_active"]
]

fig, axes = plt.subplots(1, 3, figsize=(15, 5))
fig.suptitle("Maya Reyes Consulting — Marketing Channel Quality Analysis", fontsize=13, fontweight="bold")

channels_ordered = quality_comparison["acquisition_channel"].tolist()
colors = ["#2E86AB" if c == "referral" else
          "#A23B72" if c == "speaking" else
          "#F18F01" if c == "linkedin" else
          "#C73E1D"
          for c in channels_ordered]

# Panel 1: Working Relationship Score
axes[0].barh(channels_ordered, quality_comparison["avg_relationship_score"], color=colors)
axes[0].set_title("Avg. Working Relationship Score\n(1=difficult, 5=excellent)", fontsize=10)
axes[0].set_xlim(0, 5.5)
axes[0].axvline(x=4.0, color="green", linestyle="--", alpha=0.5, label="Target score")
for i, (score, channel) in enumerate(zip(quality_comparison["avg_relationship_score"], channels_ordered)):
    axes[0].text(score + 0.05, i, f"{score:.1f}", va="center", fontsize=10)

# Panel 2: Repeat Client Rate
axes[1].barh(channels_ordered, [r * 100 for r in quality_comparison["repeat_rate"]], color=colors)
axes[1].set_title("Repeat Client Rate (%)", fontsize=10)
axes[1].set_xlim(0, 100)
for i, (rate, channel) in enumerate(zip(quality_comparison["repeat_rate"], channels_ordered)):
    axes[1].text(rate * 100 + 1, i, f"{rate:.0%}", va="center", fontsize=10)

# Panel 3: LTV:CAC Ratio (log scale)
ltv_cac = channel_summary.set_index("acquisition_channel").loc[channels_ordered, "ltv_cac_ratio"]
axes[2].barh(channels_ordered, ltv_cac.values, color=colors)
axes[2].set_title("LTV:CAC Ratio\n(higher = more efficient)", fontsize=10)
axes[2].axvline(x=3, color="orange", linestyle="--", alpha=0.7, label="3:1 minimum")
for i, (ratio, channel) in enumerate(zip(ltv_cac.values, channels_ordered)):
    axes[2].text(min(ratio + 1, 115), i, f"{ratio:.1f}×", va="center", fontsize=9)

plt.tight_layout()
plt.savefig("maya_channel_analysis.png", dpi=150, bbox_inches="tight")
plt.show()

Step 5: What Was She Actually Getting from LinkedIn Ads?

The LinkedIn ad campaign bothered Maya the most. She had spent $1,850 in cash plus 8 hours of her time to acquire four clients. Those clients had a 2.4 average relationship score — the lowest of any channel — zero repeat rate, and had generated $19,500 total.

She pulled the notes she had written about each:

  • Client A: A startup that ran out of funding before paying the final invoice (Maya had written off $2,800).
  • Client B: A company that requested three major scope changes and argued about the final bill.
  • Client C: The one decent engagement — a clean, well-defined project.
  • Client D: A company that hired her for a small project and never responded to follow-up about expanding the work.

The conclusion was stark. The LinkedIn ads had attracted clients who treated her like a vendor they found via a Google search, because that was essentially what she was to them. The premium that justified her $175/hour rate is built on trust and reputation — neither of which an ad can convey in 300 characters.


Step 6: The Referral Network Effect

Maya's referral clients had a 71% repeat rate and a 4.6 relationship score. She wanted to understand the compounding effect.

# Trace the referral tree: which original clients generated referrals?
referral_clients = clients_df[clients_df["acquisition_channel"] == "referral"].copy()

# Which clients referred others? (tracked in original_referrer column)
referral_sources = clients_df["original_referrer"].value_counts()
top_referrers = referral_sources[referral_sources > 0].head(10)

print("Clients who have referred new business:")
for client_id, referral_count in top_referrers.items():
    client_rev = clients_df[clients_df["client_id"] == client_id]["total_revenue"].values[0]
    referred_rev = clients_df[clients_df["original_referrer"] == client_id]["total_revenue"].sum()
    print(f"  {client_id}: ${client_rev:,.0f} direct + ${referred_rev:,.0f} referred = ${client_rev + referred_rev:,.0f} total ecosystem value")

Three clients alone had each referred two or more new clients. When Maya computed their "ecosystem value" — their own revenue plus the revenue from clients they had referred — the numbers were striking. One client who had paid her $22,000 had referred clients who collectively paid an additional $48,000. That original client's ecosystem value was $70,000.

She had never thought about clients this way before.


The Strategic Pivot

Maya spent Saturday morning writing a one-page strategy document for her own practice. It was simple:

What to stop doing: - LinkedIn paid advertising (immediately) - Cold outreach (gradually, redirecting that time to referral nurturing)

What to do more of: - Deliberate referral cultivation: reaching out to past clients quarterly, not just when she needed work - Speaking engagements: not for direct lead generation, but because speaking clients become referral sources - LinkedIn organic content: it was cost-effective at an LTV:CAC of 5.9, and content positioned her as a thought leader that potential referrers would feel comfortable recommending

What to track going forward: - Track the source of every new client inquiry, even informally - Track which clients refer others — and thank them explicitly - Calculate LTV:CAC by channel every six months

The total time for this analysis: four hours. The strategic clarity it produced was worth more than the 80 hours she had spent on LinkedIn the previous year.


The Insight

Maya's analysis revealed something that surprises a lot of consultants and small business owners when they run the numbers: the "scalable" paid marketing channels often have the worst unit economics compared to relationship-based channels.

This is not because paid channels do not work. It is because Maya's specific business — high-trust, high-fee, repeat-engagement consulting — depends on something that paid advertising is poorly designed to convey: the feeling that you already know her, or know someone who does.

The math confirmed what her intuition had been telling her for two years. She just had not done the calculation.


Key Python Techniques Used

  • Reconstructing CAC from time and cash cost estimates
  • Multi-metric grouped comparison with .agg()
  • LTV:CAC ratio calculation and interpretation
  • Revenue share vs. cost share efficiency analysis
  • Qualitative scoring combined with quantitative analysis
  • Network/referral tree analysis with value_counts() and merges
  • Multi-panel visualization for channel comparison