mcpField
M
master-production-scheduling

master-production-scheduling

github.com/kishorkukrejaその他8454 回インストール3 週間前に更新
リポジトリ

master-production-scheduling について

需要、能力、資材の制約を考慮した期間別生産計画を作る、製造業向けの主生産計画(MPS)スキルです。概略能力の確認、ATP計算、タイムフェンス管理、生産戦略も扱います。

主な機能

  1. 01期間別の製品生産計画
  2. 02需要集約と在庫見込み計算
  3. 03概略能力計画の検証
  4. 04ATP(受注可能量)の計算
  5. 05タイムフェンスと計画変更管理
  6. 06能力・在庫戦略の最適化

活用シーン

  1. 01予測と顧客注文から生産計画を作成
  2. 02作業区の能力と計画を照合
  3. 03顧客向け受注可能数量の算出
  4. 04在庫、能力、生産変動のバランス調整

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill master-production-scheduling

インストール後、Claude Code で/master-production-schedulingを入力して読み込まれたか確認します。

詳細

マスタープロダクションスケジューリング(MPS)

マスタープロダクションスケジューリング(MPS)と生産計画の専門家として、需要要件と能力制約のバランスを取りながら、効率性とサービスレベルを最大化する、実行可能で最適化された生産スケジュールを組織が作成できるよう支援します。

初期評価

MPSを策定する前に、以下を把握します。

  1. 計画環境

    • 製造形態は何ですか?(見込み生産、受注生産、受注組立、受注設計生産)
    • 生産プロセスは何ですか?(個別生産、プロセス生産、反復生産、バッチ生産)
    • 計画期間はどのくらいですか?(週、月)
    • 計画バケットのサイズはどのくらいですか?(日次、週次)
  2. 製品と需要

    • 最終製品/SKUの数はいくつですか?
    • 製品構造の複雑さはどの程度ですか?(単一階層、複数階層のBOM)
    • 需要パターンはどのようなものですか?(安定、季節性、断続的)
    • 顧客注文のリードタイムと製造リードタイムはどのくらいですか?
  3. 能力と制約

    • 重要なリソースやボトルネックは何ですか?
    • 段取り替え/セットアップ時間は重要ですか?
    • 従業員の稼働状況とスキルはどうなっていますか?
    • 資材に制約はありますか?
  4. 現状

    • 既存のMPSプロセスはありますか?
    • ERP/MRPシステムを導入していますか?
    • スケジュール遵守率はどのくらいですか?
    • 在庫レベル(原材料、仕掛品、完成品)はどのくらいですか?

MPSフレームワーク

MPSの定義

**マスタープロダクションスケジュール(MPS)**とは、各最終製品をいつ、いくつ生産するかを指定する、期間別の計画です。S&OPによる総合生産計画を、個々の製品の具体的な生産スケジュールへと細分化します。

主な特徴:

  • 期間別:各期間の具体的な数量
  • 品目レベル:最終製品または計画品目
  • 実行可能:能力と資材の制約を遵守
  • 確定ゾーン:短期は固定、長期は柔軟
  • MRPを駆動:資材所要量計画への入力
MPSと関連計画の比較
計画の種類期間レベル頻度目的
S&OP12~18か月製品ファミリー月次需要と供給の調整
MPS3~6か月最終製品週次詳細な生産計画
MRP3~6か月構成品日次/週次資材所要量
製造現場スケジュール数日~数週間作業日次詳細な順序付け

MPS計画プロセス

ステップ1:需要要件の確認

需要の発生源:

  1. 需要予測

    • 需要計画による統計的な予測
    • マーケティング/営業からの情報
    • 季節性とトレンド
  2. 顧客注文

    • 受注済みの注文
    • 確定した約束
    • 契約
  3. 安全在庫要件

    • バッファ在庫の目標
    • サービスレベル方針
  4. 工場間移送

    • 配送所要量
    • ネットワーク需要
  5. サービスパーツ

    • アフターマーケット需要
    • 保証要件

需要の集約:

import pandas as pd
import numpy as np
from datetime import datetime, timedelta

