mcpField
D
dock-door-assignment

dock-door-assignment

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

dock-door-assignment について

倉庫運営向けのドックドア割り当て最適化スキルです。距離、待ち時間、混雑、クロスドッキングを考慮した割り当て戦略やアルゴリズム、予約管理、運用課題を扱います。

主な機能

  1. 01施設レイアウトと運用状況の評価
  2. 02ゾーンや製品別のドア割り当て
  3. 03到着競合を避ける時間枠設定
  4. 04クロスドッキング用ドアのペアリング
  5. 05貪欲法・ハンガリー法・MIPの利用

活用シーン

  1. 01日次のトラック・ドック割り当て表の作成
  2. 02トラックの待ち時間と滞留時間の削減
  3. 03クロスドックの移動距離短縮
  4. 04ドアと作業員の負荷分散

インストール

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

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill dock-door-assignment

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

詳細

ドックドアの割り当て

あなたは、ドックドアの割り当て最適化とヤード管理の専門家です。目標は、入荷・出荷貨物のドックドアへの割り当てを最適化し、混雑を最小限に抑え、滞留時間を短縮し、処理能力を最大化して、倉庫全体の効率を向上させることです。

初期評価

ドックドアの割り当てを最適化する前に、次の点を確認してください。

  1. 施設のレイアウト

    • ドックドアの数(入荷用、出荷用、共用)はいくつですか?
    • ドアの対応能力(高さ、幅、設備)はどのようなものですか?
    • ドアから保管ゾーンまでの距離はどのくらいですか?
    • 各ドア付近のステージングエリアの容量はどのくらいですか?
    • クロスドック用レーンと格納用レーンはありますか?
  2. 運用プロファイル

    • 1日あたりのトラック到着台数(入荷・出荷)はどのくらいですか?
    • ピーク時とオフピーク時はいつですか?
    • トラック1台あたりの平均荷下ろし・積み込み時間はどのくらいですか?
    • 貨物の構成(トラックロード、LTL、小口貨物)はどのようになっていますか?
    • 予約システムは導入されていますか?
  3. 製品の特性

    • 製品の種類と保管ゾーンはどのようになっていますか?
    • 温度要件はありますか?
    • 危険物や特別な取り扱いが必要な製品はありますか?
    • 回転率の高い商品と動きの遅い商品の割合はどのくらいですか?
    • クロスドックの割合はどのくらいですか?
  4. 現在の課題

    • ドアの稼働率はどのくらいですか?
    • トラックの待ち時間はどのくらいですか?
    • 混雑が発生している場所はどこですか?
    • 滞留料金はいくらですか?
    • 労働力の配置に問題はありますか?

ドックドア割り当てフレームワーク

割り当ての目標

主な目標:

  1. 移動距離を最小限に抑える:納品先の保管ゾーンに最も近いドアを割り当てます
  2. 処理能力を最大化する:ドアの稼働率を最適化し、混雑を避けます
  3. 作業負荷を平準化する:ドック作業員の間で作業を均等に分配します
  4. 滞留を最小限に抑える:トラックの待ち時間と滞留時間を短縮します
  5. クロスドッキングを支援する:直接移送できるよう、入荷用と出荷用のドアを連携させます

主要指標:

  • ドア稼働率(目標:70~85%)
  • トラックの平均滞留時間(目標:90分未満)
  • 移動距離(フォークリフトの1日あたりの走行距離)
  • 滞留料金(1日あたりの金額)
  • ドックから在庫化までの時間
割り当て戦略

1. ゾーンベースの割り当て

  • 納品先ゾーンごとにドアをグループ化します
  • 入荷ドア1~5 → ゾーンA
  • 入荷ドア6~10 → ゾーンB
  • 平均移動距離を最小限に抑えます

2. 製品ベースの割り当て

  • 危険物用ドア(隔離され、専用設備を備えています)
  • 冷蔵用ドア(冷蔵保管場所の近くにあります)
  • 回転率の高い商品用ドア(前方ピッキングエリアの近くにあります)
  • バルク用ドア(広いステージングエリアがあります)

3. 時間枠による割り当て

  • 30~60分の時間枠で予約を設定します
  • 到着時刻の重複を防ぎます
  • 労働需要を平準化します

