mcpField
F
freight-optimization

freight-optimization

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

freight-optimization について

輸送・物流担当者向けの貨物最適化スキルです。貨物やレーン、サービス要件を踏まえ、輸送モード、混載、運送会社の配分、積載計画、実績分析を支援します。

主な機能

  1. 01貨物と輸送ネットワークの評価
  2. 02輸送モードの比較
  3. 03貨物集約とミルクラン計画
  4. 04運送会社選定と入札分析
  5. 05トレーラー積載率の最適化

活用シーン

  1. 01LTLとトラックロードの費用比較
  2. 02小口貨物を集約して積載率を向上
  3. 03レーンとサービス実績に基づく運送会社選定
  4. 04貨物費用と実績の分析レポート作成

インストール

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

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill freight-optimization

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

詳細

貨物輸送の最適化

貨物輸送の最適化と物流の専門家です。すべての輸送モードで輸送コストを最小限に抑え、サービスレベルを向上させ、運送業者の選定を最適化しながら、納期遵守と貨物の可視化を確保することが目標です。

初期評価

貨物輸送業務を最適化する前に、以下を把握してください。

  1. 貨物の特性

    • 何を輸送していますか?(製品、重量、容積)
    • 一般的な出荷サイズはどの程度ですか?(小包、LTL、TL、コンテナ)
    • 特別な取り扱いが必要ですか?(温度管理、危険物、大型貨物)
    • 貨物の価値と保険の要件は何ですか?
  2. ネットワークと輸送レーン

    • 発地と着地はどこですか?
    • 主な輸送レーンはどこですか?
    • レーンごとの輸送頻度はどの程度ですか?(毎日、毎週、毎月)
    • レーンの輸送量は均衡していますか、それとも主に出荷が中心ですか?
  3. 現在の実績

    • 現在の貨物輸送費はいくらですか?(年間)
    • 1マイルあたり、または1出荷あたりのコストはいくらですか?
    • 納期遵守率はどの程度ですか?
    • 破損・クレーム率はどの程度ですか?
    • 運送業者の構成はどうなっていますか?(利用している運送業者の数)
  4. サービス要件

    • 輸送時間の要件は何ですか?
    • 配達時間帯や配達予約の要件はありますか?
    • 追跡と可視化に関するニーズはありますか?
    • 顧客サービスに関する期待は何ですか?

貨物輸送最適化のフレームワーク

輸送モード

1. トラックロード(TL/FTL)

  • 貨物専用のトラックを手配します
  • 発地から着地までの直行輸送です
  • 輸送が速く、荷扱いが少なくなります
  • コスト:約 1 マイルあたり $2.00~3.50(レーンにより異なります)
  • 最適な用途:24 パレット以上、36,000 ポンド以上、専用輸送

2. 混載トラック輸送(LTL)

  • 他の荷主とトラックの積載スペースを共有します
  • ハブ・アンド・スポーク型のネットワークを利用します
  • 荷扱いの拠点が複数あります
  • コスト:距離に応じて 1 cwt(100 ポンド)あたり約 $20~50
  • 最適な用途:1~23 パレット、500~36,000 ポンド

3. 小包

  • 小型の荷物(150 ポンド未満)
  • 広範なネットワークを利用します(UPS、FedEx、USPS)
  • ゾーンに基づく料金設定です
  • コスト:ゾーン/重量に応じて 1 個あたり $10~50
  • 最適な用途:個別商品、e コマース

4. インターモーダル(鉄道)

  • 鉄道とトラックを組み合わせます
  • 輸送は遅くなりますが、長距離では低コストです
  • コスト:トラック輸送より約 30~40%低くなります
  • 最適な用途:1,000 マイル以上、柔軟な納期

5. 航空貨物

  • 最速の輸送モードですが、コストは最も高くなります
  • コスト:1 ポンドあたり $2~8
  • 最適な用途:高価値、時間重視、国際輸送

6. 海上輸送(コンテナ)

  • 国際輸送に使用します
  • 20' または 40' コンテナ
  • コスト:コンテナ 1 個あたり $1,500~5,000(大きく変動します)
  • 最適な用途:国際輸送、大量輸送、柔軟な納期

輸送モードの選択と最適化

輸送モード選択の判断ツリー
import numpy as np
import pandas as pd

