/* ======================================================================
   FUEL GRAPH PROCESSING  —  T-SQL (SQL Server 2016+)
   ประมวลผลข้อมูล log -> เส้นปริมาณน้ำมัน + ตรวจจับการเติม/น้ำมันหาย
   มีภาคผนวกอธิบายที่มาของค่าพารามิเตอร์ทุกตัว + แท็ก [P1]..[P14] กำกับในโค้ด
   ----------------------------------------------------------------------
   MAP: ฟังก์ชัน JS  ->  เทคนิค SQL
     engineOn()      -> dbo.fn_EngineOn (scalar)   : บิตแรกของ IO_STATUS
     invert()        -> @BPIV - v                  : กลับหัวด้วยจุดพลิกจากข้อมูล
     hampel()        -> PERCENTILE_CONT + self-join : median/MAD ในหน้าต่าง 7
     segmentIds()    -> LAG + running SUM           : แบ่งช่วงที่จุดเปลี่ยน
     smoothSeg()     -> self-join AVG (same SegId)  : เฉลี่ยเคลื่อนที่ไม่ข้ามช่วง
     holdFill/corner -> UNION ALL (แถวมุมหัก)        : เส้นตั้งฉากตอน render
     detectEvents()  -> LAG + WHERE |Δ|>EDGE        : เติม(+) / น้ำมันหาย(-)

   ======================================================================
   ★★★  ภาคผนวก: ที่มาของค่าพารามิเตอร์ทุกตัว (PARAMETER PROVENANCE)  ★★★
   ======================================================================
   หลักฐานอ้างอิงจากข้อมูลจริง (log server VID 109338, 615 records):
     • ช่วงค่า BD ตอนใช้งานจริง : 0.83 – 1.69 V  (span = 0.86)
     • กลุ่มค่าตอนอุปกรณ์ปิด     : 0.06 – 0.09 V  (ใกล้ 0, ห่างจากช่วงใช้งานมาก)
     • การใช้น้ำมันปกติ จุดต่อจุดเปลี่ยนสูงสุด : ±0.12  (≈ 14% ของ span)
     • การเติมจริง 2 ครั้ง : +0.24 (28% ของ span) และ +0.74 (86% ของ span)
     • ผลโหมด AUTO ที่ได้ : LOW=0.596, EDGE=0.172, HFLOOR=0.0172, BPIV=2.520

   ---- กลุ่ม ก: ค่าคงที่ทางสถิติ (ไม่ใช่ค่าปรับ เป็นค่าตายตัวตามทฤษฎี) ----
   [P10] 1.4826  = ค่าคงที่แปลง MAD -> ค่าเบี่ยงเบนมาตรฐาน (σ) สำหรับข้อมูลปกติ
                   ( = 1 / Φ⁻¹(0.75) ). ทำให้ threshold ของ Hampel เทียบเท่า "กี่ σ".
                   เป็นค่ามาตรฐานสากล ห้ามเปลี่ยน.
   [P2]  @Sig = 3 = กฎ "3 ซิกมา" : จุดที่ห่างจาก median เกิน 3σ ถือเป็น outlier
                   (ครอบคลุม ~99.7% ของข้อมูลปกติ). เป็นค่ามาตรฐานของ Hampel filter.
                   ลด = ตัด spike ไวขึ้น (เข้มขึ้น), เพิ่ม = ตัดเฉพาะที่โดดจริงๆ.
   [P11] cnt >= 3 = ต้องมีเพื่อนบ้านอย่างน้อย 3 จุด median/MAD ถึงจะมีความหมาย
                   (หน้าต่างที่ปลายข้อมูลอาจมีจุดน้อย -> ข้ามไปไม่ตัด).

   ---- กลุ่ม ข: ค่าจากโครงสร้างข้อมูล (สังเกตจากไฟล์จริง) ----
   [P1]  16 และ '1' = VLCTL_IO_STATUS เก็บเป็น bit-field 16 บิต, บิตแรก = สถานะ
                   สตาร์ท/ดับเครื่อง. ในไฟล์พบแค่ 2 ค่า: '1000000000000000'(ติด)/'0'(ดับ).
   [P5]  999 = ค่า sentinel ของ VLCTL_FUEL (AK) ที่หมายถึง "ค่าเสีย/ไม่พร้อม"
                   (พบในข้อมูล: AK มีค่า 0,67,133,...,999 โดย 999 คือ invalid) -> ตัดทิ้ง.
   [P13] @BPIV = min + max = "จุดพลิกกลับหัว". เซนเซอร์ผกผัน (แรงดันต่ำ=น้ำมันมาก)
                   fuel = BPIV - v ทำให้ค่าที่ต่ำสุด<->สูงสุดสลับกันพอดี. คิดจากข้อมูลเอง
                   จึงไม่ผูกสเกล (0-2V หรือ 0-10V ก็ถูกต้อง).

   ---- กลุ่ม ค: ค่าที่ derive จากช่วงข้อมูล (โหมด AUTO) พร้อมหลักฐาน ----
   [P6]  LOW_FRAC = 0.4  ->  LOW = 0.4 × median(BD ตอนติดเครื่อง)
                   เหตุผล: ค่าตอนอุปกรณ์ปิดกองอยู่ใกล้ 0 (0.06-0.09) ส่วนค่าใช้งานจริง
                   กองสูง (median ~1.0-1.5). ช่องว่างระหว่าง 0.1 กับ 0.8 กว้างมาก.
                   40% ของ median ตกกลางช่องว่างพอดี -> ผลจริง LOW=0.596 แยกได้สะอาด
                   (ตัดของปิด <0.1, เก็บของจริง ≥0.83). ค่าใช้ได้กว้าง 0.2-0.6 ไม่ไว.
   [P7]  EDGE_FRAC = 0.2  ->  EDGE = 0.2 × span   (ตัวสำคัญสุด)
                   เหตุผล (วัดจากข้อมูล): การใช้ปกติเปลี่ยน ≤14% ของ span,
                   การเติมจริงเปลี่ยน 28-86% ของ span. มี "ช่องว่าง" ชัดระหว่าง 14%↔28%.
                   ตั้ง EDGE = 20% ของ span = กึ่งกลางช่องว่าง -> คั่นได้ทั้งสองฝั่งมีระยะเผื่อ.
                   ผลจริง EDGE=0.172 : noise 0.12 อยู่ใต้, refuel 0.24/0.74 อยู่เหนือ.
   [P8]  FLOOR_FRAC = 0.02 (ขั้นต่ำ 0.005)  ->  Hampel floor = max(0.02×span, 0.005)
                   เหตุผล: ตอนข้อมูลนิ่งมาก MAD≈0 -> threshold จะ≈0 แล้วตัด jitter ปกติทิ้งหมด.
                   ตั้ง floor ~2% ของ span กันไม่ให้ตัดแรงเกินไป. 0.005 กัน floor=0 ตอน span จิ๋ว.
   [P9]  EDGE ขั้นต่ำ 0.02  ->  EDGE = max(0.2×span, 0.02)
                   กัน EDGE=0 กรณี span ยุบ (ข้อมูลแทบไม่ขยับ) จะได้ไม่จับ noise เป็นจุดเติม.

   ---- กลุ่ม ง: ค่าหน้าต่าง/เวลา (อิงอัตราการ log ~1 จุด/นาที) ----
   [P4]  @HampelWin = 7  (±3 จุด ≈ 3-7 นาที)
                   สั้นพอที่ระดับน้ำมันจริงยังคงที่ในหน้าต่าง -> ค่าที่เบี่ยงคือ spike ชัดๆ.
                   เลขคี่เพื่อให้ median เป็นจุดกลางเดี่ยว. เล็กไปหน้าต่างไม่นิ่ง, ใหญ่ไปพลาด spike ติดกัน.
   [P3]  @SmoothWin = 15 (±7 จุด ≈ 15 นาที)
                   เฉลี่ยกลบ jitter รายนาที (~±0.05V) แต่ยังคงเทรนด์การใช้ทั้งวัน.
                   ใหญ่ไป = เส้นหน่วง/แบนเกิน, เล็กไป = ยังหยัก. 15 คือจุดสมดุลจากการทดลอง.

   ---- กลุ่ม จ: ค่า default โหมดกำหนดเอง (@Auto=0) ----
   [P12] @Low=0.5, @Edge=0.2, @HFloor=0.03
                   เป็นค่าที่ "พอดี" กับสเกลข้อมูลชุดนี้ (0-1.7V) จากการทดลองปรับจูน
                   ใช้เป็น fallback เมื่อไม่อยากให้คิดอัตโนมัติ. ถ้าเซนเซอร์สเกลอื่น
                   ควรใช้ @Auto=1 (จะ derive ให้เอง) แทนการแก้ค่าพวกนี้.

   ---- อื่นๆ (ไม่ใช่ค่าปรับจูน เป็นรายละเอียดการ render/แปลงหน่วย) ----
   [P14] OrderKey = Seq×10 / Seq×10−1 : เว้นช่องเลขให้ "แถวมุมหัก" แทรกก่อนแถวจริง
                   ที่เวลาเดียวกัน (เกิดเส้นตั้งฉาก). 10 กับ −1 เป็นแค่ระยะเรียงลำดับ ไม่มีผลต่อค่า.
         HHMMSS  : /10000, /100%100, %100 = แกะ ชม./นาที/วินาที จากเลข HHMMSS.
   ====================================================================== */
