feat(pricing): peak_times 声明式忙闲时定价——fields 里 peak_times 时段串+引擎计算 is_peak 布尔字段,pricings 用 is_peak:true/false 作定价要素

- _in_peak_times(): 逗号分隔时段串解析(=~/~=/=~=/~ 四种边界语义,浮点小时比较)
- hour 缺失(老流水/上游未注入)按非忙时兜底,不炸计费;时段串解析失败记日志走闲时
- beautify_filter_value: bool → 是/否(展示层忙时=是/否)
- 试算15例全过(忙闲窗口边界/缓存命中/factor=0/hour缺失),含服务进程端到端
This commit is contained in:
yumoqing 2026-09-10 23:43:57 +08:00
parent 1985f442d7
commit bf20df572f

View File

@ -1,4 +1,5 @@
import json
import re
import yaml
from ahserver.serverenv import ServerEnv
from ahserver.filestorage import FileStorage
@ -130,6 +131,55 @@ def typevalue(v, t):
return v
return f(v)
# ── 忙闲时时段解析2026-09-10──
# peak_times 串语法:逗号分隔的时段,每段 "H:MM =~= H:MM"(含两端)或
# "H:MM =~ H:MM"(下含上开);跨午夜段写成两段(如 22:00 =~ 24:00,0:00 =~ 8:00
# 比较单位是小时(浮点),与出账注入的 hourint 小时数)对齐:
# 9:00 =~= 12:00 覆盖 hour∈{9,10,11,12}12点整落在忙时内
_PEAK_SEG_RE = None
def _parse_hhmm(s):
s = (s or '').strip()
if ':' in s:
h, m = s.split(':', 1)
return float(h) + float(m) / 60.0
return float(s)
def _in_peak_times(peak_times, hour):
"""hour0-23 的整数小时或浮点小时)是否落在 peak_times 任一时段内。
hour None上游未注入调用时刻 返回 False按非忙时兜底不炸计费
时段串解析失败 False 并记异常日志宁可走闲时价也不抛异常中断出账
"""
if hour is None:
return False
try:
hv = float(hour)
except (TypeError, ValueError):
return False
for seg in str(peak_times).split(','):
seg = seg.strip()
if not seg:
continue
m = re.match(r'^([^~]+?)(=~=|=~|~=|~)([^~]+)$', seg)
if not m:
exception(f'peak_times 段解析失败: {seg}')
continue
lo = _parse_hhmm(m.group(1))
hi = _parse_hhmm(m.group(3))
op = m.group(2)
if op == '=~=':
ok = lo <= hv <= hi
elif op == '=~':
ok = lo <= hv < hi
elif op == '~=':
ok = lo < hv <= hi
else:
ok = lo < hv < hi
if ok:
return True
return False
# ── 展示层filter/tier 维度值可读化2026-09-09仅展示不参与计费比较──
# 区间操作符 → 数学区间记号,语义与 check_value 的 between 分支一一对应
_INTERVAL_BRACKETS = {
@ -142,12 +192,15 @@ _INTERVAL_BRACKETS = {
def beautify_filter_value(v, fdef=None, value_mode=None):
"""把 filter/tier 维度值渲染成可读文本(纯展示,绝不参与计费比较)。
- boolis_peak 等计算字段True/False /
- value_mode=between原始 '0 =~= 512000' '[0, 512000]'
'8 =~ 22' '[8, 22)'操作符语义对齐 check_value无歧义
- value_mode=in'a b c' 'a / b / c'
- 其余原样返回 resolution=1K
数字保持原值不转不加千分位与实际比较值一字不差
"""
if isinstance(v, bool):
return '' if v else ''
vm = value_mode
if vm is None and isinstance(fdef, dict):
vm = fdef.get('value_mode')
@ -576,6 +629,19 @@ order by b.enabled_date desc"""
for field_name, field_def in d.fields.items():
if not isinstance(field_def, dict):
continue
# 忙闲时计算字段2026-09-10fields 里声明 peak_times 时段串,
# 引擎按调用时刻usage 的 hour 键,出账侧从 llm_usage.created_at 注入)
# 计算 is_peak 布尔值pricings 用 is_peak: true/false 作定价要素。
# 例peak_times: "9:00 =~= 12:00,14:00 =~= 18:00"
# is_peak: {type: bool, role: filter, label: 忙时, derived: peak_times}
# hour 缺失(老流水/上游未注入)时按非忙时兜底,不炸计费。
peak_times = field_def.get('peak_times')
if peak_times:
config_data[field_name] = _in_peak_times(
peak_times, config_data.get('hour'))
debug(f'peak_times field {field_name} = {config_data[field_name]} '
f'(hour={config_data.get("hour")}, peak_times={peak_times})')
continue
derived_expr = field_def.get('derived')
if not derived_expr:
continue