shakeb.techAll writingWork with me

Machine Learning · · 6 min read

Building Toronto Trendspotter: A Hyperlocal Recommendation Engine

How I combined collaborative filtering, content-based signals, and location intelligence to build a Pinterest-style discovery experience for Toronto.

In today's digital landscape, content discovery platforms like Pinterest excel at connecting users with visual content that matches their interests. But what if we could take that powerful recommendation capability and apply it to a specific geographic context? That question sparked Toronto Trendspotter — a machine learning-powered platform designed to deliver hyperlocal content recommendations for Canada's largest city.

Toronto Trendspotter combines the visual discovery approach of platforms like Pinterest with location-specific intelligence to create a unique exploration experience. In this post, I walk through the technical architecture behind the system, with a particular focus on the machine learning models that power its recommendation engine.

The challenge: hyperlocal content discovery

Building a recommendation system for local content presents several unique challenges:

  • Geographic relevance — recommendations must balance user interests with physical proximity and neighborhood relevance
  • Temporal factors — local content (especially events) has strong seasonal and temporal dimensions
  • Cold start problems — new users need good recommendations without extensive interaction history
  • Data sparsity — location-specific content has smaller datasets than global platforms

To address these challenges, Toronto Trendspotter implements a hybrid recommendation system that combines multiple algorithms with location-aware enhancements.

Architecture overview: a multi-model approach

The recommendation engine implements a weighted hybrid system with four key components:

1. Collaborative filtering (35%) 2. Content-based filtering (30%) 3. Location-based boosting (25%) 4. Temporal relevance (10%)

1. Collaborative filtering component (35%)

The collaborative filtering module identifies patterns in user interactions to find hidden connections between users and content:

def _recommend_item_based(self, user_idx, n=10):
    user_interactions = self.user_item_matrix[user_idx].toarray().flatten()
    interacted_items = np.where(user_interactions > 0)[0]

    if len(interacted_items) == 0:
        return self._recommend_popular_items(n=n)

    scores = np.zeros(self.user_item_matrix.shape[1])

    for item_idx in interacted_items:
        similar_items = self.item_similarity[item_idx]
        weight = user_interactions[item_idx]
        scores += similar_items * weight

    scores[interacted_items] = 0
    top_indices = np.argsort(-scores)[:n]
    # ... format and return recommendations

This component uses item-based similarity to recommend content that users with similar tastes have enjoyed. While Pinterest likely uses more sophisticated matrix factorization techniques at scale, this approach works well for our dataset size while addressing the same fundamental pattern recognition needs.

Key features:

  • Item-item similarity matrix computation
  • User interaction weighting
  • Popularity fallbacks for cold-start users

2. Content-based component (30%)

The content-based module analyzes content features to find items similar to what a user has previously enjoyed:

def _recommend_based_on_profile(self, user_id, n=10, **kwargs):
    profile = self.user_profiles[user_id]
    interacted_items = {i["content_id"] for i in self.db.interactions.find({"user_id": user_id})}
    scores = {}

    if profile.get("vector") is not None:
        user_vector = np.array(profile["vector"])
        for i, content_id in enumerate(self.rev_content_map.values()):
            if content_id in interacted_items:
                continue
            scores[content_id] = cosine_similarity([user_vector], [text_matrix[i]])[0][0]

    if profile.get("neighborhoods"):
        for content_id, loc_data in location_features.items():
            neighborhood = loc_data.get("neighborhood")
            if neighborhood in profile["neighborhoods"]:
                scores[content_id] = scores.get(content_id, 0) + profile["neighborhoods"][neighborhood]
    # ... return top-scoring items

This approach creates feature vectors from content text, categories, and tags. It then builds user profile vectors based on interaction history and calculates similarity between user profiles and content items.

While Pinterest built its content understanding on sophisticated computer vision, Toronto Trendspotter focuses on text and category features with neighborhood dimensions for its location-specific approach.

Key features:

  • Text feature extraction with TF-IDF
  • Category and tag embedding
  • User profile vector construction
  • Neighborhood preference modeling

3. Location-based component (25%)

What makes Toronto Trendspotter unique is its location-awareness. The location module enhances recommendations with Toronto-specific geographic relevance:

def _apply_toronto_boosts(self, recommendations, user_id):
    seasonal_boost = self.parameters.get("seasonal_boost", 0.2)
    location_boost = self.parameters.get("location_boost", 0.3)
    current_season = self._get_current_season()

    for content_id, rec in recommendations.items():
        content = self.get_content_data(content_id)
        if not content:
            continue

        if content.get("metadata", {}).get("seasonal_relevance") == current_season:
            rec["score"] += seasonal_boost

        content_neighborhood = content.get("location", {}).get("neighborhood")
        if content_neighborhood in user_neighborhoods:
            rec["score"] += location_boost

