sophhub 0.4.62 → 0.4.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sophhub",
3
- "version": "0.4.62",
3
+ "version": "0.4.63",
4
4
  "description": "SophHub CLI - Manage and download AI Agent skills and agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,23 @@
1
1
  {
2
2
  "name": "flight-booking",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "types": [
5
5
  "builtin"
6
6
  ],
7
7
  "displayName": "国内机票预定",
8
8
  "description": "国内机票查询、下单、出票、退票、改签全流程 skill",
9
9
  "changelog": [
10
+ {
11
+ "changes": [
12
+ "修复创建订单/改签时 estimated_total 未计入平台服务费(serviceFee)的Bug",
13
+ "修复验舱验价(check_price)返回 code=10301 时实时价格未更新到 estimated_total 的Bug",
14
+ "新增价格变动提示:验价后票价变动时输出 priceChanged + priceDiff,供 Agent 向用户展示新旧价格对比",
15
+ "cmd_change_order 同步应用上述修复",
16
+ "创建订单/改签输出优先使用 API 返回的 totalAmount"
17
+ ],
18
+ "date": "2026-07-23",
19
+ "version": "1.3.0"
20
+ },
10
21
  {
11
22
  "changes": [
12
23
  "search 取消前 15 条截断,返回当日全部航班并在表格末尾新增汇总行",
@@ -32,5 +43,5 @@
32
43
  }
33
44
  ],
34
45
  "createdAt": "2026-04-09",
35
- "updatedAt": "2026-05-09"
46
+ "updatedAt": "2026-07-23"
36
47
  }
@@ -82,6 +82,41 @@ python3 {baseDir}/scripts/flight_booking.py create-order --flight-no CA1723 --ca
82
82
  - 输出:订单号 `orderNo`、PNR、超时取消时间、应付金额等;用于第三步。
83
83
  - **重要**:创建订单成功后**不要立即执行 pay-issue**。应先向用户展示上述信息,待用户确认后再执行第三步出票。
84
84
 
85
+ ### 价格变动提示
86
+
87
+ 创建订单时,验舱验价(`check_price`)可能发现实时票价与查询时不一致。脚本输出中的 `priceChanged` 字段标识是否发生价格变动:
88
+
89
+ - **`priceChanged: false`**:价格未变动,按正常流程展示订单信息,等待用户确认出票。
90
+ - **`priceChanged: true`**:价格已变动。`priceDiff` 字段包含新旧价格对比:
91
+
92
+ | priceDiff 字段 | 说明 |
93
+ |----------------|------|
94
+ | `originalSalePrice` | 查询时的票价(search 缓存) |
95
+ | `updatedSalePrice` | 实时票价(验价后) |
96
+ | `salePriceDiff` | 票价差额(正数为涨价,负数为降价) |
97
+ | `originalTotal` | 原订单总额(含税费服务费) |
98
+ | `updatedTotal` | 新订单总额(含税费服务费) |
99
+ | `totalDiff` | 总额差额 |
100
+ | `message` | API 返回的价格变动说明原文 |
101
+
102
+ **当 `priceChanged: true` 时,必须将价格变动明细以表格形式展示给用户**,例如:
103
+
104
+ > ⚠️ **价格变动提示**
105
+ >
106
+ > 验舱验价时发现票价已变动:
107
+ >
108
+ > | 项目 | 查询时 | 实时 |
109
+ > |------|--------|------|
110
+ > | 票价 | ¥{originalSalePrice} | ¥{updatedSalePrice} |
111
+ > | 差额 | | {salePriceDiff} |
112
+ > | 订单总额 | ¥{originalTotal} | ¥{updatedTotal} |
113
+ >
114
+ > 是否按新价格继续下单?
115
+
116
+ 用户确认新价格后再进入第三步出票流程。若用户不接受新价格,可重新 search 选择其他航班。
117
+
118
+ - **改签同样适用**:`change-order` 输出中同样包含 `priceChanged` 字段,处理方式一致。
119
+
85
120
  ## 第三步:支付前校验与申请出票
86
121
 
87
122
  **仅在用户明确确认要出票后再执行**(例如用户说「确认出票」「去支付」「可以出票了」等)。不得在创建订单后未经用户确认就自动执行本步骤。
@@ -800,16 +800,53 @@ def cmd_create_order(args: argparse.Namespace) -> int:
800
800
  return 1
801
801
 
802
802
  verify_res = check_price(flight, cabin, from_city, to_city)
803
- shopping_code = ""
804
- price_info = None
805
- if verify_res.get("code") == "0":
806
- vdata = verify_res.get("data", {})
803
+ vdata = verify_res.get("data") or {}
804
+
805
+ # 从验价结果中提取实时价格。check_price 返回 code="0"(成功)或 code="10301"(价格变动)
806
+ # 只要 data 中有 salePrice,就使用验价后的实时数据,覆盖 search 缓存的旧价格
807
+ if vdata.get("salePrice") is not None:
808
+ sale_price = float(vdata["salePrice"])
809
+ dep_tax = float(vdata.get("departureTax", flight.get("departureTax", 0)))
810
+ fuel_tax = float(vdata.get("fuelTax", flight.get("fuelTax", 0)))
807
811
  shopping_code = vdata.get("shoppingCode", cabin.get("shoppingCode", ""))
808
812
  price_info = vdata.get("priceInfo")
809
813
  else:
814
+ # 验价失败且无价格数据,回退到 search 缓存值
815
+ sale_price = float(cabin.get("salePrice", 0))
816
+ dep_tax = float(flight.get("departureTax", 0))
817
+ fuel_tax = float(flight.get("fuelTax", 0))
810
818
  shopping_code = cabin.get("shoppingCode", "")
819
+ price_info = None
820
+
821
+ # 服务费来源:航班顶层 serviceFee 或舱位层级 serviceFee(两者值相同,优先取 flight)
822
+ service_fee = float(flight.get("serviceFee", 0) or cabin.get("serviceFee", 0) or 0)
823
+ estimated_total = sale_price + dep_tax + fuel_tax + service_fee
824
+
825
+ # 价格变动检测:当 check_price 返回 code=10301 或 isUpdatePrice=true 时,记录新旧价格对比
826
+ price_changed = (
827
+ verify_res.get("code") == "10301"
828
+ or vdata.get("isUpdatePrice") is True
829
+ )
830
+ price_diff = None
831
+ if price_changed:
832
+ orig_sale = float(cabin.get("salePrice", 0))
833
+ orig_total = orig_sale \
834
+ + float(flight.get("departureTax", 0)) \
835
+ + float(flight.get("fuelTax", 0)) \
836
+ + float(flight.get("serviceFee", 0) or cabin.get("serviceFee", 0) or 0)
837
+ price_diff = {
838
+ "originalSalePrice": orig_sale,
839
+ "updatedSalePrice": sale_price,
840
+ "salePriceDiff": round(sale_price - orig_sale, 2),
841
+ "originalTotal": round(orig_total, 2),
842
+ "updatedTotal": round(estimated_total, 2),
843
+ "totalDiff": round(estimated_total - orig_total, 2),
844
+ "departureTax": dep_tax,
845
+ "fuelTax": fuel_tax,
846
+ "serviceFee": service_fee,
847
+ "message": verify_res.get("msg", ""),
848
+ }
811
849
 
812
- estimated_total = float(cabin.get("salePrice", 0)) + float(flight.get("departureTax", 0)) + float(flight.get("fuelTax", 0))
813
850
  order_res = create_order(
814
851
  flight_detail=flight,
815
852
  cabin=cabin,
@@ -829,13 +866,22 @@ def cmd_create_order(args: argparse.Namespace) -> int:
829
866
  }, ensure_ascii=False))