class FreightModeSelector:
    """
    Intelligent freight mode selection

    Recommends optimal transportation mode based on shipment characteristics
    """

    def __init__(self):
        # Rate structures (simplified benchmarks)
        self.rates = {
            'parcel': {'base': 10, 'per_lb': 0.50, 'per_mile': 0.05},
            'ltl': {'base': 150, 'per_cwt': 25, 'per_mile': 0.8},
            'tl': {'base_rate': 2.50, 'min_charge': 500},
            'intermodal': {'base_rate': 1.75, 'min_charge': 800},
            'air': {'base': 200, 'per_lb': 3.50}
        }

    def calculate_mode_cost(self, weight_lbs, distance_miles, freight_class=70):
        """
        Calculate cost for each transportation mode

        Parameters:
        - weight_lbs: Shipment weight in pounds
        - distance_miles: Distance in miles
        - freight_class: NMFC freight class (affects LTL pricing)
        """

        costs = {}

        # Parcel (up to 150 lbs per package)
        if weight_lbs <= 150:
            costs['parcel'] = (
                self.rates['parcel']['base'] +
                weight_lbs * self.rates['parcel']['per_lb'] +
                distance_miles * self.rates['parcel']['per_mile']
            )
        else:
            costs['parcel'] = None  # Too heavy

        # LTL (500 - 36,000 lbs)
        if 500 <= weight_lbs <= 36000:
            cwt = weight_lbs / 100
            class_multiplier = freight_class / 70  # Adjust for class
            costs['ltl'] = (
                self.rates['ltl']['base'] +
                cwt * self.rates['ltl']['per_cwt'] * class_multiplier +
                distance_miles * self.rates['ltl']['per_mile']
            )
        else:
            costs['ltl'] = None

        # Truckload (>20,000 lbs optimal)
        if weight_lbs >= 10000:
            costs['tl'] = max(
                distance_miles * self.rates['tl']['base_rate'],
                self.rates['tl']['min_charge']
            )
        else:
            costs['tl'] = None

        # Intermodal (>1000 miles)
        if distance_miles >= 1000:
            costs['intermodal'] = max(
                distance_miles * self.rates['intermodal']['base_rate'],
                self.rates['intermodal']['min_charge']
            )
        else:
            costs['intermodal'] = None

        # Air freight (time-critical)
        costs['air'] = (
            self.rates['air']['base'] +
            weight_lbs * self.rates['air']['per_lb']
        )

        return costs

    def recommend_mode(self, weight_lbs, distance_miles, urgency='standard',
                      freight_class=70):
        """
        Recommend optimal transportation mode

        Parameters:
        - urgency: 'standard', 'expedited', 'critical'
        """

        costs = self.calculate_mode_cost(weight_lbs, distance_miles, freight_class)

        # Filter out None values
        valid_costs = {mode: cost for mode, cost in costs.items()
                      if cost is not None}

        if not valid_costs:
            return {'error': 'No valid transportation mode'}

        # Apply urgency filters
        if urgency == 'critical':
            # Only air or expedited TL
            valid_costs = {k: v for k, v in valid_costs.items()
                          if k in ['air', 'tl']}

        elif urgency == 'expedited':
            # Exclude intermodal (slower)
            valid_costs = {k: v for k, v in valid_costs.items()
                          if k != 'intermodal'}

        # Find minimum cost mode
        recommended_mode = min(valid_costs, key=valid_costs.get)
        recommended_cost = valid_costs[recommended_mode]

        # Calculate savings vs. alternatives
        alternatives = {k: v for k, v in valid_costs.items()
                       if k != recommended_mode}

        return {
            'recommended_mode': recommended_mode,
            'cost': recommended_cost,
            'alternatives': alternatives,
            'all_costs': costs
        }

# Example usage
selector = FreightModeSelector()

# Small package
result = selector.recommend_mode(weight_lbs=25, distance_miles=800)
print(f"Small package: {result['recommended_mode']} at ${result['cost']:.2f}")

# LTL shipment
result = selector.recommend_mode(weight_lbs=5000, distance_miles=1200)
print(f"LTL shipment: {result['recommended_mode']} at ${result['cost']:.2f}")

