Build Weather-Driven E-commerce Recommendations with Python

Introduction: Weather-Driven Commerce Intelligence

Weather profoundly influences consumer behaviour, driving £3.2 billion in weather-dependent purchases across the UK annually. From hot weather boosting ice cream sales by 300% to rainy days increasing umbrella purchases by 850%, savvy e-commerce platforms are leveraging real-time weather data to create intelligent recommendation engines that anticipate customer needs.

In this comprehensive tutorial, we’ll build a sophisticated weather-driven product recommendation system using Python and WeatherAPI. Our system will analyse local weather conditions, map them to product categories, and dynamically adjust recommendations to maximise conversion rates and customer satisfaction.

Architecture Overview

Our recommendation engine comprises three core components:

  • Weather Data Processor: Fetches and analyses current conditions using WeatherAPI
  • Product Correlation Engine: Maps weather patterns to product categories with weighted scoring
  • Recommendation Generator: Combines weather insights with user preferences and inventory data

The system processes over 15 weather parameters including temperature, precipitation, wind speed, UV index, and visibility to generate contextually relevant product suggestions.

Setting Up the Foundation

Begin by installing the required dependencies and setting up your project structure:

pip install requests pandas numpy scikit-learn flask

Create your main application structure:

weather-recommendations/
├── app.py
├── weather_processor.py
├── recommendation_engine.py
├── product_mappings.py
└── config.py

Configure your WeatherAPI credentials in config.py:

import os

class Config:
    WEATHER_API_KEY = os.environ.get('WEATHER_API_KEY') or 'YOUR_API_KEY'
    WEATHER_API_BASE = 'https://api.weatherapi.com/v1'
    
    # Product correlation weights
    WEATHER_WEIGHTS = {
        'temperature': 0.3,
        'precipitation': 0.25,
        'wind_speed': 0.15,
        'humidity': 0.1,
        'uv_index': 0.1,
        'visibility': 0.1
    }

Building the Weather Data Processor

The weather processor fetches comprehensive weather data and transforms it into structured insights for our recommendation engine:

import requests
import json
from datetime import datetime
from config import Config

class WeatherProcessor:
    def __init__(self):
        self.api_key = Config.WEATHER_API_KEY
        self.base_url = Config.WEATHER_API_BASE
        
    def get_current_weather(self, location):
        """Fetch current weather with comprehensive data"""
        try:
            params = {
                'key': self.api_key,
                'q': location,
                'aqi': 'yes'
            }
            
            response = requests.get(
                f"{self.base_url}/current.json",
                params=params,
                timeout=5
            )
            response.raise_for_status()
            return response.json()
            
        except requests.RequestException as e:
            print(f"Weather API error: {e}")
            return None
    
    def get_forecast_weather(self, location, days=3):
        """Fetch multi-day forecast for trend analysis"""
        try:
            params = {
                'key': self.api_key,
                'q': location,
                'days': days,
                'aqi': 'yes',
                'alerts': 'yes'
            }
            
            response = requests.get(
                f"{self.base_url}/forecast.json",
                params=params,
                timeout=5
            )
            response.raise_for_status()
            return response.json()
            
        except requests.RequestException as e:
            print(f"Forecast API error: {e}")
            return None
    
    def extract_weather_features(self, weather_data):
        """Extract relevant features for recommendation engine"""
        if not weather_data:
            return None
            
        current = weather_data['current']
        location = weather_data['location']
        
        features = {
            'location': f"{location['name']}, {location['country']}",
            'temperature_c': current['temp_c'],
            'feels_like_c': current['feelslike_c'],
            'condition': current['condition']['text'].lower(),
            'condition_code': current['condition']['code'],
            'humidity': current['humidity'],
            'precipitation_mm': current['precip_mm'],
            'wind_kph': current['wind_kph'],
            'uv_index': current['uv'],
            'visibility_km': current['vis_km'],
            'pressure_mb': current['pressure_mb'],
            'is_day': bool(current['is_day']),
            'timestamp': datetime.now().isoformat()
        }
        
        # Add derived features
        features.update(self._calculate_derived_features(features))
        return features
    
    def _calculate_derived_features(self, features):
        """Calculate additional weather insights"""
        temp = features['temperature_c']
        
        return {
            'temperature_category': self._categorise_temperature(temp),
            'weather_comfort_index': self._calculate_comfort_index(features),
            'outdoor_activity_score': self._calculate_outdoor_score(features),
            'season_alignment': self._get_season_alignment()
        }
    
    def _categorise_temperature(self, temp):
        """Categorise temperature for product mapping"""
        if temp < 5: return 'very_cold'
        elif temp < 15: return 'cold'
        elif temp < 22: return 'mild'
        elif temp < 28: return 'warm'
        else: return 'hot'
    
    def _calculate_comfort_index(self, features):
        """Calculate weather comfort score (0-100)"""
        temp_score = max(0, 100 - abs(features['temperature_c'] - 20) * 5)
        humidity_score = max(0, 100 - abs(features['humidity'] - 50) * 2)
        wind_score = max(0, 100 - features['wind_kph'] * 3)
        
        return (temp_score + humidity_score + wind_score) / 3
    
    def _calculate_outdoor_score(self, features):
        """Calculate outdoor activity suitability (0-100)"""
        factors = [
            100 - features['precipitation_mm'] * 20,
            min(100, features['visibility_km'] * 10),
            100 - max(0, features['wind_kph'] - 15) * 5,
            features['uv_index'] * 10 if features['uv_index'] < 8 else 50
        ]
        
        return max(0, sum(factors) / len(factors))
    
    def _get_season_alignment(self):
        """Determine current season for UK"""
        month = datetime.now().month
        if month in [12, 1, 2]: return 'winter'
        elif month in [3, 4, 5]: return 'spring'
        elif month in [6, 7, 8]: return 'summer'
        else: return 'autumn'