This component applies seasonal relevance, neighborhood preference boosts, and proximity-based adjustments to make recommendations geographically relevant.

Key features:

  • Neighborhood preference modeling
  • Seasonal content boosting
  • Proximity-based scoring adjustments
  • Event-specific temporal boosts

4. Temporal component (10%)

The temporal module ensures recommendations are timely and seasonally appropriate — filtering events by seasonal_relevance, boosting content tagged for the current season, and scoring by event date proximity.

Key features:

  • Seasonal detection
  • Event date proximity scoring
  • Trending content identification

The result: a unified hybrid system

The hybrid recommender combines outputs from all these models to provide a single ranked list of recommendations:

def recommend_for_user(self, user_id, n=10, **kwargs):
    all_recommendations = {}

    for model_name, model in self.models.items():
        weight = self.model_weights.get(model_name, 0)
        if weight <= 0:
            continue

        for rec in model.recommend_for_user(user_id, n=n * 2):
            content_id = rec["content_id"]
            score = rec["score"] * weight
            if content_id in all_recommendations:
                all_recommendations[content_id]["score"] += score
            else:
                all_recommendations[content_id] = {"content_id": content_id, "score": score}

    self._apply_toronto_boosts(all_recommendations, user_id)
    return sorted(all_recommendations.values(), key=lambda x: x["score"], reverse=True)[:n]

This approach is similar to the hybrid recommendation systems that power platforms like Pinterest, but with an added focus on geographic and temporal relevance specific to Toronto.

Frontend visualization: making recommendations understandable

Just as important as the backend recommendation logic is how we present these recommendations to users. Toronto Trendspotter includes several key frontend features:

1. Transparent recommendations — optional explanation panels show users why content was recommended 2. Interactive map — a neighborhood-based map visualization allows for geographical discovery 3. A/B testing framework — different recommendation explanation variants are tested for effectiveness 4. Seasonal content sections — content is organized by seasonal relevance

Data collection and processing

To build this system, I created a data pipeline with these stages:

1. Content collection — gathering Toronto-related content from various sources 2. Feature extraction — text processing with NLP techniques, category and tag embedding, location data enhancement 3. User profile building — creating user preference vectors from interaction data 4. Model training — training the component models and tuning weights

The current system uses curated mock data for demonstration, with plans to integrate live Toronto data sources in the future.

Comparing to Pinterest: similarities and differences

While inspired by Pinterest's visual discovery approach, Toronto Trendspotter differentiates itself in several ways. Pinterest excels at understanding the visual content of pins and user interests at a global scale. Toronto Trendspotter focuses on understanding the geographic and temporal dimensions of content in a specific city.

Lessons learned

Through developing Toronto Trendspotter, I gained several insights about building recommendation systems:

1. Model hybridization is powerful — different models capture different patterns in user behavior 2. Local context changes the game — geographic proximity fundamentally alters what makes a good recommendation 3. Cold-start solutions — a location focus helps provide reasonable recommendations even for new users 4. Balancing factors — careful weighting between interest, location, and seasonality is critical 5. Explainability matters — users appreciate understanding why they see certain recommendations

Technical implementation details

Toronto Trendspotter is built with a modern tech stack:

  • Backend: Python, FastAPI, MongoDB
  • Recommendation engine: NumPy, SciPy, scikit-learn
  • Frontend: React, TypeScript, Leaflet.js
  • Deployment: AWS (backend), Vercel (frontend)

The recommendation models leverage cosine similarity for vector comparison, TF-IDF for text feature extraction, matrix factorization for collaborative filtering, and weighted model fusion for the hybrid approach.

Future directions

Looking ahead, there are several exciting ways to enhance Toronto Trendspotter:

1. Computer vision integration — add image understanding similar to Pinterest's approach 2. Real-time event integration — connect with event APIs for live data 3. Transportation intelligence — add public transit and walking time information 4. Social features — allow users to share discoveries and follow others 5. Mobile application — create a native mobile experience

Conclusion

Toronto Trendspotter demonstrates how recommendation systems can be enhanced with location awareness to create a discovery experience tailored to a specific city. While platforms like Pinterest excel at global visual discovery, there's immense value in hyperlocal recommendation systems that understand the unique character and geography of specific places.

By combining collaborative filtering, content-based analysis, and location intelligence, we can create discovery experiences that help users explore the world immediately around them in more meaningful ways.

Try Toronto Trendspotter: https://trendspotter-v1.vercel.app

Note: The current version uses a curated mock dataset. Integration with live Toronto data sources is in progress.

S
Written by Shakeb

I build products end-to-end — and write down what I learn along the way. If this was useful, there's more where that came from.

I'm open to new opportunities.

Senior software engineering roles, interesting products, hard problems. If you're hiring, I'd love to hear what you're building.

Get in touch