SET NOCOUNT ON;
SET QUOTED_IDENTIFIER ON;
GO

/* ----------------------------------------------------------------------
   0)  ตารางต้นทาง — ชื่อคอลัมน์ตรงกับไฟล์ xlsx (ชื่อจริง)
   ---------------------------------------------------------------------- */
IF OBJECT_ID('dbo.FuelLog') IS NULL
CREATE TABLE dbo.FuelLog (
    Id                    INT IDENTITY(1,1) PRIMARY KEY,  -- ลำดับเวลา (insert order) ใช้เรียงข้อมูล
    VLCTL_GPS_TIME        INT         NULL,                -- เวลา (HHMMSS เช่น 61744)
    VLCTL_IO_STATUS       VARCHAR(32) NULL,                -- สถานะ ('1000000000000000' / '0')
    VLCTL_FUEL            FLOAT       NULL,                -- น้ำมันเส้นที่ 1 (AK)
    VLCTL_ANALOGUE_PORT1  FLOAT       NULL                 -- น้ำมันเส้นที่ 2 / แรงดัน (BD)
);
GO

/* ----------------------------------------------------------------------
   fn_EngineOn : เครื่องติดเมื่อ IO_STATUS เขียนเป็นเลขฐานสอง 16 หลัก แล้วหลักแรก = 1
   ---------------------------------------------------------------------- */
