通过狗狗来解读 Route Optimization API 响应

欧洲经济区 (EEA) 开发者

生产注意事项

为简单起见,本指南使用 REST 和 JSON。不过,对于生产环境,总体建议是使用 gRPC 和客户端库,以获得更好的性能。如需了解详情,请参阅 Route Optimization API 客户端库。

在发出第一个请求中发送请求后,优化器会返回 OptimizeToursResponse。此响应是供司机遵循的路线方案。本指南将介绍如何解读该报告。

路线规划摘要

对于狗狗日托场景,优化器会将伯恩山犬和吉娃娃分配给您的货车,并跳过德国牧羊犬。您的司机的路线规划如下:

  • 上午 7:55 在日托中心开始轮班。
  • 上午 8:20 在科伊特塔附近接伯恩山犬。
  • 上午 8:58 在南日落游乐场公园接吉娃娃。
  • 在上午 9:24 返回托儿所,等待上午 9:30 的送达时间窗口开启。
  • 上午 9:30 送伯恩山犬。
  • 上午 9:35 送吉娃娃。
  • 上午 9:40 下班。

路线规划费用为 147.22 美元,其中包括因跳过德国牧羊犬而产生的 100 美元罚款。

如需检查结果,请将您的回答与完整示例回答进行比较。 您的结果可能会略有不同,因为行程时间和距离取决于当前道路数据。

完整响应示例

{
  "routes": [
    {
      "vehicleStartTime": "2024-02-13T15:55:04Z",
      "vehicleEndTime": "2024-02-13T17:40:00Z",
      "visits": [
        {
          "isPickup": true,
          "startTime": "2024-02-13T16:20:00Z",
          "detour": "0s",
          "shipmentLabel": "Bernese mountain dog",
          "visitType": "PICKUP_SHIPMENT"
        },
        {
          "shipmentIndex": 1,
          "isPickup": true,
          "startTime": "2024-02-13T16:58:12Z",
          "detour": "2600s",
          "shipmentLabel": "Chihuahua",
          "visitType": "PICKUP_SHIPMENT"
        },
        {
          "startTime": "2024-02-13T17:30:00Z",
          "detour": "2297s",
          "shipmentLabel": "Bernese mountain dog",
          "visitType": "DELIVER_SHIPMENT"
        },
        {
          "shipmentIndex": 1,
          "startTime": "2024-02-13T17:35:00Z",
          "detour": "656s",
          "shipmentLabel": "Chihuahua",
          "visitType": "DELIVER_SHIPMENT"
        }
      ],
      "transitions": [
        {
          "travelDuration": "1496s",
          "travelDistanceMeters": 7082,
          "waitDuration": "0s",
          "totalDuration": "1496s",
          "startTime": "2024-02-13T15:55:04Z"
        },
        {
          "travelDuration": "1992s",
          "travelDistanceMeters": 16589,
          "waitDuration": "0s",
          "totalDuration": "1992s",
          "startTime": "2024-02-13T16:25:00Z"
        },
        {
          "travelDuration": "1252s",
          "travelDistanceMeters": 7860,
          "waitDuration": "356s",
          "totalDuration": "1608s",
          "startTime": "2024-02-13T17:03:12Z"
        },
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2024-02-13T17:35:00Z"
        },
        {
          "travelDuration": "0s",
          "waitDuration": "0s",
          "totalDuration": "0s",
          "startTime": "2024-02-13T17:40:00Z"
        }
      ],
      "metrics": {
        "performedShipmentCount": 2,
        "travelDuration": "4740s",
        "waitDuration": "356s",
        "delayDuration": "0s",
        "breakDuration": "0s",
        "visitDuration": "1200s",
        "totalDuration": "6296s",
        "travelDistanceMeters": 31531,
        "performedShipmentPenaltyCostSum": 2000
      },
      "routeCosts": {
        "model.vehicles.cost_per_hour": 47.22
      },
      "routeTotalCost": 47.22,
      "vehicleFullness": {
        "maxFullness": 0.3497777777777778,
        "activeSpan": 0.3497777777777778
      }
    }
  ],
  "skippedShipments": [
    {
      "index": 2,
      "label": "German shepherd",
      "penaltyCost": 100
    }
  ],
  "metrics": {
    "aggregatedRouteMetrics": {
      "performedShipmentCount": 2,
      "travelDuration": "4740s",
      "waitDuration": "356s",
      "delayDuration": "0s",
      "breakDuration": "0s",
      "visitDuration": "1200s",
      "totalDuration": "6296s",
      "travelDistanceMeters": 31531,
      "performedShipmentPenaltyCostSum": 2000
    },
    "usedVehicleCount": 1,
    "earliestVehicleStartTime": "2024-02-13T15:55:04Z",
    "latestVehicleEndTime": "2024-02-13T17:40:00Z",
    "totalCost": 147.22,
    "costs": {
      "model.vehicles.cost_per_hour": 47.22,
      "model.shipments.penalty_cost": 100
    }
  }
}
    

