Every recommendation tutorial I found was either a Netflix black box or a 1,000-row Jupyter notebook toy. I wanted something in between β real, deployable, and something I actually understood.
That's how Inkpick was born: a hybrid recommendation engine across cinema, music, and courses with sub-50ms inference on 80,000+ items. Just NumPy, FastAPI, and deliberate design choices.
What "Hybrid" Means
Content-Based Filtering β works on day one, no user history needed. But it traps users in a bubble.
Collaborative Filtering β discovers surprising cross-user patterns. Falls apart for new users (cold-start problem).
A hybrid blends both:
score_hybrid(i) = Ξ± Β· score_cb(i) + (1 - Ξ±) Β· score_cf(i)
Inkpick defaults Ξ± = 0.65 β content-biased for cold-start users, shifting toward collaborative as history grows.
plaintext
The Architecture
Client (Vanilla JS)
β
FastAPI (Async)
ββββββ΄βββββ¬βββββββββββ
TF-IDF Latent Levenshtein
+ CSR Factor Fuzzy Search
ββββββ¬βββββ
Hybrid Layer
β
Service Registry
(cinema / audio / edu)
Each domain is fully decoupled. Adding a new domain = one new service file.
Content-Based: TF-IDF + Cosine Similarity
TF-IDF turns item metadata (title, genre, tags) into vectors. Words unique to one item = high weight. Common words like "the" = penalized.
Similarity between items is then a dot product:
similarity(q, i) = (q Β· i) / (βqβ Β· βiβ)
Why not SciPy? Inkpick implements CSR (Compressed Sparse Row) ops directly in NumPy β cutting a ~30MB dependency, reducing memory, and keeping full control over the pipeline. An 80,000-item matrix is ~98% zeros; CSR stores only non-zero values.
Collaborative Filtering: Latent Factors
CF decomposes the userβitem interaction matrix into lower-dimensional embeddings:
R β U Γ Vα΅
These latent dimensions learn hidden patterns β "likes slow-burn thrillers" β without being told. In Inkpick, this module is a production-ready stub awaiting a trained ALS/BPR model. Honest limitation, next on the roadmap.
Fuzzy Search Fallback
Search "Godfater" β no match β system fails. Not ideal.
Inkpick uses Levenshtein edit-distance as a safety net:
"Godfater" β "Godfather" = 1 edit
When exact search fails, fuzzy kicks in and returns the closest matches. Small addition, big UX improvement.
The API
GET /recommend/cinema?item_id=tt0111161&top_k=5&mode=hybrid
json{
"domain": "cinema",
"results": [{ "title": "The Godfather", "score": 0.94 }],
"latency_ms": 38
}
The mode param accepts content, collaborative, or hybrid β handy for debugging.
What I'd Fix in v2
Train the CF model β ALS or BPR. The hybrid is only as good as both components.
SBERT over TF-IDF β semantic similarity that keyword matching completely misses.
Add evaluation metrics β Precision@K, NDCG. Fast latency is measurable; recommendation quality currently isn't.
Dynamic Ξ± β learn the blend weight per user instead of hardcoding 0.65.
Diversity control β MMR to avoid returning "10 Batman movies."
Try It
live : inkpick.vercel.app
github : github.com/MayankParashar28/inkpick
Drop a comment if you're building something similar β would love to exchange notes.









![Defluffer - reduce token usage π by 45% using this one simple trick! [Earthday challenge]](https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiekbgepcutl4jse0sfs0.png)