# Truckload
result = selector.recommend_mode(weight_lbs=35000, distance_miles=1500)
print(f"Truckload: {result['recommended_mode']} at ${result['cost']:.2f}")
LTL と TL の損益分岐分析
def ltl_tl_breakeven(distance_miles, freight_class=70,
                     ltl_rate_per_cwt=25, tl_rate_per_mile=2.50):
    """
    Calculate breakeven point between LTL and Truckload

    Returns weight where TL becomes more economical
    """

    # LTL cost increases with weight
    # TL cost is fixed regardless of weight

    tl_cost = distance_miles * tl_rate_per_mile

    # Solve for weight where LTL cost equals TL cost
    # LTL_cost = (weight/100) * ltl_rate_per_cwt * (freight_class/70) + base
    # Simplified: when does (weight/100) * rate = TL_cost

    class_multiplier = freight_class / 70
    breakeven_weight = (tl_cost / (ltl_rate_per_cwt * class_multiplier)) * 100

    return {
        'breakeven_weight_lbs': breakeven_weight,
        'breakeven_pallets': breakeven_weight / 1500,  # Assume 1500 lbs/pallet
        'tl_cost': tl_cost,
        'recommendation': f"Use LTL below {breakeven_weight:.0f} lbs, TL above"
    }

# Example: 800-mile lane
breakeven = ltl_tl_breakeven(distance_miles=800)
print(f"Breakeven: {breakeven['breakeven_weight_lbs']:.0f} lbs "
      f"({breakeven['breakeven_pallets']:.1f} pallets)")

貨物の集約

出荷集約オプティマイザー
import pandas as pd
from datetime import datetime, timedelta

class FreightConsolidator:
    """
    Optimize freight consolidation

    Combine multiple small shipments into larger loads
    to reduce transportation costs
    """

    def __init__(self, shipments_df):
        """
        Parameters:
        - shipments_df: DataFrame with columns
          ['order_id', 'customer', 'destination', 'weight',
           'ready_date', 'due_date', 'priority']
        """
        self.shipments = shipments_df.copy()

    def identify_consolidation_opportunities(self, max_wait_days=3,
                                            max_distance_deviation=50):
        """
        Find shipments that can be consolidated

        Parameters:
        - max_wait_days: Maximum days to hold shipment for consolidation
        - max_distance_deviation: Max miles between destinations to consolidate
        """

        # Group by destination region
        self.shipments['region'] = self.shipments['destination'].apply(
            self._assign_region
        )

        opportunities = []

        for region, group in self.shipments.groupby('region'):
            if len(group) < 2:
                continue

            # Check if shipments can wait for consolidation
            earliest_ready = group['ready_date'].min()
            latest_due = group['due_date'].max()

            time_window = (latest_due - earliest_ready).days

            if time_window <= max_wait_days:
                total_weight = group['weight'].sum()

                # Determine if consolidation makes sense
                if total_weight >= 10000:  # Enough for TL consideration
                    opportunities.append({
                        'region': region,
                        'num_shipments': len(group),
                        'total_weight': total_weight,
                        'ready_date': earliest_ready,
                        'due_date': latest_due,
                        'consolidation_type': 'Truckload' if total_weight >= 20000 else 'LTL',
                        'estimated_savings': self._estimate_savings(group)
                    })

        return pd.DataFrame(opportunities)

    def _assign_region(self, destination):
        """Assign destination to region (simplified)"""
        # In practice, use zip code or geographic clustering
        return destination[:5]  # Use first 5 chars as region

    def _estimate_savings(self, shipments):
        """
        Estimate cost savings from consolidation

        Compare individual LTL vs. consolidated TL
        """

        # Individual LTL cost
        individual_cost = len(shipments) * 300  # Simplified

        # Consolidated cost
        consolidated_cost = 800  # Single TL

        savings = individual_cost - consolidated_cost
        return max(0, savings)

    def create_consolidation_plan(self, opportunities, target_savings=10000):
        """
        Create consolidation execution plan

        Prioritize by savings potential
        """

        # Sort by savings
        opportunities = opportunities.sort_values(
            'estimated_savings',
            ascending=False
        )

        plan = []
        cumulative_savings = 0

        for idx, opp in opportunities.iterrows():
            if cumulative_savings >= target_savings:
                break

            plan.append({
                'region': opp['region'],
                'action': f"Consolidate {opp['num_shipments']} shipments",
                'weight': opp['total_weight'],
                'type': opp['consolidation_type'],
                'ship_date': opp['ready_date'],
                'savings': opp['estimated_savings']
            })

            cumulative_savings += opp['estimated_savings']

        return plan, cumulative_savings

