You have the skills. This module turns them into proof and a business: one real engagement, measured honestly and written up, and a plan to sell the next ten.
The capstone is your first public proof. Pick the client for what it lets you show, not for how exciting the technology is. A friendly client is fine. A client you cannot measure is not.
| Criterion | Test question | Red flag |
|---|---|---|
| Access to the owner | Can the owner give you 30 minutes a week and approve decisions the same week? | Everything goes through someone who cannot decide |
| Measurable process | Can you count something today (minutes, tickets, errors, response time) without new tools? | "It should feel better" is the only goal |
| Data available | Can you read real examples (emails, orders, forms) legally and without a long IT project? | Data sits in one person's head or on paper |
| Low risk | If Claude is wrong, is the output a draft that a person checks, not a payment or a legal notice? | The first idea is to auto-send to customers |
Your three friendly candidates, as hypotheses to check in a first call. None of this is known yet; it is what you need to find out.
| Candidate | Likely process to improve | Likely measure | Watch out for |
|---|---|---|---|
| Bat Yam garage (fuel injector cleaning) | Enquiries by phone and message in Hebrew and Russian; quote and booking follow-up; explaining test results to customers | Time to reply to an enquiry, enquiries that become bookings | Low volume: a small sample can show nothing. Enquiries may not be logged anywhere |
| Ceramics studio | Replying to enquiries, workshop sign-ups, product texts in several languages | Admin hours per week, time to reply | Seasonal sales; very small data; hard to prove business impact |
| Small logistics firm | Shipment-status emails, customer updates, exception handling | Time to first reply, emails per clerk per day, errors found in review | Customer data privacy; a busy owner. Best match to your target niche |
Plan eight weeks. The pilot itself is two weeks; the rest is what makes it a proper engagement. Collect the baseline during the audit, before the pilot starts, using the same method you will use later.
| Week | Phase | What you do | Output | Gate |
|---|---|---|---|---|
| Before W1 | Agreement | One-page scope: goal, metric, data you may use, who approves, what may be published, end date | Signed scope (use the Module 11 proposal template) | Owner signs |
| W1 | Audit | Shadow the process, collect 20 to 50 real examples, start logging the baseline, check data and privacy terms | Process map, baseline log started, risk list | Metric and method agreed |
| W2 | Architecture | Compare options, pick one, build a small test set with correct answers, set the pass mark | ADR, test set, pilot plan | Owner signs the ADR |
| W3 to W4 | Pilot build and run | Build the smallest working version, test against the test set, run with real work and a person reviewing every output | Working pilot, daily log of corrections | Pass mark met before real use; stop rule agreed |
| W5 | Handover | Write the pack, record the training video, train two people, agree support terms | Handover pack | Client staff run it without you once |
| W6 to W8 | Measurement | Keep logging with the same method, review errors weekly, compare with baseline | Results sheet with n, period, caveats | Owner approves numbers for publication |
After W8, write the case study (12.6) and decide with the owner whether to move to a retainer.
An ADR is a one-to-two page note that records one decision, the options you rejected and why. It protects the client (they know what they bought), you (you can show the reasoning) and the next engineer. Write one per major choice. For the pilot you usually need only one or two.
# ADR-001: [Decision title, e.g. "Draft shipment-status replies with Claude via API, human sends"]
Status: Proposed | Accepted | Superseded by ADR-00X
Date: YYYY-MM-DD
Deciders: [client owner], [your name, Vastama]
## Context
Process, current steps, volumes per week, languages, systems involved,
data sensitivity, constraints (budget, staff, deadline).
## Decision drivers
- [e.g. replies checked by a person before sending]
- [e.g. Hebrew, Russian and English]
- [e.g. no customer data stored outside the client's accounts]
## Options considered
| Option | Pros | Cons |
|---|---|---|
| 0. Do nothing | No risk, no cost | Problem stays |
| 1. Claude app with a Project, no code | Fast, no build | Manual copy and paste, no logging |
| 2. API plus small Python service, human review | Logging, control | Needs hosting and maintenance |
| 3. Agent with MCP into the client's system | Least manual work | Highest risk and effort |
## Decision
We will [option], because [two or three reasons tied to the drivers].
## Consequences
- Good: ...
- Bad or costly: ...
- Risks and mitigations: ...
## Data and privacy
What data goes to Claude, plan and terms checked on [date] (link),
retention setting, who has access, what is never sent.
## Model choice and cost tracking
Model tier and why (see docs.claude.com for current models and pricing).
How cost per task will be logged and who reviews it monthly.
## Evaluation
Test set size and source, pass mark, who judges, when it is re-run.
## Human oversight and rollback
Who approves outputs, what happens when Claude is wrong,
how to switch the system off in one step.
## Review date
[date to revisit this decision]
A pilot that only you can run is a dependency, not a result. The pack lets the client run, check and switch off the system without calling you.
| Item | Contents | Test it with |
|---|---|---|
| Runbook | What the system does, daily steps, how to review outputs, what to do when Claude is wrong, how to switch off and roll back, where logs live | A clerk follows it with you silent |
| Admin guide | Accounts and who owns them, API key handling and rotation, spend limit, user access, where prompts and config are stored, how to change a prompt safely | The owner or IT contact adds and removes a user |
| Training video | 10 minutes or less, screen recording of one real task from start to finish, in the language the staff work in. Use captions. Hide customer data | A new employee watches it and completes a task |
| Support terms | What is covered (bugs, prompt fixes), what is not (new features), response times in working days, how to report a problem, how to end support, who owns the code and prompts | The owner reads it and can say it back in one sentence |
| Source and config | Repository or folder with code, prompts, test set and the ADR; no secrets inside | Another developer can run the tests |
Your credibility depends on numbers that survive a sceptical question. Use one primary metric and up to two supporting ones. Measure the baseline and the after period the same way, written down before the pilot.
| Metric | Baseline | After | n and period | Source |
|---|---|---|---|---|
| [primary metric, with unit] | [value] | [value] | [n baseline / n after, dates] | [mail log, CRM export, stopwatch sample] |
| [supporting metric] | [value] | [value] | [n, dates] | [source] |
| [quality metric: errors found in review] | [value] | [value] | [n, dates] | [source] |
Caveats you should check against every pilot, and state the ones that apply:
One page, one client, plain language. Five parts:
Rules for approval, kept in writing:
[QUOTE PENDING APPROVAL] in the draft."AI for everyone" sells to no one. A niche lets you reuse your work, speak the client's language and be the obvious name for one kind of problem.
| Niche option | Why it can work for you | Check before you commit |
|---|---|---|
| Israeli logistics and distribution SMBs | Heavy email and document load, multi-language customers, clear time metrics, repeatable pilots | Can you reach 30 such companies in 90 days? |
| Service businesses (garages, studios, clinics, agencies) | Many small owners, quick decisions, enquiry and booking work | Is the volume large enough to measure? |
| Hebrew and Russian-speaking businesses | You work natively in both; fewer consultants do; clear language advantage | Is it a market or only a language? Combine it with an industry |
Pick one primary niche and one secondary. Your pipeline does not have to be exclusive; your message does.
I help [type of company, size, place]
[achieve a specific outcome]
by [your method, in plain words],
so that [measurable result they can check].
Unlike [the usual alternative], I [one real difference].
Draft for you to edit, not a claim of past results:
I help Israeli logistics and distribution companies with 10 to 100 staff
cut manual email and document work
with Claude, in Hebrew, Russian and English, using a two-week pilot,
so that the time saved is measured against a baseline before they decide to scale.
Unlike a general AI course or a tool reseller, I audit first, build the pilot myself,
and hand over a pack your team can run.
Test it: read it to someone in the niche. If they cannot repeat what you do, shorten it.
A ladder lets a client start small and climb as trust grows. Each step has a clear output and an end.
| Offer | What the client gets | Typical length | Entry point for |
|---|---|---|---|
| Workshop: "Claude for your team" | Hands-on half day, team's own tasks, a prompt starter pack | Half a day | Owners who want to see it before they commit |
| AI readiness audit | Process review, 2 to 3 ranked use cases, baseline, risk and data notes, a pilot proposal | 1 to 2 weeks | Clients who know they have a problem but not which one |
| Pilot | One use case built, run for two weeks with review, measured, handover pack | Under 8 weeks including measurement | Clients with a chosen process and data |
| Retainer | Monthly monitoring, fixes, prompt and model updates, one improvement per month, short report | Rolling, with notice | Clients with a pilot that worked |
Set your fees in the proposal template from Module 11. This course gives no price list, because the right figure depends on your costs and the client's scope.
One page, in English and Hebrew (and Russian if you target it), on your existing GitHub Pages site. Suggested structure:
Technical points: separate files per language (for example /ai/ and /he/ai/), lang and dir="rtl" on the Hebrew page, hreflang links between versions, a readable title and description for search, and no testimonials or logos you do not have approval for.
Your profile at linkedin.com/in/sheroleg is the page a prospect checks after your message. Make it say the same thing as your website.
Two posts a week for eight weeks. Each post should teach one thing, show one thing you built, or answer one question an owner asks. Do not name a client without written approval.
| Week | Post A | Post B |
|---|---|---|
| 1 | Who I help and why this niche | What a two-week pilot looks like (the timeline) |
| 2 | Three office tasks Claude handles well, with a synthetic example | One email in Hebrew, one in Russian, one in English: triage demo on made-up data |
| 3 | The four tests I use before taking on any pilot | Data privacy: the questions owners ask and the sources I show them |
| 4 | An ADR explained: how I record a design decision (template, no client data) | Why every output has a human reviewer at first |
| 5 | A public repo or MCP server I built, and what it does | How I measure a pilot honestly: baseline, n, caveats |
| 6 | A mistake or surprise from the build (only what the client allows) | Cost tracking: how I watch spend per task |
| 7 | The case study, with approved numbers and quote | What I would do differently next time |
| 8 | The half-day workshop: agenda and who it is for | Answers to the five questions owners asked me this month |
Build a list of 30 target companies in your primary niche: 10 reached through a warm introduction, 10 through a shared connection or meetup, 10 researched cold. For each, record the reason it might need help (a trigger such as hiring a customer service clerk, growth in shipments, a new branch) and the person who decides. The tracker in the lab holds the list.
Ask people who already trust you (former colleagues, suppliers, the capstone client) for introductions. Make it easy: send a two-line blurb they can forward.
Hi [name], I'm offering a free 20-minute call to Israeli logistics owners about
cutting email and document work with Claude. If anyone comes to mind,
could you forward this to them? Happy to write the intro for you.
| Day | Action |
|---|---|
| 0 | First message, short, one specific reason, one clear ask |
| 4 | Follow-up that adds something useful (a one-page example from their sector), not "just checking in" |
| 10 | Different channel if you have a legitimate one (LinkedIn note, phone call to a published business number) |
| 24 | Last note: you will not write again unless they ask; leave the door open |
| After | Mark as paused for 90 days. Do not keep chasing |
Hebrew (replace the bracketed parts with something true and specific about the company):
שלום [שם],
קוראים לי אולג, אני מנהל את Vastama, חברת שירותי IT. אנחנו עוזרים לחברות קטנות
בתחום הלוגיסטיקה לחסוך עבודה ידנית במיילים ובמסמכים בעזרת Claude, בעברית,
ברוסית ובאנגלית.
ראיתי ש[פרט אמיתי על החברה]. אשמח לשיחה של 20 דקות ולהראות דוגמה אחת
מהתחום שלכם, בלי התחייבות.
מתאים לך [יום] בשעה [שעה]?
אולג
Vastama | [טלפון]
English:
Hi [name],
I'm Oleg, I run Vastama, an IT services company. We help small logistics
companies cut manual email and document work with Claude, in Hebrew,
Russian and English.
I saw that [a real detail about the company]. I'd like to talk for 20 minutes
and show one example from your sector. No commitment.
Would [day] at [time] work?
Oleg
Vastama | [phone]
A half-day workshop is your lowest-risk first sale and your best source of audit leads. Run it on the client's real tasks, with their data rules.
| Time | Block | Outcome |
|---|---|---|
| 0:00 to 0:30 | What Claude is and is not; data and privacy rules for this company | Shared rules the team can follow |
| 0:30 to 1:30 | Hands-on: each person brings two real tasks; prompts with context and examples | Each person has one working prompt |
| 1:30 to 1:45 | Break | |
| 1:45 to 2:45 | Projects, files and connectors for the team's work; checking Claude's answers | A shared Project set up for one team task |
| 2:45 to 3:30 | Find use cases worth a pilot; rank by the six-point screen from Module 1 | A shortlist of 2 to 3 candidates |
| 3:30 to 4:00 | Next steps, questions | A written follow-up plan within two days |
Pre-work: a 15-minute call with the owner, accounts ready, a list of sample tasks with sensitive details removed. After the workshop, send a one-page summary and the audit offer. Run the first one for a friendly client and ask for feedback you may quote only after approval.
Credibility is evidence a stranger can check in two minutes. List only what you have actually done.
| Source | How to use it |
|---|---|
| Anthropic Academy certificates | Finish the courses relevant to your offer on anthropic.skilljar.com; add earned certificates to LinkedIn and the website with the date |
| Public MCP servers | Your servers for Israeli and EU public data are a strong demonstration. Give each a clear README, an example, and a short post |
| Open-source repositories | The prompt library (Module 2), project template (Module 7) and your lab code, with no client data or keys. Pin the best three |
| Talks at local meetups | Offer a 20-minute talk built on the capstone, with approved numbers. Record it if allowed and link it from the website |
| Published case study | The strongest item. Everything else supports it |
Goal: three working tools you will use for the next 90 days. Everything runs locally; only the case study script calls the API, and it has a dry-run mode that does not.
Part 1 (about 2 h). Engagement plan and ADR for your chosen client
adr/ADR-001.md and fill it in after the audit. Include "do nothing" and "Claude app with a Project" as options.Part 2 (about 2 h). Case study draft generator
Setup (same repository as the earlier labs):
cd claude-transformation
source .venv/bin/activate
pip install anthropic python-dotenv
mkdir module12 && cd module12
engagement.json (all values are placeholders; fill them from your real logs)
{
"client_label": "A small logistics company in central Israel",
"client_name": "[client name]",
"client_name_approved": false,
"sector": "logistics",
"problem": "[What was slow or error-prone, in the owner's words. Facts only.]",
"approach": "[Audit, architecture decision, two-week pilot. Add real dates.]",
"solution": "[What was built: model, tools, the human review step.]",
"metrics": [
{
"name": "Median time to first reply to a shipment-status email",
"unit": "minutes",
"better_when": "lower",
"baseline": null,
"after": null,
"n_baseline": null,
"n_after": null,
"period": "[baseline dates] vs [pilot dates]",
"method": "[how it was measured, by whom]"
},
{
"name": "Status emails handled per clerk per day",
"unit": "emails",
"better_when": "higher",
"baseline": null,
"after": null,
"n_baseline": null,
"n_after": null,
"period": "[baseline dates] vs [pilot dates]",
"method": "[how it was measured, by whom]"
}
],
"caveats": [
"[confirm] One company, one pilot, short period.",
"[confirm] No control group; other changes in the period are possible.",
"[confirm] Time figures come from the mail log, not from self-reports."
],
"quote": {
"text": null,
"speaker": null,
"role": null,
"approved": false,
"approval_date": null
}
}
case_study.py
#!/usr/bin/env python3
"""case_study.py - turn engagement.json into draft case studies (English + Hebrew).
The output is a DRAFT for you to edit. Facts come only from the JSON.
Percent changes are computed here in Python, not by the model.
Usage:
python case_study.py engagement.json --dry-run --allow-missing # no API call
python case_study.py engagement.json --out drafts # real run
"""
import argparse
import copy
import json
import sys
from pathlib import Path
MODEL = "claude-sonnet-5-5" # check current IDs at docs.claude.com
REQUIRED_TOP = ["client_label", "problem", "approach", "solution", "metrics", "caveats"]
REQUIRED_METRIC = ["name", "unit", "baseline", "after", "n_baseline", "n_after", "period"]
SYSTEM = """You draft case studies for a small IT consultancy.
Rules:
- Use only facts and numbers that appear in the JSON. Never invent numbers, names, dates or quotes.
- If a value is null, empty or a [bracketed placeholder], keep it visible as [MISSING: field name].
- Use change_pct exactly as given. Do not recalculate. If it is null, say the change cannot be stated yet.
- Next to every result, state the sample sizes (n_baseline, n_after) and the period.
- If quote.approved is not true, write [QUOTE PENDING APPROVAL] and no quote text.
- If client_name is absent, use client_label. Do not guess the client's identity.
- Include every caveat from the JSON in a Caveats section.
- Plain, direct language. No hype, no superlatives.
- Sections, in order: Problem, Approach, Solution, Results, Caveats, Quote.
- Output Markdown only."""
LANG_NOTES = {
"en": "Write in English.",
"he": (
"Write in natural business Hebrew, not a literal translation. "
"Translate the section headings into Hebrew. Keep numbers, units and "
"product names (Claude, MCP, Python) in Latin characters."
),
}
def pct_change(baseline, after):
"""Percent change from baseline to after, or None if it cannot be computed."""
nums = (int, float)
if not isinstance(baseline, nums) or not isinstance(after, nums) or baseline == 0:
return None
return round((after - baseline) / baseline * 100, 1)
def find_missing(data):
"""Return a list of required fields that are empty."""
missing = []
for key in REQUIRED_TOP:
if data.get(key) in (None, "", []):
missing.append(key)
for i, metric in enumerate(data.get("metrics") or []):
for key in REQUIRED_METRIC:
if metric.get(key) is None:
missing.append(f"metrics[{i}].{key}")
return missing
def prepare(data):
"""Copy the data, add computed changes, strip anything not approved."""
d = copy.deepcopy(data)
for metric in d.get("metrics", []):
metric["change_pct"] = pct_change(metric.get("baseline"), metric.get("after"))
quote = d.get("quote") or {}
if quote.get("approved") is not True:
d["quote"] = {"approved": False} # quote text never reaches the model
if d.get("client_name_approved") is not True:
d.pop("client_name", None)
return d
def build_prompt(prepared, lang):
return (
f"{LANG_NOTES[lang]}\n\n"
"Draft the case study from this engagement data:\n\n"
+ json.dumps(prepared, ensure_ascii=False, indent=2)
)
def draft(client, prepared, lang):
msg = client.messages.create(
model=MODEL,
max_tokens=2000,
system=SYSTEM,
messages=[{"role": "user", "content": build_prompt(prepared, lang)}],
)
return "".join(b.text for b in msg.content if b.type == "text")
def main():
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("path", help="engagement JSON file")
ap.add_argument("--langs", default="en,he", help="comma list: en,he")
ap.add_argument("--out", default=".", help="output folder")
ap.add_argument("--dry-run", action="store_true", help="print prompts, no API call")
ap.add_argument("--allow-missing", action="store_true", help="continue with gaps")
args = ap.parse_args()
data = json.loads(Path(args.path).read_text(encoding="utf-8"))
missing = find_missing(data)
if missing:
print("Missing fields:", ", ".join(missing), file=sys.stderr)
if not args.allow_missing:
print("Fill them in, or pass --allow-missing for a gap-marked draft.", file=sys.stderr)
return 2
prepared = prepare(data)
langs = [x.strip() for x in args.langs.split(",") if x.strip()]
bad = [x for x in langs if x not in LANG_NOTES]
if bad:
print("Unsupported language:", ", ".join(bad), file=sys.stderr)
return 2
if args.dry_run:
for lang in langs:
print(f"--- PROMPT ({lang}) ---")
print(build_prompt(prepared, lang))
return 0
import anthropic # imported here so --dry-run works without the package
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic()
out_dir = Path(args.out)
out_dir.mkdir(parents=True, exist_ok=True)
for lang in langs:
text = draft(client, prepared, lang)
header = (
"<!-- DRAFT for Oleg to edit. Not approved by the client. "
"Check every number against the source data. -->\n\n"
)
target = out_dir / f"case_study_{lang}.md"
target.write_text(header + text + "\n", encoding="utf-8")
print("Wrote", target)
return 0
if __name__ == "__main__":
sys.exit(main())
Run it:
python case_study.py engagement.json --dry-run --allow-missing # see the prompts, no API call
python case_study.py engagement.json # stops and lists missing fields
python case_study.py engagement.json --out drafts # after you fill in real data
What to notice: the percent change is calculated in Python, the quote text never reaches the model unless approved is true, and missing values stay visible as [MISSING: ...]. Edit both drafts by hand. The model's Hebrew is a first draft; your version is the one you publish.
Part 3 (about 2 h). 90-day go-to-market tracker
gtm_tracker.csv (sample rows with placeholder names; replace them with your 30 companies)
company,segment,source,contact_role,stage,status,first_contact,last_contact,next_action,next_action_date,notes
[Company 01],logistics,warm_intro,owner,contacted,open,2026-10-19,2026-10-19,follow-up message,2026-10-23,[who introduced]
[Company 02],logistics,linkedin,operations manager,replied,open,2026-10-19,2026-10-21,book 20-min call,2026-10-26,
[Company 03],services,cold,owner,target,open,,,first message,2026-10-22,
[Company 04],garage,warm_intro,owner,call_held,open,2026-10-12,2026-10-20,send audit scope,2026-10-27,[friendly capstone candidate]
[Company 05],ceramics,warm_intro,owner,contacted,lost,2026-10-12,2026-10-14,,,[reason in one line]
gtm_summary.py
#!/usr/bin/env python3
"""gtm_summary.py - weekly summary of a 90-day go-to-market tracker CSV.
Usage:
python gtm_summary.py gtm_tracker.csv
python gtm_summary.py gtm_tracker.csv --today 2026-10-26
Weeks run Sunday to Saturday (Israeli work week).
"""
import argparse
import csv
import sys
from collections import Counter
from datetime import date, datetime, timedelta
from pathlib import Path
STAGES = [
"target",
"contacted",
"replied",
"call_held",
"audit_proposed",
"audit_won",
"pilot_won",
"retainer_won",
]
STATUSES = {"open", "lost", "paused"}
STALE_DAYS = 21
def parse_date(text, row_no, column):
text = (text or "").strip()
if not text:
return None
try:
return datetime.strptime(text, "%Y-%m-%d").date()
except ValueError:
raise SystemExit(f"Row {row_no}: {column} '{text}' is not YYYY-MM-DD")
def load(path):
rows = []
with open(path, newline="", encoding="utf-8") as f:
for n, row in enumerate(csv.DictReader(f), start=2): # row 1 is the header
stage = (row.get("stage") or "").strip()
status = (row.get("status") or "open").strip()
if stage not in STAGES:
raise SystemExit(f"Row {n}: unknown stage '{stage}'. Use one of: {', '.join(STAGES)}")
if status not in STATUSES:
raise SystemExit(f"Row {n}: unknown status '{status}'. Use one of: {', '.join(sorted(STATUSES))}")
row["stage"], row["status"] = stage, status
for col in ("first_contact", "last_contact", "next_action_date"):
row[col] = parse_date(row.get(col), n, col)
rows.append(row)
return rows
def week_start(day):
"""Sunday of the week containing `day`."""
return day - timedelta(days=(day.weekday() + 1) % 7)
def reached(row, stage):
return STAGES.index(row["stage"]) >= STAGES.index(stage)
def rate(part, whole):
return f"{part / whole * 100:.0f}%" if whole else "n/a"
def summarise(rows, today):
start = week_start(today)
end = start + timedelta(days=6)
out = []
out.append(f"GTM summary for week {start} to {end} (today {today})")
out.append(f"Rows: {len(rows)} open: {sum(r['status'] == 'open' for r in rows)}")
out.append("\nFunnel (companies that reached each stage)")
previous = None
for stage in STAGES:
count = sum(reached(r, stage) for r in rows)
step = f" ({rate(count, previous)} of previous)" if previous is not None else ""
out.append(f" {stage:<15}{count:>4}{step}")
previous = count
new_this_week = [r for r in rows if r["first_contact"] and start <= r["first_contact"] <= end]
touched = [r for r in rows if r["last_contact"] and start <= r["last_contact"] <= end]
out.append("\nThis week")
out.append(f" new first contacts: {len(new_this_week)}")
out.append(f" companies touched: {len(touched)}")
out.append("\nReply rate by source (replied or further / contacted or further)")
contacted_by, replied_by = Counter(), Counter()
for r in rows:
if reached(r, "contacted"):
contacted_by[r["source"]] += 1
if reached(r, "replied"):
replied_by[r["source"]] += 1
for source in sorted(contacted_by):
c, p = contacted_by[source], replied_by[source]
out.append(f" {source:<12}{p}/{c} {rate(p, c)}")
if not contacted_by:
out.append(" no contacts yet")
open_rows = [r for r in rows if r["status"] == "open"]
due = sorted(
(r for r in open_rows if r["next_action_date"] and r["next_action_date"] <= end),
key=lambda r: r["next_action_date"],
)
out.append("\nActions due by end of this week")
for r in due:
flag = "OVERDUE " if r["next_action_date"] < today else ""
out.append(f" {r['next_action_date']} {flag}{r['company']}: {r['next_action']}")
if not due:
out.append(" none")
stale = [
r for r in open_rows
if r["stage"] == "contacted" and r["last_contact"]
and (today - r["last_contact"]).days > STALE_DAYS
]
out.append(f"\nStale: contacted, no reply, last touch over {STALE_DAYS} days ago")
for r in stale:
out.append(f" {r['company']} (last touch {r['last_contact']}): pause or close")
if not stale:
out.append(" none")
no_action = [r for r in open_rows if not r["next_action_date"] and r["stage"] != "retainer_won"]
out.append("\nOpen rows with no next action date")
for r in no_action:
out.append(f" {r['company']}")
if not no_action:
out.append(" none")
return "\n".join(out)
def main():
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("csv_path")
ap.add_argument("--today", help="override today, YYYY-MM-DD")
args = ap.parse_args()
if not Path(args.csv_path).exists():
print(f"File not found: {args.csv_path}", file=sys.stderr)
return 2
today = datetime.strptime(args.today, "%Y-%m-%d").date() if args.today else date.today()
print(summarise(load(args.csv_path), today))
return 0
if __name__ == "__main__":
sys.exit(main())
Run it every Sunday morning:
python gtm_summary.py gtm_tracker.csv
python gtm_summary.py gtm_tracker.csv --today 2026-10-22 # test with a chosen date
target, a first action and date for each).target, contacted, replied, call_held, audit_proposed, audit_won, pilot_won, retainer_won. A lost or paused company keeps its furthest stage and gets a different status.Six items. The first needs the engagement to finish; the others you can complete in this module.
gtm_tracker.csv from the lab, filled with 30 real targets, each with a trigger and a named role. Add columns website, size_estimate and trigger if you need them; the script ignores extra columns.| Time | On screen | You say |
|---|---|---|
| 0:00 to 0:30 | Title and your face or logo | Who you are, who you help, the problem in the client's words (if approved) |
| 0:30 to 1:15 | The old process, screen recording with data hidden | What the work looked like before, and what was counted |
| 1:15 to 3:00 | The pilot handling one real example from start to finish, including the human review | What Claude does, what the person checks, what happens when it is wrong |
| 3:00 to 4:00 | The before/after table, with n, period and caveats on screen | The numbers as written in the case study, nothing added |
| 4:00 to 4:40 | The handover pack | How the client runs, checks and stops it without you |
| 4:40 to 5:00 | Contact slide | The next step: a 20-minute call or a readiness audit |
Record in the language of your audience, add captions in the other language, and show no customer data you do not own.
[QUOTE PENDING APPROVAL]. Never edit the words to make them stronger.One deliverable per module. Tick each one when it is finished and saved in your portfolio folder.