回答结构

OptimizeToursResponse 包含一个 ShipmentRoute 对象(具有 visits 消息类型)和一个 Metrics 对象(具有 aggregatedRouteMetrics 和 totalCost)。

OptimizeToursResponse 包含两个主要组件:

  • routes:车队中每辆车的路线规划,包括每次取件和送件的 顺序和时间。您有一辆货车,因此有一个路线规划。
  • metrics:整个路线规划的摘要,包括总费用、时长和距离。

由于优化器会跳过德国牧羊犬,因此响应还包含 skippedShipments。

以下各部分介绍了在响应中何处可以找到路线方案摘要的每个详细信息。

哪辆车运行哪条路线?

优化器会生成一个路线方案(routes 数组),该方案与请求中的 vehicles 顺序相对应。

vehicleIndex 字段根据每辆车从零开始的位置来标识车辆,对于您的货车,该位置为 0。vehicleStartTime 和 vehicleEndTime 字段用于指示司机何时开始和结束轮班。

Route Optimization API 会返回世界协调时间 (UTC) 格式的时间戳,并带有 Z 后缀,无论请求中的偏移量如何。如需获取旧金山时间,请减去 8 小时。对于您的货车,班次从上午 7:55 运行到上午 9:40,如以下代码示例所示。

{
"routes": [
    {
    // "vehicleIndex": 0, // Omitted in actual JSON response because it's 0
    "vehicleStartTime": "2024-02-13T15:55:04Z",
    "vehicleEndTime": "2024-02-13T17:40:00Z",
    ...
    }
  ]
  ...
}

接送顺序是什么?

在每条路线中,visits 数组按时间顺序包含分配车辆的每次取件和送件。

每次造访的 shipmentIndex 是运单在请求中从零开始的位置:0 是伯恩山犬,1 是吉娃娃。shipmentLabel 字段会显示您为每批货件设置的标签。

如需区分接车和下车(或送货),您可以查看 visitType 或 isPickup 字段:

  • visitType:明确定义访问操作。
    • 自提:visitType = "PICKUP_SHIPMENT"
    • 交付日期:visitType = "DELIVER_SHIPMENT"
  • isPickup:一个布尔值辅助字段。
    • 自提:isPickup = true
    • Delivery:isPickup = false(在 JSON 中省略)

如需确定确切的事件序列以及每个地点的预计到达时间,请按顺序读取 visits 数组。下表显示了基于 shipmentIndex、isPickup 或 visitType 以及 startTime 字段的顺序和值的路由方案操作。

序列 shipmentIndex 值 isPickup 或 visitType 值 startTime 值(世界协调时间 [UTC]) 操作
首次访问 0(在 JSON 中省略) True 或 PICKUP_SHIPMENT 2024-02-13T16:20:00Z 上午 8:20 接伯恩山犬。
第二次访问 1 True 或 PICKUP_SHIPMENT 2024-02-13T16:58:12Z 上午 8:58 接吉娃娃。
第三次访问 0(在 JSON 中省略) False(省略)或 DELIVER_SHIPMENT 2024-02-13T17:30:00Z 上午 9:30 送伯恩山犬去狗狗日托中心。
第四次访问 1 False(省略)或 DELIVER_SHIPMENT 2024-02-13T17:35:00Z 上午 9:35 把吉娃娃送到狗狗日托中心。

以下代码示例展示了回答如何提供此序列:

