Courses that end in theory get forgotten. This one ends with a file on your disk. The tool below turns any text file into a structured summary, defaults to a cheap model, escalates only when you ask, and refuses to run if the job would cost more than you allowed.
#!/usr/bin/env python3
"""Summarise a text file into structured JSON. Cheap by default."""
import json, os, sys, urllib.request
CHEAP = "google/gemini-2.5-flash-lite" # $0.10 in / $0.40 out per 1M
STRONG = "anthropic/claude-sonnet-5" # $2.00 in / $10.00 out per 1M
MAX_SPEND = 0.05 # hard ceiling, in dollars
RATES = {CHEAP: (0.10, 0.40), STRONG: (2.00, 10.00)}
def estimate(text, model, out_tokens=400):
tokens_in = len(text) / 4 # ~4 characters per token
rate_in, rate_out = RATES[model]
return tokens_in / 1e6 * rate_in + out_tokens / 1e6 * rate_out
def main():
path = sys.argv[1]
model = STRONG if "--strong" in sys.argv else CHEAP
text = open(path, encoding="utf-8").read()
cost = estimate(text, model)
if cost > MAX_SPEND:
sys.exit(f"Refusing: estimated ${cost:.4f} exceeds ceiling ${MAX_SPEND}")
print(f"[{model}] estimated ${cost:.6f}", file=sys.stderr)
body = json.dumps({
"model": model,
"max_tokens": 400,
"messages": [{"role": "user", "content":
"Return JSON with keys summary, decisions, risks.\n\n" + text}],
}).encode()
req = urllib.request.Request(
"https://openrouter.ai/api/v1/chat/completions", data=body,
headers={"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}",
"Content-Type": "application/json"})
with urllib.request.urlopen(req) as r:
data = json.load(r)
print(data["choices"][0]["message"]["content"])
u = data["usage"]
rate_in, rate_out = RATES[model]
actual = u["prompt_tokens"] / 1e6 * rate_in + u["completion_tokens"] / 1e6 * rate_out
print(f"[actual ${actual:.6f} — {u['prompt_tokens']} in, "
f"{u['completion_tokens']} out]", file=sys.stderr)
if __name__ == "__main__":
main()Save the script, make it executable, and point it at a file.
chmod +x summarise.py && ./summarise.py transcript.txtEstimate first, then the answer, then what it really cost. Estimated $0.000702 against $0.000602 actual — the character-per-token approximation ran 16% high, which is the direction you want it to be wrong../summarise.py transcript.txt --strongThe strong model is noticeably richer — and 21x the price. Now you have both numbers and can decide per job instead of per habit../summarise.py enormous-report.txtThe ceiling doing its job. It stopped before spending, not after — which is the entire difference between a limit and an alert.
Your own numbers will differ — token counts depend on your text, and rates move. The arithmetic is what transfers.
What each piece is doing
- CHEAP / STRONG constants — lesson 4's routing rule, made explicit. The default is cheap; expensive is a flag you have to type.
- MAX_SPEND — lesson 3's ceiling. It refuses rather than warns.
- estimate() — the four-characters-per-token approximation. Rough, and rough is enough to catch a job that is 100× larger than you thought.
- max_tokens: 400 — the seatbelt on the expensive half of the bill.
- The actual-cost line — closes the loop. Every run teaches you whether your estimates are any good.
Where to go next
- 1
Change the prompt
The keys in that JSON are yours to choose. Make it extract what your actual job needs.
- 2
Point it at a folder
Wrap the call in a loop over files — with lesson 3's estimate-first habit intact.
- 3
Re-run lesson 4 quarterly
The cheap tier keeps getting cheaper. Your CHEAP constant should move with it.
- 4
Take the provider courses
The 101 course for whichever model you settled on goes deeper on its plans and quirks.
Knowledge check
Why does the script estimate cost before sending rather than just reporting it after?