Modular recommendation system built from first principles.
Event-driven ingestion, product statistics, explainable ranking, and a roadmap that grows from simple signals into graph, collaborative, and hybrid recommendation strategies.
Most recommendation systems are presented as black boxes or begin too late in the stack. This project takes the opposite route: start with explicit events and transparent statistics, then evolve toward relationships, similarity, collaborative filtering, and hybrid scoring.
The goal is not only to produce recommendations, but to understand exactly why they appear.
The repository implements the full roadmap (Phases 1-5) from first principles:
- event ingestion (single, batch, and a Server-Sent Events stream)
- in-memory and PostgreSQL-backed event stores
- product statistics and popular-products ranking
- co-purchase graph with an interactive, live-updating visualizer
- association rules reweighted by a recommendation feedback loop
- customer profiles and user-based collaborative filtering
- co-occurrence product embeddings and taste-profile scoring
- trend/momentum signals
- a weighted, explainable hybrid ranking that blends every signal
- offline leave-one-out benchmarking across strategies
- A/B experiments with deterministic assignment and best-variant selection
- an HTTP API, a CLI client, and a local playground
Statistics
->
Association Rules
->
Product Graph
->
Collaborative Filtering
->
Machine Learning
->
Hybrid Recommendation System
Install dependencies:
bun installRun the local API (in-memory store, no database required):
bun run dev:apiRun the sample playground — an in-process tour of every strategy (statistics, associations, collaborative filtering, embeddings, trends, hybrid ranking, benchmarking, and an A/B report) with no server or database required:
bun run playgroundapps/cli is an HTTP client for the API with tables, colors, and an interactive
purchase flow. Start the API in one terminal (bun run dev:api), then:
bun run cli seed # populate a sample dataset
bun run cli popular --limit 5
bun run cli associations bread # feedback-adjusted recommendations
bun run cli co-purchases bread
bun run cli stats
bun run cli feedback
bun run cli events --limit 20
bun run cli customers # customer profiles
bun run cli customer c-1 # profile + collaborative recs + similar
bun run cli hybrid c-1 # blended popularity + association + collaborative + trend
bun run cli hybrid c-1 --w-collab 1 --w-pop 0 --w-assoc 0 --w-trend 0
bun run cli trending # products with recent momentum
bun run cli similar-products bread # embedding neighbors (shared context)
bun run cli embedding c-1 # embedding-based recommendations
bun run cli evaluate # leave-one-out benchmark of strategies
bun run cli experiments # list A/B experiments
bun run cli experiment default c-1 # variant assignment + recommendations
bun run cli experiment-report default # conversion + best-converting variant
bun run cli graph # co-purchase edges + visualizer URL
bun run cli purchase -i bread:1:2.5 -i milk:2:1.8 -c customer-1
bun run cli purchase # interactive prompts
bun run cli healthOpen http://localhost:3000/graph/view in a browser for an interactive,
force-directed view of the co-purchase graph. It subscribes to
/events/stream and re-renders in real time as new purchases arrive.
Target a non-default API with --api <url> or the API_URL environment variable.
Type-check and test the workspace:
bun run check
bun run testThe PostgreSQL integration test is skipped by default. With a reachable database (migration applied) it runs via:
RUN_PG_TESTS=1 DATABASE_URL=postgres://app:app@localhost:5432/recommendation_engine bun run test:pgAvailable routes:
GET /healthGET /recommendations/popular?limit=5GET /recommendations/hybrid?customer=c-1&limit=5(optionalwPop,wAssoc,wCollab,wTrendweights)GET /recommendations/trending?limit=5&windowDays=30GET /evaluate?k=5— leave-one-out benchmark across strategiesGET /experiments·POST /experimentsGET /experiments/:id/report— A/B conversion + best variantGET /experiments/:id/recommendations?customer=c-1&limit=5GET /stats/productsGET /graph/co-purchases?productId=bread&limit=5GET /associations?productId=bread&limit=5GET /feedback/statsGET /events?limit=50GET /customersGET /customers/:id/profileGET /customers/:id/recommendations?limit=5GET /customers/:id/similar?limit=5GET /customers/:id/embedding-recommendations?limit=5GET /products/:id/similar?limit=5(embedding-based)GET /graphGET /graph/view— interactive graph visualizer (open in a browser)POST /eventsPOST /events/purchasePOST /events/batch— ingest an array of eventsGET /events/stream— Server-Sent Events stream of ingested events + live snapshot
Association ranking is reweighted by recommendation feedback: RecommendationAccepted
and RecommendationIgnored events raise or lower a target's feedbackFactor, which
scales its adjustedScore and can reorder results.
Example purchase ingestion:
curl -X POST http://localhost:3000/events/purchase \
-H "Content-Type: application/json" \
-d '{
"orderId": "ord-2001",
"customerId": "customer-10",
"items": [
{ "productId": "bread", "quantity": 1, "unitPrice": 2.5 },
{ "productId": "milk", "quantity": 2, "unitPrice": 1.8 }
]
}'Each strategy is an independent, explainable layer. The hybrid ranker blends them.
| Strategy | Signal | Explanation it produces |
|---|---|---|
| Popular | purchase frequency + volume | "popular product #N" |
| Association | support / confidence / lift, reweighted by feedback | "customers who buy X also buy Y" |
| Collaborative | Jaccard similarity between customers | "customers similar to you bought this" |
| Embeddings | cosine over co-purchase vectors | "appears in similar purchase contexts" |
| Trend | recent vs previous window momentum | "up N% in the last 30 days" |
| Hybrid | normalized, weighted blend of all of the above | "driven mainly by " |
HTTP API / CLI / live graph viewer
|
v
Recommendation Engine (event-driven, learns incrementally)
|
+--> Event Store (memory | PostgreSQL)
+--> Statistics --> Popular ranking
+--> Co-purchase graph --> Association ranking (x feedback loop)
+--> Customer profiles --> Collaborative filtering
+--> Product embeddings --> taste-profile scoring
+--> Trend tracker --> momentum
+--> Hybrid ranker (weighted blend) --> A/B experiments
+--> Evaluation harness (offline benchmark)
More detail: Architecture
If DATABASE_URL is set, the API uses PostgreSQL as the event store instead of memory.
docker-compose.yml runs Postgres and applies migrations/*.sql automatically on first
start. Copy the environment template and bring the database up:
cp .env.example .env
bun run db:up # start Postgres in the background
bun run dev:api # API picks up DATABASE_URL from .envRun the whole stack (API + Postgres) in containers:
bun run stack:up # build + start api and postgres
bun run stack:down # stop everythingUseful scripts: db:up, db:down, db:logs, stack:up, stack:down.
Point DATABASE_URL at any Postgres instance and apply the migration once:
psql "$DATABASE_URL" -f migrations/001_postgres_event_store.sql
DATABASE_URL=postgres://app:app@localhost:5432/recommendation_engine bun run dev:apiOptional: DATABASE_POOL_MAX tunes the connection pool size (default 10).
apps/
api/ local HTTP API
cli/ HTTP client CLI
playground/ sample dataset runner
packages/
engine/ orchestration layer
customers/ customer profiles + collaborative filtering
embeddings/ co-occurrence product embeddings
evaluation/ leave-one-out strategy benchmark
experiments/ A/B testing + variant assignment
feedback/ recommendation feedback tracker
graph/ co-purchase graph
hybrid/ weighted multi-signal ranking
ranking/ ranking strategies
shared/ shared domain types and validation
similarity/ set-similarity metrics (Jaccard, cosine)
statistics/ incremental product statistics
storage/ event storage abstractions
trends/ recent-momentum trend signals
docs/
ARCHITECTURE.md current system shape
ROADMAP.md project roadmap
migrations/
001_postgres_event_store.sql
docker-compose.yml Postgres + optional API stack
Dockerfile Bun API image
assets/
banner.svg repository banner
All phases implemented:
- Phase 1: event ingestion, storage, statistics, popular products
- Phase 2: co-purchase graph, associations, similarity
- Phase 3: collaborative filtering and explainable ranking
- Phase 4: embeddings, trend signals, hybrid ranking, evaluation
- Phase 5: streaming, real-time refresh, A/B experiments
Next: persist precomputed statistics and graph, and add automated PostgreSQL integration coverage. Full roadmap: docs/ROADMAP.md
- modular architecture
- explainability over opacity
- event-driven design
- algorithm-first evolution
- extensible package boundaries
- reproducible experiments
The engine is intentionally domain-agnostic:
- e-commerce
- grocery and retail
- media and streaming
- education platforms
- hospitality
- financial products
- inventory optimization
All five roadmap phases are implemented and covered by unit tests. Every strategy is runnable end to end through the HTTP API, the CLI, and the live graph viewer, against either the in-memory store or PostgreSQL. It remains a research project: the model is rebuilt in memory from the event log on startup rather than served from precomputed, persisted features.
This project is licensed under the MIT License.