830
867
  return 1
831
868
  order_data = order_res.get("data", {})
832
- print(json.dumps({
869
+
870
+ # 输出:优先使用 API 返回的 totalAmount,否则用自行计算的 estimated_total
871
+ api_total = order_data.get("totalAmount")
872
+ display_total = float(api_total) if api_total is not None else estimated_total
873
+
874
+ output = {
833
875
  "ok": True,
834
876
  "orderNo": str(order_data.get("orderNo", "")),
835
877
  "pnrCode": order_data.get("pnrCode"),
836
878
  "expireTime": order_data.get("expireTime"),
837
- "paymentTotalAmount": estimated_total,
838
- }, ensure_ascii=False, indent=2))
879
+ "paymentTotalAmount": display_total,
880
+ "priceChanged": price_changed,
881
+ }
882
+ if price_diff:
883
+ output["priceDiff"] = price_diff
884
+ print(json.dumps(output, ensure_ascii=False, indent=2))
839
885
  return 0
840
886
 
841
887
 
@@ -1067,16 +1113,49 @@ def cmd_change_order(args: argparse.Namespace) -> int:
1067
1113
  return 1
1068
1114
 
1069
1115
  verify_res = check_price(flight, cabin, from_city, to_city)
1070
- shopping_code = ""
1071
- price_info = None
1072
- if verify_res.get("code") == "0":
1073
- vdata = verify_res.get("data", {})
1116
+ vdata = verify_res.get("data") or {}
1117
+
1118
+ # 从验价结果中提取实时价格。check_price 返回 code="0"(成功)或 code="10301"(价格变动)
1119
+ if vdata.get("salePrice") is not None:
1120
+ sale_price = float(vdata["salePrice"])
1121
+ dep_tax = float(vdata.get("departureTax", flight.get("departureTax", 0)))
1122
+ fuel_tax = float(vdata.get("fuelTax", flight.get("fuelTax", 0)))
1074
1123
  shopping_code = vdata.get("shoppingCode", cabin.get("shoppingCode", ""))
