As a B2B AI developer, I spend my days building systems that find business signals and automate workflows. Recently, out of pure curiosity, attended a two-hour training class for cashiers at a physical retail store.
This post walks through what that class inspired me, and the architecture I'd build to fix it.

From basket data to a personalized, context-aware offer.
In this article:
- The Problem
- What I build?
- Demo 1: Traditional Recommendation (Association Rules)
- Why It Fails: Product Affinity Isn't Customer Intent
- Demo 2: Intent Classification with Embeddings
- Why That's Still Not Enough
- Demo 3: Contextual RAG Retrieves Business Context
- Demo 4: Feedback Loop Improves Future Recommendations
- Key Takeaways
The Problem
I expected to learn POS interfaces, cash handling, and inventory lookups. Instead, I found a business opportunity that neither human cashiers nor self-checkout have captured well: Upselling with Intent Signal.
Every time I pay at Walmart, I'm still asked to apply for a credit card — after almost two years in Toronto. Why not suggesting somethings based on what I'm actually buying? In convenience stores, cashiers' upselling of extra stacks ends up like robotic. In online shopping, the logic is just Simple Matching: steak always goes with BBQ sauce, for everyone, regardless of context and the target, no matter a young man or a housewife.
What we just need is a hyper-personalized digital handoff.
Picture a steak bought with champagne, by a man. Does he need flowers, or ice cream, to round out a romantic dinner, or a family celebration? And what if he got a same-day discount on that item — sent via SMS, or a QR code on the receipt?
What we need is Intent Intelligence.
Steak → BBQ Sauce
vs.
We noticed you're preparing a celebration.
Here are three offers valid until midnight.
Here's how I'd architect that.
What am I going to build?
Here's the design of Retail Intelligence System I'm building for Upselling:
Transaction
|
v
Signal Gathering (Demo 2)
|
v
Signal Enrichment
|
v
Intent Classification (Demo 2)
|
v
Contextual Retrieval (RAG) (Demo 3)
|
v
Recommendation Generation (Demo 3)
|
v
Customer Feedback
|
v
Continuous Learning
For a side by side comparison of the results, I will setup:
- Demo 1: a traditional POS matching recommendation, &
- Demo 2 & Demo 3: the simple intent intelligence I would like to build.
Setting Up the Project
python3 -m venv ragvenv
source ragvenv/bin/activate
mkdir retail-intent-demo
cd retail-intent-demo
Folder structure:
retail-intent-demo/
│
├── baskets.csv
├── traditional_pos.py
├── intent_rag.py
├── promotion_rag.py
└── requirements.txt
requirements.txt
pandas
mlxtend
sentence-transformers
scikit-learn
pip install -r requirements.txt
Basket Data
Common basket data pulled from all customers.
baskets.csv
TransactionID,Items
1,"Steak,BBQ Sauce,Beer"
2,"Steak,BBQ Sauce"
3,"Steak,Garlic Bread"
4,"Steak,Black Pepper"
5,"Champagne,Chocolate,Flowers"
6,"Champagne,Wine Glass"
7,"Cake,Candles,Balloons"
8,"Tent,Flashlight,Marshmallow"
9,"Steak,BBQ Sauce,Black Pepper"
10,"Chocolate,Flowers"
Demo 1: Traditional Recommendation (Association Rules)
What the Common "Steak → BBQ" Logic Actually Does
Apriori is commonly used to learn association rules from a basket dataset.
traditional_pos.py
import pandas as pd
from mlxtend.preprocessing import TransactionEncoder
from mlxtend.frequent_patterns import apriori, association_rules
# Load CSV
df = pd.read_csv("baskets.csv")
transactions = []
for row in df["Items"]:
transactions.append([x.strip() for x in row.split(",")])
# One-hot encoding
te = TransactionEncoder()
te_array = te.fit(transactions).transform(transactions)
basket_df = pd.DataFrame(te_array, columns=te.columns_)
# Apriori
frequent_itemsets = apriori(
basket_df,
min_support=0.2,
use_colnames=True
)
rules = association_rules(
frequent_itemsets,
metric="confidence",
min_threshold=0.5
)
print("\n===== Association Rules =====\n")
for _, r in rules.iterrows():
print(
f"{list(r['antecedents'])} -> {list(r['consequents'])}"
)
# -----------------------------
# Demo basket
# -----------------------------
basket = [
"Steak",
"Champagne",
"Chocolate"
]
print("\n===== Traditional Recommendation =====")
recommendations = set()
for _, r in rules.iterrows():
antecedent = set(r["antecedents"])
if antecedent.issubset(set(basket)):
recommendations.update(r["consequents"])
recommendations -= set(basket)
print("Basket:", basket)
print("Recommendations:", list(recommendations))
Results:
===== Association Rules =====
['Steak'] -> ['BBQ Sauce']
['BBQ Sauce'] -> ['Steak']
['Black Pepper'] -> ['Steak']
['Chocolate'] -> ['Flowers']
['Flowers'] -> ['Chocolate']
===== Traditional Recommendation =====
Basket: ['Steak', 'Champagne', 'Chocolate']
Recommendations: ['Flowers', 'BBQ Sauce']
The signal gathering step (the data pipeline feeding this) could come from the POS system itself. Not bad — at least it's not "credit card, two years running."

