追跡データを活用した配送分析ダッシュボードの構築

生の追跡イベントは、正しく分析すれば配送インテリジェンスの宝庫です。追跡データの上に分析ダッシュボードを構築することで、パフォーマンスの低い配送業者を特定し、配送ルートを最適化し、配達時間を短縮することができます。このチュートリアルでは、実用的な配送分析システムの構築方法を解説します。

何を測定するか

コードを書く前に、重要な指標を定義しましょう。

基本的な配達指標

指標計算式重要な理由
平均輸送日数輸送日数の合計 / 総配送数配達速度のベースライン
定時配達率定時配達数 / 総配達数 x 100配送業者の信頼性指標
例外発生率例外が発生した配送数 / 総配送数 x 100配送体験の品質
初回配達成功率初回で配達成功した数 / 総配達数 x 100ラストマイルの効率性

ビジネス指標

指標計算式重要な理由
WISMO率追跡に関する問い合わせ数 / 総配送数 x 100顧客体験の品質
配達1件あたりのコスト配送業者の総コスト / 配達成功数コスト効率
返品配送率返品配送数 / 総配送数 x 100商品またはフルフィルメントの問題

追跡データの収集

WhereParcelのWebhookを使用して、追跡イベントを分析用データベースにストリーミングします。

// Webhook receiver - store events for analytics
app.post('/webhooks/tracking', async (req, res) => {
  const event = req.body;

  // Store raw event for detailed analysis
  await db.trackingEvents.insert({
    trackingNumber: event.data.trackingNumber,
    carrier: event.data.carrier,
    status: event.data.latestEvent.status,
    location: event.data.latestEvent.location,
    timestamp: event.data.latestEvent.timestamp,
    receivedAt: new Date(),
  });

  // Update shipment summary record
  await updateShipmentSummary(event.data);

  res.status(200).json({ received: true });
});

async function updateShipmentSummary(data) {
  const summary = await db.shipmentSummaries.findOne({
    trackingNumber: data.trackingNumber,
  });

  const updates = {
    currentStatus: data.latestEvent.status,
    lastUpdated: new Date(),
    eventCount: (summary?.eventCount || 0) + 1,
  };

  // Record milestone timestamps
  if (data.latestEvent.status === 'picked_up' && !summary?.pickedUpAt) {
    updates.pickedUpAt = data.latestEvent.timestamp;
  }
  if (data.latestEvent.status === 'delivered') {
    updates.deliveredAt = data.latestEvent.timestamp;
    updates.transitDays = daysBetween(summary?.pickedUpAt, data.latestEvent.timestamp);
  }

  await db.shipmentSummaries.upsert(
    { trackingNumber: data.trackingNumber },
    { $set: updates }
  );
}

分析クエリの構築

配送業者パフォーマンスの比較

async function getCarrierPerformance(dateRange) {
  const results = await db.shipmentSummaries.aggregate([
    {
      $match: {
        deliveredAt: { $gte: dateRange.start, $lte: dateRange.end },
      },
    },
    {
      $group: {
        _id: '$carrier',
        totalShipments: { $sum: 1 },
        avgTransitDays: { $avg: '$transitDays' },
        onTimeCount: {
          $sum: { $cond: [{ $lte: ['$transitDays', '$estimatedDays'] }, 1, 0] },
        },
        exceptionCount: {
          $sum: { $cond: ['$hasException', 1, 0] },
        },
      },
    },
    {
      $project: {
        carrier: '$_id',
        totalShipments: 1,
        avgTransitDays: { $round: ['$avgTransitDays', 1] },
        onTimeRate: {
          $round: [{ $multiply: [{ $divide: ['$onTimeCount', '$totalShipments'] }, 100] }, 1],
        },
        exceptionRate: {
          $round: [{ $multiply: [{ $divide: ['$exceptionCount', '$totalShipments'] }, 100] }, 1],
        },
      },
    },
    { $sort: { onTimeRate: -1 } },
  ]);

  return results;
}

ルート別の輸送時間