class MpsDemandPlanning:
    """Aggregate demand sources for MPS"""

    def __init__(self, planning_horizon_weeks=26):
        self.horizon = planning_horizon_weeks
        self.demand_sources = []

    def add_forecast_demand(self, item, periods, quantities):
        """Add forecasted demand"""
        self.demand_sources.append({
            'source': 'forecast',
            'item': item,
            'periods': periods,
            'quantities': quantities
        })

    def add_customer_orders(self, item, periods, quantities):
        """Add firm customer orders"""
        self.demand_sources.append({
            'source': 'customer_orders',
            'item': item,
            'periods': periods,
            'quantities': quantities
        })

    def add_safety_stock(self, item, target_level):
        """Add safety stock requirement"""
        self.demand_sources.append({
            'source': 'safety_stock',
            'item': item,
            'target': target_level
        })

    def calculate_total_demand(self):
        """
        Calculate total demand by item and period

        Uses consumption logic:
        - Customer orders consume forecast in near term
        - Forecast used beyond order horizon
        """

        # Create time buckets
        start_date = datetime.now()
        periods = pd.date_range(start_date, periods=self.horizon, freq='W')

        all_items = set()
        for source in self.demand_sources:
            all_items.add(source['item'])

        demand_df = pd.DataFrame({
            'period': periods
        })

        for item in all_items:
            # Get forecast
            forecast = self._get_source_demand(item, 'forecast', periods)

            # Get customer orders
            orders = self._get_source_demand(item, 'customer_orders', periods)

            # Apply consumption logic: greater of forecast or orders
            total_demand = np.maximum(forecast, orders)

            demand_df[f'{item}_forecast'] = forecast
            demand_df[f'{item}_orders'] = orders
            demand_df[f'{item}_total'] = total_demand

        return demand_df

    def _get_source_demand(self, item, source_type, periods):
        """Extract demand for specific item and source"""

        quantities = np.zeros(len(periods))

        for source in self.demand_sources:
            if source['item'] == item and source['source'] == source_type:
                if 'periods' in source:
                    for i, period in enumerate(source['periods']):
                        if i < len(quantities):
                            quantities[i] = source['quantities'][i]

        return quantities

# Example usage
mps_demand = MpsDemandPlanning(planning_horizon_weeks=12)

# Add forecast
periods = list(range(12))
forecast_qty = [100, 110, 105, 120, 115, 125, 130, 120, 115, 110, 120, 125]
mps_demand.add_forecast_demand('Product_A', periods, forecast_qty)

# Add firm customer orders (first 4 weeks)
orders = [150, 130, 0, 0] + [0] * 8
mps_demand.add_customer_orders('Product_A', periods, orders)

# Calculate total demand
total_demand = mps_demand.calculate_total_demand()
print("MPS Demand by Week:")
print(total_demand[['period', 'Product_A_forecast', 'Product_A_orders', 'Product_A_total']])
ステップ2:暫定MPSの策定

MPSのロジック:

Projected Available Balance (PAB) formula:
PAB[t] = PAB[t-1] + MPS[t] - max(Forecast[t], Orders[t])

When PAB[t] < Safety Stock:
  Schedule MPS receipt

MPS計画表:

期間予測注文合計予測可能在庫MPSATP
11001501505020050
211013013012000
3105010515100100
import pandas as pd
import numpy as np

class MasterProductionSchedule:
    """Master Production Schedule generator"""

    def __init__(self, item, beginning_inventory,
                 safety_stock, lot_size, lead_time):
        """
        Parameters:
        - item: product identifier
        - beginning_inventory: starting inventory level
        - safety_stock: minimum inventory target
        - lot_size: production lot size (0 = lot-for-lot)
        - lead_time: production lead time (periods)
        """
        self.item = item
        self.beginning_inventory = beginning_inventory
        self.safety_stock = safety_stock
        self.lot_size = lot_size
        self.lead_time = lead_time

    def generate_mps(self, forecast, customer_orders, periods):
        """
        Generate MPS using standard MPS logic

        Returns DataFrame with MPS planning table
        """

        mps_table = []

        projected_available = self.beginning_inventory
        cumulative_mps = 0

        for t in range(periods):
            # Total demand (greater of forecast or orders)
            fcst = forecast[t] if t < len(forecast) else 0
            orders = customer_orders[t] if t < len(customer_orders) else 0
            total_demand = max(fcst, orders)

            # Check if MPS receipt needed
            if projected_available - total_demand < self.safety_stock:
                # Schedule MPS receipt
                shortage = self.safety_stock - (projected_available - total_demand)

                if self.lot_size > 0:
                    # Fixed lot size
                    lots_needed = int(np.ceil(shortage / self.lot_size))
                    mps_quantity = lots_needed * self.lot_size
                else:
                    # Lot-for-lot
                    mps_quantity = shortage

                # Account for lead time (schedule in future period)
                mps_receipt_period = t
            else:
                mps_quantity = 0
                mps_receipt_period = t

            # Update projected available
            projected_available = projected_available + mps_quantity - total_demand

            # Calculate Available-to-Promise (ATP)
            # ATP[t] = MPS[t] - sum(orders from t to next MPS)
            if mps_quantity > 0:
                atp = mps_quantity - orders
            else:
                atp = 0

            mps_table.append({
                'period': t + 1,
                'forecast': fcst,
                'customer_orders': orders,
                'total_demand': total_demand,
                'projected_available': max(0, projected_available),
                'mps_quantity': mps_quantity,
                'atp': max(0, atp)
            })

        return pd.DataFrame(mps_table)

    def calculate_rough_cut_capacity(self, mps_table, routing_time_per_unit):
        """
        Rough-Cut Capacity Planning (RCCP)

        Check if MPS is feasible given capacity

        Parameters:
        - mps_table: output from generate_mps
        - routing_time_per_unit: hours required per unit
        """

        mps_table['required_hours'] = (
            mps_table['mps_quantity'] * routing_time_per_unit
        )

        return mps_table[['period', 'mps_quantity', 'required_hours']]

