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:
parent
1985f442d7
commit
bf20df572f
@ -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)。
|
||||
# 比较单位是小时(浮点),与出账注入的 hour(int 小时数)对齐:
|
||||
# 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):
|
||||
"""hour(0-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 维度值渲染成可读文本(纯展示,绝不参与计费比较)。
|
||||
|
||||
- bool(is_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-10):fields 里声明 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user