# -*- coding: utf-8 -*-
"""
Template: WSAP Custom Model (2026-07-26, ระบบรวม)
====================================================
ไฟล์นี้เดียว **ใช้ได้ทั้ง 2 ความสามารถ** — อัปโหลดครั้งเดียว ติ๊กได้ทั้งคู่ในหน้าเว็บ:
  1. "แข่งขัน CV Leaderboard" — เป็น champion ได้จริง แต่ไม่กระทบ What-If/SWOT/Risk Universe
  2. "พยากรณ์อนาคต" — โผล่ในกราฟพยากรณ์ล่วงหน้า Tab③

**ไม่จำเป็นต้องใช้ทั้ง 2 อย่าง** — ถ้าโมเดลของคุณทำได้แค่อย่างเดียว ก็ติ๊กแค่ช่องเดียว ลบฟังก์ชันที่ไม่ใช้
ออกจากไฟล์ หรือปล่อยไว้เฉยๆ ก็ได้ (ไม่ติ๊ก = ไม่ถูกเรียกใช้เลย)

=== ทำไมสองความสามารถนี้รับ input คนละแบบ (สำคัญ อย่าสับสน) ===
**CV Candidate**: รับปัจจัยเศรษฐกิจ (Xs) + y — ใช้ได้เพราะแค่ "แข่งขัน/อธิบายย้อนหลัง" (เรารู้ค่า X จริง
  ของทุกเดือนในอดีตแล้ว) **ห้ามนำไปพยากรณ์อนาคตจริงเด็ดขาด** เพราะต้องรู้ค่า X ของเดือนหน้าก่อน ซึ่งไม่มี
  ทางรู้ได้จริง (จะต้องแต่งข้อมูลขึ้นมา ขัดกับกฎแม่บทของทั้งระบบ)
**Forecast**: รับแค่ y ในอดีต (ที่รู้แน่นอน) — พยากรณ์อนาคตได้จริงโดยไม่ต้องเดาอะไรเลย

ถ้าจะเขียนโมเดลที่ทำได้ทั้งสองแบบ (เช่น ใช้แค่ y เอง ไม่พึ่งปัจจัยเศรษฐกิจเลย) เขียน helper กลางไว้แล้วให้
ทั้งสองฟังก์ชันเรียกใช้ร่วมกันได้เลย (ดูตัวอย่าง _fit_moving_average ด้านล่าง) — ไฟล์นี้ import สดทุกครั้งที่
เรียกใช้ (ทั้ง CV และ Forecast) ฟังก์ชัน/helper ในไฟล์เดียวกันเรียกกันเองได้ปกติเหมือน Python module ทั่วไป

=== Contract: ฟังก์ชัน CV Candidate ===
รับพารามิเตอร์เดียว (ctx dict) มี key: ctx["Xs"] (DataFrame ปัจจัยเศรษฐกิจ scale แล้ว), ctx["y"] (Series
เป้าหมายจริง), ctx["n_splits"] (int, =5 เสมอ) — คืน {"ok":True,"oof":[...ยาวเท่า len(y), None ช่วง train...]}
หรือ {"ok":False,"error":"..."} — **ต้องใช้ TimeSeriesSplit เอง** ห้าม fit ครั้งเดียวด้วยข้อมูลทั้งหมด

=== Contract: ฟังก์ชัน Forecast ===
รับพารามิเตอร์เดียว (ctx dict) มี key: ctx["y"] (numpy array ค่าจริงทั้งหมด), ctx["n"] (int จำนวนเดือนที่ขอ
พยากรณ์) — คืน {"ok":True,"mean":[...ยาว n...], "lo95":[...]|None, "hi95":[...]|None, "meta":{...}} หรือ
{"ok":False,"error":"..."}

=== หลังอัปโหลดแล้วต้องทำอะไรต่อ ===
Tab③ โมเดล & ความแม่นยำ → 🧩 นำเข้าโมเดล → อัปโหลดไฟล์ + ติ๊กความสามารถที่ต้องการ + กรอกชื่อฟังก์ชัน →
กด "ทดสอบ" (ทดสอบทุกความสามารถที่ติ๊กพร้อมกัน) → ผ่านแล้วกด "ยืนยันใช้งานจริง" แยกตามความสามารถได้
"""
import numpy as np
import pandas as pd


def _fit_moving_average(y_values, window=3):
    """helper กลาง — ทำนายด้วยค่าเฉลี่ยเคลื่อนที่ง่ายๆ (ตัวอย่างเท่านั้น ไม่ได้ตั้งใจให้แม่นเท่า Ridge/SARIMAX
    จริง) ทั้ง 2 ฟังก์ชันด้านล่างเรียก helper ตัวนี้ร่วมกัน — เขียน logic การ fit จริงของคุณตรงนี้ที่เดียว"""
    y_arr = np.asarray(y_values, dtype=float)
    if len(y_arr) < window:
        return float(np.mean(y_arr))
    return float(np.mean(y_arr[-window:]))


def plugin_cv_template(ctx):
    """ตัวอย่างความสามารถ CV Candidate — ใช้ moving average ทำนาย OOF ทีละ fold (ไม่ใช้ Xs เลยในตัวอย่างนี้
    แม้ contract จะส่งมาให้ก็ตาม — เพราะโมเดลตัวอย่างนี้ตั้งใจให้ง่ายที่สุด แก้เป็นโมเดลจริงที่ใช้ Xs ได้เลย)"""
    try:
        from sklearn.model_selection import TimeSeriesSplit
        y, n_splits = ctx["y"], ctx.get("n_splits", 5)
        oof = pd.Series(np.nan, index=y.index)
        for tr, te in TimeSeriesSplit(n_splits=n_splits).split(y):
            pred = _fit_moving_average(y.iloc[tr].to_numpy())  # เรียก helper ตัวเดียวกับ Forecast ด้านล่าง
            oof.iloc[te] = pred
        oof_list = [None if pd.isna(v) else float(v) for v in oof]
        return {"ok": True, "oof": oof_list}
    except Exception as e:
        return {"ok": False, "error": f"{type(e).__name__}: {e}"}


def plugin_forecast_template(ctx):
    """ตัวอย่างความสามารถ Forecast — เรียก helper กลางตัวเดียวกับ CV Candidate ข้างบน แล้วคงค่าไปข้างหน้า n
    เดือน (ตัวอย่างง่ายที่สุดเพื่อโชว์โครง — โมเดลจริงควรมีความซับซ้อนกว่านี้)"""
    try:
        y, n = ctx["y"], ctx["n"]
        pred = _fit_moving_average(y)  # helper ตัวเดียวกับที่ plugin_cv_template ใช้ข้างบน
        mean = [round(pred, 0)] * n
        return {"ok": True, "mean": mean, "lo95": None, "hi95": None,
                "meta": {"method": "moving_average_template", "window": 3}}
    except Exception as e:
        return {"ok": False, "error": f"{type(e).__name__}: {e}"}
