Files
projectAIpopular/gateway/proxy/pricing.py
T
tzt a0afeec5cb feat(proxy): T-P3 计价(峰谷窗口/毫元整数/差异化售价/黄金用例)
- pricing.py:is_offpeak(含跨午夜窗口、非法回落、±1min 边界语义);
  compute 全整数毫元——上游成本叠加 offpeak_factor(空闲半价),
  学生售价按差异化折扣(in 0.5 / out 0.8,不叠加热闲系数,全天一口价),
  margin = charged - cost 恒等式
- 未配置价格模型三值零(调用方可拦截);配置换算加载期唯一
- 测试 +9(窗口边界/跨午夜/回落/黄金手算 #1#2/恒等式 10 组扫/零用量/
  稳定性/未知模型/换算不漂移),全量 354 passed
2026-09-05 09:18:51 +08:00

82 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""计价(T-P3):峰谷窗口 + 毫元整数计算。
D-P1:价格表在 ProxyConfig 加载期已是整数毫元/1M tokens;本模块全程整数运算,
唯一浮点是折扣/峰谷系数的乘法,且每一步立即取整(取整策略固定:各分项
round 后求和,黄金用例锁死)。
毛利恒等式:margin_milli = charged_milli - upstream_cost_milli(测试断言)。
"""
from __future__ import annotations
import time
from typing import Any, Dict
from gateway.proxy.ledgerutil import YUAN_TO_MILLI
def _hhmm_to_minutes(text: str, fallback: int) -> int:
parts = str(text or "").strip().split(":")
if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
h, m = int(parts[0]), int(parts[1])
if 0 <= h <= 23 and 0 <= m <= 59:
return h * 60 + m
return fallback
def is_offpeak(ts: float, peak_start: str = "08:30",
peak_end: str = "23:59") -> bool:
"""峰谷窗口判定(本地时区 HH:MM;窗口含头含尾:start<=t<=end 为高峰)。
支持跨午夜窗口(如 start=23:00, end=06:00)。
"""
lt = time.localtime(ts)
cur = lt.tm_hour * 60 + lt.tm_min
s = _hhmm_to_minutes(peak_start, 8 * 60 + 30)
e = _hhmm_to_minutes(peak_end, 23 * 60 + 59)
if s <= e:
return not (s <= cur <= e)
# 跨午夜:高峰 = [s, 24h) [0, e]
return not (cur >= s or cur <= e)
def _apply_factor(milli: int, factor: float) -> int:
return int(round(milli * factor))
def compute(usage: Dict[str, int], model: str, ts: float, cfg) -> Dict[str, int]:
"""按 usage 计费(§6 签名)-> CostBreakdown(全整数毫元)。
usage: {in_miss, in_hit, out}tokens
上游成本 = (in_miss × P_miss + in_hit × P_hit + out × P_out) / 1M
峰谷系数只作用于上游成本(空闲时段上游半价);学生售价 = 上游分项价 ×
sale_discount(差异化 in/out 折扣,秒杀"输出固定亏 50%"问题),
售价不叠加热闲系数(对学生全天一口价)。
"""
price = cfg.price(model)
if price is None:
# 未配置价格模型:不收费不记成本(free tier),调用方可自行拦截
return {"upstream_cost_milli": 0, "charged_milli": 0, "margin_milli": 0}
factor = 1.0 if not is_offpeak(ts, cfg.peak_start, cfg.peak_end) else cfg.offpeak_factor
# ---- 上游成本(毫元,每 1M tokens = 1e6----
in_miss_cost = usage.get("in_miss", 0) * price.in_miss / 1_000_000
in_hit_cost = usage.get("in_hit", 0) * price.in_hit / 1_000_000
out_cost = usage.get("out", 0) * price.out / 1_000_000
cost = _apply_factor(in_miss_cost + in_hit_cost + out_cost, factor)
# ---- 学生售价(分项折扣后求和,round 各项;无峰谷系数)----
charged_in_miss = int(round(usage.get("in_miss", 0) * price.in_miss
* cfg.sale_in / 1_000_000))
charged_in_hit = int(round(usage.get("in_hit", 0) * price.in_hit
* cfg.sale_in / 1_000_000))
charged_out = int(round(usage.get("out", 0) * price.out
* cfg.sale_out / 1_000_000))
charged = charged_in_miss + charged_in_hit + charged_out
return {
"upstream_cost_milli": int(cost),
"charged_milli": int(charged),
"margin_milli": int(charged) - int(cost),
}