GDank-StockSense / app /forecaster.py
ArizalMuluk's picture
Upload 12 files
222ee23 verified
"""
Future Forecaster — N+1 only
Prediksi 1 bulan ke depan langsung dari data terakhir.
"""
import numpy as np
import pandas as pd
from dateutil.relativedelta import relativedelta
FITUR_LGBM = [
"Price", "Is_Holiday", "Bulan", "Kuartal", "Tahun",
"Category_enc", "Types_enc",
"Qty_lag1", "Qty_lag2", "Qty_lag3", "Qty_lag12",
"Qty_roll3", "Qty_roll6", "Qty_roll12"
]
def _get_holiday(target_date: pd.Timestamp, country_code: str) -> int:
try:
import holidays
hl = getattr(holidays, country_code)(years=[target_date.year])
days_in_month = pd.date_range(
start=target_date.replace(day=1), end=target_date, freq="D"
)
return int(any(d.date() in hl for d in days_in_month))
except Exception:
return 0
def forecast_one_product(
df_cont : pd.DataFrame,
product_id : str,
model,
le_cat,
le_types,
country_code: str = "ID",
) -> dict:
"""
Prediksi stok 1 bulan ke depan (N+1) dari data terakhir.
N = bulan data terakhir
N+1 = bulan yang diprediksi
"""
df_prod = df_cont[df_cont["Product_ID"] == product_id].copy()
df_prod = df_prod.sort_values("Date").reset_index(drop=True)
if df_prod.empty:
return {"error": f"Produk {product_id} tidak ditemukan."}
last_date = df_prod["Date"].max()
target_date = last_date + relativedelta(months=1)
qty_history = df_prod["Quantity"].values
def safe_lag(n):
return float(qty_history[-n]) if len(qty_history) >= n else 0.0
qty_lag1 = safe_lag(1)
qty_lag2 = safe_lag(2)
qty_lag3 = safe_lag(3)
qty_lag12 = safe_lag(12)
qty_roll3 = float(np.mean(qty_history[-3:])) if len(qty_history) >= 3 else float(np.mean(qty_history))
qty_roll6 = float(np.mean(qty_history[-6:])) if len(qty_history) >= 6 else float(np.mean(qty_history))
qty_roll12 = float(np.mean(qty_history[-12:])) if len(qty_history) >= 12 else float(np.mean(qty_history))
price = float(df_prod["Price"].iloc[-1])
category = str(df_prod["Category"].iloc[-1])
types = str(df_prod["Types"].iloc[-1])
name = str(df_prod["Product_Name"].iloc[-1])
try: cat_enc = int(le_cat.transform([category])[0])
except: cat_enc = 0
try: types_enc = int(le_types.transform([types])[0])
except: types_enc = 0
is_holiday = _get_holiday(target_date, country_code)
feat_row = pd.DataFrame([{
"Price": price, "Is_Holiday": is_holiday,
"Bulan": target_date.month, "Kuartal": (target_date.month - 1) // 3 + 1,
"Tahun": target_date.year, "Category_enc": cat_enc, "Types_enc": types_enc,
"Qty_lag1": qty_lag1, "Qty_lag2": qty_lag2, "Qty_lag3": qty_lag3,
"Qty_lag12": qty_lag12, "Qty_roll3": qty_roll3,
"Qty_roll6": qty_roll6, "Qty_roll12": qty_roll12,
}])[FITUR_LGBM]
prediksi = max(0, round(model.predict(feat_row)[0]))
df_hist = df_prod.tail(12).copy()
hist_labels = pd.to_datetime(df_hist["Date"]).dt.strftime("%b %Y").tolist()
hist_qty = df_hist["Quantity"].tolist()
return {
"product_id" : product_id,
"product_name": name,
"category" : category,
"types" : types,
"last_data" : last_date.strftime("%b %Y"),
"target_month": target_date.strftime("%b %Y"),
"prediksi" : int(prediksi),
"fitur_input" : {
"qty_lag1" : qty_lag1, "qty_lag2": qty_lag2,
"qty_lag3" : qty_lag3, "qty_lag12": qty_lag12,
"qty_roll3": round(qty_roll3, 2), "is_holiday": is_holiday,
},
"hist_labels" : hist_labels,
"hist_qty" : hist_qty,
}
def forecast_all_products(
df_cont : pd.DataFrame,
model,
le_cat,
le_types,
country_code: str = "ID",
top_n : int = None,
) -> list:
if top_n:
products = (
df_cont.groupby("Product_ID")["Quantity"]
.sum().sort_values(ascending=False)
.head(top_n).index.tolist()
)
else:
products = df_cont["Product_ID"].unique().tolist()
results = []
for pid in products:
r = forecast_one_product(df_cont, pid, model, le_cat, le_types, country_code)
if "error" not in r:
results.append(r)
return results