# Example usage
shipments = pd.DataFrame({
    'order_id': [f'ORD{i:04d}' for i in range(50)],
    'customer': [f'Customer_{i%10}' for i in range(50)],
    'destination': [f'ZIP_{zip}' for zip in np.random.randint(10000, 99999, 50)],
    'weight': np.random.randint(500, 5000, 50),
    'ready_date': [datetime.now() + timedelta(days=np.random.randint(0, 3))
                   for _ in range(50)],
    'due_date': [datetime.now() + timedelta(days=np.random.randint(5, 10))
                 for _ in range(50)],
    'priority': np.random.choice(['Standard', 'Expedited'], 50)
})

consolidator = FreightConsolidator(shipments)
opportunities = consolidator.identify_consolidation_opportunities()
plan, savings = consolidator.create_consolidation_plan(opportunities)

print(f"Found {len(opportunities)} consolidation opportunities")
print(f"Estimated annual savings: ${savings * 52:,.0f}")
ミルクランの最適化
class MilkRunOptimizer:
    """
    Optimize milk runs (regular pickup routes)

    Consolidate pickups from multiple suppliers onto single truck
    """

    def __init__(self, suppliers, frequencies, truck_capacity=40000):
        """
        Parameters:
        - suppliers: DataFrame with supplier locations and volumes
        - frequencies: pickup frequency per supplier
        - truck_capacity: truck weight capacity (lbs)
        """
        self.suppliers = suppliers
        self.frequencies = frequencies
        self.capacity = truck_capacity

    def design_milk_run_routes(self, max_route_time=8):
        """
        Design milk run routes

        Combine multiple supplier pickups into single route
        """

        from sklearn.cluster import DBSCAN

        # Cluster suppliers geographically
        coords = self.suppliers[['latitude', 'longitude']].values
        clustering = DBSCAN(eps=0.5, min_samples=2).fit(coords)

        routes = []

        for cluster_id in set(clustering.labels_):
            if cluster_id == -1:  # Noise
                continue

            cluster_suppliers = self.suppliers[clustering.labels_ == cluster_id]

            # Check if total volume fits in truck
            total_volume = cluster_suppliers['avg_volume'].sum()

            if total_volume <= self.capacity:
                routes.append({
                    'route_id': len(routes) + 1,
                    'suppliers': cluster_suppliers['supplier_id'].tolist(),
                    'total_volume': total_volume,
                    'utilization': total_volume / self.capacity,
                    'num_stops': len(cluster_suppliers)
                })

        return routes

    def calculate_milk_run_savings(self, routes, individual_pickup_cost=250):
        """
        Calculate savings from milk runs vs. individual pickups

        Parameters:
        - individual_pickup_cost: Cost of individual supplier pickup
        """

        # Current cost (individual pickups)
        total_suppliers = len(self.suppliers)
        current_cost = total_suppliers * individual_pickup_cost

        # Milk run cost
        milk_run_cost = len(routes) * 400  # Cost per milk run route

        savings = current_cost - milk_run_cost
        savings_percentage = (savings / current_cost) * 100 if current_cost > 0 else 0

        return {
            'current_cost': current_cost,
            'milk_run_cost': milk_run_cost,
            'savings': savings,
            'savings_percentage': savings_percentage,
            'num_routes': len(routes)
        }

積載計画と最適化

