粗排引擎 (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
    • 詞彙:粗排引擎
    • 英文:RoughCutScheduler
    • 說明:編排:依 prioritySeq 逐張 → 算 LeanPlay/LSD → 全域排序 → 產 RoughScheduleResult。純試算、不寫回

演算法協作者(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(本期)、CtTimesQtyFixedDays§RCS-FS-6 / 未解 #9)
  • §RCS-OA-6
    • 詞彙:資源分配策略
    • 英文:ResourceAllocationStrategy
    • 說明:介面;per-run 選定一個。同一最早可佔日有多台 eligibleselect(candidates) 挑一台。impl:IdAscendingStrategy(本期 default,id 最小・§RCS-FS-18);MostConstrainedFirstStrategy(通用台留給受限零件)Stage 3 才實作(FS #17 / Q6)

Run 狀態(per-run working state)

詞彙英文說明
排程參數SchedulingParams輸入:起排日(§RCS-FS-11,預設今天+1)、選定的 LeadTimeStrategyResourceAllocationStrategy
排程情境SchedulingContext本次 run 狀態:起排日、兩個策略、產能 overlay、資源主檔 ResourcePool(查 eligible
產能 overlayCapacityOverlayAvailableCapacityFREEZE 唯讀)+ 本 run 的 IN_RUN 佔用帳(鍵=資源×日);earliestRuneligible 資源集合上掃天、同日多台交 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、ResourcePooldomain-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 只編排;LeanPlayCalculatorLsdCalculatorSequenceAssigner 各司其職(SRP、可分別單測)。
  • LeadTimeStrategy per-run 選定:整個 run 用同一策略,存於 SchedulingContext(呼應 #9「計算前讀當前策略」)。
  • LeanPlay 步驟先為私有 method:正推定錨/有限產能堆疊/JIT 拉動/推動 為 LeanPlayCalculator 私有步驟(YAGNI;要單獨測/替換再抽成 collaborators)。
  • IN_RUN overlay = CapacityOverlay(引擎側):包 AvailableCapacityFREEZE),自持 IN_RUN 佔用帳,跑完丟棄 →「不寫回」結構保證。

本輪共演化拍板(2026-06-08,隨 pseudo-code 展開)

  • 計算器回傳值(C 案)而非 mutate ctxcomputeassign 改回傳 map;快照在編排層才生(ScheduledNode 不變性靠結構、不靠紀律)。compute 僅保留無法避免的 overlay 副作用 → 最好測。
  • 產能二元語意(D5)粗排「一次一單」、顆粒度到天,(wc,day) all-or-nothing;isAvailable 為 primitive,effectiveRemaining(Duration)本版未用、供未來細顆粒。
  • 只有節拍點查/佔產能:上游下游純 leadtime粗排聚焦瓶頸)。
  • DayRange 小 VOstackCapacity 回傳節拍點當日起訖 (start, finish)

本輪共演化拍板(2026-06-09,eligibility cascade,承 FS #17 §RCS-FS-16–B19)

  • 產能 overlay 鍵下沉 資源×日inRunWorkCenterDayResourceDayisAvailableearliestRunoccupy 改以 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)。
  • earliestRunResourceAllocationStrategy 職責切=(A):overlay 掃天 + 判 L 連續可用(握產能資料);strategy 只在「同一最早可佔日、多台 eligible 候選」中 select(candidates)。比照 LeadTimeStrategy per-run 選定、存 SchedulingContext
  • 本期 default IdAscendingStrategy(id 最小・悲觀下界)MostConstrainedFirstStrategy 介面就位、Stage 3 才實作(FS Q6)。
  • earliestRunResourceRun(含挑中 resourceId),occupy 後對外只取 DayRange:resourceId 留引擎內、不外露(呼應 FS:輸出不含機台)。

4. SOLID 審查 + 設計模式

Part A — SOLID

✅ 穩固

  • SRPRoughCutScheduler 只編排;LeanPlayCalculatorLsdCalculatorSequenceAssigner 各算一種輸出;CapacityOverlay 專責 per-run 產能帳。computeassign 改回傳值後各自可獨立單測。
  • OCPLeadTimeStrategy 三策略可替換、不改演算法主體;新增策略只加 impl。ResourceAllocationStrategy 同模式:換資源分配規則(id 最小 → 受限優先)只加 impl、不動 overlay 掃天主體。
  • DIP:計算器依賴 LeadTimeStrategy 介面與 CapacityOverlay 抽象,不綁定特定 leadtime 公式或產能來源;eligible 查詢依賴 domain-model ResourcePool 抽象,不自存資源能力。
  • 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 — 設計模式

#模式用於決議
1StrategyLeadTimeStrategy(三策略 per-run 切換)✅ 採用
2Overlay/唯讀包裝CapacityOverlay wraps AvailableCapacityFREEZE 唯讀)+ 自持 IN_RUN✅ 採用(「不寫回」結構保證)
3(計算器拆分非 pattern)三計算器 + 編排純 SRP 切分,不套 pattern 名
4StrategyResourceAllocationStrategy(同日多台 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 AvailableCapacityFREEZE
§RCS-FS-16 多資源(棋盤)/N 台並行CapacityOverlay(鍵資源×日、earliestRuneligible 集合)
§RCS-FS-17 eligibility(可用資源子集)ResourcePool.eligible()(domain-model WorkCapability.canPerform,exact 製程-工作站)
§RCS-FS-18eligible 資源分配ResourceAllocationStrategy(本期 IdAscendingStrategy
§RCS-FS-19 eligible 空集 → fail-fastNoEligibleResourceErrorLeanPlayCalculator 守衛)
UC A2 細排取用RoughScheduleResult(domain-model)

簽核

  • 編輯者:Alan / 日期:2026/06/09
  • Reviewer:Alan / 日期:2026/06/09