CREATE OR ALTER FUNCTION dbo.fn_EngineOn (@status SQL_VARIANT)
RETURNS BIT
AS
BEGIN
    DECLARE @s VARCHAR(64) = CONVERT(VARCHAR(64), @status);
    IF @s IS NULL RETURN 0;
    SET @s = LTRIM(RTRIM(@s));
    -- ถ้ามีอักขระที่ไม่ใช่ตัวเลข (จุดทศนิยม/สัญกรณ์ e) ให้แปลงเป็นจำนวนเต็มก่อน
    IF @s LIKE '%[^0-9]%'
        SET @s = CONVERT(VARCHAR(64), TRY_CONVERT(BIGINT, TRY_CONVERT(FLOAT, @s)));
    IF @s IS NULL OR @s = '' RETURN 0;
    SET @s = RIGHT(REPLICATE('0', 16) + @s, 16);   -- [P1] pad ซ้ายให้ครบ 16 บิต (bit-field)
    RETURN CASE WHEN LEFT(@s, 1) = '1' THEN 1 ELSE 0 END;   -- [P1] บิตแรก=1 -> ติดเครื่อง
END
GO

/* ======================================================================
   STORED PROCEDURE หลัก  (ดูที่มาของค่า default ที่ [Pxx] ในภาคผนวกด้านบน)
   ====================================================================== */