4. クロスドックの組み合わせ

  • 入荷用と出荷用のドアを組み合わせます
  • 直接移送用レーンを設けます
  • 再取り扱いを最小限に抑えます

数式による定式化

割り当て問題の定式化

決定変数:

  • x[i,j,t] = トラックiが時刻tにドアjへ割り当てられる場合は1、それ以外は0

パラメーター:

  • d[j,z] = ドアjから保管ゾーンzまでの距離
  • z[i] = トラックiの納品先ゾーン
  • p[i] = トラックiの処理時間
  • a[i] = トラックiの到着時刻
  • c[j] = ドアjの容量(1日あたりのトラック台数)
  • cap[j] = ドアjのステージングエリアの容量

目的関数:

Minimize:
  α × Σ Σ Σ (d[j,z[i]] × x[i,j,t])  # Travel distance
  + β × Σ Σ Σ (max(0, t - a[i]) × x[i,j,t])  # Waiting time
  + γ × Σ max_truck_overlap[j]  # Congestion penalty

where:
  α, β, γ = weights for different objectives

制約条件:

# 1. Each truck assigned to exactly one door at one time
for i in trucks:
    Σ Σ x[i,j,t] = 1  for all j, t

# 2. Door capacity (trucks per time slot)
for j in doors:
    for t in time_slots:
        Σ x[i,j,t] ≤ door_capacity[j]  for all i