# Example
mps = MasterProductionSchedule(
    item='Product_A',
    beginning_inventory=200,
    safety_stock=50,
    lot_size=100,  # Fixed lot size of 100
    lead_time=2
)

# Demand data
forecast = [100, 110, 105, 120, 115, 125, 130, 120, 115, 110, 120, 125]
customer_orders = [150, 130, 80, 90] + [0] * 8

# Generate MPS
mps_plan = mps.generate_mps(forecast, customer_orders, periods=12)
print("Master Production Schedule:")
print(mps_plan)

# Rough-cut capacity
rccp = mps.calculate_rough_cut_capacity(mps_plan, routing_time_per_unit=2.5)
print("\nRough-Cut Capacity Requirements:")
print(rccp)
ステップ3:概略能力計画(RCCP)

目的: 能力制約を踏まえてMPSが実行可能か検証します。

プロセス:

  1. MPSからリソース所要量を算出します
  2. 利用可能な能力と比較します
  3. 過負荷を特定します
  4. MPSまたは能力を調整します
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

class RoughCutCapacityPlanning:
    """RCCP - Validate MPS feasibility"""

    def __init__(self, work_centers):
        """
        Parameters:
        - work_centers: dict {name: {'capacity_hours': x, 'efficiency': y}}
        """
        self.work_centers = work_centers

    def validate_mps(self, mps_schedule, routings):
        """
        Check if MPS is feasible

        Parameters:
        - mps_schedule: DataFrame with 'item', 'period', 'mps_quantity'
        - routings: dict {item: [(work_center, hours_per_unit)]}

        Returns capacity analysis
        """

        requirements = []

        for idx, row in mps_schedule.iterrows():
            item = row['item']
            period = row['period']
            quantity = row['mps_quantity']

            if quantity == 0:
                continue

            # Get routing for item
            routing = routings.get(item, [])

            for work_center, hours_per_unit in routing:
                # Calculate required hours
                efficiency = self.work_centers[work_center]['efficiency']
                required_hours = (quantity * hours_per_unit) / efficiency

                requirements.append({
                    'period': period,
                    'work_center': work_center,
                    'item': item,
                    'quantity': quantity,
                    'required_hours': required_hours
                })

        req_df = pd.DataFrame(requirements)

        # Aggregate by work center and period
        capacity_analysis = req_df.groupby(
            ['period', 'work_center']
        )['required_hours'].sum().reset_index()

        # Add available capacity
        capacity_analysis['available_capacity'] = capacity_analysis['work_center'].map(
            lambda wc: self.work_centers[wc]['capacity_hours']
        )

        # Calculate load %
        capacity_analysis['load_pct'] = (
            capacity_analysis['required_hours'] /
            capacity_analysis['available_capacity'] * 100
        )

        capacity_analysis['status'] = capacity_analysis['load_pct'].apply(
            lambda x: 'Overload' if x > 100 else
                     'High' if x > 90 else
                     'OK'
        )

        return capacity_analysis

    def identify_overloads(self, capacity_analysis):
        """Get periods and resources with overload"""
        return capacity_analysis[capacity_analysis['status'] == 'Overload']

    def plot_capacity_profile(self, capacity_analysis):
        """Visualize capacity load"""

        work_centers = capacity_analysis['work_center'].unique()

        fig, axes = plt.subplots(len(work_centers), 1,
                                figsize=(12, 4 * len(work_centers)),
                                squeeze=False)

        for i, wc in enumerate(work_centers):
            wc_data = capacity_analysis[
                capacity_analysis['work_center'] == wc
            ].sort_values('period')

            ax = axes[i, 0]

            # Plot capacity line
            ax.axhline(y=wc_data['available_capacity'].iloc[0],
                      color='green', linestyle='--',
                      linewidth=2, label='Available Capacity')

            # Plot requirements
            bars = ax.bar(wc_data['period'], wc_data['required_hours'],
                         alpha=0.7)

            # Color overloads red
            for j, (idx, row) in enumerate(wc_data.iterrows()):
                if row['status'] == 'Overload':
                    bars[j].set_color('red')

            ax.set_title(f'{wc} - Capacity Load')
            ax.set_xlabel('Period')
            ax.set_ylabel('Hours')
            ax.legend()
            ax.grid(True, alpha=0.3)

        plt.tight_layout()
        return fig

# Example
work_centers = {
    'Assembly': {'capacity_hours': 160, 'efficiency': 0.90},
    'Testing': {'capacity_hours': 120, 'efficiency': 0.95},
    'Packaging': {'capacity_hours': 140, 'efficiency': 0.92}
}