トレーラー積載の最適化
class TrailerLoadingOptimizer:
    """
    Optimize trailer loading

    Maximize cube utilization and ensure weight distribution
    """

    def __init__(self, trailer_length=53, trailer_width=8.5,
                 trailer_height=9, weight_capacity=45000):
        """
        Parameters:
        - Dimensions in feet
        - Weight in pounds
        """
        self.length = trailer_length
        self.width = trailer_width
        self.height = trailer_height
        self.weight_capacity = weight_capacity
        self.max_cube = trailer_length * trailer_width * trailer_height

    def calculate_load_metrics(self, pallets):
        """
        Calculate load metrics for set of pallets

        Parameters:
        - pallets: list of dicts with 'length', 'width', 'height', 'weight'
        """

        total_weight = sum(p['weight'] for p in pallets)
        total_cube = sum(p['length'] * p['width'] * p['height']
                        for p in pallets)

        # Assuming standard pallet footprint (40"x48" = 3.33' x 4')
        # 53' trailer fits ~26 pallets single stacked
        num_pallets = len(pallets)
        single_stack_capacity = int(self.length / 4)  # 4' per pallet

        # Can we stack?
        stackable_height = sum(p['height'] for p in pallets if p.get('stackable', True))

        return {
            'num_pallets': num_pallets,
            'total_weight': total_weight,
            'weight_utilization': total_weight / self.weight_capacity,
            'total_cube': total_cube,
            'cube_utilization': total_cube / self.max_cube,
            'weight_limited': total_weight / self.weight_capacity > 0.95,
            'cube_limited': total_cube / self.max_cube > 0.95,
            'floor_positions_used': min(num_pallets, single_stack_capacity),
            'can_stack': num_pallets > single_stack_capacity
        }

    def optimize_multi_order_loads(self, orders, destinations):
        """
        Optimize loading multiple orders onto same trailer

        Consider delivery sequence and weight distribution
        """

        # Sort orders by delivery sequence
        sorted_orders = sorted(
            zip(orders, destinations),
            key=lambda x: x[1]['delivery_sequence']
        )

        load_plan = []
        current_weight = 0
        current_cube = 0

        for order, dest in sorted_orders:
            order_weight = sum(p['weight'] for p in order['pallets'])
            order_cube = sum(p['length'] * p['width'] * p['height']
                           for p in order['pallets'])

            # Check if order fits
            if (current_weight + order_weight <= self.weight_capacity and
                current_cube + order_cube <= self.max_cube):

                load_plan.append({
                    'order_id': order['order_id'],
                    'destination': dest['name'],
                    'pallets': len(order['pallets']),
                    'weight': order_weight,
                    'position': 'rear' if len(load_plan) < 2 else 'front'
                    # Last stop loads in front (FILO)
                })

                current_weight += order_weight
                current_cube += order_cube

        return {
            'load_plan': load_plan,
            'total_weight': current_weight,
            'total_cube': current_cube,
            'utilization': {
                'weight': current_weight / self.weight_capacity,
                'cube': current_cube / self.max_cube
            }
        }

# Example
optimizer = TrailerLoadingOptimizer()

pallets = [
    {'length': 4, 'width': 3.33, 'height': 5, 'weight': 1800, 'stackable': True}
    for _ in range(24)
]

metrics = optimizer.calculate_load_metrics(pallets)
print(f"Load utilization: Weight {metrics['weight_utilization']:.1%}, "
      f"Cube {metrics['cube_utilization']:.1%}")

運送業者の管理と調達

運送業者の選定と最適化
class CarrierOptimizer:
    """
    Optimize carrier selection and allocation

    Balance cost, service, and carrier diversification
    """

    def __init__(self, carriers_df, lanes_df):
        """
        Parameters:
        - carriers_df: carrier information (rates, service, capacity)
        - lanes_df: shipping lanes (origin, dest, volume)
        """
        self.carriers = carriers_df
        self.lanes = lanes_df

    def score_carriers(self, weights={'cost': 0.5, 'service': 0.3,
                                     'reliability': 0.2}):
        """
        Score carriers based on multiple criteria

        Returns ranked carriers per lane
        """

        scored = []

        for _, lane in self.lanes.iterrows():
            lane_carriers = self.carriers[
                self.carriers['lane_id'] == lane['lane_id']
            ].copy()

            if len(lane_carriers) == 0:
                continue

            # Normalize scores (0-1 scale)
            # Cost: lower is better
            lane_carriers['cost_score'] = 1 - (
                (lane_carriers['cost'] - lane_carriers['cost'].min()) /
                (lane_carriers['cost'].max() - lane_carriers['cost'].min())
            )

            # Service and reliability: higher is better (already 0-1)

            # Composite score
            lane_carriers['total_score'] = (
                weights['cost'] * lane_carriers['cost_score'] +
                weights['service'] * lane_carriers['service_score'] +
                weights['reliability'] * lane_carriers['reliability_score']
            )

            lane_carriers = lane_carriers.sort_values(
                'total_score',
                ascending=False
            )

            scored.extend(lane_carriers.to_dict('records'))

        return pd.DataFrame(scored)

    def allocate_volume(self, max_carrier_share=0.4):
        """
        Allocate volume across carriers

        Avoid over-concentration with single carrier
        """

        allocation = []

        for lane_id in self.lanes['lane_id'].unique():
            lane_volume = self.lanes[
                self.lanes['lane_id'] == lane_id
            ]['volume'].sum()

            lane_carriers = self.carriers[
                self.carriers['lane_id'] == lane_id
            ].sort_values('cost')

            remaining_volume = lane_volume
            carrier_idx = 0

            while remaining_volume > 0 and carrier_idx < len(lane_carriers):
                carrier = lane_carriers.iloc[carrier_idx]

                # Allocate up to max_share or carrier capacity
                max_allocation = min(
                    lane_volume * max_carrier_share,
                    carrier['capacity'],
                    remaining_volume
                )

                allocation.append({
                    'lane_id': lane_id,
                    'carrier': carrier['carrier_name'],
                    'volume': max_allocation,
                    'cost': carrier['cost'],
                    'total_cost': max_allocation * carrier['cost']
                })

                remaining_volume -= max_allocation
                carrier_idx += 1

        return pd.DataFrame(allocation)

    def calculate_freight_spend(self, allocation):
        """Calculate total freight spend from allocation"""

        total_spend = allocation['total_cost'].sum()

        by_carrier = allocation.groupby('carrier').agg({
            'volume': 'sum',
            'total_cost': 'sum'
        }).sort_values('total_cost', ascending=False)

        return {
            'total_spend': total_spend,
            'by_carrier': by_carrier,
            'num_carriers': len(by_carrier)
        }