CREATE OR ALTER PROCEDURE dbo.usp_ProcessFuelGraph
    @Auto       BIT   = 1,      -- 1 = คิดเกณฑ์อัตโนมัติจากช่วงข้อมูล (แนะนำ)
    @Low        FLOAT = 0.5,    -- [P12] default โหมดกำหนดเอง (ใช้เมื่อ @Auto = 0)
    @Edge       FLOAT = 0.2,    -- [P12] default โหมดกำหนดเอง (ใช้เมื่อ @Auto = 0)
    @Sig        FLOAT = 3,      -- [P2]  กฎ 3 ซิกมา (ความเข้ม Hampel)
    @SmoothWin  INT   = 15,     -- [P3]  ~15 นาที (ระดับความสมูท)
    @HampelWin  INT   = 7,      -- [P4]  ~7 นาที (หน้าต่างจับ spike)
    @AkInvalid  FLOAT = 999     -- [P5]  ค่า sentinel ของ AK = เสีย
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @sHalf INT = @SmoothWin / 2;      -- ครึ่งหน้าต่างสมูท (=7)
    DECLARE @hHalf INT = @HampelWin / 2;      -- ครึ่งหน้าต่าง Hampel (=3)
    DECLARE @HFloor FLOAT, @BPIV FLOAT, @opMed FLOAT, @span FLOAT, @mn FLOAT, @mx FLOAT;

    /* --- 1) อ่านข้อมูล + แปลงเวลา + ธงเครื่องยนต์ (คิดแค่ครั้งเดียว) ---
       dev แค่เปลี่ยน dbo.FuelLog เป็นชื่อตารางจริง
       (สมมติ VLCTL_GPS_TIME เป็น int HHMMSS ; ถ้าเป็น time/datetime ใช้ DATEPART แทน CROSS APPLY) */
    IF OBJECT_ID('tempdb..#src') IS NOT NULL DROP TABLE #src;
    SELECT
        Seq     = ROW_NUMBER() OVER (ORDER BY s.Id),
        TimeSec = p.h * 3600 + p.m * 60 + p.ss,
        TimeStr = RIGHT('0' + CONVERT(VARCHAR(2), p.h),  2) + ':' +
                  RIGHT('0' + CONVERT(VARCHAR(2), p.m),  2) + ':' +
                  RIGHT('0' + CONVERT(VARCHAR(2), p.ss), 2),
        eng     = dbo.fn_EngineOn(s.VLCTL_IO_STATUS),
        ak      = s.VLCTL_FUEL,
        bd      = s.VLCTL_ANALOGUE_PORT1
    INTO #src
    FROM dbo.FuelLog s
    CROSS APPLY (SELECT h  = s.VLCTL_GPS_TIME / 10000,           -- HHMMSS -> ชม.
                        m  = (s.VLCTL_GPS_TIME / 100) % 100,      -- -> นาที
                        ss = s.VLCTL_GPS_TIME % 100) p            -- -> วินาที
    WHERE s.VLCTL_GPS_TIME IS NOT NULL;

    /* --- 2) เกณฑ์แบบ AUTO: คิดจากช่วงข้อมูล BD ตอนติดเครื่อง (รองรับทุกสเกล) --- */
    SELECT TOP 1 @opMed = PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY bd) OVER ()
    FROM #src WHERE eng = 1 AND bd IS NOT NULL;

    IF @Auto = 1 SET @Low = 0.4 * @opMed;    -- [P6] LOW = 40% ของค่ากลาง (แยกช่วงอุปกรณ์ปิด)

    SELECT @mn = MIN(bd), @mx = MAX(bd)
    FROM #src WHERE eng = 1 AND bd IS NOT NULL AND bd >= @Low;
    SET @span = NULLIF(@mx - @mn, 0);  IF @span IS NULL SET @span = 1;

    IF @Auto = 1
    BEGIN
        -- [P7] EDGE = 20% ของช่วงกว้าง (กึ่งกลางระหว่าง noise 14% กับ refuel 28%)  [P9] ขั้นต่ำ 0.02
        SET @Edge   = CASE WHEN 0.2  * @span > 0.02  THEN 0.2  * @span ELSE 0.02  END;
        -- [P8] floor Hampel = 2% ของช่วง (กันตัดแรงตอนข้อมูลนิ่ง)  ขั้นต่ำ 0.005
        SET @HFloor = CASE WHEN 0.02 * @span > 0.005 THEN 0.02 * @span ELSE 0.005 END;
    END
    ELSE SET @HFloor = 0.03;                  -- [P12] floor default โหมดกำหนดเอง

    SET @BPIV = @mn + @mx;                     -- [P13] จุดพลิกกลับหัว (min + max) คิดจากข้อมูล

    /* --- 3) base: เฉพาะติดเครื่อง + ตัดค่าต่ำผิดปกติ (อุปกรณ์ปิด) --- */
    IF OBJECT_ID('tempdb..#base') IS NOT NULL DROP TABLE #base;
    SELECT Seq, TimeSec, TimeStr, eng, ak, bd,
           base = CASE WHEN eng = 1 AND bd IS NOT NULL AND bd >= @Low THEN bd END
    INTO #base
    FROM #src;

    /* --- 4) Hampel filter: ตัด spike ที่ห่างจาก median เพื่อนบ้าน > max(Sig*1.4826*MAD, floor)
             median/MAD ในหน้าต่าง ±@hHalf ทำด้วย PERCENTILE_CONT OVER(PARTITION BY เป้าหมาย) --- */
    IF OBJECT_ID('tempdb..#ham') IS NOT NULL DROP TABLE #ham;
    ;WITH BV AS (            -- เฉพาะจุดที่ใช้งานได้
        SELECT Seq, base FROM #base WHERE base IS NOT NULL
    ),
    WIN AS (                 -- [P4] จับคู่แต่ละจุดกับเพื่อนบ้าน ±hHalf ตามดัชนี Seq
        SELECT t.Seq AS tSeq, t.base AS tBase, w.base AS wBase
        FROM BV t
        JOIN BV w ON w.Seq BETWEEN t.Seq - @hHalf AND t.Seq + @hHalf
    ),
    MED AS (                 -- median ของหน้าต่าง (เท่ากันทุกแถวใน partition)
        SELECT tSeq, tBase, wBase,
               med = PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY wBase) OVER (PARTITION BY tSeq),
               cnt = COUNT(*) OVER (PARTITION BY tSeq)
        FROM WIN
    ),
    MAD AS (                 -- MAD = median ของ |x - med| ในหน้าต่าง
        SELECT DISTINCT tSeq, tBase, med, cnt,
               mad = PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY ABS(wBase - med)) OVER (PARTITION BY tSeq)
        FROM MED
    )
    SELECT b.Seq, b.TimeSec, b.TimeStr, b.eng, b.ak, b.bd, b.base,
           -- [P11] ต้องมีอย่างน้อย 3 จุด  [P2] เกิน @Sig σ  [P10] 1.4826 = MAD->σ  [P8] floor
           removed = CASE WHEN m.cnt >= 3
                           AND ABS(m.tBase - m.med) >
                               CASE WHEN @Sig * 1.4826 * m.mad > @HFloor
                                    THEN @Sig * 1.4826 * m.mad ELSE @HFloor END
                          THEN 1 ELSE 0 END,
           [out]   = CASE WHEN m.cnt >= 3
                           AND ABS(m.tBase - m.med) >
                               CASE WHEN @Sig * 1.4826 * m.mad > @HFloor
                                    THEN @Sig * 1.4826 * m.mad ELSE @HFloor END
                          THEN NULL ELSE b.base END
    INTO #ham
    FROM #base b
    LEFT JOIN MAD m ON m.tSeq = b.Seq;

    /* --- 5a) segmentIds: แบ่งช่วงที่ "จุดเปลี่ยน" (|Δ| > EDGE) ด้วย LAG + running SUM --- */
    IF OBJECT_ID('tempdb..#seg') IS NOT NULL DROP TABLE #seg;
    ;WITH V AS (SELECT Seq, [out] FROM #ham WHERE [out] IS NOT NULL),
    D AS (SELECT Seq, [out], d = [out] - LAG([out]) OVER (ORDER BY Seq) FROM V)
    SELECT Seq, [out],
           -- [P7] |Δ| > EDGE = จุดเปลี่ยน (ขึ้น segment ใหม่)
           SegId = SUM(CASE WHEN d IS NOT NULL AND ABS(d) > @Edge THEN 1 ELSE 0 END)
                       OVER (ORDER BY Seq ROWS UNBOUNDED PRECEDING)
    INTO #seg
    FROM D;

    /* --- 5b) smoothSeg: เฉลี่ยเคลื่อนที่ ±sHalf เฉพาะจุดใน SegId เดียวกัน (ไม่ข้ามจุดเติม) --- */
    IF OBJECT_ID('tempdb..#sm') IS NOT NULL DROP TABLE #sm;
    SELECT t.Seq, sm = AVG(w.[out])
    INTO #sm
    FROM #seg t
    JOIN #seg w ON w.Seq BETWEEN t.Seq - @sHalf AND t.Seq + @sHalf   -- [P3] หน้าต่าง ±7
               AND w.SegId = t.SegId
    GROUP BY t.Seq;

    /* --- 6) เส้นสำหรับกราฟ (กลับหัวเป็นปริมาณน้ำมัน) : AK, BD ดิบ, BD สมูท --- */
    IF OBJECT_ID('tempdb..#line') IS NOT NULL DROP TABLE #line;
    SELECT h.Seq, h.TimeSec, h.TimeStr,
           -- [P5] ตัด AK=999 (เสีย) และเฉพาะติดเครื่อง
           Ak         = CASE WHEN h.eng = 1 AND h.ak IS NOT NULL AND h.ak <> @AkInvalid THEN h.ak END,
           FuelRaw    = CASE WHEN h.base IS NOT NULL THEN @BPIV - h.base END,   -- [P13] กลับหัว
           FuelSmooth = CASE WHEN m.sm  IS NOT NULL THEN @BPIV - m.sm  END
    INTO #line
    FROM #ham h
    LEFT JOIN #sm m ON m.Seq = h.Seq;

    /* --- 7) detectEvents: ขึ้นฮวบ = เติมน้ำมัน / ลงฮวบ = น้ำมันหายผิดปกติ (สวนทางกัน, EDGE เดียวกัน) --- */
    IF OBJECT_ID('tempdb..#events') IS NOT NULL DROP TABLE #events;
    ;WITH F AS (SELECT Seq, TimeStr, FuelSmooth FROM #line WHERE FuelSmooth IS NOT NULL),
    E AS (
        SELECT Seq, TimeStr, FuelSmooth,
               prevF = LAG(FuelSmooth) OVER (ORDER BY Seq),
               d     = FuelSmooth - LAG(FuelSmooth) OVER (ORDER BY Seq)
        FROM F
    )
    SELECT Seq, TimeStr,
           EventType  = CASE WHEN d > 0 THEN N'⛽ เติมน้ำมัน' ELSE N'⚠️ น้ำมันหายผิดปกติ' END,
           FuelBefore = ROUND(prevF, 3),
           FuelAfter  = ROUND(FuelSmooth, 3),
           Delta      = ROUND(d, 3)
    INTO #events
    FROM E
    WHERE d IS NOT NULL AND ABS(d) > @Edge;   -- [P7] เกิน EDGE = เหตุการณ์ (ไม่ใช่การใช้ปกติ)

    /* ============ ผลลัพธ์ ============ */

    -- (A) สรุปพารามิเตอร์ที่ใช้จริง
    SELECT AutoMode = @Auto, BPIV = @BPIV, LOW = @Low, EDGE = @Edge, HampelFloor = @HFloor,
           SpanBD = @span,
           Records = (SELECT COUNT(*) FROM #src),
           OffCut  = (SELECT COUNT(*) FROM #base WHERE base IS NULL AND bd IS NOT NULL),
           Spikes  = (SELECT COUNT(*) FROM #ham WHERE removed = 1),
           Refuel  = (SELECT COUNT(*) FROM #events WHERE Delta > 0),
           Loss    = (SELECT COUNT(*) FROM #events WHERE Delta < 0);

    -- (B) เส้นกราฟ พร้อม "แถวมุมหัก" (IsCorner=1) ทำให้จุดเปลี่ยนขึ้น/ลงตั้งฉาก
    SELECT OrderKey = Seq * 10, Seq, TimeSec, TimeStr, Ak, FuelRaw, FuelSmooth, IsCorner = 0   -- [P14]
    FROM #line
    UNION ALL
    SELECT OrderKey = e.Seq * 10 - 1, e.Seq, l.TimeSec, e.TimeStr,   -- [P14] มุมหักแทรกก่อนแถวจริง
           Ak = NULL, FuelRaw = NULL, FuelSmooth = e.FuelBefore, IsCorner = 1
    FROM #events e
    JOIN #line l ON l.Seq = e.Seq
    ORDER BY OrderKey;

    -- (C) รายการเหตุการณ์ เติม / น้ำมันหายผิดปกติ
    SELECT TimeStr, EventType, FuelBefore, FuelAfter, Delta
    FROM #events
    ORDER BY Seq;
END
GO

/* ======================================================================
   ตัวอย่างการเรียกใช้
   ====================================================================== */
-- EXEC dbo.usp_ProcessFuelGraph;                 -- โหมด AUTO (แนะนำ)
-- EXEC dbo.usp_ProcessFuelGraph @Auto = 0, @Low = 0.5, @Edge = 0.2;   -- กำหนดเกณฑ์เอง
GO
