Capstone: TrackFlow
"Where's my order?" is the most expensive question in B2B commerce — every call to a human rep costs ~$8–15. The answer is an API. You're building it.
Domain Briefing: The Purchase-Order Lifecycle
A restaurant chain sends your distributor a purchase order (PO)A buyer's formal document requesting goods: PO number, line items with SKUs and quantities, requested delivery date. In B2B, the PO number is the key both sides use for everything. for 40 cases of cooking oil. From that moment, the order moves through a strict lifecycle: submitted, confirmed against inventory, picked in the warehouse, handed to a carrier, delivered. Two things make this harder than a shopping-cart tutorial. First, not all transitions are legal — a DELIVERED order cannot become CANCELLED, an order on credit hold can't ship — and every illegal transition that slips through becomes a financial dispute. Second, B2B contracts carry an SLAService Level Agreement — a contractual promise, e.g. "orders confirmed by 2pm ship within 2 business days." Breaches often trigger penalty credits.: ship within N days of confirmation or owe the customer credits. Distributors live and die by catching breaches before the customer's procurement system does.
This project is three durable API patterns in one: a state machine over HTTP (the transition endpoint), the adapter pattern (multiple carrier APIs behind one interface — the same shape as payment gateways and SMS providers), and computed business views (the SLA breach report). Master these three and most CRUD-plus services you'll ever build are variations.
What You Build
trackflow — a Flask + SQLite API with:
- PO creation with line items; an enforced lifecycle state machine with audited transitions
- Two mock carrier adapters (FastShip, EconoFreight) behind one interface — tracking events per shipped order
- An SLA engine: promised-ship-date computed at confirmation;
GET /sla/breachesreports at-risk and breached orders - Full pytest suite (state machine exhaustively tested); GEMINI.md + .aiexclude from day zero; carrier adapter #2 shipped via agent mode; PR-reviewed before merge
- Forward-only happy path; no skipping states (SUBMITTED → SHIPPED is illegal).
ON_HOLDremembers and restores the previous state on release.CANCELLEDis allowed from SUBMITTED/CONFIRMED/PICKING/ON_HOLD — never from SHIPPED or DELIVERED.- Illegal transition → 409 Conflict with
{"error": "...", "current_state": "...", "allowed": [...]}. - Every transition appends to an immutable
order_eventsaudit table (who, when, from, to). - SHIPPED requires a carrier + tracking number in the request body.
Executing This Capstone in VS Code / PyCharm
Everything in this project is built through Gemini Code Assist inside your IDE — never by copying finished code from elsewhere. Each phase uses some of these five moves; every paste-block below names its move in the header.
- Create the project folder and open it: VS Code File → Open Folder (PyCharm: File → Open). New files via the Explorer panel / Project tool window.
- Confirm Gemini Code Assist is signed in: the ✦ icon in VS Code's Activity Bar opens the panel with no error badge (PyCharm: the Gemini tool window on the right edge greets you by account). Setup is module G01.
- Open the integrated terminal for git, Flask, curl, and pytest:
Ctrl+`in VS Code (PyCharm:Alt+F12). Run the Flask server in one terminal tab and curl from a second (the + button splits/creates tabs). - After creating GEMINI.md in Phase 1, start a new chat so the rules load into context (the G05 lesson).
- Chat and smart commands act on the active file or selection — keep the file you're working on focused, and select code before asking about it (the G03 gotcha).
| Move | VS Code | PyCharm / IntelliJ |
|---|---|---|
| 1 · Spec-comment | Type the comment in the editor and pause — ghost text appears. Tab accepts, Ctrl+→ accepts word-by-word, Esc rejects (G02) | Same: type, pause, Tab; hover the suggestion to cycle alternatives |
| 2 · Chat / smart cmd | Click the ✦ icon in the Activity Bar → chat panel opens. Select code first to scope it; /explain /fix /doc /simplify work here (G03) | Gemini tool window (right edge) → Chat tab; same slash commands |
| 3 · Generate tests | Select the function/class → right-click → Gemini Code Assist → Generate unit tests; save the output into tests/ (G04) | Select → right-click → Gemini → Generate unit tests |
| 4 · Agent ticket | In the chat input, switch the Agent toggle ON → paste the ticket → review the plan in the panel → Accept/Reject each diff → click Allow when it asks to run a command (G06) | Gemini tool window → Agent tab → paste the ticket; leave Auto-approve changes OFF; review diffs the same way |
| 5 · Headless CLI | Integrated terminal: gemini -p "..." — same login, same GEMINI.md rules as the IDE (G08) | |
Example: a block labeled "Move 4 · paste in Agent mode" means: open the Gemini panel, flip the Agent toggle (VS Code) or open the Agent tab (PyCharm), paste, and work the plan→diff→approve loop.
API Contracts
POST /orders
body: {"po_number": "PO-88412", "customer": "Riverside Restaurants",
"lines": [{"sku": "OIL-CAN-5L", "qty": 40, "unit_price": 18.50}]}
201 + order | 400 invalid | 409 duplicate po_number
GET /orders/<po_number> 200 (order + events + sla block) | 404
GET /orders?state=SHIPPED&customer= 200 filtered list
POST /orders/<po_number>/transition
body: {"to": "SHIPPED", "actor": "warehouse-3",
"carrier": "fastship", "tracking_number": "FS123456"} (carrier fields req. for SHIPPED)
200 + updated order | 409 illegal transition | 400 missing fields | 404
GET /orders/<po_number>/tracking 200 carrier events via adapter | 409 if not yet SHIPPED
GET /sla/breaches?as_of=2026-06-15
200 {"breached": [...], "at_risk": [...]} # at_risk = due within 1 business day
Build Phases
| Phase | What happens | Skills from |
|---|---|---|
| 1. Foundation | GEMINI.md with contracts + rules | G05 |
| 2. Core | Orders + state machine + audit | G02–G03 |
| 3. Tests | Exhaustive transition matrix | G04 |
| 4. Carriers | Adapter #1 by hand, #2 by agent | G02, G06 |
| 5. SLA + Ship | Breach engine, PR review, deploy | G06, G09–G10 |
Each phase shows a sample of success. Your generated code will phrase things differently — match the shape: the status codes, the JSON fields named in the contracts, and the invariants (64 matrix cases, 409 bodies that include allowed). Run the server with python app.py in one terminal tab and curl from a second.
Foundation: the contract is the GEMINI.md
New repo trackflow/. Paste the endpoint contracts and the six state-machine rules into GEMINI.md verbatim, plus your standing conventions (storage layer separation, JSON errors, parameterized SQL, type hints, pytest temp-DB fixture). Add .aiexclude (*.db, .env). Commit before writing any code — in this capstone, the spec quality directly determines how little correcting you'll do later.
Core: the state machine deserves its own module
Build in this order, each via spec-comments with immediate verification: storage.py (orders, order_lines, order_events tables), then lifecycle.py — a pure module with no Flask and no SQL:
# Pure state machine for PO lifecycle. No I/O.
# TRANSITIONS maps each state to the set of legal next states per the
# six rules in GEMINI.md (ON_HOLD restores previous state; CANCELLED
# only before SHIPPED; terminal states have no exits).
# validate_transition(current, to, previous_state=None) returns None if legal,
# else a string reason. Raises ValueError on unknown state names.
TRANSITIONS: dict[str, set[str]] = {
Then app.py routes that only orchestrate: validate input → call lifecycle → call storage → map results to status codes. Use chat on your own transition table (Move 2: select the TRANSITIONS dict in the editor, open the ✦ panel, type /explain plus the question): "list every (from, to) pair this table allows — do any violate the six rules?" — an AI audit of AI-written code, with you as the judge.
Tests: the matrix, exhaustively
Move 3, with a twist: select the whole of lifecycle.py → right-click → Gemini Code Assist → Generate unit tests — then refine the result in chat (Move 2) with an explicit demand: "a parameterized pytest that checks EVERY (from, to) state pair — all 8×8 — against the expected legality, plus the ON_HOLD restore behavior and the SHIPPED-requires-carrier rule at the API level." 64 combinations is exactly what humans skip and generators don't.
PS> pytest tests/ -q
......................................................................... [100%]
73 passed in 0.84s # 64 matrix cases + API/lifecycle tests
PS> curl -X POST http://localhost:5001/orders -H "Content-Type: application/json" -d "{\"po_number\":\"PO-88412\",\"customer\":\"Riverside Restaurants\",\"lines\":[{\"sku\":\"OIL-CAN-5L\",\"qty\":40,\"unit_price\":18.50}]}"
{"po_number":"PO-88412","state":"SUBMITTED","customer":"Riverside Restaurants",
"lines":[{"sku":"OIL-CAN-5L","qty":40,"unit_price":18.5}],
"created_at":"2026-06-11T09:12:44"} <-- 201
PS> curl -X POST http://localhost:5001/orders/PO-88412/transition -H "Content-Type: application/json" -d "{\"to\":\"SHIPPED\",\"actor\":\"warehouse-3\"}"
{"error":"illegal transition SUBMITTED -> SHIPPED",
"current_state":"SUBMITTED",
"allowed":["CONFIRMED","ON_HOLD","CANCELLED"]} <-- 409
PS> curl http://localhost:5001/orders/PO-88412
{"po_number":"PO-88412","state":"SUBMITTED", ...,
"events":[{"from":null,"to":"SUBMITTED","actor":"api","at":"2026-06-11T09:12:44"}],
"sla":{"promised_ship_date":null,"status":"NOT_CONFIRMED"}} <-- 200
allowed list; the events table shows the full audit trail.Carriers: hand-build one, delegate the second
Write carriers/base.py (an abstract CarrierAdapter with get_tracking_events(tracking_number) -> list[dict]) and carriers/fastship.py yourself with comment-driven generation (Move 1) — a mock returning deterministic fake events (created → in_transit → out_for_delivery → delivered, derived from a hash of the tracking number so tests are stable). Then hand the pattern to the agent (G06):
Add a second carrier adapter "econofreight" following the existing
CarrierAdapter pattern in carriers/. Differences: EconoFreight event
payloads use {"status_code": "IT", "ts": ...} style codes (map them to our
normalized events), and its mock should simulate a 2-day-slower timeline.
Register it in the carrier factory, make POST .../transition accept it,
and add tests mirroring the fastship ones. Plan first.
# after transitioning PO-88412: CONFIRMED -> PICKING -> SHIPPED (carrier fastship, FS123456)
PS> curl http://localhost:5001/orders/PO-88412/tracking
{"po_number":"PO-88412","carrier":"fastship","tracking_number":"FS123456",
"events":[
{"status":"created", "at":"2026-06-09T08:14:00"},
{"status":"in_transit", "at":"2026-06-10T02:31:00"},
{"status":"out_for_delivery", "at":"2026-06-11T07:05:00"}]} <-- 200
# an econofreight order returns the SAME event vocabulary —
# its IT/OFD status codes were mapped inside the adapter:
{"po_number":"PO-88436","carrier":"econofreight","tracking_number":"EF777102",
"events":[
{"status":"created", "at":"2026-06-08T11:02:00"},
{"status":"in_transit","at":"2026-06-10T19:44:00"}]} <-- 200
# tracking before SHIPPED:
{"error":"PO-88451 is in state PICKING; tracking available after SHIPPED"} <-- 409
GET /orders/<po>/tracking returns normalized events for orders shipped on either carrier — proof the adapter seam works. The agent's diff stayed inside carriers/, the factory, and tests; if it "improved" unrelated code, you rejected those hunks.SLA engine, review gate, ship
Build sla.py (business-day math — write the Friday-confirmation test FIRST, then generate the implementation until it passes) and the /sla/breaches endpoint. Then the full G09–G10 finish: push to GitHub with .gemini/config.yaml + styleguide.md (encode the 409 contract and audit-trail rule as "must flag"), open the SLA feature as a PR, process the bot review, merge clean — and optionally deploy to Cloud Run (the G10 prep checklist applies unchanged: requirements.txt, Procfile, PORT, ephemeral-SQLite caveat).
PS> pytest tests/test_sla.py -q
........ [100%]
8 passed in 0.21s
# the test that matters: confirmed Friday 2026-06-12 ->
# promised_ship_date == Tuesday 2026-06-16 (weekend skipped, NOT Sunday 06-14)
PS> curl "http://localhost:5001/sla/breaches?as_of=2026-06-15"
{"breached":[
{"po_number":"PO-88371","customer":"Marina Foods","state":"CONFIRMED",
"promised_ship_date":"2026-06-10","days_late":3}],
"at_risk":[
{"po_number":"PO-88393","customer":"Hilltop Cafes","state":"PICKING",
"promised_ship_date":"2026-06-15"}]} <-- 200
Grading Rubric
Stretch Goals
- Webhooks:
POST /webhooks/carrierreceiving simulated carrier status pushes that auto-transition SHIPPED→DELIVERED — with signature verification. - Idempotency keys: make
POST /orderssafe to retry via anIdempotency-Keyheader (real procurement systems retry aggressively). - Pagination + ETags: page the order list and add conditional GET — ask chat to explain the trade-offs first.
- Front it: the Frontend capstone builds an ops dashboard directly on this API — do them as a pair for a full-stack portfolio piece.
"Designed a B2B order-tracking API with an audited lifecycle state machine, pluggable carrier adapters, and SLA breach detection; exhaustive parameterized test coverage; AI-assisted development with human-reviewed agent workflows and automated PR review."