help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • Blog
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
weather-chatbot
/
tests
RSK World
weather-chatbot
Weather Chatbot - Python + Flask + OpenWeatherMap + OpenAI + Weather Forecast + Weather Alerts + Natural Language Processing
tests
  • __init__.py110 B
  • conftest.py1.3 KB
  • test_app.py3.3 KB
  • test_utils.py1.8 KB
  • test_weather_api.py1.6 KB
app.py.env.example
app.py
Raw Download
Find: Go to:
#!/usr/bin/env python3
"""
Weather Chatbot Application
===========================

Author: RSK World (https://rskworld.in)
Founded by: Molla Samser
Designer & Tester: Rima Khatun
Contact: +91 93305 39277, hello@rskworld.in, support@rskworld.in
Location: Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147
Year: 2026

Description: A weather chatbot providing forecasts, alerts, and weather information
using OpenWeatherMap API and OpenAI API for natural language processing.
"""

import os
import json
import requests
from datetime import datetime
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS
from werkzeug.utils import secure_filename
from werkzeug.middleware.proxy_fix import ProxyFix
import openai
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'weather-chatbot-secret-key-2026')

# Enable CORS
cors_origins = os.getenv('CORS_ORIGINS', '*').split(',')
CORS(app, origins=cors_origins, supports_credentials=True)

# Proxy fix for production (if behind reverse proxy)
if os.getenv('PROXY_FIX', 'False').lower() == 'true':
    app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)

# Security headers
@app.after_request
def set_security_headers(response):
    """Add security headers to responses."""
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'SAMEORIGIN'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    if not app.debug:
        response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    return response

