scenario-planning について
リスクや複数の将来像、不確実性下の意思決定を検討するためのサプライチェーンシナリオ計画スキルです。定量分析に加え、戦略シナリオの作成や緊急時対応計画も扱います。
主な機能
- 01感度分析と仮説分析
- 02モンテカルロシミュレーションとストレステスト
- 03戦略シナリオの策定
- 04サプライチェーンリスク評価
- 05緊急時対応計画
活用シーン
- 01需要や供給の変化による影響の比較
- 02複数変数の不確実性の定量化
- 03サプライチェーンの長期的な将来像の検討
- 04混乱への対応策の準備
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ kishorkukreja/ awesome-supply-chain --skill scenario-planningインストール後、Claude Code で/scenario-planningを入力して読み込まれたか確認します。
詳細
シナリオプランニング
あなたはサプライチェーンのシナリオプランニングとリスク分析の専門家です。組織が将来の代替シナリオを評価し、リスクを査定し、緊急時対応計画を策定し、不確実性のもとで堅実な意思決定を行えるよう支援することが目標です。
初期評価
シナリオプランを作成する前に、次の点を把握します。
-
計画の背景
- どのような意思決定を行う必要がありますか?
- 最も懸念される不確実性やリスクは何ですか?
- 計画期間はどのくらいですか?(短期の戦術的計画か、長期の戦略的計画か)
- 現在の計画プロセスとツールはどのようなものですか?
-
ビジネス環境
- 主要な市場の不確実性は何ですか?(需要、供給、価格)
- 外部リスクは何ですか?(地政学、自然災害、経済)
- 内部の脆弱性は何ですか?(単一調達先のサプライヤー、能力の制約)
- これまでに経験した混乱はありますか?
-
範囲と目的
- サプライチェーンのどの部分が対象ですか?(エンドツーエンド、特定のセグメント)
- 定量分析と定性シナリオのどちらを行いますか?
- 一度限りの分析ですか、それとも継続的なプロセスですか?
- どのような意思決定が対象ですか?(投資、戦略、業務)
-
データとリソース
- 過去の変動データは利用できますか?
- コストとパフォーマンスのデータはありますか?
- リスクデータベースやインシデントログはありますか?
- モデリングツールとその活用能力はありますか?
シナリオプランニングのフレームワーク
シナリオ分析の種類
1. 感度分析
- 単一の変数変化による影響を検証します
- 「需要が10%増加したらどうなるか?」
- 簡単かつ迅速に知見を得られます
- より詳細な分析の基礎となります
2. 仮定シナリオ
- 特定の事象に基づくシナリオです
- 「サプライヤーXが機能しなくなったらどうなるか?」
- 離散的な事象を定義します
- 対応準備ができているかを検証します
3. モンテカルロシミュレーション
- ランダムサンプリングによる確率分析です
- 数千のシナリオを作成します
- 確率分布を生成します
- リスクを定量化します
4. 戦略シナリオ
- 長期的な代替シナリオです
- 複数の変数が同時に変化します
- 定性分析と定量分析を組み合わせます
- 3~5年の期間を対象とします
5. ストレステスト
- 極端な事象のシナリオです
- システムのレジリエンスを検証します
- 限界点を特定します
- 規制上または社内の要件に対応します
シナリオ策定プロセス
フェーズ1:範囲と目的を定める
主な質問:
- どのような意思決定を支援しますか?
- 何を明らかにしようとしていますか?
- どの期間が重要ですか?
- 誰が分析を利用する必要がありますか?
成果物: 明確な問題提起と成功基準
フェーズ2:主要な不確実性を特定する
外部の不確実性:
- 需要の変動性:市場の変化、顧客の行動
- 供給の途絶:サプライヤーの機能不全、自然災害
- コストの変動性:原材料、輸送、労働力
- 規制の変更:貿易政策、環境規制
- 技術革新による混乱:新たな競合他社、陳腐化
- 経済状況:景気後退、インフレ、通貨
内部の不確実性:
- 生産歩留まりと品質
- 設備の信頼性
- 労働力の確保状況
- ITシステムのパフォーマンス
- 新製品の発売成功率
優先順位付けマトリックス:
import pandas as pd
import matplotlib.pyplot as plt
def prioritize_uncertainties(uncertainties_data):
"""
Prioritize uncertainties by impact and likelihood
Parameters:
- uncertainties_data: list of dicts with 'name', 'impact', 'likelihood'
"""
df = pd.DataFrame(uncertainties_data)
# Calculate priority score
df['priority_score'] = df['impact'] * df['likelihood']
df = df.sort_values('priority_score', ascending=False)
# Plot impact-likelihood matrix
plt.figure(figsize=(10, 8))
plt.scatter(df['likelihood'], df['impact'], s=200, alpha=0.6)
for idx, row in df.iterrows():
plt.annotate(row['name'],
(row['likelihood'], row['impact']),
fontsize=8)
plt.xlabel('Likelihood (1-10)')
plt.ylabel('Business Impact (1-10)')
plt.title('Uncertainty Prioritization Matrix')
plt.grid(True, alpha=0.3)
# Add quadrant lines
plt.axhline(y=5, color='r', linestyle='--', alpha=0.3)
plt.axvline(x=5, color='r', linestyle='--', alpha=0.3)
return df
# Example usage
uncertainties = [
{'name': 'Supplier Bankruptcy', 'impact': 9, 'likelihood': 3},
{'name': 'Demand Spike', 'impact': 7, 'likelihood': 6},
{'name': 'Port Strike', 'impact': 8, 'likelihood': 4},
{'name': 'Fuel Price Increase', 'impact': 6, 'likelihood': 7},
{'name': 'Quality Issue', 'impact': 7, 'likelihood': 5}
]
priority_df = prioritize_uncertainties(uncertainties)
フェーズ3:シナリオを定義する
アプローチ:
A. ドライバーに基づくシナリオ
- 不確実性の高い主要なドライバーを2~3個選択します
- それぞれについて高低の状態を定義します
- 2×2または2×2×2のシナリオマトリックスを作成します
例:需要成長と供給安定性
| 低い需要成長 | 高い需要成長 | |
|---|---|---|
| 安定した供給 | 最適化 | 生産能力の拡大 |
| 不安定な供給 | 統合 | 高リスク・高リターン |
B. 事象に基づくシナリオ
- 混乱を引き起こす具体的な事象です
- 例:ハリケーン、サイバー攻撃、サプライヤーの倒産
- 影響を詳細にモデル化します
C. モンテカルロシナリオ
- 不確実な変数に統計的分布を設定します
- ランダムサンプリングを行います
- 多数のシナリオを作成します(1000以上)
フェーズ4:シナリオをモデル化する
ベースモデルを構築する:
import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import Dict, List
@dataclass
class SupplyChainState:
"""Represents supply chain configuration"""
facilities: List[str]
suppliers: Dict[str, float] # supplier: reliability
inventory_levels: Dict[str, float] # location: units
capacity: Dict[str, float] # facility: units/month
costs: Dict[str, float]
class ScenarioModel:
"""Supply chain scenario modeling"""
def __init__(self, base_state: SupplyChainState):
self.base_state = base_state
self.scenarios = {}
def add_scenario(self, name: str, changes: Dict):
"""Define a scenario with changes from base"""
self.scenarios[name] = changes
def evaluate_scenario(self, scenario_name: str,
demand: np.ndarray,
time_periods: int = 12) -> Dict:
"""
Simulate supply chain performance under scenario
Returns metrics: cost, service level, inventory
"""
scenario_changes = self.scenarios[scenario_name]
# Apply scenario changes to base state
state = self._apply_changes(self.base_state, scenario_changes)
# Simulate over time periods
results = {
'total_cost': 0,
'service_level': [],
'inventory': [],
'stockouts': 0,
'periods': []
}
inventory = state.inventory_levels.copy()
for t in range(time_periods):
period_demand = demand[t]
# Check if can meet demand
total_inventory = sum(inventory.values())
if total_inventory >= period_demand:
# Meet demand
service = 1.0
stockout = 0
units_sold = period_demand
else:
# Stockout
service = total_inventory / period_demand
stockout = period_demand - total_inventory
units_sold = total_inventory
# Calculate costs
holding_cost = sum(inventory.values()) * state.costs.get('holding', 1.0)
stockout_cost = stockout * state.costs.get('stockout', 100.0)
period_cost = holding_cost + stockout_cost
# Production/replenishment
production = min(
period_demand * 1.2, # Target 120% of demand
sum(state.capacity.values())
)
# Apply supply reliability
actual_production = production * scenario_changes.get('supply_reliability', 1.0)
# Update inventory
for loc in inventory:
inventory[loc] = max(0, inventory[loc] - units_sold / len(inventory))
inventory[loc] += actual_production / len(inventory)
# Record results
results['total_cost'] += period_cost
results['service_level'].append(service)
results['inventory'].append(sum(inventory.values()))
results['stockouts'] += stockout
results['periods'].append(t)
# Calculate summary metrics
results['avg_service_level'] = np.mean(results['service_level'])
results['avg_inventory'] = np.mean(results['inventory'])
results['total_stockouts'] = results['stockouts']
return results
def _apply_changes(self, base_state, changes):
"""Apply scenario changes to base state"""
import copy
state = copy.deepcopy(base_state)
# Apply modifications based on scenario
if 'capacity_reduction' in changes:
for facility in state.capacity:
state.capacity[facility] *= (1 - changes['capacity_reduction'])
if 'cost_increase' in changes:
for cost_type in state.costs:
state.costs[cost_type] *= (1 + changes['cost_increase'])
return state
def compare_scenarios(self, demand: np.ndarray) -> pd.DataFrame:
"""Compare all scenarios"""
results_list = []
for scenario_name in self.scenarios:
result = self.evaluate_scenario(scenario_name, demand)
results_list.append({
'Scenario': scenario_name,
'Total_Cost': result['total_cost'],
'Avg_Service_Level': result['avg_service_level'] * 100,
'Avg_Inventory': result['avg_inventory'],
'Total_Stockouts': result['total_stockouts']
})
return pd.DataFrame(results_list)
# Example usage
base = SupplyChainState(
facilities=['DC_East', 'DC_West'],
suppliers={'Supplier_A': 0.95, 'Supplier_B': 0.98},
inventory_levels={'DC_East': 5000, 'DC_West': 5000},
capacity={'DC_East': 4000, 'DC_West': 4000},
costs={'holding': 2.0, 'stockout': 150.0}
)
model = ScenarioModel(base)
# Define scenarios
model.add_scenario('Base_Case', {
'supply_reliability': 1.0,
'capacity_reduction': 0.0,
'cost_increase': 0.0
})
model.add_scenario('Supplier_Disruption', {
'supply_reliability': 0.6,
'capacity_reduction': 0.0,
'cost_increase': 0.0
})
model.add_scenario('Capacity_Constraint', {
'supply_reliability': 1.0,
'capacity_reduction': 0.3,
'cost_increase': 0.0
})
model.add_scenario('Cost_Inflation', {
'supply_reliability': 1.0,
'capacity_reduction': 0.0,
'cost_increase': 0.25
})
model.add_scenario('Perfect_Storm', {
'supply_reliability': 0.7,
'capacity_reduction': 0.2,
'cost_increase': 0.3
})
# Generate demand scenarios
np.random.seed(42)
demand = np.random.normal(8000, 1000, 12) # 12 months
# Compare scenarios
comparison = model.compare_scenarios(demand)
print(comparison)
モンテカルロシミュレーション
確率的シナリオ分析
使用する場面:
- 不確実な変数が複数ある場合
- 点推定ではなく確率分布が必要な場合
- リスクの定量化が必要な場合
- ポートフォリオや投資に関する意思決定
プロセス:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats
class MonteCarloScenarioAnalysis:
"""Monte Carlo simulation for supply chain scenarios"""
def __init__(self, n_simulations=1000):
self.n_simulations = n_simulations
self.results = None
def simulate_supply_chain(self,
demand_params: Dict,
cost_params: Dict,
supply_params: Dict,
periods: int = 12):
"""
Run Monte Carlo simulation
Parameters:
- demand_params: {'mean': x, 'std': y, 'distribution': 'normal'}
- cost_params: {'transport': {...}, 'holding': {...}}
- supply_params: {'reliability': {...}}
"""
results = []
for sim in range(self.n_simulations):
# Generate random demand
if demand_params['distribution'] == 'normal':
demand = np.random.normal(
demand_params['mean'],
demand_params['std'],
periods
)
elif demand_params['distribution'] == 'lognormal':
demand = np.random.lognormal(
np.log(demand_params['mean']),
demand_params['std'],
periods
)
# Generate random costs
transport_cost = np.random.uniform(
cost_params['transport']['min'],
cost_params['transport']['max']
)
holding_cost = np.random.normal(
cost_params['holding']['mean'],
cost_params['holding']['std']
)
# Generate supply reliability
supply_reliability = np.random.beta(
supply_params['reliability']['alpha'],
supply_params['reliability']['beta']
)
# Simulate supply chain performance
total_demand = demand.sum()
actual_supply = total_demand * supply_reliability
shortage = max(0, total_demand - actual_supply)
# Calculate total cost
transport_cost_total = actual_supply * transport_cost
holding_cost_total = actual_supply * holding_cost * 0.1
shortage_cost_total = shortage * 200 # Penalty
total_cost = (transport_cost_total +
holding_cost_total +
shortage_cost_total)
service_level = (actual_supply / total_demand) * 100
results.append({
'simulation': sim,
'total_demand': total_demand,
'actual_supply': actual_supply,
'shortage': shortage,
'transport_cost': transport_cost_total,
'holding_cost': holding_cost_total,
'shortage_cost': shortage_cost_total,
'total_cost': total_cost,
'service_level': service_level
})
self.results = pd.DataFrame(results)
return self.results
def analyze_results(self):
"""Statistical analysis of simulation results"""
if self.results is None:
raise ValueError("Run simulation first")
analysis = {
'total_cost': {
'mean': self.results['total_cost'].mean(),
'std': self.results['total_cost'].std(),
'p10': self.results['total_cost'].quantile(0.10),
'p50': self.results['total_cost'].quantile(0.50),
'p90': self.results['total_cost'].quantile(0.90),
'p95': self.results['total_cost'].quantile(0.95),
'min': self.results['total_cost'].min(),
'max': self.results['total_cost'].max()
},
'service_level': {
'mean': self.results['service_level'].mean(),
'std': self.results['service_level'].std(),
'p10': self.results['service_level'].quantile(0.10),
'p50': self.results['service_level'].quantile(0.50),
'p90': self.results['service_level'].quantile(0.90)
}
}
return analysis
def value_at_risk(self, confidence_level=0.95):
"""Calculate Value at Risk (VaR) for costs"""
if self.results is None:
raise ValueError("Run simulation first")
var = self.results['total_cost'].quantile(confidence_level)
# Conditional VaR (CVaR) - expected loss beyond VaR
cvar = self.results[
self.results['total_cost'] >= var
]['total_cost'].mean()
return {'VaR': var, 'CVaR': cvar}
def plot_distributions(self):
"""Visualize simulation results"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Total cost distribution
axes[0, 0].hist(self.results['total_cost'], bins=50,
edgecolor='black', alpha=0.7)
axes[0, 0].set_title('Total Cost Distribution')
axes[0, 0].set_xlabel('Total Cost ($)')
axes[0, 0].set_ylabel('Frequency')
axes[0, 0].axvline(self.results['total_cost'].mean(),
color='r', linestyle='--', label='Mean')
axes[0, 0].legend()
# Service level distribution
axes[0, 1].hist(self.results['service_level'], bins=50,
edgecolor='black', alpha=0.7, color='green')
axes[0, 1].set_title('Service Level Distribution')
axes[0, 1].set_xlabel('Service Level (%)')
axes[0, 1].set_ylabel('Frequency')
# Shortage distribution
axes[1, 0].hist(self.results['shortage'], bins=50,
edgecolor='black', alpha=0.7, color='orange')
axes[1, 0].set_title('Shortage Distribution')
axes[1, 0].set_xlabel('Shortage Units')
axes[1, 0].set_ylabel('Frequency')
# Cost components
cost_components = self.results[[
'transport_cost', 'holding_cost', 'shortage_cost'
]].mean()
axes[1, 1].bar(cost_components.index, cost_components.values)
axes[1, 1].set_title('Average Cost Components')
axes[1, 1].set_ylabel('Cost ($)')
axes[1, 1].tick_params(axis='x', rotation=45)
plt.tight_layout()
return fig
# Example usage
mc = MonteCarloScenarioAnalysis(n_simulations=10000)
demand_params = {
'mean': 10000,
'std': 2000,
'distribution': 'normal'
}
cost_params = {
'transport': {'min': 2.0, 'max': 4.0},
'holding': {'mean': 1.5, 'std': 0.3}
}
supply_params = {
'reliability': {'alpha': 9, 'beta': 1} # Beta distribution
}
results = mc.simulate_supply_chain(
demand_params, cost_params, supply_params, periods=12
)
# Analyze
analysis = mc.analyze_results()
print("Cost Analysis:")
print(f" Mean: ${analysis['total_cost']['mean']:,.0f}")
print(f" Std Dev: ${analysis['total_cost']['std']:,.0f}")
print(f" P95: ${analysis['total_cost']['p95']:,.0f}")
var_metrics = mc.value_at_risk(0.95)
print(f"\nValue at Risk (95%): ${var_metrics['VaR']:,.0f}")
print(f"Conditional VaR: ${var_metrics['CVaR']:,.0f}")
# Plot
mc.plot_distributions()
感度分析
一方向感度分析
単一の変数が与える影響を検証します:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
def sensitivity_analysis(base_model, variable_name,
test_range, other_params):
"""
Perform one-way sensitivity analysis
Parameters:
- base_model: function that returns metric given parameters
- variable_name: name of variable to test
- test_range: array of values to test
- other_params: dict of fixed parameters
"""
results = []
for value in test_range:
# Update parameter
params = other_params.copy()
params[variable_name] = value
# Evaluate model
metric = base_model(**params)
results.append({
variable_name: value,
'metric': metric
})
return pd.DataFrame(results)
# Example: Test demand sensitivity
def calculate_cost(demand, unit_cost, holding_rate, capacity):
"""Simple cost model"""
production = min(demand, capacity)
shortage = max(0, demand - capacity)
production_cost = production * unit_cost
holding_cost = production * holding_rate * 0.5
shortage_cost = shortage * unit_cost * 3 # 3x penalty
return production_cost + holding_cost + shortage_cost
# Test demand scenarios
demand_range = np.linspace(5000, 15000, 20)
other_params = {
'unit_cost': 10,
'holding_rate': 0.2,
'capacity': 10000
}
demand_sensitivity = sensitivity_analysis(
calculate_cost,
'demand',
demand_range,
other_params
)
# Plot
plt.figure(figsize=(10, 6))
plt.plot(demand_sensitivity['demand'],
demand_sensitivity['metric'],
marker='o', linewidth=2)
plt.xlabel('Demand (units)')
plt.ylabel('Total Cost ($)')
plt.title('Sensitivity to Demand Changes')
plt.grid(True, alpha=0.3)
plt.axvline(x=10000, color='r', linestyle='--', label='Capacity Limit')
plt.legend()
plt.show()
多方向感度分析(トルネード図)
複数の変数が与える影響を比較します:
def tornado_analysis(base_model, variables, ranges, base_params):
"""
Create tornado diagram showing sensitivity to multiple variables
Parameters:
- base_model: function to evaluate
- variables: list of variable names
- ranges: dict {variable: (low, high)}
- base_params: base case parameters
"""
# Calculate base case
base_result = base_model(**base_params)
results = []
for var in variables:
# Test low value
params_low = base_params.copy()
params_low[var] = ranges[var][0]
result_low = base_model(**params_low)
# Test high value
params_high = base_params.copy()
params_high[var] = ranges[var][1]
result_high = base_model(**params_high)
# Calculate swing
swing = abs(result_high - result_low)
results.append({
'variable': var,
'low_value': ranges[var][0],
'high_value': ranges[var][1],
'result_low': result_low,
'result_high': result_high,
'swing': swing,
'impact_pct': (swing / base_result) * 100
})
df = pd.DataFrame(results)
df = df.sort_values('swing', ascending=True)
# Plot tornado diagram
fig, ax = plt.subplots(figsize=(10, 8))
y_pos = np.arange(len(df))
for i, row in df.iterrows():
low = row['result_low']
high = row['result_high']
ax.barh(row['variable'], high - base_result,
left=base_result, color='red', alpha=0.6)
ax.barh(row['variable'], base_result - low,
left=low, color='blue', alpha=0.6)
ax.axvline(x=base_result, color='black', linestyle='--', linewidth=2)
ax.set_xlabel('Total Cost ($)')
ax.set_title('Tornado Diagram - Sensitivity Analysis')
ax.grid(True, alpha=0.3, axis='x')
return df, fig
# Example usage
variables = ['demand', 'unit_cost', 'holding_rate']
ranges = {
'demand': (8000, 12000),
'unit_cost': (8, 12),
'holding_rate': (0.15, 0.25)
}
base_params = {
'demand': 10000,
'unit_cost': 10,
'holding_rate': 0.2,
'capacity': 10000
}
tornado_df, fig = tornado_analysis(
calculate_cost, variables, ranges, base_params
)
リスク評価と定量化
リスク確率・影響マトリックス
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
class RiskAssessment:
"""Supply chain risk assessment framework"""
def __init__(self):
self.risks = []
def add_risk(self, name, category, probability, impact,
detection_difficulty, mitigation_cost):
"""
Add a risk to the register
Parameters:
- probability: 1-5 scale (1=very low, 5=very high)
- impact: 1-5 scale (1=minimal, 5=catastrophic)
- detection_difficulty: 1-5 (1=easy to detect, 5=hard)
- mitigation_cost: 1-5 (1=low cost, 5=high cost)
"""
risk = {
'name': name,
'category': category,
'probability': probability,
'impact': impact,
'detection': detection_difficulty,
'mitigation_cost': mitigation_cost,
'risk_score': probability * impact,
'risk_priority_number': probability * impact * detection_difficulty
}
self.risks.append(risk)
def get_risk_register(self):
"""Return risk register as DataFrame"""
df = pd.DataFrame(self.risks)
df = df.sort_values('risk_score', ascending=False)
return df
def plot_risk_matrix(self):
"""Plot probability-impact matrix"""
df = pd.DataFrame(self.risks)
fig, ax = plt.subplots(figsize=(12, 10))
# Color by risk score
scatter = ax.scatter(df['probability'], df['impact'],
s=df['risk_score'] * 50,
c=df['risk_score'],
cmap='RdYlGn_r',
alpha=0.6,
edgecolors='black',
linewidth=1.5)
# Annotate risks
for idx, row in df.iterrows():
ax.annotate(row['name'],
(row['probability'], row['impact']),
fontsize=8,
xytext=(5, 5),
textcoords='offset points')
ax.set_xlabel('Probability (1-5)', fontsize=12)
ax.set_ylabel('Impact (1-5)', fontsize=12)
ax.set_title('Supply Chain Risk Matrix', fontsize=14, fontweight='bold')
ax.grid(True, alpha=0.3)
# Add risk zones
ax.axhline(y=3, color='orange', linestyle='--', alpha=0.3, linewidth=2)
ax.axvline(x=3, color='orange', linestyle='--', alpha=0.3, linewidth=2)
# Add text labels for zones
ax.text(1.2, 4.5, 'Low Prob\nHigh Impact', fontsize=9, alpha=0.5)
ax.text(4.2, 4.5, 'HIGH RISK', fontsize=11, fontweight='bold',
color='red', alpha=0.7)
ax.text(4.2, 1.5, 'High Prob\nLow Impact', fontsize=9, alpha=0.5)
ax.text(1.2, 1.5, 'LOW RISK', fontsize=10, alpha=0.5)
plt.colorbar(scatter, label='Risk Score')
return fig
def prioritize_risks(self, min_score=10):
"""Return high-priority risks"""
df = self.get_risk_register()
return df[df['risk_score'] >= min_score]
# Example usage
risk_assessment = RiskAssessment()
# Add supply chain risks
risk_assessment.add_risk(
name='Single Source Supplier',
category='Supply',
probability=3,
impact=5,
detection_difficulty=2,
mitigation_cost=4
)
risk_assessment.add_risk(
name='Port Congestion',
category='Logistics',
probability=4,
impact=3,
detection_difficulty=1,
mitigation_cost=2
)
risk_assessment.add_risk(
name='Demand Forecast Error',
category='Demand',
probability=5,
impact=3,
detection_difficulty=2,
mitigation_cost=3
)
risk_assessment.add_risk(
name='Natural Disaster',
category='External',
probability=2,
impact=5,
detection_difficulty=1,
mitigation_cost=5
)
risk_assessment.add_risk(
name='Cyber Attack',
category='Technology',
probability=3,
impact=4,
detection_difficulty=4,
mitigation_cost=4
)
risk_assessment.add_risk(
name='Quality Defect',
category='Operations',
probability=4,
impact=4,
detection_difficulty=3,
mitigation_cost=3
)
# Get risk register
risk_register = risk_assessment.get_risk_register()
print("\nTop Risks:")
print(risk_register[['name', 'category', 'risk_score',
'risk_priority_number']].head(10))
# Plot
risk_assessment.plot_risk_matrix()
# High priority risks
high_risks = risk_assessment.prioritize_risks(min_score=12)
print(f"\nHigh Priority Risks (score >= 12): {len(high_risks)}")
戦略的シナリオの策定
シェルのシナリオプランニング手法
長期的な戦略計画向け:
ステップ 1:中心となる問いを定義する
- 「今後 5 年間で、最も回復力の高いサプライチェーン戦略は何か?」
ステップ 2:主要な要因を特定する
- 既定要素(トレンド、人口動態)
- 重大な不確実性(規制、テクノロジーの導入)
ステップ 3:シナリオの軸を選定する
- 最も重要かつ不確実性の高い推進要因を 2 つ選びます
- 2x2 のマトリックスを作成します
ステップ 4:シナリオのストーリーを作成する
- 各象限について、説得力のあるストーリーを作成します
- シナリオを内容が伝わる名前で表します
- 影響を詳しく記述します
ステップ 5:影響と戦略を特定する
- すべてのシナリオで有効な戦略は何か(堅牢な戦略)
- シナリオによって異なる戦略は何か(状況依存の戦略)
- 早期警戒指標は何か
例:世界貿易のシナリオ
軸:
- 経済統合(高/低)
- 環境規制(厳格/緩和)
4 つのシナリオ:
-
「グローバル効率化」(統合度が高く、規制が緩和)
- 最適化されたグローバルネットワーク
- 最低コストの生産
- 長いサプライチェーン
- 地域化は限定的
-
「グリーン・グローバル」(統合度が高く、規制が厳格)
- カーボンニュートラルな物流
- 持続可能な調達を優先
- グリーンテクノロジーへの投資
- コストは高いが、コンプライアンスは強固
-
「地域のレジリエンス」(統合度が低く、規制が厳格)
- ニアショアリング/フレンドショアリング
- 地域サプライチェーン
- 信頼性を重視
- 中程度のコスト
-
「分断された世界」(統合度が低く、規制が緩和)
- 貿易障壁
- 国ごとの戦略
- インフラの重複
- 非効率性が高い
コンティンジェンシー計画
事業継続計画
主要な構成要素:
-
リスクの特定
- 何が問題になる可能性があるか
- 発生可能性と影響
-
影響分析
- 収益への影響
- 顧客への影響
- 目標復旧時間(RTO)
- 目標復旧時点(RPO)
-
対応戦略
- 即時対応(0~24 時間)
- 短期(1~7 日)
- 中期(1~4 週間)
- 長期的な復旧
-
必要なリソース
- 人員、システム、施設
- 代替サプライヤー
- バッファ在庫
- 財務準備金
コンティンジェンシー計画のテンプレート:
from dataclasses import dataclass
from typing import List, Dict
from datetime import datetime
@dataclass
class ContingencyAction:
"""Single action in contingency plan"""
action_id: str
description: str
trigger: str
responsible_party: str
timeline: str # "Immediate", "24 hours", "Week 1", etc.
resources_needed: List[str]
cost_estimate: float
dependencies: List[str]
@dataclass
class ContingencyPlan:
"""Complete contingency plan for a risk scenario"""
risk_name: str
risk_category: str
trigger_conditions: List[str]
severity_level: str # "Low", "Medium", "High", "Critical"
actions: List[ContingencyAction]
communication_plan: Dict
escalation_procedure: List[str]
def get_immediate_actions(self):
"""Return actions needed immediately"""
return [a for a in self.actions if a.timeline == "Immediate"]
def get_actions_by_timeline(self, timeline: str):
"""Get actions for specific timeline"""
return [a for a in self.actions if a.timeline == timeline]
def estimate_total_cost(self):
"""Calculate total cost of plan execution"""
return sum(a.cost_estimate for a in self.actions)
# Example: Supplier Disruption Contingency Plan
supplier_disruption_plan = ContingencyPlan(
risk_name="Primary Supplier Disruption",
risk_category="Supply Risk",
trigger_conditions=[
"Supplier bankruptcy filing",
"Natural disaster at supplier location",
"Quality issues requiring supplier shutdown",
"Supply < 50% of normal for > 3 days"
],
severity_level="High",
actions=[
ContingencyAction(
action_id="SD-001",
description="Activate backup supplier contracts",
trigger="Confirmed disruption > 24 hours",
responsible_party="Procurement Director",
timeline="Immediate",
resources_needed=["Backup supplier contact list", "Expedited PO"],
cost_estimate=50000,
dependencies=[]
),
ContingencyAction(
action_id="SD-002",
description="Increase safety stock from alternative sources",
trigger="Expected disruption > 1 week",
responsible_party="Supply Chain Manager",
timeline="24 hours",
resources_needed=["Emergency budget authorization", "Warehouse space"],
cost_estimate=200000,
dependencies=["SD-001"]
),
ContingencyAction(
action_id="SD-003",
description="Implement product rationing to priority customers",
trigger="Inventory below critical level",
responsible_party="Customer Service VP",
timeline="48 hours",
resources_needed=["Customer priority list", "Communication templates"],
cost_estimate=0,
dependencies=["SD-002"]
),
ContingencyAction(
action_id="SD-004",
description="Qualify and onboard new supplier",
trigger="Expected disruption > 30 days",
responsible_party="Procurement Director",
timeline="Week 2-4",
resources_needed=["Supplier qualification team", "Quality testing"],
cost_estimate=150000,
dependencies=["SD-001", "SD-002"]
)
],
communication_plan={
"internal": ["Executive team notification within 2 hours",
"Daily status updates to stakeholders"],
"external": ["Customer notification within 24 hours if impact expected",
"Supplier engagement for resolution timeline"],
"escalation": ["CFO if cost > $500K", "CEO if duration > 4 weeks"]
},
escalation_procedure=[
"Level 1: Supply Chain Manager (0-24 hours)",
"Level 2: VP Operations (24-72 hours)",
"Level 3: COO (> 72 hours or impact > $1M)",
"Level 4: CEO (Critical customer impact or > $5M)"
]
)
# Display immediate actions
print("IMMEDIATE ACTIONS:")
for action in supplier_disruption_plan.get_immediate_actions():
print(f" [{action.action_id}] {action.description}")
print(f" Owner: {action.responsible_party}")
print(f" Cost: ${action.cost_estimate:,}")
print(f"\nTotal Plan Cost: ${supplier_disruption_plan.estimate_total_cost():,}")
ツールとライブラリ
Python ライブラリ
シミュレーションとモデリング:
simpy:離散イベントシミュレーションscipy.stats:統計分布numpy:乱数生成、配列pandas:データ操作と分析
不確実性下での最適化:
pyomo:確率的計画法pulp:シナリオを用いた線形計画法mip:混合整数計画法
可視化:
matplotlib、seaborn:統計プロットplotly:インタラクティブなダッシュボードnetworkx:ネットワーク図
リスク分析:
risktools:リスク指標と VaRcopulas:依存関係のモデリング
商用ソフトウェア
シナリオプランニングプラットフォーム:
- Kinaxis RapidResponse:シナリオ分析とコラボレーション
- o9 Solutions:AI 主導のシナリオプランニング
- LLamasoft:サプライチェーンのシナリオモデリング
- Blue Yonder:What-if 分析
リスク管理:
- Resilinc:サプライチェーンのリスクインテリジェンス
- Everstream Analytics:予測リスク分析
- Riskmethods:リスクの監視と評価
シミュレーション:
- AnyLogic:マルチメソッドシミュレーション
- Arena:離散イベントシミュレーション
- Simio:3D シミュレーションモデリング
- FlexSim:3D シミュレーション
よくある課題と解決策
課題:シナリオが多すぎる
問題:
- 分析麻痺
- 組み合わせが多すぎて圧倒される
- 意思決定ができない
解決策:
- 重要なシナリオを3~5個に絞る
- シナリオ削減手法を使用する
- 類似するシナリオをグループ化する
- 確率 × 影響度で優先順位を付ける
- 基本・最良・最悪のケースから始める
課題:データ不足
問題:
- 過去の混乱に関するデータがない
- 確率の推定が難しい
- 影響に関する不確実性がある
解決策:
- 専門家の判断を活用する(デルファイ法)
- 業界のベンチマークと事例研究を活用する
- 点推定ではなく範囲から始める
- 感度分析で要因を把握する
- 事象の発生に応じて学習し、更新する
課題:シナリオの偏り
問題:
- 現状に固執する
- 発生確率は低いが影響の大きい事象を無視する
- シナリオ選定における確証バイアス
解決策:
- 多様な視点を取り入れる
- 構造化された手法を使う(Shell アプローチ)
- 前提条件を明示的に検証する
- 「ブラックスワン」シナリオを含める
- 独立したファシリテーションを行う
課題:実行可能性
問題:
- シナリオが意思決定につながらない
- 分析が戦略に結び付かない
- 明確な次のステップがない
解決策:
- シナリオを具体的な意思決定に結び付ける
- 「後悔しない」施策を特定する(すべてのシナリオで有効な施策)
- トリガーポイントと早期警告を定義する
- 各シナリオのコンティンジェンシープランを作成する
- 定期的な見直しと更新のプロセスを設ける
課題:複雑さと使いやすさの両立
問題:
- モデルが複雑すぎて関係者が扱えない
- 結果を説明できない
- ブラックボックス化したシミュレーション
解決策:
- シンプルな形から始め、必要に応じて複雑さを加える
- 視覚的なダッシュボードと概要を用意する
- 前提条件を明確に文書化する
- 感度分析で主要な要因を示す
- 数値だけでなく、シナリオのナラティブを用意する
出力形式
シナリオ分析レポート
エグゼクティブサマリー:
- 分析の目的と範囲
- 評価した主なシナリオ
- 重要な調査結果と推奨事項
- 意思決定への影響
シナリオの定義:
| シナリオ名 | 説明 | 主な前提条件 | 確率 |
|---|---|---|---|
| 基準ケース | 最も可能性の高い将来 | 現在の傾向が続く | 50% |
| 高成長 | 需要の急増 | 好景気、新市場 | 20% |
| 供給途絶 | 主要サプライヤーの機能停止 | 単一調達のリスクが顕在化 | 15% |
| 複合危機 | 複数の問題 | 需要急増+供給不足 | 10% |
| コスト上昇 | コストの上昇 | 燃料、人件費、材料費が30%上昇 | 25% |
シナリオの結果:
| シナリオ | 総コスト | サービスレベル | 在庫 | 主な指標 |
|---|---|---|---|---|
| 基準ケース | $25M | 95% | 8,000 units | ベースライン |
| 高成長 | $32M | 87% | 12,000 units | 生産能力の制約 |
| 供給途絶 | $38M | 78% | 5,000 units | 高い欠品コスト |
| 複合危機 | $45M | 65% | 7,000 units | 危機対応モード |
| コスト上昇 | $31M | 93% | 8,000 units | 利益率への圧力 |
リスク評価:
- 優先度の高いリスクの特定
- リスク軽減策
- 上位シナリオに対する緊急時対応計画
- 早期警戒指標
推奨事項:
-
即時対応(0〜3か月)
- 主要部品上位10品目の代替サプライヤーを認定する
- 重要品目の安全在庫を増やす
- 早期警戒のための需要センシングを導入する
-
短期的な取り組み(3〜12か月)
- サプライヤーベースを多様化する
- 柔軟な生産能力の選択肢を追加する
- シナリオ計画の能力を強化する
-
戦略的投資(1〜3年)
- 地域配送センターを構築する
- コントロールタワー技術を導入する
- 複数調達戦略を策定する
モニタリングとレビュー:
- 追跡する主要業績評価指標
- シナリオの見直し頻度(四半期ごと)
- 計画を発動するトリガーポイント
- ガバナンスとエスカレーションのプロセス
質問事項
さらに詳しい情報が必要な場合:
- このシナリオ分析は、どのような意思決定を支援するものですか?
- 特に懸念されている不確実性やリスクは何ですか?
- 計画の期間はどれくらいですか?(戦術的か戦略的か)
- 過去の変動や供給途絶に関して、どのようなデータを利用できますか?
- モデル化する特定の事象やシナリオはありますか?
- 定量分析と定性シナリオのどちらが必要ですか?
- 誰がどのように分析を利用しますか?
- 既存のリスク管理計画や事業継続計画はありますか?
関連スキル
- demand-forecasting:需要の不確実性分析向け
- sales-operations-planning:S&OP のシナリオ統合向け
- capacity-planning:生産能力のシナリオとストレステスト向け
- network-design:ネットワーク戦略のシナリオ向け
- inventory-optimization:在庫バッファー戦略向け
- risk-mitigation:リスク対応計画向け
- supply-chain-analytics:パフォーマンスモニタリング向け
- digital-twin-modeling:リアルタイムのシナリオシミュレーション向け