배송 추적 데이터로 물류 분석 대시보드 구축하기

원시 추적 이벤트는 물류 인텔리전스의 보물 창고입니다 — 분석 방법만 알면 됩니다. 추적 데이터 위에 분석 대시보드를 구축하면 성과가 낮은 택배사를 식별하고, 배송 경로를 최적화하며, 배송 시간을 단축할 수 있습니다. 이 튜토리얼에서는 실용적인 물류 분석 시스템 구축 과정을 안내합니다.

무엇을 측정해야 하는가

코드를 작성하기 전에 핵심이 되는 지표를 정의합니다:

배송 핵심 지표

지표계산식중요한 이유
평균 운송 시간운송 일수 합계 / 총 배송 건수배송 속도의 기준선
정시 배송률정시 배송 건수 / 총 배송 건수 x 100택배사 신뢰도 지표
예외 발생률예외 발생 건수 / 총 배송 건수 x 100배송 경험 품질
첫 번째 시도 성공률첫 시도 배송 완료 / 총 배송 건수 x 100라스트마일 효율성

비즈니스 지표

지표계산식중요한 이유
WISMO 비율추적 문의 / 총 배송 건수 x 100고객 경험 품질
배송 건당 택배 비용총 택배 비용 / 성공 배송 건수비용 효율성
반품 배송률반품 건수 / 총 배송 건수 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 이전 기간)
  • 평균 운송 시간 추이
  • 전체 정시 배송률
  • 현재 활성 예외 건수

택배사 성과 섹션

  • 택배사 간 성과 비교 테이블
  • 택배사별 운송 시간 분포 차트
  • 시간 경과에 따른 택배사별 예외 발생률

경로 분석 섹션

  • 출발지/도착지별 운송 시간 히트맵
  • 가장 빠른/느린 경로 Top 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 모범 사례 가이드를 참고하세요.