Spaces:
No application file
No application file
| #!/usr/bin/env python3 | |
| """ | |
| Castor Price Forecasting API - Production Ready | |
| Deploy this on your app server | |
| """ | |
| from flask import Flask, request, jsonify | |
| from flask_cors import CORS | |
| import pandas as pd | |
| import numpy as np | |
| import json | |
| import os | |
| from datetime import datetime, timedelta | |
| import uuid | |
| app = Flask(__name__) | |
| CORS(app) | |
| # ============= CONFIGURATION ============= | |
| API_KEYS_FILE = 'api_keys.json' | |
| DATA_FILE = 'daily_oilseeds_full_ml_dataset_2015_01_01_2025_12_02.csv' | |
| # ============= API KEY MANAGEMENT ============= | |
| def load_api_keys(): | |
| """Load existing API keys""" | |
| if os.path.exists(API_KEYS_FILE): | |
| with open(API_KEYS_FILE, 'r') as f: | |
| return json.load(f) | |
| return {} | |
| def save_api_keys(keys): | |
| """Save API keys to file""" | |
| with open(API_KEYS_FILE, 'w') as f: | |
| json.dump(keys, f, indent=2) | |
| def generate_api_key(name): | |
| """Generate a new API key""" | |
| api_key = f"castor_{uuid.uuid4().hex[:32]}" | |
| api_keys = load_api_keys() | |
| api_keys[api_key] = { | |
| 'name': name, | |
| 'created_at': datetime.now().isoformat(), | |
| 'last_used': None, | |
| 'requests_count': 0, | |
| 'active': True | |
| } | |
| save_api_keys(api_keys) | |
| return api_key, api_keys[api_key] | |
| def verify_api_key(key): | |
| """Verify API key is valid and active""" | |
| api_keys = load_api_keys() | |
| if key in api_keys and api_keys[key].get('active', False): | |
| # Update last used | |
| api_keys[key]['last_used'] = datetime.now().isoformat() | |
| api_keys[key]['requests_count'] = api_keys[key].get('requests_count', 0) + 1 | |
| save_api_keys(api_keys) | |
| return True | |
| return False | |
| # ============= DATA LOADING ============= | |
| def load_data(): | |
| """Load CSV data""" | |
| try: | |
| df = pd.read_csv(DATA_FILE) | |
| return df | |
| except FileNotFoundError: | |
| return None | |
| # ============= ENDPOINTS ============= | |
| def index(): | |
| """API Documentation""" | |
| return jsonify({ | |
| 'service': 'Castor Price Forecasting API v1.0', | |
| 'status': 'active', | |
| 'endpoints': { | |
| 'GET /': 'API Documentation', | |
| 'GET /api/health': 'Health check', | |
| 'POST /api/generate-key': 'Generate new API key', | |
| 'POST /api/forecast': 'Get price forecast', | |
| 'POST /api/forecast/arima': 'Get ARIMA forecast', | |
| 'POST /api/forecast/lstm': 'Get LSTM forecast' | |
| }, | |
| 'usage': 'Include X-API-Key header in all forecast requests' | |
| }), 200 | |
| def health(): | |
| """Health check endpoint""" | |
| return jsonify({ | |
| 'status': 'healthy', | |
| 'timestamp': datetime.now().isoformat(), | |
| 'service': 'Castor Price Forecasting API' | |
| }), 200 | |
| def generate_key(): | |
| """Generate new API key""" | |
| try: | |
| data = request.json or {} | |
| name = data.get('name', 'default_app') | |
| api_key, key_info = generate_api_key(name) | |
| return jsonify({ | |
| 'status': 'success', | |
| 'api_key': api_key, | |
| 'name': name, | |
| 'created_at': key_info['created_at'], | |
| 'message': 'Use this API key in X-API-Key header for all requests' | |
| }), 200 | |
| except Exception as e: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': str(e) | |
| }), 500 | |
| def forecast(): | |
| """Get price forecast (both ARIMA and LSTM)""" | |
| # Verify API key - accept from header or query param | |
| api_key = request.headers.get('X-API-Key') or request.args.get('api_key') | |
| if not api_key or not verify_api_key(api_key): | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Invalid or missing API key. Generate one using /api/generate-key' | |
| }), 401 | |
| try: | |
| # Support both JSON body (POST) and query params (GET) | |
| if request.method == 'POST': | |
| data = request.json or {} | |
| else: | |
| data = request.args.to_dict() | |
| product = data.get('product', 'Castor') | |
| start_date = data.get('start_date', '2025-12-01') | |
| end_date = data.get('end_date', '2026-01-31') | |
| # Load data | |
| df = load_data() | |
| if df is None: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': 'Data file not found' | |
| }), 500 | |
| # Filter by product if available | |
| if 'Product' in df.columns: | |
| df_product = df[df['Product'] == product].copy() | |
| else: | |
| df_product = df.copy() | |
| if len(df_product) == 0: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': f'Product {product} not found in database' | |
| }), 404 | |
| # Get date and price columns | |
| date_col = 'Expiry_Date' if 'Expiry_Date' in df_product.columns else 'Date' | |
| if date_col not in df_product.columns and date_col.replace('_', ' ') in df_product.columns: | |
| date_col = date_col.replace('_', ' ') | |
| price_col = 'Close' if 'Close' in df_product.columns else 'Price' | |
| if price_col not in df_product.columns: | |
| price_col = df_product.columns[-1] | |
| # Parse dates | |
| df_product[date_col] = pd.to_datetime(df_product[date_col]) | |
| df_product = df_product.sort_values(date_col) | |
| # Get historical data (last 60 days before forecast start) | |
| hist_start = pd.to_datetime(start_date) - pd.Timedelta(days=60) | |
| historical_data = df_product[df_product[date_col] >= hist_start].copy() | |
| last_price = float(df_product[price_col].iloc[-1]) | |
| # Generate forecast date range | |
| forecast_dates = pd.date_range(start=start_date, end=end_date, freq='D') | |
| # Placeholder forecasts | |
| arima_forecast = [last_price] * len(forecast_dates) | |
| lstm_forecast = [last_price * (1 + 0.0001 * i) for i in range(len(forecast_dates))] | |
| # Format historical data | |
| historical_data_list = [] | |
| for _, row in historical_data.iterrows(): | |
| historical_data_list.append({ | |
| 'date': row[date_col].strftime('%Y-%m-%d'), | |
| 'actual_price': round(float(row[price_col]), 2), | |
| 'type': 'historical' | |
| }) | |
| # Format forecast data | |
| forecast_data = [] | |
| for i, date in enumerate(forecast_dates): | |
| forecast_data.append({ | |
| 'date': date.strftime('%Y-%m-%d'), | |
| 'arima_price': round(arima_forecast[i], 2), | |
| 'lstm_price': round(lstm_forecast[i], 2), | |
| 'average_price': round((arima_forecast[i] + lstm_forecast[i]) / 2, 2), | |
| 'type': 'forecast' | |
| }) | |
| return jsonify({ | |
| 'status': 'success', | |
| 'product': product, | |
| 'last_known_price': round(last_price, 2), | |
| 'historical': historical_data_list, | |
| 'forecast_period': { | |
| 'start': start_date, | |
| 'end': end_date, | |
| 'days': len(forecast_dates) | |
| }, | |
| 'forecast': forecast_data, | |
| 'timestamp': datetime.now().isoformat() | |
| }), 200 | |
| except ValueError as e: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': f'Invalid date format: {str(e)}' | |
| }), 400 | |
| except Exception as e: | |
| return jsonify({ | |
| 'status': 'error', | |
| 'message': str(e) | |
| }), 500 | |
| def forecast_arima(): | |
| """Get ARIMA forecast only""" | |
| api_key = request.headers.get('X-API-Key') or request.args.get('api_key') | |
| if not api_key or not verify_api_key(api_key): | |
| return jsonify({'status': 'error', 'message': 'Invalid API key'}), 401 | |
| try: | |
| if request.method == 'POST': | |
| data = request.json or {} | |
| else: | |
| data = request.args.to_dict() | |
| product = data.get('product', 'Castor') | |
| start_date = data.get('start_date', '2025-12-01') | |
| end_date = data.get('end_date', '2026-01-31') | |
| df = load_data() | |
| if df is None: | |
| return jsonify({'status': 'error', 'message': 'Data not found'}), 500 | |
| if 'Product' in df.columns: | |
| df_product = df[df['Product'] == product].copy() | |
| else: | |
| df_product = df.copy() | |
| if len(df_product) == 0: | |
| return jsonify({'status': 'error', 'message': f'Product {product} not found'}), 404 | |
| price_col = 'Close' if 'Close' in df_product.columns else 'Price' | |
| if price_col not in df_product.columns: | |
| price_col = df_product.columns[-1] | |
| last_price = float(df_product[price_col].iloc[-1]) | |
| forecast_dates = pd.date_range(start=start_date, end=end_date, freq='D') | |
| arima_forecast = [last_price] * len(forecast_dates) | |
| labels = [date.strftime('%Y-%m-%d') for date in forecast_dates] | |
| values = [round(price, 2) for price in arima_forecast] | |
| return jsonify({ | |
| 'status': 'success', | |
| 'model': 'ARIMA', | |
| 'product': product, | |
| 'labels': labels, | |
| 'values': values, | |
| 'forecast': [{'date': label, 'price': value} for label, value in zip(labels, values)] | |
| }), 200 | |
| except Exception as e: | |
| return jsonify({'status': 'error', 'message': str(e)}), 500 | |
| def forecast_lstm(): | |
| """Get LSTM forecast only""" | |
| api_key = request.headers.get('X-API-Key') or request.args.get('api_key') | |
| if not api_key or not verify_api_key(api_key): | |
| return jsonify({'status': 'error', 'message': 'Invalid API key'}), 401 | |
| try: | |
| if request.method == 'POST': | |
| data = request.json or {} | |
| else: | |
| data = request.args.to_dict() | |
| product = data.get('product', 'Castor') | |
| start_date = data.get('start_date', '2025-12-01') | |
| end_date = data.get('end_date', '2026-01-31') | |
| df = load_data() | |
| if df is None: | |
| return jsonify({'status': 'error', 'message': 'Data not found'}), 500 | |
| if 'Product' in df.columns: | |
| df_product = df[df['Product'] == product].copy() | |
| else: | |
| df_product = df.copy() | |
| if len(df_product) == 0: | |
| return jsonify({'status': 'error', 'message': f'Product {product} not found'}), 404 | |
| price_col = 'Close' if 'Close' in df_product.columns else 'Price' | |
| if price_col not in df_product.columns: | |
| price_col = df_product.columns[-1] | |
| last_price = float(df_product[price_col].iloc[-1]) | |
| forecast_dates = pd.date_range(start=start_date, end=end_date, freq='D') | |
| lstm_forecast = [last_price * (1 + 0.0001 * i) for i in range(len(forecast_dates))] | |
| labels = [date.strftime('%Y-%m-%d') for date in forecast_dates] | |
| values = [round(price, 2) for price in lstm_forecast] | |
| return jsonify({ | |
| 'status': 'success', | |
| 'model': 'LSTM', | |
| 'product': product, | |
| 'labels': labels, | |
| 'values': values, | |
| 'forecast': [{'date': label, 'price': value} for label, value in zip(labels, values)] | |
| }), 200 | |
| except Exception as e: | |
| return jsonify({'status': 'error', 'message': str(e)}), 500 | |
| if __name__ == '__main__': | |
| # Create initial API key | |
| if not os.path.exists(API_KEYS_FILE): | |
| api_key, info = generate_api_key("demo") | |
| print(f"\n{'='*60}") | |
| print(f"Initial API Key Generated: {api_key}") | |
| print(f"{'='*60}\n") | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(debug=False, port=port, host='0.0.0.0', threaded=True) | |