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
RSK World
weather-chatbot
Weather Chatbot - Python + Flask + OpenWeatherMap + OpenAI + Weather Forecast + Weather Alerts + Natural Language Processing
weather-chatbot
  • __pycache__
  • cache
  • logs
  • scripts
  • sessions
  • static
  • templates
  • tests
  • utils
  • .dockerignore778 B
  • .env.example1.5 KB
  • .gitignore2.4 KB
  • .pre-commit-config.yaml1 KB
  • API.md7.9 KB
  • CHANGELOG.md2.4 KB
  • CHECKLIST.md5.4 KB
  • CONTRIBUTING.md1.9 KB
  • Dockerfile1.4 KB
  • FEATURES.md7.1 KB
  • FINAL_CHECK.md6.7 KB
  • GITHUB_RELEASE_INSTRUCTIONS.md5.4 KB
  • INSTALL.md4 KB
  • LICENSE1.3 KB
  • MANIFEST.in553 B
  • Makefile2 KB
  • PROJECT_SUMMARY.md12.9 KB
  • README.md7.2 KB
  • RELEASE_NOTES_v1.0.0.md8.9 KB
  • VERIFICATION_REPORT.md9.2 KB
  • app.py22.2 KB
  • chatbot.py1.7 KB
  • config.py4.9 KB
  • docker-compose.yml2.2 KB
  • nginx.conf2.3 KB
  • pytest.ini549 B
  • requirements.txt1.9 KB
  • run.py3.1 KB
  • setup.py3.1 KB
  • weather_api.py578 B
Dockerfileapp.py
Dockerfile
Raw Download
Find: Go to:
# Weather Chatbot Dockerfile
# ==========================
#
# 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: Docker configuration for Weather Chatbot application

# Use Python 3.11 slim image
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV FLASK_APP=app.py
ENV FLASK_ENV=production

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    make \
    libffi-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements file
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

# Copy application files
COPY . .

# Create necessary directories
RUN mkdir -p logs static/uploads cache sessions

# Set permissions
RUN chmod -R 755 /app

# Expose port
EXPOSE 5000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c "import requests; requests.get('http://localhost:5000/health')"

# Run the application
CMD ["python", "run.py"]
59 lines•1.4 KB
text
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

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