2026-W34 週報:離開電力樹的那一週¶
第四份週報,本週 3 張卡——比上週少兩張,而且兩天的缺口原因不同(見下面「節奏」)。
但這三張卡的份量不在數量。W31–W33 的十三張卡有一個共同前提,只是從來沒被說出口:每張卡都是電力樹上的一個節點,六格裡的「拓撲位置」永遠答得出「上游接誰、下游接誰」。 本週三張卡全部破了這個前提:
| 卡 | 它住在哪 | 「上游接誰」怎麼答 |
|---|---|---|
dc-09b 電池測試制度 |
時間軸 | 答不出來。它的上游是 commissioning,下游是汰換決策 |
dc-09c 鋰電消防合規 |
第二棵樹(空間/防火區劃) | 答不出來。它問的是「這個房間裡堆了多少可燃能量」 |
dc-10 STS |
電力樹上,但關鍵事實在邊上 | 有兩條上游邊,恰好一條導通——upstream_id: str 直接破功 |
一週之內,同一個資料模型被三種不同的方式證明「節點字典 + upstream_id」不夠用:一次是時間、一次是第二棵樹、一次是邊。 這不是巧合,是佇列走到電力鏈末端的必然——愈往下游,設備愈少,而規則愈多。
節奏(誠實紀錄):五個工作日只產出 3 張卡,8/19(三)與 8/21(五)沒有卡。兩天的原因不一樣,第二天的原因是本站自己的基礎設施:
- 8/19:無 commit、無 lock 殘留、無檔案異動,原因不明。當作單純的一天空窗。
- 8/21 起:
dc-10的檔案在 8/20 寫進去了,但 commit 沒有完成——.git/index.lock停在Aug 20 08:58,一直留到今天。這正是CLAUDE.md警告過的沙箱坑(不能刪檔 → git unlink 不掉自己的 lock),而它擋住了 8/21 之後的每一次排程與本機 git。本次週報開工時已清除 lock,並補上feat(dc): card dc-10 static-transfer-switch這筆遺失的 commit。建議:
CLAUDE.md說「每支排程 skill 開工前與收工後都要rm -f .git/*.lock」——dc-daily-card顯然只做了「開工前」或根本沒做。收工後那次才是關鍵,因為 lock 是 commit 動作自己留下的。另外可以加一道更便宜的保險:每次開工先檢查git status有沒有上一次遺留的 staged 變更,有就先補 commit 再開始今天的卡——本週若有這道檢查,8/21 不會空掉。
本週卡片¶
| 卡 | 標題 | 這張卡最關鍵的一個事實 |
|---|---|---|
dc-09b |
電池測試制度(IEEE 1188) | 內阻沒有絕對門檻,只有相對基準線的偏差——而那條 baseline 必須在投入服務約 6 個月後量,錯過就永遠補不回來。同一筆讀值換個分母(baseline 3.20 → 串平均 3.80),35.9% 變 14.5%,判定完全翻轉 |
dc-09c |
鋰電消防合規(NFPA 855 / UL 9540A) | 消防合規用銘牌最大儲能,不是你模型裡的可用容量——同一組電池是 140 kWh 不是 112 kWh。而 25% LFL = 10,000 ppm,比 off-gas 偵測的工作範圍高 3–4 個數量級:它不是早期預警,是防爆的最後緩衝 |
dc-10 |
靜態切換開關 STS | active_source 是邊的狀態不是節點的欄位,而「有兩個源」是宣稱不是事實——至少四件事會讓冗餘歸零卻不改變儀表板上任何數值。SCR 故障是全軌跡第一個 latched 告警:ack 掉之後設備仍然只剩一個源 |
串起來¶
一、三張卡,三種「不住在電力樹上」的事實¶
W33 的結論是「有些答案不是一個數,是一個數加上它的來歷」。本週更狠一層:有些事實根本不在這棵樹上。
┌─────────────── 時間軸(dc-09b)───────────────┐
│ commissioning baseline → 季測 → 年測 → 汰換 │
│ 沒有協定、沒有感測器、只有一台儀器和一個工程師 │
└───────────────────┬───────────────────────────┘
│ 描述同一批電池
電力樹: … → LV dc-07 → UPS dc-08 → STS dc-10 → (dc-11 PDU)
│ ↑
電池 dc-09 邊上的狀態
│ active_source
│ 同一批電池
┌───────────────────┴───────────────────────────┐
│ 空間樹(dc-09c) │
│ Site → Building → Floor → FireCompartment → … │
│ 電力樹上並排的 A、B 兩路,在這裡可能是同一個節點 │
└───────────────────────────────────────────────┘
dc-09 那組電池同時是三棵結構的成員,而三棵結構的邊完全不同:
| 結構 | 邊的意義 | 往上走是為了 |
|---|---|---|
| 電力樹 | 誰餵誰 | 找源頭、算容量 |
| 時間軸 | 誰是誰的基準線 | 判斷「它現在還行不行」 |
| 空間樹 | 誰在誰的區劃裡 | 聚合能量(合規) |
這是「DCIM 不能只做設備清單」的第二個具體證據(第一個是 W33 那條跨三實體的告警規則)。而且它比第一個更根本:W33 說的是「規則不能掛在設備上」,本週說的是「連『範圍』本身都由另一棵樹決定」——aggregate_energy() 該加總哪幾台,答案不在電力樹上。
二、消防要求回頭吃掉電力鏈的容量與續航——本週最反直覺的一條連鎖¶
dc-09c 演算 4 有一句容易滑過去的話:NFPA 855 2026 版 §4.10 要求排風機這類關鍵安全系統掛 NFPA 110/111 緊急電源。把它接回去看:
鋰電選型(dc-09)
└→ 落入 NFPA 855 管制(dc-09c 演算 1:140 kWh,門檻 20 kWh,7 倍)
└→ 需要連續機械排風 ≥ 1 CFM/ft²(60 m² 電池室 ≈ 1,100 m³/h)
└→ 排風機必須掛發電機盤(2026 版 §4.10)
└→ 回頭增加 dc-05 發電機的必要容量
└→ 回頭縮短 dc-06 的燃油續航(16.40 h 那個數字要重算)
└→ 而 dc-06 的續航門檻是 Uptime Tier 的 12 h
一個電池化學的選型決定,經過消防規則,繞了一圈回來改變柴油續航。 這條邊在單線圖上不存在、在設備清單上不存在,只有在「兩棵樹疊在一起」時才看得見。
風機本身耗電不大(1,100 m³/h 級的排風機約 1 kW 量級,相對 MW 級機房是零頭),所以這條連鎖的價值不在數字大小,在於它證明了兩棵樹之間有真實的依賴邊。而且它是雙向的:消防規則要求排風機掛發電機(空間樹 → 電力樹),發電機的可用性又決定排風機在停電時還轉不轉(電力樹 → 空間樹)。
對模型的意涵:FireCompartment 不能只是聚合能量的容器,它得能宣告「本區劃的安全系統依賴哪些電力節點」。這是兩棵樹之間的第一條橫向邊,dc-10b 建 DeviceRegistry 時要預留它的位置——否則第二輪冷卻鏈(dc-22 CRAH 接不接 UPS)會第二次撞上同一件事。
三、「冗餘暫時為零」本週一次出現三種,全部不可觀測¶
這是 W32「只有故障當下才會被用到的功能」與 W33「保護等級降級是狀態不是屬性」的合流版,本週把它推到最完整:
| 卡 | 冗餘歸零的方式 | 持續多久 | 儀表板顯示 |
|---|---|---|---|
dc-09b |
容量測試:串離線放電+再充電 8–24 h | 10–27 h(放電只佔 1 h) | 窗口關閉後轉綠,實際保護還沒回來 |
dc-09b |
量內阻:並聯串要離線才準 | 季測,數小時 | 完全不顯示 |
dc-09c |
A、B 兩路電池同一防火區劃 | 永久(設計缺陷) | 完全不顯示 |
dc-10 |
transfer_inhibit / SCR latched / 維修旁路 / 兩源上游合流 |
視情況 | 「Source 1 正常、Source 2 正常」仍為真 |
四行裡有三行是「儀表板不會變」。這已經不是告警設計問題,是資料模型問題:只要系統裡存在一個叫 source_count 或 redundancy_level 的欄位,它就會在上面四種情境下全部說謊。
dc-10 給出了正確的形狀:redundancy_effective() 是衍生值,不是欄位,而且它的輸入包含 latched 告警與維護窗口。dc-09b 的 MaintenanceWindow(start, end, recharge_until, effective_redundancy_delta) 是同一件事的時間版本。兩者應該接起來——見下面連貫性檢視 #2。
四、本週母題:判定是「事實 × 判準」的笛卡爾積¶
四週四個母題,這次是最抽象的一個,也是最快就能寫成 code 的一個:
| 週次 | 母題 |
|---|---|
| W31 | 單一欄位裝不下一個事實(數字要帶著量測條件) |
| W32 | 有些事實根本不住在欄位裡(住在序列、存量、多對多裡) |
| W33 | 上限是多候選取 min/max,裸數字丟掉了最有用的資訊 |
| W34 | 判定不是事實的屬性,是事實與判準的笛卡爾積——而判準本身是版本化的資料 |
三張卡各自獨立撞到它,用詞不同但形狀一模一樣:
| 卡 | 事實 | 判準 | 判準為什麼必須是資料 |
|---|---|---|---|
dc-09b |
OhmicReading 4.35 mΩ |
ThresholdPolicy 20% / 30–50% / 50% |
三個門檻服務三個目的:安全裕度/工程指引/商業契約。同一筆讀值同時是「該換了」與「不能索賠」 |
dc-09c |
EssUnit 140 kWh |
CodeRule NFPA855_2023 / 2026 / TW |
2023 版有能量表、2026 版移除改 HMA、台灣指引又是另一套。三者會並存好幾年 |
dc-10 |
scr 狀態 + breaker_open |
latched 語意 | 判定是歷史的函數(曾經壞過、有沒有人來修),不是即時量測的函數 |
前兩張的結論完全一致,而且 dc-09c 自己點名了:「跟 dc-09b 的 ThresholdPolicy 是同一個形狀」。兩張卡看見了同構,卻各寫了一份 code——這是本週連貫性檢視的第一順位。
第三張是這個母題的邊界案例,值得單獨記:dc-10 的 SCR latched 告警把家族補到三個,且三個都不能只看即時遙測算出來:
測試型 :last_verified_at 過期 → 「你不知道它還行不行」 (dc-05c / dc-06 / dc-07b / dc-09b)
可算型 :Isc(config) > Icw → 「這個配置本身違規」 (dc-07b / dc-09c 能量閘門)
latched :曾經壞過且沒人來修 → 「它現在確實是壞的」 (dc-10)★ 新增
注意 dc-09c 的能量閘門落在「可算型」——枚舉區劃、加總銘牌、比對規則,不必等火災。W33 說過可算型的正確歸宿是互鎖而非告警;消防這裡的對應物是設計審查與 AHJ 竣工查驗,同樣是「在事情發生前用制度封死」,不是掛一條會被 ack 掉的告警。
自我測驗¶
Q1(事實回憶). 某 12 V block 的 commissioning baseline 是 3.20 mΩ,本季量到 4.35 mΩ,該串的串平均是 3.80 mΩ。這顆電池該換嗎?能跟廠商索賠嗎?
答案
兩個問題的答案不一樣,而且兩個都對。
對 35.9% 的三種判定:
| 門檻 | 出處 | 判定 | 動作 |
|---|---|---|---|
| 20% | CPBS 引 IEEE 1188 較新版本 | 超標 | 排汰換 |
| 30–50% | IEEE 1188-2005 Annex C.4 原文「typically a change of 30% to 50% … is considered significant」 | 灰帶 | 加測、追趨勢 |
| 50% | 廠商保固索賠門檻 | 未超標 | 索賠被打回 |
所以答案是:該換,但要不回錢。 這不是資料品質問題,是三個門檻服務三個不同目的(保守營運/工程指引/商業契約)。而且部分廠商用串平均當分母——換個分母,35.9% 直接變 14.5%,連「該換」都不成立了。
兩個容易漏的前置條件:baseline 必須在投入服務約 6 個月後量(新電池尚未完全化成,型錄值也不能用),而且必須是同一台儀器——換儀器 = baseline 作廢,instrument_id 不是稽核欄位,它是 trend 的分組鍵。
對你的模型:門檻是策略表 ThresholdPolicy(kind, source, denominator, pct, action),多筆並存;denominator ∈ {baseline, string_avg} 必須是欄位;一筆讀值 fan-out 成多筆 Finding。把 status: "bad" 寫進 OhmicReading,等於把當下這套策略燒進歷史資料。
Q2(事實回憶). 你的 dc-09 模型裡 energy_kwh() 回傳 28.0。2N、每路 2 個 bank。消防合規上這座機房算幾 kWh?裝了 25% LFL 氫氣偵測器算不算有早期預警?
答案
140 kWh,不是 112;而且 LFL 偵測器不是早期預警。
銘牌 = 28.0 / 0.8(DoD) = 35.0 kWh ← 合規用這個
4 × 35.0 = 140.0 kWh
對鋰系門檻 20 kWh → 7 倍,毫無疑問落入管制
(若誤用 usable:4 × 28.0 = 112.0,小 20%)
差這 20% 足以讓一個「算起來 580 kWh、以為過了 600 kWh 那道閘門」的設計實際上是 725 kWh。dc-09 現在的 energy_kwh() 已經乘過 DoD,它是運維的數字,不是合規的數字——見連貫性檢視 #1,這是本週唯一一個會直接產生錯誤數字的問題。
LFL 那半:
$$25\%\ \text{LFL(H}_2) = 0.25 \times 4\ \text{vol}\% = 1\ \text{vol}\% = \mathbf{10{,}000\ ppm}$$
off-gas 偵測工作在 ppm 到 ppb,兩者差 3–4 個數量級。等 LFL 偵測器叫,房裡氫氣已累積到 off-gas 門檻的一萬倍——電池早就在排氣了。25% LFL 的用途是防止累積到爆炸下限(4:1 安全係數),不是提前告訴你哪顆 cell 快壞了。NFPA 75(2024 版)首次為鋰電 UPS 加入 off-gas 要求,正是因為傳統可燃氣體感測器不是合適的替代品。
對你的模型:nameplate_energy_kwh 與 usable_energy_kwh 必須是兩個欄位;偵測器必須有 detector_class ∈ {off_gas, lfl, smoke, thermal, bms},告警動作與時間預算綁在 class 上——同一顆「氣體偵測」告警,off-gas 給你數十分鐘,LFL 給你的是疏散時間。塞進同一張表用同一套嚴重度分級,等於把最有價值的那三十分鐘丟掉。
Q3(應用). 廠商說「STS 轉換 4 ms,遠低於 IT 設備 20 ms 容忍度」。把台灣機房的真實最壞情況算出來。這條規則該掛在哪個實體上?
答案
台灣是 60 Hz,所以最壞是 5.50 ms,不是卡片表格裡那個 3.00 ms。
60 Hz:1 cycle = 16.67 ms
1/4(快速 POG) = 4.17 ms
3/4(抗湧流 VSS) = 12.50 ms
餘裕基準取 WP 62 實測重載 SMPS 的 18 ms(比 ITIC 的 20 ms 誠實):
60 Hz + POG :18 − 4.17 = 13.83 ms
60 Hz + VSS :18 − 12.50 = 5.50 ms ← 本站真正的最壞
(50 Hz + VSS:18 − 15.00 = 3.00 ms ← 歐洲/中國,本站不適用)
餘裕從 13.83 掉到 5.50 ms,剩下不到四成。 觸發它的是一個完全不在 STS 身上的事實:下游若是變壓器型 PDU(dc-11),快速轉換會讓磁通不連續而產生湧流,必須開抗湧流演算法。
另外兩個吃餘裕的變數同樣不在 STS 身上:市電頻率(型錄那個 4 ms 通常是 60 Hz 的數字,但別的地區不是)與下游負載率(18 ms 是重載實測,輕載長很多,但不能拿輕載數字做設計)。
掛在哪:三個輸入沒有一個是 Sts 的欄位,所以 effective_transfer_ms() 不該是 Sts 的 method。它是 algorithm × line_hz × downstream_has_transformer × load_pct 的函數,跟 W33 那條「電池 runtime > 發電機起動時間 × 安全係數」同一類——跨實體規則,必須是有 id、有輸入清單、有版本的一等公民。這是同一個結論第三次出現(dc-05c 合規規則、W33 Q4 電池 vs 發電機、本週 STS 轉換時間),而 Rule 這個實體到今天還不存在。
存成 transfer_time_ms = 4 的系統,在有人為了下游 PDU 把演算法切成抗湧流那天起,就開始說謊。
Q4(應用). 你把 A 路的年度電池容量測試排在週日 02:00–04:00,同一個週日 10:00–12:00 排 STS 維修旁路作業。兩個窗口在行事曆上不重疊。這樣排安全嗎?
答案
不安全,而且 10:00–12:00 那兩小時是本週三張卡疊起來的最壞情況。
02:00 A 路電池離線,接 DC load bank
03:00 放電結束(只佔 0.5–1 h)
04:00 行事曆上的 end —— 儀表板轉綠
04:00 開始再充電…… 要 8–24 h → recharge_until = 12:00 ~ 隔日 04:00
─────────────────────────────────────────────────────
10:00 STS 進 MAINTENANCE:電子元件隔離,沒有自動轉換
12:00 STS 作業結束
10:00–12:00 期間同時成立三件事:
- A 路電池尚未充飽(
dc-09b演算 4:總窗口 10–27 h,放電只佔其中 1 小時),這一路實際上沒有 ride-through; - STS 在維修旁路(
dc-10):redundancy_effective() = False,下游單路設備完全沒有自動轉換; - 儀表板全綠——第一項在行事曆上已經「結束」,第二項只是一條 INFO。
再加第四層:若 A、B 兩路電池在同一個防火區劃(dc-09c),那麼連「還有另一路可以救」都是假的——電力樹上的 2N 在消防樹上是 1N,而這件事在單線圖上看不出來,只在建築平面圖上看得到。
對你的模型(三個具體結論):
MaintenanceWindow的衝突檢查必須用recharge_until,不是end。 兩個欄位獨立存在的唯一理由就是這題。順帶一提量內阻也不是免費的(並聯串要離線才準),季測也該進同一套衝突檢查。- 衝突檢查的對象不是「時間重疊」,是「
effective_redundancy_delta疊加後是否歸零」。 兩個窗口可以在時間上重疊而完全無害(不同故障域),也可以在時間上不重疊而致命(本題)。 redundancy_effective()必須把「進行中的維護窗口」當輸入,跟它把 latched 告警當輸入是同一個道理——冗餘是算出來的,不是存出來的。
Q5(建模判斷). 本週三張卡產出了兩種「判定」:dc-09b/dc-09c 的 Finding,與 dc-10 的 Alarm。它們是同一件事嗎?如果混用會怎樣?該怎麼收斂?
答案
不是同一件事,而且混用會長出兩套平行的告警系統——這是本週最該立刻決定的一件事。
先看它們的實際差異:
Finding(dc-09b / dc-09c) |
Alarm(dc-10) |
|
|---|---|---|
| 怎麼產生 | 純函數:evaluate(事實, 判準) → list[Finding] |
有生命週期:raised → acked → reset |
| 有沒有狀態 | 沒有。輸入不變,重跑結果就一樣 | 有。latched / acked / reset_at |
| 重算會怎樣 | 隨時可全量重算,換了判準舊資料也能重評 | 不能重算——重算會抹掉「誰在什麼時候 ack 了什麼」 |
| 典型例子 | 「這批電池對 IEEE 20% 超標、對保固 50% 未超標」 | 「S1 SCR 短路,進線開關已跳開,等人來修」 |
混用的具體災難有兩種,方向相反:
- 把
Finding當Alarm存(給它acked欄位):判定變成有狀態的,於是換門檻之後不能重算——你會發現舊資料的判定是用兩年前的策略算的,跟新資料不可比。這正是dc-09bQ3 那句「把status: "bad"寫進OhmicReading就等於把當下這套策略燒進歷史資料」的翻版,只是換了個容器。 - 把
Alarm當Finding算(每輪重算):latched 語意直接消失。SCR 修好前條件確實還在,看起來沒事;但一旦有人手動把scr標回 OK 而沒真的修,或任何一次重算的輸入漏了,那條「這台 STS 沒有冗餘」就會安靜消失。
收斂建議——兩層,不要合併:
@dataclass(frozen=True)
class Finding: # 純判定,可重算,無狀態
subject_id: str # cell / compartment / bank …
policy_id: str # 指向 ThresholdPolicy 或 CodeRule
verdict: str # PASS | FAIL | REQUIRES_HMA | NOT_IN_SCOPE …
computed_at: datetime
inputs: dict # 讓判定可稽核、可重現
@dataclass
class Alarm: # 有生命週期的實體
code: str # 穩定識別碼,去重/抑制/runbook 用
severity: Sev
entity_ids: list[str] # 複數(W33 已定案)
raised_at: datetime
latched: bool = False
acked: bool = False
reset_at: datetime | None = None
reset_by: str | None = None
origin_finding_id: str | None = None # ★ 兩層之間唯一的橋
關鍵是最後那一行:Alarm 可以由 Finding 升上來,但不等於 Finding。判定持續為 FAIL 才升成 Alarm;Alarm 一旦升起就有自己的生命週期,不再隨判定消失而消失(latched 者尤其)。
三個立刻可驗證的約束:
ack()對latched告警不得改變active()——允許 ack 消掉它,等於允許一個人用一次點擊把「這台 STS 沒有冗餘」變成看不見。Finding不得有acked;Alarm不得有policy_id(它應該透過origin_finding_id去查)。redundancy_effective()讀的是scr/breaker_open/mode/ 維護窗口這些「事實」,不是讀Alarm。dc-10自我檢核 Q3 寫「告警反過來成為狀態計算的來源」——這句話容易被實作成循環依賴(alarms()讀redundancy_effective(),而redundancy_effective()讀alarms())。正確的方向是:latched 的故障狀態住在scr/breaker_open上,Alarm與redundancy_effective()都是它的下游。 卡片的骨架其實做對了,但那句話的措辭會誤導未來的自己。
一句話總結本週的建模躍遷:W33 教你「有些答案是一個數加上它的來歷」,W34 教你「判定不是事實的屬性,是事實乘上判準——而判準有版本、有出處,判定本身還分成可重算的與有生命週期的兩種。」
間隔複習¶
本週抽兩週前(W32);四週前(W30)尚無卡片。
| 週次 | 抽兩週前 | 抽四週前 |
|---|---|---|
| W33 | W31 的 dc-02 ✅ |
尚無(W29 無卡片) |
| W34(本週) | W32 的卡(dc-04 ~ dc-05c)✅ |
尚無(W30 無卡片) |
| W35 | W33 的卡(dc-06 ~ dc-09) |
W31 的卡(第二次)← 雙軌全開 |
| W36 | W34 的卡(dc-09b ~ dc-10) |
W32 的卡(第二次) |
抽中 dc-05c NFPA 110 測試制度(W32,2026-08-07)。挑它的理由不是隨機:本週的 dc-09b 犯了跟它一模一樣的錯,而且是同一個機制。
複習題(W32 dc-05c × 本週 dc-09b). dc-05c 說 30% 月測門檻的分母是「ESP 銘牌」。為什麼不能用降載後的 kW?這個錯誤在本週用另一個面貌又出現了一次——在哪裡?兩者的共同機制是什麼?
答案
dc-05c 那半:
NFPA 110 月測要求帶到銘牌額定的 30% 以上,而分母是 ESP(緊急備用額定)銘牌——五個 rating class 裡最大的那個,而且不套場址降載(溫度、海拔、進氣)。
選錯分母,門檻永遠偏小(825 → 567,少了 31%)。後果特別惡毒:你以為合規,實際上長期輕載,而長期輕載正是 wet stacking 的成因——這台機會因為「合規測試」本身而慢慢變壞。
本週的同一個錯誤(dc-09b 演算 2):
容量測試週期的升級條件之一是「已達 85% 預期服務壽命」。分母該用 service_life_est_years(估算值),不是 design_life_years(型錄值)。
晚了三年半,而那三年半正好是電池最可能掉下去的時段。
共同機制(這才是這題的重點):
選錯分母
→ 門檻/時點往「寬鬆」的方向移動
→ 系統不會報錯,因為兩個分母都是合法的數字、都存在於資料庫裡
→ 所有檢查都通過、所有儀表板都是綠的
→ 而設備正因為「合規」本身而在劣化(wet stacking/電池未被及時加密監測)
兩次的方向完全一致:錯誤的分母永遠讓標準變鬆,不會變嚴。 原因很簡單——會讓標準變嚴的錯誤會立刻造成誤報,有人會來查;讓標準變鬆的錯誤沒有人會發現。這類 bug 的偵測成本是不對稱的,所以必須在 schema 層擋掉,不能靠測試發現。
對你的模型:凡是「門檻 = 基準 × 係數」的地方,基準必須是具名的、有型別的、不可互換的。design_life_years 與 service_life_est_years 分兩欄還不夠——dc-09b 的驗收表裡那行「design_life=10 誤填進 service_life → 仍是 2 年測」證明了只要它們是同型別的 float,就一定有人填錯。同理 esp_kw / cop_kw / prp_kw 也不能都是 float。W33 提的 Bound 值物件正好可以擴充成這個用途:讓「這是哪一個額定」變成型別的一部分,而不是欄位名稱的一部分。
這也是 W33 連貫性檢視 #5 排在第二順位的另一個理由——它現在同時解決兩個問題。
四週前(W30):尚無。 本軌跡起始於 2026-07-28,W30 無卡片。W35 起雙軌全開,屆時每份週報多一題(抽 W31 的 dc-01 ~ dc-03b,第二次複習)。
本週的 model code 連貫性檢視¶
先結算舊帳:
| 來源 | 項目 | 狀態 |
|---|---|---|
| W33 #1 | Assembly 名稱碰撞 |
⚠️ 仍未收斂(本週三張卡沒碰 Assembly,但也沒修) |
| W33 #2 | EnergyStore 抽象 |
⚠️ 未動;dc-09c 又加了第三個能量欄位語意 |
| W33 #3 | DeviceRegistry 缺席 |
🟡 終於有排程了——dc-09b 加分題要求建、dc-09c 動手練習疊第二種遍歷、dc-10b 的動手練習就是它。欠四週,但下週會清掉 |
| W33 #4 | Alarm 形狀 |
✅ 本週被採納並擴充——dc-10 定義了 Alarm(code, severity, message, entity_ids, latched, acked)。這是週報建議第一次真的進到卡片裡 |
| W33 #5 | Bound 值物件 |
⚠️ 未動。但本週的間隔複習題給了它第二個用途(見上) |
| W33 #6 | 命名漂移 usable_* |
✅ 部分收斂——dc-09c 用 usable_energy_kwh(),符合建議的 usable_<unit>() 慣例 |
| W31 #3 | id 參照 vs 物件內含 | ✅ 本週事實上收斂了——OhmicReading.cell_id、BaselineSet.bank_id、EssUnit.compartment_id、FireCompartment.parent_id、Alarm.entity_ids 全部用 id。W33 訂的規則(持久化層一律 id)被三張卡一致遵守,四週來第一次有規則真的黏住 |
兩則好消息值得記下來:W33 的 Alarm 建議與 id 參照規則都被採納了。規律看起來是:具體到可以直接貼進 code 的建議會被採納,抽象的(EnergyStore 協定、Assembly 合併)不會。 下面的建議因此全部寫成可貼的 dataclass。
本週新增五處。
🔴 1. dc-09 的 energy_kwh() 現在是錯的——本週唯一會產生錯誤數字的項目¶
dc-09 定義 energy_kwh() 時已經乘過 0.8 DoD,回傳 28.0。dc-09c 則明確要求合規要用 35.0(銘牌),並在誤解 3 裡點名這個坑,且自己的骨架寫成 nameplate_energy_kwh + usable_energy_kwh()。
於是同一個模型裡有兩套相反的慣例:dc-09 的 energy_kwh() 是「已打折」,dc-09c 的 nameplate_energy_kwh 是「未打折」。名字上完全看不出差別。
收斂建議(純機械,10 分鐘):
# dc-09 改名,並讓兩張卡共用同一組語意
nameplate_energy_kwh: float # 銘牌,未打折 —— 合規/樓板承重/運輸法規
def usable_energy_kwh(self) -> float: # = nameplate × dod —— runtime
return self.nameplate_energy_kwh * self.dod
# 刪掉 energy_kwh():這個名字無法表達它是哪一個,留著就會有人用錯
這是本週第一順位,理由跟 W33 排序邏輯一致:它會產生錯誤數字,而且是靜默的——aggregate_energy() 誤用 usable 會小 20%,剛好足以讓 725 kWh 看起來像 580 kWh 而通過 600 kWh 那道閘門。dc-09c 的驗收表已經把這一行寫進去了(「若誤用 usable_energy_kwh() → 112.0 ← 跑出這個就是踩到誤解 3」),但驗收表擋不住 dc-09 那個舊名字。
🔴 2. ThresholdPolicy 與 CodeRule 是同構的,兩張卡都看見了,卻各寫了一份¶
dc-09c 自己寫著「跟 dc-09b 的 ThresholdPolicy 是同一個形狀」——然後定義了一個欄位完全不同的 CodeRule。逐項對照:
| 概念 | ThresholdPolicy(dc-09b) |
CodeRule(dc-09c) |
|---|---|---|
| 誰說的 | source ∈ {ieee, vendor_warranty, internal} |
edition ∈ {NFPA855_2023, NFPA855_2026, TW_ESS_GUIDE} |
| 管什麼 | kind: "ohmic" \| "capacity" |
scope: unit \| group \| compartment \| applicability |
| 對誰適用 | (隱含:這串電池) | chemistry: Chem |
| 分母/基準 | denominator ∈ {baseline, string_avg} |
(隱含:nameplate) |
| 門檻 | pct: float |
limit_kwh: float \| None |
| 判定為真時 | action: str |
requires_hma: bool |
六格逐項對應,沒有一格是多出來的。 差別只在單位(% vs kWh)與詞彙(policy vs rule)。
收斂建議:一個 Policy 基底,兩個具體型別各自帶自己的閾值單位:
@dataclass(frozen=True)
class Policy:
id: str
authority: str # "IEEE_1188" / "NFPA855_2023" / "TW_ESS_GUIDE" / "internal"
scope: str # 這條規則作用在哪個層級
applies_when: dict # {"chemistry": "LI_ION"} / {"kind": "ohmic"}
basis: str # ★ 分母/基準:baseline / string_avg / nameplate / esp
limit: float | None # None = 該版本移除此表(NFPA855_2026)
limit_unit: str # "pct" / "kwh" / "m"
on_violation: str # replace / investigate / warranty_claim / requires_hma
basis 那一欄是整個收斂的重點:它同時裝下 dc-09b 的 denominator、dc-09c 的「用銘牌不是用 usable」、以及間隔複習題裡 dc-05c 的「分母是 ESP 銘牌」。三張卡、三個子系統,同一個「選錯基準就靜默放寬標準」的坑——把它變成一個必填欄位,是唯一能在 schema 層擋住它的做法。
SeparationRule(dc-09c 的間距規則)也可以掛進來,limit_unit = "m"。
🟡 3. Finding 被三處使用,但從來沒有被定義過¶
dc-09b 的 evaluate() -> list[Finding]、dc-09c 的 evaluate_gates() -> list[Finding] 與 check_separation() -> list[Finding]、dc-09c 加分題「在 Finding 上加 source_edition」——四次引用,零次定義。它現在是模型裡出現頻率最高的型別,而它是一行 TODO 註解。
收斂建議:見自我測驗 Q5 的 dataclass。加分題要的 source_edition 不需要新欄位,policy_id 指向的 Policy.authority 就是它——這正是把 ThresholdPolicy / CodeRule 合併成 Policy 之後立刻拿到的好處,所以 #2 與 #3 應該一起做。
🟡 4. Alarm 與 Finding 的分界沒有被畫出來(=自我測驗 Q5)¶
不重複展開。結論:兩層不合併,origin_finding_id 是唯一的橋;Finding 不得有 acked,Alarm 不得有 policy_id;redundancy_effective() 讀事實不讀告警。
特別提醒一句措辭:dc-10 自我檢核 Q3 寫「告警反過來成為狀態計算的來源,第一次不只是輸出」——這句話是對的但危險,照字面實作會做出循環依賴。卡片骨架其實把 latched 狀態放在 scr / breaker_open 上(正確),建議在 dc-10b 開頭補一句澄清,否則三個月後的自己會照那句話重寫。
🟡 5. 兩棵樹的橫向邊沒有位置¶
dc-09c 要求 DeviceRegistry 支援 upstream(device) 與 fire_compartment(device) 兩種遍歷——這部分講清楚了。但兩棵樹之間的依賴邊沒有:串起來第二節那條「排風機必須掛發電機盤」是空間樹的節點依賴電力樹的節點,既不是 upstream 也不是 fire_compartment。
收斂建議:FireCompartment 加一個 safety_system_feeds: list[str](指向 PowerEdge 或 Device 的 id),並讓「本區劃的安全系統是否有緊急電源」成為一條可求值的 Policy(NFPA 855 2026 §4.10)。
為什麼現在就要留位置:dc-10b 的動手練習正是建 DeviceRegistry,這是四週以來唯一一次會動到它骨架的機會。第二輪冷卻鏈的 dc-22 CRAH(接不接 UPS)與 dc-20 儲冷槽會第二、三次撞上同一個形狀——那時再改,代價是七張卡。
優先序:
- #1
energy_kwh()改名 — 唯一會產生錯誤數字的,10 分鐘,先做。- #2 + #3
Policy/Finding— 一起做(source_edition因此免費)。basis欄位同時封死了dc-05c/dc-09b/dc-09c三張卡的同一個坑,是本週投報率最高的一項。- #5 兩棵樹的橫向邊 — 必須在
dc-10b動手練習之前決定,因為那次就是建DeviceRegistry。錯過要等到第二輪。4
Alarm/Finding分界 — 跟 #3 同時定案即可。¶- W33 舊帳 #1
Assembly、#5Bound、#2EnergyStore—Bound現在有第二個用途(額定型別化),可以跟 #2 的basis一起想。
本週該問 facility 的問題¶
三張卡共提出 8 個問題,去重後選 3 個下週真的拿去問人的。挑選標準沿用前三週:「現在問還來得及改,晚問就固化了」。本週三題的固化時點分別是——已經在倒數/設計階段/commissioning。
Q1 — 問 UPS/電池供應商 + 維運(⏳ 倒數中,錯過不可回溯)¶
「這批電池的 commissioning baseline 是誰量的、哪一天、用哪一台儀器?投入服務滿 6 個月那一筆有沒有?報告在哪? 量的時候有沒有記 cell 表面溫度(量負極柱)、浮充電壓、充電電流?讀值有沒有校正到 25 °C? 保固索賠認的分母是 baseline 還是 string average、門檻幾 %?需要幾筆週期讀值才受理? 電池間有沒有全年溫度曲線紀錄、月均幾度?」
為什麼是這題,而且為什麼最急:這是整條學習軌跡裡少數幾件真正不可回溯的事,跟 commissioning 同一類。
- baseline 有一個窗口:太早(安裝當天)電池尚未完全化成、讀值不穩;太晚就已經劣化過,之後所有偏差都以一個「已經壞了一點」的零點在算。錯過就永遠補不回來,而且沒有任何告警會提醒你錯過了。
- 沒有 baseline,保固從第一天起就是空的。IEEE 1188 原文說沒有基準線的內阻讀值 "difficult to interpret and of limited value"——你可以有一整年的高頻 BMS 遙測,卻在索賠時被打回。
- 分母那題決定我們的內部門檻要不要比廠商更嚴(Q1 算過:同一筆讀值換分母,35.9% → 14.5%)。
- 溫度曲線是
service_life_est_years唯一的輸入。沒有它,「型錄寫 10 年、我們四年就要換」這件事永遠只能吵,而且間隔複習題證明了分母填錯會讓測試排程安靜遲到三年半。
Q2 — 問 消防設備師 / 機電設計單位(必要時直接問 AHJ)(⚠️ 設計階段,蓋完就是拆牆)¶
「轄區 AHJ 採用哪一版——NFPA 855 的 2023、2026,還是只走台灣的儲能系統消防安全管理指引? 這決定我們是守 50/250/600 kWh 表格,還是一定要做 HMA(危害減緩分析),預算與時程差很多。 A 路與 B 路的電池在不在同一個防火區劃?時效幾小時?平面圖在哪? 廠商那份 UL 9540A 報告測到第幾層——cell、module、unit 還是 installation? 只做到 module 層的報告不能拿來放寬間距。 排風機與 off-gas 偵測盤掛在哪一路電源、有沒有進發電機盤?(2026 版 §4.10) 台灣端:廠內建築物內設置是否已規劃 2 小時防火區劃?周邊退距是走 30 m 基準還是 3 m 縮短條件(2 小時防火牆 + 自動滅火)?」
為什麼是這題:
- 版本分歧是真的、而且會並存好幾年——2023 版有能量表、2026 版把表移除改成 HMA 預設(僅鉛酸與水系鎳基豁免,且須由具 ESS 經驗的 PE 主導、另加大型燃燒試驗 LSFT),而 IFC 2024 引用的仍是 2023 版文字。這不是誰對誰錯,是同一份規範的兩個時間切面。問清楚才知道
CodeRule該填哪一版,也才知道要不要編 HMA 的預算。 - A/B 同區劃是 2N 的隱形殺手:
dc-09c說得直白——若同一區劃,2N 在消防事故上其實是 1N。而這件事在單線圖上看不出來,只在建築平面圖上看得到。這也是 W32 補考題(兩路市電同變電所、三台機共用油管)那個錯誤的第四次出現:用數量冒充獨立性。 - UL 9540A 是 test method 不是認證,四層依序測、任一層未觀察到火焰傳播就可停止——所以「測過了」完全可能是「整櫃從未被燒過」。只有 unit 或 installation 層的結果才足以支撐小於 3 ft 的間距宣告。
- 0.9 m 與 30 m 差 33 倍,因為量的根本不是同一件事(NFPA 量 unit 對 unit,台灣指引量 ESS 對周邊建物/道路)。把 3 ft 當成「離牆 3 呎就好」搬到台灣案子,是這裡最典型的誤讀。
Q3 — 問 控制系統承包商 / STS 供應商(⚠️ commissioning 前)¶
「STS 的轉換演算法設哪一種——快速(POG,1/4 cycle)還是抗湧流(VSS,3/4 cycle)?下游有沒有變壓器型 PDU? 高負載電流抑制門檻設多少(出廠約 3 倍額定)、有沒有調過?
Retransfer設 Yes 還是 No、延遲幾秒(4 s–10 min)? SCR 的 latched 告警現場怎麼 reset、誰有權限、有沒有留記錄? 另外——你報的 MTBF 邊界畫在哪裡?含不含輸出斷路器?含不含旁路操作的人為誤動作?」
為什麼是這題:
- 前兩問決定 Q3 算的那個餘裕:60 Hz + 抗湧流 = 5.50 ms,只剩快速轉換的四成。而「下游有沒有變壓器」是一個不在 STS 身上的事實,所以規格書上那個 4 ms 不會告訴你。
- 抑制門檻決定 STS 會凍結多久:下游短路時 STS 依設計不動作,只能等上游斷路器清除(有選擇性協調約 0.3 s、開 ERMS 約 0.05 s,兩個都遠超 ITIC 的 20 ms)。這條把 STS 的可用性直接綁在
dc-07b的保護協調設定上——同一次 commissioning 該一起驗。 - latched reset 的權限與記錄,決定你能不能相信「告警消失 = 問題解決」。這是全軌跡第一個 ack 不掉的告警,如果現場的實務是「隨手 reset」,那
latched這個欄位在資料庫裡就是裝飾。 - MTBF 分歧是真的:Schneider WP 62(約 2010)說 400,000–1,000,000 h 且結論是小型負載別用 STS;PDI/Eaton 規格書(2017)說超過 2,000,000 h。兩邊很可能在算不同的邊界(WP 62 算整台、PDI 明寫算「關鍵交流輸出匯流排」,中間又隔七年)。所以可驗證的問法不是「STS 可不可靠」,而是「你報的邊界畫在哪」——WP 62 把人為誤操作列為主要失效模式之一,而它不會出現在任何規格書的 MTBF 裡。
這週沒選上、但別忘記的¶
- 容量測試那天的替代供電路徑寫在哪份 MOP 裡?窗口算到放電結束還是算到充飽?(Q4 那兩小時就是這題沒問清楚的後果。)
- 量內阻時並聯串要不要離線?季測有沒有進維護窗口衝突檢查?(很多人以為量內阻是非侵入式的。)
- 偵測器清單:off-gas 有幾顆、裝在哪、跟 LFL 偵測器是不是同一套系統? 兩者不是同一件事的兩種精度,告警動作與時間預算完全不同。
- 鋰電室的樓板承重上限(W33 已列,仍未問;11,340 kg 對舊建物翻新是真實限制)。
- STS 的
active_source有沒有進歷史記錄?512 筆事件記錄與波形圖保存多久? 轉換只有幾毫秒,沒波形就查不出原因,而 512 筆很容易被日常事件洗掉。
下週¶
- 佇列下一張:
dc-10bSTS 的兩源關係(相位同步窗與頻率漂移、拓撲獨立性 common ancestor、雙母線容量會計)。它的動手練習就是欠了四週的DeviceRegistry——連貫性檢視 #5 那條「兩棵樹的橫向邊」必須在動筆前決定好,因為這是唯一一次會動到它骨架的機會。 - #1(
energy_kwh()改名)請在下一張卡之前做完,10 分鐘,且它是本週唯一會產生錯誤數字的項目。#2 + #3(Policy/Finding)建議一起做,basis欄位同時封死三張卡的同一個坑。 - 節奏檢討:本週 3 張卡,其中一天的空窗是
.git/index.lock造成的。建議把「開工先檢查有無遺留 staged 變更 + 收工必清 lock」寫進dc-daily-card的 SKILL——這次的損失是一張卡加兩天的 push 停擺,下次可能更久,因為沒有任何東西會通知你。 - ⚠️ commissioning 隨時可能開始,而且本週的 Q1 提醒了一件新的事:電池 baseline 的 6 個月窗口跟 commissioning 是兩個不同的時鐘。W32 Q3(ATS 五個計時器設定值 + NFPA 110 §7.13.4.1.4 分段時序記錄)、W33 Q2(ZSI 注入電流功能測試、tie 閉合互鎖驗證、arc flash 標籤)、本週 Q3(STS 演算法、抑制門檻、latched reset 權限)——這三份清單合起來就是現場驗收單,而 Q1 那份是驗收之後六個月才要做的事,最容易在專案結案後被忘掉。建議現在就把它排進行事曆。
已經先查到、留給 dc-10b 的材料¶
補寫本週報時為 dc-10 多查了幾筆專屬於「兩個源之間關係」的資料,dc-10 卡片刻意沒用(超出它的範圍),先寄放在這裡免得下次重查:
- IEC 62310-3 的 STS 效能碼
XX YY B TS——XX= 故障電流管理(CB有整合斷路器/熔絲、可開斷;PC只能承受不能開斷)|YY= 中性線處理(00無/NC兩輸入中性線併接/NS切換分離/NI隔離變壓器分離)|B= 轉換型式(Bbreak-before-make/Mmake-before-break)|TS= 電壓限值。這四段應該原樣存成四個 enum 欄位,不要塞成一個型號字串——因為YY與站點接地系統之間有硬約束(下一點)。順帶一提PC級是第四次出現「條件式額定」:它的耐受值綁定一顆特定型號的 uR 快速熔絲,換絲即失效,跟dc-07b的Icc同一個形狀。 - 極數 × 接地系統是跨實體約束:TN-S 四線強制 4W4P(否則不平衡負載與接地阻抗差異造成環流;裝了 RCD 還會誤跳);TN-C 禁止切換 PEN;IT 有分佈中性線必須 4W4P,否則兩顆 IMD 並聯會量到阻抗下降而誤報。這是
dc-02阻抗決定dc-07b合法性之後,第二個「A 的欄位決定 B 的合法性」——而且這次是站點層級欄位決定設備層級合法性。 - 來源分歧(BBM vs MBB):Socomec 明說預設且建議 BBM,MBB「技術上可行但很少用,因為重疊兩個獨立源會產生不受控的環流」;但也有資料說同步源可做 MBB(重疊不到一個週期)。兩邊很可能在講不同的設備——UPS 內部的靜態開關(逆變器↔旁路,同一台 UPS 主動同步,確實是 MBB 重疊導通)vs 獨立兩源之間的 STS。同樣叫「靜態開關」,同步前提完全不同,這正是
dc-10b該處理的分歧。 - 相位同步窗有三個數字,而且量的不是同一件事:MBB 安全條件約 5 電氣度(另加電壓差 <5%、頻率差 <0.2 Hz)|一般 STS 可接受窗口 10–20 電氣度|Socomec 說本體可在高達 180° 相位差下切換,但負載未必受得了(馬達、變壓器),所以提供「非同步時禁止轉換」的設定。三個數字分別是「MBB 的安全條件」「典型設定」「設備本體能力」——別放同一欄。
- 4 極 STS 的中性線是刻意重疊的:BBM 逐相進行,但中性線要重疊不超過 10 ms 以維持參考電位。所以「break-before-make」這個描述對相線成立、對中性線不成立。
- 可靠度數字的第三個版本:Socomec 給 λ_STS ≈ 150 FIT(MTBF 6.67×10⁶ h)、λ_UPS ≈ 2860 FIT(35 萬 h)、λ_cable ≈ 1 FIT/m。這跟
dc-10已記錄的 WP 62(40 萬–100 萬 h)與 PDI(>200 萬 h)湊成三個版本。dc-10b可以拿它算「加一台 STS 到底划不划算」,而關鍵在於答案是上游共祖的函數,不是 STS 的函數:兩條路徑若共用一個高失效率的祖先,STS 帶來的改善會從一個數量級塌到接近 1。這正好是ancestors()那段程式碼的動機——也是連貫性檢視 #5 那條橫向邊為什麼要在動筆前決定。