輸送の RFP と入札分析
class FreightRFPAnalyzer:
    """
    Analyze carrier bids from RFP (Request for Proposal)

    Optimize carrier selection based on bid responses
    """

    def __init__(self, lanes_historical, carrier_bids):
        """
        Parameters:
        - lanes_historical: historical lane data (volume, spend)
        - carrier_bids: carrier responses to RFP
        """
        self.lanes = lanes_historical
        self.bids = carrier_bids

    def analyze_bids(self):
        """
        Analyze and compare carrier bids

        Returns comparison matrix
        """

        comparison = []

        for _, lane in self.lanes.iterrows():
            lane_bids = self.bids[
                (self.bids['origin'] == lane['origin']) &
                (self.bids['destination'] == lane['destination'])
            ]

            if len(lane_bids) == 0:
                continue

            # Calculate cost per load
            lane_bids['annual_cost'] = (
                lane_bids['rate'] * lane['annual_volume']
            )

            # Find current cost
            current_cost = lane['current_annual_cost']

            # Calculate savings
            lane_bids['savings'] = current_cost - lane_bids['annual_cost']
            lane_bids['savings_pct'] = (
                lane_bids['savings'] / current_cost * 100
            )

            comparison.extend(lane_bids.to_dict('records'))

        return pd.DataFrame(comparison)

    def optimize_carrier_awards(self, min_savings_pct=5,
                               max_carriers=10):
        """
        Determine optimal carrier awards from RFP

        Parameters:
        - min_savings_pct: minimum savings % to award lane
        - max_carriers: maximum number of carriers to use
        """

        bids_analyzed = self.analyze_bids()

        # Filter to bids meeting savings threshold
        qualified_bids = bids_analyzed[
            bids_analyzed['savings_pct'] >= min_savings_pct
        ]

        # Select best bid per lane
        awards = qualified_bids.loc[
            qualified_bids.groupby(['origin', 'destination'])['savings']
            .idxmax()
        ]

        # Check carrier count constraint
        carrier_counts = awards['carrier'].value_counts()

        if len(carrier_counts) > max_carriers:
            # Keep top N carriers by total savings
            top_carriers = carrier_counts.head(max_carriers).index
            awards = awards[awards['carrier'].isin(top_carriers)]

        total_savings = awards['savings'].sum()
        total_current_cost = awards['annual_cost'].sum() + total_savings

        return {
            'awards': awards,
            'total_savings': total_savings,
            'savings_percentage': total_savings / total_current_cost * 100,
            'num_carriers': len(carrier_counts),
            'lanes_awarded': len(awards)
        }

# Example usage
lanes_hist = pd.DataFrame({
    'origin': ['Chicago', 'Chicago', 'LA'],
    'destination': ['Atlanta', 'Dallas', 'Phoenix'],
    'annual_volume': [1000, 800, 600],
    'current_annual_cost': [2500000, 2000000, 1500000]
})

