|
@@ -70,19 +70,54 @@ def _to_tokens(val: str) -> Optional[int]:
|
|
|
def _parse_price(obj: Any) -> Optional[float]:
|
|
def _parse_price(obj: Any) -> Optional[float]:
|
|
|
if isinstance(obj, (int, float)):
|
|
if isinstance(obj, (int, float)):
|
|
|
return float(obj)
|
|
return float(obj)
|
|
|
- if isinstance(obj, dict):
|
|
|
|
|
- v = obj.get("price")
|
|
|
|
|
|
|
+ if not isinstance(obj, dict):
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ # 优先取 price_original(原价),爬虫把折扣价和原价分开存
|
|
|
|
|
+ for key in ("price_original", "price", "price_current"):
|
|
|
|
|
+ v = obj.get(key)
|
|
|
if v is not None:
|
|
if v is not None:
|
|
|
try:
|
|
try:
|
|
|
return float(v)
|
|
return float(v)
|
|
|
except (TypeError, ValueError):
|
|
except (TypeError, ValueError):
|
|
|
- pass
|
|
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ # 嵌套结构:{"8元": {"price": 8}, "24元": {"price": 24}},取最大
|
|
|
|
|
+ candidates: List[float] = []
|
|
|
|
|
+ for sub_v in obj.values():
|
|
|
|
|
+ if isinstance(sub_v, dict):
|
|
|
|
|
+ p = _parse_price(sub_v)
|
|
|
|
|
+ if p is not None:
|
|
|
|
|
+ candidates.append(p)
|
|
|
|
|
+ elif isinstance(sub_v, list):
|
|
|
|
|
+ for item in sub_v:
|
|
|
|
|
+ if isinstance(item, dict):
|
|
|
|
|
+ p = _parse_price(item)
|
|
|
|
|
+ if p is not None:
|
|
|
|
|
+ candidates.append(p)
|
|
|
|
|
+ if candidates:
|
|
|
|
|
+ return max(candidates)
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_unit(obj: Any) -> Optional[str]:
|
|
def _parse_unit(obj: Any) -> Optional[str]:
|
|
|
if isinstance(obj, dict):
|
|
if isinstance(obj, dict):
|
|
|
- return obj.get("unit")
|
|
|
|
|
|
|
+ # 直接有 unit 字段
|
|
|
|
|
+ u = obj.get("unit")
|
|
|
|
|
+ if u:
|
|
|
|
|
+ return u
|
|
|
|
|
+ # 新格式:嵌套结构,递归搜索
|
|
|
|
|
+ for sub_v in obj.values():
|
|
|
|
|
+ if isinstance(sub_v, dict):
|
|
|
|
|
+ u = sub_v.get("unit")
|
|
|
|
|
+ if u:
|
|
|
|
|
+ return u
|
|
|
|
|
+ elif isinstance(sub_v, list):
|
|
|
|
|
+ for item in sub_v:
|
|
|
|
|
+ if isinstance(item, dict):
|
|
|
|
|
+ u = item.get("unit")
|
|
|
|
|
+ if u:
|
|
|
|
|
+ return u
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
@@ -149,10 +184,35 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
video_items: List[Dict] = []
|
|
video_items: List[Dict] = []
|
|
|
input_entry: Optional[Dict] = None
|
|
input_entry: Optional[Dict] = None
|
|
|
|
|
|
|
|
|
|
+ _CACHE_RE = re.compile(r"缓存|cache", re.I)
|
|
|
|
|
+
|
|
|
|
|
+ # 预扫描:是否存在阶梯 key
|
|
|
|
|
+ _has_tiers = any(_parse_tier_key(k) is not None for k in prices)
|
|
|
|
|
+
|
|
|
|
|
+ # 有阶梯时,预先提取顶层 "output"/"输出" 里的所有独立 output 价格
|
|
|
|
|
+ # 爬虫把所有 output 价格汇总在顶层 output key 里,需要按大小分配给各阶梯
|
|
|
|
|
+ _output_prices: List[float] = []
|
|
|
|
|
+ if _has_tiers:
|
|
|
|
|
+ for _ok in ("output", "输出"):
|
|
|
|
|
+ if _ok in prices and isinstance(prices[_ok], dict):
|
|
|
|
|
+ for _sub_v in prices[_ok].values():
|
|
|
|
|
+ _p = _parse_price(_sub_v)
|
|
|
|
|
+ if _p is not None:
|
|
|
|
|
+ _output_prices.append(_p)
|
|
|
|
|
+ break
|
|
|
|
|
+ _output_prices.sort() # 从小到大,与阶梯从低到高对应
|
|
|
|
|
+
|
|
|
for key, val in prices.items():
|
|
for key, val in prices.items():
|
|
|
|
|
+ # 跳过缓存相关条目
|
|
|
|
|
+ if _CACHE_RE.search(key):
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ if not isinstance(val, dict):
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
# ── token 阶梯 ──
|
|
# ── token 阶梯 ──
|
|
|
tier = _parse_tier_key(key)
|
|
tier = _parse_tier_key(key)
|
|
|
- if tier is not None and isinstance(val, dict):
|
|
|
|
|
|
|
+ if tier is not None:
|
|
|
entry: Dict = {
|
|
entry: Dict = {
|
|
|
"label": key,
|
|
"label": key,
|
|
|
"tier_min": tier[0],
|
|
"tier_min": tier[0],
|
|
@@ -165,6 +225,8 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
}
|
|
}
|
|
|
for sub_key, sub_val in val.items():
|
|
for sub_key, sub_val in val.items():
|
|
|
sk = sub_key.strip()
|
|
sk = sub_key.strip()
|
|
|
|
|
+ if _CACHE_RE.search(sk):
|
|
|
|
|
+ continue
|
|
|
price = _parse_price(sub_val)
|
|
price = _parse_price(sub_val)
|
|
|
unit = _parse_unit(sub_val)
|
|
unit = _parse_unit(sub_val)
|
|
|
if unit:
|
|
if unit:
|
|
@@ -173,10 +235,14 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
entry["input_price"] = price
|
|
entry["input_price"] = price
|
|
|
elif re.match(r"^输出|^output", sk, re.I):
|
|
elif re.match(r"^输出|^output", sk, re.I):
|
|
|
entry["output_price"] = price
|
|
entry["output_price"] = price
|
|
|
|
|
+ elif price is not None and entry["input_price"] is None:
|
|
|
|
|
+ # 无标签的纯价格 → 当作输入价格
|
|
|
|
|
+ entry["input_price"] = price
|
|
|
result.append(entry)
|
|
result.append(entry)
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
- if not isinstance(val, dict):
|
|
|
|
|
|
|
+ # 有阶梯时,顶层 output 已预提取,跳过
|
|
|
|
|
+ if _has_tiers and re.match(r"^(输出|output)$", key.strip(), re.I):
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
price = _parse_price(val)
|
|
price = _parse_price(val)
|
|
@@ -194,7 +260,6 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
"unit": unit,
|
|
"unit": unit,
|
|
|
})
|
|
})
|
|
|
else:
|
|
else:
|
|
|
- # 未知规格,直接输出
|
|
|
|
|
result.append({
|
|
result.append({
|
|
|
"label": key,
|
|
"label": key,
|
|
|
"tier_min": None,
|
|
"tier_min": None,
|
|
@@ -207,7 +272,7 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
})
|
|
})
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
- # ── 简单非阶梯(输入/输出) ──
|
|
|
|
|
|
|
+ # ── 简单非阶梯:输入 → 暂存,输出 → 配对 ──
|
|
|
if re.match(r"^输入|^input", key.strip(), re.I):
|
|
if re.match(r"^输入|^input", key.strip(), re.I):
|
|
|
input_entry = {"price": price, "unit": unit}
|
|
input_entry = {"price": price, "unit": unit}
|
|
|
continue
|
|
continue
|
|
@@ -237,7 +302,16 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
"unit": unit,
|
|
"unit": unit,
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- # 处理只有输入没有输出的情况
|
|
|
|
|
|
|
+ # 有阶梯时,把预提取的 output 价格按顺序分配给各阶梯
|
|
|
|
|
+ if _has_tiers and _output_prices:
|
|
|
|
|
+ # 按 tier_min 从小到大排序(与 output 价格从小到大对应)
|
|
|
|
|
+ tier_entries = [r for r in result if r.get("tier_unit") == "tokens"]
|
|
|
|
|
+ tier_entries.sort(key=lambda e: e["tier_min"] or 0)
|
|
|
|
|
+ for i, entry in enumerate(tier_entries):
|
|
|
|
|
+ if i < len(_output_prices) and entry["output_price"] is None:
|
|
|
|
|
+ entry["output_price"] = _output_prices[i]
|
|
|
|
|
+
|
|
|
|
|
+ # 只有输入没有输出 → 单独一条
|
|
|
if input_entry:
|
|
if input_entry:
|
|
|
result.append({
|
|
result.append({
|
|
|
"label": "input",
|
|
"label": "input",
|
|
@@ -250,7 +324,7 @@ def parse_prices(prices: Dict[str, Any]) -> List[Dict]:
|
|
|
"unit": input_entry["unit"],
|
|
"unit": input_entry["unit"],
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- # 把视频条目转成连续区间
|
|
|
|
|
|
|
+ # 视频条目转成连续区间
|
|
|
if video_items:
|
|
if video_items:
|
|
|
result.extend(_build_video_tiers(video_items))
|
|
result.extend(_build_video_tiers(video_items))
|
|
|
|
|
|