A full-stack, event-driven recommendation system that tracks user behavior across 760K+ interactions, dynamically updating personalized product rankings based on purchase intent signals, with results streamed live to an analytics dashboard.
- Project Overview
- Architecture
- Tech Stack
- Dataset
- Project Structure
- Prerequisites
- First-Time Setup
- Running the Project
- Dashboard Features
- API Reference
- Recommendation Model
- Troubleshooting
ShopFlow is a real-time behavioral recommendation engine built on actual e-commerce data. Events stream through Apache Kafka, scores are computed in real time by Apache Spark Structured Streaming, and results are delivered instantly to a React dashboard via WebSocket.
The system reflects recommendation changes within seconds of user activity — behaving like a production-grade recommendation pipeline rather than a static ML project.
events_new.csv + sessions.csv
│
▼
kafka_producer.py ──────────────► Kafka Topic: click_events
│
┌───────────────┴───────────────┐
▼ ▼
kafka_consumer.py realtime_recommender.py
│ (Spark Structured Streaming)
▼ ▼
user_click_logs user_recommendations
sessions (PostgreSQL) (PostgreSQL)
│ │
└───────────────┬───────────────┘
▼
FastAPI Backend
(REST APIs + WebSocket)
│
▼
React Frontend
(Live Dashboard at :3000)
kafka_producer.pyreadsevents_new.csv+sessions.csv, joins them to attachcustomer_idto each event, and replays them chronologically into theclick_eventsKafka topic.kafka_consumer.pyreads from Kafka and writes each event touser_click_logsand each session to thesessionstable in PostgreSQL.realtime_recommender.py(Spark) reads from the same Kafka topic, applies weighted scoring, and upserts aggregated scores intouser_recommendations.- FastAPI serves the frontend via REST endpoints and a WebSocket that streams events directly from Kafka.
- React polls recommendation endpoints every 3 seconds and receives live events via WebSocket.
| Layer | Technology |
|---|---|
| Event Streaming | Apache Kafka 3.4.2 (KRaft mode) |
| Stream Processing | Apache Spark 4.1.1 Structured Streaming |
| Backend API | FastAPI + SQLAlchemy + aiokafka |
| Database | PostgreSQL |
| Frontend | React 18 + Vite + Recharts |
| Data Processing | pandas |
| Language | Python 3.13 / JavaScript (ES2022) |
Seven CSV files located in ecommerce_data/:
| File | Rows | Description |
|---|---|---|
customers.csv |
20,000 | User profiles — name, email, country, age |
products.csv |
1,197 | Product catalog — name, category, price, margin |
orders.csv |
33,580 | Completed purchases with payment and device info |
order_items.csv |
59,163 | Line items per order |
events_new.csv |
760,958 | User interactions — page views, add-to-cart, checkout, purchase |
sessions.csv |
120,000 | Browsing sessions with device, source, country |
reviews.csv |
10,780 | Product ratings and review text |
Static data (customers, products, orders, order_items, reviews) is loaded once via seed_db.py.
Real-time data (events, sessions) streams through Kafka and is written to PostgreSQL as it arrives.
ecommerce_pipeline/
├── ecommerce_data/
│ ├── customers.csv
│ ├── products.csv
│ ├── orders.csv
│ ├── order_items.csv
│ ├── events_new.csv
│ ├── sessions.csv
│ └── reviews.csv
│
├── backend/
│ ├── .env # Your local DB credentials (never commit this)
│ ├── .gitignore # Excludes .env from version control
│ ├── main.py # FastAPI app — all REST + WebSocket endpoints
│ ├── database.py # SQLAlchemy engine — reads credentials from .env
│ ├── seed_db.py # One-time static data loader
│ ├── kafka_producer.py # Replays events_new.csv into Kafka
│ ├── kafka_consumer.py # Writes Kafka events → PostgreSQL
│ └── realtime_recommender.py # Spark Structured Streaming job
│
├── frontend/
│ ├── src/
│ │ ├── App.jsx
│ │ ├── index.css
│ │ ├── api.js
│ │ └── components/
│ │ ├── EventStream.jsx
│ │ ├── RecommendationPanel.jsx
│ │ ├── TopProducts.jsx
│ │ ├── SalesTrend.jsx
│ │ ├── CategorySales.jsx
│ │ ├── TopUsers.jsx
│ │ ├── DeviceStats.jsx
│ │ └── CountryMap.jsx
│ ├── package.json
│ └── vite.config.js
│
└── README.md
Credentials are stored in a .env file inside the backend/ folder and are never committed to version control.
cd ~/ecommerce_pipeline/backend
touch .envDB_USER=postgres
DB_PASSWORD=your_password_here
DB_HOST=localhost
DB_PORT=5432
DB_NAME=ecommerce_dbecho ".env" >> ~/ecommerce_pipeline/backend/.gitignoresource ../venv/bin/activate
pip install python-dotenvAnyone cloning this project on a new machine must create their own
.envwith their local PostgreSQL credentials before running anything. The app will not start without it.
Make sure these are installed inside WSL (Debian/Ubuntu):
- Python 3.13 with
pip - Node.js (via
nvm) — must be WSL-native, not Windows - Java 11+ (required for Kafka and Spark)
- Apache Kafka 3.4.2 at
~/kafka - Apache Spark 4.1.1 with
spark-submiton PATH - PostgreSQL running as a service
- A Python virtual environment at
ecommerce_pipeline/venv
cd ~/ecommerce_pipeline
source venv/bin/activate
pip install fastapi uvicorn[standard] sqlalchemy psycopg2-binary \
kafka-python aiokafka pandas pysparkcd ~/ecommerce_pipeline/frontend
npm installImportant: always run
npmfrom inside WSL terminal. Never run it from Windows PowerShell or CMD — it will fail with UNC path errors.
These steps are run once only when setting up the project from scratch.
Connect to PostgreSQL and run:
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN (
SELECT tablename FROM pg_tables WHERE schemaname = 'public'
)
LOOP
EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END $$;cd ~/ecommerce_pipeline/backend
source ../venv/bin/activate
python seed_db.pyThis loads customers, products, orders, order_items, and reviews into PostgreSQL. It also clears the sessions, user_click_logs, and user_recommendations tables for a fresh start.
Expected output:
=== Seeding PostgreSQL with static data ===
loaded customers.csv: 20000 rows
loaded products.csv: 1197 rows
loaded orders.csv: 33580 rows
loaded order_items.csv: 59163 rows
loaded reviews.csv: 10780 rows
-- Creating tables...
-- Loading static tables...
-- Clearing real-time tables for fresh start...
=== Done! Run kafka_producer.py to start streaming. ===
cd ~/kafka
bin/kafka-storage.sh format --standalone -t $(bin/kafka-storage.sh random-uuid) -c config/server.propertiesOnly run this once. Running it again will wipe all Kafka topics and offsets.
Open 5 separate WSL terminals and run each command in order. Wait for each service to be ready before starting the next.
cd ~/kafka
bin/kafka-server-start.sh config/server.propertiesWait until you see:
Kafka Server started
cd ~/ecommerce_pipeline/backend
source ../venv/bin/activate
uvicorn main:app --reload --port 8000Wait until you see:
Application startup complete.
Verify it's running:
curl http://localhost:8000/
# → {"message":"Recommendation API running"}cd ~/ecommerce_pipeline/backend
source ../venv/bin/activate
spark-submit --packages org.apache.spark:spark-sql-kafka-0-10_2.13:4.1.1 realtime_recommender.pyWait until you see:
Streaming query started
Spark will download the Kafka connector on first run — this may take a few minutes.
cd ~/ecommerce_pipeline/backend
source ../venv/bin/activate
python kafka_producer.py --speed 5This replays events_new.csv at 5 events per second into the click_events Kafka topic.
Speed options:
python kafka_producer.py # 1 event/sec (default)
python kafka_producer.py --speed 5 # 5 events/sec
python kafka_producer.py --speed 20 # 20 events/sec
python kafka_producer.py --speed 0 # as fast as possiblecd ~/ecommerce_pipeline/backend
source ../venv/bin/activate
python kafka_consumer.pyThis reads events from Kafka and writes them to user_click_logs and sessions in PostgreSQL — powering the KPIs, event stream, country map, and device stats on the dashboard.
cd ~/ecommerce_pipeline/frontend
npm run devOpen your browser at:
http://localhost:3000
| Panel | Description | Data Source |
|---|---|---|
| KPI Strip | Total events, active users, purchases, avg score for the current data day | user_click_logs |
| Live Event Stream | Real-time feed of user interactions via WebSocket | Kafka → WebSocket |
| Sales Trend | Line chart of revenue + order count, filterable by granularity (hour/day/month/year) and date range | user_click_logs (session-deduplicated) |
| Top Products | Bar chart of most interacted products by weighted score | Live event stream |
| Top Users | Top 3 users by total interaction score with real names | user_click_logs + customers |
| Recommendations | Personalized ranked product recommendations per user (search by user ID 1–20000) | user_recommendations |
| Sales by Category | Revenue breakdown by product category from streamed events | user_click_logs + products |
| Traffic by Device | Donut chart of mobile/desktop/tablet split | sessions |
| Traffic by Source | Donut chart of organic/paid/email/direct split | sessions |
| Active Users by Country | World map with color intensity showing user distribution for current day | sessions |
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Health check |
| GET | /search?query=&min_price=&max_price= |
Product search |
| GET | /recommend/top |
Top products across all users |
| GET | /recommend/{user_id} |
Personalized recommendations for a user |
| GET | /recent-events |
Last 30 events from click logs |
| POST | /log-click?user_id=&product_id=&event_type= |
Log a manual event |
| GET | /stats/daily |
KPI stats for current data day |
| GET | /stats/sales-trend |
Sales over time (session-deduplicated) |
| GET | /stats/sales-trend/filters |
Available years/months/days from data |
| GET | /stats/top-categories |
Revenue by product category |
| GET | /stats/active-countries |
Active users by country (current day) |
| GET | /stats/top-users |
Top 3 users by interaction score |
| GET | /stats/devices |
Device and source breakdown |
| WS | /ws/events |
WebSocket — live event stream from Kafka |
ShopFlow uses a weighted implicit feedback scoring model — purchase intent is inferred from user behavior rather than explicit ratings.
| Event Type | Score | Reasoning |
|---|---|---|
page_view |
1 | Low intent — passive browsing |
add_to_cart |
5 | Medium intent — actively considering |
checkout |
7 | High intent — about to purchase |
purchase |
10 | Strongest signal — confirmed buy |
- Each event from Kafka is scored based on its type.
- Spark aggregates scores per
(user_id, product_id)pair usingSUM. - Products are ranked per user by descending score.
- The frontend polls
/recommend/{user_id}every 3 seconds to reflect updates.
A user who viewed product A three times and added it to cart once gets:
score(A) = (3 × 1) + (1 × 5) = 8
If they then purchase it:
score(A) = 8 + 10 = 18 → moves to top of recommendations
You are running npm from Windows. Open a WSL terminal and run from there.
Your Kafka uses config/server.properties, not config/kraft/server.properties. Use:
bin/kafka-server-start.sh config/server.propertiesUse the --standalone flag:
bin/kafka-storage.sh format --standalone -t $(bin/kafka-storage.sh random-uuid) -c config/server.propertiesFastAPI is missing the uvicorn[standard] package. Run:
pip install 'uvicorn[standard]'kafka_consumer.py is not running. Start it in a new terminal. Then restart kafka_producer.py to stream fresh events.
Use -h localhost to force TCP connection:
psql -U postgres -d ecommerce_db -h localhostCheck that realtime_recommender.py is running and that the Kafka topic name in all files is click_events.
Events need event_type = 'purchase' and a non-null amount_usd to appear in the sales trend. Run the producer long enough for purchase events to stream through.