Case Study · AI-Assisted Software Delivery
Building an Education Marketplace with Private AI Code Generation
How a two-sided tutoring platform for teachers and students was delivered with a PRD-first method, bulk code generation running entirely on private infrastructure (OpenCode + Ollama), and manual-first debugging assisted by Claude Code and OpenAI Codex.
Client name withheld on request01At a Glance
Figures are representative of the engagement and rounded for confidentiality.
02The Project
The client operates an education marketplace connecting verified teachers with students, built for affordability and low-bandwidth access. Students search for teachers by subject, board, language, budget, and rating; browse their notes and courses; and book one-to-one live sessions or small batch classes that work on entry-level phones. Teachers manage availability, publish content, and receive fast payouts. The platform handles identity verification, OTP login, payments and subscriptions, doubt chat, and session recordings.
The engineering difficulty was not any single feature. It was that a two-sided marketplace multiplies scenarios. Every capability behaves differently depending on who is acting, what state they are in, and what can go wrong around them.
03The Technology Stack
We deliberately chose a mainstream, well-documented stack. This was a product decision and an AI decision at the same time: open-weight code models are strongest on technologies with deep public documentation and abundant examples, so a boring stack directly improved the quality of generated code.
- Frontend: Angular. The application was built on the Angular framework as a TypeScript single-page application. Strict typing, a strong CLI, and an opinionated project structure kept generated components uniform, and lazy-loaded feature modules kept the initial payload small for students on entry-level phones and slow networks.
- Backend: FastAPI. The backend was built on FastAPI with async Python. Pydantic models gave every request and response a machine-readable schema, and FastAPI’s auto-generated OpenAPI specification became a first-class input to our context packets: the generator always knew the exact shape of the API it was writing against.
- Database: PostgreSQL. PostgreSQL was our system of record. Bookings, wallet movements, and payouts are exactly the kind of data that wants real transactions, foreign keys, and constraints; schema changes flowed through versioned migrations, and the schema fragments in each context packet came straight from the migration files.
- End-to-end testing: Playwright. Playwright drove browser-based end-to-end tests against real workflows on the dev environment. Key PRD scenarios, such as searching for a teacher, booking a first free session, and completing a payment, ran as scripted user journeys on every deployment, including mobile viewports and throttled-network profiles that mirror our target users. Because these tests exercised the same flows the PRD described, they were the final word on whether a generated feature actually behaved as specified.
The stack paid a hidden dividend for AI generation: with TypeScript on one end and Pydantic on the other, both edges of every feature were statically typed. A large share of hallucinated or subtly wrong generated code failed the compiler or the schema check before it ever reached a human reviewer.
One more piece completed the picture: environments. Alongside production, a dedicated development deployment ran on its own subdomain. Every accepted, CI-green feature landed there first, where the Playwright suites ran against real workflows and the team exercised it on real phones and throttled networks before it was promoted to production. For a codebase with a high share of generated code, that standing dev environment was not a luxury; it was where generated features earned the right to ship.
04The Real Foundation: Actors, Scenarios, and the PRD
The most important decision in this project had nothing to do with AI. Before a line of code, the early weeks went into a deep product requirement document built around actors and scenario walkthroughs.
Scenario walkthroughs
For every capability, the team ran structured walkthroughs from each actor’s seat: what is this actor trying to do, what do they see when it works, what are all the ways it can go wrong, and who cleans up the state left behind. “Book a one-to-one session” alone produced walkthroughs for withdrawn slots, late payment webhooks, free-session abuse via duplicate accounts, mid-session drops on 3G networks, and guardian-pays-student-attends splits of receipts and reports.
More than forty walkthroughs became a 12-chapter PRD: actor goals, happy path, enumerated failure paths, state diagrams, and acceptance criteria per chapter.
05Private AI Code Generation: OpenCode + Ollama
The client asked that source code and product documents never leave infrastructure they control during bulk generation. So the generation stack was fully local: Ollama served pinned, open-weight code models from a shared Apple Silicon Mac inside the private network, and OpenCode, an open-source agentic coding tool, acted as the harness that read task files, navigated the repository, called the shared Ollama endpoint over the LAN, and applied edits. A model upgrade was treated like a dependency upgrade: pinned by version, regression-tested before adoption.
The models used
The team standardized on a small, boring set of open-weight models served through Ollama, each mapped to a job. All ran as quantized builds (Q4/Q5 class) that fit comfortably in the Mac’s unified memory.
Qwen2.5-Coder (14B / 32B)
Primary code generation from context packets. The 32B variant handled cross-file tasks; the 14B handled single-file tasks.
DeepSeek-Coder-V2 (16B)
Second opinion in the self-review pass. Strong at spotting mismatches with acceptance criteria.
Codestral (22B)
Test skeletons, fixtures, and fill-in-the-middle completions inside existing files.
Llama 3.1 (8B)
Lightweight utility work: commit message drafts, PRD slice summarization, packet formatting.
The pipeline, step by step
Where local models were reliably good
- CRUD endpoints and repositories from schema
- State-machine skeletons from PRD state tables
- Form screens from field lists; serializers; validation
- Test scaffolding and fixtures
Where humans stayed in charge
- Cross-actor flows (booking + payment + notification)
- Concurrency around slot locking; webhook idempotency
- Low-bandwidth video edge cases
- Anywhere two PRD chapters interacted
That split is exactly why the PRD mattered: packets made the first column near-automatic, and the walkthroughs meant the second column was known in advance rather than discovered in production.
06Challenges and the Solutions Adopted
No project like this goes smoothly end to end, and ours was no exception. We faced every challenge described below. We document them here because they shaped the decisions in this case study, and because most teams attempting private AI code generation will meet the same ones.
Challenge 1 · Hardware limitation: no GPU farm, one capable Mac
The problem. The budget did not stretch to a dedicated GPU server or cloud GPU rentals, and most team laptops could not run a serious code model locally. Early attempts to run models on individual machines produced slow, inconsistent results and duplicated model downloads everywhere.
The solution adopted. Ollama ran on a single high-memory Apple Silicon Mac, and its HTTP API was exposed as a web service over the office network: bound to the LAN interface, placed behind a lightweight reverse proxy with token authentication, with every team member’s OpenCode pointed at the same shared endpoint. One machine, one set of pinned models, many consumers. The Mac’s unified memory made mid-size quantized models (14B to 32B) practical without a discrete GPU farm; a simple request queue smoothed peak-hour spikes, and long batch jobs ran overnight.
Challenge 2 · Live video: build vs buy
The problem. One-to-one tutoring is live video at its core, and self-hosting real-time video infrastructure is deceptively expensive: TURN servers, media servers, bandwidth bills, mobile quirks on low-end devices, and an on-call burden the team did not want.
The solution adopted. The team initially experimented with Jitsi for self-hosted conferencing. It worked as a prototype, but scaling it for low-bandwidth users across regions would have demanded infrastructure effort disproportionate to the product stage. Video conferencing and calls were therefore routed through a third-party provider, saving the cost and effort of building in-house media infrastructure. The integration was isolated behind a thin session-provider interface, so the platform can revisit self-hosting (Jitsi included) later without rewriting booking, recording, or billing logic.
Other challenges we faced on this project
Small context windows on local models
The mid-size open models we ran could not hold the whole repository in mind. Our context packet discipline was the antidote: every task carried exactly the context it needed and nothing more.
Style and convention drift
We saw different models (and different days) produce differently shaped code. The exemplar file in each packet, plus strict linters in CI, kept our generated code looking like one codebase.
Hallucinated APIs
Our local models occasionally invented library methods. The self-review pass caught many; compile checks and tests in CI caught the rest. Nothing hallucinated ever reached review silently.
Model upgrades as regressions
More than once, a “better” model rewrote our working patterns in a new style. We treated model versions like dependency versions, with a regression suite of past packets, and that kept upgrades honest.
Shared-endpoint contention
One Mac serving our whole team meant queuing at peak hours. We scheduled batch generation off-hours and kept interactive tasks small, which preserved a good developer experience.
The temptation to skip the PRD
When generation became cheap, we caught ourselves reaching for the generator before the thinking was done. We enforced a strict rule of “no packet, no generation”, and it paid for itself every time.
Mid-task tool restriction: the Fable case
We also learned that external AI tools can disappear mid-task. One specific task, a review and validation of a PRD chapter, was being run on Fable when it was obstructed and stopped midway due to a restriction imposed by the US government on the use of Fable. The whole task was then reviewed again from the start on OpenAI Codex. The incident cost us a day, and it taught us to keep every task self-contained and portable across assistants, so that no single tool can block the pipeline.
07Code Generation Is One Part of the Ecosystem
A fair accounting of where the effort went. Generation was roughly a seventh of the total, yet it gets all the attention. The honest claim is narrower and more useful: private code generation removed the typing, not the thinking.
The compounding advantage came from the PRD discipline: the same packets that fed the models also fed reviewers, testers, and every future maintainer.
08Debugging and Error Fixing: Manual First, AI Assisted
Debugging was deliberately kept primarily manual. Generated code that fails teaches you where your packets are weak; outsourcing that lesson back to a model would have hidden the signal.
09Outcomes
- The platform shipped with teacher discovery, notes and courses, one-to-one and batch live sessions, verification, and payments, on the timeline the PRD had projected.
- Roughly two-thirds of first-draft code came from the private generation pipeline, at zero marginal token cost, with zero proprietary code leaving the client’s infrastructure during generation.
- A single shared Mac running Ollama served the whole team’s generation needs; no GPU farm was ever purchased.
- The defect pattern validated the method: bugs clustered almost entirely in the cross-actor seams the walkthroughs had flagged as risky, not in the generated single-actor code.
- The PRD outlived the build: support runbooks, test plans, and onboarding documents all derived from the same actor-and-scenario chapters.
10Lessons
- Write the PRD as if the reader is a machine, because sometimes it is. Precision about actors, states, and failure paths pays twice: once with the model, once with every human who follows.
- Keep generation private and boring. Pinned local models, low temperature, one bounded task at a time. Creativity belongs in the scenario room, not in the code generator.
- Share the hardware, not the chaos. One well-provisioned Mac exposing Ollama as a network service beat a fleet of laptops each running their own models.
- Buy the commodity, build the differentiator. Third-party video conferencing (after a Jitsi experiment) freed the team to spend its effort on discovery, booking, and trust, which is where the product actually competes.
- Let humans own the debugger. Manual-first debugging converted every bug into an improvement of the packets, so the pipeline got better with age.
- Match the tool to the job. Local OpenCode + Ollama for bulk private generation; Claude Code for interactive investigation; Codex for bounded delegated chores; humans for judgment.