Chapter 06 · Section II · 32 min read
Building it end to end
One full project, from empty folder to working program — the NEPSE summariser. Follow it as a walkthrough, or use the structure as a template for the project you actually want to build.
We will build one small project together, end to end, using almost every piece of the previous five chapters. The example is the NEPSE summariser — a script that fetches today’s NEPSE close, asks a language model for a 3-sentence Nepali summary, and prints it. Follow along with the same idea if you do not have one of your own yet. If you do, treat the shape of the build as the template, and replace the specifics.
Step 1 — set up the project
From your terminal, in your building-ai-notes parent folder:
mkdir nepse-summariser
cd nepse-summariser
python3 -m venv .venv
source .venv/bin/activate
pip install requests anthropic python-dotenv
pip freeze > requirements.txt
Create the basic project files:
touch summarise.py .env .gitignore README.md
In .gitignore:
.venv/
__pycache__/
*.pyc
.env
.ipynb_checkpoints/
In .env:
ANTHROPIC_API_KEY=sk-ant-api03-...your-key...
Initialise the repo so your work is saved from the start:
git init
git add .
git commit -m "Initial project structure and dependencies"
You will commit again at each clean checkpoint. Each commit is a snapshot you can step back to if you break something.
Step 2 — fetch the data
The NEPSE Alpha API and several other public services expose the day’s index close as JSON. For this example we will fake a small endpoint, because writing live-API code that works two years from now is a losing bet — and the technique is the same either way.
Add to summarise.py:
import requests
def fetch_nepse_close() -> dict:
# In a real version, hit a public NEPSE-index API.
# For now, return a small stub so the rest of the program is testable.
return {
"date": "2026-06-27",
"close": 2142.1,
"change_pct": 0.8,
"leaders": ["Banking", "Hydropower"],
"laggards": ["Microfinance"],
}
if __name__ == "__main__":
data = fetch_nepse_close()
print(data)
Run it:
python summarise.py
You should see a dictionary printed. One small piece works. Commit:
git add summarise.py
git commit -m "Add NEPSE data fetch stub"
Step 3 — write the prompt
Add a function that turns the data into a request for the model. This is the prompt, and it is the single most important piece of the program:
def build_prompt(data: dict) -> str:
return (
f"You are explaining the day's NEPSE close to a small "
f"shopkeeper in Itahari who reads Nepali but not English. "
f"In 3 short sentences, in plain Nepali (Devanagari), say: "
f"what happened to the index today, which sectors led, "
f"and which lagged. Avoid jargon. Avoid English words.\n\n"
f"Today's data:\n"
f" Date: {data['date']}\n"
f" Close: {data['close']}\n"
f" Change: {data['change_pct']}%\n"
f" Leaders: {', '.join(data['leaders'])}\n"
f" Laggards: {', '.join(data['laggards'])}\n"
)
Notice the same three framings from Chapter 5: a role (Itahari shopkeeper), a length (3 short sentences), and a format (plain Nepali, Devanagari, no jargon). These three lines do as much work as the model itself.
Test it in isolation:
if __name__ == "__main__":
data = fetch_nepse_close()
prompt = build_prompt(data)
print(prompt)
Run, read the prompt, satisfy yourself it is what you would want to send. Commit.
Step 4 — call the model
Now wire up Claude. Add at the top:
import os
from dotenv import load_dotenv
import anthropic
load_dotenv()
And the call:
def summarise(prompt: str) -> str:
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=400,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text.strip()
And the main flow:
if __name__ == "__main__":
data = fetch_nepse_close()
prompt = build_prompt(data)
summary = summarise(prompt)
print(summary)
Run:
python summarise.py
Three Nepali sentences should appear. If they do not — if you see an error — read the last line of the traceback (Chapter 4 Section 3) before reacting. Common causes:
ANTHROPIC_API_KEYnot set: didload_dotenv()find your.envfile? Checkos.environ.get("ANTHROPIC_API_KEY").anthropic.APIError: the request reached the server but failed. The message will say why — bad model name, out of credit, malformed request.ImportError: did youpip installinside the activated environment?
Once it works, commit:
git add summarise.py
git commit -m "Add Claude summarisation call"
Step 5 — add the rough edges
The script runs end to end. Now improve the rough edges — the small touches that separate “I made it work once” from “I built a thing.”
Handle missing data. What happens if fetch_nepse_close() returns None because the API was down?
data = fetch_nepse_close()
if data is None:
print("Could not fetch NEPSE data. Try again later.")
sys.exit(1)
Write the output to a file as well as printing. A file is what your aunt can read on her phone tomorrow morning.
from pathlib import Path
from datetime import date
out = Path(f"summaries/{date.today().isoformat()}.txt")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(summary, encoding="utf-8")
Add a header for context. A timestamp and the raw numbers, so the summary is reproducible:
header = (
f"NEPSE Summary — {data['date']}\n"
f"Close: {data['close']} ({data['change_pct']}%)\n"
f"---\n"
)
print(header + summary)
out.write_text(header + summary, encoding="utf-8")
Each of these is two or three minutes of work. None is glamorous. Together they turn a script into a small product.
Commit again:
git add .
git commit -m "Handle missing data, save summary to file, add header"
Step 6 — replace the stub with the real API call
Now, finally, swap the fake fetch_nepse_close() with a real one. The exact endpoint will depend on which public NEPSE data source you choose; the shape is:
def fetch_nepse_close() -> dict | None:
try:
response = requests.get(
"https://example.nepse-api.com/index/today",
timeout=10,
)
response.raise_for_status()
raw = response.json()
return {
"date": raw["date"],
"close": raw["close"],
"change_pct": raw["change_pct"],
"leaders": raw["leading_sectors"],
"laggards": raw["lagging_sectors"],
}
except (requests.RequestException, KeyError) as e:
print(f"Failed to fetch NEPSE data: {e}")
return None
The try/except is doing real work here — network calls fail; APIs change their JSON shape; both should be handled, not crash the program. We covered the pattern in Chapter 4 Section 3.
Step 7 — the small polish
The script works, end to end, with real data. Three final touches that take five minutes each:
- A docstring at the top of the file. One paragraph: what it does, who it is for, how to run it. The future-you of six months will be grateful.
- A
--datecommand-line argument so you can re-run last week’s summary. Use Python’s built-inargparse. - A small print statement at the start — “Fetching NEPSE close for 2026-06-27…” — so the user knows the program is doing something during the second it waits.
Each of these is the kind of thing that costs nothing and reads as care. Commit, and git push — your work is now on GitHub.
git add .
git commit -m "Add CLI args, status output, and module docstring"
git push
The shape we just built
Take a step back. The shape of what you built is the shape of almost every modern AI application:
- Fetch data from somewhere — a file, an API, an image, a database.
- Prepare the data for the model — clean it up, format it as a prompt or feature vector.
- Call the model — locally or hosted.
- Use the response — print, save, send, display.
Every project in the rest of the track follows this pattern. The pieces change — a different API, a different model, a richer interface — but the shape stays. If you can read your summarise.py and tell me which of those four steps each line belongs to, you have learned the most important thing in Course 01.
Check your understanding
Quick check
—Why does this section recommend replacing the data fetch stub with the real API call *last*, rather than first?
Quick check
—Read the four steps the section names — fetch, prepare, call, use. A small program 'translate a photo of Nepali text to English'. Which line of code matches the `prepare` step?
What comes next
You have a finished program. The last and most often-skipped step is to write it up so a stranger could understand what it does, why, and how to use it — and then put it where strangers can find it. That is the next section, and it is the difference between code that exists and code that counts.