1. 問題不在算 Cpk,在算之前那十分鐘
Cpk 的公式不難,難的是把量測儀器吐出來的檔案變成能算的資料。我們品管室的自動影像量測系統按一下就產生一個 CSV,一次可能是幾百筆量測值;客戶要的則是一份製程能力報表。中間這一段長年是這樣做的:開 Excel、複製貼上、拉公式。
這件事的成本不只是時間。它是一段沒有紀錄、沒有版本、每次都靠人重做一遍的流程——同一批資料換個人整理,結果可能不一樣。
真正讓我們決定動手的,是發現有人把量測軟體輸出的 ---(代表這個尺寸沒量到)當成 0 貼進試算表算了平均值。那份報表上的 Cpk 是錯的,而且錯得很有說服力——數字看起來完全正常,沒有任何地方會跳出來告訴你它是錯的。
2. 技術選型:為什麼是零依賴的 Python
我們的 ERP 主體是 .NET,但這支工具刻意選了 Python,而且不使用 pandas、numpy 或任何第三方套件,只用標準函式庫。
理由很現實:這支工具要能在品管室那台電腦上跑。工廠現場的電腦通常不能隨便裝東西、不一定連得上外網、也不會有人願意為了一份報表去處理套件相依問題。「複製資料夾過去就能跑」是這個場景唯一可靠的安裝方式。
架構刻意切成四塊,因為會變的東西和不會變的東西不一樣:reader 處理檔案格式(每換一台機器就可能要調)、spec 處理規格定義(每個料號不同)、stats 是純統計(永遠不變)、report 負責輸出。統計那一層不碰檔案、不碰 I/O,所以可以被完整測試。
cpk_report/
reader.py # 編碼偵測、資料列判斷、數值解析
spec.py # 規格檔(哪一欄是什麼尺寸、公差多少)
stats.py # Cp / Cpk / Pp / Ppk / Ca — 純函式,可完整測試
report.py # 文字 + 單一自足 HTML3. 踩坑其一:檔案不是 UTF-8,而且錯了不會報錯
第一版直接用 UTF-8 開檔,當場 UnicodeDecodeError。台灣現場的量測軟體多半輸出 Big5 / cp950。
但真正危險的不是這個 error——是那些不會噴 error 的情況。Big5 被誤判成別的編碼時,程式不會停,只會安靜地把中文欄位變成亂碼繼續跑下去。所以最後的做法是依序嘗試候選編碼,並且把實際用到的編碼印在報表上:
DEFAULT_ENCODINGS = ("utf-8-sig", "big5", "cp950", "utf-8", "latin-1")
latin-1 放在最後是保底——它對任何位元組都不會失敗。這代表工具永遠不會因為遇到沒見過的編碼就整個停擺,寧可讓人在報表上看到怪字元、去指定 --encoding,也不要在半夜的排程裡靜默失敗。
4. 踩坑其二:用固定列號切資料,一定會切歪
匯出檔的前幾行是機台標頭,很自然會想「跳過前 4 行」。實際跑下去才發現現場檔案沒那麼乖:標頭行數會因設定而變、資料中間會夾空行、檔尾還有註記行。任何固定列號的寫法都會在某一天切錯。
所以判斷改成「這一列長得像不像資料列」——欄數夠、而且時間戳記欄真的像時間戳記:
def looks_like_data_row(parts, min_columns, timestamp_column, timestamp_prefix):
if len(parts) < min_columns:
return False
cell = parts[timestamp_column].strip()
return len(cell) > 8 and cell.startswith(timestamp_prefix)
這裡刻意不檢查數值欄位能不能解析——因為「判定為 NG、數值是 ---」也是一列完全合法的資料,把它濾掉就會低估不良率。
5. 踩坑其三:「沒量到」永遠不是 0(這才是重點)
這是整支工具存在的理由,也是 Excel 做不到的一件事。
量測軟體用 --- 表示這個尺寸沒有量到。貼進 Excel,它就是 0。而 0 會同時做兩件事:把平均值拉偏、把標準差撐大。算出來的 Cpk 於是變成一個看似合理的錯誤結論。
工具的做法是把這些值排除出統計,而且在報表上單獨列出「排除」筆數:
NOT_MEASURED = {"", "-", "--", "---", "----", "n/a", "na", "null", "none"}
def parse_number(cell):
text = cell.strip()
if text.lower() in NOT_MEASURED:
return None # 關鍵:回 None,不是 0.0
...
報表上那個「排除」欄位存在的唯一理由,是讓看報表的人知道這批資料到底有幾筆是真的量到的。60 筆裡有 2 筆沒量到,跟 60 筆全部量到,是兩件事——但如果報表不講,沒有人會發現。
6. 一個延伸決定:零變異回報「無法計算」,不回報「完美」
如果 60 筆量測值全部一模一樣,標準差是 0,Cpk 在數學上是無限大。
但在現場,這幾乎永遠代表量具的解析度不夠,不代表製程完美。所以工具在這種情況印 — 而不是一個漂亮的數字。這是同一個原則的延伸:report 的責任是反映真實狀況,不是產生好看的結果。
同樣的原則也用在輸出上——Cpk 和 Ppk 一律並列。兩者接近代表製程穩定;Cpk 明顯大於 Ppk,代表機台本身能力沒問題,但存在批間漂移(刀具磨耗、換料、換班),該查的是製程管制而不是機台精度。只印一個數字,看不出這件事。
7. 還有一個決定:只讀,不寫
工具不會修改、搬動或刪除來源 CSV,連寫入的程式碼路徑都不存在。
這聽起來像廢話,但值得說明白:我們自己的 ERP 裡就有一段「把已處理的列從 CSV 砍掉、用剩餘列當處理進度」的設計。在那個情境下它是刻意的取捨——人工匯入與自動匯入需要共用同一個進度來源。但代價是,一旦砍行失敗,原始資料就回不來了。
一支分析工具沒有理由承擔這種風險。這條規則有測試守著:讀完之後,來源檔的位元組必須與讀取前完全相同。
8. 成果
| 項目 | 原本(Excel 手工) | 現在(工具) |
|---|---|---|
| 單份報表耗時 | 10–15 分鐘 | 數秒 |
| 「沒量到」的處理 | 當成 0 帶入計算 | 排除,並列出排除筆數 |
| 使用的編碼 | 不明(亂碼才發現) | 印在報表上 |
| Cpk / Ppk | 通常只算一個 | 並列,可看出批間漂移 |
| 單邊公差 | 公式要另外改 | 直接支援,Cp 標示為無定義 |
| 結果一致性 | 依整理的人而異 | 相同輸入必得相同輸出 |
| 可否進排程 | 不行 | 可,離開碼區分品質與程式問題 |
離開碼刻意分成三種:0 正常、1 有項目未達門檻或超規、2 執行錯誤。「品質不達標」和「程式出錯」是兩件事,分開才知道該找製程的人還是找寫程式的人。
$ cpk-report data.csv --spec spec.json
項目 n 排除 平均 σ組內 Cp Cpk Ppk 超規 判定
------------------------------------------------------------------------
全長 60 0 42.4993 0.0117 1.42 1.40 1.38 0 合格
刃厚 60 0 0.6004 0.0043 1.55 1.52 1.48 0 合格
平面度 58 2 0.0072 0.0046 — 0.94 1.06 0 不足9. 為什麼開源
這支工具沒有任何商業機密——它不知道我們做什麼零件、不知道客戶是誰,規格與公差全部由使用者自己的設定檔提供。它解決的是所有做精密加工的工廠都會遇到的同一件事。
對我們自己而言,開源的意義是把內部工具攤在陽光下:程式碼、測試、以及上面那些踩坑的理由都寫清楚了,任何人(包括來稽核我們的客戶)都可以自己判斷這套邏輯站不站得住腳。這比在簡報上寫「我們有數位化品管」有意義得多。
如果你也在處理量測資料,歡迎直接拿去改。有踩到不一樣的坑,開 issue 聊聊。
原始碼:github.com/jet113102/cpk-report(MIT 授權,Python 3.8+,無相依套件)