From c3ed03c92fda864590107038bf287d6697f61e75 Mon Sep 17 00:00:00 2001 From: "1173117610@qq.com" Date: Fri, 26 Jun 2026 16:34:34 +0800 Subject: [PATCH] =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/AI_INTEGRATION_GUIDE.md | 27 +++- docs/AI_RESPONSE_INTERPRETATION_GUIDE.md | 84 ++++++++++- src/Tools/AnalyzeOfflineOrdersTool.php | 170 ++++++++++++++++++++++- 3 files changed, 271 insertions(+), 10 deletions(-) diff --git a/docs/AI_INTEGRATION_GUIDE.md b/docs/AI_INTEGRATION_GUIDE.md index 397824c..0584c19 100644 --- a/docs/AI_INTEGRATION_GUIDE.md +++ b/docs/AI_INTEGRATION_GUIDE.md @@ -60,7 +60,7 @@ POST /api/offline-stats 对应工具:`get_offline_stats` -用途:订单状态分布、支付方式分布、订单类型分布、平均订单金额、平均实付金额、最大订单金额。 +用途:订单状态分布、支付方式分布、订单类型分布、营销中台分布、退款状态分布、抵扣金额、手续费、`diamond` 字段代币值汇总、平均订单金额、平均实付金额、最大订单金额、活跃商户数、实付金额 Top 商户。 ### 3.3 商户排行榜 @@ -204,7 +204,30 @@ AI 默认不要使用 `date`,除非调用平台只能传固定后台日期格 | `type` | string | 订单类型,对应后台 `type` | | `btcId` | integer | 商户分类 ID,对应后台 `btc_id` | -### 7.1 订单状态 `status` 取值 +### 7.1 `diamond` 字段与 `btc_id` 的关系 + +后台订单里存在 `diamond` 字段。该字段不是固定只表示“钻石”,需要结合 `btc_id` 判断业务含义: + +| btc_id | `diamond` 字段含义 | +| --- | --- | +| `1` | 钻石值 | +| `3` | 绿宝石值 | +| `4` | 算力值 | + +`/api/offline-stats` 会返回: + +| 字段 | 含义 | +| --- | --- | +| `totalTokenValue` | 所有订单 `diamond` 字段本身的合计 | +| `tokenValueSummary` | 按 `btc_id` 解释后的 `diamond` 字段汇总,例如钻石值、绿宝石值、算力值 | + +注意: + +- `diamond` 表示本单关联/附赠/记录的代币值。 +- `diamond_ded` 表示消费券/钻石抵扣金额。 +- 两者不是同一个字段,不要混淆。 + +### 7.2 订单状态 `status` 取值 | status | 含义 | 用户常见说法 | | --- | --- | --- | diff --git a/docs/AI_RESPONSE_INTERPRETATION_GUIDE.md b/docs/AI_RESPONSE_INTERPRETATION_GUIDE.md index 11daeec..531e489 100644 --- a/docs/AI_RESPONSE_INTERPRETATION_GUIDE.md +++ b/docs/AI_RESPONSE_INTERPRETATION_GUIDE.md @@ -133,21 +133,52 @@ AI 必须严格遵守: "data": { "orderCount": 100, "statusDistribution": { - "1": 47, - "2": 10 + "1 已完成": 47, + "2 已取消": 10 }, "payTypeDistribution": { - "wechat": 50, - "balance": 20 + "7 蜜传微信支付": 50, + "2 微信支付": 20 }, "typeDistribution": { - "offline": 100 + "0 扫码订单": 70, + "5 视频团单": 30 + }, + "btcDistribution": { + "3 绿宝石中台(绿宝石)": 80, + "4 算力中台(算力)": 20 + }, + "refundDistribution": { + "0 未退款": 95, + "1 已退款": 5 }, "totalOrderAmount": 882.89, "totalRealPay": 662.39, + "totalCoinDeduction": 100.00, + "totalBrokerDeduction": 10.00, + "totalDiamondDeduction": 20.00, + "totalTokenValue": 123.456789, + "tokenValueSummary": { + "diamond 钻石值": 10.000000, + "diamond 绿宝石值": 100.000000, + "diamond 算力值": 13.456789 + }, + "totalPreferentialAmount": 5.00, + "totalMerchantRatePrice": 2.50, + "totalDeductionAmount": 135.00, "averageOrderAmount": 8.83, "averageRealPay": 6.62, "maxOrderAmount": 99.99, + "activeMerchantCount": 5, + "topMerchantsByRealPay": [ + { + "merchantId": 110, + "merchantName": "示例商户", + "orderCount": 20, + "totalOrderAmount": 200.00, + "totalRealPay": 180.00 + } + ], "_debug_query": { "time": "2026/06/18-2026/06/24" } @@ -163,11 +194,23 @@ AI 必须严格遵守: | `statusDistribution` | 订单状态分布 | | `payTypeDistribution` | 支付方式分布 | | `typeDistribution` | 订单类型分布 | +| `btcDistribution` | 营销代币中台分布,如积分、钻石、绿宝石、算力 | +| `refundDistribution` | 退款状态分布 | | `totalOrderAmount` | 订单总金额 | | `totalRealPay` | 实付总金额 | +| `totalCoinDeduction` | 抵用券/积分抵扣金额合计,对应 `coin_ded` | +| `totalBrokerDeduction` | 佣金抵扣金额合计,对应 `broker_ded` | +| `totalDiamondDeduction` | 消费券/钻石抵扣金额合计,对应 `diamond_ded` | +| `totalTokenValue` | `diamond` 字段本身的总值,不是抵扣金额 | +| `tokenValueSummary` | 按 `btc_id` 解释后的 `diamond` 字段汇总 | +| `totalPreferentialAmount` | 平台让利抵扣券金额合计,对应 `pr_amount` | +| `totalMerchantRatePrice` | 商户承担通道手续费合计,对应 `merchant_rate_price` | +| `totalDeductionAmount` | 抵扣类金额合计,包含抵用券、佣金、消费券、平台让利抵扣券 | | `averageOrderAmount` | 平均订单金额 | | `averageRealPay` | 平均实付金额 | | `maxOrderAmount` | 最大订单金额 | +| `activeMerchantCount` | 当前返回数据中的活跃商户数量 | +| `topMerchantsByRealPay` | 按实付金额排序的前 10 个商户 | 状态值参考: @@ -191,9 +234,18 @@ AI 必须严格遵守: - 订单数:{orderCount} 单 - 订单总金额:{totalOrderAmount} - 实付总金额:{totalRealPay} +- 抵用券/积分抵扣:{totalCoinDeduction} +- 佣金抵扣:{totalBrokerDeduction} +- 消费券/钻石抵扣:{totalDiamondDeduction} +- diamond 字段总值:{totalTokenValue} +- diamond 字段分中台汇总:{tokenValueSummary 中逐项列出} +- 平台让利抵扣券:{totalPreferentialAmount} +- 通道手续费:{totalMerchantRatePrice} +- 抵扣合计:{totalDeductionAmount} - 平均订单金额:{averageOrderAmount} - 平均实付金额:{averageRealPay} - 最大订单金额:{maxOrderAmount} +- 活跃商户数:{activeMerchantCount} 状态分布: - {statusDistribution 中逐项列出} @@ -204,9 +256,31 @@ AI 必须严格遵守: 订单类型分布: - {typeDistribution 中逐项列出} +营销中台分布: +- {btcDistribution 中逐项列出} + +退款状态分布: +- {refundDistribution 中逐项列出} + +实付金额 Top 商户: +- {topMerchantsByRealPay 中逐项列出} + 注意:以上结果基于接口本次返回的数据统计。 ``` +`diamond` 字段业务解释规则: + +| btc_id | `diamond` 字段含义 | +| --- | --- | +| `1` | 钻石值 | +| `3` | 绿宝石值 | +| `4` | 算力值 | + +注意:`diamond` 和 `diamond_ded` 不是同一个含义。 + +- `diamond`:本单关联/附赠/记录的代币值,需结合 `btc_id` 判断是钻石、绿宝石还是算力。 +- `diamond_ded`:消费券/钻石抵扣金额,属于抵扣金额。 + ## 6. 商户排行榜 JSON 字段说明 接口:`POST /api/merchant-ranking` diff --git a/src/Tools/AnalyzeOfflineOrdersTool.php b/src/Tools/AnalyzeOfflineOrdersTool.php index 98096bd..79c2d0f 100644 --- a/src/Tools/AnalyzeOfflineOrdersTool.php +++ b/src/Tools/AnalyzeOfflineOrdersTool.php @@ -32,37 +32,103 @@ final readonly class AnalyzeOfflineOrdersTool $statusDistribution = []; $payTypeDistribution = []; $typeDistribution = []; + $btcDistribution = []; + $refundDistribution = []; + $merchantStats = []; $totalOrderAmount = 0.0; $totalRealPay = 0.0; + $totalCoinDeduction = 0.0; + $totalBrokerDeduction = 0.0; + $totalDiamondDeduction = 0.0; + $totalTokenValue = 0.0; + $tokenValueSummary = []; + $totalPreferentialAmount = 0.0; + $totalMerchantRatePrice = 0.0; $maxOrderAmount = 0.0; foreach ($orders as $order) { - $statusKey = (string) ($order['status'] ?? 'unknown'); - $payTypeKey = (string) ($order['pay_type'] ?? 'unknown'); - $typeKey = (string) ($order['type'] ?? 'unknown'); + $statusKey = $this->statusLabel($order); + $payTypeKey = $this->payTypeLabel($order['pay_type'] ?? null); + $typeKey = $this->orderTypeLabel($order['type'] ?? null, $order['is_backend'] ?? null); + $btcKey = $this->btcLabel($order['btc_id'] ?? null, $order['btc_type'] ?? null); + $refundKey = $this->refundLabel($order['refund_status'] ?? null); $orderAmount = (float) ($order['order_price'] ?? 0); $realPay = (float) ($order['real_pay'] ?? 0); + $coinDeduction = (float) ($order['coin_ded'] ?? 0); + $brokerDeduction = (float) ($order['broker_ded'] ?? 0); + $diamondDeduction = (float) ($order['diamond_ded'] ?? 0); + $tokenValue = (float) ($order['diamond'] ?? 0); + $preferentialAmount = (float) ($order['pr_amount'] ?? 0); + $merchantRatePrice = (float) ($order['merchant_rate_price'] ?? 0); + $merchantId = (int) ($order['merchant_id'] ?? 0); + $tokenValueKey = $this->tokenValueLabel($order['btc_id'] ?? null); $statusDistribution[$statusKey] = ($statusDistribution[$statusKey] ?? 0) + 1; $payTypeDistribution[$payTypeKey] = ($payTypeDistribution[$payTypeKey] ?? 0) + 1; $typeDistribution[$typeKey] = ($typeDistribution[$typeKey] ?? 0) + 1; + $btcDistribution[$btcKey] = ($btcDistribution[$btcKey] ?? 0) + 1; + $refundDistribution[$refundKey] = ($refundDistribution[$refundKey] ?? 0) + 1; $totalOrderAmount += $orderAmount; $totalRealPay += $realPay; + $totalCoinDeduction += $coinDeduction; + $totalBrokerDeduction += $brokerDeduction; + $totalDiamondDeduction += $diamondDeduction; + $totalTokenValue += $tokenValue; + $tokenValueSummary[$tokenValueKey] = round(($tokenValueSummary[$tokenValueKey] ?? 0) + $tokenValue, 6); + $totalPreferentialAmount += $preferentialAmount; + $totalMerchantRatePrice += $merchantRatePrice; $maxOrderAmount = max($maxOrderAmount, $orderAmount); + + if ($merchantId > 0) { + if (!isset($merchantStats[$merchantId])) { + $merchantStats[$merchantId] = [ + 'merchantId' => $merchantId, + 'merchantName' => (string) ($order['merchant_name'] ?? ''), + 'orderCount' => 0, + 'totalOrderAmount' => 0.0, + 'totalRealPay' => 0.0, + ]; + } + + $merchantStats[$merchantId]['orderCount']++; + $merchantStats[$merchantId]['totalOrderAmount'] += $orderAmount; + $merchantStats[$merchantId]['totalRealPay'] += $realPay; + } } $orderCount = count($orders); + $topMerchants = array_values($merchantStats); + + usort($topMerchants, static fn (array $left, array $right): int => $right['totalRealPay'] <=> $left['totalRealPay']); + $topMerchants = array_slice($topMerchants, 0, 10); + + foreach ($topMerchants as $index => $merchant) { + $topMerchants[$index]['totalOrderAmount'] = round((float) $merchant['totalOrderAmount'], 2); + $topMerchants[$index]['totalRealPay'] = round((float) $merchant['totalRealPay'], 2); + } return [ 'orderCount' => $orderCount, 'statusDistribution' => $statusDistribution, 'payTypeDistribution' => $payTypeDistribution, 'typeDistribution' => $typeDistribution, + 'btcDistribution' => $btcDistribution, + 'refundDistribution' => $refundDistribution, 'totalOrderAmount' => round($totalOrderAmount, 2), 'totalRealPay' => round($totalRealPay, 2), + 'totalCoinDeduction' => round($totalCoinDeduction, 2), + 'totalBrokerDeduction' => round($totalBrokerDeduction, 2), + 'totalDiamondDeduction' => round($totalDiamondDeduction, 2), + 'totalTokenValue' => round($totalTokenValue, 6), + 'tokenValueSummary' => $tokenValueSummary, + 'totalPreferentialAmount' => round($totalPreferentialAmount, 2), + 'totalMerchantRatePrice' => round($totalMerchantRatePrice, 2), + 'totalDeductionAmount' => round($totalCoinDeduction + $totalBrokerDeduction + $totalDiamondDeduction + $totalPreferentialAmount, 2), 'averageOrderAmount' => $orderCount > 0 ? round($totalOrderAmount / $orderCount, 2) : 0, 'averageRealPay' => $orderCount > 0 ? round($totalRealPay / $orderCount, 2) : 0, 'maxOrderAmount' => round($maxOrderAmount, 2), + 'activeMerchantCount' => count($merchantStats), + 'topMerchantsByRealPay' => $topMerchants, '_debug_query' => $query, ]; } @@ -75,4 +141,102 @@ final readonly class AnalyzeOfflineOrdersTool ]; } + /** + * @param array $order + */ + private function statusLabel(array $order): string + { + $status = (int) ($order['status'] ?? -999); + $refundStatus = (int) ($order['refund_status'] ?? 0); + + if ($status === 1 && $refundStatus === 1) { + return '1 已完成(已退款)'; + } + + if ($status === 1 && $refundStatus > 1) { + return '1 已完成(部分退款)'; + } + + return match ($status) { + 0 => '0 待付款', + 1 => '1 已完成', + 2 => '2 已取消', + 3 => '3 待使用/待核销', + 4 => '4 已退款', + default => 'unknown 未知状态', + }; + } + + private function payTypeLabel(mixed $payType): string + { + return match ((int) $payType) { + 0 => '0 中台微信', + 1 => '1 中台支付宝', + 2 => '2 微信支付', + 3 => '3 公众号支付', + 4 => '4 中台公众号', + 7 => '7 蜜传微信支付', + default => 'unknown 未知支付渠道', + }; + } + + private function orderTypeLabel(mixed $type, mixed $isBackend): string + { + $orderType = (int) $type; + + if ($orderType === 0 && (int) $isBackend === 1) { + return '0 后台扫码订单'; + } + + if ($orderType === 0) { + return '0 扫码订单'; + } + + return match ($orderType) { + 1 => '1 点餐订单', + 2 => '2 团购订单', + 3 => '3 收银系统订单', + 4 => '4 曹操出行订单', + 5 => '5 视频团单', + default => 'unknown 未知订单类型', + }; + } + + private function btcLabel(mixed $btcId, mixed $btcType): string + { + $label = match ((int) $btcId) { + 0 => '0 积分中台', + 1 => '1 钻石中台', + 3 => '3 绿宝石中台', + 4 => '4 算力中台', + default => 'unknown 未知中台', + }; + + if ($btcType !== null && trim((string) $btcType) !== '') { + return $label . '(' . $btcType . ')'; + } + + return $label; + } + + private function tokenValueLabel(mixed $btcId): string + { + return match ((int) $btcId) { + 1 => 'diamond 钻石值', + 3 => 'diamond 绿宝石值', + 4 => 'diamond 算力值', + default => 'diamond 其他中台值', + }; + } + + private function refundLabel(mixed $refundStatus): string + { + return match ((int) $refundStatus) { + 0 => '0 未退款', + 1 => '1 已退款', + 2 => '2 部分退款', + default => 'unknown 未知退款状态', + }; + } + }