#!/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 ============= @app.route('/', methods=['GET']) 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 @app.route('/api/health', methods=['GET']) def health(): """Health check endpoint""" return jsonify({ 'status': 'healthy', 'timestamp': datetime.now().isoformat(), 'service': 'Castor Price Forecasting API' }), 200 @app.route('/api/generate-key', methods=['POST']) 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 @app.route('/api/forecast', methods=['GET', 'POST']) 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 @app.route('/api/forecast/arima', methods=['GET', 'POST']) 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 @app.route('/api/forecast/lstm', methods=['GET', 'POST']) 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)