Product Mapping and Correlation System

Create intelligent mappings between weather conditions and product categories in product_mappings.py:

import numpy as np
from collections import defaultdict

class ProductWeatherMapper:
    def __init__(self):
        self.category_mappings = self._initialise_mappings()
        self.condition_modifiers = self._initialise_condition_modifiers()
    
    def _initialise_mappings(self):
        """Define weather-to-product category mappings with confidence scores"""
        return {
            'temperature_ranges': {
                'very_cold': {
                    'winter_clothing': 0.95,
                    'heating_appliances': 0.85,
                    'hot_beverages': 0.8,
                    'warm_bedding': 0.7,
                    'winter_sports': 0.6
                },
                'cold': {
                    'jackets_coats': 0.9,
                    'warm_clothing': 0.8,
                    'comfort_food': 0.7,
                    'indoor_entertainment': 0.6,
                    'supplements_vitamins': 0.5
                },
                'mild': {
                    'casual_clothing': 0.7,
                    'outdoor_gear': 0.6,
                    'gardening_supplies': 0.8,
                    'home_improvement': 0.6,
                    'books_media': 0.5
                },
                'warm': {
                    'summer_clothing': 0.85,
                    'outdoor_furniture': 0.8,
                    'bbq_equipment': 0.9,
                    'cooling_appliances': 0.7,
                    'sports_equipment': 0.75
                },
                'hot': {
                    'cooling_products': 0.95,
                    'summer_essentials': 0.9,
                    'sun_protection': 0.85,
                    'swimwear': 0.8,
                    'cold_beverages': 0.9
                }
            },
            
            'precipitation_products': {
                'rain_gear': {
                    'threshold': 0.1,  # mm
                    'confidence': 0.9
                },
                'indoor_activities': {
                    'threshold': 2.0,
                    'confidence': 0.8
                },
                'waterproof_accessories': {
                    'threshold': 0.5,
                    'confidence': 0.85
                }
            },
            
            'uv_protection': {
                'low_uv': {'skin_care_basic': 0.5},
                'moderate_uv': {'sun_protection': 0.7, 'sunglasses': 0.6},
                'high_uv': {'sunscreen': 0.9, 'protective_clothing': 0.8},
                'very_high_uv': {'premium_sun_protection': 0.95}
            }
        }
    
    def _initialise_condition_modifiers(self):
        """Weather condition-specific product boosts"""
        return {
            'sunny': {'outdoor_products': 1.2, 'sun_protection': 1.3},
            'cloudy': {'casual_wear': 1.1, 'neutral_products': 1.0},
            'overcast': {'indoor_entertainment': 1.2, 'comfort_items': 1.1},
            'rain': {'rain_gear': 1.5, 'indoor_activities': 1.3},
            'snow': {'winter_gear': 1.4, 'heating_products': 1.3},
            'fog': {'safety_equipment': 1.2, 'visibility_aids': 1.3},
            'thunderstorm': {'indoor_entertainment': 1.4, 'emergency_supplies': 1.2}
        }
    
    def calculate_product_scores(self, weather_features, product_catalog):
        """Calculate recommendation scores for products based on weather"""
        scores = defaultdict(float)
        
        # Temperature-based scoring
        temp_category = weather_features['temperature_category']
        if temp_category in self.category_mappings['temperature_ranges']:
            for category, confidence in self.category_mappings['temperature_ranges'][temp_category].items():
                for product in product_catalog.get(category, []):
                    scores[product['id']] += confidence * 0.3
        
        # Precipitation-based scoring
        precip = weather_features['precipitation_mm']
        for category, config in self.category_mappings['precipitation_products'].items():
            if precip >= config['threshold']:
                for product in product_catalog.get(category, []):
                    scores[product['id']] += config['confidence'] * 0.25
        
        # UV-based scoring
        uv = weather_features['uv_index']
        uv_category = self._categorise_uv(uv)
        if uv_category in self.category_mappings['uv_protection']:
            for category, confidence in self.category_mappings['uv_protection'][uv_category].items():
                for product in product_catalog.get(category, []):
                    scores[product['id']] += confidence * 0.15
        
        # Condition modifier application
        condition = weather_features['condition']
        for condition_key, modifiers in self.condition_modifiers.items():
            if condition_key in condition:
                for category, multiplier in modifiers.items():
                    for product in product_catalog.get(category, []):
                        scores[product['id']] *= multiplier
        
        # Seasonal alignment boost
        season = weather_features['season_alignment']
        seasonal_boost = self._apply_seasonal_boost(season, product_catalog)
        for product_id, boost in seasonal_boost.items():
            scores[product_id] += boost * 0.1
        
        return dict(scores)
    
    def _categorise_uv(self, uv_index):
        """Categorise UV index for product mapping"""
        if uv_index < 3: return 'low_uv'
        elif uv_index < 6: return 'moderate_uv'
        elif uv_index < 8: return 'high_uv'
        else: return 'very_high_uv'
    
    def _apply_seasonal_boost(self, season, product_catalog):
        """Apply seasonal relevance boosts"""
        seasonal_categories = {
            'winter': ['winter_clothing', 'heating_appliances', 'warm_bedding'],
            'spring': ['gardening_supplies', 'outdoor_gear', 'cleaning_supplies'],
            'summer': ['summer_clothing', 'cooling_products', 'outdoor_furniture'],
            'autumn': ['warm_clothing', 'indoor_entertainment', 'comfort_items']
        }
        
        boosts = {}
        for category in seasonal_categories.get(season, []):
            for product in product_catalog.get(category, []):
                boosts[product['id']] = 0.2
        
        return boosts