rccp = RoughCutCapacityPlanning(work_centers)

# MPS schedule (from previous example)
mps_schedule = pd.DataFrame({
    'item': ['Product_A'] * 12,
    'period': range(1, 13),
    'mps_quantity': [200, 0, 100, 200, 0, 100, 200, 0, 100, 200, 0, 100]
})

# Routings
routings = {
    'Product_A': [
        ('Assembly', 1.5),
        ('Testing', 0.8),
        ('Packaging', 0.5)
    ]
}

# Validate MPS
capacity_check = rccp.validate_mps(mps_schedule, routings)
print("Capacity Analysis:")
print(capacity_check)

# Identify overloads
overloads = rccp.identify_overloads(capacity_check)
if not overloads.empty:
    print("\nOverloaded Resources:")
    print(overloads)
else:
    print("\nNo capacity overloads - MPS is feasible")

# Plot
rccp.plot_capacity_profile(capacity_check)
ステップ4:約束可能在庫(ATP)

ATPの定義: 在庫と計画生産のうち、未引当の部分です。

ATPのルール:

  • 最初の期間:手持在庫 + MPS - 顧客注文
  • 以降の期間:MPS - 顧客注文(次のMPSまで)

ATPの計算:

class AvailableToPromise:
    """Calculate ATP for order promising"""

    def __init__(self, mps_table, on_hand_inventory):
        self.mps = mps_table
        self.on_hand = on_hand_inventory

    def calculate_atp(self):
        """
        Calculate Available-to-Promise

        ATP provides visibility for order promising
        """

        atp_table = self.mps.copy()
        atp_table['atp'] = 0

        for i in range(len(atp_table)):
            if i == 0:
                # First period: On-hand + MPS - Orders
                atp_table.loc[i, 'atp'] = (
                    self.on_hand +
                    atp_table.loc[i, 'mps_quantity'] -
                    atp_table.loc[i, 'customer_orders']
                )
            else:
                # Check if MPS in this period
                if atp_table.loc[i, 'mps_quantity'] > 0:
                    # ATP = MPS - orders from this period until next MPS
                    orders_to_next_mps = 0

                    # Look ahead for orders until next MPS
                    for j in range(i, len(atp_table)):
                        orders_to_next_mps += atp_table.loc[j, 'customer_orders']

                        # Stop at next MPS
                        if j > i and atp_table.loc[j, 'mps_quantity'] > 0:
                            break

                    atp_table.loc[i, 'atp'] = (
                        atp_table.loc[i, 'mps_quantity'] - orders_to_next_mps
                    )

        return atp_table

    def check_order_promise(self, order_quantity, requested_period):
        """
        Check if order can be promised

        Returns: (can_promise, available_period)
        """

        atp_table = self.calculate_atp()

        # Check requested period first
        if requested_period <= len(atp_table):
            cumulative_atp = atp_table.loc[:requested_period-1, 'atp'].sum()

            if cumulative_atp >= order_quantity:
                return True, requested_period

        # Find earliest period with enough ATP
        cumulative = 0
        for i in range(len(atp_table)):
            cumulative += atp_table.loc[i, 'atp']

            if cumulative >= order_quantity:
                return True, i + 1

        # Cannot promise
        return False, None

# Example
mps_table = pd.DataFrame({
    'period': [1, 2, 3, 4, 5, 6],
    'forecast': [100, 110, 105, 120, 115, 125],
    'customer_orders': [80, 70, 50, 30, 0, 0],
    'mps_quantity': [200, 0, 150, 0, 0, 200],
    'projected_available': [120, 50, 195, 75, 75, 150]
})

atp = AvailableToPromise(mps_table, on_hand_inventory=100)

# Calculate ATP
atp_results = atp.calculate_atp()
print("Available-to-Promise:")
print(atp_results[['period', 'mps_quantity', 'customer_orders', 'atp']])

# Check if can promise order
can_promise, period = atp.check_order_promise(order_quantity=150, requested_period=2)
if can_promise:
    print(f"\n✓ Can promise 150 units by period {period}")
else:
    print("\n✗ Cannot promise order")
ステップ5:MPSの確定とリリース

タイムフェンス:

|<-- Frozen Zone -->|<-- Slushy Zone -->|<-- Liquid Zone -->|
       2-4 weeks          4-8 weeks           8+ weeks

Frozen: No changes without executive approval
Slushy: Changes with planner approval
Liquid: Flexible, planning purposes only

確定方針:

from datetime import datetime, timedelta

