diff --git a/webui/app.py b/webui/app.py index d240a3729..13b851344 100644 --- a/webui/app.py +++ b/webui/app.py @@ -75,7 +75,7 @@ def load_data_files(): return data_files -def load_data_file(file_path): +def load_data_file(file_path: str) -> tuple[pd.DataFrame | None, str | None]: """Load data file""" try: if file_path.endswith('.csv'): @@ -108,18 +108,27 @@ def load_data_file(file_path): # Process volume column (optional) if 'volume' in df.columns: - df['volume'] = pd.to_numeric(df['volume'], errors='coerce') + df['volume'] = pd.to_numeric(df[col], errors='coerce') # Process amount column (optional, but not used for prediction) if 'amount' in df.columns: - df['amount'] = pd.to_numeric(df['amount'], errors='coerce') + df['amount'] = pd.to_numeric(df[col], errors='coerce') # Remove rows containing NaN values df = df.dropna() + # New log message for data freshness debugging + if 'timestamps' in df.columns and not df.empty: + print(f"Loaded data from {file_path}. Rows: {len(df)}. Time range: {df['timestamps'].min().isoformat()} to {df['timestamps'].max().isoformat()}.") + elif not df.empty: + print(f"Loaded data from {file_path}. Rows: {len(df)}. No timestamp column found.") + else: + print(f"Loaded data from {file_path}. No data found after processing.") + return df, None except Exception as e: + print(f"Failed to load file: {file_path} - {e}") # Added file_path to error log return None, f"Failed to load file: {str(e)}" def save_prediction_results(file_path, prediction_type, prediction_results, actual_data, input_data, prediction_params): @@ -162,10 +171,9 @@ def save_prediction_results(file_path, prediction_type, prediction_results, actu } # If actual data exists, perform comparison analysis - if actual_data and len(actual_data) > 0: + if actual_data and len(actual_data) > 0 and len(prediction_results) > 0: # Ensure prediction results also exist # Calculate continuity analysis - if len(prediction_results) > 0 and len(actual_data) > 0: - last_pred = prediction_results[0] # First prediction point + last_pred = prediction_results[0] # First prediction point first_actual = actual_data[0] # First actual point save_data['analysis']['continuity'] = { @@ -188,10 +196,10 @@ def save_prediction_results(file_path, prediction_type, prediction_results, actu 'close_gap': abs(last_pred['close'] - first_actual['close']) }, 'gap_percentages': { - 'open_gap_pct': (abs(last_pred['open'] - first_actual['open']) / first_actual['open']) * 100, - 'high_gap_pct': (abs(last_pred['high'] - first_actual['high']) / first_actual['high']) * 100, - 'low_gap_pct': (abs(last_pred['low'] - first_actual['low']) / first_actual['low']) * 100, - 'close_gap_pct': (abs(last_pred['close'] - first_actual['close']) / first_actual['close']) * 100 + 'open_gap_pct': (abs(last_pred['open'] - first_actual['open']) / first_actual['open']) * 100 if first_actual['open'] != 0 else float('inf'), + 'high_gap_pct': (abs(last_pred['high'] - first_actual['high']) / first_actual['high']) * 100 if first_actual['high'] != 0 else float('inf'), + 'low_gap_pct': (abs(last_pred['low'] - first_actual['low']) / first_actual['low']) * 100 if first_actual['low'] != 0 else float('inf'), + 'close_gap_pct': (abs(last_pred['close'] - first_actual['close']) / first_actual['close']) * 100 if first_actual['close'] != 0 else float('inf') } } @@ -206,19 +214,16 @@ def save_prediction_results(file_path, prediction_type, prediction_results, actu print(f"Failed to save prediction results: {e}") return None -def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, historical_start_idx=0): +def create_prediction_chart(df: pd.DataFrame, pred_df: pd.DataFrame | None, lookback: int, pred_len: int, actual_df: pd.DataFrame | None = None, historical_start_idx: int = 0) -> str: """Create prediction chart""" # Use specified historical data start position, not always from the beginning of df if historical_start_idx + lookback + pred_len <= len(df): # Display lookback historical points + pred_len prediction points starting from specified position historical_df = df.iloc[historical_start_idx:historical_start_idx+lookback] - prediction_range = range(historical_start_idx+lookback, historical_start_idx+lookback+pred_len) else: # If data is insufficient, adjust to maximum available range available_lookback = min(lookback, len(df) - historical_start_idx) - available_pred_len = min(pred_len, max(0, len(df) - historical_start_idx - available_lookback)) historical_df = df.iloc[historical_start_idx:historical_start_idx+available_lookback] - prediction_range = range(historical_start_idx+available_lookback, historical_start_idx+available_lookback+available_pred_len) # Create chart fig = go.Figure() @@ -230,15 +235,16 @@ def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, his high=historical_df['high'], low=historical_df['low'], close=historical_df['close'], - name='Historical Data (400 data points)', + name=f'Historical Data ({len(historical_df)} data points)', # Make count dynamic increasing_line_color='#26A69A', decreasing_line_color='#EF5350' )) # Add prediction data (candlestick chart) - if pred_df is not None and len(pred_df) > 0: + pred_timestamps = pd.Series(dtype='datetime64[ns]') # Initialize for consistent type inference + if pred_df is not None and not pred_df.empty: # Calculate prediction data timestamps - ensure continuity with historical data - if 'timestamps' in df.columns and len(historical_df) > 0: + if 'timestamps' in df.columns and not historical_df.empty: # Start from the last timestamp of historical data, create prediction timestamps with the same time interval last_timestamp = historical_df['timestamps'].iloc[-1] time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] if len(df) > 1 else pd.Timedelta(hours=1) @@ -250,7 +256,7 @@ def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, his ) else: # If no timestamps, use index - pred_timestamps = range(len(historical_df), len(historical_df) + len(pred_df)) + pred_timestamps = pd.Series(range(len(historical_df), len(historical_df) + len(pred_df))) # Use Series for consistency fig.add_trace(go.Candlestick( x=pred_timestamps, @@ -258,32 +264,28 @@ def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, his high=pred_df['high'], low=pred_df['low'], close=pred_df['close'], - name='Prediction Data (120 data points)', + name=f'Prediction Data ({len(pred_df)} data points)', # Make count dynamic increasing_line_color='#66BB6A', decreasing_line_color='#FF7043' )) # Add actual data for comparison (if exists) - if actual_df is not None and len(actual_df) > 0: + actual_timestamps = pd.Series(dtype='datetime64[ns]') # Initialize for consistent type inference + if actual_df is not None and not actual_df.empty: # Actual data should be in the same time period as prediction data if 'timestamps' in df.columns: - # Actual data should use the same timestamps as prediction data to ensure time alignment - if 'pred_timestamps' in locals(): - actual_timestamps = pred_timestamps - else: - # If no prediction timestamps, calculate from the last timestamp of historical data - if len(historical_df) > 0: - last_timestamp = historical_df['timestamps'].iloc[-1] - time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] if len(df) > 1 else pd.Timedelta(hours=1) - actual_timestamps = pd.date_range( - start=last_timestamp + time_diff, - periods=len(actual_df), - freq=time_diff - ) - else: - actual_timestamps = range(len(historical_df), len(historical_df) + len(actual_df)) - else: - actual_timestamps = range(len(historical_df), len(historical_df) + len(actual_df)) + if not pred_timestamps.empty: + actual_timestamps = pred_timestamps # Align with prediction timestamps if they were generated + elif not historical_df.empty: + last_timestamp = historical_df['timestamps'].iloc[-1] + time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] if len(df) > 1 else pd.Timedelta(hours=1) + actual_timestamps = pd.date_range( + start=last_timestamp + time_diff, + periods=len(actual_df), + freq=time_diff + ) + if actual_timestamps.empty: + actual_timestamps = pd.Series(range(len(historical_df), len(historical_df) + len(actual_df))) # Fallback to index Series fig.add_trace(go.Candlestick( x=actual_timestamps, @@ -291,14 +293,14 @@ def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, his high=actual_df['high'], low=actual_df['low'], close=actual_df['close'], - name='Actual Data (120 data points)', + name=f'Actual Data ({len(actual_df)} data points)', # Make count dynamic increasing_line_color='#FF9800', decreasing_line_color='#F44336' )) # Update layout fig.update_layout( - title='Kronos Financial Prediction Results - 400 Historical Points + 120 Prediction Points vs 120 Actual Points', + title=f'Kronos Financial Prediction Results - {len(historical_df)} Historical Points + {len(pred_df) if pred_df is not None else 0} Prediction Points' + (f' vs {len(actual_df)} Actual Points' if actual_df is not None and not actual_df.empty else ''), # Dynamic title xaxis_title='Time', yaxis_title='Price', template='plotly_white', @@ -307,26 +309,55 @@ def create_prediction_chart(df, pred_df, lookback, pred_len, actual_df=None, his ) # Ensure x-axis time continuity - if 'timestamps' in historical_df.columns: - # Get all timestamps and sort them - all_timestamps = [] - if len(historical_df) > 0: - all_timestamps.extend(historical_df['timestamps']) - if 'pred_timestamps' in locals(): - all_timestamps.extend(pred_timestamps) - if 'actual_timestamps' in locals(): - all_timestamps.extend(actual_timestamps) - - if all_timestamps: - all_timestamps = sorted(all_timestamps) + # Collect all potential timestamps from historical, prediction, and actual data + all_timestamps_series = [] + if not historical_df.empty and 'timestamps' in historical_df.columns: + all_timestamps_series.append(historical_df['timestamps']) + if not pred_timestamps.empty: + all_timestamps_series.append(pred_timestamps) + if not actual_timestamps.empty: + all_timestamps_series.append(actual_timestamps) + + if all_timestamps_series: + all_timestamps_combined = pd.concat(all_timestamps_series).drop_duplicates().sort_values() + if not all_timestamps_combined.empty: fig.update_xaxes( - range=[all_timestamps[0], all_timestamps[-1]], + range=[all_timestamps_combined.iloc[0], all_timestamps_combined.iloc[-1]], rangeslider_visible=False, type='date' ) return json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) +def get_latest_prediction_file() -> str | None: + """ + Scans the 'prediction_results' directory and returns the path to the most recent prediction JSON file. + Assumes filename format 'prediction_YYYYMMDD_HHMMSS.json'. + Returns None if no prediction files are found or directory does not exist. + """ + results_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'prediction_results') + if not os.path.exists(results_dir): + print(f"Warning: Prediction results directory not found at {results_dir}") + return None + + json_files = [f for f in os.listdir(results_dir) if f.startswith('prediction_') and f.endswith('.json')] + if not json_files: + print(f"No prediction JSON files found in {results_dir}") + return None + + try: + # Sort files by the timestamp embedded in their names + json_files.sort(key=lambda x: datetime.datetime.strptime(x, 'prediction_%Y%m%d_%H%M%S.json'), reverse=True) + latest_file = os.path.join(results_dir, json_files[0]) + print(f"Found latest prediction file: {latest_file}") + return latest_file + except ValueError as e: + print(f"Error parsing prediction filenames for sorting: {e}. Skipping auto-load of latest prediction.") + return None + except Exception as e: + print(f"An unexpected error occurred while getting latest prediction file: {e}") + return None + @app.route('/') def index(): """Home page""" @@ -353,13 +384,14 @@ def load_data(): return jsonify({'error': error}), 400 # Detect data time frequency - def detect_timeframe(df): - if len(df) < 2: + def detect_timeframe(df_local: pd.DataFrame) -> str: + if len(df_local) < 2: return "Unknown" time_diffs = [] - for i in range(1, min(10, len(df))): # Check first 10 time differences - diff = df['timestamps'].iloc[i] - df['timestamps'].iloc[i-1] + # Check first 10 time differences or up to df_local length + for i in range(1, min(10, len(df_local))): + diff = df_local['timestamps'].iloc[i] - df_local['timestamps'].iloc[i-1] time_diffs.append(diff) if not time_diffs: @@ -399,6 +431,7 @@ def detect_timeframe(df): }) except Exception as e: + print(f"Error in /api/load-data: {e}") # Added error log return jsonify({'error': f'Failed to load data: {str(e)}'}), 500 @app.route('/api/predict', methods=['POST']) @@ -426,6 +459,12 @@ def predict(): if len(df) < lookback: return jsonify({'error': f'Insufficient data length, need at least {lookback} rows'}), 400 + # New log message for prediction data usage + if 'timestamps' in df.columns and not df.empty: + print(f"Preparing prediction for data from {file_path}. Total rows: {len(df)}. Time range: {df['timestamps'].min().isoformat()} to {df['timestamps'].max().isoformat()}") + else: + print(f"Preparing prediction for data from {file_path}. Total rows: {len(df)}. No timestamp column found.") + # Perform prediction if MODEL_AVAILABLE and predictor is not None: try: @@ -438,8 +477,13 @@ def predict(): # Process time period selection start_date = data.get('start_date') + x_df: pd.DataFrame + x_timestamp: pd.Series + y_timestamp: pd.Series + actual_df: pd.DataFrame + if start_date: - # Custom time period - fix logic: use data within selected window + # Custom time period - use data within selected window start_dt = pd.to_datetime(start_date) # Find data after start time @@ -454,21 +498,36 @@ def predict(): x_df = time_range_df.iloc[:lookback][required_cols] x_timestamp = time_range_df.iloc[:lookback]['timestamps'] - # Use last pred_len data points within selected window as actual values - y_timestamp = time_range_df.iloc[lookback:lookback+pred_len]['timestamps'] + # Use last pred_len data points within selected window as actual values for comparison + actual_df = time_range_df.iloc[lookback:lookback+pred_len] + y_timestamp = actual_df['timestamps'] # For model output alignment - # Calculate actual time period length + # Calculate actual time period length (for description) start_timestamp = time_range_df['timestamps'].iloc[0] end_timestamp = time_range_df['timestamps'].iloc[lookback+pred_len-1] time_span = end_timestamp - start_timestamp prediction_type = f"Kronos model prediction (within selected window: first {lookback} data points for prediction, last {pred_len} data points for comparison, time span: {time_span})" else: - # Use latest data - x_df = df.iloc[:lookback][required_cols] - x_timestamp = df.iloc[:lookback]['timestamps'] - y_timestamp = df.iloc[lookback:lookback+pred_len]['timestamps'] - prediction_type = "Kronos model prediction (latest data)" + # Use latest data: slice the last (lookback + pred_len) data points from df + # The prediction input (x_df) will be the first 'lookback' points of this slice. + # The actual data (actual_df) will be the last 'pred_len' points of this slice. + if len(df) < lookback + pred_len: + print(f"Warning: Not enough data for latest prediction with comparison. Available: {len(df)}, Required: {lookback+pred_len}. Proceeding with prediction without actual comparison data.") + # If not enough data for full lookback + pred_len, just take the last 'lookback' for prediction input. + x_df = df.iloc[-lookback:][required_cols] + x_timestamp = df.iloc[-lookback:]['timestamps'] + actual_df = pd.DataFrame() # No actuals for comparison + y_timestamp = pd.Series(dtype='datetime64[ns]') # Empty timestamp series for model output alignment + prediction_type = "Kronos model prediction (latest available data, no comparison)" + else: + # Take the last (lookback + pred_len) points for the full window + latest_window_df = df.iloc[-(lookback + pred_len):] + x_df = latest_window_df.iloc[:lookback][required_cols] # Input for prediction model + x_timestamp = latest_window_df.iloc[:lookback]['timestamps'] # Timestamps for input + actual_df = latest_window_df.iloc[lookback:] # Actuals for comparison + y_timestamp = latest_window_df.iloc[lookback:]['timestamps'] # Timestamps for model output alignment + prediction_type = "Kronos model prediction (latest data with comparison)" # Ensure timestamps are Series format, not DatetimeIndex, to avoid .dt attribute error in Kronos model if isinstance(x_timestamp, pd.DatetimeIndex): @@ -476,6 +535,12 @@ def predict(): if isinstance(y_timestamp, pd.DatetimeIndex): y_timestamp = pd.Series(y_timestamp, name='timestamps') + # Log input data range before prediction + if not x_timestamp.empty and 'timestamps' in df.columns: + print(f"Kronos prediction input: Using {len(x_df)} data points from {x_timestamp.min().isoformat()} to {x_timestamp.max().isoformat()} for prediction.") + else: + print(f"Kronos prediction input: Using {len(x_df)} data points for prediction (no timestamp info).") + pred_df = predictor.predict( df=x_df, x_timestamp=x_timestamp, @@ -487,107 +552,72 @@ def predict(): ) except Exception as e: + print(f"Kronos model prediction failed: {e}") # Log error for server side return jsonify({'error': f'Kronos model prediction failed: {str(e)}'}), 500 else: return jsonify({'error': 'Kronos model not loaded, please load model first'}), 400 # Prepare actual data for comparison (if exists) actual_data = [] - actual_df = None - - if start_date: # Custom time period - # Fix logic: use data within selected window - # Prediction uses first 400 data points within selected window - # Actual data should be last 120 data points within selected window - start_dt = pd.to_datetime(start_date) - - # Find data starting from start_date - mask = df['timestamps'] >= start_dt - time_range_df = df[mask] - - if len(time_range_df) >= lookback + pred_len: - # Get last 120 data points within selected window as actual values - actual_df = time_range_df.iloc[lookback:lookback+pred_len] - - for i, (_, row) in enumerate(actual_df.iterrows()): - actual_data.append({ - 'timestamp': row['timestamps'].isoformat(), - 'open': float(row['open']), - 'high': float(row['high']), - 'low': float(row['low']), - 'close': float(row['close']), - 'volume': float(row['volume']) if 'volume' in row else 0, - 'amount': float(row['amount']) if 'amount' in row else 0 - }) - else: # Latest data - # Prediction uses first 400 data points - # Actual data should be 120 data points after first 400 data points - if len(df) >= lookback + pred_len: - actual_df = df.iloc[lookback:lookback+pred_len] - for i, (_, row) in enumerate(actual_df.iterrows()): - actual_data.append({ - 'timestamp': row['timestamps'].isoformat(), - 'open': float(row['open']), - 'high': float(row['high']), - 'low': float(row['low']), - 'close': float(row['close']), - 'volume': float(row['volume']) if 'volume' in row else 0, - 'amount': float(row['amount']) if 'amount' in row else 0 - }) + if not actual_df.empty: + for _, row in actual_df.iterrows(): + actual_data.append({ + 'timestamp': row['timestamps'].isoformat(), + 'open': float(row['open']), + 'high': float(row['high']), + 'low': float(row['low']), + 'close': float(row['close']), + 'volume': float(row['volume']) if 'volume' in row else 0.0, # ensure float type for volume/amount + 'amount': float(row['amount']) if 'amount' in row else 0.0 + }) # Create chart - pass historical data start position if start_date: # Custom time period: find starting position of historical data in original df start_dt = pd.to_datetime(start_date) mask = df['timestamps'] >= start_dt - historical_start_idx = df[mask].index[0] if len(df[mask]) > 0 else 0 + historical_start_idx = df[mask].index[0] if not df[mask].empty else 0 else: - # Latest data: start from beginning - historical_start_idx = 0 + # Latest data: start from the beginning of the `latest_window_df` slice in the original `df` + # This ensures the chart displays the entire window used for the latest prediction and comparison. + historical_start_idx = max(0, len(df) - (lookback + pred_len)) chart_json = create_prediction_chart(df, pred_df, lookback, pred_len, actual_df, historical_start_idx) - # Prepare prediction result data - fix timestamp calculation logic + # Prepare prediction result data - calculate future timestamps + future_timestamps = pd.Series(dtype='datetime64[ns]') # Initialize if 'timestamps' in df.columns: - if start_date: - # Custom time period: use selected window data to calculate timestamps - start_dt = pd.to_datetime(start_date) - mask = df['timestamps'] >= start_dt - time_range_df = df[mask] - - if len(time_range_df) >= lookback: - # Calculate prediction timestamps starting from last time point of selected window - last_timestamp = time_range_df['timestamps'].iloc[lookback-1] - time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] - future_timestamps = pd.date_range( - start=last_timestamp + time_diff, - periods=pred_len, - freq=time_diff - ) - else: - future_timestamps = [] - else: - # Latest data: calculate from last time point of entire data file - last_timestamp = df['timestamps'].iloc[-1] - time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] + if not x_timestamp.empty: + last_input_timestamp = x_timestamp.iloc[-1] + time_diff = df['timestamps'].iloc[1] - df['timestamps'].iloc[0] if len(df) > 1 else pd.Timedelta(hours=1) future_timestamps = pd.date_range( - start=last_timestamp + time_diff, + start=last_input_timestamp + time_diff, periods=pred_len, freq=time_diff ) else: - future_timestamps = range(len(df), len(df) + pred_len) - + # If no timestamps, use index relative to the end of input data + future_timestamps = pd.Series(range(len(x_df), len(x_df) + pred_len)) + + # Log predicted output time range + if not future_timestamps.empty: + if isinstance(future_timestamps.iloc[0], datetime.datetime): + print(f"Prediction output covers time range: {future_timestamps.iloc[0].isoformat()} to {future_timestamps.iloc[-1].isoformat()}.") + else: # range of integers + print(f"Prediction output covers index range: {future_timestamps.iloc[0]} to {future_timestamps.iloc[-1]}.") + else: + print("No prediction output timestamps generated.") + prediction_results = [] for i, (_, row) in enumerate(pred_df.iterrows()): prediction_results.append({ - 'timestamp': future_timestamps[i].isoformat() if i < len(future_timestamps) else f"T{i}", + 'timestamp': future_timestamps.iloc[i].isoformat() if i < len(future_timestamps) else f"T{i}", 'open': float(row['open']), 'high': float(row['high']), 'low': float(row['low']), 'close': float(row['close']), - 'volume': float(row['volume']) if 'volume' in row else 0, - 'amount': float(row['amount']) if 'amount' in row else 0 + 'volume': float(row['volume']) if 'volume' in row else 0.0, # ensure float type + 'amount': float(row['amount']) if 'amount' in row else 0.0 }) # Save prediction results to file @@ -616,11 +646,12 @@ def predict(): 'chart': chart_json, 'prediction_results': prediction_results, 'actual_data': actual_data, - 'has_comparison': len(actual_data) > 0, - 'message': f'Prediction completed, generated {pred_len} prediction points' + (f', including {len(actual_data)} actual data points for comparison' if len(actual_data) > 0 else '') + 'has_comparison': not actual_df.empty, + 'message': f'Prediction completed, generated {pred_len} prediction points' + (f', including {len(actual_data)} actual data points for comparison' if not actual_df.empty else '') }) except Exception as e: + print(f"Error in /api/predict: {e}") # Added error log return jsonify({'error': f'Prediction failed: {str(e)}'}), 500 @app.route('/api/load-model', methods=['POST']) @@ -648,6 +679,8 @@ def load_model(): # Create predictor predictor = KronosPredictor(model, tokenizer, device=device, max_context=model_config['context_length']) + print(f"Kronos model loaded successfully: {model_config['name']} ({model_config['params']}) on {device}") # Added informative log + return jsonify({ 'success': True, 'message': f'Model loaded successfully: {model_config["name"]} ({model_config["params"]}) on {device}', @@ -660,6 +693,7 @@ def load_model(): }) except Exception as e: + print(f"Error in /api/load-model: {e}") # Added error log return jsonify({'error': f'Model loading failed: {str(e)}'}), 500 @app.route('/api/available-models') @@ -697,12 +731,35 @@ def get_model_status(): 'message': 'Kronos model library not available, please install related dependencies' }) +@app.route('/api/latest-prediction') +def get_latest_prediction(): + """ + Get the latest saved prediction results. + This endpoint facilitates displaying the most recent pre-computed prediction + for a "live demo" context, assuming an external process regularly runs predictions + and saves them using `save_prediction_results`. + """ + latest_file_path = get_latest_prediction_file() + if not latest_file_path: + return jsonify({'success': False, 'message': 'No prediction results found'}), 404 + + try: + with open(latest_file_path, 'r', encoding='utf-8') as f: + prediction_data = json.load(f) + return jsonify({'success': True, 'prediction_data': prediction_data}) + except json.JSONDecodeError as e: + print(f"Error decoding JSON from {latest_file_path}: {e}") + return jsonify({'success': False, 'error': f'Failed to parse latest prediction file: {str(e)}'}), 500 + except Exception as e: + print(f"Failed to load latest prediction from {latest_file_path}: {e}") + return jsonify({'success': False, 'error': f'Failed to load latest prediction: {str(e)}'}), 500 + if __name__ == '__main__': print("Starting Kronos Web UI...") print(f"Model availability: {MODEL_AVAILABLE}") if MODEL_AVAILABLE: print("Tip: You can load Kronos model through /api/load-model endpoint") else: - print("Tip: Will use simulated data for demonstration") + print("Tip: Kronos model library not found. Will use simulated data for demonstration if prediction logic were implemented (currently requires actual model for prediction).") - app.run(debug=True, host='0.0.0.0', port=7070) + app.run(debug=True, host='0.0.0.0', port=7070) \ No newline at end of file