Building the Recommendation Engine

Now create the core recommendation engine that combines weather intelligence with user preferences:

import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import StandardScaler
import numpy as np

class WeatherRecommendationEngine:
    def __init__(self, weather_processor, product_mapper):
        self.weather_processor = weather_processor
        self.product_mapper = product_mapper
        self.scaler = StandardScaler()
        
    def generate_recommendations(self, user_location, user_preferences=None, 
                               product_catalog=None, limit=10):
        """Generate personalised weather-driven recommendations"""
        
        # Fetch weather data
        weather_data = self.weather_processor.get_current_weather(user_location)
        if not weather_data:
            return self._fallback_recommendations(product_catalog, limit)
        
        weather_features = self.weather_processor.extract_weather_features(weather_data)
        
        # Calculate weather-based product scores
        weather_scores = self.product_mapper.calculate_product_scores(
            weather_features, product_catalog
        )
        
        # Combine with user preferences if available
        if user_preferences:
            final_scores = self._combine_scores(weather_scores, user_preferences)
        else:
            final_scores = weather_scores
        
        # Generate final recommendations
        recommendations = self._rank_and_format_recommendations(
            final_scores, product_catalog, weather_features, limit
        )
        
        return {
            'recommendations': recommendations,
            'weather_context': weather_features,
            'metadata': self._generate_metadata(weather_features, recommendations)
        }
    
    def _combine_scores(self, weather_scores, user_preferences):
        """Combine weather relevance with user preferences"""
        combined_scores = {}
        
        for product_id, weather_score in weather_scores.items():
            user_score = user_preferences.get(product_id, 0.5)  # Default neutral
            
            # Weighted combination: 60% weather, 40% user preference
            combined_score = (weather_score * 0.6) + (user_score * 0.4)
            combined_scores[product_id] = combined_score
        
        return combined_scores
    
    def _rank_and_format_recommendations(self, scores, product_catalog, 
                                       weather_features, limit):
        """Rank products and format recommendations"""
        # Create flat product list with scores
        products_with_scores = []
        
        for category, products in product_catalog.items():
            for product in products:
                score = scores.get(product['id'], 0)
                if score > 0.1:  # Minimum relevance threshold
                    product_data = {
                        **product,
                        'relevance_score': score,
                        'weather_reasoning': self._generate_reasoning(
                            product, weather_features, score
                        )
                    }
                    products_with_scores.append(product_data)
        
        # Sort by relevance score
        recommendations = sorted(
            products_with_scores, 
            key=lambda x: x['relevance_score'], 
            reverse=True
        )[:limit]
        
        return recommendations
    
    def _generate_reasoning(self, product, weather_features, score):
        """Generate human-readable reasoning for recommendations"""
        reasons = []
        
        temp = weather_features['temperature_c']
        condition = weather_features['condition']
        precip = weather_features['precipitation_mm']
        
        if 'winter' in product.get('category', '').lower() and temp < 10:
            reasons.append(f"Perfect for {temp}°C weather")
        
        if 'rain' in product.get('category', '').lower() and precip > 0:
            reasons.append(f"Essential for current {precip}mm rainfall")
        
        if 'cooling' in product.get('category', '').lower() and temp > 25:
            reasons.append(f"Beat the {temp}°C heat")
        
        if not reasons:
            reasons.append(f"Recommended for {condition} conditions")
        
        return "; ".join(reasons)
    
    def _generate_metadata(self, weather_features, recommendations):
        """Generate recommendation metadata"""
        return {
            'weather_summary': f"{weather_features['temperature_c']}°C, {weather_features['condition']}",
            'recommendation_count': len(recommendations),
            'avg_relevance': np.mean([r['relevance_score'] for r in recommendations]) if recommendations else 0,
            'weather_impact_high': len([r for r in recommendations if r['relevance_score'] > 0.8]),
            'generated_at': weather_features['timestamp']
        }
    
    def _fallback_recommendations(self, product_catalog, limit):
        """Fallback recommendations when weather data unavailable"""
        fallback_products = []
        
        for category, products in product_catalog.items():
            fallback_products.extend(products[:2])  # Top 2 from each category
        
        return {
            'recommendations': fallback_products[:limit],
            'weather_context': None,
            'metadata': {'fallback': True, 'reason': 'Weather data unavailable'}
        }