Traditional POS logic: steak always means BBQ sauce, no matter who's buying.
Why It Fails: Product Affinity Isn't Customer Intent
The algorithm has no understanding of why those products were purchased. It only knows what tends to sit in the same basket.
Here's what Intent Intelligence does differently: instead of relying only on basket data, much more signal can be brought in.
Signals Worth Enriching
This article focuses on the ones marked with *
- Basket *
- Time
- Store
- Weather
- Cashier
- Customer *
- Payment
- Coupon
- Inventory
- Location
- Holiday *
- Previous visits *
Complicated? No — just more context than a single basket snapshot gives you.
Demo 2: Intent Classification with Embeddings
Now we set Apriori aside entirely. Instead, we compare the basket against a set of possible intents.
all-MiniLM-L6-v2 is the embedding model used here to build the vector representation on the fly.
I previously wrote about building a RAG pipeline with LlamaIndex — Tasting RAG with LlamaIndex — which covers the classic chatbot RAG shape:
Question
|
v
Vector DB
|
v
LLM
This time, the architecture is different. Here, RAG retrieves business context, not an answer to a question:
POS Webhook
|
v
Basket
["Steak","Champagne","Chocolate"]
|
v
Intent Engine
"Romantic Dinner"
|
v
RAG retrieves
- today's promotions
- nearby inventory
- customer preferences
- weather
- local events
- coupon rules
- supplier campaigns
- CRM profile
|
v
LLM generates
"15% off flowers today"
or
"Fresh strawberries available in aisle 5"
or
"Premium wine glasses now 20% off."

From basket data to a personalized, context-aware offer.
intent_rag.py
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
basket = [
"Steak",
"Champagne",
"Chocolate"
]
basket_text = ", ".join(basket)
intent_library = [
"Romantic Dinner",
"Family BBQ",
"Birthday Party",
"Camping",
"Movie Night",
"Weekly Grocery",
"Office Party"
]
basket_embedding = model.encode([basket_text])
intent_embeddings = model.encode(intent_library)
scores = cosine_similarity(
basket_embedding,
intent_embeddings
)[0]
print("\n===== Intent Scores =====\n")
for intent, score in zip(intent_library, scores):
print(f"{intent:20} {score:.3f}")
best = scores.argmax()
intent = intent_library[best]
print("\nDetected Intent")
print(intent)
Results:
===== Intent Scores =====
Romantic Dinner 0.438
Family BBQ 0.348
Birthday Party 0.270
Camping 0.154
Movie Night 0.204
Weekly Grocery 0.175
Office Party 0.234
Detected Intent
Romantic Dinner
Instead of "BBQ Sauce," we now have a far more meaningful marketing signal: Romantic Dinner.
Imagine you're the marketing manager. You immediately have a clearer idea of where to focus, if a significant share of customers walk in with this same intent. What you can sell and plan around is no longer just BBQ Sauce. It's an entire occasion.
This is the power of Leading with Signal: signal → what you should sell.
Why That's Still Not Enough
Knowing the intent — "Romantic Dinner" — is a big improvement. But intent alone doesn't know what's actually happening in the business today: what's on promotion, what's in stock nearby, what holiday it is, what this customer has bought before. Without that layer, "Romantic Dinner" is just a label. The next step is retrieving that context and turning it into an actual offer.
Demo 3: Contextual RAG Retrieves Business Context
Once intent is known, the next step in the pipeline is a RAG layer that recommends what to push, as an SMS, or a QR code on the customer's receipt.
promotion_rag.py
context = {
"Romantic Dinner": {
"promotion": "20% Roses",
"holiday": "Valentine's Day",
"inventory": "Flowers Available"
},
"Family BBQ": {
"promotion": "10% Charcoal",
"holiday": "Summer BBQ Weekend",
"inventory": "BBQ Sauce Available"
},
"Camping": {
"promotion": "15% Flashlights",
"holiday": "National Park Week",
"inventory": "Camping Gear Available"
}
}
intent = "Romantic Dinner"
ctx = context[intent]
print("\n===== Context & Recommendation =====")
print(
f"""
Detected Intent : {intent}
Promotion : {ctx['promotion']}
Holiday : {ctx['holiday']}
Inventory : {ctx['inventory']}
Send customer a QR coupon.
"""
)
Results:
===== Context & Recommendation =====
Detected Intent : Romantic Dinner
Promotion : 20% Roses
Holiday : Valentine's Day
Inventory : Flowers Available
If you noticed there's no real vector database exist in this code. I'm delighted you're still with me this deep into the article. Yes, I used plain-text JSON here, on purpose, to keep the demo simple.
My earlier article on Tasting RAG with LlamaIndex already covers embedding → vector → similarity search → retrieval in depth. This piece focuses on how those same building blocks get repurposed into an Intent Intelligence architecture for physical retail.

Detected Intent: Romantic Dinner — 20% off roses, tied to Valentine's Day, with flowers in stock nearby.
Demo 4: Feedback Loop Improves Future Recommendations
The last two stages in the design — Customer Feedback and Continuous Learning, close the loop: whether a customer redeems the QR offer or ignores it becomes a new signal, feeding back into the intent library and context rules over time. That part isn't built yet in this demo; it's the natural next step once the core pipeline above is running in production.
Key Takeaways
- Association rules (Demo 1) show what sells together, not why. They treat every basket the same, regardless of occasion.
- Intent classification (Demo 2) reframes the signal. The same basket becomes "Romantic Dinner" — a far more useful signal than a single product pairing.
- Intent alone isn't enough. Contextual retrieval (Demo 3) — promotions, inventory, holidays, CRM — turns a detected intent into a specific, timely offer.
- The whole system start at one line:
SentenceTransformer("all-MiniLM-L6-v2"). Simple, but foundational. all-MiniLM-L6-v2is general-purpose. Enterprises serious about this will eventually train their own retail-specific embedding model.
Next up: how enterprises build their own Retail Embedding Models.