Case Study 6.2: Maya's Rate Calculator — The Function Version

The Situation

In Chapter 3, Maya built a rate calculator that computed invoice amounts for her consulting work. It worked — but it was a flat script with all the logic inline. If she wanted to calculate rates for two clients, she had to change the variables at the top and run the script twice.

Now she rebuilds it using functions.

The Refactored Rate Calculator

```python """ maya_rate_calculator.py — Version 2 (Functions) Chapter 6: Functions — Building Reusable Business Logic

Maya Reyes Consulting — Invoice calculation with functions. All pricing rules live here as importable functions. """

── CONSTANTS ─────────────────────────────────────────────────────────────────

HOURLY_RATES = { "consulting": 175.00, "training": 200.00, "strategy": 250.00, }

Subcontracting: paid to sub / billed to client

SUBCONTRACT_RATES = { "cost": 95.00, "billing": 145.00, }

Volume discounts: (hours_threshold, discount_rate)

VOLUME_DISCOUNTS = [ (20, 0.10), # 20+ hours: 10% off (10, 0.05), # 10–19 hours: 5% off (0, 0.00), # Under 10 hours: no discount ]

── CORE FUNCTIONS ────────────────────────────────────────────────────────────

def get_hourly_rate(project_type): """ Return hourly rate for a project type.

Args:
    project_type: One of "consulting", "training", "strategy", "subcontract"

Returns:
    Hourly billing rate (float), or 0.0 for unknown types
"""
if project_type == "subcontract":
    return SUBCONTRACT_RATES["billing"]
return HOURLY_RATES.get(project_type, 0.0)

def get_volume_discount_rate(hours): """ Return volume discount rate based on hours billed this month.

Args:
    hours: Total billable hours for the client this month

Returns:
    Discount rate as decimal
"""
for threshold, rate in VOLUME_DISCOUNTS:
    if hours >= threshold:
        return rate
return 0.0

def is_retainer_client(client_name, retainer_clients): """ Check if a client is on a retainer agreement.

Retainer clients get an 8% discount.

Args:
    client_name: Name to look up
    retainer_clients: List of retainer client names

Returns:
    bool
"""
return client_name in retainer_clients

def calculate_invoice(client_name, project_type, hours, retainer_clients=None, extra_expenses=0.0): """ Calculate a complete invoice for one client.

Args:
    client_name: Client name (for display and retainer check)
    project_type: "consulting", "training", "strategy", or "subcontract"
    hours: Billable hours this invoice period
    retainer_clients: List of retainer client names (default: empty)
    extra_expenses: Reimbursable expenses (not discounted)

Returns:
    dict with full invoice breakdown
"""
if retainer_clients is None:
    retainer_clients = []

# Billing rate
hourly_rate = get_hourly_rate(project_type)
base_amount = hours * hourly_rate

# Volume discount
volume_rate = get_volume_discount_rate(hours)
volume_discount = base_amount * volume_rate
after_volume = base_amount - volume_discount

# Retainer discount (stacks with volume discount)
retainer = is_retainer_client(client_name, retainer_clients)
retainer_rate = 0.08 if retainer else 0.0
retainer_discount = after_volume * retainer_rate
after_retainer = after_volume - retainer_discount

# Add expenses (no discount on expenses)
invoice_total = after_retainer + extra_expenses

# Subcontract cost (for margin tracking)
sub_cost = hours * SUBCONTRACT_RATES["cost"] if project_type == "subcontract" else 0.0
gross_profit = invoice_total - sub_cost - extra_expenses

return {
    "client_name": client_name,
    "project_type": project_type,
    "hours": hours,
    "hourly_rate": hourly_rate,
    "base_amount": base_amount,
    "volume_discount_rate": volume_rate,
    "volume_discount": volume_discount,
    "retainer_discount_rate": retainer_rate,
    "retainer_discount": retainer_discount,
    "fees_subtotal": after_retainer,
    "expenses": extra_expenses,
    "invoice_total": invoice_total,
    "subcontract_cost": sub_cost,
    "gross_profit": gross_profit,
    "gross_margin": gross_profit / invoice_total if invoice_total > 0 else 0,
}

def format_invoice(result): """Format an invoice result dict as a human-readable string.""" lines = [ "=" * 50, f"INVOICE — {result['client_name'].upper()}", f"Project type: {result['project_type'].title()}", "=" * 50, f" Hours: {result['hours']:>8.1f}", f" Rate: ${result['hourly_rate']:>7.2f}/hr", f" Base fees: ${result['base_amount']:>8,.2f}", ]

if result["volume_discount"] > 0:
    lines.append(
        f"  Volume discount ({result['volume_discount_rate']:.0%}): "
        f"-${result['volume_discount']:>7,.2f}"
    )

if result["retainer_discount"] > 0:
    lines.append(
        f"  Retainer discount ({result['retainer_discount_rate']:.0%}): "
        f"-${result['retainer_discount']:>7,.2f}"
    )

lines.extend([
    f"  Fees subtotal:      ${result['fees_subtotal']:>8,.2f}",
])

if result["expenses"] > 0:
    lines.append(f"  Expenses:           ${result['expenses']:>8,.2f}")

lines.extend([
    "-" * 50,
    f"  INVOICE TOTAL:      ${result['invoice_total']:>8,.2f}",
    "=" * 50,
])

if result["subcontract_cost"] > 0:
    lines.extend([
        f"  [Internal] Sub cost: ${result['subcontract_cost']:>7,.2f}",
        f"  [Internal] GP:       ${result['gross_profit']:>7,.2f} "
        f"({result['gross_margin']:.1%})",
    ])

return "\n".join(lines)

── DEMO ──────────────────────────────────────────────────────────────────────

if name == "main": retainer_clients = ["Hartwell Financial", "Meridian Group"]

# Process multiple clients
clients = [
    ("Hartwell Financial", "consulting", 22.5, 0),
    ("Pacific Ridge Partners", "training", 8.0, 340.00),
    ("Meridian Group", "consulting", 35.0, 0),
    ("Apex Tech", "subcontract", 16.0, 0),
]

monthly_total = 0
for name, ptype, hrs, expenses in clients:
    result = calculate_invoice(
        client_name=name,
        project_type=ptype,
        hours=hrs,
        retainer_clients=retainer_clients,
        extra_expenses=expenses,
    )
    print(format_invoice(result))
    print()
    monthly_total += result["invoice_total"]

print(f"MONTHLY TOTAL: ${monthly_total:,.2f}")