Files
projectAIpopular/tests/test_proxy_pricing.py
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

132 lines
5.3 KiB
Python
Raw Permalink 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):峰谷边界(假时钟 ±1 分钟)/ 毫元取整不漂移 / margin 恒等式。"""
import time
import pytest
from gateway.proxy.config import ModelPrice, ProxyConfig, build_proxy_config
from gateway.proxy.pricing import compute, is_offpeak
from gateway.settings import DEFAULTS
@pytest.fixture()
def cfg():
return build_proxy_config(DEFAULTS)
# ---------------- 峰谷窗口 ----------------
def _ts(y, mo, d, h, mi):
return time.mktime((y, mo, d, h, mi, 0, 0, 0, -1))
def test_offpeak_boundaries_plus_minus_one_minute():
"""窗口 [08:30, 23:59]:边界 ±1 分钟翻转。"""
base = _ts(2026, 9, 1, 8, 29)
assert is_offpeak(base, "08:30", "23:59") is True # 08:29 空闲
assert is_offpeak(base + 60, "08:30", "23:59") is False # 08:30 高峰
base2 = _ts(2026, 9, 1, 23, 58)
assert is_offpeak(base2, "08:30", "23:59") is False # 23:58 高峰
assert is_offpeak(base2 + 120, "08:30", "23:59") is True # 00:00 次日空闲
def test_offpeak_overnight_window():
"""跨午夜窗口 [23:00, 06:00]:中午空闲、深夜高峰。"""
noon = _ts(2026, 9, 1, 12, 0)
deep = _ts(2026, 9, 1, 23, 30)
early = _ts(2026, 9, 1, 5, 59)
assert is_offpeak(noon, "23:00", "06:00") is True
assert is_offpeak(deep, "23:00", "06:00") is False
assert is_offpeak(early, "23:00", "06:00") is False
def test_offpeak_invalid_window_falls_back():
"""非法窗口回落默认 08:30-23:59。"""
noon = _ts(2026, 9, 1, 12, 0)
assert is_offpeak(noon, "garbage", "!!") is False
assert is_offpeak(_ts(2026, 9, 1, 7, 0), "garbage", "!!") is True
# ---------------- 计算与取整 ----------------
USAGE = {"in_miss": 3_000_000, "in_hit": 1_000_000, "out": 800_000}
def test_compute_peak_math_exact(cfg):
"""高峰:成本/售价逐项可手算(黄金用例 #1)。
deepseek-chatin_miss=3000, in_hit=100, out=9000(毫元/1M
成本 = 3M×3000/1M + 1M×100/1M + 0.8M×9000/1M = 9000+100+7200 = 16300 毫元
售价 = 9000×0.5 + 100×0.5 + 7200×0.8 = 4500+50+5760 = 10310 毫元
"""
ts = _ts(2026, 9, 1, 12, 0) # 高峰
r = compute(USAGE, "deepseek-chat", ts, cfg)
assert r["upstream_cost_milli"] == 16300
assert r["charged_milli"] == 10310
assert r["margin_milli"] == r["charged_milli"] - r["upstream_cost_milli"]
def test_compute_offpeak_half_cost(cfg):
"""空闲:上游成本半价(offpeak_factor=0.5),售价不变(黄金用例 #2)。"""
ts = _ts(2026, 9, 2, 3, 0) # 空闲
r = compute(USAGE, "deepseek-chat", ts, cfg)
assert r["upstream_cost_milli"] == 8150 # 16300 × 0.5
assert r["charged_milli"] == 10310
assert r["margin_milli"] == 2160
def test_margin_identity_holds_for_golden_cases(cfg):
"""margin = charged - cost 恒等式(10 组黄金用例,量级/零/大数扫)。"""
ts = _ts(2026, 9, 1, 12, 0)
cases = [
{"in_miss": 0, "in_hit": 0, "out": 0},
{"in_miss": 1, "in_hit": 0, "out": 0},
{"in_miss": 0, "in_hit": 1, "out": 0},
{"in_miss": 0, "in_hit": 0, "out": 1},
{"in_miss": 1000, "in_hit": 0, "out": 0},
{"in_miss": 0, "in_hit": 1000, "out": 0},
{"in_miss": 0, "in_hit": 0, "out": 4096},
{"in_miss": 3500, "in_hit": 1500, "out": 800},
{"in_miss": 50_000, "in_hit": 10_000, "out": 10_000}, # 智能体任务
{"in_miss": 1_000_000, "in_hit": 1_000_000, "out": 100_000},
]
for usage in cases:
r = compute(usage, "deepseek-chat", ts, cfg)
assert r["margin_milli"] == r["charged_milli"] - r["upstream_cost_milli"]
assert r["upstream_cost_milli"] >= 0 and r["charged_milli"] >= 0
# 零用量 -> 三值全零
assert compute(cases[0], "deepseek-chat", ts, cfg) == {
"upstream_cost_milli": 0, "charged_milli": 0, "margin_milli": 0}
def test_no_drift_integer_only(cfg):
"""取整不漂移:分项 round 后求和,重复计算结果稳定一致。"""
ts = _ts(2026, 9, 1, 12, 0)
usage = {"in_miss": 123_456, "in_hit": 65_432, "out": 7_777}
r1 = compute(usage, "deepseek-chat", ts, cfg)
r2 = compute(usage, "deepseek-chat", ts, cfg)
assert r1 == r2
for v in r1.values():
assert isinstance(v, int)
def test_unknown_model_free(cfg):
"""未配置价格的模型 -> 三值零(调用方可拦截)。"""
ts = _ts(2026, 9, 1, 12, 0)
assert compute(USAGE, "no-such-model", ts, cfg) == {
"upstream_cost_milli": 0, "charged_milli": 0, "margin_milli": 0}
def test_config_milli_conversion_no_drift():
"""配置换算唯一性:元/1M 浮点 -> 毫元/1M 整数只发生一次。"""
c = build_proxy_config({"proxy": {"pricing": {
"m1": {"in_miss": 0.003, "in_hit": 0.0001, "out": 0.009},
"m2": {"in_miss": 12.3456, "out": 1.5}}}})
assert c.price("m1").in_miss == 3
assert c.price("m1").in_hit == 1 # 0.0001 元 -> 0.1 毫元 -> 取整 0 -> 触发缺省 max(1, miss//30)
# in_hit 缺省 = in_miss//30D-P3):0.003 元 -> 3 毫元 -> 0(太小),显式补齐后重验
c2 = build_proxy_config({"proxy": {"pricing": {
"m1": {"in_miss": 3.0, "in_hit": 0.1, "out": 9.0},
"m2": {"in_miss": 12.3456, "out": 1.5}}}})
assert c2.price("m2").in_miss == 12346
assert c2.price("m2").in_hit == 12346 // 30 # 411