记录一下账单拆分系统开发过程中对于一些基础类的设计思想或规划
Rules类
- 读取YAML配置文件
- 填充属性 costbodys 字典
- 填充属性 ratios 字典
- 填充属性 accounts 字典
- 填充属 costs 字典
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 属性:
costbodys:
{ "成本主体1": CostBody1,
"成本主体N": CostBodyN,
}
ratios:
{ "比率1": Ratio1,
"比率N": RatioN,
}
accounts:
{ "账号1": Account1,
"账号N": AccountN,
}
costs:
{ "标签1": Cost1,
"标签N": CostN,
}
|
fill_costbodys实现逻辑
- 遍历配置字典中 costbody 下的所有Key-Value, Key为
成本主体名称 , Value为字典dict1
- 从dict1遍历所有Key-Value, Key为 成本主体有效期 ,
Value为 成本主体信息 dict2
- 检查 成本主体有效期 是否位于有效期内,
不满足则跳过
- 使用 成本主体信息 dict2, 初始化
成本主体实例
- 使用 成本主体名称 : 成本主体实例
填充属性字典 costbodys
fill_ratios实现逻辑
- 遍历配置字典中 ratio 下的所有Key-Value, Key为
比率名称 , Value为字典dict1
- 从dict1中尝试提取当前月份的比率信息字典dict2,
不存在则打印warning日志并跳过
- 从dict2遍历所有Key-Value, Key为 成本主体名称 ,
Value为 成本主体比重值
- 在属性字典 costbodys 中提取
成本主体名称 的 成本主体实例, 并生成{
成本主体实例 : 成本主体比重值
}的比率字典dict3
- 使用 比率名称 和比率字典dict3, 初始化
比率实例
- 使用 比率名称 : 比率实例
填充属性字典 ratios
fill_accounts实现逻辑
- 遍历配置字典中 account 下的所有Key-Value, Key为
账号ID , Value为字典dict1
- 以 账号ID 初始化 账号实例
- 从dict1遍历所有Key-Value, Key为 区域名称 ,
Value为字典dict2
- 从dict2遍历所有Key-Value, Key为 区域有效期 ,
Value为 区域信息字典 dict3
- 检查 区域有效期 是否位于有效期内, 不满足则跳过
- 使用 **_create_region** 方法创建以dict3为信息的
区域实例, 并填充 账号实例
的rules属性
- 使用 账号ID : 账号实例
填充属性字典 accounts
_crete_region方法实现逻辑
- 尝试提取传参字典中的 detail 的结果,
该结果应为dict
- 如存在结果, 则执行 **_convert_detail_dict** 方法, 将字典转化
- 用传参字典初始化 Region实例 并返回
_convert_detail_dict方法实现逻辑
- 解析传参字典, 其中应该仅包含单个Key-Value对, 否则抛出异常
ValueError
- 上述Key表示属性关键字, 使用 getattr()
方法提取指定属性
- 上述Value表示属性值, 从2中提取的属性中再次提取 Key =
Value 的结果instance, 该实例为 CostBody实例 或
Ratio实例
- 检查3的结果instance是否有 percentage 属性, 存在则为
Ratio实例, 返回 percentage 属性
- 实例不存在 percentage 属性, 则为
CostBody实例 , 构造字典 { CostBody实例
: 1} 返回
fill_costs实现逻辑
- 遍历配置字典中 cost 下的所有Key-Value, Key为
标签值 , Value为字典dict1
- 从dict1遍历所有Key-Value, Key为 标签值有效期 ,
Value为 标签信息 dict2
- 执行 **_create_cost** 方法, 将标签信息转为
Cost实例
- 使用 标签值 : 标签实例
填充属性字典 costs
_create_cost方法实现逻辑
- 尝试提取传参字典中的 detail 的结果,
该结果应为dict
- 如存在结果, 则执行 **_convert_detail_dict** 方法, 将字典转化
- 用传参字典初始化 Cost实例 并返回
fill_ris实现逻辑
- 遍历配置字典中 ri 下的所有Key-Value, Key为
RI订阅ID , Value为字典dict1
- 以 账号ID 和字典dict1初始化
RI实例
- 使用 RI实例 的cost属性, 查询 costs
, 使用 Cost 实例替换str
- 使用 RI实例 填充属性字典 ris
CostBody类
1 2 3
| 属性:
dataframe: pandas.DataFrame
|
Ratio类
1 2 3 4 5 6 7 8 9 10 11 12
| 属性:
percentage: dict {
CostBody1: float1,
CostBodyN: floatN,
}
注意: sum(percentage.values()) = 1
|
Account类
1 2 3 4 5 6 7 8 9 10
| 属性:
rules: dict {
RegionName1: Region1,
RegionNameN: RegionN,
}
|
Region类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 属性:
detail: dict
按照Ratio比率拆分
{
CostBody1: float1,
CostBodyN: floatN,
}
按照CostBody拆分
{
CostBody: 1,
}
按照Cost拆分
{
}
|
Cost类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 属性:
detail: dict
按照Ratio比率拆分
{
CostBody1: float1,
CostBodyN: floatN,
}
按照CostBody拆分
{
CostBody: 1,
}
|
Spliter类
_get_accounts方法实现逻辑
- 将本月账单数据按照关联账号列进行分组, 分别获得
账号ID 和 账号数据框
- 使用 账号ID 和 账号数据框 产生
Account 实例
- 使用 账号ID : Account
的方式填充结果字典并返回
_check_deviation方法实现逻辑
1 2 3 4 5 6 7 8
| 误差允许范围计算方法为: tolerance * pow(10, -precision)
Example:
precision=2 tolerance=5 max_deviation = 5 * 10^-2 -> 0.05 误差允许范围为 ±0.05 不满足则抛出异常
|
split方法实现逻辑
- 检查数据源表是否存在
- 载入账单数据源信息
- 深拷贝区域公摊成本主体类, 用于存储本次拆分区域公摊结果
- 遍历当前属性 accounts , 获得
Account 实例, 依据账号规则进行拆分
- 将 Account 拆分结果合并到私有属性 **__dict**
- 对公摊的数据框进行重新分配
- 检查分配后的误差结果是否符合预期
dump方法实现逻辑
- 遍历私有属性 **__dict** ,提取 CostBody 实例
- 跳过公摊实例及数据框为空的结果
- 将 CostBody 名称按照拼音首字母转换, 产生name
- 使用name加月份信息拼接成本主体详单表明,
将详单数据框存储到数据库
- 按照区域对 CostBody 的数据框进行分组,
得到区域信息及区域数据框
- 计算区域总费用, 填充月度信息列表数据
- 使用区域信息列表数据生成数据框, 并存储数据库
_allocate_shared_costs方法实现逻辑
- 提取月度公摊的 CostBody 实例,
shared
- 对 shared 的数据框按照region进行分组, 得到
region , 和对应的数据框df
- 计算区域公摊数据框df的总费用 shared_total
和区域数据框总费用 total
- 区域总费用剔除该区域的总公摊, 用于计算各个成本主体的占比,
否则总比率之和将不足100%, 继而产生较大误差
- 遍历私有属性 **__dict** 提取所有成本主体实例
CostBody , 跳过费用为空的结果及区域公摊结果
- 计算 CostBody 的数据框总费用
costbody_total
- 使用 costbody_total / total
得到该成本主体的占比信息 percentage
- 复制 shared 的数据框, 对费用列执行乘以
percentage 操作 产生数据框 df1
- 将 df1 结果与 CostBody
的数据框进行拼接
- 所有成本主体处理完成后, 清空 shared 的数据框
Account类
_split_with_percentage方法实现逻辑
- 遍历 percentages 字典, 得到
CostBody 及对应的比率 float
- 深拷贝需要分摊的数据框, 产生 df
- 对 df 的费用列应用比率
- 将 CostBody 的数据框与 df
进行合并
- 对需要分摊的数据框按照标签列进行分组, 得到 tag
及对应数据框 df
- 如果标签为空, 则将 tag 更名为AccountShared
- 提取 tag 对应的 Cost 实例
- 使用 df 和 Cost 实例的
detail 属性, 执行 **_split_with_percentage**
进行拆分
_allocate_shared_costs方法实现逻辑
- 提取账号公摊的 CostBody 实例数据框,
shared_df
- 对 shared_df 的数据框按照region进行分组, 得到
region , 和对应的数据框 df
- 检查 region 是否为空, 为空则重命名为 NO_REGION
- 提取账号在当前 region 的数据框, 并计算总费用
region_total
- 计算区域公摊数据框df的总费用 shared_total
- 使用 region_total - shared_total
计算净费用 net_total
- 如果 net_total 为0,
则表示该账号在该区域的拆分结果应为tag, 但所有资源均未标签任何有效tag,
因此费用将划拨至区域总公摊
- 遍历属性 costbodys 提取所有成本主体名称
name 和实例 CostBody ,
跳过费用为空的结果及账号公摊结果
- 计算 CostBody 在当前 region
数据框总费用 costbody_total
- 使用 costbody_total / net_total
得到该成本主体的占比信息 percentage
- 复制 df 的数据框, 对费用列执行乘以
percentage 操作 产生数据框 df1
- 将 df1 结果与 CostBody
的数据框进行拼接
- 所有成本主体处理完成后, 清空 shared 的数据框
_ri_repay方法实现逻辑
- 遍历RI实例列表, 对每个RI实例的 Subscription ID
进行检查, 提取数据框df
- 如果数据框df为空, 则跳过, 进行下一个RI实例检查
- 对数据框df按照标签列进行分组, 得到 Tag 和
df1
- 如果 Tag 符合RI实例的 Cost
属性名称, 则跳过
- 如果 Tag 值为空, 则提取实例ID及使用量,
计算占用比率, 打印警告信息
- 按照 Tag 结果, 检查 costs 字典,
找出对应的应承担成本的成本主体
- 深拷贝数据框, 产生数据框 df2
- 检查RI实例类型, 如果是 on_self 则对
df2 的RI标准化使用总量乘以RI的 price
属性来计算总费用
- 合并到应付费的成本主体, 然后深拷贝 df2 产生
df3 对价格列求反后, 合并到付费成本主体
- 如果是 on_behalf 则对 df2
的RI标准化使用总量乘以该资源的OD价格(#TODO)来计算总费用
- 合并到应付费的成本主体, 然后深拷贝 df2 产生
df3 对价格列求反后, 合并到付费成本主体
split方法实现逻辑
- 在规则字典 rules 中检查名为"Default"的规则是否存在,
不存在则抛出异常 RegionRuleNotFoundError
- 如果规则字典 rules 有且仅有"Default"的规则,
则不区分区域, 直接按照默认规则进行拆分
- 上述条件不成立, 则将本账号数据框按照region列分组, 得到
region 和数据框 df
- 在规则字典 rules 中检查是否存在
region 的规则, 不存在则使用"Default"规则
- 深拷贝区域公摊成本主体类, 用于存储本次拆分区域公摊结果
- 遍历当前属性 accounts , 获得
Account 实例, 依据账号规则进行拆分
- 按照规则对数据框进行拆分
- 对账号公摊的数据框进行重新分配
- 检查分配后的误差结果是否符合预期