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 json
|
||||||
|
import re
|
||||||
import yaml
|
import yaml
|
||||||
from ahserver.serverenv import ServerEnv
|
from ahserver.serverenv import ServerEnv
|
||||||
from ahserver.filestorage import FileStorage
|
from ahserver.filestorage import FileStorage
|
||||||
@ -130,6 +131,55 @@ def typevalue(v, t):
|
|||||||
return v
|
return v
|
||||||
return f(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,仅展示不参与计费比较)──
|
# ── 展示层:filter/tier 维度值可读化(2026-09-09,仅展示不参与计费比较)──
|
||||||
# 区间操作符 → 数学区间记号,语义与 check_value 的 between 分支一一对应
|
# 区间操作符 → 数学区间记号,语义与 check_value 的 between 分支一一对应
|
||||||
_INTERVAL_BRACKETS = {
|
_INTERVAL_BRACKETS = {
|
||||||
@ -142,12 +192,15 @@ _INTERVAL_BRACKETS = {
|
|||||||
def beautify_filter_value(v, fdef=None, value_mode=None):
|
def beautify_filter_value(v, fdef=None, value_mode=None):
|
||||||
"""把 filter/tier 维度值渲染成可读文本(纯展示,绝不参与计费比较)。
|
"""把 filter/tier 维度值渲染成可读文本(纯展示,绝不参与计费比较)。
|
||||||
|
|
||||||
|
- bool(is_peak 等计算字段):True/False → 是/否
|
||||||
- value_mode=between:原始 '0 =~= 512000' → '[0, 512000]',
|
- value_mode=between:原始 '0 =~= 512000' → '[0, 512000]',
|
||||||
'8 =~ 22' → '[8, 22)',操作符语义对齐 check_value,无歧义
|
'8 =~ 22' → '[8, 22)',操作符语义对齐 check_value,无歧义
|
||||||
- value_mode=in:'a b c' → 'a / b / c'
|
- value_mode=in:'a b c' → 'a / b / c'
|
||||||
- 其余:原样返回(如 resolution=1K)
|
- 其余:原样返回(如 resolution=1K)
|
||||||
数字保持原值(不转「万」、不加千分位)——与实际比较值一字不差。
|
数字保持原值(不转「万」、不加千分位)——与实际比较值一字不差。
|
||||||
"""
|
"""
|
||||||
|
if isinstance(v, bool):
|
||||||
|
return '是' if v else '否'
|
||||||
vm = value_mode
|
vm = value_mode
|
||||||
if vm is None and isinstance(fdef, dict):
|
if vm is None and isinstance(fdef, dict):
|
||||||
vm = fdef.get('value_mode')
|
vm = fdef.get('value_mode')
|
||||||
@ -576,6 +629,19 @@ order by b.enabled_date desc"""
|
|||||||
for field_name, field_def in d.fields.items():
|
for field_name, field_def in d.fields.items():
|
||||||
if not isinstance(field_def, dict):
|
if not isinstance(field_def, dict):
|
||||||
continue
|
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')
|
derived_expr = field_def.get('derived')
|
||||||
if not derived_expr:
|
if not derived_expr:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user