{
  "routes": [
    {
      "visits": [
        {
          // "shipmentIndex": 0, // Omitted in actual JSON response because it's 0
          "isPickup": true,
          "startTime": "2024-02-13T16:20:00Z",
          "shipmentLabel": "Bernese mountain dog",
          "visitType": "PICKUP_SHIPMENT"
        },
        {
          "shipmentIndex": 1,
          "isPickup": true,
          "startTime": "2024-02-13T16:58:12Z",
          "shipmentLabel": "Chihuahua",
          "visitType": "PICKUP_SHIPMENT"
        },
        {
          // "shipmentIndex": 0, // Omitted in actual JSON response because it's 0
          // "isPickup": false, // Omitted in actual JSON response because it's false
          "startTime": "2024-02-13T17:30:00Z",
          "shipmentLabel": "Bernese mountain dog",
          "visitType": "DELIVER_SHIPMENT"
        },
        {
          "shipmentIndex": 1,
          // "isPickup": false, // Omitted in actual JSON response because it's false
          "startTime": "2024-02-13T17:35:00Z",
          "shipmentLabel": "Chihuahua",
          "visitType": "DELIVER_SHIPMENT"
        }
      ]
    }
  ]
}

每次造访还具有一个 visitRequestIndex,即优化器为运单选择的取件或送货选项的从零开始的位置。在此场景中,每只狗只有一个接送选项和一个送达选项,因此 visitRequestIndex 始终为 0,并且响应会省略它。

两次访问之间会发生什么?

在每条路线中,transitions 数组会列出各次到访之间的行程详情。虽然 visits 和 transitions 是单独的列表,但它们在时间上交错,如下所示:

  1. transitions[0](从起点到第一个到访地点的驾车路线)
  2. visits[0](首次访问)
  3. transitions[1](促成首次访问到第二次访问)
  4. visits[1](第二次访问)
  5. ...
  6. visits[N-1](最后一次访问,其中 N 是总访问次数)
  7. transitions[N](从上次到访地点驾车前往终点)

您可以通过查看两个数组中的 startTime 字段来跟踪路线的时间轴。过渡的 startTime 是指司机开始行程的时间,而访问的 startTime 是指司机开始接送或送达的时间。

每个过渡都会显示地点之间的预期驾车时间和距离。 下表将 transitions 和 visits 字段映射到宠物狗日托场景的按时间顺序排列的路线操作。

序列 字段名称 startTime 值(世界协调时间 [UTC]) 详细信息 操作
首次转换 transitions[0] 2024-02-13T15:55:04Z travelDuration:1496s
travelDistanceMeters:7082
从日托中心驱车 7 公里,耗时 25 分钟,去接伯恩山犬。班次于上午 7:55 开始。
首次访问 visits[0] 2024-02-13T16:20:00Z shipmentIndex:0(在 JSON 中省略)
isPickup:True
上午 8:20 开始接伯恩山犬。访问用时 5 分钟。
第二次转换 transitions[1] 2024-02-13T16:25:00Z travelDuration:1992s
travelDistanceMeters:16589
从科伊特塔出发,驾车 17 公里,用时 33 分钟,于上午 8:25 接上吉娃娃。
第二次访问 visits[1] 2024-02-13T16:58:12Z shipmentIndex:1
isPickup:True
上午 8:58 开始接吉娃娃。访问用时 5 分钟。
第三次转换 transitions[2] 2024-02-13T17:03:12Z travelDuration:1252s
travelDistanceMeters:7860
waitDuration:356s
上午 9:03 从南日落游乐场出发,行驶 8 公里,用时 21 分钟,返回日托中心。上午 9:24 到达,等待 6 分钟,直到下车时间段开始。
第三次访问 visits[2] 2024-02-13T17:30:00Z shipmentIndex:0(在 JSON 中省略)
isPickup:False(在 JSON 中省略)
从上午 9:30 开始,将伯恩山犬送到狗狗日托中心。访问用时 5 分钟。
第四次转换 transitions[3] 2024-02-13T17:35:00Z travelDuration:0s
travelDistanceMeters:0(在 JSON 中省略)
没有在开车。准备在上午 9:35 将吉娃娃送到宠物托管中心。
第四次访问 visits[3] 2024-02-13T17:35:00Z shipmentIndex:1
isPickup:False(在 JSON 中省略)
从上午 9:35 开始,将吉娃娃送到狗狗日托中心。访问需要 5 分钟。
第五次转换 transitions[4] 2024-02-13T17:40:00Z travelDuration:0s
travelDistanceMeters:0(在 JSON 中省略)
没有在开车。司机在上午 9:40 结束轮班。

