import io, base64
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.seasonal import STL
from statsmodels.graphics.tsaplots import plot_acf
# 1. Đọc dữ liệu từ biến môi trường của JavaScript truyền sang
df = pd.read_csv(io.StringIO(csv_raw_text))
df[TIME_COL] = pd.to_datetime(df[TIME_COL])
df = df.sort_values(TIME_COL).set_index(TIME_COL)
y_series = df[COL_NAME].astype(float)
# Nội suy tuyến tính bảo toàn lưới thời gian
y_clean = y_series.interpolate(method='linear').bfill().ffill()
# 2. Hồi quy điều hòa (Harmonic Regression - 2 bậc)
t = np.arange(len(y_clean)) / float(PERIOD_VAL)
X = np.column_stack([
np.ones_like(t),
np.cos(2 * np.pi * t), np.sin(2 * np.pi * t),
np.cos(4 * np.pi * t), np.sin(4 * np.pi * t),
t
])
b, *_ = np.linalg.lstsq(X, y_clean.values, rcond=None)
# Trích xuất các thành phần của Harmonic
harmonic_trend = X[:, 0] * b[0] + X[:, 5] * b[5]
harmonic_seasonal = X[:, 1:5] @ b[1:5]
harmonic_resid = y_clean.values - (harmonic_trend + harmonic_seasonal)
y_deseason_harmonic = y_clean.values - harmonic_seasonal
# 3. Phân rã theo chuẩn STL (Robust LOESS)
stl = STL(y_clean, period=PERIOD_VAL, seasonal=13, robust=True)
stl_res = stl.fit()
df_out = pd.DataFrame(index=df.index)
df_out[COL_NAME] = y_series
df_out[f'{COL_NAME}_Harmonic_Deseason'] = y_deseason_harmonic
df_out[f'{COL_NAME}_Harmonic_Trend'] = harmonic_trend
df_out[f'{COL_NAME}_Harmonic_Seasonal'] = harmonic_seasonal
df_out[f'{COL_NAME}_Harmonic_Resid'] = harmonic_resid
df_out[f'{COL_NAME}_STL_Trend'] = stl_res.trend
df_out[f'{COL_NAME}_STL_Seasonal'] = stl_res.seasonal
df_out[f'{COL_NAME}_STL_Resid'] = stl_res.resid
df_out[f'{COL_NAME}_STL_Deseason'] = stl_res.trend + stl_res.resid
# 4. Xuất đồ thị kiểm định sang định dạng Base64
fig = plt.figure(figsize=(12, 16))
fig.suptitle(f"KẾT QUẢ KHỬ MÙA: {COL_NAME.upper()}", fontsize=15, fontweight='bold')
# Subplot 1: Dữ liệu gốc
ax1 = plt.subplot(5, 1, 1)
ax1.plot(df_out.index, df_out[COL_NAME], color='#1f77b4', linewidth=1.5, label='Original Data')
ax1.set_title("Dữ liệu gốc (Original Signal)", fontsize=11, fontweight='bold')
ax1.grid(True, linestyle='--', alpha=0.5)
ax1.legend(loc='upper right')
# Subplot 2: So sánh Xu thế (Trend)
ax2 = plt.subplot(5, 1, 2)
ax2.plot(df_out.index, df_out[f'{COL_NAME}_Harmonic_Trend'], color='#ff7f0e', linewidth=1.5, linestyle='--', label='Harmonic Trend (Linear)')
ax2.plot(df_out.index, df_out[f'{COL_NAME}_STL_Trend'], color='#9467bd', linewidth=1.5, alpha=0.85, label='STL Trend (Non-linear)')
ax2.set_title("So sánh Xu thế dài hạn (Trend Component)", fontsize=11, fontweight='bold')
ax2.grid(True, linestyle='--', alpha=0.5)
ax2.legend(loc='upper right')
# Subplot 3: So sánh Thành phần mùa vụ (Seasonal)
ax3 = plt.subplot(5, 1, 3)
ax3.plot(df_out.index, df_out[f'{COL_NAME}_Harmonic_Seasonal'], color='#ff7f0e', linewidth=1.5, linestyle='--', label='Harmonic Seasonality')
ax3.plot(df_out.index, df_out[f'{COL_NAME}_STL_Seasonal'], color='#2ca02c', linewidth=1.5, alpha=0.85, label='STL Seasonality')
ax3.set_title(f"So sánh Thành phần mùa vụ S(t) - Chu kỳ {PERIOD_VAL}", fontsize=11, fontweight='bold')
ax3.grid(True, linestyle='--', alpha=0.5)
ax3.legend(loc='upper right')
# Subplot 4: So sánh Thành phần dư sai (Residual)
ax4 = plt.subplot(5, 1, 4)
ax4.plot(df_out.index, df_out[f'{COL_NAME}_Harmonic_Resid'], color='#ff7f0e', linewidth=1.2, linestyle='--', alpha=0.8, label='Harmonic Residual')
ax4.plot(df_out.index, df_out[f'{COL_NAME}_STL_Resid'], color='#7f7f7f', linewidth=1.5, alpha=0.85, label='STL Residual')
ax4.set_title("So sánh Thành phần dư sai (Residual / Nhiễu)", fontsize=11, fontweight='bold')
ax4.grid(True, linestyle='--', alpha=0.5)
ax4.legend(loc='upper right')
# Subplot 5: ACF Dữ liệu gốc
ax5 = plt.subplot(5, 2, 9)
plot_acf(y_clean, lags=min(36, len(y_clean)//2), ax=ax5, color='#2563eb', title="ACF: Dữ liệu gốc")
ax5.set_xlabel("Lag")
ax5.grid(True, linestyle='--', alpha=0.5)
# Subplot 6: ACF Đã khử mùa
ax6 = plt.subplot(5, 2, 10)
plot_acf(df_out[f'{COL_NAME}_STL_Deseason'], lags=min(36, len(y_clean)//2), ax=ax6, color='#dc2626', title="ACF: Đã khử mùa (STL)")
ax6.set_xlabel("Lag")
ax6.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout(pad=2.0)
buf = io.BytesIO()
plt.savefig(buf, format='png', dpi=140, bbox_inches='tight')
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode('utf-8')
plt.close(fig)
csv_buffer = io.StringIO()
df_out.to_csv(csv_buffer)
csv_output = csv_buffer.getvalue()
(img_base64, csv_output)