feat: support new pricing format (price_factors + unit_prices)

- get_pricing_from_ymalstr: 支持新格式和旧公式格式
  * 新格式: price_factors + unit_prices + unit 自动计算
  * 旧格式: formula eval 计算(向后兼容)
  * filters: 支持区间定价,多个区间只要匹配一个即可
  * min_amount: 支持最低消费
  * flat: 支持固定费用

- convert_pricing_to_new_design.py: 转换脚本
  * 自动转换 35 条定价记录
  * 无需人工审核

测试通过:
- 多因子计费 (prompt_tokens + completion_tokens)
- 按时长计费 (duration)
- 固定费用 (flat)
- 最低消费 (min_amount)
- 区间定价 (filters)
- 旧公式兼容
This commit is contained in:
yumoqing 2026-06-05 14:00:24 +08:00
parent 392f281758
commit 7200454c46
2 changed files with 120 additions and 20 deletions

View File

@ -525,7 +525,10 @@ order by b.enabled_date desc"""
@staticmethod
def get_pricing_from_ymalstr(config_data, yamlstr):
"""
yamlstr是从
解析定价YAML并计算费用
支持两种格式
1. 旧格式formula 字段eval计算
2. 新格式price_factors + unit_prices + unit自动计算
"""
if config_data is None:
e = Exception(f'config_data is None, {yamlstr=}')
@ -545,19 +548,34 @@ order by b.enabled_date desc"""
if not d.pricings:
exception(f'{d} has not "pricings"')
raise Exception(f'定价定义中没有pricing数据')
# 单位映射表
unit_values = d.get('unit_values', {'百万': 1000000, '': 1, '': 1000, '': 1, '': 1, '毫秒': 0.001})
ret_items = []
for i, p in enumerate(d.pricings):
if not p.formula:
debug(f'无公式:{p=}')
# 跳过需要人工审核的记录
if p.get('_NEEDS_MANUAL_REVIEW'):
debug(f'跳过需要人工审核的定价项: {i}')
continue
# 判断是旧格式还是新格式
is_new_format = p.get('price_factors') is not None and p.get('unit_prices') is not None
is_old_format = p.get('formula') is not None
if not is_new_format and not is_old_format:
debug(f'无公式也无price_factors{p=}')
continue
p_ok = True
times = 1
unit = 1
ns = DictObject(**config_data)
# 检查过滤条件(排除定价计算字段)
skip_keys = {'formula', 'price_factors', 'unit_prices', 'unit', 'min_amount', 'filters', 'pricing_type'}
for k, spec_value in p.items():
if spec_value is None:
continue
if k == 'formula':
if k in skip_keys:
continue
f = d.fields.get(k)
if not f:
@ -565,8 +583,6 @@ order by b.enabled_date desc"""
exception(f'{e}')
raise Exception(e)
data_value = config_data.get(k)
# p[f'old_{k}'] = data_value
# p[f'mapping_{k}'] = data_mapping(d, k, data_value) #需要mapping的数据转换
data_value = data_mapping(d, k, data_value)
if data_value is None:
if 'default' in f.keys():
@ -578,28 +594,112 @@ order by b.enabled_date desc"""
try:
flg = check_value(f, spec_value, data_value)
if not flg:
# 条件不满足
# debug(f'条件不满足:{p=},{spec_value=}, {data_value=}, {k=}')
p_ok = False
break
except Exception as e:
msg = f'{p=},{f}: {spec_value=}, {data_value=}'
exception(f'{e}:{msg}')
break
if p_ok and p.formula:
# 检查 filters 区间条件(新格式)
if p_ok and is_new_format and 'filters' in p:
# filters 是多个区间选项,只要有一个匹配就行
filter_matched = False
for filter_item in p['filters']:
item_ok = True
for fk, fv in filter_item.items():
if fk == 'unit_prices':
continue
f = d.fields.get(fk)
if not f:
continue
data_value = config_data.get(fk)
data_value = data_mapping(d, fk, data_value)
if data_value is None:
continue
try:
flg = check_value(f, fv, data_value)
if not flg:
item_ok = False
break
except Exception as e:
debug(f'filter check error: {e}')
item_ok = False
break
if item_ok:
filter_matched = True
break
if not filter_matched:
p_ok = False
if not p_ok:
info(f'{config_data=}, {p=}, mismatched')
continue
np = p.copy()
formula = p.formula
if not formula:
e = f'{p} not formula found'
exception(e)
raise Exception(e)
debug(f'{formula=}, {ns=}, {p=}, {d.fields=}')
np.data = config_data
if is_new_format:
# 新格式price_factors + unit_prices + unit
factor_name = p['price_factors']
unit_price = p['unit_prices']
unit_str = p.get('unit', '')
# 处理 filters 中的区间定价(查找匹配的 unit_prices
if 'filters' in p:
for filter_item in p['filters']:
for fk, fv in filter_item.items():
if fk == 'unit_prices':
continue
f = d.fields.get(fk)
if not f:
continue
data_value = config_data.get(fk)
data_value = data_mapping(d, fk, data_value)
if data_value is None:
continue
try:
flg = check_value(f, fv, data_value)
if flg and 'unit_prices' in filter_item:
unit_price = filter_item['unit_prices']
except:
pass
# 获取 usage 值
if factor_name == 'flat':
# 固定费用
usage_value = 1
else:
usage_value = config_data.get(factor_name)
if usage_value is None:
debug(f'新格式config_data中缺少{factor_name}')
continue
usage_value = float(usage_value)
# 获取单位值
unit_val = unit_values.get(unit_str, 1)
if isinstance(unit_val, str):
unit_val = float(unit_val)
# 计算金额
amount = unit_price * usage_value / unit_val
# 应用 min_amount
min_amount = p.get('min_amount', 0)
if min_amount and amount < min_amount:
amount = min_amount
np.amount = amount
ret_items.append(np)
elif is_old_format:
# 旧格式formula
formula = p.formula
debug(f'{formula=}, {ns=}, {p=}, {d.fields=}')
env_data = DictObject(config_data)
np.amount = eval(formula, env_data)
ret_items.append(np)
else:
info(f'{config_data=}, {p=}, {d.model_mappings=}, mismatched')
if len(ret_items) == 0:
e = f'{config_data=}{yamlstr=}没有找到合适的定价'
exception(e)