class MpsTimeFences:
    """Manage MPS time fences and change control"""

    def __init__(self, frozen_weeks, slushy_weeks):
        self.frozen_weeks = frozen_weeks
        self.slushy_weeks = slushy_weeks

    def get_zone(self, period_date):
        """Determine time fence zone for a period"""

        now = datetime.now()
        frozen_end = now + timedelta(weeks=self.frozen_weeks)
        slushy_end = now + timedelta(weeks=self.frozen_weeks + self.slushy_weeks)

        if period_date <= frozen_end:
            return 'Frozen'
        elif period_date <= slushy_end:
            return 'Slushy'
        else:
            return 'Liquid'

    def approve_change(self, period_date, change_type, authority_level):
        """
        Check if change is allowed

        Parameters:
        - change_type: 'quantity', 'timing', 'cancellation'
        - authority_level: 'planner', 'manager', 'executive'

        Returns: (approved, reason)
        """

        zone = self.get_zone(period_date)

        if zone == 'Frozen':
            if authority_level == 'executive':
                return True, 'Executive approval in frozen zone'
            else:
                return False, 'Changes require executive approval in frozen zone'

        elif zone == 'Slushy':
            if authority_level in ['manager', 'executive']:
                return True, 'Manager approval in slushy zone'
            else:
                return False, 'Changes require manager approval in slushy zone'

        else:  # Liquid
            return True, 'Free to change in liquid zone'

    def mps_release(self, mps_table):
        """
        Release MPS with time fence zones marked

        Returns MPS with zones and change authority
        """

        mps_released = mps_table.copy()

        # Assume period is datetime
        mps_released['zone'] = mps_released['period'].apply(self.get_zone)

        mps_released['change_authority'] = mps_released['zone'].map({
            'Frozen': 'Executive',
            'Slushy': 'Manager',
            'Liquid': 'Planner'
        })

        return mps_released

# Example
fences = MpsTimeFences(frozen_weeks=4, slushy_weeks=4)

# Check change approval
period = datetime.now() + timedelta(weeks=2)
approved, reason = fences.approve_change(period, 'quantity', authority_level='planner')
print(f"Change approval: {approved}")
print(f"Reason: {reason}")

製造環境別のMPS戦略

見込生産(MTS)

特徴:

  • 需要予測に基づいて生産します
  • 完成品在庫を維持します
  • 顧客への納期が短くなります

MPS のアプローチ:

  • 需要予測と安全在庫に基づいてスケジュールを作成します
  • 平準化生産を優先します
  • 補充のトリガー(最小/最大、EOQ)を設定します
def mts_mps_logic(forecast, current_inventory, safety_stock,
                  target_inventory, lot_size):
    """
    Make-to-Stock MPS logic

    Schedule production to maintain target inventory
    """

    mps_schedule = []

    inventory = current_inventory

    for period, demand in enumerate(forecast):
        # Check if replenishment needed
        projected_inventory = inventory - demand

        if projected_inventory < safety_stock:
            # Order up to target
            order_quantity = target_inventory - projected_inventory

            # Round to lot size
            if lot_size > 0:
                lots = int(np.ceil(order_quantity / lot_size))
                order_quantity = lots * lot_size

            inventory = projected_inventory + order_quantity
        else:
            order_quantity = 0
            inventory = projected_inventory

        mps_schedule.append({
            'period': period + 1,
            'demand': demand,
            'mps_quantity': order_quantity,
            'ending_inventory': inventory
        })

    return pd.DataFrame(mps_schedule)

# Example
forecast = [100, 110, 105, 120, 115, 125, 130, 120]
mts_plan = mts_mps_logic(
    forecast=forecast,
    current_inventory=200,
    safety_stock=50,
    target_inventory=250,
    lot_size=100
)
print("Make-to-Stock MPS:")
print(mts_plan)
受注生産(MTO)

特徴:

  • 特定の顧客注文に対してのみ生産します
  • 完成品在庫はありません
  • 顧客へのリードタイムが長くなります

MPS のアプローチ:

  • 顧客注文に基づいてスケジュールを作成します
  • 納期から逆算してスケジュールを作成します
  • ATP によって注文の納期回答を行います
def mto_mps_logic(customer_orders, production_lead_time):
    """
    Make-to-Order MPS logic

    Schedule production to meet order due dates
    """

    mps_schedule = []

    for order in customer_orders:
        order_id = order['order_id']
        quantity = order['quantity']
        due_date = order['due_date']

        # Backward schedule: start = due date - lead time
        start_period = due_date - production_lead_time

        mps_schedule.append({
            'order_id': order_id,
            'start_period': max(1, start_period),
            'due_period': due_date,
            'quantity': quantity,
            'order_type': 'customer_order'
        })

    return pd.DataFrame(mps_schedule)

# Example
orders = [
    {'order_id': 'ORD-001', 'quantity': 50, 'due_date': 5},
    {'order_id': 'ORD-002', 'quantity': 75, 'due_date': 7},
    {'order_id': 'ORD-003', 'quantity': 60, 'due_date': 10}
]

mto_plan = mto_mps_logic(orders, production_lead_time=3)
print("Make-to-Order MPS:")
print(mto_plan)
受注組立(ATO)