1075
1124
  price_info = vdata.get("priceInfo")
1076
1125
  else:
1126
+ sale_price = float(cabin.get("salePrice", 0))
1127
+ dep_tax = float(flight.get("departureTax", 0))
1128
+ fuel_tax = float(flight.get("fuelTax", 0))
1077
1129
  shopping_code = cabin.get("shoppingCode", "")
1130
+ price_info = None
1131
+
1132
+ service_fee = float(flight.get("serviceFee", 0) or cabin.get("serviceFee", 0) or 0)
1133
+ estimated_total = sale_price + dep_tax + fuel_tax + service_fee
1134
+
1135
+ price_changed = (
1136
+ verify_res.get("code") == "10301"
1137
+ or vdata.get("isUpdatePrice") is True
1138
+ )
1139
+ price_diff = None
1140
+ if price_changed:
1141
+ orig_sale = float(cabin.get("salePrice", 0))
1142
+ orig_total = orig_sale \
1143
+ + float(flight.get("departureTax", 0)) \
1144
+ + float(flight.get("fuelTax", 0)) \
1145
+ + float(flight.get("serviceFee", 0) or cabin.get("serviceFee", 0) or 0)
1146
+ price_diff = {
1147
+ "originalSalePrice": orig_sale,
1148
+ "updatedSalePrice": sale_price,
1149
+ "salePriceDiff": round(sale_price - orig_sale, 2),
1150
+ "originalTotal": round(orig_total, 2),
1151
+ "updatedTotal": round(estimated_total, 2),
1152
+ "totalDiff": round(estimated_total - orig_total, 2),
1153
+ "departureTax": dep_tax,
1154
+ "fuelTax": fuel_tax,
1155
+ "serviceFee": service_fee,
1156
+ "message": verify_res.get("msg", ""),
1157
+ }
1078
1158
 
1079
- estimated_total = float(cabin.get("salePrice", 0)) + float(flight.get("departureTax", 0)) + float(flight.get("fuelTax", 0))
1080
1159
  change_res = order_change(
1081
1160
  original_order_no=original_order_no,
1082
1161
  flight_detail=flight,
@@ -1097,14 +1176,22 @@ def cmd_change_order(args: argparse.Namespace) -> int:
1097
1176
  }, ensure_ascii=False))
1098
1177
  return 1
1099
1178
  change_data = change_res.get("data", {})
1100
- print(json.dumps({
1179
+
1180
+ api_total = change_data.get("totalAmount")
1181
+ display_total = float(api_total) if api_total is not None else estimated_total
1182
+
1183
+ output = {
1101
1184
  "ok": True,
1102
1185
  "changeOrderNo": str(change_data.get("orderNo", "")),
1103
1186
  "pnrCode": change_data.get("pnrCode"),
1104
1187
  "expireTime": change_data.get("expireTime"),
1105
- "paymentTotalAmount": estimated_total,
1188
+ "paymentTotalAmount": display_total,
1189
+ "priceChanged": price_changed,
1106
1190
  **rules,
1107
- }, ensure_ascii=False, indent=2))
1191
+ }
1192
+ if price_diff:
1193
+ output["priceDiff"] = price_diff
1194
+ print(json.dumps(output, ensure_ascii=False, indent=2))
1108
1195
  return 0
1109
1196
 
1110
1197