以下代码示例展示了回答如何为 transitions[0] 和 transitions[1] 提供此序列:

{
  "routes": [
    {
      "transitions": [
        {
          "travelDuration": "1496s",
          "travelDistanceMeters": 7082,
          "waitDuration": "0s",
          "totalDuration": "1496s",
          "startTime": "2024-02-13T15:55:04Z"
        },
        {
          "travelDuration": "1992s",
          "travelDistanceMeters": 16589,
          "waitDuration": "0s",
          "totalDuration": "1992s",
          "startTime": "2024-02-13T16:25:00Z"
        },
        ...
      ]
    }
  ]
}

这条路线的费用是多少?

您可以查看响应中的多个字段,具体取决于您需要特定车辆的费用还是整个车队的总费用。

下表列出了在响应中查找费用信息的所有方法:

父级 参数 范围 说明
ShipmentRoute routeCosts 按路线 该特定路线的具体费用构成(例如每小时费用)明细。
routeTotalCost 按路由 为该特定路线计算的总费用。
Metrics totalCost 总计(所有路线) 解决方案中所有路线的总费用。
costs 总计(所有路线) 解决方案中所有路线的汇总费用组成部分的明细。

routeCosts 和 costs 中指定的费用反映了您在请求中指定的费用限制。如需了解详情,请参阅费用模型关键概念文档。

对于此狗狗日托场景,由于您跳过了一次配送,因此每条路线的费用和汇总总费用会有所不同。路线费用仅包含与该特定路线相关的费用(司机工资),而汇总总费用还包含跳过配送的惩罚。以下代码示例展示了如何在响应中找到这些值:

{
  "routes": [
    {
      "vehicleStartTime": "2024-02-13T15:55:04Z",
      "vehicleEndTime": "2024-02-13T17:40:00Z",
      ...
      "routeCosts": {
        "model.vehicles.cost_per_hour": 47.22
      },
      "routeTotalCost": 47.22
    }
  ],
  "metrics": {
    "totalCost": 147.22,
    "costs": {
      "model.shipments.penalty_cost": 100,
      "model.vehicles.cost_per_hour": 47.22
    },
    ...
  }
}

根据路线规划,司机在上午 7:55 至 9:40 期间驾驶货车,大约 1 小时 45 分钟(6,296 秒)。按照您设定的每小时 27 美元的费率,路线费用为 47.22 美元。由于优化器跳过了德国牧羊犬运输,因此它会增加 100 美元的罚款费用,使总费用达到 147.22 美元。

为什么跳过了某次发货?

优化器会在 skippedShipments 数组中列出其未执行的所有配送。跳过发货的原因取决于您是否设置了 penaltyCost:

  • 强制性配送(无 penaltyCost):优化工具绝不会因为这些配送的执行成本高昂而跳过它们。只有在无法将运单纳入任何路线时(例如,因为其时间窗口与其他运单冲突),它才会跳过该运单。
  • 可选配送(带有 penaltyCost):如果执行这些配送的费用高于罚金,优化器也可以跳过这些配送。您可以设置不同的惩罚费用,以便优先处理某些配送。

在此场景中,德国牧羊犬的接送时间窗口与伯恩山犬的时间窗口冲突。科伊特塔和兰兹角相距 25 分钟路程,因此司机无法在 20 分钟的时间范围内完成这两次接单。优化器必须跳过其中一个。由于德国牧羊犬的罚金较低(100 美元,而普通客户为 1,000 美元),优化器会跳过它并产生 100 美元的罚金。

以下代码示例展示了响应中的 skippedShipments 对象:

{
  ...
  "skippedShipments": [
    {
      "index": 2,
      "label": "German shepherd",
      "penaltyCost": 100
    }
  ],
  ...
}

响应中的 skippedShipments 对象包含以下字段:

  • index:请求中运单的从零开始的索引(第三个运单为 2)。
  • label:您在请求中指定的标签(“德国牧羊犬”)。
  • penaltyCost:您在请求中指定的惩罚费用,API 会将其添加到总费用中。

