🍀FT Loader(ftldr.py)如何同時支援 BIN / SBIN 欄位、只寫入 >0 的 Bin,並確認是否載入成功(新手也看得懂)

前言:為什麼你會遇到 BIN / SBIN 的坑?

在製造業測試資料(FT/CP/ATE)世界裡,「Bin」常用來分類測試結果:

  • BIN1 可能代表 Pass

  • BIN2~BINn 代表不同失敗原因或分類

但實務上,各家供應商或不同版本報表,欄位命名不一定一致:

  • 有的報表用 BIN2

  • 有的報表用 SBIN2

  • 還可能出現大小寫混用 item_No / ITEM_NO、或 sbin10

如果 Loader 沒有做「欄位正規化(Normalization)」,就會出現:

  • 程式找不到 BIN1,直接報錯

  • 或把 SBIN2 當成奇怪字串,轉數字時爆炸

  • 或 0 的 Bin(沒有任何數量)也被寫進資料庫,造成報表顯示一堆不必要的欄位

這篇文章用「完全新手能懂」的方式,解釋:

  1. Loader 什麼時候會產生 SBIN/BIN

  2. 如何同時支援 BINxSBINx

  3. 如何做到「只記錄數值 > 0 的 Bin」

  4. 如何判斷檔案到底跑成功了沒(不用猜)


一、你要解決的需求其實是兩件事

需求 A:BIN / SBIN 欄位都能讀

意思是:Excel 欄位可能叫 BIN4SBIN4,你希望都能被 Loader 視為同一個「第 4 號 bin」。

需求 B:只有 Bin 數量 > 0 才寫進資料庫

意思是:如果 BIN2 = 0,就不要 insert 到 FT_SITE_SUMMARYBIN_DEF,避免後續報表出現一堆「永遠為 0」的 bin。


二、SBIN / BIN 的數值到底「在哪裡被記錄」?

以常見 Loader 架構來說(不管你是 STDF、CSV/SPD、或 Excel Summary),大方向都是:

  1. 解析檔案(把欄位/記錄讀進來)

  2. 組成一份 Bin 清單(每個 bin 的編號、名稱、數量、是否 Pass)

  3. 寫入資料庫(常見表:站點統計、bin 定義、die 資料…)

其中「SBIN/BIN 的值」通常會在第 2 步形成,然後在第 3 步寫入:

  • FT_SITE_SUMMARY:通常只需要 BINCOUNT(依 site 彙總)

  • BIN_DEF:通常會存 SBINHBINBIN_NAMEGOOD(Pass/Fail)

如果你的 Loader 是「Excel Summary 模式」,更常見的做法是:

  • 用 Excel 欄位名稱推導 bin 編號(從 BIN4 推到 4、從 SBIN4 推到 4)


三、做欄位正規化:讓 BINx / SBINx 都能變成數字

核心思路:不要只 replace BIN,要同時處理 SBIN

下面是一個「完全通用」的寫法:

  • 先把欄位名變大寫

  • 再從欄位名中抓出數字(比只 replace 字串更耐用)

✅ 範例:把欄位名轉成 bin 編號(支援 BIN、SBIN、BIN_01 等)

import pandas as pd def parse_bin_no(col_name: str) -> int | None: s = str(col_name).upper() # 抓出第一段連續數字,例如 "SBIN10" -> "10", "BIN_03" -> "03" m = pd.Series([s]).str.extract(r'(\d+)')[0].iloc[0] return int(m) if pd.notna(m) else None

你可以把這個邏輯套在一組欄位上,形成 bin 編號欄:

def build_bin_table(row: pd.Series, bin_columns: list[str]) -> pd.DataFrame: tmp = pd.DataFrame({ "COL": bin_columns, "COUNT": [row.get(c) for c in bin_columns] }) # 轉數值(非數值就變 NaN),避免 " " 或 "N/A" 造成錯誤 tmp["COUNT"] = pd.to_numeric(tmp["COUNT"], errors="coerce") # 只保留 COUNT > 0 的 bin(這就是需求 B) tmp = tmp[tmp["COUNT"] > 0].copy() # 從欄位名推導 bin 編號(需求 A) tmp["BIN_NO"] = tmp["COL"].apply(parse_bin_no) # 你也可以決定:BIN_NO 無法解析就丟掉 tmp = tmp.dropna(subset=["BIN_NO"]) tmp["BIN_NO"] = tmp["BIN_NO"].astype(int) # 最終統一欄位命名:SBIN/HBIN/BIN 都用同一個號碼 tmp["SBIN"] = tmp["BIN_NO"] tmp["HBIN"] = tmp["BIN_NO"] tmp["BIN"] = tmp["BIN_NO"] return tmp.reset_index(drop=True)

這樣你就能同時支援:

  • BIN1BIN2

  • SBIN2sbin10

  • BIN_03S_BIN04(只要欄位裡有數字就行)


四、只記錄「數量 > 0」的 Bin:放對位置很重要