async function getTransitTimeByRoute(carrier, dateRange) {
  return db.shipmentSummaries.aggregate([
    {
      $match: {
        carrier,
        deliveredAt: { $gte: dateRange.start, $lte: dateRange.end },
      },
    },
    {
      $group: {
        _id: {
          origin: '$originCountry',
          destination: '$destinationCountry',
        },
        avgDays: { $avg: '$transitDays' },
        minDays: { $min: '$transitDays' },
        maxDays: { $max: '$transitDays' },
        p90Days: { $percentile: { input: '$transitDays', p: [0.9], method: 'approximate' } },
        count: { $sum: 1 },
      },
    },
    { $match: { count: { $gte: 10 } } }, // Only statistically significant routes
    { $sort: { avgDays: 1 } },
  ]);
}

日別配達トレンド

async function getDailyDeliveryTrends(dateRange) {
  return db.shipmentSummaries.aggregate([
    {
      $match: {
        deliveredAt: { $gte: dateRange.start, $lte: dateRange.end },
      },
    },
    {
      $group: {
        _id: { $dateToString: { format: '%Y-%m-%d', date: '$deliveredAt' } },
        delivered: { $sum: 1 },
        avgTransitDays: { $avg: '$transitDays' },
        exceptions: { $sum: { $cond: ['$hasException', 1, 0] } },
      },
    },
    { $sort: { _id: 1 } },
  ]);
}

ダッシュボードのレイアウト

ダッシュボードを論理的なセクションに整理しましょう。

概要セクション

  • 総配送数(当期 vs 前期)
  • 平均輸送時間のトレンド
  • 全体の定時配達率
  • アクティブな例外の件数

配送業者パフォーマンスセクション

  • 配送業者の横並び比較テーブル
  • 配送業者ごとの輸送時間分布チャート
  • 配送業者別の例外率の推移

ルート分析セクション

  • 出発地/到着地別の輸送時間ヒートマップ
  • 最速・最遅ルートのトップ10
  • ルートレベルの例外パターン

例外モニタリングセクション

  • 種類・重要度別のアクティブな例外
  • 例外解決時間のトレンド
  • 配送業者固有の例外パターン

分析から得られる実践的なインサイト

以下は、発見される可能性のある実際のパターンの例です。

1. 配送業者選択の最適化

Carrier A: Avg 3.2 days, 94% on-time, $8.50/shipment
Carrier B: Avg 2.8 days, 91% on-time, $12.00/shipment
Carrier C: Avg 4.1 days, 97% on-time, $6.00/shipment

時間に敏感な配送には、Carrier Bが最速です。信頼性を伴うコスト最適化には、Carrier Cが最良の選択肢です。これらの判断が、仮定ベースではなくデータドリブンになります。

2. ルート別の配送業者パフォーマンス

ある配送業者が国内では優秀でも、国際配送では低い成績を示す場合があります。ルートレベルの分析でこれが明らかになります。

Carrier A: US → US: 2.1 days avg (excellent)
Carrier A: US → KR: 8.5 days avg (poor — competitors average 5.2 days)

3. 季節的なパターン

繁忙期、梅雨の時期、大規模セールイベントはすべて輸送時間に影響します。過去の分析データは以下の計画に役立ちます。

  • 繁忙期の配送業者キャパシティの確保
  • 遅延時のお客様へのコミュニケーション
  • 輸送距離を短縮するための在庫配置

始め方

  1. Webhookの収集を設定する — 今日から追跡イベントのキャプチャを始めましょう
  2. 指標を定義する — まず追跡すべき3〜5個の主要指標を選びましょう
  3. 基本的なクエリを構築する — 配送業者の比較と日別トレンドが最も価値が高い分析です
  4. 反復する — 追跡すべきパターンが見つかるにつれて、分析の軸を追加していきましょう

必要なデータはすでに追跡連携を通じて流れています。それを分析に変換することが、配送をコストセンターから競争優位性へと変革するステップです。

Webhookを使ったデータ収集の設定について詳しくは、Webhook ベストプラクティスガイドをご覧ください。