2024-11-05 20:50:07 +04:00
|
|
|
|
import numpy as np
|
|
|
|
|
import pandas as pd
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
from tensorflow.keras.models import load_model
|
|
|
|
|
from sklearn.preprocessing import MinMaxScaler
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
import io
|
|
|
|
|
import joblib
|
|
|
|
|
from flask import Flask, request, jsonify, Blueprint, send_file
|
|
|
|
|
from flasgger import Swagger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
api = Blueprint('api', __name__)
|
|
|
|
|
Swagger(app)
|
|
|
|
|
|
|
|
|
|
# Загружаем модель и scaler
|
|
|
|
|
model = load_model("my_model_1H.keras")
|
|
|
|
|
scaler = MinMaxScaler(feature_range=(0, 1))
|
|
|
|
|
|
|
|
|
|
# Загружаем данные
|
|
|
|
|
column_names = ['product_url', 'price', 'datetime']
|
|
|
|
|
|
2024-11-11 20:13:53 +04:00
|
|
|
|
df = pd.read_csv('parsed_data_public_price_history_all.csv')
|
2024-11-05 20:50:07 +04:00
|
|
|
|
|
|
|
|
|
# Преобразуем колонку 'datetime' в тип данных datetime
|
|
|
|
|
df['datetime'] = pd.to_datetime(df['datetime'], format='mixed', utc=True)
|
|
|
|
|
df['price'] = df['price'].astype(float)
|
|
|
|
|
|
|
|
|
|
q_low = df['price'].quantile(0.55)
|
|
|
|
|
q_hi = df['price'].quantile(0.75)
|
|
|
|
|
q_range = q_hi - q_low
|
|
|
|
|
df = df[(df['price'] < q_hi + 1.5 * q_range) & (df['price'] > q_low - 1.5 * q_range)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df_hourly_avg = df[['price', 'datetime']]
|
|
|
|
|
df_hourly_avg['datetime'] = df_hourly_avg['datetime'].dt.floor('1H')
|
|
|
|
|
df_hourly_avg = df_hourly_avg.groupby('datetime').agg({'price': 'mean'}).reset_index()
|
|
|
|
|
|
|
|
|
|
df_hourly_avg.set_index('datetime', inplace=True)
|
|
|
|
|
|
|
|
|
|
# Подготовка данных для прогнозирования
|
|
|
|
|
def prepare_data(df, days_forward=7):
|
|
|
|
|
last_date = df.index[-1]
|
|
|
|
|
scaled_data = scaler.fit_transform(df[['price']].values)
|
|
|
|
|
n = 3 # число временных шагов (можно менять)
|
|
|
|
|
X_test = []
|
|
|
|
|
|
|
|
|
|
# Формируем X_test на основе последних n значений
|
|
|
|
|
for i in range(n, len(scaled_data)):
|
|
|
|
|
X_test.append(scaled_data[i - n:i, 0])
|
|
|
|
|
X_test = np.array(X_test)
|
|
|
|
|
X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1))
|
|
|
|
|
|
|
|
|
|
# Предсказание на 7 дней вперед
|
|
|
|
|
predictions = []
|
|
|
|
|
current_input = X_test[-1] # начальное состояние для прогноза
|
|
|
|
|
|
|
|
|
|
for _ in range(days_forward):
|
|
|
|
|
pred = model.predict(np.expand_dims(current_input, axis=0))
|
|
|
|
|
predictions.append(pred[0, 0])
|
|
|
|
|
|
|
|
|
|
# Обновляем current_input, добавляя новое предсказание и удаляя старое
|
|
|
|
|
current_input = np.append(current_input[1:], pred).reshape(n, 1)
|
|
|
|
|
|
|
|
|
|
# Масштабируем предсказания обратно
|
|
|
|
|
predictions = scaler.inverse_transform(np.array(predictions).reshape(-1, 1)).flatten()
|
|
|
|
|
|
|
|
|
|
future_dates = [last_date + timedelta(days=i) for i in range(1, days_forward + 1)]
|
|
|
|
|
forecast_df = pd.DataFrame({'date': future_dates, 'predicted_price': predictions})
|
|
|
|
|
return forecast_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Построение графика
|
|
|
|
|
def plot_price(forecast_df):
|
|
|
|
|
plt.figure(figsize=(14, 7))
|
|
|
|
|
plt.plot(df_hourly_avg.index, df_hourly_avg['price'], label='Actual Price', color='blue')
|
|
|
|
|
plt.plot(forecast_df['date'], forecast_df['predicted_price'], label='Predicted Price', color='orange')
|
|
|
|
|
plt.title("Price Prediction")
|
|
|
|
|
plt.xlabel("Date")
|
|
|
|
|
plt.ylabel("Price")
|
|
|
|
|
plt.legend()
|
|
|
|
|
plt.grid(True)
|
|
|
|
|
|
|
|
|
|
img = io.BytesIO()
|
|
|
|
|
plt.savefig(img, format='png')
|
|
|
|
|
img.seek(0)
|
|
|
|
|
plt.close()
|
|
|
|
|
return img
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@api.route('/predict_price', methods=['GET'])
|
|
|
|
|
def predict_price():
|
|
|
|
|
"""
|
|
|
|
|
Предсказание цены на 7 дней вперед
|
|
|
|
|
---
|
|
|
|
|
responses:
|
|
|
|
|
200:
|
|
|
|
|
description: JSON с предсказаниями цен и днем минимальной цены
|
|
|
|
|
schema:
|
|
|
|
|
type: object
|
|
|
|
|
properties:
|
|
|
|
|
forecast:
|
|
|
|
|
type: array
|
|
|
|
|
items:
|
|
|
|
|
type: object
|
|
|
|
|
properties:
|
|
|
|
|
date:
|
|
|
|
|
type: string
|
|
|
|
|
format: date
|
|
|
|
|
predicted_price:
|
|
|
|
|
type: number
|
|
|
|
|
min_price_day:
|
|
|
|
|
type: object
|
|
|
|
|
properties:
|
|
|
|
|
date:
|
|
|
|
|
type: string
|
|
|
|
|
format: date
|
|
|
|
|
price:
|
|
|
|
|
type: number
|
|
|
|
|
"""
|
|
|
|
|
forecast_df = prepare_data(df_hourly_avg)
|
|
|
|
|
forecast_list = forecast_df.to_dict(orient='records') # Преобразование в список словарей
|
|
|
|
|
|
|
|
|
|
# Преобразуем значения 'predicted_price' в float
|
|
|
|
|
for record in forecast_list:
|
|
|
|
|
record['predicted_price'] = float(record['predicted_price'])
|
|
|
|
|
|
|
|
|
|
# Определяем день с минимальной предсказанной ценой
|
|
|
|
|
min_price_day = forecast_df.loc[forecast_df['predicted_price'].idxmin()]
|
|
|
|
|
|
|
|
|
|
# Преобразуем минимальную цену в float
|
|
|
|
|
min_price_day_price = float(min_price_day['predicted_price'])
|
|
|
|
|
|
|
|
|
|
# Формируем ответ
|
|
|
|
|
return jsonify({
|
|
|
|
|
'forecast': forecast_list,
|
|
|
|
|
'min_price_day': {
|
|
|
|
|
'date': min_price_day['date'].strftime('%Y-%m-%d'),
|
|
|
|
|
'price': min_price_day_price
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Эндпоинт для получения графика
|
|
|
|
|
@api.route('/plot', methods=['GET'])
|
|
|
|
|
def plot():
|
|
|
|
|
"""
|
|
|
|
|
Получение графика предсказанных и фактических цен
|
|
|
|
|
---
|
|
|
|
|
responses:
|
|
|
|
|
200:
|
|
|
|
|
description: Возвращает график предсказанных и фактических цен в формате PNG
|
|
|
|
|
content:
|
|
|
|
|
image/png:
|
|
|
|
|
schema:
|
|
|
|
|
type: string
|
|
|
|
|
format: binary
|
|
|
|
|
"""
|
|
|
|
|
forecast_df = prepare_data(df_hourly_avg)
|
|
|
|
|
img = plot_price(forecast_df)
|
|
|
|
|
return send_file(img, mimetype='image/png')
|
|
|
|
|
|
|
|
|
|
app.register_blueprint(api, url_prefix='/api')
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
app.run(debug=True)
|