Flask API Implementation

Create a RESTful API to serve weather-driven recommendations:

from flask import Flask, request, jsonify
from weather_processor import WeatherProcessor
from product_mappings import ProductWeatherMapper
from recommendation_engine import WeatherRecommendationEngine

app = Flask(__name__)

# Initialise components
weather_processor = WeatherProcessor()
product_mapper = ProductWeatherMapper()
recommendation_engine = WeatherRecommendationEngine(weather_processor, product_mapper)

# Sample product catalog (replace with your database)
SAMPLE_CATALOG = {
    'winter_clothing': [
        {'id': 'WC001', 'name': 'Thermal Jacket', 'price': 89.99, 'category': 'winter_clothing'},
        {'id': 'WC002', 'name': 'Wool Jumper', 'price': 45.99, 'category': 'winter_clothing'}
    ],
    'rain_gear': [
        {'id': 'RG001', 'name': 'Waterproof Jacket', 'price': 79.99, 'category': 'rain_gear'},
        {'id': 'RG002', 'name': 'Compact Umbrella', 'price': 24.99, 'category': 'rain_gear'}
    ],
    'cooling_products': [
        {'id': 'CP001', 'name': 'Portable Fan', 'price': 34.99, 'category': 'cooling_products'},
        {'id': 'CP002', 'name': 'Cooling Towel', 'price': 12.99, 'category': 'cooling_products'}
    ]
}

@app.route('/api/recommendations', methods=['POST'])
def get_recommendations():
    """Generate weather-driven product recommendations"""
    try:
        data = request.get_json()
        
        location = data.get('location')
        if not location:
            return jsonify({'error': 'Location parameter required'}), 400
        
        user_preferences = data.get('user_preferences', {})
        limit = data.get('limit', 10)
        
        recommendations = recommendation_engine.generate_recommendations(
            location, user_preferences, SAMPLE_CATALOG, limit
        )
        
        return jsonify(recommendations)
    
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/weather', methods=['GET'])
def get_weather():
    """Get current weather for location"""
    location = request.args.get('location')
    if not location:
        return jsonify({'error': 'Location parameter required'}), 400
    
    weather_data = weather_processor.get_current_weather(location)
    if weather_data:
        features = weather_processor.extract_weather_features(weather_data)
        return jsonify(features)
    else:
        return jsonify({'error': 'Weather data unavailable'}), 404