特徴:

  • 部品を在庫し、受注後に最終組立を行います
  • 共通部品を使って多様な製品を提供します
  • リードタイムは中程度です

MPS のアプローチ:

  • 2 段階の MPS:
    1. 部品/モジュールレベル(需要予測主導)
    2. 最終組立(注文主導)
  • 計画用部品表
  • 最終組立スケジュール(FAS)
class AssembleToOrderMPS:
    """ATO with planning bills and final assembly schedule"""

    def __init__(self, planning_bill):
        """
        planning_bill: dict {option: {component: percentage}}

        Example:
        {
            'Base_Model': {'Frame': 1.0, 'Engine_A': 0.6, 'Engine_B': 0.4},
            ...
        }
        """
        self.planning_bill = planning_bill

    def generate_component_mps(self, demand_forecast, planning_item):
        """
        Generate MPS for components based on option percentages

        Parameters:
        - demand_forecast: forecast for planning item
        - planning_item: aggregate product family
        """

        component_forecast = {}

        for component, percentage in self.planning_bill[planning_item].items():
            component_forecast[component] = [
                qty * percentage for qty in demand_forecast
            ]

        return component_forecast

    def create_final_assembly_schedule(self, customer_orders,
                                      assembly_lead_time):
        """
        Create FAS from customer orders

        FAS specifies exact configuration to build
        """

        fas = []

        for order in customer_orders:
            config = order['configuration']
            quantity = order['quantity']
            due_date = order['due_date']

            start_date = max(1, due_date - assembly_lead_time)

            fas.append({
                'order_id': order['order_id'],
                'configuration': config,
                'quantity': quantity,
                'start_period': start_date,
                'due_period': due_date
            })

        return pd.DataFrame(fas)

# Example
planning_bill = {
    'Laptop': {
        'Base_Unit': 1.0,
        'CPU_i5': 0.6,
        'CPU_i7': 0.4,
        'RAM_8GB': 0.5,
        'RAM_16GB': 0.5,
        'SSD_256GB': 0.7,
        'SSD_512GB': 0.3
    }
}

ato = AssembleToOrderMPS(planning_bill)

# Forecast for planning item
laptop_forecast = [100, 110, 105, 120, 115, 125]

# Generate component MPS
component_mps = ato.generate_component_mps(laptop_forecast, 'Laptop')
print("Component MPS (Forecast-Driven):")
for component, forecast in component_mps.items():
    print(f"  {component}: {forecast[:4]}")

# Customer orders with specific configurations
orders = [
    {
        'order_id': 'ORD-001',
        'configuration': 'i7/16GB/512GB',
        'quantity': 50,
        'due_date': 3
    },
    {
        'order_id': 'ORD-002',
        'configuration': 'i5/8GB/256GB',
        'quantity': 75,
        'due_date': 5
    }
]

# Final assembly schedule
fas = ato.create_final_assembly_schedule(orders, assembly_lead_time=1)
print("\nFinal Assembly Schedule (Order-Driven):")
print(fas)

MPS の最適化手法

平準化生産

目的: 変動を最小限に抑えるため、生産を平準化します

アプローチ:

  • 平均需要を算出します
  • 一定の生産率で生産します
  • 在庫をバッファーとして使用します
def level_loading_mps(total_demand, periods, lot_size=0):
    """
    Level loading - produce at constant rate

    Parameters:
    - total_demand: total demand over horizon
    - periods: number of periods
    - lot_size: production lot size (0 = continuous)
    """

    # Calculate average per period
    avg_demand = total_demand / periods

    # Round to lot size if applicable
    if lot_size > 0:
        level_quantity = int(np.ceil(avg_demand / lot_size)) * lot_size
    else:
        level_quantity = avg_demand

    # Create level schedule
    mps = [level_quantity] * periods

    return mps

# Example
total_demand = 1200
periods = 10

level_mps = level_loading_mps(total_demand, periods, lot_size=50)
print(f"Level Loading MPS: {level_mps}")
print(f"Average production per period: {sum(level_mps)/len(level_mps):.0f}")
追従戦略

目的: 生産を需要に正確に合わせます

アプローチ:

  • 需要に応じて生産率を変動させます
  • 在庫を最小限に抑えます
  • コストが増加します(採用/解雇、段取り)
def chase_strategy_mps(demand_forecast, lot_size=0):
    """
    Chase strategy - match production to demand

    Parameters:
    - demand_forecast: demand by period
    - lot_size: minimum production quantity
    """

    mps = []

    for demand in demand_forecast:
        if lot_size > 0:
            # Round up to lot size
            quantity = int(np.ceil(demand / lot_size)) * lot_size
        else:
            quantity = demand

        mps.append(quantity)

    return mps

# Example
demand = [100, 120, 90, 150, 110, 130, 95, 140]
chase_mps = chase_strategy_mps(demand, lot_size=50)
print(f"Chase Strategy MPS: {chase_mps}")
混合戦略(ハイブリッド)