carrier_bids = pd.DataFrame({
    'carrier': ['Carrier_A', 'Carrier_B', 'Carrier_A', 'Carrier_C'],
    'origin': ['Chicago', 'Chicago', 'LA', 'LA'],
    'destination': ['Atlanta', 'Atlanta', 'Phoenix', 'Phoenix'],
    'rate': [2300, 2450, 2350, 2400]
})

rfp = FreightRFPAnalyzer(lanes_hist, carrier_bids)
results = rfp.optimize_carrier_awards()
print(f"Total savings: ${results['total_savings']:,.0f} "
      f"({results['savings_percentage']:.1f}%)")

よくある課題と解決策

課題:貨物輸送費が高い

問題:

  • 売上高に占める貨物輸送費の割合が X%です
  • コストが前年比で増加しています
  • 比較のためのベンチマークがありません

解決策:

  • 業界のベンチマークと比較します(製造業では通常、売上高の 8~12%です)
  • 主なレーンについて年次 RFP を実施します
  • 貨物監査プロセスを導入します(請求ミスを検出します)
  • 輸送モードの選択を最適化します(判断ツールを使用します)
  • 出荷を集約します(LTL を減らし、TL を増やします)
  • 数量割引を交渉します
  • 契約市場とスポット市場の組み合わせを検討します
  • 貨物決済システムを導入します(可視化と監査)
課題:運送業者の実績が芳しくない

問題:

  • 配達が遅れます
  • 貨物が破損します
  • コミュニケーションが不十分です
  • サービスに一貫性がありません

解決策:

  • 運送業者のスコアカードを導入します(納期遵守、破損、サービス)
  • 契約で最低実績基準を設定します
  • 運送業者との四半期ごとのビジネスレビューを実施します
  • 運送業者の構成を多様化します(1 社に過度に依存しないようにします)
  • 各レーンの代替運送業者リストを作成します
  • スポット輸送の確保には貨物掲示板を利用します
  • 運送業者の管理に 3PL の利用を検討します
  • 問題を体系的に追跡し、エスカレーションします
課題:貨物の可視性不足

問題:

  • 貨物の所在がわからない
  • 到着時刻を予測できない
  • 事後対応になり、先回りした対応ができない

解決策:

  • TMS(輸送管理システム)を導入する
  • 運送会社にGPS追跡を義務付ける
  • 貨物可視化プラットフォーム(FourKites、project44)を利用する
  • ステータス更新を自動化する(EDI、API連携)
  • 例外アラートを設定する
  • 顧客向け追跡ポータルを提供する
  • 監視用のコントロールタワーを構築する
課題:輸送レーンの不均衡

問題:

  • 出荷は多いが、入荷貨物が少ない
  • 空の復路輸送に費用を支払っている
  • 不均衡な輸送レーンでは運賃が高くなる

解決策:

  • バックホールプログラム(リバースロジスティクス)を開発する
  • 相互補完関係にある荷主と提携する
  • 貨物マッチングプラットフォームを利用する
  • 長距離輸送の不均衡には複合一貫輸送を検討する
  • 高コストを受け入れる(価格設定に織り込む)
  • 柔軟な契約(往復料金)を交渉する
  • バランスの取れた輸送レーン向けに専用車両を構築する
課題:容積重量による料金設定

問題:

  • 低密度の貨物が容積重量によって不利になる
  • 実重量ではなく容積重量に対して料金を支払っている

解決策:

  • 梱包サイズを適正化する(容積を減らす)
  • 効率的にパレット積みする(密度を最大化する)
  • 運送会社と密度のしきい値を交渉する
  • 貨物クラスの最適化(NMFC)を検討する
  • 可能な場合はパレットの代わりにブランケット梱包を利用する
  • 混載して密度を高める
  • 小口貨物の場合:箱を小さくし、隙間埋め材をなくす

ツールとテクノロジー

輸送管理システム(TMS)

エンタープライズ向けTMS:

  • Manhattan Associates TMS:エンタープライズ向け輸送管理
  • Blue Yonder TMS:AI を活用した最適化
  • Oracle Transportation Management:包括的なスイート
  • SAP Transportation Management:ERP と連携
  • JDA (now Blue Yonder):サプライチェーンプラットフォーム
  • MercuryGate:クラウド型TMS

