前些时间,群内有人提出一个“按时计费”的场景需求,概括些来说就是在不同的时段有不同的计费规则,需要根据提供的开始时间与结束时间来计算总费用。
已知,不同时段的计费规则如下:
00:00-08:00,每分钟收费0.20元;
08:00-18:00,每分钟收费0.10元;
18:00-24:00,每分钟收费0.15元。
> 方案优劣
常规来说,针对类似场景有三种解决方案:
>> 涉及到子表的函数公式
通过子表辅助来实现,整体逻辑在表单内半可视化,较容易理解,适用于每次单组数据。
>> 不涉及子表的函数公式
仅通过公式来实现,可以快速部署,可用于多组数据,复用时,涉及到多处参数修订,需注意参数配置。
>> 基于场景定制自建插件
通过自建插件来实现,可以快速部署,可用于多组数据,复用时,仅需要修订必要参数。
> 测试链接
>> 涉及到子表的函数公式
https://tnvew1vqtq.jiandaoyun.com/f/6617a6df098cd49ea460f517
>> 不涉及子表的函数公式
https://tnvew1vqtq.jiandaoyun.com/f/66150a29679d5c121a58b50e
>> 基于场景定制自建插件
https://tnvew1vqtq.jiandaoyun.com/f/661cbdebd69bcc314cd8b15f
> 涉及到子表公式版
下面针对本场景,以涉及到子表的公式版为例,我们一起来看下整体的实现逻辑。
>> 如何破题
整理已知内容、相关内容、求解内容,以及相关的可使用的或调用的资源。
在本场景中,题面给出的是三个时段,实际就等同于给出了24组数据(24小时),如果放在一张表里,就是把代表开始时间与结束时间范围内的所有格子基于计费规则涂成不同的颜色,然后汇总这些格子所代表的费用。
那么,在简道云里,如何去“数格子”,字段层面较为相关的是子表,函数层面较为相关的是SPLIT()、SUM()、SUMPRODUCT()等具备数组相关计算能力的函数。
>> 如何拆题
将一个复杂的结果分拆成若干个简单的实现部件。
在本场景中,无论是开始时间还是结束时间,都是一个具体的某日某时某分,如果直观的去理解,每分钟我们是不是就需要60个格子,这样一个小时就需要3600个格子,24个小时就需要86400个格子,显然这种方式,如果去放在子表中是有难度的。
那么我们就需要,基于这个逻辑,再把具备相同特征的格子进行进一步的整合,这时可得出以下内容。
1、开时时间:可得出时、分,本小时剩余分;
2、结束时间:可得出时、分,本小时已过分;
3、开时与结束时间当天的所经历的完整小时;
4、开时与结束时间是同天还是跨天或者隔天;
5、开时与结束时间中间的所经历的完整天数。
基于以上内容,把所涉及到的数据进行汇总即可得出最终结果。
>> 如何解题
针对每一个小部件进行测试,最终组合成整体,并进行相关整体性的优化。
>>> 开始时间本小时剩余分计费
(60-MINUTE(开始时间))*IFS(HOUR(开始时间)<8,0.2,HOUR(开始时间)<18,0.1,HOUR(开始时间)>=18,0.15)
>>> 结束时间本小时已过分计费
MINUTE(结束时间)*IFS(HOUR(结束时间)<8,0.2,HOUR(结束时间)<18,0.1,HOUR(结束时间)>=18,0.15)
>>> 所经历的完整天数计费
(DATEDIF(DATE(YEAR(开始时间),MONTH(开始时间),DAY(开始时间)),DATE(YEAR(结束时间),MONTH(结束时间),DAY(结束时间)))-1)*SUM(计算.整天计费)
>>> 通过子表来完成不同场景下小时的计费
1、建立一个24行数据的子表用来代表24小时,并在子表内完成每小时的标准计费。
2、在子表内分别完成开时与结束时间当天的所经历的完整小时计费。
>>> 计费汇总公式
最后将以上部分进行整合,即可得出最终结果。
IF(
VALUE(TEXT(DATE(开始时间),"yyyyMMdd")) < VALUE(TEXT(DATE(结束时间),"yyyyMMdd")),
SUM(
中间整天计费,
开始天分计费,
结束天分计费,
计算.开始天计费,
计算.结束天计费
),
IF(
VALUE(TEXT(DATE(开始时间),"yyyyMMddHH")) < VALUE(TEXT(DATE(结束时间),"yyyyMMddHH")),
SUM(
计算.仅当天计费,
开始天分计费,
结束天分计费
),
(MINUTE(结束时间) - MINUTE(开始时间)) * IFS(
HOUR(开始时间) < 8,
0.2,
HOUR(开始时间) < 18,
0.1,
HOUR(开始时间) >= 18,
0.15
)
)
)
> 不涉及子表公式版
以下为不涉及到子表的公式版,整体就不在赘述了,公式如下:
>> 计费规则
SPLIT(CONCATENATE("*",CONCATENATE(REPT("#0.20",8),REPT("#0.10",10),REPT("#0.15",6))),"*#")[1]
>> 计费汇总
IFS(
AND(NOT(ISEMPTY(开始时间)),NOT(ISEMPTY(结束时间)),开始时间>结束时间),
"时间输入有误,请确认后重新计算 … ",
VALUE(TEXT(DATE(开始时间),"yyyyMMddHH")) == VALUE(TEXT(DATE(结束时间),"yyyyMMddHH")),
MID(计费规则,HOUR(开始时间)*5+1,4)*(MINUTE(结束时间)-MINUTE(开始时间)),
VALUE(TEXT(DATE(开始时间),"yyyyMMdd")) == VALUE(TEXT(DATE(结束时间),"yyyyMMdd")),
SUM(
MID(计费规则,HOUR(开始时间)*5+1,4)*(60-MINUTE(开始时间)),
MID(计费规则,HOUR(结束时间)*5+1,4)*MINUTE(结束时间),
SUMPRODUCT(SPLIT(MID(计费规则,(HOUR(开始时间)+1)*5+1,(HOUR(结束时间)-HOUR(开始时间)-1)*5-1),"#"),SPLIT(CONCATENATE("60",REPT("#60",(HOUR(结束时间)-HOUR(开始时间)-2))),"#"))
),
VALUE(TEXT(DATE(开始时间),"yyyyMMdd")) < VALUE(TEXT(DATE(结束时间),"yyyyMMdd")),
SUM(
SUMPRODUCT(SPLIT(计费规则,"#"),SPLIT(CONCATENATE("60",REPT("#60",23)),"#"))*(DATEDIF(DATE(YEAR(开始时间),MONTH(开始时间),DAY(开始时间)),DATE(YEAR(结束时间),MONTH(结束时间),DAY(结束时间)))-1),
SUMPRODUCT(SPLIT(CONCATENATE(MID(计费规则,(HOUR(开始时间)+1)*5+1,5*24),"#",MID(计费规则,1,MAX(HOUR(结束时间)*5-1),0)),"#"),SPLIT(REPT("60#",HOUR(结束时间)+(24-HOUR(开始时间)-1)),"#")),
MID(计费规则,HOUR(开始时间)*5+1,4)*(60-MINUTE(开始时间)),
MID(计费规则,HOUR(结束时间)*5+1,4)*MINUTE(结束时间)
)
)
> 自建插件版
本版本需要具备基本的自建插件部署能力
>> 插件代码
# @Time : 2024/4/15
# @Author : zmlnow
# @Desc : 按时计费汇总
from datetime import datetime, timedelta
def parse_fee_string(fee_string, separator):
"""
根据给定的费率字符串,解析出费率字典。
Args:
fee_string (str): 以分隔符分隔的费率字符串,例如 "0.20#0.20#0.10#0.15#0.10#0.10"
separator (str, optional): 分隔符,默认为 '#'。
Returns:
dict: 以小时数为键,费率为值的字典,例如 {0: 0.2, 1: 0.2, 2: 0.2, ..., 22: 0.1, 23: 0.15}
"""
# 将费率字符串按分隔符分割成费率列表
fees = fee_string.split(separator)
# 初始化费率字典和当前小时数
fee_dict = {}
current_hour = 0
# 遍历费率列表
for fee in fees:
# 将费率转换为浮点数并添加到字典中
fee_dict[current_hour] = float(fee)
# 更新当前小时数
current_hour += 1
return fee_dict
def calculate_fee(start_time, end_time, fee_rules):
"""
计算按时计费的费用。
Args:
start_time (datetime): 开始时间,需提供至分钟。
end_time (datetime): 结束时间,需提供至分钟。
fee_rules (dict): 费率规则字典,键为小时数,值为对应的费率。
Returns:
float: 计算出的费用,单位为元。
"""
# 如果结束时间小于开始时间,则交换两者,表示跨日情况
if start_time > end_time:
start_time, end_time = end_time, start_time
# 初始化费用与当前时间
total_fee = 0.0
current_time = start_time.replace(minute=0, second=0, microsecond=0)
# 如果开始时间的分钟不是0,则将分钟数累加到费用中
if start_time.minute > 0:
total_fee += fee_rules.get(start_time.hour, 0.0) * (60 - start_time.minute)
current_time += timedelta(hours=1)
while current_time <= end_time:
# 获取当前时间的费率
current_hour = current_time.hour
fee_rate = fee_rules.get(current_hour, 0.0)
# 获取下一个费率时段的开始时间
next_time = current_time + timedelta(hours=1)
# 获取当前时间分钟数
if next_time > end_time:
minutes_in_period = end_time.minute
else:
minutes_in_period = 60
# 累加当前费率时段的费用
total_fee += fee_rate * minutes_in_period
# 更新当前时间为下一个费率时段的开始
current_time = next_time
return total_fee
# 提取所供的参数信息
fee_string = triggerConf.get('fee_string')
separator = triggerConf.get('separator')
start_time_str = triggerConf.get('start_time')
end_time_str = triggerConf.get('end_time')
# 判断分隔符是否在费率字符串中
if separator not in fee_string:
raise ValueError('请确认计费规则分隔符号是否有误 …')
# 判断费率个数是否为24
if len(fee_string.split(separator)) != 24:
raise ValueError('请确认计费规则数量是否有误 …')
# 判断是否提供开始时间与结束时间
if start_time_str is None or end_time_str is None:
raise ValueError('请确认开始时间与结束时间是否有误 …')
# 将开始时间与结束时间转换为时间类型
start_time = datetime.strptime(start_time_str, '%Y-%m-%dT%H:%M:%S.%fZ')
end_time = datetime.strptime(end_time_str, '%Y-%m-%dT%H:%M:%S.%fZ')
# 判断开始时间是否大于结束时间
if start_time > end_time:
raise ValueError('请确认开始时间与结束时间是否有误 …')
# 解析费率字符串为费率字典
fee_rules = parse_fee_string(fee_string, separator)
# 计算总费用
fee_sum = calculate_fee(start_time, end_time, fee_rules)
# 返回计算结果
return { 'fee_sum' : fee_sum }
>> 请求参数
[
{
"id": "start_time",
"label": "开始时间",
"description": "",
"fieldType": "datetime",
"isHidden": false,
"isEnabled": true,
"isRequired": false,
"fieldConf": {}
},
{
"id": "end_time",
"label": "结束时间",
"description": "",
"fieldType": "datetime",
"isHidden": false,
"isEnabled": true,
"isRequired": false,
"fieldConf": {}
},
{
"id": "fee_string",
"label": "计费规则",
"description": "以分隔符分隔的24小时费率字符串",
"fieldType": "text",
"isHidden": false,
"isEnabled": true,
"isRequired": false,
"fieldConf": {
"isMultiLine": false
},
"defaultValue": ""
},
{
"id": "separator",
"label": "分隔符号",
"description": "费率分隔符",
"fieldType": "text",
"isHidden": false,
"isEnabled": true,
"isRequired": false,
"fieldConf": {
"isMultiLine": false
},
"defaultValue": ""
}
]
>> 返回参数
[
{
"label": "计费汇总",
"id": "fee_sum",
"fieldConf": {},
"fieldType": "any"
}
]
> 注意事项
当前各方式,并为做严谨的场景测试,如果需要用于生产环境,请自行做好测试,同时欢迎于此反馈所存在的问题。
> 更多内容
承接简道云技术咨询与应用定制
承接月度技术支持服务
更多沟通交流可添加微信(zmlnow)
添加时请备注:简道云
|