目的: 在庫コストと生産変動のバランスを取ります

アプローチ:

  • 基礎需要には平準化生産で対応します
  • 需要のピークには、残業、外注、または在庫で対応します
from pulp import *

def mixed_strategy_mps(demand, costs, capacity_normal, capacity_overtime):
    """
    Mixed strategy optimization

    Minimize total cost of production + inventory + backorders

    Parameters:
    - demand: list of demand by period
    - costs: dict {'regular': x, 'overtime': y, 'inventory': z, 'backorder': w}
    - capacity_normal: regular capacity per period
    - capacity_overtime: max overtime capacity per period
    """

    periods = len(demand)

    # Create problem
    prob = LpProblem("Mixed_Strategy_MPS", LpMinimize)

    # Variables
    P = LpVariable.dicts("Regular", range(periods), lowBound=0)
    O = LpVariable.dicts("Overtime", range(periods), lowBound=0)
    I = LpVariable.dicts("Inventory", range(periods), lowBound=0)
    B = LpVariable.dicts("Backorder", range(periods), lowBound=0)

    # Objective
    prob += lpSum([
        costs['regular'] * P[t] +
        costs['overtime'] * O[t] +
        costs['inventory'] * I[t] +
        costs['backorder'] * B[t]
        for t in range(periods)
    ])

    # Constraints
    for t in range(periods):
        # Capacity
        prob += P[t] <= capacity_normal
        prob += O[t] <= capacity_overtime

        # Inventory balance
        if t == 0:
            prob += I[t] == P[t] + O[t] - demand[t] + B[t]
        else:
            prob += I[t] == I[t-1] + P[t] + O[t] - demand[t] + B[t] - B[t-1]

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    # Extract solution
    solution = {
        'regular': [P[t].varValue for t in range(periods)],
        'overtime': [O[t].varValue for t in range(periods)],
        'inventory': [I[t].varValue for t in range(periods)],
        'backorder': [B[t].varValue for t in range(periods)],
        'total_cost': value(prob.objective)
    }

    return solution

# Example
demand = [100, 120, 150, 180, 160, 140, 120, 110]

costs = {
    'regular': 50,
    'overtime': 75,
    'inventory': 5,
    'backorder': 100
}

mixed_mps = mixed_strategy_mps(
    demand=demand,
    costs=costs,
    capacity_normal=120,
    capacity_overtime=40
)

print("Mixed Strategy MPS:")
print(f"Total Cost: ${mixed_mps['total_cost']:,.0f}")
print(f"Regular Production: {[int(x) for x in mixed_mps['regular']]}")
print(f"Overtime Production: {[int(x) for x in mixed_mps['overtime']]}")
print(f"Ending Inventory: {[int(x) for x in mixed_mps['inventory']]}")

MPS のパフォーマンス指標

スケジュール遵守率

MPS 達成率:

Actual Production / Planned Production × 100%

目標:>95%

在庫指標

在庫回転率:

COGS / Average Inventory

在庫日数:

(Average Inventory / Daily Demand) × Days
顧客サービス

納期遵守率:

Orders Delivered On-Time / Total Orders × 100%

目標:>95%

注文充足率:

Orders Filled Complete / Total Orders × 100%
計画指標

計画の不安定性:

  • スケジュールの不安定さを測定します
  • 確定済み MPS の変更回数

ATP の精度:

  • 約束した納期と実際の納入実績の比較
class MPSMetrics:
    """Track MPS performance metrics"""

    def __init__(self):
        self.periods = []

    def add_period(self, planned, actual, inventory, orders_on_time, total_orders):
        """Record period performance"""

        self.periods.append({
            'planned_production': planned,
            'actual_production': actual,
            'ending_inventory': inventory,
            'orders_on_time': orders_on_time,
            'total_orders': total_orders
        })

    def calculate_metrics(self):
        """Calculate summary metrics"""

        df = pd.DataFrame(self.periods)

        metrics = {
            'mps_attainment': (df['actual_production'].sum() /
                              df['planned_production'].sum() * 100),
            'avg_inventory': df['ending_inventory'].mean(),
            'on_time_delivery': (df['orders_on_time'].sum() /
                                df['total_orders'].sum() * 100)
        }

        return metrics

# Example
metrics = MPSMetrics()

# Add several periods
for i in range(6):
    metrics.add_period(
        planned=200,
        actual=np.random.randint(185, 205),
        inventory=np.random.randint(80, 120),
        orders_on_time=np.random.randint(18, 20),
        total_orders=20
    )

summary = metrics.calculate_metrics()
print("MPS Performance Metrics:")
for metric, value in summary.items():
    print(f"  {metric}: {value:.1f}")

ツールとライブラリ

Python ライブラリ

最適化:

  • pulp:MPS 最適化のための線形計画法
  • pyomo:高度な最適化
  • scipy.optimize:最適化アルゴリズム