class WeatherChatbot:
    """
    Weather Chatbot class for handling weather queries and providing forecasts.
    Integrates with OpenWeatherMap API and OpenAI for natural language processing.
    """
    
    def __init__(self):
        self.openweather_api_key = os.getenv('OPENWEATHER_API_KEY')
        self.openai_api_key = os.getenv('OPENAI_API_KEY')
        self.base_url = "https://api.openweathermap.org/data/2.5"
        
        if not self.openweather_api_key:
            print("Warning: OpenWeatherMap API key not found in environment variables")
        
        if not self.openai_api_key:
            print("Warning: OpenAI API key not found in environment variables")
        else:
            self.openai_client = openai.OpenAI(api_key=self.openai_api_key)
    
    def get_weather_by_city(self, city):
        """
        Get current weather data for a specific city.
        
        Args:
            city (str): Name of the city
            
        Returns:
            dict: Weather data or error message
        """
        try:
            url = f"{self.base_url}/weather"
            params = {
                'q': city,
                'appid': self.openweather_api_key,
                'units': 'metric'
            }
            
            response = requests.get(url, params=params)
            response.raise_for_status()
            
            data = response.json()
            
            # Handle visibility (may be None or missing)
            visibility_meters = data.get('visibility')
            if visibility_meters is None:
                visibility_km = 0
            else:
                visibility_km = visibility_meters / 1000 if visibility_meters > 0 else 0
            
            # Handle wind speed (may be missing)
            wind_speed = data.get('wind', {}).get('speed', 0)
            
            # Handle weather array (may be empty or missing)
            weather_array = data.get('weather', [])
            if weather_array and len(weather_array) > 0:
                weather_main = weather_array[0]
                description = weather_main.get('description', 'Unknown')
                icon = weather_main.get('icon', '')
            else:
                description = 'Unknown conditions'
                icon = ''
            
            weather_info = {
                'city': data.get('name', 'Unknown'),
                'country': data.get('sys', {}).get('country', ''),
                'temperature': data.get('main', {}).get('temp', 0),
                'feels_like': data.get('main', {}).get('feels_like', 0),
                'humidity': data.get('main', {}).get('humidity', 0),
                'pressure': data.get('main', {}).get('pressure', 0),
                'description': description,
                'icon': icon,
                'wind_speed': wind_speed,
                'visibility': visibility_km,
                'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            }
            
            return weather_info
            
        except requests.exceptions.RequestException as e:
            return {'error': f'Failed to fetch weather data: {str(e)}'}
        except KeyError as e:
            return {'error': f'Invalid weather data format: {str(e)}'}
    
    def get_forecast_by_city(self, city, days=5):
        """
        Get weather forecast for a specific city.
        
        Args:
            city (str): Name of the city
            days (int): Number of days to forecast (max 5)
            
        Returns:
            dict: Forecast data or error message
        """
        try:
            url = f"{self.base_url}/forecast"
            params = {
                'q': city,
                'appid': self.openweather_api_key,
                'units': 'metric',
                'cnt': days * 8  # 8 forecasts per day (3-hour intervals)
            }
            
            response = requests.get(url, params=params)
            response.raise_for_status()
            
            data = response.json()
            
            forecasts = []
            for item in data.get('list', []):
                # Safely extract wind speed (may be missing)
                wind_data = item.get('wind', {})
                wind_speed = wind_data.get('speed', 0) if isinstance(wind_data, dict) else 0
                
                # Safely extract weather information (may be missing or empty)
                weather_array = item.get('weather', [])
                if weather_array and len(weather_array) > 0:
                    weather_main = weather_array[0]
                    description = weather_main.get('description', '')
                    icon = weather_main.get('icon', '')
                else:
                    description = 'Unknown conditions'
                    icon = ''
                
                forecast = {
                    'datetime': item.get('dt_txt', ''),
                    'temperature': item.get('main', {}).get('temp', 0),
                    'feels_like': item.get('main', {}).get('feels_like', 0),
                    'humidity': item.get('main', {}).get('humidity', 0),
                    'description': description,
                    'icon': icon,
                    'wind_speed': wind_speed,
                    'rain': item.get('rain', {}).get('3h', 0) if isinstance(item.get('rain'), dict) else 0,
                    'snow': item.get('snow', {}).get('3h', 0) if isinstance(item.get('snow'), dict) else 0
                }
                forecasts.append(forecast)
            
            return {
                'city': data['city']['name'],
                'country': data['city']['country'],
                'forecasts': forecasts
            }
            
        except requests.exceptions.RequestException as e:
            return {'error': f'Failed to fetch forecast data: {str(e)}'}
        except KeyError as e:
            return {'error': f'Invalid forecast data format: {str(e)}'}
    
    def get_weather_alerts(self, city):
        """
        Get weather alerts for a specific city.
        
        Args:
            city (str): Name of the city
            
        Returns:
            dict: Weather alerts or error message
        """
        try:
            # First get coordinates for the city
            geo_url = f"http://api.openweathermap.org/geo/1.0/direct"
            geo_params = {
                'q': city,
                'limit': 1,
                'appid': self.openweather_api_key
            }
            
            geo_response = requests.get(geo_url, params=geo_params)
            geo_response.raise_for_status()
            
            geo_data = geo_response.json()
            if not geo_data:
                return {'error': 'City not found'}
            
            lat, lon = geo_data[0]['lat'], geo_data[0]['lon']
            
            # Get weather alerts using One Call API (Note: Requires One Call API subscription)
            # If not available, return empty alerts
            alerts_url = f"{self.base_url}/onecall"
            alerts_params = {
                'lat': lat,
                'lon': lon,
                'appid': self.openweather_api_key,
                'exclude': 'minutely,hourly,daily'
            }
            
            try:
                alerts_response = requests.get(alerts_url, params=alerts_params, timeout=10)
                alerts_response.raise_for_status()
                alerts_data = alerts_response.json()
            except requests.exceptions.HTTPError as e:
                # One Call API might not be available (requires subscription)
                if e.response.status_code == 401:
                    return {
                        'city': city,
                        'alerts': [],
                        'has_alerts': False,
                        'message': 'Weather alerts API not available. One Call API subscription required.'
                    }
                raise
            
            alerts = []
            if 'alerts' in alerts_data and isinstance(alerts_data['alerts'], list):
                for alert in alerts_data['alerts']:
                    try:
                        alert_info = {
                            'event': alert.get('event', 'Weather Alert'),
                            'start': datetime.fromtimestamp(alert.get('start', 0)).strftime('%Y-%m-%d %H:%M:%S') if alert.get('start') else '',
                            'end': datetime.fromtimestamp(alert.get('end', 0)).strftime('%Y-%m-%d %H:%M:%S') if alert.get('end') else '',
                            'description': alert.get('description', ''),
                            'severity': alert.get('severity', 'unknown')
                        }
                        alerts.append(alert_info)
                    except (ValueError, TypeError, KeyError) as e:
                        # Skip invalid alert entries
                        continue
            
            return {
                'city': city,
                'alerts': alerts,
                'has_alerts': len(alerts) > 0
            }
            
        except requests.exceptions.RequestException as e:
            return {'error': f'Failed to fetch weather alerts: {str(e)}'}
        except KeyError as e:
            return {'error': f'Invalid alerts data format: {str(e)}'}
    
    def process_natural_language_query(self, query):
        """
        Process natural language weather queries using OpenAI.
        
        Args:
            query (str): Natural language weather query
            
        Returns:
            dict: Processed query result with city and query type
        """
        if not self.openai_api_key:
            # Fallback to simple keyword matching
            query_lower = query.lower()
            
            # Extract city name (simple approach)
            cities = ['london', 'new york', 'paris', 'tokyo', 'delhi', 'mumbai', 'kolkata', 'chennai', 'bangalore']
            found_city = None
            for city in cities:
                if city in query_lower:
                    found_city = city
                    break
            
            # Determine query type
            query_type = 'current'
            if 'forecast' in query_lower or 'tomorrow' in query_lower or 'next' in query_lower:
                query_type = 'forecast'
            elif 'alert' in query_lower or 'warning' in query_lower:
                query_type = 'alerts'
            
            return {
                'city': found_city or 'unknown',
                'query_type': query_type,
                'confidence': 0.5
            }
        
        try:
            prompt = f"""
            Analyze this weather query and extract the city and query type:
            
            Query: "{query}"
            
            Respond with JSON format:
            {{
                "city": "city_name",
                "query_type": "current|forecast|alerts",
                "confidence": 0.0-1.0
            }}
            
            Query types:
            - "current": for current weather conditions
            - "forecast": for weather forecasts
            - "alerts": for weather alerts and warnings
            """
            
            response = self.openai_client.chat.completions.create(
                model="gpt-3.5-turbo",
                messages=[
                    {"role": "system", "content": "You are a weather query analyzer. Extract city and query type from user queries."},
                    {"role": "user", "content": prompt}
                ],
                max_tokens=100,
                temperature=0.3
            )
            
            content = response.choices[0].message.content.strip()
            
            # Try to parse JSON response
            try:
                result = json.loads(content)
                return result
            except json.JSONDecodeError:
                # If response is not JSON, try to extract information from text
                return {'error': 'Could not parse OpenAI response as JSON', 'raw_response': content}
            
        except Exception as e:
            print(f"OpenAI API error: {str(e)}")
            return {'error': f'Failed to process natural language query: {str(e)}'}
    
    def chat(self, user_query):
        """
        Main chat function to handle user queries.
        
        Args:
            user_query (str): User's weather query
            
        Returns:
            dict: Response with weather information
        """
        # Process the query
        processed = self.process_natural_language_query(user_query)
        
        if 'error' in processed:
            return processed
        
        city = processed.get('city')
        query_type = processed.get('query_type')
        
        if not city or city == 'unknown':
            return {'error': 'Could not determine city from your query. Please specify a city name.'}
        
        # Get appropriate weather data
        if query_type == 'current':
            weather_data = self.get_weather_by_city(city)
            return weather_data
        elif query_type == 'forecast':
            forecast_data = self.get_forecast_by_city(city)
            return forecast_data
        elif query_type == 'alerts':
            alert_data = self.get_weather_alerts(city)
            return alert_data
        else:
            return {'error': 'Unknown query type'}

# Initialize chatbot
chatbot = WeatherChatbot()

@app.route('/')
def index():
    """Render the main chat interface."""
    return render_template('index.html')

@app.route('/robots.txt')
def robots_txt():
    """Serve robots.txt file."""
    return app.send_static_file('robots.txt')

@app.route('/chat', methods=['POST'])
def chat():
    """Handle chat requests."""
    try:
        user_query = request.form.get('message', '').strip()
        
        if not user_query:
            return jsonify({'error': 'Please enter a message'})
        
        response = chatbot.chat(user_query)
        return jsonify(response)
        
    except Exception as e:
        return jsonify({'error': f'An error occurred: {str(e)}'})

@app.route('/weather/<city>')
def weather(city):
    """Get weather for a specific city."""
    try:
        weather_data = chatbot.get_weather_by_city(city)
        return jsonify(weather_data)
    except Exception as e:
        return jsonify({'error': str(e)})

@app.route('/forecast/<city>')
def forecast(city):
    """Get forecast for a specific city."""
    try:
        forecast_data = chatbot.get_forecast_by_city(city)
        return jsonify(forecast_data)
    except Exception as e:
        return jsonify({'error': str(e)})

@app.route('/alerts/<city>')
def alerts(city):
    """Get weather alerts for a specific city."""
    try:
        alert_data = chatbot.get_weather_alerts(city)
        return jsonify(alert_data)
    except Exception as e:
        return jsonify({'error': str(e)})

@app.route('/health')
def health():
    """Health check endpoint."""
    return jsonify({
        'status': 'healthy',
        'timestamp': datetime.now().isoformat(),
        'version': '1.0.0',
        'app': 'Weather Chatbot'
    })

@app.route('/api/status')
def api_status():
    """API status and information endpoint."""
    return jsonify({
        'status': 'operational',
        'version': '1.0.0',
        'app_name': 'Weather Chatbot',
        'author': 'RSK World',
        'website': 'https://rskworld.in',
        'timestamp': datetime.now().isoformat(),
        'endpoints': {
            'chat': '/chat',
            'weather': '/weather/<city>',
            'forecast': '/forecast/<city>',
            'alerts': '/alerts/<city>',
            'health': '/health',
            'status': '/api/status'
        },
        'features': [
            'current_weather',
            'weather_forecast',
            'weather_alerts',
            'natural_language_processing',
            'location_detection'
        ]
    })

@app.route('/api/search/cities', methods=['GET'])
def search_cities():
    """Search for cities by name."""
    try:
        query = request.args.get('q', '').strip()
        
        if not query:
            return jsonify({'error': 'Query parameter "q" is required'}), 400
        
        if len(query) < 2:
            return jsonify({'error': 'Query must be at least 2 characters long'}), 400
        
        # Use geolocation service to search for cities
        from utils.geolocation import GeolocationService
        geo_service = GeolocationService(openweather_api_key=os.getenv('OPENWEATHER_API_KEY'))
        
        results = geo_service.search_locations(query, limit=10)
        
        if 'error' in results:
            return jsonify(results), 500
        
        return jsonify({
            'query': query,
            'results': results.get('results', []),
            'count': results.get('count', 0)
        })
        
    except Exception as e:
        return jsonify({'error': f'Search failed: {str(e)}'}), 500

@app.route('/api/compare', methods=['POST'])
def compare_cities():
    """Compare weather between multiple cities."""
    try:
        data = request.get_json()
        
        if not data or 'cities' not in data:
            return jsonify({'error': 'Cities list is required'}), 400
        
        cities = data.get('cities', [])
        
        if not isinstance(cities, list) or len(cities) < 2:
            return jsonify({'error': 'At least 2 cities are required for comparison'}), 400
        
        if len(cities) > 5:
            return jsonify({'error': 'Maximum 5 cities allowed for comparison'}), 400
        
        # Get weather data for all cities
        cities_data = {}
        for city in cities:
            weather_data = chatbot.get_weather_by_city(city)
            cities_data[city] = weather_data
        
        # Use comparison utility
        from utils.comparison import WeatherComparison
        comparison_service = WeatherComparison()
        
        comparison_result = comparison_service.compare_current_weather(cities_data)
        
        return jsonify(comparison_result)
        
    except Exception as e:
        return jsonify({'error': f'Comparison failed: {str(e)}'}), 500

@app.route('/api/stats', methods=['GET'])
def api_stats():
    """Get API usage statistics."""
    try:
        # This would typically come from database/analytics
        # For now, return basic stats
        return jsonify({
            'status': 'operational',
            'uptime': 'N/A',
            'total_requests': 0,
            'successful_requests': 0,
            'failed_requests': 0,
            'average_response_time': 0,
            'timestamp': datetime.now().isoformat()
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.errorhandler(404)
def not_found_error(error):
    """Handle 404 errors."""
    if request.path.startswith('/api/') or request.accept_mimetypes.accept_json:
        return jsonify({'error': 'Endpoint not found', 'status': 404}), 404
    return render_template('errors/404.html'), 404

@app.errorhandler(500)
def internal_error(error):
    """Handle 500 errors."""
    app.logger.error(f'Server Error: {error}', exc_info=True)
    if request.path.startswith('/api/') or request.accept_mimetypes.accept_json:
        return jsonify({
            'error': 'Internal server error',
            'status': 500,
            'message': 'An unexpected error occurred. Please try again later.'
        }), 500
    return render_template('errors/500.html', error=str(error) if app.debug else None), 500

@app.errorhandler(403)
def forbidden_error(error):
    """Handle 403 errors."""
    if request.path.startswith('/api/') or request.accept_mimetypes.accept_json:
        return jsonify({'error': 'Forbidden', 'status': 403}), 403
    return jsonify({'error': 'Access forbidden', 'status': 403}), 403

@app.errorhandler(429)
def rate_limit_error(error):
    """Handle rate limit errors."""
    return jsonify({
        'error': 'Rate limit exceeded',
        'status': 429,
        'message': 'Too many requests. Please try again later.'
    }), 429

if __name__ == '__main__':
    print("Starting Weather Chatbot...")
    print("RSK World - Weather Chatbot Application")
    print("https://rskworld.in")
    print("© 2026 RSK World. All rights reserved.")
    
    app.run(debug=True, host='0.0.0.0', port=5000)
610 lines•22.2 KB
python
.env.example
Raw Download
Find: Go to:
# Weather Chatbot Environment Configuration
# ==========================================
#
# Author: RSK World (https://rskworld.in)
# Founded by: Molla Samser
# Designer & Tester: Rima Khatun
# Contact: +91 93305 39277, hello@rskworld.in, support@rskworld.in
# Location: Nutanhat, Mongolkote, Purba Burdwan, West Bengal, India, 713147
# Year: 2026
#
# Copy this file to .env and fill in your actual API keys and configuration

# OpenWeatherMap API Key
# Get your free API key from: https://openweathermap.org/api
OPENWEATHER_API_KEY=your_openweathermap_api_key_here

# OpenAI API Key (Optional - for natural language processing)
# Get your API key from: https://platform.openai.com/api-keys
OPENAI_API_KEY=your_openai_api_key_here

# Flask Configuration
SECRET_KEY=your_secret_key_here_for_flask_sessions
FLASK_ENV=development
FLASK_DEBUG=True

# Server Configuration
HOST=0.0.0.0
PORT=5000

# Logging Configuration
LOG_LEVEL=INFO
LOG_FILE=logs/weather_chatbot.log

# Rate Limiting
RATE_LIMIT_ENABLED=True
RATE_LIMIT_PER_MINUTE=30

# Weather API Configuration
WEATHER_UNITS=metric
WEATHER_LANGUAGE=en
DEFAULT_CITY=London

# Cache Configuration (optional)
CACHE_ENABLED=False
CACHE_TTL=300

# Database Configuration (optional - for chat history)
DATABASE_URL=sqlite:///weather_chatbot.db

# CORS Configuration
CORS_ORIGINS=*

# Security Configuration
SESSION_COOKIE_SECURE=False
SESSION_COOKIE_HTTPONLY=True
SESSION_COOKIE_SAMESITE=Lax
57 lines•1.5 KB
text

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer