粗排引擎 (Rough-Cut Scheduler) OOA
來源:fp 共用物件 OOA:domain-model OOA(本份只引用、不重定義 VSM/投產單/產能/結果) 下游:pseudo 狀態:已簽核(2026-06-09,含 eligibility cascade)
1. 領域詞彙(Ubiquitous Language)
本份是「排程行為」層。資料物件(
ProductionOrder/VSM/VSMNode/DataBox/OperationCoordinate/AvailableCapacity/CapacitySlot/Occupation/RoughScheduleResult/ScheduledNode)一律引用 domain-model OOA,此處不重述。
主導服務(Domain Service)
- §RCS-OA-1
演算法協作者(Calculators)
- §RCS-OA-2
- 詞彙:LeanPlay 計算器
- 英文:
LeanPlayCalculator - 說明:正推定錨求節拍點最早日 → 有限產能堆疊 → 上游 JIT 拉動/下游推動(§RCS-FS-11/§RCS-FS-2/§RCS-FS-15)。步驟為私有 method
- §RCS-OA-3
- 詞彙:LSD 計算器
- 英文:
LsdCalculator - 說明:自
dueDate反推、純 leadtime、不看產能(§RCS-FS-13)
- §RCS-OA-4
- 詞彙:投產順序指派
- 英文:
SequenceAssigner - 說明:全批節點依 LeanPlay 完工日全域排序 →
sequence(§RCS-FS-12)
策略(Strategy)
- §RCS-OA-5
- 詞彙:前置時間策略
- 英文:
LeadTimeStrategy - 說明:介面;per-run 選定一個。impl:
CtTimesQtyPlusCo(本期)、CtTimesQty、FixedDays(§RCS-FS-6 / 未解 #9)
- §RCS-OA-6
- 詞彙:資源分配策略
- 英文:
ResourceAllocationStrategy - 說明:介面;per-run 選定一個。同一最早可佔日有多台 eligible 時
select(candidates)挑一台。impl:IdAscendingStrategy(本期 default,id 最小・§RCS-FS-18);MostConstrainedFirstStrategy(通用台留給受限零件)Stage 3 才實作(FS #17 / Q6)
Run 狀態(per-run working state)
| 詞彙 | 英文 | 說明 |
|---|---|---|
| 排程參數 | SchedulingParams | 輸入:起排日(§RCS-FS-11,預設今天+1)、選定的 LeadTimeStrategy 與 ResourceAllocationStrategy |
| 排程情境 | SchedulingContext | 本次 run 狀態:起排日、兩個策略、產能 overlay、資源主檔 ResourcePool(查 eligible) |
| 產能 overlay | CapacityOverlay | 包 AvailableCapacity(FREEZE 唯讀)+ 本 run 的 IN_RUN 佔用帳(鍵=資源×日);earliestRun 在 eligible 資源集合上掃天、同日多台交 strategy 挑;隨逐張排單 mutate、跑完丟棄(不寫回) |
例外(Exception)
- §RCS-OA-7
- 詞彙:無可用資源例外
- 英文:
NoEligibleResourceError - 說明:§RCS-FS-19:節拍點 eligible 資源空集(無資源 match 製程-工作站)→ fail-fast 中止該次粗排(視為資料/設定錯誤;與 §RCS-FS-8 空批次容忍不同)
2. 關係草圖
RoughCutScheduler ──收──> SchedulingParams
RoughCutScheduler ──建立 per-run──> SchedulingContext
SchedulingContext ──has 1──> LeadTimeStrategy
SchedulingContext ──has 1──> ResourceAllocationStrategy
SchedulingContext ──has 1──> CapacityOverlay
SchedulingContext ──has 1──> ResourcePool(見 domain-model:資源主檔 + eligible 查詢)
CapacityOverlay ──wraps 1──> AvailableCapacity(FREEZE 唯讀,見 domain-model)
CapacityOverlay ──holds *──> Occupation(IN_RUN,鍵=資源×日)
CapacityOverlay ──uses──> ResourceAllocationStrategy(同日多台候選挑台)
RoughCutScheduler ──uses──> LeanPlayCalculator / LsdCalculator / SequenceAssigner
LeanPlayCalculator ──reads──> VSM/DataBox ; ──queries──> ResourcePool.eligible(製程-工作站) ; ──occupies──> CapacityOverlay ; ──uses──> LeadTimeStrategy
LeanPlayCalculator ──throws──> NoEligibleResourceError(eligible 空集・§RCS-FS-19)
LsdCalculator ──reads──> VSM/DataBox/dueDate ; ──uses──> LeadTimeStrategy(不看產能)
SequenceAssigner ──orders──> ScheduledNode[]
RoughCutScheduler ──produces──> RoughScheduleResult(見 domain-model)
3. 類別圖(拆成三張子圖)
子圖 A — 編排與協作者(Orchestration)
classDiagram direction TB class RoughCutScheduler { +run(orders, capacity, resourcePool, params) RoughScheduleResult } class SchedulingParams { <<value object>> -startDate: LocalDate -strategy: LeadTimeStrategy -allocationStrategy: ResourceAllocationStrategy } class SchedulingContext { -startDate: LocalDate -strategy: LeadTimeStrategy -allocationStrategy: ResourceAllocationStrategy -overlay: CapacityOverlay -resourcePool: ResourcePool } class LeanPlayCalculator { +compute(order, ctx) Map~OperationCoordinate, LocalDate~ } class LsdCalculator { +compute(order, ctx) Map~OperationCoordinate, LocalDate~ } class SequenceAssigner { +assign(working) Map~OperationCoordinate, int~ } class DayRange { <<value object>> -start: LocalDate -finish: LocalDate } class CapacityOverlay { } class LeadTimeStrategy { <<interface>> } class ResourceAllocationStrategy { <<interface>> } class ResourcePool { } class NoEligibleResourceError { <<exception>> } RoughCutScheduler ..> SchedulingParams : 收 RoughCutScheduler "1" *-- "1" SchedulingContext : 建立 per-run RoughCutScheduler ..> LeanPlayCalculator RoughCutScheduler ..> LsdCalculator RoughCutScheduler ..> SequenceAssigner SchedulingContext "1" *-- "1" CapacityOverlay SchedulingContext --> LeadTimeStrategy SchedulingContext --> ResourceAllocationStrategy SchedulingContext --> ResourcePool LeanPlayCalculator ..> SchedulingContext LsdCalculator ..> SchedulingContext LeanPlayCalculator ..> DayRange : compute 內部回傳節拍點起訖 LeanPlayCalculator ..> ResourcePool : eligible(processId, workCenterId) LeanPlayCalculator ..> NoEligibleResourceError : eligible 空集拋(§RCS-FS-19)
圖外導航(取代 note 框,省版面):
CapacityOverlay見子圖 C、LeadTimeStrategy見子圖 B、ResourceAllocationStrategy見子圖 D、ResourcePool見domain-model(資源主檔 + eligible 查詢)、NoEligibleResourceError為 §RCS-FS-19 fail-fast 例外。LeanPlayCalculator私有步驟(anchor/stack/pull/push,含 eligible 掃天+strategy 挑台)見 pseudo-code §2.2–§2.5。
子圖 B — 前置時間策略(LeadTimeStrategy,per-run)
classDiagram direction TB class LeadTimeStrategy { <<interface>> +leadTimeDays(node) int } class CtTimesQtyPlusCo { +leadTimeDays(node) int } class CtTimesQty { +leadTimeDays(node) int } class FixedDays { -daysByWorkCenter: Map +leadTimeDays(node) int } LeadTimeStrategy <|.. CtTimesQtyPlusCo LeadTimeStrategy <|.. CtTimesQty LeadTimeStrategy <|.. FixedDays note for CtTimesQtyPlusCo "本期:ceil((CT×qty+CO)/480)" note for FixedDays "方案 3:生管給固定天數(『如何給』未定,#9 殘留)"
子圖 C — 產能 overlay(引擎側 IN_RUN,鍵=資源×日)
classDiagram direction TB class CapacityOverlay { -base: AvailableCapacity -inRun: Map < ResourceDay, List < Occupation > > +isAvailable(resourceId, day) boolean +earliestRun(eligible, fromDay, days, strategy) ResourceRun +occupy(resourceId, startDay, endDay, occupier) void +effectiveRemaining(resourceId, day) Duration } class ResourceRun { <<value object>> -resourceId: ResourceId -start: LocalDate -finish: LocalDate } class AvailableCapacity { } class Occupation { } class ResourceAllocationStrategy { <<interface>> } note for AvailableCapacity "見 domain-model(只含 FREEZE、唯讀);由引擎外小模組備妥,引擎不設定(§RCS-FS-9)" note for Occupation "見 domain-model;此處產生 source=IN_RUN" note for CapacityOverlay "isAvailable=單資源二元可用;earliestRun 在 eligible 集合上掃天找最早湊得出 L 連續空日者、同日多台交 strategy 挑(回 ResourceRun);occupy 記連續日;inRun 鍵控 ResourceDay(資源×日)。N 台並行=不同 resourceId 同日各記;resourceId 留引擎內、不外露(FS:輸出不含機台)" CapacityOverlay "1" o-- "1" AvailableCapacity : wraps(唯讀) CapacityOverlay "1" *-- "*" Occupation : IN_RUN(per-run,跑完丟棄) CapacityOverlay ..> ResourceAllocationStrategy : 同日候選挑台 CapacityOverlay ..> ResourceRun : earliestRun 回傳
子圖 D — 資源分配策略(ResourceAllocationStrategy,per-run)
classDiagram direction TB class ResourceAllocationStrategy { <<interface>> +select(candidates) ResourceId } class IdAscendingStrategy { +select(candidates) ResourceId } class MostConstrainedFirstStrategy { +select(candidates) ResourceId } ResourceAllocationStrategy <|.. IdAscendingStrategy ResourceAllocationStrategy <|.. MostConstrainedFirstStrategy note for ResourceAllocationStrategy "candidates=最早可佔日有 L 連續空檔的 eligible 資源(帶 capability);select 在同日候選中挑一台" note for IdAscendingStrategy "本期 default:resource id 最小者(悲觀下界・§RCS-FS-18)" note for MostConstrainedFirstStrategy "Stage 3 才實作(通用台留給受限零件、減少不必要排擠・FS #17/Q6);介面就位、簽章已足、本期不掛載"
設計決策(步驟 1 已拍板)
- 拆三計算器 + 編排:
RoughCutScheduler只編排;LeanPlayCalculator/LsdCalculator/SequenceAssigner各司其職(SRP、可分別單測)。 LeadTimeStrategyper-run 選定:整個 run 用同一策略,存於SchedulingContext(呼應 #9「計算前讀當前策略」)。- LeanPlay 步驟先為私有 method:正推定錨/有限產能堆疊/JIT 拉動/推動 為
LeanPlayCalculator私有步驟(YAGNI;要單獨測/替換再抽成 collaborators)。 - IN_RUN overlay =
CapacityOverlay(引擎側):包AvailableCapacity(FREEZE),自持 IN_RUN 佔用帳,跑完丟棄 →「不寫回」結構保證。
本輪共演化拍板(2026-06-08,隨 pseudo-code 展開)
- 計算器回傳值(C 案)而非 mutate ctx:
compute/assign改回傳 map;快照在編排層才生(ScheduledNode不變性靠結構、不靠紀律)。compute僅保留無法避免的 overlay 副作用 → 最好測。 - 產能二元語意(D5):粗排「一次一單」、顆粒度到天,(wc,day) all-or-nothing;
isAvailable為 primitive,effectiveRemaining(Duration)本版未用、供未來細顆粒。 - 只有節拍點查/佔產能:上游下游純 leadtime(粗排聚焦瓶頸)。
DayRange小 VO:stackCapacity回傳節拍點當日起訖(start, finish)。
本輪共演化拍板(2026-06-09,eligibility cascade,承 FS #17 §RCS-FS-16–B19)
- 產能 overlay 鍵下沉 資源×日:
inRun由WorkCenterDay改ResourceDay;isAvailable/earliestRun/occupy改以resourceId。N 台並行=不同 resourceId 同日各記。地基見../domain-model/domain-model.ooa.md(2026-06-09 段)。 - eligible 由 domain-model
ResourcePool查:引擎不自存資源能力;LeanPlayCalculator定錨堆疊前ctx.resourcePool.eligible(processId, workCenterId),空集即拋NoEligibleResourceError(§RCS-FS-19,fail-fast)。 earliestRun/ResourceAllocationStrategy職責切=(A):overlay 掃天 + 判 L 連續可用(握產能資料);strategy 只在「同一最早可佔日、多台 eligible 候選」中select(candidates)。比照LeadTimeStrategyper-run 選定、存SchedulingContext。- 本期 default
IdAscendingStrategy(id 最小・悲觀下界);MostConstrainedFirstStrategy介面就位、Stage 3 才實作(FS Q6)。 earliestRun回ResourceRun(含挑中 resourceId),occupy 後對外只取DayRange:resourceId 留引擎內、不外露(呼應 FS:輸出不含機台)。
4. SOLID 審查 + 設計模式
Part A — SOLID
✅ 穩固
- SRP:
RoughCutScheduler只編排;LeanPlayCalculator/LsdCalculator/SequenceAssigner各算一種輸出;CapacityOverlay專責 per-run 產能帳。compute/assign改回傳值後各自可獨立單測。 - OCP:
LeadTimeStrategy三策略可替換、不改演算法主體;新增策略只加 impl。ResourceAllocationStrategy同模式:換資源分配規則(id 最小 → 受限優先)只加 impl、不動 overlay 掃天主體。 - DIP:計算器依賴
LeadTimeStrategy介面與CapacityOverlay抽象,不綁定特定 leadtime 公式或產能來源;eligible 查詢依賴 domain-modelResourcePool抽象,不自存資源能力。 - LSP:三 Strategy impl 對
leadTimeDays(node)可互換。
⚠️ 風險與處置
LeanPlayCalculator私有步驟職責偏多(anchor/stack/pull/push 四步):決議先私有(YAGNI);若某步要單測或替換(如換 pull 策略)再抽為 collaborator。展開見 pseudo-code §2.2–§2.5。compute()的 overlay 副作用:逐張排擠的本質、無法純函數化;已用 C 案把「答案」改回傳值,使副作用面僅剩 overlay。
Part B — 設計模式
| # | 模式 | 用於 | 決議 |
|---|---|---|---|
| 1 | Strategy | LeadTimeStrategy(三策略 per-run 切換) | ✅ 採用 |
| 2 | Overlay/唯讀包裝 | CapacityOverlay wraps AvailableCapacity(FREEZE 唯讀)+ 自持 IN_RUN | ✅ 採用(「不寫回」結構保證) |
| 3 | (計算器拆分非 pattern) | 三計算器 + 編排 | 純 SRP 切分,不套 pattern 名 |
| 4 | Strategy | ResourceAllocationStrategy(同日多台 eligible 挑台、per-run 切換) | ✅ 採用(IdAscendingStrategy 本期;MostConstrainedFirstStrategy Stage 3) |
5. functional spec 對照
| Use Case / 邊界條件 | 對應物件職責 |
|---|---|
| UC A1 行為(黑箱整體) | RoughCutScheduler.run() |
| 圖 1 正推定錨(§RCS-FS-11 起排日) | LeanPlayCalculator.anchorTakt() |
| §RCS-FS-2 有限產能堆疊/排擠 | LeanPlayCalculator.stackCapacity() + CapacityOverlay.earliestRun() |
| §RCS-FS-15 節拍點後移、上游整段隨之後移(JIT) | LeanPlayCalculator.pullUpstream() |
| 下游 N+y 推動 | LeanPlayCalculator.pushDownstream() |
| §RCS-FS-13 LSD 反推、不看產能 | LsdCalculator.compute() |
| §RCS-FS-12 投產順序全域排序 | SequenceAssigner.assign() |
| §RCS-FS-1 無節拍點→以最後一站為節拍點 | VSM.takt() fallback(domain-model)+ scheduler |
| §RCS-FS-6 / #9 x-y 三策略 | LeadTimeStrategy(per-run 選定) |
| §RCS-FS-8 空批次 | RoughCutScheduler.run() 前置守衛 |
| §RCS-FS-3 交期早於今日 | LsdCalculator(反推落過去、不擋);LeanPlay 仍落未來 |
| §RCS-FS-7 不判 overdue | 引擎不做比對(刻意無對應方法) |
| §RCS-FS-9 Freeze 唯讀 | CapacityOverlay wraps AvailableCapacity(FREEZE) |
| §RCS-FS-16 多資源(棋盤)/N 台並行 | CapacityOverlay(鍵資源×日、earliestRun 掃 eligible 集合) |
| §RCS-FS-17 eligibility(可用資源子集) | ResourcePool.eligible()(domain-model WorkCapability.canPerform,exact 製程-工作站) |
| §RCS-FS-18 多 eligible 資源分配 | ResourceAllocationStrategy(本期 IdAscendingStrategy) |
| §RCS-FS-19 eligible 空集 → fail-fast | NoEligibleResourceError(LeanPlayCalculator 守衛) |
| UC A2 細排取用 | RoughScheduleResult(domain-model) |
簽核
- 編輯者:Alan / 日期:2026/06/09
- Reviewer:Alan / 日期:2026/06/09