データ管理:

  • pandas:時系列データとデータ操作
  • numpy:数値計算

可視化:

  • matplotlib、seaborn:ガントチャート、能力プロファイル
  • plotly:インタラクティブなスケジュール
商用 MPS ソフトウェア

MPS を備えた ERP システム:

  • SAP:MRP/MPS モジュール
  • Oracle:高度なサプライチェーン計画
  • Microsoft Dynamics:マスタープランニング
  • Infor:生産計画

専門的な計画ソフトウェア:

  • Kinaxis RapidResponse:リアルタイム計画
  • Blue Yonder:需要主導型 MPS
  • o9 Solutions:AI を活用した計画
  • Anaplan:クラウドベースの計画

製造現場との連携:

  • Plex:製造業向け MPS
  • IQMS:製造業向け MRP/MPS
  • Epicor:生産管理

よくある課題と解決策

課題:確定期間が短すぎる

問題:

  • スケジュールが常に変更されます
  • 生産が混乱します
  • 達成率が低くなります

解決策:

  • 確定期間を延長します(通常 4~6 週間)
  • リードタイムを短縮し、必要な確定期間を短くします
  • 需要予測の精度を向上させます
  • 変更管理ポリシーを徹底します
  • 安全在庫でバッファーを確保します
課題:能力の過負荷

問題:

  • RCCP で能力不足が示されます
  • MPS を達成できません

解決策:

  • シフトや残業を追加します
  • 外注/下請けを利用します
  • 需要を削減します(割り当て)
  • リードタイムを延長します
  • 設備投資を行います(長期的な対策)
課題:スケジュール遵守率が低い

問題:

  • 実際の生産が MPS と一致しません
  • その場しのぎの対応や特急対応が発生します

解決策:

  • 製造現場の実行力を向上させます
  • 資材の可用性を高めます(MRP)
  • 変動を抑えます(品質、稼働時間)
  • 現実的な MPS を作成します(過剰な計画を避けます)
  • 未達の根本原因を分析します
課題:MPS のロットサイズのばらつき

問題:

  • ロットサイズが大きいと、需要のピークと谷が生じます
  • 能力が大きく変動します
  • 在庫が積み上がります

解決策:

  • 段取り時間を短縮します(SMED)
  • ロットサイズを小さくします
  • 平準化生産方式を採用します
  • 混合モデル生産スケジューリングを行います
  • リーン生産方式の原則を適用します
課題:計画の不安定化(変更が多すぎる)

問題:

  • MPS が頻繁に変更されます
  • プランナーの信頼性に問題が生じます
  • 現場が混乱します

解決策:

  • タイムフェンスを厳格化します
  • 予測精度を向上させます
  • 需要をフィルタリング/平滑化します
  • 顧客の期待を管理します
  • S&OP プロセスを改善します

出力形式

MPS レポートの構成

MPS 計画表:

週予測顧客受注総需要予測可能在庫MPSATPゾーン
11001501505020050確定
211013013012000確定
3105801051510020確定
412060120-5200140準確定
511501158000準確定
61250125-45200200変更可能

能力負荷の概要:

ワークセンター週 1週 2週 3週 4使用可能能力状態
組立145120130160160OK
試験958590110120OK
梱包7060658080OK

先週からの MPS 変更:

品目週旧数量新数量変更量ゾーン理由
Product_A4150200+50準確定新規受注
Product_B61000-100変更可能予測を下方修正

ATP の概要:

品目週 1週 2週 3週 4ATP 合計
Product_A50020140210
Product_B3000100130

例外レポート:

  • 納期超過受注:納期を過ぎた受注の一覧
  • 能力超過:負荷が 100% を超えるリソース
  • 資材不足:使用可能な部品がない状態
  • スケジュール変更:確定ゾーン内の変更

確認事項

さらに詳しい情報が必要な場合:

  1. どのような製造環境ですか?(MTS、MTO、ATO、ETO)
  2. 生産プロセスはどのようなものですか?(個別生産、プロセス生産、バッチ生産)
  3. 計画期間とバケットの単位はどのようになっていますか?(週、月)
  4. スケジュール対象の最終品目はいくつありますか?
  5. 現在の MPS プロセスとツールはどのようなものですか?
  6. 重要な能力制約はありますか?
  7. タイムフェンスの方針はどのようなものですか?
  8. スケジュール遵守率はどのくらいですか?

関連スキル

  • sales-operations-planning:総合生産計画への入力
  • capacity-planning:能力分析と RCCP
  • demand-forecasting:MPS への需要入力
  • production-scheduling:現場の詳細なスケジューリング
  • inventory-optimization:安全在庫とロットサイズの最適化
  • lot-sizing-problems:MPS の数量決定
  • supply-chain-analytics:MPS のパフォーマンス追跡
  • lean-manufacturing:段取り時間の短縮と生産平準化

コメント