The Project: the Tidewell Cycles Inbox
Every lesson in this course is set at one company, on one project. Tidewell Cycles is made up. The shop, its people, its customers, its products, every message and every number in the project are fictional and simulated. The real cases that open each lesson are separate, cited, and clearly marked as real.
The company
Tidewell Cycles is an independent bike shop and repair workshop in Bristol with a small web shop. It sells city bikes, e-bikes, a gravel bike, a children's bike, accessories and workshop services. Customers write in by email, through the web form on the website, and in product reviews. All of it lands in one shared inbox.
On Wednesday 1 July 2026 Tidewell started delivering to Germany, Spain and France. Until then almost every message was in English. From July about four in ten are in German, Spanish or French.
Money in this course is in pounds sterling (GBP), the currency Tidewell sells in. Hosted model prices are in US dollars, as the providers publish them; Module 10 says which is which wherever both appear.
The problem
The inbox received 200 to 270 messages a month in the spring, and about 430 a month since July (simulated, measured from the shared data). One person, Ines, reads them part-time. She forwards repair bookings to Dev in the workshop, product questions to Callum, and the rest she answers herself; spam stays with her too, by default, because nothing filters it. Spam, a battery fault and a summer sale have all arrived in the same queue, and nobody can say which problems are getting worse.
Rosa, the owner, wants five jobs done:
- Route every message to the right person (intent classification).
- Pull out the order number, product, fault and preferred date, so nobody retypes them (extraction).
- Tell her each Monday what customers are unhappy about, and whether it is getting worse (sentiment and topics over time).
- Suggest the right help-centre article to Ines while she replies (semantic search and question answering).
- Handle German, Spanish and French messages without hiring for each language (multilingual NLP and translation).
You are the contractor Rosa hires to do it. Module 1 Lesson 1 sets the five jobs; the capstone delivers them.
What this course uses the project for
This course teaches natural language processing from the ground up: text as data, rules, tokens, linguistic structure, vectors, classifiers, evaluation, sentiment, topics, embeddings, transformers, extraction, question answering, summarisation, translation, and large language models used as one more tool. The inbox is the instrument. The same hand-written test set (hard_test, 80 messages) is scored by every method as it arrives: the rule router in Module 1, TF-IDF models in Module 4, embeddings in Module 6, a fine-tuned DistilBERT in Module 7 and hosted LLMs in Module 10. By the end you have one honest comparison table, with cost.
It is not the BrightCart store (AI Engineering), the Harbourline Freight engagement (Forward Deployed Engineer), the returns desk (Agentic AI Engineer), the Quillpath support desk (Large Language Models), Hollin Answers (RAG), the notes assistant (MCP), the TinyFormer lab (Transformers) or the Booknook shelf (Python). Tidewell is a different company, with a different cast, data and problem.
The people
| Name | Role | In the project |
|---|---|---|
| Rosa Mensah | Owner | Sets the five jobs, makes the decisions, reads the Monday report, signs off the capstone. Cares about cost and about not upsetting customers |
| Ines Duarte | Customer service, part-time; speaks Spanish and Portuguese | Reads the inbox. Writes the label definitions and labels the hand-written test set. The main user of search and routing |
| Dev Malhotra | Workshop lead | Receives repair bookings and warranty faults; wants booking fields extracted |
| Callum Reid | Runs the web shop and the e-bike range | Owns the help-centre articles and the product list |
| Amara Osei | Part-time bookkeeper who also looks after data protection | Asks the privacy questions: redaction before storage, how long messages are kept, whether customer text may be sent to a hosted model. Her questions are answered from named, verified sources and marked "not legal advice" |
| You | NLP contractor | Build the inbox service, from the first regular expression to the capstone |
Customers come from a fixed list of 60 fictional names in the shared data (30 women and 30 men, 36 writing in English and 8 each in German, Spanish and French), deliberately varied in origin so that the Module 10 fairness check has something to measure. No customer is a recurring character except Mrs Joan Hartley of Clifton, whose long, polite, increasingly angry thread about her Volt 2 battery (TH01) is the running example for summarisation and escalation. Do not add named staff; a customer may appear in one lesson only.
The products
Fourteen products, fixed. SKUs, names and prices come from PRODUCTS in 06-shared-data.md and never vary.
| SKU | Product | Price | Kind |
|---|---|---|---|
TW-VOLT2-BLK | Volt 2 (e-bike; the battery-fault product) | £2,199 | e-bike |
TW-VOLT2-STP | Volt 2 Step-Through | £2,249 | e-bike |
TW-HARB-GRN | Harbour (city bike) | £649 | bike |
TW-HKID-20R | Harbour Kids 20 | £329 | bike |
TW-CAUS-GRY | Causeway (gravel bike) | £1,149 | bike |
TW-PANN-SET | Commuter Pannier Set | £89 | accessory |
TW-BATT-500 | Volt 2 battery pack (500 Wh) | £549 | accessory |
TW-LOCK-DLK | LockSure D-lock | £45 | accessory |
TW-LITE-BCN | Beacon Light Set | £39 | accessory |
TW-SERV-BAS | Basic Service | £65 | service |
TW-SERV-FUL | Full Service | £129 | service |
TW-REPR-PUN | Puncture Repair | £18 | service |
TW-DIAG-EBK | E-bike Diagnostic | £45 | service |
TW-GIFT-CRD | Gift Card | £25 to £200 (£50 in the data) | gift |
Identifiers have fixed shapes, so that Module 1 can find them with regular expressions:
| Thing | Shape | Example |
|---|---|---|
| Order number | TW- and six digits | TW-104233 |
| SKU | TW-, four or five capitals or digits, -, three | TW-BATT-500 |
| Repair ticket | RJ-2026- and four digits | RJ-2026-0412 |
| Message id | TW-M and four digits | TW-M0001 |
| Battery batch | VB- and four digits | VB-0426 (the faulty batch) |
| Help-centre article | KB01 to KB24 | KB05 |
| Email address | always on the reserved .example domain | joan.hartley@post.example, help@tidewell.example |
| Phone number | Ofcom's drama range | 07700 900123 |
| UK postcode | valid format, tied to nobody | BS3 4QT |
The rules
The help centre (KB01 to KB24 in the shared data) is the written policy. The facts lessons rely on most:
| Rule | Value | Article |
|---|---|---|
| Returns | 30 days from delivery, unused and in original packaging; sale items the same. Free UK returns label; EU customers pay £14.95 return postage | KB01 |
| Refunds | Sent within 5 working days of the return arriving; duplicate charges refunded in full, usually the same day | KB02 |
| Bike returns | 14 days, not ridden outdoors; UK collection free, EU collection £79 | KB03 |
| Warranty | Frames 5 years; e-bike motors and batteries 2 years; other parts 1 year. Wear parts (tyres, tubes, brake pads, chains, cassettes) and crash damage are not covered | KB04 |
| Battery batch VB-0426 | Some Volt 2 battery packs (500 Wh) sold March to May 2026; may stop charging or run flat quickly; replaced free; check the label under the pack | KB05 |
| Volt 2 range | 40 to 80 km (about 25 to 50 miles) a charge | KB07 |
| UK delivery | Parcels 2 to 4 working days, free over £50 (else £4.95); bikes 3 to 5 working days, £29 | KB08 |
| EU delivery (from 1 July 2026) | Parcels 4 to 7 working days, £14.95; bikes 5 to 8 working days, £79; import charges included | KB09 |
| Courier | ParcelNorth until 9 August 2026; SwiftDrop from 10 August 2026 (both fictional) | KB10 |
| Workshop | Monday to Saturday, 9:00 to 17:30. Basic Service £65 (about an hour), Full Service £129 (one working day), Puncture Repair £18 (same day if in before 10:00), E-bike Diagnostic £45 (taken off any repair you book) | KB14 to KB17 |
| Replies | Within one working day; in English, German, Spanish or French since 1 July 2026 | KB22 |
| Data | Messages kept 18 months after the last order or message, then deleted; card details removed before storage; requests to privacy@tidewell.example | KB21 |
Two project rules are not in the help centre:
- Labels come from Ines's written definitions,
LABELSin the shared data. Seven intents:order_status,returns_refund,repair_booking,product_question,warranty_fault,praise,spam. Three sentiments:neg,neu,pos. When a message fits two intents, the label is the one that needs action first, andhard_testrecords the second. - Amara's rule for hosted models (agreed with Rosa in Module 10): only redacted text, with names, emails, phone numbers and postcodes replaced, may be sent to a hosted model, and the lesson that does it names the provider's own data terms rather than guessing them. This is the project's rule, not legal advice.
The data
All Tidewell data is simulated and generated by code with a fixed seed, random.Random(20260831), in plain Python, so every learner gets identical data. 06-shared-data.md holds the code and the fingerprints to check it.
| Name | Size | What it is | Main use |
|---|---|---|---|
MESSAGES | 1,800 | The inbox, 2 March to 30 August 2026, from templates with noise added on purpose | Training and every analysis |
HARD_TEST | 80 | Hand-written messages: sarcasm, two intents, code-switching, slang, very short and very long. 40 English, 40 German, Spanish and French with reference translations | The headline test set for every model |
OOS | 60 | Out-of-scope messages that fit no intent | Abstaining (Module 5) |
HELP_CENTRE | 24 articles | KB01 to KB24, 155 to 182 words each | Search, question answering |
SEARCH_QUERIES | 60 | Queries with the article that answers each | Module 6 |
QA_PAIRS | 40 | Questions with an exact answer span; 8 cannot be answered from the article | Module 8 |
THREADS | 24 | Long email threads with a hand-written reference summary each | Modules 8 and 9 |
TRANSLATIONS | 403 pairs | Every non-English message with its English reference | Module 9 |
POS_GOLD | 20 sentences | Hand-tagged Universal POS tags | Module 3 |
W2V_CORPUS | 20,000 sentences | Short templated reviews and repair notes | Training word2vec and fastText (Module 6) |
name_swap | built in Module 10 | Copies of test messages with names and pronouns swapped | Fairness check |
Template messages flatter template-learnable models: a classifier trained and tested on MESSAGES scores far higher than it will on real mail. That is why HARD_TEST is the headline number, and Module 4 measures the gap rather than claiming it.
Dates
- Course date: Wednesday 14 October 2026. Library versions, model names and prices were checked on this day or on the date shown beside them.
- The data window: Monday 2 March to Sunday 30 August 2026, 26 weeks.
DATA_AS_OF= Monday 31 August 2026, the morning the inbox was exported. Code counts days toDATA_AS_OF, never to the computer's clock.- The project: Monday 7 September to Monday 16 November 2026. Module n is project week n. Lessons are told in the present tense, as if you are there.
Key events in the data
Fixed, so that every module tells the same story. All simulated.
| When | Event | Where it shows |
|---|---|---|
| 6 to 19 April 2026 | A spam wave: fake "partnership", crypto and phishing messages | Spam is visible in the April data (Modules 1, 4, 5) |
| Tuesday 9 June 2026 | Tidewell emails Volt 2 owners about battery batch VB-0426 | Warranty faults and negative sentiment rise through June and July: the Module 5 "topics over time" spike, and the Hartley thread |
| Wednesday 1 July 2026 | Delivery to Germany, Spain and France starts | Non-English messages go from about 5% to about 38% of the inbox; the train-before, test-after drift split (Modules 5 and 9) |
| 15 to 28 July 2026 | Summer sale | Order-status messages spike; returns rise in the fortnight after |
| Monday 10 August 2026 | The courier changes from ParcelNorth to SwiftDrop | Delivery complaints rise ("attempted delivery") |
The project weeks
| Week | Monday | Module | What happens |
|---|---|---|---|
| 1 | 7 September | 1 | Rosa sets the five jobs. You load the export, clean it, pull out order numbers and dates, and build the rule router: the baseline |
| 2 | 14 September | 2 | Tokenisation and word normalisation, measured on the router |
| 3 | 21 September | 3 | Dev asks for the products, dates and order numbers in each booking: the entity extractor |
| 4 | 28 September | 4 | The first trained intent classifier beats the rule router, and you measure how much of that is the templates |
| 5 | 5 October | 5 | One evaluation report for every model; Rosa's first Monday report on what customers are unhappy about |
| 6 | 12 October | 6 | Help-centre search for Ines |
| 7 | 19 October | 7 | A fine-tuned DistilBERT against the TF-IDF model |
| 8 | 26 October | 8 | Repair-booking records for Dev, an answer finder for Ines, thread summaries for Rosa |
| 9 | 2 November | 9 | Multilingual routing for the July launch |
| 10 | 9 November | 10 | The bake-off: every model, two hosted LLMs, a cost table, a hybrid router and a name-swap fairness check, presented to Rosa |
| 11 | 16 November | 11 | Capstone hand-over: the whole service, documented and tested, signed off by Rosa |
Fixed names
So that modules fit together, these names do not change:
- The repository is
tidewell-inbox/; the package isinbox/; tests live intests/. inbox/data.pyholds the shared data blocks from course-guide/06, copied exactly.- Every intent model, from the rule router to the LLM classifier, offers
predict(texts: list[str]) -> list[str], returning one of the seven intents (or"abstain"once Module 5 adds abstaining). The bake-off calls every model the same way. inbox/evaluate.pyoffersreport(y_true, y_pred), which every module from 5 on uses to score a model onHARD_TEST.inbox/llm.pyholds the setup cell from course-guide/02 andask(model, messages), created once.
One repository: which module adds which file
In Colab each lesson writes the files it needs with %%writefile and imports them; on a laptop the same files form the repository. Tests run with !pytest -q in Colab and pytest -q locally. Every lab from Module 2 on starts by checking that the earlier modules' files are present and writing back any that are missing, so you can start at any module.
| Module | Adds to tidewell-inbox/ |
|---|---|
| 1 | inbox/__init__.py, inbox/data.py (the shared data), inbox/clean.py (repair and normalise text), inbox/rules.py (field extraction with regular expressions, redaction, the keyword rule router); tests/test_clean.py, tests/test_rules.py; requirements.txt; .gitignore |
| 2 | inbox/tokens.py (tokenisers, stop words, stemming and lemmatisation as switchable options); tests/test_tokens.py |
| 3 | inbox/entities.py (the spaCy pipeline, noun phrases, negation scope, NER plus an EntityRuler for Tidewell products and order numbers); tests/test_entities.py |
| 4 | inbox/features.py (bag of words, n-grams, TF-IDF), inbox/classify.py (naive Bayes and logistic regression intent classifiers); models/ for saved pipelines, git-ignored; tests/test_classify.py |
| 5 | inbox/evaluate.py (one evaluation report for every model), inbox/abstain.py (Abstainer, the router that can answer "abstain"), inbox/sentiment.py (VADER, a trained model, aspect sentiment), inbox/topics.py (LDA and NMF, topics by week); reports/ for Rosa's Monday report; tests/test_evaluate.py, tests/test_sentiment.py |
| 6 | inbox/embed.py (word and sentence embeddings), inbox/search.py (BM25 and dense help-centre search, recall@k and MRR); tests/test_search.py |
| 7 | inbox/hf_classifier.py (pipelines, tokenisers, the fine-tuned DistilBERT intent model); the fine-tuned weights go in models/, git-ignored; tests/test_hf_classifier.py |
| 8 | inbox/extract.py (repair-booking records validated with pydantic), inbox/qa.py (a help-centre reader), inbox/summarise.py (thread summaries); tests/test_extract.py (with the summary tests), tests/test_qa.py |
| 9 | inbox/langid.py (language identification), inbox/translate.py (MarianMT and multilingual routing), inbox/gen_metrics.py (BLEU, chrF, ROUGE); tests/test_multilingual.py |
| 10 | inbox/llm.py (one model client for three providers, and ask), inbox/llm_tasks.py (LLM classification and extraction), inbox/compare.py (the bake-off and the hybrid router), inbox/fairness.py (the name-swap check); cache/ for saved model replies, git-ignored; tests/test_llm_tasks.py, tests/test_fairness.py |
| 11 | inbox/pipeline.py (the whole service end to end), README.md, WHAT_BROKE.md, tests/test_capstone.py; MODEL_CARD.md, generated by lab cells from the run's measured numbers |
The finished inbox service
- Clean, normalised text, with personal data redacted before storage
- Order numbers, products, dates, money and faults extracted, validated and scored field by field
- Every message routed to Ines, Dev or Callum, in four languages, with spam set aside in a spam folder and an "abstain" (sent to Ines) for what fits nowhere
- One comparison table: rules, TF-IDF, embeddings, DistilBERT and two hosted LLMs on the same 80 hand-written messages, per class and per language, with cost and speed
- A hybrid router that sends only the hard cases to a hosted model
- Help-centre search and an answer finder for Ines, scored with recall@5, MRR and exact match
- Rosa's Monday report: what customers are unhappy about, and whether it is getting worse
- A name-swap fairness check, a model card and a list of what broke
The one idea
Measure the simple method first. A regular expression, a word count and a logistic regression are cheap, fast and explainable, and on many inbox jobs they are hard to beat. Every model in this course has to earn its place against them on the same 80 hand-written messages, and the capstone keeps the cheapest method that does each job well enough.