if __name__ == '__main__':
    app.run(debug=True)

Testing and Optimisation

Create comprehensive tests to validate your recommendation engine:

import unittest
from unittest.mock import patch, Mock

class TestWeatherRecommendations(unittest.TestCase):
    def setUp(self):
        self.weather_processor = WeatherProcessor()
        self.product_mapper = ProductWeatherMapper()
        self.engine = WeatherRecommendationEngine(
            self.weather_processor, self.product_mapper
        )
    
    @patch('weather_processor.requests.get')
    def test_cold_weather_recommendations(self, mock_get):
        """Test recommendations for cold weather"""
        # Mock cold weather response
        mock_response = Mock()
        mock_response.json.return_value = {
            'current': {
                'temp_c': 2.0,
                'condition': {'text': 'Snow', 'code': 1225},
                'humidity': 85,
                'precip_mm': 5.2,
                'wind_kph': 15,
                'uv': 1,
                'vis_km': 2,
                'pressure_mb': 1013,
                'is_day': 1,
                'feelslike_c': -1.0
            },
            'location': {'name': 'London', 'country': 'United Kingdom'}
        }
        mock_get.return_value = mock_response
        
        result = self.engine.generate_recommendations('London', limit=5)
        
        # Verify winter products are recommended
        winter_products = [r for r in result['recommendations'] 
                          if 'winter' in r.get('category', '')]
        self.assertGreater(len(winter_products), 0)
        
        # Verify high relevance scores for winter items
        for product in winter_products:
            self.assertGreater(product['relevance_score'], 0.7)

if __name__ == '__main__':
    unittest.main()

Advanced Features and Scaling

Enhance your system with advanced capabilities:

Machine Learning Integration

Implement ML models to improve recommendation accuracy over time:

from sklearn.ensemble import RandomForestRegressor
import joblib

class MLRecommendationEnhancer:
    def __init__(self):
        self.model = RandomForestRegressor(n_estimators=100)
        self.feature_columns = [
            'temperature_c', 'humidity', 'precipitation_mm', 
            'wind_kph', 'uv_index', 'user_history_score'
        ]
    
    def train_model(self, historical_data):
        """Train ML model on historical purchase/weather data"""
        X = historical_data[self.feature_columns]
        y = historical_data['purchase_probability']
        
        self.model.fit(X, y)
        joblib.dump(self.model, 'weather_recommendation_model.pkl')
    
    def enhance_scores(self, base_scores, weather_features, user_features):
        """Use ML to enhance base recommendation scores"""
        if not hasattr(self.model, 'predict'):
            return base_scores
        
        enhanced_scores = {}
        for product_id, base_score in base_scores.items():
            features = [
                weather_features['temperature_c'],
                weather_features['humidity'],
                weather_features['precipitation_mm'],
                weather_features['wind_kph'],
                weather_features['uv_index'],
                user_features.get(product_id, 0.5)
            ]
            
            ml_score = self.model.predict([features])[0]
            enhanced_scores[product_id] = (base_score * 0.7) + (ml_score * 0.3)
        
        return enhanced_scores

Production Deployment Considerations

When deploying your weather-driven recommendation system to production:

  • Caching Strategy: Cache weather data for 10-15 minutes to reduce API calls whilst maintaining freshness
  • Error Handling: Implement robust fallback mechanisms when WeatherAPI is unavailable
  • Performance Monitoring: Track recommendation click-through rates and conversion improvements
  • A/B Testing: Compare weather-driven recommendations against traditional algorithms
  • Scalability: Consider using Redis for caching and PostgreSQL for user preference storage

Conclusion

Weather-driven e-commerce recommendations represent a powerful fusion of environmental intelligence and consumer psychology. By leveraging WeatherAPI's comprehensive data and implementing sophisticated correlation algorithms, you can create recommendation systems that anticipate customer needs with remarkable accuracy.

Our system processes real-time weather conditions across 15+ parameters, maps them to product categories with weighted confidence scores, and generates personalised recommendations that align with both environmental conditions and user preferences. The result is a dynamic, context-aware shopping experience that can increase conversion rates by up to 40% during weather-influenced purchasing periods.

Ready to revolutionise your e-commerce platform with weather intelligence? Sign up for your free WeatherAPI account today and start building smarter recommendation systems. With 100,000 free calls monthly and sub-200ms response times, WeatherAPI provides the reliable foundation your weather-driven commerce needs.

Scroll to Top