为防止优化器跳过运单,请放宽导致冲突的限制。例如,您可以向德国牧羊犬的主人询问更灵活的接送时间范围,然后在请求中更新该接送的 timeWindows。

示例中的响应字段

下表介绍了示例场景的 OptimizeToursResponse 中返回的所有响应字段。虽然您无需使用每个字段来了解路线规划,但您可以分析这些字段,以确定如何调整 OptimizeToursRequest 来满足您的业务需求。如需了解详情,请参阅路线规划生命周期。

您可以按父级或文本搜索过滤内容。

父级 参数 物业类型 说明
OptimizeToursResponse routes 对象数组 (ShipmentRoute) 其中包含为每辆车计算的路线列表(每辆车一条路线)。对于此狗狗日托场景,此字段包含单辆货车的详细路线规划。
metrics 对象 (Metrics) 其中包含解决方案的总体指标,这些指标是针对所有路线汇总的。对于这个狗狗日托场景,这是当天的费用和运营时长摘要。
skippedShipments 对象数组 (SkippedShipment) 其中包含解决方案中跳过的配送列表。 对于此场景,第三只狗(德国牧羊犬)会被跳过。
ShipmentRoute vehicleStartTime 时间戳 这是车辆开始其路线的时间。对于此狗狗日托场景,司机于上午 7:55 开始驾驶面包车。
vehicleEndTime 时间戳 这是车辆完成其路线的时间。对于此狗狗日托场景,司机在上午 9:40 完成了货车驾驶。
visits 对象数组 (Visit) 这是路线上的取件和送件的有序列表。对于此宠物狗日托场景,这是接送两只宠物狗的顺序。
transitions 对象数组 (Transition) 这是距离以及两次到访之间的行程时间和等待时间的有序列表。对于此狗狗日托场景,此信息详细说明了科伊特塔、南日落游乐场公园和日托中心之间的驾车距离和时间。
metrics 对象 (AggregatedMetrics) 这些是此单一路线特有的指标。对于这个狗狗日托场景,这些是这辆特定货车的统计数据。这些指标基于您在请求中指定的费用。如需了解详情,请参阅费用模型关键概念文档。
routeCosts 对象(地图) 这是此路线的具体费用明细。对于这个狗狗日托场景,此图表显示了根据司机每小时 27 美元的工资计算出的费用。
routeTotalCost 数字 这是此特定路线的总费用。对于这个狗狗日托场景,这辆货车路线的总费用为 47.22。
vehicleFullness 对象 (VehicleFullness) 此指标细分了车辆接近其运营限制(例如时间、距离和负载)的程度。对于此狗狗日托场景,只有班次开始和结束时间会限制司机,因此优化器会将 vehicleFullness 计算为实际路线时长与司机班次可用时间上限的比值。
Visit shipmentIndex 整数 这是原始请求中运单的索引(从零开始)。对于此狗狗日托场景,0 表示伯恩山犬,1 表示吉娃娃。
isPickup 布尔值 如果此到访是取货,则此字段为 true。对于此狗狗日托场景,在接狗狗时,此值为 true;在将狗狗送到日托中心时,此值为 false(或缺失)。
startTime 时间戳 这是预计的到访开始时间。对于这个狗狗日托场景,这就是司机敲门的时候。
detour 时长 这是优化器为此特定配送行程添加的额外时间。绕行是指实际路线规划与理想直达行程之间的差异(如果司机没有其他配送或任务要完成)。

