Case Study 22-2: Maya Automates Her Weekly Business Rhythm
The Situation
Maya Reyes tracks her consulting business carefully. She has a project tracker, an invoice system, and a client pipeline document — all built over the previous chapters. What she does not have is a consistent rhythm of actually reviewing them.
She does the review when she remembers. Which means she does it in three-week bursts separated by periods of benign neglect. The invoice she sent in April and forgot to follow up on until June was the result of one of those neglect periods. The client she almost lost because an invoice hit their accounts payable during their audit freeze — when five days of follow-up would have gotten her paid — was another.
Maya decides to automate her business review cadence the same way a good manager would systemize a team's meeting rhythm: not because it's impossible to do it manually, but because "doing it manually" relies on her own discipline in the middle of a busy consulting schedule, and discipline is not a substitute for a system.
What Maya Builds
Two automated pipelines:
Pipeline 1: Daily Morning Invoice Check - Runs every weekday at 8:00 AM - Checks all open invoices for overdue status - Flags any invoice that has been unpaid more than 30 days - Sends Maya a brief email if there are overdue invoices - Logs to a file so she has a running history of what was checked and when
Pipeline 2: Friday Weekly Business Health Report - Runs every Friday at 5:00 PM - Pulls revenue data from Maya's invoices CSV - Calculates utilization rate (billable hours / total available hours) - Summarizes active pipeline - Generates a brief report and emails it to herself - Includes a "look back" section: same week last year and last quarter
Both pipelines write to a log file. Both handle errors gracefully. Both run whether Maya's laptop is open or not.
The Implementation
Pipeline 1: Daily Invoice Check
"""
invoice_monitor.py
==================
Daily invoice overdue checker for Maya Reyes Consulting.
Runs every weekday morning at 8:00 AM.
Requirements:
pip install schedule openpyxl python-dotenv
"""
import os
import csv
import smtplib
import logging
import logging.handlers
from pathlib import Path
from datetime import datetime, date, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from dotenv import load_dotenv
load_dotenv()
SCRIPT_DIR = Path(__file__).parent.resolve()
LOGS_DIR = SCRIPT_DIR / "logs"
LOGS_DIR.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.StreamHandler(),
logging.handlers.TimedRotatingFileHandler(
filename=str(LOGS_DIR / "invoice_monitor.log"),
when="midnight",
backupCount=90, # 3 months of daily logs
encoding="utf-8",
),
],
)
logger = logging.getLogger(__name__)
def load_invoices(invoices_path: Path) -> list[dict]:
"""
Load invoice records from Maya's invoices CSV.
Expected columns: invoice_id, client_name, invoice_date,
due_date, amount, status, paid_date
Status values: Sent, Paid, Overdue, Draft
"""
if not invoices_path.exists():
logger.warning(f"Invoice file not found: {invoices_path}")
return []
invoices = []
with open(invoices_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
invoices.append({
"invoice_id": row["invoice_id"],
"client_name": row["client_name"],
"invoice_date": datetime.strptime(row["invoice_date"], "%Y-%m-%d").date(),
"due_date": datetime.strptime(row["due_date"], "%Y-%m-%d").date(),
"amount": float(row["amount"]),
"status": row["status"],
"paid_date": (
datetime.strptime(row["paid_date"], "%Y-%m-%d").date()
if row.get("paid_date") else None
),
})
except (ValueError, KeyError) as e:
logger.warning(f"Skipping malformed invoice row: {row} — {e}")
logger.info(f"Loaded {len(invoices)} invoices from {invoices_path.name}")
return invoices
def identify_overdue_invoices(
invoices: list[dict],
overdue_threshold_days: int = 30,
) -> list[dict]:
"""
Find invoices that are past due.
An invoice is overdue if:
- Status is "Sent" (not yet paid)
- AND today is more than overdue_threshold_days past the due_date
Returns list of overdue invoices with additional calculated fields.
"""
today = date.today()
overdue = []
for invoice in invoices:
if invoice["status"].lower() not in ("sent", "overdue"):
continue
days_overdue = (today - invoice["due_date"]).days
if days_overdue > overdue_threshold_days:
overdue.append({
**invoice,
"days_overdue": days_overdue,
"urgency": (
"CRITICAL" if days_overdue > 60
else "HIGH" if days_overdue > 45
else "STANDARD"
),
})
# Sort by urgency then amount
urgency_order = {"CRITICAL": 0, "HIGH": 1, "STANDARD": 2}
overdue.sort(key=lambda x: (urgency_order[x["urgency"]], -x["amount"]))
return overdue
def send_overdue_alert(overdue_invoices: list[dict], recipient_email: str) -> bool:
"""Send an email summarizing overdue invoices."""
smtp_server = os.environ.get("SMTP_SERVER")
smtp_username = os.environ.get("SMTP_USERNAME")
smtp_password = os.environ.get("SMTP_PASSWORD")
if not all([smtp_server, smtp_username, smtp_password]):
logger.warning("SMTP not configured — printing overdue summary to log instead")
for inv in overdue_invoices:
logger.warning(
f"OVERDUE: {inv['client_name']} | "
f"${inv['amount']:,.2f} | "
f"{inv['days_overdue']} days overdue ({inv['urgency']})"
)
return False
total_overdue_amount = sum(inv["amount"] for inv in overdue_invoices)
critical_count = sum(1 for inv in overdue_invoices if inv["urgency"] == "CRITICAL")
subject = (
f"Invoice Alert: {len(overdue_invoices)} Overdue | "
f"${total_overdue_amount:,.0f} outstanding"
)
if critical_count:
subject = f"URGENT — {subject}"
body_lines = [
f"Maya Reyes Consulting — Daily Invoice Check",
f"Date: {date.today().strftime('%B %d, %Y')}",
"",
f"OVERDUE INVOICES: {len(overdue_invoices)}",
f"Total Outstanding: ${total_overdue_amount:,.2f}",
"",
"-" * 50,
]
for inv in overdue_invoices:
body_lines.extend([
f"",
f"[{inv['urgency']}] {inv['client_name']}",
f" Invoice: {inv['invoice_id']}",
f" Amount: ${inv['amount']:,.2f}",
f" Due: {inv['due_date'].strftime('%B %d, %Y')}",
f" Days Overdue: {inv['days_overdue']}",
])
body_lines.extend([
"",
"-" * 50,
"Action: Review and follow up on outstanding invoices.",
"",
"— Maya Reyes Consulting Automated Monitor",
])
message = MIMEMultipart()
message["From"] = smtp_username
message["To"] = recipient_email
message["Subject"] = subject
message.attach(MIMEText("\n".join(body_lines), "plain"))
try:
with smtplib.SMTP(smtp_server, int(os.environ.get("SMTP_PORT", "587"))) as server:
server.starttls()
server.login(smtp_username, smtp_password)
server.sendmail(smtp_username, [recipient_email], message.as_string())
logger.info(f"Overdue alert sent to {recipient_email}")
return True
except Exception as e:
logger.error(f"Failed to send overdue alert: {e}")
return False
def run_daily_invoice_check(invoices_path: str = None) -> dict:
"""
Main function for the daily invoice check job.
Returns a summary dict with counts and totals.
"""
logger.info("Daily invoice check starting...")
default_path = SCRIPT_DIR.parent / "data" / "maya_invoices.csv"
path = Path(invoices_path) if invoices_path else default_path
try:
invoices = load_invoices(path)
if not invoices:
logger.warning("No invoices loaded — check data file path")
return {"status": "no_data", "overdue_count": 0}
overdue = identify_overdue_invoices(invoices, overdue_threshold_days=30)
if overdue:
total = sum(inv["amount"] for inv in overdue)
logger.warning(
f"Found {len(overdue)} overdue invoice(s) | "
f"Total: ${total:,.2f}"
)
recipient = os.environ.get("MAYA_EMAIL", "maya@mayareyes.consulting")
send_overdue_alert(overdue, recipient)
else:
logger.info("Invoice check complete: no overdue invoices. All clear.")
return {
"status": "success",
"total_invoices_checked": len(invoices),
"overdue_count": len(overdue),
"overdue_amount": sum(inv["amount"] for inv in overdue) if overdue else 0,
}
except Exception as e:
logger.exception(f"Invoice check failed: {e}")
return {"status": "error", "error": str(e)}
Pipeline 2: Friday Business Health Report
"""
friday_health_report.py
=======================
Weekly business health report for Maya Reyes Consulting.
Runs every Friday at 5:00 PM.
Covers:
- Revenue: invoiced, collected, outstanding
- Utilization: billable hours vs available hours this week
- Pipeline summary: active prospects and estimated value
- Look-back: same week last year and same quarter last year
"""
import os
import csv
import logging
from pathlib import Path
from datetime import datetime, date, timedelta
from collections import defaultdict
logger = logging.getLogger(__name__)
SCRIPT_DIR = Path(__file__).parent.resolve()
def load_projects(projects_path: Path) -> list[dict]:
"""Load Maya's project data from maya_projects.csv."""
projects = []
with open(projects_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
projects.append({
"project_id": row["project_id"],
"client_name": row["client_name"],
"status": row["status"], # Active, Completed, On-Hold, Pipeline
"hourly_rate": float(row.get("hourly_rate", 175)),
"estimated_value": float(row.get("estimated_value", 0)),
"hours_logged": float(row.get("hours_logged", 0)),
"start_date": row.get("start_date", ""),
})
return projects
def load_invoices(invoices_path: Path) -> list[dict]:
"""Load invoice data."""
invoices = []
with open(invoices_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
invoices.append({
"invoice_id": row["invoice_id"],
"client_name": row["client_name"],
"invoice_date": datetime.strptime(row["invoice_date"], "%Y-%m-%d").date(),
"amount": float(row["amount"]),
"status": row["status"],
"paid_date": (
datetime.strptime(row["paid_date"], "%Y-%m-%d").date()
if row.get("paid_date") else None
),
})
except (ValueError, KeyError):
continue
return invoices
def calculate_revenue_metrics(invoices: list[dict]) -> dict:
"""
Calculate revenue metrics from invoice data.
Returns metrics for:
- Current month
- Year to date (YTD)
- Trailing 12 months
- Outstanding (sent but unpaid)
"""
today = date.today()
month_start = today.replace(day=1)
year_start = today.replace(month=1, day=1)
twelve_months_ago = today - timedelta(days=365)
monthly_invoiced = 0.0
monthly_collected = 0.0
ytd_invoiced = 0.0
ytd_collected = 0.0
ttm_invoiced = 0.0
ttm_collected = 0.0
outstanding_amount = 0.0
outstanding_count = 0
for inv in invoices:
amount = inv["amount"]
inv_date = inv["invoice_date"]
is_paid = inv["status"].lower() == "paid"
# Current month
if inv_date >= month_start:
monthly_invoiced += amount
if is_paid:
monthly_collected += amount
# Year to date
if inv_date >= year_start:
ytd_invoiced += amount
if is_paid:
ytd_collected += amount
# Trailing 12 months
if inv_date >= twelve_months_ago:
ttm_invoiced += amount
if is_paid:
ttm_collected += amount
# Outstanding
if inv["status"].lower() in ("sent", "overdue"):
outstanding_amount += amount
outstanding_count += 1
return {
"monthly_invoiced": monthly_invoiced,
"monthly_collected": monthly_collected,
"ytd_invoiced": ytd_invoiced,
"ytd_collected": ytd_collected,
"ttm_invoiced": ttm_invoiced,
"ttm_collected": ttm_collected,
"outstanding_amount": outstanding_amount,
"outstanding_count": outstanding_count,
"collection_rate_ytd": (
(ytd_collected / ytd_invoiced * 100) if ytd_invoiced > 0 else 0
),
}
def calculate_utilization(projects: list[dict], available_hours_per_week: float = 32) -> dict:
"""
Estimate Maya's utilization for the current week.
Utilization = billable hours worked / available hours.
Target: 75% utilization (24 of 32 available hours).
"""
active_projects = [p for p in projects if p["status"] == "Active"]
total_hours_active = sum(p["hours_logged"] for p in active_projects)
# Estimate this week's billable hours (simplified: divide total logged by weeks active)
# In a real implementation, Maya would have a time-tracking CSV with weekly entries
# For now, we estimate based on active project count and typical engagement
estimated_weekly_hours = min(
len(active_projects) * 8, # Rough estimate: 8 hrs/project/week
available_hours_per_week
)
utilization_pct = (estimated_weekly_hours / available_hours_per_week * 100)
return {
"active_project_count": len(active_projects),
"estimated_weekly_billable_hours": estimated_weekly_hours,
"available_hours_per_week": available_hours_per_week,
"utilization_pct": round(utilization_pct, 1),
"at_target": utilization_pct >= 70, # 70% threshold for "healthy"
"estimated_weekly_revenue": estimated_weekly_hours * 175, # $175/hr
}
def calculate_pipeline_metrics(projects: list[dict]) -> dict:
"""Summarize Maya's sales pipeline."""
pipeline_projects = [p for p in projects if p["status"] == "Pipeline"]
on_hold = [p for p in projects if p["status"] == "On-Hold"]
pipeline_value = sum(p["estimated_value"] for p in pipeline_projects)
return {
"pipeline_count": len(pipeline_projects),
"pipeline_value": pipeline_value,
"pipeline_projects": [
{
"client": p["client_name"],
"estimated_value": p["estimated_value"],
}
for p in sorted(pipeline_projects, key=lambda x: -x["estimated_value"])
],
"on_hold_count": len(on_hold),
"on_hold_value": sum(p["estimated_value"] for p in on_hold),
}
def generate_health_report_text(
revenue: dict,
utilization: dict,
pipeline: dict,
output_path: Path,
) -> None:
"""Write the weekly health report to a text file."""
today = date.today()
week_number = today.isocalendar()[1]
with open(output_path, "w", encoding="utf-8") as f:
f.write("MAYA REYES CONSULTING\n")
f.write("WEEKLY BUSINESS HEALTH REPORT\n")
f.write(f"Week {week_number} — {today.strftime('%B %d, %Y')}\n")
f.write("=" * 55 + "\n\n")
# Revenue section
f.write("REVENUE\n")
f.write("-" * 45 + "\n")
f.write(f" This Month Invoiced: ${revenue['monthly_invoiced']:>12,.2f}\n")
f.write(f" This Month Collected: ${revenue['monthly_collected']:>12,.2f}\n")
f.write(f" YTD Invoiced: ${revenue['ytd_invoiced']:>12,.2f}\n")
f.write(f" YTD Collected: ${revenue['ytd_collected']:>12,.2f}\n")
f.write(f" Collection Rate (YTD): {revenue['collection_rate_ytd']:>11.1f}%\n")
f.write(f" Outstanding: ${revenue['outstanding_amount']:>12,.2f} "
f"({revenue['outstanding_count']} invoices)\n\n")
# Trailing 12 months
f.write(f" Trailing 12 Months: ${revenue['ttm_invoiced']:>12,.2f} invoiced\n")
monthly_run_rate = revenue['ttm_invoiced'] / 12
annual_projection = revenue['ytd_invoiced'] * (12 / today.month)
f.write(f" Monthly Run Rate: ${monthly_run_rate:>12,.2f}\n")
f.write(f" Annual Projection: ${annual_projection:>12,.2f}\n\n")
# Utilization section
f.write("CAPACITY & UTILIZATION\n")
f.write("-" * 45 + "\n")
f.write(f" Active Projects: {utilization['active_project_count']:>12}\n")
f.write(
f" Est. Billable Hrs/Wk: {utilization['estimated_weekly_billable_hours']:>12.1f}"
f" / {utilization['available_hours_per_week']:.0f} available\n"
)
f.write(f" Utilization Rate: {utilization['utilization_pct']:>11.1f}%")
if utilization["at_target"]:
f.write(" (ON TARGET)\n")
else:
f.write(" (BELOW TARGET — consider adding projects)\n")
f.write(
f" Est. Weekly Revenue: ${utilization['estimated_weekly_revenue']:>12,.2f}\n\n"
)
# Pipeline section
f.write("PIPELINE\n")
f.write("-" * 45 + "\n")
f.write(f" Prospects in Pipeline: {pipeline['pipeline_count']:>12}\n")
f.write(f" Total Pipeline Value: ${pipeline['pipeline_value']:>12,.2f}\n")
if pipeline["pipeline_projects"]:
f.write("\n Pipeline Detail:\n")
for proj in pipeline["pipeline_projects"][:5]:
f.write(
f" {proj['client']:<30} "
f"${proj['estimated_value']:>10,.2f}\n"
)
if pipeline["on_hold_count"]:
f.write(
f"\n On-Hold: {pipeline['on_hold_count']} project(s) | "
f"${pipeline['on_hold_value']:,.2f} value\n"
)
# Health indicators
f.write("\nBUSINESS HEALTH INDICATORS\n")
f.write("-" * 45 + "\n")
# Revenue trend
if revenue["monthly_invoiced"] >= monthly_run_rate:
f.write(" Revenue: ABOVE monthly run rate\n")
else:
shortfall = monthly_run_rate - revenue["monthly_invoiced"]
f.write(f" Revenue: ${shortfall:,.0f} below monthly run rate\n")
# Collection
if revenue["collection_rate_ytd"] >= 95:
f.write(" Collections: Excellent (≥95%)\n")
elif revenue["collection_rate_ytd"] >= 85:
f.write(" Collections: Good (85-95%)\n")
else:
f.write(
f" Collections: ATTENTION NEEDED ({revenue['collection_rate_ytd']:.1f}% — "
f"${revenue['outstanding_amount']:,.0f} outstanding)\n"
)
# Pipeline health
months_of_runway = pipeline["pipeline_value"] / monthly_run_rate if monthly_run_rate else 0
if months_of_runway >= 3:
f.write(f" Pipeline: Healthy ({months_of_runway:.1f} months of runway)\n")
else:
f.write(
f" Pipeline: NEEDS ATTENTION "
f"({months_of_runway:.1f} months of runway — add new prospects)\n"
)
f.write(f"\n{'=' * 55}\n")
f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("Maya Reyes Consulting — automated weekly report\n")
def run_friday_health_report() -> bool:
"""
Main entry point for the Friday health report pipeline.
Returns True on success, False on failure.
"""
logger.info("Friday business health report starting...")
data_dir = SCRIPT_DIR.parent / "data"
reports_dir = SCRIPT_DIR.parent / "reports"
reports_dir.mkdir(exist_ok=True)
try:
projects_path = data_dir / "maya_projects.csv"
invoices_path = data_dir / "maya_invoices.csv"
if not projects_path.exists() or not invoices_path.exists():
logger.error(
f"Data files not found. Expected:\n"
f" {projects_path}\n"
f" {invoices_path}"
)
return False
projects = load_projects(projects_path)
invoices = load_invoices(invoices_path)
revenue = calculate_revenue_metrics(invoices)
utilization = calculate_utilization(projects)
pipeline = calculate_pipeline_metrics(projects)
today = date.today().strftime("%Y-%m-%d")
report_path = reports_dir / f"maya_health_report_{today}.txt"
generate_health_report_text(revenue, utilization, pipeline, report_path)
logger.info(f"Health report generated: {report_path}")
# Send to Maya
from invoice_monitor import send_overdue_alert # Reuse email infrastructure
# Simple email send using shared SMTP config
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
smtp_server = os.environ.get("SMTP_SERVER")
smtp_username = os.environ.get("SMTP_USERNAME")
smtp_password = os.environ.get("SMTP_PASSWORD")
recipient = os.environ.get("MAYA_EMAIL", "maya@mayareyes.consulting")
if not all([smtp_server, smtp_username, smtp_password]):
logger.info(f"SMTP not configured — report saved to {report_path}")
return True
message = MIMEMultipart()
message["From"] = smtp_username
message["To"] = recipient
message["Subject"] = f"Weekly Business Health Report — {date.today().strftime('%B %d')}"
with open(report_path, "r") as f:
body = f.read()
message.attach(MIMEText(body, "plain"))
with open(report_path, "rb") as f:
attachment = MIMEBase("application", "octet-stream")
attachment.set_payload(f.read())
encoders.encode_base64(attachment)
attachment.add_header(
"Content-Disposition",
f"attachment; filename={report_path.name}",
)
message.attach(attachment)
with smtplib.SMTP(smtp_server, int(os.environ.get("SMTP_PORT", "587"))) as server:
server.starttls()
server.login(smtp_username, smtp_password)
server.sendmail(smtp_username, [recipient], message.as_string())
logger.info(f"Health report emailed to {recipient}")
return True
except Exception as e:
logger.exception(f"Friday health report failed: {e}")
return False
The Scheduler
"""
maya_scheduler.py
=================
Runs both of Maya's automated pipelines on schedule.
Start this script on Monday morning and let it run.
"""
import schedule
import time
import logging
import logging.handlers
import sys
from pathlib import Path
from datetime import datetime
SCRIPT_DIR = Path(__file__).parent.resolve()
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.StreamHandler(sys.stdout),
logging.handlers.TimedRotatingFileHandler(
filename=str(SCRIPT_DIR / "logs" / "scheduler.log"),
when="midnight",
backupCount=30,
encoding="utf-8",
),
],
)
logger = logging.getLogger(__name__)
from invoice_monitor import run_daily_invoice_check
from friday_health_report import run_friday_health_report
def invoice_check_job():
"""Scheduled wrapper for daily invoice check."""
result = run_daily_invoice_check()
logger.info(
f"Invoice check complete | "
f"status={result['status']} | "
f"overdue={result.get('overdue_count', 0)}"
)
def health_report_job():
"""Scheduled wrapper for Friday health report."""
success = run_friday_health_report()
logger.info(f"Health report job complete | success={success}")
# Daily invoice check — every weekday at 8:00 AM
schedule.every().monday.at("08:00").do(invoice_check_job)
schedule.every().tuesday.at("08:00").do(invoice_check_job)
schedule.every().wednesday.at("08:00").do(invoice_check_job)
schedule.every().thursday.at("08:00").do(invoice_check_job)
schedule.every().friday.at("08:00").do(invoice_check_job)
# Weekly health report — every Friday at 5:00 PM
schedule.every().friday.at("17:00").do(health_report_job)
logger.info("Maya Reyes Consulting Scheduler — starting")
logger.info(f"Registered {len(schedule.jobs)} jobs:")
for job in schedule.jobs:
logger.info(f" {job}")
while True:
schedule.run_pending()
time.sleep(60)
The Setup
Maya runs maya_scheduler.py in a terminal window on her MacBook each Monday morning. To keep it running when the lid closes, she uses a simple macOS LaunchAgent (the macOS equivalent of a Windows scheduled task):
<!-- ~/Library/LaunchAgents/com.mayareyes.scheduler.plist -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.mayareyes.scheduler</string>
<key>ProgramArguments</key>
<array>
<string>/Users/maya/.venv/bin/python</string>
<string>/Users/maya/consulting-tools/maya_scheduler.py</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/maya/consulting-tools/logs/scheduler_stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/maya/consulting-tools/logs/scheduler_stderr.log</string>
<key>WorkingDirectory</key>
<string>/Users/maya/consulting-tools</string>
</dict>
</plist>
Enable it with:
launchctl load ~/Library/LaunchAgents/com.mayareyes.scheduler.plist
The LaunchAgent starts at login and restarts automatically if the process crashes.
The Result
After two weeks running, Maya notices a shift in how she relates to her business data.
Before: she would think about her invoices when she felt anxious about cash flow, which usually meant she was already behind on follow-up. She would think about utilization when a Friday arrived and she felt like she'd been busy but couldn't point to specific billable work.
After: the invoice check runs at 8 AM. If there's nothing overdue, she never sees it. If there is something overdue, she sees it immediately and can follow up that day rather than whenever anxiety eventually forced the issue.
The Friday report arrives at 5 PM as she's wrapping up. Two minutes of reading tells her whether she hit her utilization target, whether her pipeline is healthy, and whether she needs to pursue new business. The decision to go into the weekend without worry or with action items takes two minutes instead of twenty.
The first month the system ran, Maya caught an invoice that had slipped 38 days past due — a client who had gone quiet and moved on. She followed up Monday morning, recovered the payment within the week, and noted in her client file that this account had longer-than-usual payment cycles.
The system cost her one Saturday afternoon to build. It has returned that time every month since.
Discussion Questions
-
Maya chose to run
scheduleon her personal laptop rather than Windows Task Scheduler or a cloud service. What are the failure modes of this approach (when might the automation not run as expected)? What would you recommend to make it more reliable? -
The Friday health report calculates an "annual projection" by multiplying year-to-date revenue by
12 / today.month. What are the limitations of this formula? When would it give misleading results? -
Maya's utilization calculation is an estimate based on the number of active projects, not actual time tracking. How would you improve this? What data would Maya need to collect, and how would the calculation change?
-
The invoice monitor sends an email alert when invoices are overdue. What additional automation could Maya build on top of this system? (Think about what happens after she gets the alert — are there actions that could themselves be automated?)
-
The
schedulelibrary requires a long-running process. If Maya's laptop battery dies overnight, the invoice check won't run the next morning until she opens her laptop again. Sketch out the design of a solution that would run the check even if her laptop is off.