File size: 2,477 Bytes
d445415
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# utils/prediction_utils.py
import warnings
warnings.filterwarnings("ignore")

import numpy as np

# Prophet ์‹œ๋„, ์‹คํŒจ ์‹œ Holt(์ง€์ˆ˜ ํ‰ํ™œ) ํด๋ฐฑ
try:
    from prophet import Prophet
    _HAS_PROPHET = True
except Exception:
    _HAS_PROPHET = False

try:
    from statsmodels.tsa.holtwinters import ExponentialSmoothing
    _HAS_HOLT = True
except Exception:
    _HAS_HOLT = False

import pandas as pd

def prophet_emotion_forecast(scores, steps=3):
    """
    Prophet ๊ธฐ๋ฐ˜ ์˜ˆ์ธก. ์ž…๋ ฅ: ์ ์ˆ˜ ๋ฆฌ์ŠคํŠธ(0~100)
    ๋ฐ˜ํ™˜: ์˜ˆ์ธก ์ ์ˆ˜ ๋ฆฌ์ŠคํŠธ ๊ธธ์ด=steps (float)
    ์ฐธ๊ณ : Taylor & Letham (2018) 'Forecasting at Scale'
    """
    if not _HAS_PROPHET or len(scores) < 5:
        return None

    df = pd.DataFrame({
        'ds': pd.date_range(start='2023-01-01', periods=len(scores), freq='D'),
        'y': scores
    })
    model = Prophet(
        daily_seasonality=False, weekly_seasonality=False, yearly_seasonality=False,
        changepoint_prior_scale=0.2  # ๋ณ€ํ™”์  ๋ฏผ๊ฐ๋„
    )
    model.fit(df)
    future = model.make_future_dataframe(periods=steps)
    forecast = model.predict(future)
    tail = forecast[['yhat']].tail(steps)['yhat'].to_list()
    # ์•ˆ์ „ ํด๋ฆฌํ•‘
    return [float(np.clip(x, 0, 100)) for x in tail]

def holt_fallback_forecast(scores, steps=3):
    """
    Holt ์ง€์ˆ˜ํ‰ํ™œ(์ถ”์„ธํ˜•) ํด๋ฐฑ ์˜ˆ์ธก.
    Prophet ์‚ฌ์šฉ ๋ถˆ๊ฐ€ ํ™˜๊ฒฝ์—์„œ ๊ฐ„๋‹จํ•˜๊ณ  ์•ˆ์ •์ .
    """
    if not _HAS_HOLT or len(scores) < 4:
        return None
    try:
        model = ExponentialSmoothing(
            np.array(scores, dtype=float),
            trend='add', seasonal=None
        ).fit()
        fcast = model.forecast(steps)
        return [float(np.clip(x, 0, 100)) for x in fcast.tolist()]
    except Exception:
        return None

def forecast_scores(scores, steps=3):
    """
    ํ†ตํ•ฉ ์˜ˆ์ธก ์—”์ง„:
    1) Prophet ์„ฑ๊ณต ์‹œ Prophet ์‚ฌ์šฉ
    2) ์•„๋‹ˆ๋ฉด Holt ํด๋ฐฑ
    3) ๊ทธ๋ž˜๋„ ์•ˆ ๋˜๋ฉด, ๋งˆ์ง€๋ง‰ ๊ธฐ์šธ๊ธฐ ์„ ํ˜• ์™ธ์‚ฝ(๊ทน๋‹จ ํด๋ฐฑ)
    """
    if len(scores) < 2:
        return [scores[-1]] * steps if scores else [0.0]*steps

    yhat = prophet_emotion_forecast(scores, steps=steps)
    if yhat is not None:
        return yhat

    yhat = holt_fallback_forecast(scores, steps=steps)
    if yhat is not None:
        return yhat

    # ๋งˆ์ง€๋ง‰ ๋‘ ์ ์˜ ๊ธฐ์šธ๊ธฐ๋กœ ์™ธ์‚ฝ
    slope = scores[-1] - scores[-2]
    return [float(np.clip(scores[-1] + slope*(i+1), 0, 100)) for i in range(steps)]