对于此狗狗日托场景,第一次访问(即接伯恩山犬)的绕行距离为 0,因为司机直接从日托中心前往此接送点。对于吉娃娃接送,绕行时间为 43 分钟,因为司机先接伯恩山犬(并花费 5 分钟装载)。
shipmentLabel 字符串 这是每个配送请求中的标签。对于这个狗狗日托场景,标签为“吉娃娃”或“伯恩山犬”。
visitType 字符串(枚举) 这表示访问类型,可以是 PICKUP_SHIPMENT(表示取货)或 DELIVER_SHIPMENT(表示送货)。对于此场景,该值为 PICKUP_SHIPMENT 或 DELIVER_SHIPMENT。
Transition travelDuration 时长 这是在两次访问之间的过渡期间花费的行程时间。例如,在这个狗狗日托场景中,司机从日托中心开车到科伊特塔接伯恩山犬大约需要 25 分钟。
travelDistanceMeters 整数 这是在此转换期间行进的距离(以米为单位)。例如,在这个狗狗日托场景中,司机从日托中心行驶 7 公里到科伊特塔,接上伯恩山犬。
waitDuration 时长 这是在下次取餐或送餐之前等待的时间。 对于此狗狗日托场景,第三次转换的等待时间为 6 分钟(356 秒),因为司机在上午 9:30 狗狗日托中心开门之前就到达了。对于其他转换,此值为 0。
totalDuration 时长 这是过渡的总时长,包括驾驶和等待时间。例如,司机在第一次过渡时花费 25 分钟(25 分钟驾驶,0 分钟等待),在第三次过渡返回托儿所时花费 27 分钟(21 分钟驾驶,6 分钟等待)。
startTime 时间戳 这是行程开始的时间。它与上一次到访的完成时间(对于第一次过渡,则为车辆的开始时间)一致。对于狗狗日托场景,第一次转换从上午 7:55 开始,此时司机离开日托中心;第二次转换从上午 8:25 开始,此时司机离开科伊特塔。
Metrics aggregatedRouteMetrics 对象 (AggregatedMetrics) 这是所有路线的指标总和。对于这个狗狗日托场景,这些是您一辆货车的总指标。
usedVehicleCount 整数 这是解决方案中使用的车辆数量。对于此狗狗日托场景,您只需使用一辆货车。
earliestVehicleStartTime 时间戳 这是所有车辆中最早的开始时间。对于此狗狗日托场景,货车的路线从上午 7:55 开始。
latestVehicleEndTime 时间戳 这是所有车辆中最晚的结束时间。对于此狗狗日托场景,货车的路线在上午 9:40 结束。
totalCost 数字 这是解决方案的总费用。对于此狗狗日托场景,整个路线规划当天的费用为 147.22(包括 47.22 司机工资和 100 跳过配送罚金)。
costs 对象(地图) 这是按类别划分的总费用明细。对于此狗狗日托场景,优化器根据每小时费用和跳过德国牧羊犬的惩罚费用计算了路线费用。
AggregatedMetrics performedShipmentCount 整数 这是成功配送的货件数量。在此狗狗日托场景中,司机运送两只狗狗。
travelDuration 时长 这是总行程时间。对于此狗狗日托场景,司机需要行驶 79 分钟。
waitDuration 时长 这是总等待时间。对于此狗狗日托场景,司机在日托中心闲置 6 分钟(356 秒),等待下车窗口打开。
delayDuration 时长 这是您使用过渡属性定义的额外时长,例如预留的停车时间。对于此狗狗日托场景,您未在请求中定义任何过渡属性,因此该值为零。
breakDuration 时长 这是休息的总时长。对于这个狗狗日托场景,没有休息时间。
visitDuration 时长 这是在访问期间执行服务所花费的总时间。对于此狗狗日托场景,此值为 20 分钟(每次访问 5 分钟,共 4 次访问)。
totalDuration 时长 这是所有时长组成部分的总和。对于此狗狗日托场景,总时长为 105 分钟(6296 秒)。
travelDistanceMeters 整数 这是总行程距离(以米为单位)。对于此狗狗日托场景,司机行驶的总距离为 31.5 公里。
performedShipmentPenaltyCostSum 双精度 这是所有已执行配送的惩罚费用之和。对于此场景,该值为 2000,因为司机接载了伯恩山犬和吉娃娃(每只的惩罚费用为 1000)。
VehicleFullness maxFullness 数字 这是所有 VehicleFullness 字段中的最高利用率。对于此狗狗日托场景,此值为 0.35,因为司机在整个早班(您在请求中定义为 5 小时)中仅操作货车 105 分钟。
activeSpan 数字 这是司机的总班次时长与路线规划的营业时间之比。数值较低表示司机在工作日内操作车辆的时间较少。对于此狗狗日托场景,该值为 0.35,因为司机在 5 小时轮班期间驾驶货车 105 分钟。
SkippedShipment index 整数 这是原始请求中运单的索引(从零开始)。 在此场景中,2 代表德国牧羊犬。
label 字符串 这是您在请求中为配送指定的标签。
penaltyCost 数字 这是优化器跳过此配送时您产生的惩罚费用。它与您在请求中指定的 penaltyCost 匹配。