很多人會犯一個錯:

先算 SBIN,再過濾 COUNT

建議順序應該是:

  1. 先把數量轉成數值

  2. 先過濾 COUNT > 0

  3. 再去推導 BIN_NO / SBIN(避免無效欄位引發後續錯誤或垃圾資料)

也就是像剛剛範例那樣先做:

tmp["COUNT"] = pd.to_numeric(tmp["COUNT"], errors="coerce") tmp = tmp[tmp["COUNT"] > 0].copy()

這一步做完,後面不管要寫 FT_SITE_SUMMARYBIN_DEF,都只會包含「真的有數量」的 bin。


五、如果同時存在 BIN2 和 SBIN2,要不要合併?

有些報表可能同時出現 BIN2SBIN2 兩個欄位,代表同一個概念。
這時最保險做法是:用 BIN_NO groupby 合併 COUNT

bins = build_bin_table(row, bin_columns) bins = ( bins.groupby("BIN_NO", as_index=False) .agg({ "COUNT": "sum", "SBIN": "first", "HBIN": "first", "BIN": "first" }) )

如果你能確定永遠只會有一套(只有 BIN 或只有 SBIN),那不一定要做合併。
但如果你遇過多版本報表混在一起,做合併可以省掉很多後續報表混亂。


六、如何判斷 ftldr.py 是否「跑成功」?(不用猜)

以下提供 3 層驗證,照「最直覺」到「最可靠」排序。

方法 1:看檔案是否被移到 success / error / duplicate

多數 Loader 會在處理結束後把檔案移動到不同資料夾:

  • success/:成功載入

  • error/:失敗(通常會留 log 或錯誤訊息)

  • duplicate/:判定重複載入

你看到檔案出現在 success/,通常就很接近「成功」。


方法 2:看 log 是否有「結尾成功訊號」或 Exception 堆疊

建議你在批次執行時把 stdout/stderr 都導到 log:
(以下是概念示範)

python loader_main.py --mode summary --vendor XAHT > loader_run.log 2>&1

判斷方式:

  • ✅ 成功:log 最後會看到「完成/寫入成功」等訊息

  • ❌ 失敗:會看到 TracebackValueErrorKeyError、資料庫連線錯誤等


方法 3:直接查資料庫是否真的寫入(最硬核、最準)

最終要以 DB 為準。你可以查「這個 LOT 是否存在」或「這次寫入的筆數」。

範例(示意用 SQL Server):

-- 1) 查 LOT 是否進 FT_LOT_INFO SELECT TOP (5) * FROM dbo.FT_LOT_INFO WHERE LOT_ID = '你的LotId' ORDER BY CREATE_TIME DESC; -- 2) 查站點/ bin 統計是否進 FT_SITE_SUMMARY SELECT BIN, SUM([COUNT]) AS TOTAL_CNT FROM dbo.FT_SITE_SUMMARY WHERE OBJECT_ID = 123456 -- 對應你的 TEST OBJECT_ID GROUP BY BIN ORDER BY BIN; -- 3) 查 BIN_DEF 是否建立(若你有建立/更新 bin 定義) SELECT TOP (50) * FROM dbo.BIN_DEF WHERE BIN_DEF_NAME = '你的BinDefId' ORDER BY OBJECT_ID DESC;

如果 FT_LOT_INFOFT_SITE_SUMMARY 都能查到這次 lot 的資料,基本上就能確定 Loader 成功。


七、常見錯誤與快速排查

1) 找不到 BIN1(KeyError / 起點欄位不存在)

原因:報表用 SBIN1 或 bin 欄位不是從 BIN1 開始。
解法:不要硬寫死 BIN1 作起點,改成「找到第一個符合 BIN/SBIN 的欄位」。

2) astype(int) 失敗(ValueError)

原因:欄位名轉換後不是純數字,例如 SBIN2 被處理成 S2
解法:改成「抓數字」的做法(extract r'(\d+)'),或先處理 SBIN 再處理 BIN

3) Bin 全部變成 0 仍寫入、報表顯示一堆 bin

原因:只 dropna 沒有過濾 0。
解法:在寫入前加 COUNT > 0 过滤。


結語:把 Loader 做得「耐髒」才是王道

你真正想要的是:

  • 報表欄位亂命名也不怕

  • 0 的 Bin 不要污染資料庫

  • 跑完能快速知道成功或失敗

做到這三點,Loader 就能更穩、更省時間,也更不容易被不同供應商格式搞到爆炸。

留言

這個網誌中的熱門文章

🔍Vue.js 專案錯誤排查:解決 numericFields is not defined 與合併儲存格邏輯最佳化

🖥️遠端桌面連線完整新手指南:Windows RDP、Chrome Remote Desktop、AnyDesk、TeamViewer 一次搞懂

🔎EF Core 連 Oracle 出現 ORA-00600 [kpp_concatq:2] 的完整排錯指南(含 EF Core ToString/CultureInfo 錯誤)