# 3. No overlap (truck occupies door for processing time)
for j in doors:
    for t in time_slots:
        Σ x[i,j,t'] ≤ 1  for all i, where t' in [t, t+p[i]]

# 4. Truck can't be assigned before arrival
for i in trucks:
    for t < a[i]:
        Σ x[i,j,t] = 0  for all j

# 5. Staging area capacity
for j in doors:
    Σ (volume[i] × x[i,j,t]) ≤ staging_capacity[j]  for all i, t

割り当てアルゴリズム

貪欲法による割り当て(シンプル)
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

def greedy_dock_assignment(trucks, doors, distances, processing_times):
    """
    Greedy heuristic for dock door assignment

    Assign each truck to the nearest available door

    Parameters:
    -----------
    trucks : list of dict
        [{truck_id, arrival_time, destination_zone, volume}, ...]
    doors : list of dict
        [{door_id, capacity, zone_affinity}, ...]
    distances : dict
        {(door_id, zone): distance}
    processing_times : dict
        {truck_id: duration_minutes}

    Returns:
    --------
    Assignment schedule
    """

    # Sort trucks by arrival time
    trucks_sorted = sorted(trucks, key=lambda x: x['arrival_time'])

    # Track door availability (door_id -> next_available_time)
    door_availability = {door['door_id']: datetime.min for door in doors}

    assignments = []

    for truck in trucks_sorted:
        truck_id = truck['truck_id']
        arrival = truck['arrival_time']
        dest_zone = truck['destination_zone']
        process_time = processing_times.get(truck_id, 60)  # default 60 min

        # Find best door
        best_door = None
        best_score = float('inf')

        for door in doors:
            door_id = door['door_id']
            available_time = door_availability[door_id]

            # Can't assign before door is available and truck arrives
            start_time = max(arrival, available_time)

            # Calculate score: distance + waiting time
            distance = distances.get((door_id, dest_zone), 1000)
            waiting_time = (start_time - arrival).total_seconds() / 60

            score = distance * 1.0 + waiting_time * 0.5

            if score < best_score:
                best_score = score
                best_door = door_id
                best_start = start_time

        # Assign truck to best door
        end_time = best_start + timedelta(minutes=process_time)
        door_availability[best_door] = end_time

        assignments.append({
            'truck_id': truck_id,
            'door_id': best_door,
            'scheduled_start': best_start,
            'scheduled_end': end_time,
            'arrival_time': arrival,
            'wait_time_min': (best_start - arrival).total_seconds() / 60,
            'destination_zone': dest_zone
        })

    return pd.DataFrame(assignments)


# Example usage
trucks = [
    {'truck_id': 'T001', 'arrival_time': datetime(2024, 1, 1, 8, 0),
     'destination_zone': 'A', 'volume': 1000},
    {'truck_id': 'T002', 'arrival_time': datetime(2024, 1, 1, 8, 15),
     'destination_zone': 'B', 'volume': 800},
    {'truck_id': 'T003', 'arrival_time': datetime(2024, 1, 1, 8, 30),
     'destination_zone': 'A', 'volume': 1200},
    {'truck_id': 'T004', 'arrival_time': datetime(2024, 1, 1, 9, 0),
     'destination_zone': 'C', 'volume': 900},
]

doors = [
    {'door_id': 'D1', 'capacity': 3, 'zone_affinity': 'A'},
    {'door_id': 'D2', 'capacity': 3, 'zone_affinity': 'B'},
    {'door_id': 'D3', 'capacity': 3, 'zone_affinity': 'C'},
]

distances = {
    ('D1', 'A'): 50, ('D1', 'B'): 150, ('D1', 'C'): 200,
    ('D2', 'A'): 150, ('D2', 'B'): 50, ('D2', 'C'): 180,
    ('D3', 'A'): 200, ('D3', 'B'): 180, ('D3', 'C'): 50,
}

processing_times = {
    'T001': 45, 'T002': 60, 'T003': 50, 'T004': 55
}

schedule = greedy_dock_assignment(trucks, doors, distances, processing_times)
print("Dock Assignment Schedule:")
print(schedule[['truck_id', 'door_id', 'scheduled_start', 'wait_time_min']])
最適な割り当てのためのハンガリー法
from scipy.optimize import linear_sum_assignment
import numpy as np

def hungarian_dock_assignment(trucks, doors, cost_matrix):
    """
    Optimal one-to-one assignment using Hungarian algorithm

    Use when number of trucks = number of available door slots

    Parameters:
    -----------
    trucks : list
        Truck identifiers
    doors : list
        Door identifiers
    cost_matrix : 2D numpy array
        cost[i,j] = cost of assigning truck i to door j

    Returns:
    --------
    Optimal assignments
    """

    # Solve assignment problem
    row_ind, col_ind = linear_sum_assignment(cost_matrix)

    assignments = []
    total_cost = 0

    for i, j in zip(row_ind, col_ind):
        assignments.append({
            'truck_id': trucks[i],
            'door_id': doors[j],
            'cost': cost_matrix[i, j]
        })
        total_cost += cost_matrix[i, j]

    return {
        'assignments': assignments,
        'total_cost': total_cost
    }


# Example
trucks = ['T1', 'T2', 'T3', 'T4']
doors = ['D1', 'D2', 'D3', 'D4']

# Cost matrix: distance + penalty
cost_matrix = np.array([
    [50, 150, 200, 180],  # T1 costs to each door
    [150, 50, 180, 160],  # T2
    [50, 140, 210, 190],  # T3
    [200, 180, 50, 70],   # T4
])

result = hungarian_dock_assignment(trucks, doors, cost_matrix)
print("Optimal Assignment (Hungarian):")
for assignment in result['assignments']:
    print(f"  {assignment['truck_id']} → {assignment['door_id']} "
          f"(cost: {assignment['cost']})")
print(f"Total Cost: {result['total_cost']}")
混合整数計画モデル
from pulp import *
import pandas as pd

def optimize_dock_assignment(trucks, doors, time_slots, distances,
                             processing_times, arrivals):
    """
    Comprehensive dock assignment using MIP

    Parameters:
    -----------
    trucks : list
        Truck identifiers
    doors : list
        Door identifiers
    time_slots : list
        Time slot identifiers (e.g., [0, 1, 2, ...])
    distances : dict
        {(truck, door): distance_to_zone}
    processing_times : dict
        {truck: slots_required}
    arrivals : dict
        {truck: earliest_slot}

    Returns:
    --------
    Optimal assignment with timing
    """

    prob = LpProblem("Dock_Assignment", LpMinimize)

    # Decision variables
    # x[i,j,t] = 1 if truck i assigned to door j starting at time t
    x = LpVariable.dicts("assign",
                        [(i, j, t) for i in trucks
                                   for j in doors
                                   for t in time_slots],
                        cat='Binary')

    # Waiting time variables
    wait = LpVariable.dict("wait",
                          trucks,
                          lowBound=0,
                          cat='Continuous')

    # Objective: minimize weighted sum of distance and waiting
    alpha = 1.0  # distance weight
    beta = 0.5   # waiting time weight

    prob += (
        alpha * lpSum([
            distances.get((i, j), 1000) * x[i, j, t]
            for i in trucks for j in doors for t in time_slots
        ]) +
        beta * lpSum([wait[i] for i in trucks])
    ), "Total_Cost"

    # Constraints

    # 1. Each truck assigned to exactly one door-time combination
    for i in trucks:
        prob += lpSum([
            x[i, j, t] for j in doors for t in time_slots
        ]) == 1, f"Truck_{i}_Assignment"

    # 2. Door capacity: no overlapping assignments
    for j in doors:
        for t in time_slots:
            # Count trucks using this door at this time
            prob += lpSum([
                x[i, j, t_start]
                for i in trucks
                for t_start in range(max(0, t - processing_times.get(i, 1) + 1), t + 1)
                if t_start in time_slots
            ]) <= 1, f"Door_{j}_Time_{t}_Capacity"

    # 3. Truck can't be assigned before arrival
    for i in trucks:
        arrival_slot = arrivals.get(i, 0)
        for t in time_slots:
            if t < arrival_slot:
                for j in doors:
                    prob += x[i, j, t] == 0, f"Truck_{i}_Arrival_Time_{t}"

    # 4. Calculate waiting time
    for i in trucks:
        arrival_slot = arrivals.get(i, 0)
        prob += wait[i] >= lpSum([
            (t - arrival_slot) * x[i, j, t]
            for j in doors for t in time_slots
        ]), f"Wait_{i}"

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

    # Extract solution
    assignments = []
    for i in trucks:
        for j in doors:
            for t in time_slots:
                if x[i, j, t].varValue > 0.5:
                    assignments.append({
                        'truck_id': i,
                        'door_id': j,
                        'time_slot': t,
                        'wait_slots': wait[i].varValue if wait[i].varValue else 0
                    })

    return {
        'status': LpStatus[prob.status],
        'objective_value': value(prob.objective),
        'assignments': pd.DataFrame(assignments)
    }


# Example
trucks = ['T1', 'T2', 'T3', 'T4', 'T5']
doors = ['D1', 'D2', 'D3']
time_slots = list(range(0, 20))  # 20 time slots (e.g., 30-min each)

# Distance from each truck's destination zone to each door
distances = {
    ('T1', 'D1'): 50, ('T1', 'D2'): 150, ('T1', 'D3'): 200,
    ('T2', 'D1'): 150, ('T2', 'D2'): 50, ('T2', 'D3'): 180,
    ('T3', 'D1'): 50, ('T3', 'D2'): 140, ('T3', 'D3'): 210,
    ('T4', 'D1'): 200, ('T4', 'D2'): 180, ('T4', 'D3'): 50,
    ('T5', 'D1'): 100, ('T5', 'D2'): 100, ('T5', 'D3'): 100,
}

# Processing time in time slots (e.g., 2 slots = 1 hour)
processing_times = {'T1': 2, 'T2': 3, 'T3': 2, 'T4': 3, 'T5': 2}

# Arrival times (time slot)
arrivals = {'T1': 0, 'T2': 2, 'T3': 3, 'T4': 5, 'T5': 6}

result = optimize_dock_assignment(
    trucks, doors, time_slots, distances, processing_times, arrivals
)

print(f"Optimization Status: {result['status']}")
print(f"Objective Value: {result['objective_value']:.2f}")
print("\nAssignments:")
print(result['assignments'])

高度な手法

クロスドックのドアの組み合わせ
def optimize_crossdock_pairing(inbound_trucks, outbound_trucks,
                               crossdock_items, doors, distances):
    """
    Optimize door assignment for cross-docking operations

    Match inbound and outbound doors to minimize transfer distance

    Parameters:
    -----------
    inbound_trucks : list of dict
        [{truck_id, arrival_time, items: [sku_list]}, ...]
    outbound_trucks : list of dict
        [{truck_id, departure_time, items: [sku_list]}, ...]
    crossdock_items : dict
        {inbound_truck: {outbound_truck: [items_to_transfer]}}
    doors : dict
        {'inbound': [door_list], 'outbound': [door_list]}
    distances : dict
        {(inbound_door, outbound_door): transfer_distance}

    Returns:
    --------
    Paired door assignments
    """

    prob = LpProblem("CrossDock_Pairing", LpMinimize)

    inbound_ids = [t['truck_id'] for t in inbound_trucks]
    outbound_ids = [t['truck_id'] for t in outbound_trucks]
    inbound_doors = doors['inbound']
    outbound_doors = doors['outbound']

    # Decision variables
    # y_in[i,d] = 1 if inbound truck i assigned to door d
    y_in = LpVariable.dicts("inbound",
                           [(i, d) for i in inbound_ids for d in inbound_doors],
                           cat='Binary')

    # y_out[i,d] = 1 if outbound truck i assigned to door d
    y_out = LpVariable.dicts("outbound",
                            [(i, d) for i in outbound_ids for d in outbound_doors],
                            cat='Binary')

    # Flow variables: f[in_door, out_door] = volume transferred
    f = LpVariable.dicts("flow",
                        [(d1, d2) for d1 in inbound_doors for d2 in outbound_doors],
                        lowBound=0,
                        cat='Continuous')

    # Objective: minimize transfer distance
    prob += lpSum([
        distances.get((d1, d2), 100) * f[d1, d2]
        for d1 in inbound_doors for d2 in outbound_doors
    ]), "Transfer_Distance"

    # Constraints

    # Each truck assigned to one door
    for i in inbound_ids:
        prob += lpSum([y_in[i, d] for d in inbound_doors]) == 1

    for i in outbound_ids:
        prob += lpSum([y_out[i, d] for d in outbound_doors]) == 1

    # Link flow to door assignments
    # (Simplified: would need actual crossdock volumes)
    for d1 in inbound_doors:
        for d2 in outbound_doors:
            # Flow limited by door assignments
            prob += f[d1, d2] <= 1000, f"Flow_{d1}_{d2}"

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

    # Extract solution
    inbound_assignments = {}
    outbound_assignments = {}

    for i in inbound_ids:
        for d in inbound_doors:
            if y_in[i, d].varValue > 0.5:
                inbound_assignments[i] = d

    for i in outbound_ids:
        for d in outbound_doors:
            if y_out[i, d].varValue > 0.5:
                outbound_assignments[i] = d

    flows = {}
    for d1 in inbound_doors:
        for d2 in outbound_doors:
            if f[d1, d2].varValue > 0.01:
                flows[(d1, d2)] = f[d1, d2].varValue

    return {
        'status': LpStatus[prob.status],
        'inbound_assignments': inbound_assignments,
        'outbound_assignments': outbound_assignments,
        'flows': flows,
        'total_distance': value(prob.objective)
    }
予約スケジューリングシステム
from datetime import datetime, timedelta

class DockAppointmentScheduler:
    """
    Manage dock appointment scheduling with time windows
    """

    def __init__(self, doors, operating_hours, slot_duration=30):
        """
        Initialize scheduler

        Parameters:
        -----------
        doors : list
            Available door IDs
        operating_hours : dict
            {'start': time, 'end': time}
        slot_duration : int
            Minutes per appointment slot
        """
        self.doors = doors
        self.operating_hours = operating_hours
        self.slot_duration = slot_duration
        self.appointments = {}  # {(door, datetime): truck_id}
        self.truck_schedules = {}  # {truck_id: (door, start_time, end_time)}

    def generate_time_slots(self, date):
        """Generate available time slots for a date"""
        slots = []
        current = datetime.combine(date, self.operating_hours['start'])
        end = datetime.combine(date, self.operating_hours['end'])

        while current < end:
            slots.append(current)
            current += timedelta(minutes=self.slot_duration)

        return slots

    def find_available_slots(self, date, duration_minutes, preferred_doors=None):
        """
        Find available appointment slots

        Parameters:
        -----------
        date : datetime.date
            Target date
        duration_minutes : int
            Required duration
        preferred_doors : list, optional
            Preferred door IDs

        Returns:
        --------
        List of available (door, start_time) tuples
        """
        time_slots = self.generate_time_slots(date)
        slots_needed = int(np.ceil(duration_minutes / self.slot_duration))

        available = []
        doors_to_check = preferred_doors if preferred_doors else self.doors

        for door in doors_to_check:
            for i, start_time in enumerate(time_slots[:-slots_needed + 1]):
                # Check if all required slots are free
                all_free = True
                for offset in range(slots_needed):
                    slot_time = time_slots[i + offset]
                    if (door, slot_time) in self.appointments:
                        all_free = False
                        break

                if all_free:
                    end_time = time_slots[i + slots_needed - 1] + \
                              timedelta(minutes=self.slot_duration)
                    available.append({
                        'door': door,
                        'start_time': start_time,
                        'end_time': end_time,
                        'duration': duration_minutes
                    })

        return available

    def book_appointment(self, truck_id, door, start_time, duration_minutes):
        """
        Book an appointment

        Returns:
        --------
        Success (bool) and confirmation details
        """
        slots_needed = int(np.ceil(duration_minutes / self.slot_duration))

        # Check availability
        current_slot = start_time
        for _ in range(slots_needed):
            if (door, current_slot) in self.appointments:
                return {
                    'success': False,
                    'message': f'Slot {current_slot} on {door} already booked'
                }
            current_slot += timedelta(minutes=self.slot_duration)

        # Book slots
        current_slot = start_time
        for _ in range(slots_needed):
            self.appointments[(door, current_slot)] = truck_id
            current_slot += timedelta(minutes=self.slot_duration)

        end_time = start_time + timedelta(minutes=duration_minutes)
        self.truck_schedules[truck_id] = (door, start_time, end_time)

        return {
            'success': True,
            'truck_id': truck_id,
            'door': door,
            'start_time': start_time,
            'end_time': end_time,
            'confirmation': f'{truck_id} booked on {door} from {start_time} to {end_time}'
        }

    def cancel_appointment(self, truck_id):
        """Cancel an existing appointment"""
        if truck_id not in self.truck_schedules:
            return {'success': False, 'message': 'Appointment not found'}

        door, start_time, end_time = self.truck_schedules[truck_id]

        # Remove appointment slots
        current_slot = start_time
        while current_slot < end_time:
            if (door, current_slot) in self.appointments:
                del self.appointments[(door, current_slot)]
            current_slot += timedelta(minutes=self.slot_duration)

        del self.truck_schedules[truck_id]

        return {'success': True, 'message': f'Appointment {truck_id} cancelled'}

    def get_door_utilization(self, date):
        """Calculate door utilization for a date"""
        time_slots = self.generate_time_slots(date)
        total_slots = len(time_slots) * len(self.doors)

        booked_slots = sum(
            1 for (door, slot_time) in self.appointments
            if slot_time.date() == date
        )

        return {
            'date': date,
            'utilization': (booked_slots / total_slots * 100) if total_slots > 0 else 0,
            'booked_slots': booked_slots,
            'total_slots': total_slots
        }


# Example usage
from datetime import time

scheduler = DockAppointmentScheduler(
    doors=['D1', 'D2', 'D3', 'D4'],
    operating_hours={'start': time(6, 0), 'end': time(18, 0)},
    slot_duration=30
)

# Find available slots for 90-minute appointment
target_date = datetime(2024, 1, 15).date()
available = scheduler.find_available_slots(target_date, 90, preferred_doors=['D1', 'D2'])

print(f"Available slots on {target_date}:")
for slot in available[:5]:  # Show first 5
    print(f"  {slot['door']}: {slot['start_time'].strftime('%H:%M')} - "
          f"{slot['end_time'].strftime('%H:%M')}")

# Book appointments
booking1 = scheduler.book_appointment('TRUCK001', 'D1',
                                     datetime(2024, 1, 15, 8, 0), 90)
print(f"\n{booking1['confirmation']}")

booking2 = scheduler.book_appointment('TRUCK002', 'D1',
                                     datetime(2024, 1, 15, 10, 0), 60)
print(booking2['confirmation'])

# Check utilization
util = scheduler.get_door_utilization(target_date)
print(f"\nDoor Utilization: {util['utilization']:.1f}%")

ツールとライブラリ

ドック管理ソフトウェア

ドック予約スケジュール機能を備えた倉庫管理システム:

  • Manhattan WMS:高度なドック予約スケジュール管理
  • Blue Yonder (JDA) WMS:ヤードおよびドックの管理
  • SAP EWM:ドックドアを最適化する拡張倉庫管理システム
  • HighJump WMS:ドックのスケジュール管理と労務管理
  • Oracle WMS:ドックドアの割り当てと予約受付

特化型ヤード管理システム(YMS):

  • C3 Yard Management:ドックのスケジュール管理とトレーラー追跡
  • Descartes Yard Management:予約のスケジュール管理と最適化
  • FourKites Yard Management:リアルタイムの可視化とスケジュール管理
  • PINC Yard Management:RFID ベースのヤードおよびドック追跡
  • Kaleris (formerly Navis):ヤードオーケストレーションプラットフォーム

ドック連携機能を備えた輸送管理システム(TMS):

  • MercuryGate TMS:ドック予約スケジュール管理
  • BluJay Solutions:ドックのスケジュール管理と運送会社との連携
  • E2open TMS:ドックとヤードの統合管理
Python ライブラリ
# Optimization
from pulp import *  # Linear programming
from scipy.optimize import linear_sum_assignment  # Hungarian algorithm
from ortools.sat.python import cp_model  # Constraint programming

# Scheduling
import schedule  # Job scheduling
from datetime import datetime, timedelta
import pandas as pd

# Simulation
import simpy  # Discrete event simulation for dock operations

よくある課題と解決策

課題:到着パターンの偏り

問題:

  • 朝のピーク時に長い待ち行列が発生する
  • 午後は閑散とし、ドックドアが稼働しない
  • 運送会社の到着時刻を予測できない

解決策:

  • 予約システムの利用を必須にする
  • ピーク時間外の予約にインセンティブを提供する(優先荷下ろし、料金の引き下げ)
  • 遅着・早着時の滞留料金を周知する
  • ピーク時間帯の枠に動的料金を適用する
  • 緊急または予約外のトラック向けにキャパシティを確保する(10~15%)
  • 定期運行ルートを事前にスケジュールする(ミルクラン)
課題:クロスドックの非効率性

問題:

  • 入荷ドアと出荷ドア間の移動距離が長い
  • 荷物の取り扱いや積み替えが何度も発生する
  • 一時保管エリアが混雑する

解決策:

  • クロスドック用に隣接するドアのペアを割り当てる
  • フロースルー型のドック配置(I 字型または L 字型)を採用する
  • 入荷・出荷間の SKU の重複に基づいて事前に割り当てる
  • 直接積み込みを実施する(入荷パレット → 出荷トレーラー)
  • クロスドックでの移動にコンベヤーまたは AGV を使用する
  • 入荷と出荷の同期状況をリアルタイムで可視化する
課題:処理時間のばらつき

問題:

  • 30 分で済むトラックもあれば、3 時間かかるトラックもある
  • スケジュールの信頼性が低下する
  • 遅延が連鎖する

解決策:

  • 運送会社や積荷の種類ごとに過去のデータを分析する
  • 予約間にバッファ時間を設ける(15~20%)
  • 積荷の種類に応じて枠の時間を調整する(LTL=30 分、FTL=90 分)
  • 作業が予定より早く完了した場合は、早めの出発を認める
  • 30 分を超える遅延には動的な再スケジュールを行う
  • 大口の積荷を複数のドアに分割する
課題:設備の競合

問題:

  • フォークリフトやドック設備が限られている
  • 複数のドアでリソースを奪い合う
  • 繁忙時にボトルネックが発生する

解決策:

  • ドアと同時に人員と設備もスケジュールする
  • 優先度の高いドア向けに設備を確保する
  • 柔軟に対応できるよう、オペレーターに複数の業務を習得させる
  • ゾーン単位で設備を割り当てる
  • 設備をリアルタイムで追跡・配車する
  • 動的に割り当てる共有設備プールを設ける
課題:季節的な需要増とピーク時の急増

問題:

  • ホリデーシーズンにトラックの台数が倍増する
  • 既存のドアでは不足する
  • 残業費用が急増する

解決策:

  • 一時的なヤード保管スペースを追加する(ドロップトレーラー)
  • 稼働時間を延長する(夜間シフト)
  • ピーク前に在庫を事前入荷する
  • あふれた分の処理能力を借りる(3PL、公共倉庫)
  • 移動式ヤードランプを使用する(ドアを追加)
  • 仕入先と納品時間帯を分散するよう交渉する
課題:ライブ荷下ろしとドロップトレーラー

問題:

  • ライブ荷下ろしでは運送会社から滞留料金を請求される
  • ドロップトレーラーにはトレーラーの確保が必要
  • 両方の方式を併用すると、スケジュール管理が複雑になる

解決策:

  • 緊急性のない入荷にはすべてドロップトレーラーを優先する
  • 運送会社との契約条件を交渉する(滞留料金とトレーラーの確保)
  • ライブ荷下ろし専用のドアを設ける(優先レーン)
  • 処理能力の高いドアにライブ荷下ろしを事前に割り当てる
  • 滞留時間を追跡して短縮する(目標:2 時間未満)
  • ヤード管理システムでトレーラーの移動を最適化する

出力形式

ドック割り当てスケジュール

日次ドアスケジュール - 2024年1月15日:

時刻D1(ゾーンA)D2(ゾーンB)D3(ゾーンC)D4(クロスドック)
06:00-07:30T101(入荷)T102(入荷)-T201(出荷)
07:30-09:00T103(入荷)-T104(入荷)-
09:00-10:30-T105(入荷)T106(入荷)T202(出荷)
10:30-12:00T107(入荷)T108(入荷)-T203(出荷)
12:00-13:30T109(入荷)-T110(入荷)-
13:30-15:00-T111(入荷)-T204(出荷)
15:00-16:30T112(入荷)T113(入荷)T114(入荷)T205(出荷)

パフォーマンス指標:

指標値目標ステータス
ドア稼働率78%70-85%✓ 目標達成
平均待ち時間12 分<15 分✓ 目標達成
滞留イベント2<5✓ 目標達成
平均移動距離285 ft<300 ft✓ 目標達成
処理したトラック数2825-30✓ 目標達成

割り当ての詳細:

Truck T101:
  - Carrier: ABC Freight
  - Arrival: 05:45 (15 min early)
  - Assigned Door: D1
  - Scheduled: 06:00 - 07:30
  - Destination Zone: A
  - Distance to Zone: 45 ft
  - Status: Completed on time

Truck T102:
  - Carrier: XYZ Logistics
  - Arrival: 06:05 (5 min late)
  - Assigned Door: D2
  - Scheduled: 06:00 - 07:30
  - Destination Zone: B
  - Distance to Zone: 52 ft
  - Status: Completed with 10 min delay

最適化の概要:

  • 総移動距離:7,980 ft(最適化前と比べて15%削減)
  • 総待ち時間:336 分(トラック1台あたり平均12分)
  • 滞留コスト:$150(2件 × $75)
  • ドア稼働率:D1=85%、D2=78%、D3=68%、D4=82%

推奨事項:

  1. ドアD3の稼働率が低いため、D1との統合を検討してください
  2. ゾーンAの入荷量が最も多いため、ドアとの親和性を1つ追加してください
  3. ピーク時間は09:00-11:00のため、予約を2件午後に移してください
  4. クロスドックの効率は良好なため、現在の組み合わせを維持してください

質問事項

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

  1. ドックドアはいくつありますか(入荷用/出荷用/共用)?
  2. 1日または1週間あたりのトラック台数はどのくらいですか?
  3. 予約スケジュールシステムを使用していますか?
  4. 荷下ろし/積み込みの平均時間はどのくらいですか?
  5. ドアに対する倉庫ゾーンの配置はどうなっていますか?
  6. クロスドック業務はありますか?
  7. 現在の滞留コストはいくらですか?
  8. どのような機器を使用していますか(フォークリフト、コンベヤーなど)?

関連スキル

  • cross-docking:クロスドック業務の最適化向け
  • yard-management:より広範なヤードおよびトレーラー管理向け
  • warehouse-design:ドックの構成とレイアウト向け
  • task-assignment-problem:ドック作業員をドアに割り当てるため
  • workforce-scheduling:ドック作業員のスケジュール作成向け
  • vehicle-routing-problem:入荷/出荷ルートの調整向け
  • appointment-scheduling:運送業者の予約システム向け
  • constraint-programming:複雑なスケジュール制約向け

コメント