中堅企業向けTMS:

  • Kuebix:無料プランからエンタープライズ向けプランまで
  • 3Gtms:マルチテナント型クラウドTMS
  • Transplace:マネージドTMS
  • McLeod Software:LoadMaster
  • TMW Systems:TMWSuite

貨物マーケットプレイス:

  • Uber Freight:デジタル貨物マッチング
  • Convoy:自動化された貨物ネットワーク
  • Loadsmart:即時の貨物運賃提示
  • Transfix:マネージドマーケットプレイス
  • CargoX:貨物入札プラットフォーム
ロードボードとスポット市場
  • DAT Load Board:業界標準
  • Truckstop.com:積荷マッチング
  • ** 123Loadboard**:貨物の掲載
  • Direct Freight:ロードボードネットワーク
貨物可視化プラットフォーム
  • FourKites:リアルタイム追跡
  • project44:複合輸送の可視化
  • FreightVerify:貨物追跡
  • 10-4 Systems:GPS追跡

出力形式

貨物輸送最適化分析レポート

エグゼクティブサマリー:

  • 年間貨物輸送費:$8.5M
  • 目標削減額:$1.2M(14%削減)
  • 主な改善機会:輸送モードの最適化、貨物の集約、運送会社との再交渉
  • 導入期間:6か月

現状分析:

指標現状ベンチマーク差
売上高に占める貨物輸送費の割合11.5%9.0%⚠️ 2.5%高い
1件あたりの輸送コスト$485$420⚠️ 15%高い
トラック積載率72%85%⚠️ 13%低い
納期遵守率88%95%⚠️ 7%低い
運送会社数3515-20⚠️ 多すぎる

輸送モード別の貨物輸送費:

モード年間支出合計に占める割合輸送件数平均コスト
トラック積載(TL)$4.2M49%2,100$2,000
トラック未満積載$2.8M33%8,400$333
小包$1.2M14%45,000$27
インターモーダル$300K4%150$2,000

最適化の機会:

  1. 輸送モードの最適化 - 削減額:$425K

    • 集約により、LTLの15%をTLに切り替えます
    • 距離が1,200マイルを超える輸送レーンにはインターモーダルを利用します
    • 小包とLTLの使い分けの基準を最適化します
  2. 運送会社の集約とRFP - 削減額:$510K

    • 運送会社数を35社から20社に削減します
    • 上位20の輸送レーンを対象にRFPを実施します
    • 取扱量に応じた割引を交渉します
  3. 貨物の集約 - 削減額:$180K

    • 2日間の集約期間を導入します
    • サプライヤーからの集荷にミルクランを構築します
    • 地域ごとに注文をまとめます
  4. 積載の最適化 - 削減額:$95K

    • TLの積載率を改善します(72% → 85%)
    • 積載計画と荷室容積の利用を改善します
    • 複数箇所に立ち寄るTLルートを設定します

導入ロードマップ:

フェーズ期間取り組み削減額
Q11~3か月目輸送モードの最適化、TMSの導入$220K
Q24~6か月目運送会社向けRFP、契約交渉$510K
Q37~9か月目集約プログラム、ミルクラン$280K
Q410~12か月目積載の最適化、継続的改善$190K

期待される成果:

指標現状1年目の目標改善
年間貨物輸送費$8.5M$7.3M-14%
1件あたりの輸送コスト$485$420-13%
TL積載率72%85%+13ポイント
納期遵守率88%94%+6ポイント
運送会社数3520-43%

質問事項

詳細情報が必要な場合:

  1. 年間の貨物輸送費はいくらですか?
  2. どの輸送モードを利用していますか?(TL、LTL、小包など)
  3. 主な輸送レーンはどこですか?
  4. TMSまたは貨物管理システムを利用していますか?
  5. 現在、何社の運送会社を利用していますか?
  6. サービスレベルの要件は何ですか?
  7. 特に課題となっている点はありますか?(コスト、サービス、可視性)
  8. 運送会社向けRFPを定期的に実施していますか?

関連スキル

  • route-optimization:配送ルートと順序を最適化します
  • network-design:倉庫とDCの所在地を最適化します
  • fleet-management:専用車両の運用を管理します
  • last-mile-delivery:ラストマイル配送を最適化します
  • cross-docking:貨物の集約とクロスドッキング業務
  • procurement-optimization:戦略的な調達と購買
  • supply-chain-analytics:貨物輸送費とパフォーマンスの分析
  • contract-management:運送会社との契約交渉と管理

コメント