feat: add assessment workbench and September reconciliation
Preserve August hydrogen revision and September audit evidence; calculate interrupted vehicle history across all prior months. Record remaining carry-policy and layout-baseline discrepancies.
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
# 1—8月已发与9月发放核对
|
||||
|
||||
核对对象:2026年9月40,211.67元版本,145条发放项。原Excel保持不变。核对以用户确认的1—7月历史最终表、8月纯氢修正版实际已发45,065.47元为基准。
|
||||
|
||||
## 结论
|
||||
|
||||
**补发与金额算术检查通过;结转资格存在规则分支不一致,需要澄清后再将结果作为最终付款依据。**
|
||||
按8月表内结转余额逐月减1的现行逻辑,独立重算9月仍为40,211.67元,未发现金额公式错误或重复历史补发。此结论不等于认可结转余额的形成口径。
|
||||
|
||||
## 1. 结转两种算法不一致:9月有150元需要复核
|
||||
|
||||
有实际考核记录的车组按“累计多跑里程÷月目标取整−实际已用结转期数”计算余额;无本月考核记录的车组却按“上期余额−1”,同时在累计目标中增加一个月目标。这是代码中不同分支的实际行为。
|
||||
8月底有5辆车的表内结转余额比统一累计公式多1期。尚建华的粤AGP5158会影响9月当期150元,其余4辆在两种口径下9月都有至少1期可用结转,主要影响后续余额。
|
||||
|
||||
|车辆|负责人|8月底表内余额|累计公式余额|1—8月已领结转次数|9月结转|
|
||||
|---|---|---:|---:|---:|---:|
|
||||
|粤AGR8531|刘念念|4|3|4|150.00|
|
||||
|粤AGP5158|尚建华|1|0|6|150.00|
|
||||
|粤AGP9786|董剑煜|5|4|4|150.00|
|
||||
|粤AGR6877|董剑煜|6|5|4|150.00|
|
||||
|粤AGR5560|金可鹏|2|1|7|150.00|
|
||||
|
||||
粤AGP5158截至8月累计实际41,174.00公里、累计目标22,451.61公里、月目标3,000公里,实际已领6次结转。统一累计公式得到 floor((41,174.00−22,451.61)/3,000)−6=0期;8月表却保存1期,9月据此发150元。
|
||||
**不能直接将150元定为多发并扣除。** 无考核月份是否应增加虚拟考核目标,决定了两种余额计算哪一种适用;若增加虚拟目标又扣除已领期数,可能重复消耗同一结转额度。应先统一规则,再调整9月及后续余额,1—8月实际已发保持不变。若明确要求保留目前累计目标、全部统一累计公式,则这笔150元不满足资格,9月在其他条件不变时为40,061.67元。这只是该口径下的测算,不是建议直接改付。
|
||||
|
||||
## 2. 同车新旧负责人同时领取,共涉及原负责人970元
|
||||
|
||||
本月无考核记录的结转共33笔、5,940元。其中以下5辆同时给新负责人发当月奖金、给旧负责人发历史结转。按目前“车牌+负责人”分账、历史结转继续发放的规则可以复现,并非同一负责人重复发放。若制度要求交接后原负责人停止领取,才会影响这970元。不能把它直接作为错付款扣除。
|
||||
|
||||
|车辆|原负责人|原负责人结转|新负责人发放|
|
||||
|---|---|---:|---|
|
||||
|粤AGP3080|董剑煜|150.00|尚建华 当月 5.00元|
|
||||
|粤AGR6869|董剑煜|150.00|尚建华 当月 150.00元|
|
||||
|粤AGP3627|金可鹏|260.00|秦挺 当月 260.00元|
|
||||
|粤AGP3692|金可鹏|260.00|秦挺 当月 260.00元|
|
||||
|粤AGR5560|金可鹏|150.00|钟祥 当月 5.00元|
|
||||
|
||||
|
||||
## 3. 9月底17辆结转余额不同,33条累计状态显示不准确
|
||||
|
||||
17个无本月考核车组的表内结转余额与统一累计公式合计相差20期。这是口径差异,不能直接换算成确定多发金额;不同车辆奖励基准、未来有无考核及补数均可能变化。
|
||||
|
||||
|车辆|负责人|9月底表内余额|统一累计公式余额|
|
||||
|---|---|---:|---:|
|
||||
|粤AGP3508|刘念念|3|2|
|
||||
|粤AGR8531|刘念念|3|1|
|
||||
|粤AGP9825|尚建华|1|0|
|
||||
|粤AGR0790|尚建华|1|0|
|
||||
|粤AGR6859|尚建华|2|1|
|
||||
|粤AGR9815|尚建华|3|2|
|
||||
|粤AGR9866|尚建华|6|5|
|
||||
|粤AGP3080|董剑煜|10|9|
|
||||
|粤AGP9786|董剑煜|4|2|
|
||||
|粤AGR2833|董剑煜|3|2|
|
||||
|粤AGR6828|董剑煜|2|1|
|
||||
|粤AGR6869|董剑煜|5|4|
|
||||
|粤AGR6877|董剑煜|5|3|
|
||||
|粤AGP3506|谯云|2|1|
|
||||
|粤AGP9707|谯云|1|0|
|
||||
|粤AGP3627|金可鹏|4|3|
|
||||
|粤AGR5560|金可鹏|1|0|
|
||||
|
||||
33个无本月考核车组在程序中把累计达标标志固定为否,但显示的累计实际里程均已达到累计目标。例如粤AGP5158累计实际41,174.00大于目标25,451.61,计算过程仍显示“已有里程未达标”。这是展示语义问题,并不等于应额外补发。建议将“累计里程是否达标”与“本月是否有考核、是否具备补发资格”分开显示。
|
||||
|
||||
## 4. 已确认没有重复的7笔历史补发
|
||||
|
||||
按“车辆+原考核负责人+考核月”逐项检查1—8月已发;7笔合计819.67元,没有重复,非虚拟考核车组也未发现符合现行规则而遗漏的补发。
|
||||
|
||||
|车辆|负责人|补发类别|金额|9月累计实际|9月累计目标|
|
||||
|---|---|---|---:|---:|---:|
|
||||
|粤AGR5228|吴纬涛|补发8月|150.00|6069.00|6000.00|
|
||||
|粤AGR8536|吴纬涛|补发8月|24.19|3603.70|3483.87|
|
||||
|粤AGP3612|尚建华|补发5月|33.55|20767.20|20645.16|
|
||||
|粤AGP3612|尚建华|补发7月|260.00|20767.20|20645.16|
|
||||
|粤AGP3612|尚建华|补发8月|260.00|20767.20|20645.16|
|
||||
|粤AGR0772|尚建华|补发8月|19.35|6470.60|3387.10|
|
||||
|粤AGP5138|钟祥|补发8月|72.58|5932.30|4451.61|
|
||||
|
||||
粤AGP3612尚建华到9月累计超目标122.04公里,8月底尚差1,023.06公里,因此9月才补5月33.55元、7月260元、8月260元。6月已经实际发放,未重复补发。
|
||||
粤AGR0772尚建华补8月19.35元,与谯云8月已发116.13元不是重复:分别为8月28—31日与8月1—24日两个负责人区间。
|
||||
|
||||
## 5. 台账、取数及报表一致性
|
||||
|
||||
- 1—6月已发台账逐条吻合历史最终表;6月人员转接后的个人合计也一致。
|
||||
- 7月原明细仍列9笔离职不发款2,749.35元,已发台账已正确剔除。最终汇总62,616.73元与逐项和62,616.76元相差0.03元,是历史原表舍入差。
|
||||
- 8月纯氢修正版实际已发45,065.47元及原扣减9.68元保持不变,扣减项不再次补发。
|
||||
- 9月145条发放项与Excel缓存值一致,金额公式未发现错误。结转、当月、累计补发当月之间未发现同负责人同车的重复占用。
|
||||
- 实际已领结转次数与1—8月台账逐车一致,问题发生在余额计算方式,不是漏记已领次数。
|
||||
- 9月刘念念全部归业务一部,部门汇总与明细一致;新政策名单沿用原39辆、当月匹配36辆。
|
||||
- 8,737个考核车辆日无重复计入,原始来源文件哈希未改变。此前中断月份清零触发误补发的问题在当前版本未再出现。
|
||||
|
||||
## 6. 仍有数据缺口
|
||||
|
||||
9月1,000个车辆日缺少所需来源,涉及176辆车;其中991个需仪表、9个需GPS。6条考核记录整段缺少可用里程。61条发放项(16,662.34元)带有本月或8月缺口标记,标记不代表这些金额均算错:部分已有里程已经足够达标,结转也可能来自历史。补齐数据后仍可能改变未达标、补发和结转余额。
|
||||
尚建华粤AGR5535虽然9月2天全缺,仍列累计补发9月10元:仅历史已知里程4,246.80公里已经大于包含9月目标后的3,521.43公里,按现行累计达标规则可以复现;0仅为当前已知累计,不是实测零里程。
|
||||
|
||||
## 处理建议
|
||||
|
||||
1. 优先统一无本月考核车辆的结转目标、余额消耗方式,避免一边增加虚拟目标、一边重复扣已用期数。先核实粤AGP5158的150元,再统一17辆后续余额。
|
||||
2. 明确交接后原负责人能否继续领取历史结转;若允许,表内将旧负责人历史结转与新负责人当月奖励清楚分开即可。
|
||||
3. 更正无本月考核车组的累计状态文字,保持累计结果与补发资格两个概念分开。
|
||||
4. 补齐1,000个车辆日缺口后重算。当前40,211.67元保留为现行口径暂算,不表示以上规则问题已解决。
|
||||
|
||||
## 核对范围及证据
|
||||
|
||||
全量核对145条9月发放、1—8月对应已发状态、369个当前车组的历史累计及结转次数;重复展示的个人页沿用已完成金额核验。没有重新从平台获取GPS,也未重新审核1—6月纯氢原始采集准确性。银行支付未另取流水,按用户确认的历史最终表作为已发。
|
||||
|
||||
当前9月文件SHA256:`48adeb7390b57bad94a78f06f84504988a8b8c845a6d3156e11845cb85a8bff4`
|
||||
|
||||
- [里程任务考核_1月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/里程任务考核_1月核算.xlsx)
|
||||
- [里程任务考核_2月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/里程任务考核_2月核算.xlsx)
|
||||
- [里程任务考核_3月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/里程任务考核_3月核算.xlsx)
|
||||
- [里程任务考核_4月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/里程任务考核_4月核算.xlsx)
|
||||
- [里程任务考核_5月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/里程任务考核_5月核算.xlsx)
|
||||
- [里程任务考核_6月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/里程任务考核_6月核算.xlsx)
|
||||
- [里程任务考核_7月核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx)
|
||||
- [里程任务考核_8月核算_纯氢修正版.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/outputs/hydrogen-history-20260916/里程任务考核_8月核算_纯氢修正版.xlsx)
|
||||
- [里程任务考核_2026年9月_含缺口核算.xlsx](/Users/shishengliang/Code/lingniu/mileage-bonus/outputs/september-20261009/里程任务考核_2026年9月_含缺口核算.xlsx)
|
||||
|
||||
复核脚本:audit_september_against_paid.py、independent.py;逐项结果:audit_results.json、history_checks.json。
|
||||
@@ -0,0 +1,820 @@
|
||||
{
|
||||
"amount": 40211.67,
|
||||
"ledger_items": 145,
|
||||
"expected_amount_under_locked_aug_carry": 40211.67,
|
||||
"missing_payments": [],
|
||||
"extra_payments": [],
|
||||
"categories": {
|
||||
"当月": 23692.01,
|
||||
"结转": 13350.0,
|
||||
"累计补发9月": 2349.99,
|
||||
"补发8月": 526.12,
|
||||
"补发7月": 260.0,
|
||||
"补发6月": 0.0,
|
||||
"补发5月": 33.55,
|
||||
"补发4月": 0.0,
|
||||
"补发3月": 0.0,
|
||||
"补发2月": 0.0,
|
||||
"补发1月": 0.0
|
||||
},
|
||||
"historical_backpay": [
|
||||
{
|
||||
"plate": "粤AGR5228",
|
||||
"sales": "吴纬涛",
|
||||
"category": "补发8月",
|
||||
"amount": 150.0,
|
||||
"aug_cumulative_target": 3000.0,
|
||||
"aug_cumulative_actual": 1378.8,
|
||||
"sep_cumulative_target": 6000.0,
|
||||
"sep_cumulative_actual": 6069.0,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR8536",
|
||||
"sales": "吴纬涛",
|
||||
"category": "补发8月",
|
||||
"amount": 24.19,
|
||||
"aug_cumulative_target": 483.87,
|
||||
"aug_cumulative_actual": 371.7,
|
||||
"sep_cumulative_target": 3483.87,
|
||||
"sep_cumulative_actual": 3603.7,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"category": "补发5月",
|
||||
"amount": 33.55,
|
||||
"aug_cumulative_target": 15645.16,
|
||||
"aug_cumulative_actual": 14622.1,
|
||||
"sep_cumulative_target": 20645.16,
|
||||
"sep_cumulative_actual": 20767.2,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"category": "补发7月",
|
||||
"amount": 260.0,
|
||||
"aug_cumulative_target": 15645.16,
|
||||
"aug_cumulative_actual": 14622.1,
|
||||
"sep_cumulative_target": 20645.16,
|
||||
"sep_cumulative_actual": 20767.2,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"category": "补发8月",
|
||||
"amount": 260.0,
|
||||
"aug_cumulative_target": 15645.16,
|
||||
"aug_cumulative_actual": 14622.1,
|
||||
"sep_cumulative_target": 20645.16,
|
||||
"sep_cumulative_actual": 20767.2,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR0772",
|
||||
"sales": "尚建华",
|
||||
"category": "补发8月",
|
||||
"amount": 19.35,
|
||||
"aug_cumulative_target": 387.1,
|
||||
"aug_cumulative_actual": 300.6,
|
||||
"sep_cumulative_target": 3387.1,
|
||||
"sep_cumulative_actual": 6470.6,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5138",
|
||||
"sales": "钟祥",
|
||||
"category": "补发8月",
|
||||
"amount": 72.58,
|
||||
"aug_cumulative_target": 1451.61,
|
||||
"aug_cumulative_actual": 718.5,
|
||||
"sep_cumulative_target": 4451.61,
|
||||
"sep_cumulative_actual": 5932.3,
|
||||
"historically_unpaid": true
|
||||
}
|
||||
],
|
||||
"prior_carry_basis_differences": [
|
||||
{
|
||||
"plate": "粤AGR8531",
|
||||
"sales": "刘念念",
|
||||
"aug_cumulative_target": 17516.13,
|
||||
"aug_cumulative_actual": 40637.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 4,
|
||||
"aug_formula_balance": 3,
|
||||
"sep_saved_balance": 3,
|
||||
"sep_formula_balance": 1,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5158",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 22451.61,
|
||||
"aug_cumulative_actual": 41174.0,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 6,
|
||||
"aug_saved_balance": 1,
|
||||
"aug_formula_balance": 0,
|
||||
"sep_saved_balance": 0,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9786",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 15580.64,
|
||||
"aug_cumulative_actual": 41070.5,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 5,
|
||||
"aug_formula_balance": 4,
|
||||
"sep_saved_balance": 4,
|
||||
"sep_formula_balance": 2,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6877",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 16838.71,
|
||||
"aug_cumulative_actual": 44184.4,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 6,
|
||||
"aug_formula_balance": 5,
|
||||
"sep_saved_balance": 5,
|
||||
"sep_formula_balance": 3,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"sales": "金可鹏",
|
||||
"aug_cumulative_target": 22500.0,
|
||||
"aug_cumulative_actual": 47026.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 7,
|
||||
"aug_saved_balance": 2,
|
||||
"aug_formula_balance": 1,
|
||||
"sep_saved_balance": 1,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
}
|
||||
],
|
||||
"next_balance_differences": [
|
||||
{
|
||||
"plate": "粤AGP3508",
|
||||
"sales": "刘念念",
|
||||
"aug_cumulative_target": 23225.81,
|
||||
"aug_cumulative_actual": 54980.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 6,
|
||||
"aug_saved_balance": 4,
|
||||
"aug_formula_balance": 4,
|
||||
"sep_saved_balance": 3,
|
||||
"sep_formula_balance": 2,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR8531",
|
||||
"sales": "刘念念",
|
||||
"aug_cumulative_target": 17516.13,
|
||||
"aug_cumulative_actual": 40637.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 4,
|
||||
"aug_formula_balance": 3,
|
||||
"sep_saved_balance": 3,
|
||||
"sep_formula_balance": 1,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9825",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 23612.9,
|
||||
"aug_cumulative_actual": 41230.0,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 3,
|
||||
"aug_saved_balance": 2,
|
||||
"aug_formula_balance": 2,
|
||||
"sep_saved_balance": 1,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR0790",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 24000.0,
|
||||
"aug_cumulative_actual": 34894.5,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 1,
|
||||
"aug_saved_balance": 2,
|
||||
"aug_formula_balance": 2,
|
||||
"sep_saved_balance": 1,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6859",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 22935.48,
|
||||
"aug_cumulative_actual": 48164.7,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 5,
|
||||
"aug_saved_balance": 3,
|
||||
"aug_formula_balance": 3,
|
||||
"sep_saved_balance": 2,
|
||||
"sep_formula_balance": 1,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR9815",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 22838.71,
|
||||
"aug_cumulative_actual": 50731.1,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 5,
|
||||
"aug_saved_balance": 4,
|
||||
"aug_formula_balance": 4,
|
||||
"sep_saved_balance": 3,
|
||||
"sep_formula_balance": 2,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR9866",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 22935.48,
|
||||
"aug_cumulative_actual": 60137.7,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 5,
|
||||
"aug_saved_balance": 7,
|
||||
"aug_formula_balance": 7,
|
||||
"sep_saved_balance": 6,
|
||||
"sep_formula_balance": 5,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3080",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 16548.39,
|
||||
"aug_cumulative_actual": 58747.1,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 3,
|
||||
"aug_saved_balance": 11,
|
||||
"aug_formula_balance": 11,
|
||||
"sep_saved_balance": 10,
|
||||
"sep_formula_balance": 9,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9786",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 15580.64,
|
||||
"aug_cumulative_actual": 41070.5,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 5,
|
||||
"aug_formula_balance": 4,
|
||||
"sep_saved_balance": 4,
|
||||
"sep_formula_balance": 2,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR2833",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 17236.17,
|
||||
"aug_cumulative_actual": 44475.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 5,
|
||||
"aug_saved_balance": 4,
|
||||
"aug_formula_balance": 4,
|
||||
"sep_saved_balance": 3,
|
||||
"sep_formula_balance": 2,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6828",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 14806.45,
|
||||
"aug_cumulative_actual": 35422.1,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 3,
|
||||
"aug_saved_balance": 3,
|
||||
"aug_formula_balance": 3,
|
||||
"sep_saved_balance": 2,
|
||||
"sep_formula_balance": 1,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6869",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 11225.81,
|
||||
"aug_cumulative_actual": 35687.9,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 2,
|
||||
"aug_saved_balance": 6,
|
||||
"aug_formula_balance": 6,
|
||||
"sep_saved_balance": 5,
|
||||
"sep_formula_balance": 4,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6877",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 16838.71,
|
||||
"aug_cumulative_actual": 44184.4,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 6,
|
||||
"aug_formula_balance": 5,
|
||||
"sep_saved_balance": 5,
|
||||
"sep_formula_balance": 3,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3506",
|
||||
"sales": "谯云",
|
||||
"aug_cumulative_target": 22064.52,
|
||||
"aug_cumulative_actual": 56936.05,
|
||||
"monthly_target": 5000,
|
||||
"carry_paid_jan_aug": 3,
|
||||
"aug_saved_balance": 3,
|
||||
"aug_formula_balance": 3,
|
||||
"sep_saved_balance": 2,
|
||||
"sep_formula_balance": 1,
|
||||
"sep_payment": 260.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9707",
|
||||
"sales": "谯云",
|
||||
"aug_cumulative_target": 22064.52,
|
||||
"aug_cumulative_actual": 47339.56,
|
||||
"monthly_target": 5000,
|
||||
"carry_paid_jan_aug": 3,
|
||||
"aug_saved_balance": 2,
|
||||
"aug_formula_balance": 2,
|
||||
"sep_saved_balance": 1,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 260.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3627",
|
||||
"sales": "金可鹏",
|
||||
"aug_cumulative_target": 40000.0,
|
||||
"aug_cumulative_actual": 91230.6,
|
||||
"monthly_target": 5000,
|
||||
"carry_paid_jan_aug": 5,
|
||||
"aug_saved_balance": 5,
|
||||
"aug_formula_balance": 5,
|
||||
"sep_saved_balance": 4,
|
||||
"sep_formula_balance": 3,
|
||||
"sep_payment": 260.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"sales": "金可鹏",
|
||||
"aug_cumulative_target": 22500.0,
|
||||
"aug_cumulative_actual": 47026.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 7,
|
||||
"aug_saved_balance": 2,
|
||||
"aug_formula_balance": 1,
|
||||
"sep_saved_balance": 1,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
}
|
||||
],
|
||||
"cumulative_status_inconsistencies": [
|
||||
{
|
||||
"plate": "粤AGP3508",
|
||||
"sales": "刘念念",
|
||||
"target": 26225.81,
|
||||
"actual": 54980.6,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3607",
|
||||
"sales": "刘念念",
|
||||
"target": 44516.13,
|
||||
"actual": 53398.0,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9835",
|
||||
"sales": "刘念念",
|
||||
"target": 26129.03,
|
||||
"actual": 43693.2,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5050",
|
||||
"sales": "刘念念",
|
||||
"target": 25741.94,
|
||||
"actual": 44215.4,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR8531",
|
||||
"sales": "刘念念",
|
||||
"target": 20516.13,
|
||||
"actual": 40637.6,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3027",
|
||||
"sales": "尚建华",
|
||||
"target": 28870.97,
|
||||
"actual": 29873.8,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5158",
|
||||
"sales": "尚建华",
|
||||
"target": 25451.61,
|
||||
"actual": 41174.0,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5165",
|
||||
"sales": "尚建华",
|
||||
"target": 27715.06,
|
||||
"actual": 33049.8,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5770",
|
||||
"sales": "尚建华",
|
||||
"target": 20645.16,
|
||||
"actual": 23343.6,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP6639",
|
||||
"sales": "尚建华",
|
||||
"target": 26225.81,
|
||||
"actual": 34430.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9825",
|
||||
"sales": "尚建华",
|
||||
"target": 26612.9,
|
||||
"actual": 41230.0,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGQ3688",
|
||||
"sales": "尚建华",
|
||||
"target": 26225.81,
|
||||
"actual": 48641.2,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR0790",
|
||||
"sales": "尚建华",
|
||||
"target": 27000.0,
|
||||
"actual": 34894.5,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5539",
|
||||
"sales": "尚建华",
|
||||
"target": 22358.06,
|
||||
"actual": 41459.3,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6859",
|
||||
"sales": "尚建华",
|
||||
"target": 25935.48,
|
||||
"actual": 48164.7,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6881",
|
||||
"sales": "尚建华",
|
||||
"target": 11612.91,
|
||||
"actual": 18752.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR9815",
|
||||
"sales": "尚建华",
|
||||
"target": 25838.71,
|
||||
"actual": 50731.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR9866",
|
||||
"sales": "尚建华",
|
||||
"target": 25935.48,
|
||||
"actual": 60137.7,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3080",
|
||||
"sales": "董剑煜",
|
||||
"target": 19548.39,
|
||||
"actual": 58747.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9785",
|
||||
"sales": "董剑煜",
|
||||
"target": 18870.97,
|
||||
"actual": 29223.8,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9786",
|
||||
"sales": "董剑煜",
|
||||
"target": 18580.64,
|
||||
"actual": 41070.5,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR2833",
|
||||
"sales": "董剑煜",
|
||||
"target": 20236.17,
|
||||
"actual": 44475.6,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6828",
|
||||
"sales": "董剑煜",
|
||||
"target": 17806.45,
|
||||
"actual": 35422.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6869",
|
||||
"sales": "董剑煜",
|
||||
"target": 14225.81,
|
||||
"actual": 35687.9,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6877",
|
||||
"sales": "董剑煜",
|
||||
"target": 19838.71,
|
||||
"actual": 44184.4,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR8500",
|
||||
"sales": "董剑煜",
|
||||
"target": 20332.95,
|
||||
"actual": 35661.2,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3506",
|
||||
"sales": "谯云",
|
||||
"target": 27064.52,
|
||||
"actual": 56936.05,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5760",
|
||||
"sales": "谯云",
|
||||
"target": 26741.94,
|
||||
"actual": 44870.89,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9707",
|
||||
"sales": "谯云",
|
||||
"target": 27064.52,
|
||||
"actual": 47339.56,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR9769",
|
||||
"sales": "谯云",
|
||||
"target": 16935.49,
|
||||
"actual": 28617.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3627",
|
||||
"sales": "金可鹏",
|
||||
"target": 45000.0,
|
||||
"actual": 91230.6,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3692",
|
||||
"sales": "金可鹏",
|
||||
"target": 45000.0,
|
||||
"actual": 73049.1,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"sales": "金可鹏",
|
||||
"target": 25500.0,
|
||||
"actual": 47026.6,
|
||||
"stored": false,
|
||||
"calculated": true,
|
||||
"virtual": true
|
||||
}
|
||||
],
|
||||
"used_count_differences": [],
|
||||
"cross_owner_payments": [
|
||||
{
|
||||
"plate": "粤AGP3080",
|
||||
"carry_owner": "董剑煜",
|
||||
"carry_amount": 150.0,
|
||||
"current_payments": [
|
||||
{
|
||||
"sales": "尚建华",
|
||||
"category": "当月",
|
||||
"amount": 5.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6869",
|
||||
"carry_owner": "董剑煜",
|
||||
"carry_amount": 150.0,
|
||||
"current_payments": [
|
||||
{
|
||||
"sales": "尚建华",
|
||||
"category": "当月",
|
||||
"amount": 150.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3627",
|
||||
"carry_owner": "金可鹏",
|
||||
"carry_amount": 260.0,
|
||||
"current_payments": [
|
||||
{
|
||||
"sales": "秦挺",
|
||||
"category": "当月",
|
||||
"amount": 260.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3692",
|
||||
"carry_owner": "金可鹏",
|
||||
"carry_amount": 260.0,
|
||||
"current_payments": [
|
||||
{
|
||||
"sales": "秦挺",
|
||||
"category": "当月",
|
||||
"amount": 260.0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"carry_owner": "金可鹏",
|
||||
"carry_amount": 150.0,
|
||||
"current_payments": [
|
||||
{
|
||||
"sales": "钟祥",
|
||||
"category": "当月",
|
||||
"amount": 5.0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"virtual_count": 33,
|
||||
"virtual_amount": 5940.0,
|
||||
"gap_payment_count": 61,
|
||||
"gap_payment_amount": 16662.34,
|
||||
"wholly_missing": [
|
||||
{
|
||||
"车牌号": "粤AGR5535",
|
||||
"销售经理": "尚建华",
|
||||
"考核天数": 2
|
||||
},
|
||||
{
|
||||
"车牌号": "粤AG39007",
|
||||
"销售经理": "吴纬涛",
|
||||
"考核天数": 9
|
||||
},
|
||||
{
|
||||
"车牌号": "粤AGP5139",
|
||||
"销售经理": "吴纬涛",
|
||||
"考核天数": 30
|
||||
},
|
||||
{
|
||||
"车牌号": "粤AGP5639",
|
||||
"销售经理": "吴纬涛",
|
||||
"考核天数": 30
|
||||
},
|
||||
{
|
||||
"车牌号": "粤AGR1586",
|
||||
"销售经理": "吴纬涛",
|
||||
"考核天数": 30
|
||||
},
|
||||
{
|
||||
"车牌号": "粤AGR8551",
|
||||
"销售经理": "吴纬涛",
|
||||
"考核天数": 30
|
||||
}
|
||||
],
|
||||
"source_hash_checks": {
|
||||
"assessment": true,
|
||||
"meter": true,
|
||||
"gps": true,
|
||||
"hydrogen": true,
|
||||
"tests": true,
|
||||
"policy": true
|
||||
},
|
||||
"excel_ledger_matches": true,
|
||||
"formula_errors": [],
|
||||
"workbook_sha256": "0f734b431c860b29aa97177ecc5c36e6f1ba04df4b1ecd853178bee7eb30ccf4"
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Read-only audit. No business workbook or calculation state is modified."""
|
||||
from pathlib import Path
|
||||
from collections import Counter,defaultdict
|
||||
from decimal import Decimal,ROUND_HALF_UP
|
||||
import json,hashlib,calendar
|
||||
import openpyxl
|
||||
P=Path(__file__).resolve().parent;S=P.parent;H=S.parent/'hydrogen-history-20260916'
|
||||
load=lambda p:json.loads(p.read_text())
|
||||
d=load(S/'september_calculation.json');aug=load(H/'august_calculation.json');paid=load(H/'historical_paid.json')
|
||||
money=lambda v:float(Decimal(str(v)).quantize(Decimal('.01'),rounding=ROUND_HALF_UP))
|
||||
key=lambda x:(x['plate'],x['sales'])
|
||||
groups={key(g):g for g in d['groups']};prior={key(g):g for g in aug['groups']}
|
||||
hist={(x['month'],x['plate'],x['sales']):x for x in d['historical_months']}
|
||||
settled={(r['assessment_month'],r['plate'],r['owner']) for r in paid}
|
||||
settled.update((l['assessment_month'],*key(l)) for l in aug['ledger'] if l['gross_amount']>0)
|
||||
carry_paid=Counter((r['plate'],r['owner']) for r in paid if r['category']=='结转')
|
||||
carry_paid.update(key(l) for l in aug['ledger'] if l['category']=='结转' and l['amount']>0)
|
||||
ledger={(l['plate'],l['sales'],l['category']):l for l in d['ledger']}
|
||||
assert len(ledger)==len(d['ledger'])
|
||||
expected={};details=[];carry_basis=[];balance_inconsistent=[];status_inconsistent=[];used_inconsistent=[];cross_owner=[]
|
||||
for k,g in groups.items():
|
||||
rows=[r for r in d['records'] if (r['车牌号'],r['销售经理'])==k]
|
||||
hist_t=sum(x['target'] for h,x in hist.items() if h[1:]==k)
|
||||
hist_a=sum(x['actual'] for h,x in hist.items() if h[1:]==k)
|
||||
full_t=money(hist_t+g['target']);full_a=money(hist_a+g['actual']);cq=full_a>=full_t and full_t>0
|
||||
assert abs(full_t-g['cum_target'])<.02 and abs(full_a-g['cum_actual'])<.02,k
|
||||
prev=prior.get(k,{})
|
||||
locked=prev.get('carry_left',0)
|
||||
if locked>=1:expected[(*k,'结转')]=money(g['reward'])
|
||||
else:
|
||||
current=sum(Decimal(str(r['月奖励']))*Decimal(str(r['考核天数']))/30 for r in rows if r['实际行驶里程(km)']>=r['应考核里程(km)'])
|
||||
if current:expected[(*k,'当月')]=money(current)
|
||||
if cq and not g['virtual']:
|
||||
for m in range(1,9):
|
||||
old=hist.get((m,*k))
|
||||
if old and (m,*k) not in settled:expected[(*k,f'补发{m}月')]=money(Decimal(str(old['reward']))*Decimal(str(old['days']))/calendar.monthrange(2026,m)[1])
|
||||
if not any(r['实际行驶里程(km)']>=r['应考核里程(km)'] for r in rows) and locked<1:
|
||||
expected[(*k,'累计补发9月')]=money(Decimal(str(g['reward']))*Decimal(str(g['days']))/30)
|
||||
used_actual=carry_paid[k]+int(locked>=1)
|
||||
if used_actual!=g['used_carry']:used_inconsistent.append(dict(plate=k[0],sales=k[1],ledger_used=used_actual,stored_used=g['used_carry']))
|
||||
recomputed_prior=max(0,int(max(0,hist_a-hist_t)//g['monthly_target'])-carry_paid[k])
|
||||
recomputed_balance=max(0,int(max(0,full_a-full_t)//g['monthly_target'])-used_actual)
|
||||
evidence=dict(plate=k[0],sales=k[1],aug_cumulative_target=money(hist_t),aug_cumulative_actual=money(hist_a),monthly_target=g['monthly_target'],carry_paid_jan_aug=carry_paid[k],aug_saved_balance=locked,aug_formula_balance=recomputed_prior,sep_saved_balance=g['carry_left'],sep_formula_balance=recomputed_balance,sep_payment=g['total'],virtual=g['virtual'])
|
||||
if recomputed_prior!=locked:carry_basis.append(evidence)
|
||||
if recomputed_balance!=g['carry_left']:balance_inconsistent.append(evidence)
|
||||
if cq!=g['cum_qualified']:status_inconsistent.append(dict(plate=k[0],sales=k[1],target=full_t,actual=full_a,stored=g['cum_qualified'],calculated=cq,virtual=g['virtual']))
|
||||
if g['virtual']:
|
||||
other=[l for l in d['ledger'] if l['plate']==k[0] and l['sales']!=k[1]]
|
||||
if other:cross_owner.append(dict(plate=k[0],carry_owner=k[1],carry_amount=ledger.get((*k,'结转'),{}).get('amount',0),current_payments=[{f:l[f] for f in ['sales','category','amount']} for l in other]))
|
||||
for l in d['ledger']:
|
||||
if key(l)==k and l['assessment_month']<9:
|
||||
assert (l['assessment_month'],*k) not in settled
|
||||
details.append({**{f:l[f] for f in ['plate','sales','category','amount']},'aug_cumulative_target':prev.get('cum_target'),'aug_cumulative_actual':prev.get('cum_actual'),'sep_cumulative_target':full_t,'sep_cumulative_actual':full_a,'historically_unpaid':True})
|
||||
missing=[dict(plate=k[0],sales=k[1],category=k[2],expected=v,actual=ledger.get(k,{}).get('amount',0)) for k,v in expected.items() if abs(v-ledger.get(k,{}).get('amount',0))>.001]
|
||||
extra=[dict(plate=k[0],sales=k[1],category=k[2],actual=l['amount'],expected=expected.get(k,0)) for k,l in ledger.items() if abs(l['amount']-expected.get(k,0))>.001]
|
||||
source_checks={}
|
||||
for tag,x in d['sources'].items():source_checks[tag]=hashlib.sha256(Path(x['path']).read_bytes()).hexdigest()==x['sha256']
|
||||
fp=S/'里程任务考核_2026年9月_含缺口核算.xlsx';wb=openpyxl.load_workbook(fp,data_only=True,read_only=True)
|
||||
xrows=list(wb['9月奖金发放记录'].values)
|
||||
excel_ledger={(r[0],r[1],r[4]):r[9] for r in xrows[1:] if r[0] and r[0]!='合计' and isinstance(r[9],(int,float))}
|
||||
assert excel_ledger=={k:l['amount'] for k,l in ledger.items()}
|
||||
errors=[(s.title,c.coordinate,c.value) for s in wb for row in s for c in row if c.data_type=='e'];wb.close()
|
||||
assert not errors
|
||||
by_cat={c:money(sum(l['amount'] for l in d['ledger'] if l['category']==c)) for c in d['categories']}
|
||||
virtual_pay=[l for l in d['ledger'] if l['virtual']]
|
||||
gap_pay=[l for l in d['ledger'] if l['gap_days']>0 or l['history_gap_days']>0]
|
||||
wholly=[r for r in d['records'] if r['缺口天数']==r['考核天数']]
|
||||
out=dict(amount=d['summary']['amount'],ledger_items=len(ledger),expected_amount_under_locked_aug_carry=money(sum(expected.values())),missing_payments=missing,extra_payments=extra,categories=by_cat,historical_backpay=details,prior_carry_basis_differences=carry_basis,next_balance_differences=balance_inconsistent,cumulative_status_inconsistencies=status_inconsistent,used_count_differences=used_inconsistent,cross_owner_payments=cross_owner,virtual_count=len(virtual_pay),virtual_amount=money(sum(l['amount'] for l in virtual_pay)),gap_payment_count=len(gap_pay),gap_payment_amount=money(sum(l['amount'] for l in gap_pay)),wholly_missing=[{f:r[f] for f in ['车牌号','销售经理','考核天数']} for r in wholly],source_hash_checks=source_checks,excel_ledger_matches=True,formula_errors=errors,workbook_sha256=hashlib.sha256(fp.read_bytes()).hexdigest())
|
||||
(P/'audit_results.json').write_text(json.dumps(out,ensure_ascii=False,indent=2));print(json.dumps({k:v for k,v in out.items() if k not in ['next_balance_differences','cumulative_status_inconsistencies','source_hash_checks','historical_backpay']},ensure_ascii=False,indent=2));print('NEXT_BALANCES',len(balance_inconsistent),'STATUS',len(status_inconsistent))
|
||||
@@ -0,0 +1,500 @@
|
||||
{
|
||||
"controls": [
|
||||
{
|
||||
"month": 1,
|
||||
"raw_sum": 21212.27,
|
||||
"locked_sum": 21212.27
|
||||
},
|
||||
{
|
||||
"month": 2,
|
||||
"raw_sum": 21152.15,
|
||||
"locked_sum": 21152.15
|
||||
},
|
||||
{
|
||||
"month": 3,
|
||||
"raw_sum": 56907.08,
|
||||
"locked_sum": 56907.08
|
||||
},
|
||||
{
|
||||
"month": 4,
|
||||
"raw_sum": 86848.09,
|
||||
"locked_sum": 86848.09
|
||||
},
|
||||
{
|
||||
"month": 5,
|
||||
"raw_sum": 92912.54,
|
||||
"locked_sum": 92912.54
|
||||
},
|
||||
{
|
||||
"month": 6,
|
||||
"raw_sum": 93696.88,
|
||||
"locked_sum": 93696.88
|
||||
},
|
||||
{
|
||||
"month": 7,
|
||||
"raw_sum": 65366.11,
|
||||
"locked_sum": 62616.76
|
||||
}
|
||||
],
|
||||
"raw_vs_locked_differences": [
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGP5623",
|
||||
"owner": "岑彦",
|
||||
"category": "累计补发7月",
|
||||
"amount": 260,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 83
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGP5685",
|
||||
"owner": "岑彦",
|
||||
"category": "结转",
|
||||
"amount": 150,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 84
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGQ8399",
|
||||
"owner": "岑彦",
|
||||
"category": "结转",
|
||||
"amount": 150,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 85
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤A02509F",
|
||||
"owner": "赵连飞",
|
||||
"category": "结转",
|
||||
"amount": 1000,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 145
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤A06158F",
|
||||
"owner": "赵连飞",
|
||||
"category": "累计补发7月",
|
||||
"amount": 451.61,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 146
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGP5691",
|
||||
"owner": "赵连飞",
|
||||
"category": "结转",
|
||||
"amount": 260,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 147
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGP5712",
|
||||
"owner": "赵连飞",
|
||||
"category": "结转",
|
||||
"amount": 260,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 148
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGP9331",
|
||||
"owner": "赵连飞",
|
||||
"category": "结转",
|
||||
"amount": 150,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 149
|
||||
},
|
||||
{
|
||||
"payment_month": 7,
|
||||
"assessment_month": 7,
|
||||
"plate": "粤AGR8539",
|
||||
"owner": "赵连飞",
|
||||
"category": "累计补发7月",
|
||||
"amount": 67.74,
|
||||
"source": "/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx",
|
||||
"source_row": 150
|
||||
}
|
||||
],
|
||||
"backpay_checks": [
|
||||
{
|
||||
"id": 129,
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"department": "业务二部",
|
||||
"category": "补发5月",
|
||||
"assessment_month": 5,
|
||||
"base": 260,
|
||||
"days": 4.0,
|
||||
"month_days": 31,
|
||||
"amount": 33.55,
|
||||
"gross_amount": 33.55,
|
||||
"withheld": 0,
|
||||
"unrounded": 33.54838709677419,
|
||||
"virtual": false,
|
||||
"gap_days": 6,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算",
|
||||
"prior_same_plate_month": []
|
||||
},
|
||||
{
|
||||
"id": 130,
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"department": "业务二部",
|
||||
"category": "补发7月",
|
||||
"assessment_month": 7,
|
||||
"base": 260,
|
||||
"days": 31.0,
|
||||
"month_days": 31,
|
||||
"amount": 260.0,
|
||||
"gross_amount": 260.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 260.0,
|
||||
"virtual": false,
|
||||
"gap_days": 6,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算",
|
||||
"prior_same_plate_month": []
|
||||
},
|
||||
{
|
||||
"id": 131,
|
||||
"plate": "粤AGP5138",
|
||||
"sales": "钟祥",
|
||||
"department": "业务一部",
|
||||
"category": "补发8月",
|
||||
"assessment_month": 8,
|
||||
"base": 150,
|
||||
"days": 15,
|
||||
"month_days": 31,
|
||||
"amount": 72.58,
|
||||
"gross_amount": 72.58,
|
||||
"withheld": 0,
|
||||
"unrounded": 72.58064516129032,
|
||||
"virtual": false,
|
||||
"gap_days": 5,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算",
|
||||
"prior_same_plate_month": []
|
||||
},
|
||||
{
|
||||
"id": 132,
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"department": "业务二部",
|
||||
"category": "补发8月",
|
||||
"assessment_month": 8,
|
||||
"base": 260,
|
||||
"days": 31,
|
||||
"month_days": 31,
|
||||
"amount": 260.0,
|
||||
"gross_amount": 260.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 260.0,
|
||||
"virtual": false,
|
||||
"gap_days": 6,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算",
|
||||
"prior_same_plate_month": []
|
||||
},
|
||||
{
|
||||
"id": 133,
|
||||
"plate": "粤AGR0772",
|
||||
"sales": "尚建华",
|
||||
"department": "业务二部",
|
||||
"category": "补发8月",
|
||||
"assessment_month": 8,
|
||||
"base": 150,
|
||||
"days": 4,
|
||||
"month_days": 31,
|
||||
"amount": 19.35,
|
||||
"gross_amount": 19.35,
|
||||
"withheld": 0,
|
||||
"unrounded": 19.35483870967742,
|
||||
"virtual": false,
|
||||
"gap_days": 3,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算",
|
||||
"prior_same_plate_month": [
|
||||
{
|
||||
"payment_month": 8,
|
||||
"assessment_month": 8,
|
||||
"plate": "粤AGR0772",
|
||||
"owner": "谯云",
|
||||
"category": "当月",
|
||||
"amount": 116.13
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 134,
|
||||
"plate": "粤AGR5228",
|
||||
"sales": "吴纬涛",
|
||||
"department": "业务三部",
|
||||
"category": "补发8月",
|
||||
"assessment_month": 8,
|
||||
"base": 150,
|
||||
"days": 31,
|
||||
"month_days": 31,
|
||||
"amount": 150.0,
|
||||
"gross_amount": 150.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 150.0,
|
||||
"virtual": false,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全",
|
||||
"prior_same_plate_month": []
|
||||
},
|
||||
{
|
||||
"id": 135,
|
||||
"plate": "粤AGR8536",
|
||||
"sales": "吴纬涛",
|
||||
"department": "业务三部",
|
||||
"category": "补发8月",
|
||||
"assessment_month": 8,
|
||||
"base": 150,
|
||||
"days": 5,
|
||||
"month_days": 31,
|
||||
"amount": 24.19,
|
||||
"gross_amount": 24.19,
|
||||
"withheld": 0,
|
||||
"unrounded": 24.193548387096776,
|
||||
"virtual": false,
|
||||
"gap_days": 5,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算",
|
||||
"prior_same_plate_month": []
|
||||
}
|
||||
],
|
||||
"multiple_owners_same_plate_paid": [
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"items": [
|
||||
{
|
||||
"id": 35,
|
||||
"plate": "粤AGR5560",
|
||||
"sales": "钟祥",
|
||||
"department": "业务一部",
|
||||
"category": "当月",
|
||||
"assessment_month": 9,
|
||||
"base": 150,
|
||||
"days": 1,
|
||||
"month_days": 30,
|
||||
"amount": 5.0,
|
||||
"gross_amount": 5.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 5.0,
|
||||
"virtual": false,
|
||||
"gap_days": 1,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算"
|
||||
},
|
||||
{
|
||||
"id": 128,
|
||||
"plate": "粤AGR5560",
|
||||
"sales": "金可鹏",
|
||||
"department": "业务三部",
|
||||
"category": "结转",
|
||||
"assessment_month": 9,
|
||||
"base": 150,
|
||||
"days": 1,
|
||||
"month_days": 1,
|
||||
"amount": 150.0,
|
||||
"gross_amount": 150.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 150.0,
|
||||
"virtual": true,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3080",
|
||||
"items": [
|
||||
{
|
||||
"id": 40,
|
||||
"plate": "粤AGP3080",
|
||||
"sales": "尚建华",
|
||||
"department": "业务二部",
|
||||
"category": "当月",
|
||||
"assessment_month": 9,
|
||||
"base": 150,
|
||||
"days": 1,
|
||||
"month_days": 30,
|
||||
"amount": 5.0,
|
||||
"gross_amount": 5.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 5.0,
|
||||
"virtual": false,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
},
|
||||
{
|
||||
"id": 116,
|
||||
"plate": "粤AGP3080",
|
||||
"sales": "董剑煜",
|
||||
"department": "业务二部",
|
||||
"category": "结转",
|
||||
"assessment_month": 9,
|
||||
"base": 150,
|
||||
"days": 1,
|
||||
"month_days": 1,
|
||||
"amount": 150.0,
|
||||
"gross_amount": 150.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 150.0,
|
||||
"virtual": true,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6869",
|
||||
"items": [
|
||||
{
|
||||
"id": 46,
|
||||
"plate": "粤AGR6869",
|
||||
"sales": "尚建华",
|
||||
"department": "业务二部",
|
||||
"category": "当月",
|
||||
"assessment_month": 9,
|
||||
"base": 150,
|
||||
"days": 30,
|
||||
"month_days": 30,
|
||||
"amount": 150.0,
|
||||
"gross_amount": 150.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 150.0,
|
||||
"virtual": false,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
},
|
||||
{
|
||||
"id": 120,
|
||||
"plate": "粤AGR6869",
|
||||
"sales": "董剑煜",
|
||||
"department": "业务二部",
|
||||
"category": "结转",
|
||||
"assessment_month": 9,
|
||||
"base": 150,
|
||||
"days": 1,
|
||||
"month_days": 1,
|
||||
"amount": 150.0,
|
||||
"gross_amount": 150.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 150.0,
|
||||
"virtual": true,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3627",
|
||||
"items": [
|
||||
{
|
||||
"id": 69,
|
||||
"plate": "粤AGP3627",
|
||||
"sales": "秦挺",
|
||||
"department": "业务五部",
|
||||
"category": "当月",
|
||||
"assessment_month": 9,
|
||||
"base": 260,
|
||||
"days": 30,
|
||||
"month_days": 30,
|
||||
"amount": 260.0,
|
||||
"gross_amount": 260.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 260.0,
|
||||
"virtual": false,
|
||||
"gap_days": 1,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算"
|
||||
},
|
||||
{
|
||||
"id": 122,
|
||||
"plate": "粤AGP3627",
|
||||
"sales": "金可鹏",
|
||||
"department": "业务三部",
|
||||
"category": "结转",
|
||||
"assessment_month": 9,
|
||||
"base": 260,
|
||||
"days": 1,
|
||||
"month_days": 1,
|
||||
"amount": 260.0,
|
||||
"gross_amount": 260.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 260.0,
|
||||
"virtual": true,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3692",
|
||||
"items": [
|
||||
{
|
||||
"id": 70,
|
||||
"plate": "粤AGP3692",
|
||||
"sales": "秦挺",
|
||||
"department": "业务五部",
|
||||
"category": "当月",
|
||||
"assessment_month": 9,
|
||||
"base": 260,
|
||||
"days": 30,
|
||||
"month_days": 30,
|
||||
"amount": 260.0,
|
||||
"gross_amount": 260.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 260.0,
|
||||
"virtual": false,
|
||||
"gap_days": 1,
|
||||
"history_gap_days": 0,
|
||||
"status": "按现有里程计算"
|
||||
},
|
||||
{
|
||||
"id": 123,
|
||||
"plate": "粤AGP3692",
|
||||
"sales": "金可鹏",
|
||||
"department": "业务三部",
|
||||
"category": "结转",
|
||||
"assessment_month": 9,
|
||||
"base": 260,
|
||||
"days": 1,
|
||||
"month_days": 1,
|
||||
"amount": 260.0,
|
||||
"gross_amount": 260.0,
|
||||
"withheld": 0,
|
||||
"unrounded": 260.0,
|
||||
"virtual": true,
|
||||
"gap_days": 0,
|
||||
"history_gap_days": 0,
|
||||
"status": "数据齐全"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"eligibility_mismatches": []
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import json,openpyxl,re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict,Counter
|
||||
P=Path(__file__).resolve().parents[1];R=P.parents[1];H=P.parent/'hydrogen-history-20260916'
|
||||
s=json.loads((P/'september_calculation.json').read_text());paid=json.loads((H/'historical_paid.json').read_text())
|
||||
raw=[];dif=[];controls=[]
|
||||
for m in range(1,8):
|
||||
f=R/f'里程任务考核_{m}月核算.xlsx' if m<7 else R/'7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx'
|
||||
w=openpyxl.load_workbook(f,data_only=True,read_only=True);rows=list(w[f'{m}月奖金发放记录'].values)
|
||||
for rn,r in enumerate(rows[1:],2):
|
||||
if not r[0] or not isinstance(r[9],(int,float)) or r[9]<=0:continue
|
||||
q=dict(payment_month=m,assessment_month=int(re.search(r'补发(\d+)月',r[4]).group(1)) if '补发' in r[4] else m,plate=r[0],owner=r[1],category=r[4],amount=r[9],source=str(f),source_row=rn);raw.append(q)
|
||||
found=[p for p in paid if p['payment_month']==m and p['source_row']==rn]
|
||||
if not found or any(q[k]!=found[0][k] for k in ['plate','owner','category','amount','assessment_month']):dif.append(q)
|
||||
controls.append(dict(month=m,raw_sum=round(sum(x['amount'] for x in raw if x['payment_month']==m),2),locked_sum=round(sum(x['amount'] for x in paid if x['payment_month']==m),2)))
|
||||
w.close()
|
||||
allpaid=raw+[dict(payment_month=8,assessment_month=l['assessment_month'],plate=l['plate'],owner=l['sales'],category=l['category'],amount=l['gross_amount']) for l in s['august_ledger'] if l['gross_amount']>0]
|
||||
back=[]
|
||||
for l in s['ledger']:
|
||||
if l['assessment_month']<9:
|
||||
prior=[p for p in allpaid if p['assessment_month']==l['assessment_month'] and p['plate']==l['plate']]
|
||||
back.append(dict(**l,prior_same_plate_month=prior))
|
||||
byplate=defaultdict(list)
|
||||
for l in s['ledger']:byplate[l['plate']].append(l)
|
||||
multi=[dict(plate=p,items=ls) for p,ls in byplate.items() if len({l['sales'] for l in ls})>1]
|
||||
# Check complete history eligibility independently from engine, all record/group state used only as metadata.
|
||||
elig=[]
|
||||
for g in s['groups']:
|
||||
if g['virtual']:continue
|
||||
h=[h for h in s['historical_months'] if h['plate']==g['plate'] and h['sales']==g['sales']]
|
||||
t=sum(x['target'] for x in h)+g['target'];a=sum(x['actual'] for x in h)+g['actual']
|
||||
for x in h:
|
||||
settled=any(p['assessment_month']==x['month'] and p['plate']==g['plate'] and p['owner']==g['sales'] for p in allpaid)
|
||||
due=a>=t>0 and not settled
|
||||
has=any(l['plate']==g['plate'] and l['sales']==g['sales'] and l['assessment_month']==x['month'] for l in s['ledger'])
|
||||
if due!=has:elig.append(dict(group=g,month=x,due=due,has=has))
|
||||
result=dict(controls=controls,raw_vs_locked_differences=dif,backpay_checks=back,multiple_owners_same_plate_paid=multi,eligibility_mismatches=elig)
|
||||
(P/'audit_history/history_checks.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
|
||||
print(json.dumps(result,ensure_ascii=False,indent=2))
|
||||
@@ -0,0 +1,372 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"items": [
|
||||
{
|
||||
"id": "historical-backpay",
|
||||
"title": "7笔历史补发未发现重复",
|
||||
"queries": [
|
||||
{
|
||||
"id": "historical-backpay-evidence",
|
||||
"source": {
|
||||
"label": "2026年里程绩效核算表",
|
||||
"files": [
|
||||
{
|
||||
"label": "里程任务考核_1月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_2月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_3月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_4月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_5月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_6月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_7月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_8月核算_纯氢修正版.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_2026年9月_含缺口核算.xlsx"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
"按车辆与原考核负责人区分资格",
|
||||
"历史已发锁定,不追扣"
|
||||
],
|
||||
"caveats": [
|
||||
"已发以用户确认的历史最终表为准,未另取银行流水。"
|
||||
]
|
||||
},
|
||||
"reportingPeriod": "2026年1—8月已发,对比9月核算",
|
||||
"columns": [
|
||||
"plate",
|
||||
"sales",
|
||||
"category",
|
||||
"amount"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"plate": "粤AGR5228",
|
||||
"sales": "吴纬涛",
|
||||
"category": "补发8月",
|
||||
"amount": 150.0,
|
||||
"aug_cumulative_target": 3000.0,
|
||||
"aug_cumulative_actual": 1378.8,
|
||||
"sep_cumulative_target": 6000.0,
|
||||
"sep_cumulative_actual": 6069.0,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR8536",
|
||||
"sales": "吴纬涛",
|
||||
"category": "补发8月",
|
||||
"amount": 24.19,
|
||||
"aug_cumulative_target": 483.87,
|
||||
"aug_cumulative_actual": 371.7,
|
||||
"sep_cumulative_target": 3483.87,
|
||||
"sep_cumulative_actual": 3603.7,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"category": "补发5月",
|
||||
"amount": 33.55,
|
||||
"aug_cumulative_target": 15645.16,
|
||||
"aug_cumulative_actual": 14622.1,
|
||||
"sep_cumulative_target": 20645.16,
|
||||
"sep_cumulative_actual": 20767.2,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"category": "补发7月",
|
||||
"amount": 260.0,
|
||||
"aug_cumulative_target": 15645.16,
|
||||
"aug_cumulative_actual": 14622.1,
|
||||
"sep_cumulative_target": 20645.16,
|
||||
"sep_cumulative_actual": 20767.2,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3612",
|
||||
"sales": "尚建华",
|
||||
"category": "补发8月",
|
||||
"amount": 260.0,
|
||||
"aug_cumulative_target": 15645.16,
|
||||
"aug_cumulative_actual": 14622.1,
|
||||
"sep_cumulative_target": 20645.16,
|
||||
"sep_cumulative_actual": 20767.2,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR0772",
|
||||
"sales": "尚建华",
|
||||
"category": "补发8月",
|
||||
"amount": 19.35,
|
||||
"aug_cumulative_target": 387.1,
|
||||
"aug_cumulative_actual": 300.6,
|
||||
"sep_cumulative_target": 3387.1,
|
||||
"sep_cumulative_actual": 6470.6,
|
||||
"historically_unpaid": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5138",
|
||||
"sales": "钟祥",
|
||||
"category": "补发8月",
|
||||
"amount": 72.58,
|
||||
"aug_cumulative_target": 1451.61,
|
||||
"aug_cumulative_actual": 718.5,
|
||||
"sep_cumulative_target": 4451.61,
|
||||
"sep_cumulative_actual": 5932.3,
|
||||
"historically_unpaid": true
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"language": "calculation",
|
||||
"code": "33.55 + 260 + 260 + 72.58 + 19.35 + 150 + 24.19 = 819.67元;逐笔排除同车、同负责人、同考核月已经发放。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "carry-balance",
|
||||
"title": "无本月考核车辆的结转余额存在两种口径",
|
||||
"queries": [
|
||||
{
|
||||
"id": "carry-balance-evidence",
|
||||
"source": {
|
||||
"label": "2026年里程绩效核算表",
|
||||
"files": [
|
||||
{
|
||||
"label": "里程任务考核_1月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_2月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_3月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_4月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_5月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_6月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_7月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_8月核算_纯氢修正版.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_2026年9月_含缺口核算.xlsx"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
"按车辆与原考核负责人区分资格",
|
||||
"历史已发锁定,不追扣"
|
||||
],
|
||||
"caveats": [
|
||||
"150元是口径待确认金额,不能直接认定多发;虚拟考核目标是否应增加尚需明确。"
|
||||
]
|
||||
},
|
||||
"reportingPeriod": "2026年1—8月已发,对比9月核算",
|
||||
"columns": [
|
||||
"plate",
|
||||
"sales",
|
||||
"aug_saved_balance",
|
||||
"aug_formula_balance",
|
||||
"carry_paid_jan_aug"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"plate": "粤AGR8531",
|
||||
"sales": "刘念念",
|
||||
"aug_cumulative_target": 17516.13,
|
||||
"aug_cumulative_actual": 40637.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 4,
|
||||
"aug_formula_balance": 3,
|
||||
"sep_saved_balance": 3,
|
||||
"sep_formula_balance": 1,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP5158",
|
||||
"sales": "尚建华",
|
||||
"aug_cumulative_target": 22451.61,
|
||||
"aug_cumulative_actual": 41174.0,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 6,
|
||||
"aug_saved_balance": 1,
|
||||
"aug_formula_balance": 0,
|
||||
"sep_saved_balance": 0,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP9786",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 15580.64,
|
||||
"aug_cumulative_actual": 41070.5,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 5,
|
||||
"aug_formula_balance": 4,
|
||||
"sep_saved_balance": 4,
|
||||
"sep_formula_balance": 2,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6877",
|
||||
"sales": "董剑煜",
|
||||
"aug_cumulative_target": 16838.71,
|
||||
"aug_cumulative_actual": 44184.4,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 4,
|
||||
"aug_saved_balance": 6,
|
||||
"aug_formula_balance": 5,
|
||||
"sep_saved_balance": 5,
|
||||
"sep_formula_balance": 3,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"sales": "金可鹏",
|
||||
"aug_cumulative_target": 22500.0,
|
||||
"aug_cumulative_actual": 47026.6,
|
||||
"monthly_target": 3000,
|
||||
"carry_paid_jan_aug": 7,
|
||||
"aug_saved_balance": 2,
|
||||
"aug_formula_balance": 1,
|
||||
"sep_saved_balance": 1,
|
||||
"sep_formula_balance": 0,
|
||||
"sep_payment": 150.0,
|
||||
"virtual": true
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"language": "calculation",
|
||||
"code": "粤AGP5158:floor((41174.00 − 22451.61) / 3000) − 6 = 0期;8月表内余额1期,9月据此发150元。9月底另有17组余额与统一公式不一致。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "owner-handover",
|
||||
"title": "5辆车原负责人领结转且新负责人领当月奖金",
|
||||
"queries": [
|
||||
{
|
||||
"id": "owner-handover-evidence",
|
||||
"source": {
|
||||
"label": "2026年里程绩效核算表",
|
||||
"files": [
|
||||
{
|
||||
"label": "里程任务考核_1月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_2月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_3月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_4月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_5月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_6月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_7月核算.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_8月核算_纯氢修正版.xlsx"
|
||||
},
|
||||
{
|
||||
"label": "里程任务考核_2026年9月_含缺口核算.xlsx"
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
"按车辆与原考核负责人区分资格",
|
||||
"历史已发锁定,不追扣"
|
||||
],
|
||||
"caveats": [
|
||||
"按目前车牌加负责人分账可以复现,不等于同一负责人重复发放;是否交接后停止旧负责人结转属于业务规则。"
|
||||
]
|
||||
},
|
||||
"reportingPeriod": "2026年1—8月已发,对比9月核算",
|
||||
"columns": [
|
||||
"plate",
|
||||
"owner",
|
||||
"carry_amount",
|
||||
"current"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"plate": "粤AGP3080",
|
||||
"owner": "董剑煜",
|
||||
"carry_amount": 150.0,
|
||||
"current": "[{'sales': '尚建华', 'category': '当月', 'amount': 5.0}]"
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR6869",
|
||||
"owner": "董剑煜",
|
||||
"carry_amount": 150.0,
|
||||
"current": "[{'sales': '尚建华', 'category': '当月', 'amount': 150.0}]"
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3627",
|
||||
"owner": "金可鹏",
|
||||
"carry_amount": 260.0,
|
||||
"current": "[{'sales': '秦挺', 'category': '当月', 'amount': 260.0}]"
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3692",
|
||||
"owner": "金可鹏",
|
||||
"carry_amount": 260.0,
|
||||
"current": "[{'sales': '秦挺', 'category': '当月', 'amount': 260.0}]"
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5560",
|
||||
"owner": "金可鹏",
|
||||
"carry_amount": 150.0,
|
||||
"current": "[{'sales': '钟祥', 'category': '当月', 'amount': 5.0}]"
|
||||
}
|
||||
],
|
||||
"methods": [
|
||||
{
|
||||
"language": "calculation",
|
||||
"code": "150 + 150 + 150 + 260 + 260 = 970元原负责人结转,包含在33笔无本月考核结转5940元中。"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
from pathlib import Path
|
||||
import json,hashlib
|
||||
P=Path(__file__).parent;S=P.parent;R=S.parents[1]
|
||||
a=json.loads((P/'audit_results.json').read_text());d=json.loads((S/'september_calculation.json').read_text())
|
||||
lines=['# 1—8月已发与9月发放核对','',
|
||||
'核对对象:2026年9月40,211.67元版本,145条发放项。原Excel保持不变。核对以用户确认的1—7月历史最终表、8月纯氢修正版实际已发45,065.47元为基准。',
|
||||
'','## 结论','',
|
||||
'**补发与金额算术检查通过;结转资格存在规则分支不一致,需要澄清后再将结果作为最终付款依据。**',
|
||||
'按8月表内结转余额逐月减1的现行逻辑,独立重算9月仍为40,211.67元,未发现金额公式错误或重复历史补发。此结论不等于认可结转余额的形成口径。',
|
||||
'','## 1. 结转两种算法不一致:9月有150元需要复核','',
|
||||
'有实际考核记录的车组按“累计多跑里程÷月目标取整−实际已用结转期数”计算余额;无本月考核记录的车组却按“上期余额−1”,同时在累计目标中增加一个月目标。这是代码中不同分支的实际行为。',
|
||||
'8月底有5辆车的表内结转余额比统一累计公式多1期。尚建华的粤AGP5158会影响9月当期150元,其余4辆在两种口径下9月都有至少1期可用结转,主要影响后续余额。',
|
||||
'','|车辆|负责人|8月底表内余额|累计公式余额|1—8月已领结转次数|9月结转|',
|
||||
'|---|---|---:|---:|---:|---:|']
|
||||
for x in a['prior_carry_basis_differences']:lines.append(f"|{x['plate']}|{x['sales']}|{x['aug_saved_balance']}|{x['aug_formula_balance']}|{x['carry_paid_jan_aug']}|{x['sep_payment']:.2f}|")
|
||||
lines += ['',
|
||||
'粤AGP5158截至8月累计实际41,174.00公里、累计目标22,451.61公里、月目标3,000公里,实际已领6次结转。统一累计公式得到 floor((41,174.00−22,451.61)/3,000)−6=0期;8月表却保存1期,9月据此发150元。',
|
||||
'**不能直接将150元定为多发并扣除。** 无考核月份是否应增加虚拟考核目标,决定了两种余额计算哪一种适用;若增加虚拟目标又扣除已领期数,可能重复消耗同一结转额度。应先统一规则,再调整9月及后续余额,1—8月实际已发保持不变。若明确要求保留目前累计目标、全部统一累计公式,则这笔150元不满足资格,9月在其他条件不变时为40,061.67元。这只是该口径下的测算,不是建议直接改付。',
|
||||
'','## 2. 同车新旧负责人同时领取,共涉及原负责人970元','',
|
||||
'本月无考核记录的结转共33笔、5,940元。其中以下5辆同时给新负责人发当月奖金、给旧负责人发历史结转。按目前“车牌+负责人”分账、历史结转继续发放的规则可以复现,并非同一负责人重复发放。若制度要求交接后原负责人停止领取,才会影响这970元。不能把它直接作为错付款扣除。',
|
||||
'','|车辆|原负责人|原负责人结转|新负责人发放|','|---|---|---:|---|']
|
||||
for x in a['cross_owner_payments']:
|
||||
new=';'.join(f"{y['sales']} {y['category']} {y['amount']:.2f}元" for y in x['current_payments'])
|
||||
lines.append(f"|{x['plate']}|{x['carry_owner']}|{x['carry_amount']:.2f}|{new}|")
|
||||
lines += ['',
|
||||
'','## 3. 9月底17辆结转余额不同,33条累计状态显示不准确','',
|
||||
'17个无本月考核车组的表内结转余额与统一累计公式合计相差20期。这是口径差异,不能直接换算成确定多发金额;不同车辆奖励基准、未来有无考核及补数均可能变化。',
|
||||
'','|车辆|负责人|9月底表内余额|统一累计公式余额|','|---|---|---:|---:|']
|
||||
for x in a['next_balance_differences']:lines.append(f"|{x['plate']}|{x['sales']}|{x['sep_saved_balance']}|{x['sep_formula_balance']}|")
|
||||
lines += ['',
|
||||
'33个无本月考核车组在程序中把累计达标标志固定为否,但显示的累计实际里程均已达到累计目标。例如粤AGP5158累计实际41,174.00大于目标25,451.61,计算过程仍显示“已有里程未达标”。这是展示语义问题,并不等于应额外补发。建议将“累计里程是否达标”与“本月是否有考核、是否具备补发资格”分开显示。',
|
||||
'','## 4. 已确认没有重复的7笔历史补发','',
|
||||
'按“车辆+原考核负责人+考核月”逐项检查1—8月已发;7笔合计819.67元,没有重复,非虚拟考核车组也未发现符合现行规则而遗漏的补发。',
|
||||
'','|车辆|负责人|补发类别|金额|9月累计实际|9月累计目标|','|---|---|---|---:|---:|---:|']
|
||||
for x in a['historical_backpay']:lines.append(f"|{x['plate']}|{x['sales']}|{x['category']}|{x['amount']:.2f}|{x['sep_cumulative_actual']:.2f}|{x['sep_cumulative_target']:.2f}|")
|
||||
lines += ['',
|
||||
'粤AGP3612尚建华到9月累计超目标122.04公里,8月底尚差1,023.06公里,因此9月才补5月33.55元、7月260元、8月260元。6月已经实际发放,未重复补发。',
|
||||
'粤AGR0772尚建华补8月19.35元,与谯云8月已发116.13元不是重复:分别为8月28—31日与8月1—24日两个负责人区间。',
|
||||
'','## 5. 台账、取数及报表一致性','',
|
||||
'- 1—6月已发台账逐条吻合历史最终表;6月人员转接后的个人合计也一致。',
|
||||
'- 7月原明细仍列9笔离职不发款2,749.35元,已发台账已正确剔除。最终汇总62,616.73元与逐项和62,616.76元相差0.03元,是历史原表舍入差。',
|
||||
'- 8月纯氢修正版实际已发45,065.47元及原扣减9.68元保持不变,扣减项不再次补发。',
|
||||
'- 9月145条发放项与Excel缓存值一致,金额公式未发现错误。结转、当月、累计补发当月之间未发现同负责人同车的重复占用。',
|
||||
'- 实际已领结转次数与1—8月台账逐车一致,问题发生在余额计算方式,不是漏记已领次数。',
|
||||
'- 9月刘念念全部归业务一部,部门汇总与明细一致;新政策名单沿用原39辆、当月匹配36辆。',
|
||||
'- 8,737个考核车辆日无重复计入,原始来源文件哈希未改变。此前中断月份清零触发误补发的问题在当前版本未再出现。',
|
||||
'','## 6. 仍有数据缺口','',
|
||||
'9月1,000个车辆日缺少所需来源,涉及176辆车;其中991个需仪表、9个需GPS。6条考核记录整段缺少可用里程。61条发放项(16,662.34元)带有本月或8月缺口标记,标记不代表这些金额均算错:部分已有里程已经足够达标,结转也可能来自历史。补齐数据后仍可能改变未达标、补发和结转余额。',
|
||||
'尚建华粤AGR5535虽然9月2天全缺,仍列累计补发9月10元:仅历史已知里程4,246.80公里已经大于包含9月目标后的3,521.43公里,按现行累计达标规则可以复现;0仅为当前已知累计,不是实测零里程。',
|
||||
'','## 处理建议','',
|
||||
'1. 优先统一无本月考核车辆的结转目标、余额消耗方式,避免一边增加虚拟目标、一边重复扣已用期数。先核实粤AGP5158的150元,再统一17辆后续余额。',
|
||||
'2. 明确交接后原负责人能否继续领取历史结转;若允许,表内将旧负责人历史结转与新负责人当月奖励清楚分开即可。',
|
||||
'3. 更正无本月考核车组的累计状态文字,保持累计结果与补发资格两个概念分开。',
|
||||
'4. 补齐1,000个车辆日缺口后重算。当前40,211.67元保留为现行口径暂算,不表示以上规则问题已解决。',
|
||||
'','## 核对范围及证据','',
|
||||
'全量核对145条9月发放、1—8月对应已发状态、369个当前车组的历史累计及结转次数;重复展示的个人页沿用已完成金额核验。没有重新从平台获取GPS,也未重新审核1—6月纯氢原始采集准确性。银行支付未另取流水,按用户确认的历史最终表作为已发。',
|
||||
'',f"当前9月文件SHA256:`{a['workbook_sha256']}`",'']
|
||||
paths=[R/f'里程任务考核_{m}月核算.xlsx' for m in range(1,7)]+[R/'7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx',S.parent/'hydrogen-history-20260916/里程任务考核_8月核算_纯氢修正版.xlsx',S/'里程任务考核_2026年9月_含缺口核算.xlsx']
|
||||
lines.extend(f'- [{p.name}]({p})' for p in paths)
|
||||
lines+=['','复核脚本:audit_september_against_paid.py、independent.py;逐项结果:audit_results.json、history_checks.json。']
|
||||
(P/'1-8月与9月发放核对报告.md').write_text('\n'.join(lines))
|
||||
nb={'nbformat':4,'nbformat_minor':5,'metadata':{'kernelspec':{'display_name':'Python 3','language':'python','name':'python3'}},'cells':[{'cell_type':'markdown','metadata':{},'source':['# 1—8月与9月发放只读核验\n','先阅读核对报告,代码只读取原表并写出核验结果,不修改奖金工作簿。']},{'cell_type':'code','metadata':{},'execution_count':None,'outputs':[],'source':(P/'audit_september_against_paid.py').read_text().replace("P=Path(__file__).resolve().parent",f"P=Path({str(P.resolve())!r})").splitlines(True)}]}
|
||||
(P/'历史与9月发放核验.ipynb').write_text(json.dumps(nb,ensure_ascii=False,indent=2))
|
||||
files=[{'label':p.name} for p in paths]
|
||||
def q(id,title,rows,columns,caveats,method):
|
||||
return {'id':id,'title':title,'queries':[{'id':id+'-evidence','source':{'label':'2026年里程绩效核算表','files':files,'filters':['按车辆与原考核负责人区分资格','历史已发锁定,不追扣'],'caveats':caveats},'reportingPeriod':'2026年1—8月已发,对比9月核算','columns':columns,'rows':rows,'methods':[{'language':'calculation','code':method}]}]}
|
||||
receipt={'schemaVersion':1,'items':[
|
||||
q('historical-backpay','7笔历史补发未发现重复',a['historical_backpay'],['plate','sales','category','amount'],['已发以用户确认的历史最终表为准,未另取银行流水。'],'33.55 + 260 + 260 + 72.58 + 19.35 + 150 + 24.19 = 819.67元;逐笔排除同车、同负责人、同考核月已经发放。'),
|
||||
q('carry-balance','无本月考核车辆的结转余额存在两种口径',a['prior_carry_basis_differences'],['plate','sales','aug_saved_balance','aug_formula_balance','carry_paid_jan_aug'],['150元是口径待确认金额,不能直接认定多发;虚拟考核目标是否应增加尚需明确。'],'粤AGP5158:floor((41174.00 − 22451.61) / 3000) − 6 = 0期;8月表内余额1期,9月据此发150元。9月底另有17组余额与统一公式不一致。'),
|
||||
q('owner-handover','5辆车原负责人领结转且新负责人领当月奖金',[{'plate':x['plate'],'owner':x['carry_owner'],'carry_amount':x['carry_amount'],'current':str(x['current_payments'])} for x in a['cross_owner_payments']],['plate','owner','carry_amount','current'],['按目前车牌加负责人分账可以复现,不等于同一负责人重复发放;是否交接后停止旧负责人结转属于业务规则。'],'150 + 150 + 150 + 260 + 260 = 970元原负责人结转,包含在33笔无本月考核结转5940元中。')
|
||||
]}
|
||||
(P/'sources_receipt.json').write_text(json.dumps(receipt,ensure_ascii=False,indent=2))
|
||||
assert hashlib.sha256((S/'里程任务考核_2026年9月_含缺口核算.xlsx').read_bytes()).hexdigest()==a['workbook_sha256']
|
||||
print('Read-only audit report and notebook saved; workbook unchanged.')
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5,
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
}
|
||||
},
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# 1—8月与9月发放只读核验\n",
|
||||
"先阅读核对报告,代码只读取原表并写出核验结果,不修改奖金工作簿。"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"metadata": {},
|
||||
"execution_count": null,
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\"\"\"Read-only audit. No business workbook or calculation state is modified.\"\"\"\n",
|
||||
"from pathlib import Path\n",
|
||||
"from collections import Counter,defaultdict\n",
|
||||
"from decimal import Decimal,ROUND_HALF_UP\n",
|
||||
"import json,hashlib,calendar\n",
|
||||
"import openpyxl\n",
|
||||
"P=Path('/Users/shishengliang/Code/lingniu/mileage-bonus/outputs/september-20261009/audit_history');S=P.parent;H=S.parent/'hydrogen-history-20260916'\n",
|
||||
"load=lambda p:json.loads(p.read_text())\n",
|
||||
"d=load(S/'september_calculation.json');aug=load(H/'august_calculation.json');paid=load(H/'historical_paid.json')\n",
|
||||
"money=lambda v:float(Decimal(str(v)).quantize(Decimal('.01'),rounding=ROUND_HALF_UP))\n",
|
||||
"key=lambda x:(x['plate'],x['sales'])\n",
|
||||
"groups={key(g):g for g in d['groups']};prior={key(g):g for g in aug['groups']}\n",
|
||||
"hist={(x['month'],x['plate'],x['sales']):x for x in d['historical_months']}\n",
|
||||
"settled={(r['assessment_month'],r['plate'],r['owner']) for r in paid}\n",
|
||||
"settled.update((l['assessment_month'],*key(l)) for l in aug['ledger'] if l['gross_amount']>0)\n",
|
||||
"carry_paid=Counter((r['plate'],r['owner']) for r in paid if r['category']=='结转')\n",
|
||||
"carry_paid.update(key(l) for l in aug['ledger'] if l['category']=='结转' and l['amount']>0)\n",
|
||||
"ledger={(l['plate'],l['sales'],l['category']):l for l in d['ledger']}\n",
|
||||
"assert len(ledger)==len(d['ledger'])\n",
|
||||
"expected={};details=[];carry_basis=[];balance_inconsistent=[];status_inconsistent=[];used_inconsistent=[];cross_owner=[]\n",
|
||||
"for k,g in groups.items():\n",
|
||||
" rows=[r for r in d['records'] if (r['车牌号'],r['销售经理'])==k]\n",
|
||||
" hist_t=sum(x['target'] for h,x in hist.items() if h[1:]==k)\n",
|
||||
" hist_a=sum(x['actual'] for h,x in hist.items() if h[1:]==k)\n",
|
||||
" full_t=money(hist_t+g['target']);full_a=money(hist_a+g['actual']);cq=full_a>=full_t and full_t>0\n",
|
||||
" assert abs(full_t-g['cum_target'])<.02 and abs(full_a-g['cum_actual'])<.02,k\n",
|
||||
" prev=prior.get(k,{})\n",
|
||||
" locked=prev.get('carry_left',0)\n",
|
||||
" if locked>=1:expected[(*k,'结转')]=money(g['reward'])\n",
|
||||
" else:\n",
|
||||
" current=sum(Decimal(str(r['月奖励']))*Decimal(str(r['考核天数']))/30 for r in rows if r['实际行驶里程(km)']>=r['应考核里程(km)'])\n",
|
||||
" if current:expected[(*k,'当月')]=money(current)\n",
|
||||
" if cq and not g['virtual']:\n",
|
||||
" for m in range(1,9):\n",
|
||||
" old=hist.get((m,*k))\n",
|
||||
" if old and (m,*k) not in settled:expected[(*k,f'补发{m}月')]=money(Decimal(str(old['reward']))*Decimal(str(old['days']))/calendar.monthrange(2026,m)[1])\n",
|
||||
" if not any(r['实际行驶里程(km)']>=r['应考核里程(km)'] for r in rows) and locked<1:\n",
|
||||
" expected[(*k,'累计补发9月')]=money(Decimal(str(g['reward']))*Decimal(str(g['days']))/30)\n",
|
||||
" used_actual=carry_paid[k]+int(locked>=1)\n",
|
||||
" if used_actual!=g['used_carry']:used_inconsistent.append(dict(plate=k[0],sales=k[1],ledger_used=used_actual,stored_used=g['used_carry']))\n",
|
||||
" recomputed_prior=max(0,int(max(0,hist_a-hist_t)//g['monthly_target'])-carry_paid[k])\n",
|
||||
" recomputed_balance=max(0,int(max(0,full_a-full_t)//g['monthly_target'])-used_actual)\n",
|
||||
" evidence=dict(plate=k[0],sales=k[1],aug_cumulative_target=money(hist_t),aug_cumulative_actual=money(hist_a),monthly_target=g['monthly_target'],carry_paid_jan_aug=carry_paid[k],aug_saved_balance=locked,aug_formula_balance=recomputed_prior,sep_saved_balance=g['carry_left'],sep_formula_balance=recomputed_balance,sep_payment=g['total'],virtual=g['virtual'])\n",
|
||||
" if recomputed_prior!=locked:carry_basis.append(evidence)\n",
|
||||
" if recomputed_balance!=g['carry_left']:balance_inconsistent.append(evidence)\n",
|
||||
" if cq!=g['cum_qualified']:status_inconsistent.append(dict(plate=k[0],sales=k[1],target=full_t,actual=full_a,stored=g['cum_qualified'],calculated=cq,virtual=g['virtual']))\n",
|
||||
" if g['virtual']:\n",
|
||||
" other=[l for l in d['ledger'] if l['plate']==k[0] and l['sales']!=k[1]]\n",
|
||||
" if other:cross_owner.append(dict(plate=k[0],carry_owner=k[1],carry_amount=ledger.get((*k,'结转'),{}).get('amount',0),current_payments=[{f:l[f] for f in ['sales','category','amount']} for l in other]))\n",
|
||||
" for l in d['ledger']:\n",
|
||||
" if key(l)==k and l['assessment_month']<9:\n",
|
||||
" assert (l['assessment_month'],*k) not in settled\n",
|
||||
" details.append({**{f:l[f] for f in ['plate','sales','category','amount']},'aug_cumulative_target':prev.get('cum_target'),'aug_cumulative_actual':prev.get('cum_actual'),'sep_cumulative_target':full_t,'sep_cumulative_actual':full_a,'historically_unpaid':True})\n",
|
||||
"missing=[dict(plate=k[0],sales=k[1],category=k[2],expected=v,actual=ledger.get(k,{}).get('amount',0)) for k,v in expected.items() if abs(v-ledger.get(k,{}).get('amount',0))>.001]\n",
|
||||
"extra=[dict(plate=k[0],sales=k[1],category=k[2],actual=l['amount'],expected=expected.get(k,0)) for k,l in ledger.items() if abs(l['amount']-expected.get(k,0))>.001]\n",
|
||||
"source_checks={}\n",
|
||||
"for tag,x in d['sources'].items():source_checks[tag]=hashlib.sha256(Path(x['path']).read_bytes()).hexdigest()==x['sha256']\n",
|
||||
"fp=S/'里程任务考核_2026年9月_含缺口核算.xlsx';wb=openpyxl.load_workbook(fp,data_only=True,read_only=True)\n",
|
||||
"xrows=list(wb['9月奖金发放记录'].values)\n",
|
||||
"excel_ledger={(r[0],r[1],r[4]):r[9] for r in xrows[1:] if r[0] and r[0]!='合计' and isinstance(r[9],(int,float))}\n",
|
||||
"assert excel_ledger=={k:l['amount'] for k,l in ledger.items()}\n",
|
||||
"errors=[(s.title,c.coordinate,c.value) for s in wb for row in s for c in row if c.data_type=='e'];wb.close()\n",
|
||||
"assert not errors\n",
|
||||
"by_cat={c:money(sum(l['amount'] for l in d['ledger'] if l['category']==c)) for c in d['categories']}\n",
|
||||
"virtual_pay=[l for l in d['ledger'] if l['virtual']]\n",
|
||||
"gap_pay=[l for l in d['ledger'] if l['gap_days']>0 or l['history_gap_days']>0]\n",
|
||||
"wholly=[r for r in d['records'] if r['缺口天数']==r['考核天数']]\n",
|
||||
"out=dict(amount=d['summary']['amount'],ledger_items=len(ledger),expected_amount_under_locked_aug_carry=money(sum(expected.values())),missing_payments=missing,extra_payments=extra,categories=by_cat,historical_backpay=details,prior_carry_basis_differences=carry_basis,next_balance_differences=balance_inconsistent,cumulative_status_inconsistencies=status_inconsistent,used_count_differences=used_inconsistent,cross_owner_payments=cross_owner,virtual_count=len(virtual_pay),virtual_amount=money(sum(l['amount'] for l in virtual_pay)),gap_payment_count=len(gap_pay),gap_payment_amount=money(sum(l['amount'] for l in gap_pay)),wholly_missing=[{f:r[f] for f in ['车牌号','销售经理','考核天数']} for r in wholly],source_hash_checks=source_checks,excel_ledger_matches=True,formula_errors=errors,workbook_sha256=hashlib.sha256(fp.read_bytes()).hexdigest())\n",
|
||||
"(P/'audit_results.json').write_text(json.dumps(out,ensure_ascii=False,indent=2));print(json.dumps({k:v for k,v in out.items() if k not in ['next_balance_differences','cumulative_status_inconsistencies','source_hash_checks','historical_backpay']},ensure_ascii=False,indent=2));print('NEXT_BALANCES',len(balance_inconsistent),'STATUS',len(status_inconsistent))\n"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# 1—8月已发及9月补发独立核验
|
||||
|
||||
本核验只读原Excel/JSON,没有修改核算结果。独立脚本 independent.py、完整逐项证据 history_checks.json 位于同目录。
|
||||
|
||||
## 确定结论
|
||||
|
||||
- 1—6月 historical_paid.json 每条实发金额、车牌、原负责人、发放类型及对应考核月均与根目录“里程任务考核_N月核算.xlsx”的“N月奖金发放记录”对应原行吻合。
|
||||
- 6月已离职当月奖金转接后的收款人逐人合计,与6月汇总98—105行完全吻合。
|
||||
- 7月发放记录页保留9条离职人员不发项,共2749.35元;已发JSON正确剔除。证据:“7月绩效重算_多源考核里程_20260817/离职人员7月奖金处理明细.xlsx”→离职人员发放处理总览,第2行处理规则、第5/7行不发2189.35+560。7月主表汇总C106为62616.73;逐项四舍五入相加62616.76,差0.03属于原表舍入差,非9月新增差错。7月个人合计舍入差最多0.01。
|
||||
- 9月7条历史补发共819.67元,没有同车、同负责人、同考核月已发后再次补发。按所有历史考核目标和实际里程独立复核非虚拟车组的累计达标及未付月,未发现漏补。
|
||||
- 9月发放记录130—136行分别:尚建华粤AGP3612补5月33.55、补7月260、补8月260;钟祥粤AGP5138补8月72.58;尚建华粤AGR0772补8月19.35;吴纬涛粤AGR5228补8月150、粤AGR8536补8月24.19。
|
||||
- 粤AGR0772的19.35元和8月给谯云116.13元不重复:尚建华8月28—31日4天,谯云8月1—24日24天(8月修正版业务考核源行92、166)。
|
||||
- 粤AGP3612到9月底累计20767.20/20645.16,超122.04公里;截至8月累计14622.10/15645.16,差1023.06公里。因9月新增6145.10公里而取得此前未付月资格。该车6月已发锁定,所以没有6月重复补发。
|
||||
|
||||
## 待业务口径确认,不应直接视为错误或擅自扣款
|
||||
|
||||
9月延用旧规则:原负责人本月无考核,但上月仍有结转资格,自动生成虚拟结转。共33项5940元。其中5辆同时给新负责人9月当月奖金,旧负责人结转合计970元:
|
||||
|
||||
|车辆|原负责人结转|新负责人当月|9月发放记录原负责人行/新负责人行|
|
||||
|---|---:|---:|---|
|
||||
|粤AGR5560|金可鹏150|钟祥5|129 / 36|
|
||||
|粤AGP3080|董剑煜150|尚建华5|117 / 41|
|
||||
|粤AGR6869|董剑煜150|尚建华150|121 / 47|
|
||||
|粤AGP3627|金可鹏260|秦挺260|123 / 70|
|
||||
|粤AGP3692|金可鹏260|秦挺260|124 / 71|
|
||||
|
||||
这五辆按现行车+负责人分账及无考核仍发结转规则计算一致;如果管理制度要求交车后原负责人停止结转,将影响970元,但现有确认“沿用8月”不足以直接改成停止结转。需要把这一具体业务情形展示给用户。
|
||||
@@ -0,0 +1,66 @@
|
||||
from pathlib import Path
|
||||
from datetime import date,datetime,timedelta
|
||||
from collections import Counter,defaultdict
|
||||
import json,re,hashlib,shutil,sys
|
||||
import openpyxl
|
||||
P=Path(__file__).resolve().parent
|
||||
FILES={
|
||||
'assessment':Path('/Users/shishengliang/Downloads/租赁任务考核_2026年9月.xlsx'),
|
||||
'meter':Path('/Users/shishengliang/Downloads/车辆里程查询_20260901-20260930_1024辆_仪表里程.xlsx'),
|
||||
'gps':Path('/Users/shishengliang/Downloads/车辆里程查询_20260901-20260930_1024辆_GPS里程.xlsx'),
|
||||
'hydrogen':Path('/Users/shishengliang/Downloads/车辆纯氢气里程查询_20260901-20260930_1024辆.xlsx'),
|
||||
'tests':Path('/Users/shishengliang/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/wxid_1704407055112_3af8/temp/RWTemp/2026-10/4340099467fbf08b4c6583c49adba4ee/测试车辆9月.xlsx'),
|
||||
'policy':Path('/Users/shishengliang/Documents/羚牛/里程考核核算/8月/输入数据/新合同+新提车.xls'),
|
||||
}
|
||||
def norm(v):return re.sub(r'\s+','',str(v or '')).upper()
|
||||
def dt(v):return v.date() if isinstance(v,datetime) else date.fromisoformat(str(v)[:10])
|
||||
def read(path,sheet=None):
|
||||
w=openpyxl.load_workbook(path,read_only=True,data_only=True);s=w[sheet] if sheet else w.worksheets[0];s.reset_dimensions();rs=list(s.values);w.close();return rs
|
||||
def dump(name,value):(P/name).write_text(json.dumps(value,ensure_ascii=False,indent=2,default=str))
|
||||
sources={};(P/'sources').mkdir(parents=True,exist_ok=True)
|
||||
for name,path in FILES.items():
|
||||
digest=hashlib.sha256(path.read_bytes()).hexdigest();target=P/'sources'/path.name
|
||||
if not target.exists():shutil.copy2(path,target)
|
||||
assert hashlib.sha256(target.read_bytes()).hexdigest()==digest
|
||||
sources[name]=dict(path=str(path),snapshot=str(target),sha256=digest)
|
||||
matrices={};provenance={};matrix_stats={};meta={}
|
||||
for name in ['meter','gps','hydrogen']:
|
||||
rs=read(FILES[name]);cols={i:dt(v) for i,v in enumerate(rs[5]) if isinstance(v,(date,datetime))}
|
||||
assert set(cols.values())=={date(2026,9,i) for i in range(1,31)}
|
||||
rows={};refs={};nonblank=0
|
||||
for rn,r in enumerate(rs[6:],7):
|
||||
if not r[0]:continue
|
||||
p=norm(r[0]);assert p not in rows,(name,p)
|
||||
rows[p]={};refs[p]={};meta.setdefault(p,dict(vin=r[1],brand=r[2],model=r[3]))
|
||||
for i,d in cols.items():
|
||||
v=r[i] if i<len(r) else None
|
||||
assert v is None or isinstance(v,(int,float)) and v>=0,(name,p,d,v)
|
||||
rows[p][str(d)]=v;refs[p][str(d)]=f'{FILES[name].name} / {openpyxl.utils.get_column_letter(i+1)}{rn}'
|
||||
nonblank+=v is not None
|
||||
subtotal=sum(v for v in rows[p].values() if v is not None)
|
||||
assert r[-1] is None or abs(subtotal-r[-1])<.01,(name,p,subtotal,r[-1])
|
||||
matrices[name]=rows;provenance[name]=refs;matrix_stats[name]=dict(vehicles=len(rows),nonblank_days=nonblank)
|
||||
rs=read(FILES['tests']);tests=[norm(r[0]) for r in rs[2:] if r and r[0]]
|
||||
assert len(tests)==len(set(tests))
|
||||
rs=read(FILES['assessment']);headers=rs[0];records=[];ownership=defaultdict(list);date_errors=[];targets=[]
|
||||
for rn,row in enumerate(rs[1:],2):
|
||||
r=dict(zip(headers,row))
|
||||
if not r.get('车牌号'):continue
|
||||
p=norm(r['车牌号']);r['车牌号']=p;r['源表行']=rn
|
||||
a,b=dt(r['考核开始日期']),dt(r['考核结束日期']);days=(b-a).days+1
|
||||
if a<date(2026,9,1) or b>date(2026,9,30) or days<=0 or days!=r['考核天数']:date_errors.append([rn,p,str(a),str(b),r['考核天数']])
|
||||
for i in range(max(0,days)):ownership[(p,str(a+timedelta(days=i)))].append([rn,r['销售经理'],r['合同编号']])
|
||||
records.append(r)
|
||||
overlaps=[dict(plate=p,date=d,rows=items) for (p,d),items in ownership.items() if len(items)>1]
|
||||
coverage={name:sum(matrices[name].get(p,{}).get(d) is not None for p,d in ownership) for name in matrices}
|
||||
sys.path.insert(0,'/tmp/mileage-september-read-deps')
|
||||
import xlrd
|
||||
w=xlrd.open_workbook(FILES['policy']);policy_rows={s.name:[s.row_values(i) for i in range(s.nrows)] for s in w.sheets()}
|
||||
plates=set()
|
||||
for rs in policy_rows.values():
|
||||
for row in rs:
|
||||
for value in row:
|
||||
plates.update(re.findall(r'[粤沪浙苏鲁皖赣鄂湘闽川渝京津豫冀陕晋桂琼贵云甘宁青新蒙黑吉辽藏][A-Z][A-Z0-9]{5,6}',norm(value)))
|
||||
summary=dict(records=len(records),vehicles=len({r['车牌号'] for r in records}),test_roster=len(tests),assessed_test_vehicles=len(set(tests)&{r['车牌号'] for r in records}),policy_roster=len(plates),assessed_policy_vehicles=len(plates&{r['车牌号'] for r in records}),assessed_vehicle_days=len(ownership),covered_days=coverage,matrix_stats=matrix_stats,overlap_days=len(overlaps),date_errors=date_errors,people=dict(Counter(r['销售经理'] for r in records)))
|
||||
dump('inputs.json',dict(sources=sources,records=records,headers=headers,matrices=matrices,provenance=provenance,tests=tests,policy_plates=sorted(plates),policy_rows=policy_rows,vehicle_metadata=meta,overlaps=overlaps,summary=summary))
|
||||
dump('source_audit.json',summary);print(json.dumps(summary,ensure_ascii=False,indent=2));print('Overlaps:',json.dumps(overlaps[:12],ensure_ascii=False));print('Policy headers:',json.dumps({k:v[:3] for k,v in policy_rows.items()},ensure_ascii=False))
|
||||
@@ -0,0 +1,64 @@
|
||||
// Presentation layout carried forward from the August revised workbook.
|
||||
// Calculated September records and settlement eligibility are not changed here.
|
||||
export function applyAugustLayout(x){
|
||||
const {w,d,meta,mainRules,monthly,oldSum,sumSheet:modern,ledger,calc,track,personal,detail,previews,put,head,table,widths,col,pay,L,money}=x;
|
||||
const n=d.ledger.length+1,gs=d.groups,rr=d.records.length+1;
|
||||
const grid=(s,r)=>s.getRange(r).format.borders={preset:'all',style:'thin',color:'#000000'};
|
||||
const bold=(s,r,n=3)=>s.getRange(`A${r}:${col(n)}${r}`).format.font={name:'宋体',size:11,bold:true,color:'#000000'};
|
||||
const current=new Map(d.records.map(r=>[r['车牌号']+'|'+r['销售经理'],r]));
|
||||
const rec=g=>current.get(g.plate+'|'+g.sales)||meta.last_records[g.plate+'|'+g.sales]||{};
|
||||
const deps=p=>[...new Set(gs.filter(g=>g.sales===p).map(g=>g.department))].join('、');
|
||||
const clean=s=>s.getUsedRange().clear({applyTo:'all'});
|
||||
const cats=['结转','当月','累计补发9月',...Array.from({length:8},(_,i)=>`补发${8-i}月`)];
|
||||
table(mainRules,['考核目标','月度目标里程(km)','当月达标','当月未达标','结转','补发/累计补发'],[
|
||||
['交投40辆4.5T普货',3000,150],['交投190辆4.5T冷链车',3000,150],['羚牛136辆4.5T冷链车',5000,260],['恒运50辆4.5T普货',5000,260],['羚牛100辆18T',6000,1000]
|
||||
].map(([a,b,c])=>[a,b,`${c}元×天数折算`,'不发',`${c}元整月`,`${c}元×折算`]),{A:38,B:18,C:22,D:20,E:20,F:24});
|
||||
head(mainRules,9,['名单对应考核目标','月目标不变','名单当月奖励','未达标','名单结转','9月补发基准']);
|
||||
[['交投40辆4.5T普货',3000,150],['交投190辆4.5T冷链车',3000,150],['羚牛136辆4.5T冷链车',5000,300],['恒运50辆4.5T普货',5000,300],['羚牛100辆18T',6000,150]].forEach(([a,b,c],i)=>put(mainRules,10+i,[a,b,`${c}元×天数折算`,'不发',`${c}元整月`,`${c}元×折算`]));grid(mainRules,'A9:F14');
|
||||
mainRules.mergeCells('A16:F18');put(mainRules,16,['沿用8月原始39辆名单,本月匹配36辆;名单外沿用原金额。折算、结转、补发规则不变,历史月补发仍使用历史基准。亏损暂不处理。']);mainRules.getRange('A16:F18').format.wrapText=true;
|
||||
mainRules.mergeCells('A20:F22');put(mainRules,20,['8月实际已发45,065.47元锁定。9月沿用8月逐日取数,交接日计前段。缺少所需来源的车辆日保留缺口,按已有里程累计;补数后重新核算。具体口径及完整名单见“规则与名单”。']);mainRules.getRange('A20:F22').format.wrapText=true;
|
||||
head(mainRules,24,['名单车牌','考核源表行','9月月奖励(元)','业务员','部门','考核目标']);
|
||||
const matched=d.records.filter(r=>r['新政策']);matched.forEach((r,i)=>put(mainRules,25+i,[r['车牌号'],r['源表行'],r['月奖励'],r['销售经理'],r['部门名称'],r['考核目标']]));grid(mainRules,`A24:F${24+matched.length}`);mainRules.getRange(`A25:F${24+matched.length}`).format.rowHeight=34;mainRules.getRange(`F25:F${24+matched.length}`).format.wrapText=true;
|
||||
table(monthly,['车牌号','部门名称','销售经理','客户名称','合同编号','考核目标','月度目标里程','月奖励金额','考核天数','9月应考核里程','9月实际行驶里程','完成率','9月是否达标','9月奖金(天数折算)','多跑里程','可结转月数','考核状态','源表行','取数方式','新政策','考核开始日期','考核结束日期','说明','缺口天数'],d.records.map(r=>[r['车牌号'],r['部门名称'],r['销售经理'],r['客户名称'],r['合同编号'],r['考核目标'],r['月度目标里程'],r['月奖励'],r['考核天数'],null,null,null,null,null,r['多跑'],r['可结转'],r['考核状态'],r['源表行'],r['取数方式'],r['新政策']?'是':'否',new Date(r['考核开始日期'].slice(0,10)+'T00:00:00Z'),new Date(r['考核结束日期'].slice(0,10)+'T00:00:00Z'),r['缺口天数']?'仅累计已有数据,缺失不代表实际零里程;补数后重算。':'数据齐全',r['缺口天数']]),{A:16,B:14,C:12,D:34,E:28,F:26,G:16,H:14,I:12,J:18,K:18,L:12,M:22,N:20,O:16,P:14,Q:14,R:10,S:25,T:12,U:16,V:16,W:65,X:14},['H','J','K','N','O']);
|
||||
monthly.getRange(`J2:N${rr}`).formulas=d.records.map((r,i)=>{const j=i+2;return[`='业务考核视图'!N${j}`,`='业务考核视图'!O${j}`,`=K${j}/J${j}`,`='业务考核视图'!Q${j}`,`=IF(K${j}>=J${j},ROUND(H${j}*I${j}/'规则与名单'!$B$3,2),0)`];});monthly.getRange(`L2:L${rr}`).setNumberFormat('0.00%');monthly.getRange(`U2:V${rr}`).setNumberFormat('yyyy-mm-dd');
|
||||
clean(ledger);
|
||||
table(ledger,['车牌号','业务员','部门','客户名称','发放类型','对应考核月','考核应发','客户盈亏','不发/扣减','实发金额','奖励基准','折算分子','折算分母','原发放编号','说明','本月缺口天数','8月缺口天数','本月无考核记录'],d.ledger.map(l=>[l.plate,l.sales,l.department,rec(l)['客户名称']||meta.vehicles[l.plate]?.[7]||'',l.category,l.assessment_month,null,'暂不处理',0,null,l.base,l.days,l.month_days,l.id,l.status,l.gap_days,l.history_gap_days,l.virtual?'是':'否']),{A:16,B:12,C:16,D:34,E:22,F:14,G:16,H:16,I:14,J:16,K:14,L:14,M:14,N:14,O:36,P:18,Q:18,R:22},['G','I','J','K']);
|
||||
ledger.getRange(`G2:G${n}`).formulas=d.ledger.map((_,i)=>[`=ROUND(K${i+2}*L${i+2}/M${i+2},2)`]);ledger.getRange(`J2:J${n}`).formulas=d.ledger.map((_,i)=>[`=G${i+2}-I${i+2}`]);ledger.getRange(`F2:F${n}`).setNumberFormat('0"月"');put(ledger,n+1,['合计']);for(const c of ['G','I','J'])ledger.getRange(`${c}${n+1}`).formulas=[[`=SUM(${c}2:${c}${n})`]];bold(ledger,n+1,18);ledger.getRange(`G${n+1}:K${n+1}`).setNumberFormat(money);
|
||||
ledger.mergeCells(`A${n+3}:O${n+4}`);put(ledger,n+3,['考核应发(G)−不发/扣减(I)=实发金额(J)。本表金额按现有里程核算,存在缺口,补齐后重算;亏损暂不处理。']);ledger.getRange(`A${n+3}:O${n+4}`).format={wrapText:true,font:{name:'宋体',size:10,color:'#C00000'}};
|
||||
clean(calc);
|
||||
table(calc,['车牌号','销售经理','部门','9月记录数','9月总天数','9月应考核','9月实际','9月有达标','9月奖金','累计应考核','累计实际','累计达标','可结转',...cats.map(c=>c==='当月'?'当月奖金':c==='结转'?'结转发放':c),'9月发放合计','无本月考核记录','9月缺口天数','8月缺口天数','上期可用结转'],gs.map(g=>{const rs=d.records.filter(r=>r['车牌号']===g.plate&&r['销售经理']===g.sales);return[g.plate,g.sales,g.department,rs.length,g.days,g.target,g.actual,g.qualified?'是':'否',rs.reduce((a,r)=>a+r['奖金'],0),g.cum_target,g.cum_actual,g.cum_qualified?'达标':'已有里程未达标',g.carry_left,...cats.map(()=>null),null,g.virtual?'是':'否',g.gap_days,g.history_gap_days,g.previous_carry];}),{A:16,B:12,C:16,D:14,E:14,F:18,G:18,H:14,I:16,J:18,K:18,L:22,M:12,Y:18,Z:22,AA:18,AB:18,AC:18},['F','G','I','J','K',...Array.from({length:12},(_,i)=>col(i+14))]);
|
||||
calc.getRange(`N2:X${gs.length+1}`).formulas=gs.map((g,i)=>cats.map(c=>`=${pay(`B${i+2}`,`A${i+2}`,c)}`));calc.getRange(`Y2:Y${gs.length+1}`).formulas=gs.map((g,i)=>[`=SUM(N${i+2}:X${i+2})`]);
|
||||
clean(track);
|
||||
table(track,['车牌号','车架号','归属公司','车型','考核目标','月度奖励','业务员',...Array.from({length:9},(_,i)=>[`${i+1}月应考核`,`${i+1}月实际`,`${i+1}月达标`]).flat(),'累计应完成','累计实际','累计达标','客户名称','客户是否亏损','考核应发','最终发放','未发放原因','本月无考核记录','9月缺口天数'],gs.map(g=>{const r=rec(g),v=meta.vehicles[g.plate]||[];return[g.plate,v[1]||'',v[2]||'',v[3]||'',r['考核目标']||v[4]||'',g.reward,g.sales,...g.history.flatMap(([t,a],i)=>[t,a,t===null?'':i===8&&g.gap_days>0&&a<t?'待补里程':a>=t?'✓':'✗']),g.cum_target,g.cum_actual,g.cum_qualified?'✓':'✗',r['客户名称']||v[7]||'','暂不处理',null,null,g.total?'':g.gap_days?'已有里程不足,补数后重算':g.virtual?'本月无新增发放':'未满足本月奖励、结转或补发条件',g.virtual?'是':'否',g.gap_days];}),{A:16,B:24,C:38,D:18,E:28,F:14,G:14,AL:36,AM:18,AN:16,AO:16,AP:40,AQ:22,AR:18},['F','AI','AJ','AN','AO']);
|
||||
track.getRange(`AN2:AO${gs.length+1}`).formulas=gs.map((g,i)=>[`=${pay(`G${i+2}`,`A${i+2}`)}`,`=${pay(`G${i+2}`,`A${i+2}`)}`]);for(let i=0;i<gs.length;i++)track.getRange(`A${i+2}:AR${i+2}`).format.fill=i%2?'#E8E8E8':'#F2F2F2';track.freezePanes.freezeColumns(7);
|
||||
clean(modern);
|
||||
widths(oldSum,{A:28,B:26,C:20},250);put(oldSum,1,['一、实发明细(按发放类别)']);bold(oldSum,1);let row=3;
|
||||
const section=(title,cat)=>{put(oldSum,row++,[title]);bold(oldSum,row-1);head(oldSum,row++,['销售人员','车辆数','金额']);const start=row;for(const p of d.people.filter(p=>d.ledger.some(l=>l.sales===p&&(!cat||l.category===cat)))){put(oldSum,row,[p,null,null]);oldSum.getRange(`B${row}`).formulas=[[`=COUNTIFS(${L('B')},A${row}${cat?`,${L('E')},"${cat}"`:''})`]];oldSum.getRange(`C${row}`).formulas=[[`=${pay('A'+row,null,cat)}`]];row++;}put(oldSum,row,['总计']);for(const c of ['B','C'])oldSum.getRange(`${c}${row}`).formulas=[[`=SUM(${c}${start}:${c}${row-1})`]];grid(oldSum,`A${start}:C${row}`);bold(oldSum,row);row+=2;};
|
||||
for(const c of d.categories.filter(c=>d.ledger.some(l=>l.category===c)))section('实发-'+c,c);
|
||||
put(oldSum,row,['考核应发合计']);oldSum.getRange(`C${row}`).formulas=[[`=SUM(${L('G')})`]];bold(oldSum,row);row+=2;
|
||||
put(oldSum,row++,['二、亏损处理']);bold(oldSum,row-1);put(oldSum,row++,['亏损暂不处理。']);row++;
|
||||
put(oldSum,row++,['三、最终发放']);bold(oldSum,row-1);row++;section('最终发放明细',null);
|
||||
put(oldSum,row++,['9月最终发放(按销售人员)']);bold(oldSum,row-1);head(oldSum,row++,['销售人员','部门名称','最终发放']);let start=row;
|
||||
for(const p of d.people){put(oldSum,row,[p,deps(p)]);oldSum.getRange(`C${row}`).formulas=[[`=${pay('A'+row)}`]];row++;}grid(oldSum,`A${start}:C${row-1}`);put(oldSum,row,['合计']);oldSum.getRange(`C${row}`).formulas=[[`=SUM(C${start}:C${row-1})`]];bold(oldSum,row);row+=2;
|
||||
const departments=[...new Set(gs.map(g=>g.department))].sort();
|
||||
put(oldSum,row++,['9月最终发放(按部门)']);bold(oldSum,row-1);head(oldSum,row++,['部门名称','最终发放']);start=row;
|
||||
for(const dep of departments){put(oldSum,row,[dep]);oldSum.getRange(`B${row}`).formulas=[[`=SUMIF(${L('C')},A${row},${L('J')})`]];row++;}grid(oldSum,`A${start}:B${row-1}`);put(oldSum,row,['合计']);oldSum.getRange(`B${row}`).formulas=[[`=SUM(B${start}:B${row-1})`]];bold(oldSum,row);row+=2;
|
||||
put(oldSum,row++,['总览']);bold(oldSum,row-1);for(const [label,c]of [['考核应发','G'],['不发/扣减','I'],['最终发放','J']]){put(oldSum,row,[label]);oldSum.getRange(`B${row}`).formulas=[[`=SUM(${L(c)})`]];row++;}bold(oldSum,row-1);const oldTotal=`B${row-1}`;oldSum.getRange(`C1:C${row}`).setNumberFormat(money);oldSum.getRange(`B${start}:B${row}`).setNumberFormat(money);oldSum.mergeCells(`A${row+1}:C${row+3}`);put(oldSum,row+1,['以上为按现有里程核算金额。1,000个车辆日仍有缺口,补齐后重新确认达标、结转及补发。']);oldSum.getRange(`A${row+1}:C${row+3}`).format.wrapText=true;
|
||||
modern.showGridLines=false;modern.getRange('A1:N36').format={font:{name:'PingFang SC',size:11,color:'#183D50'},columnWidth:16,rowHeight:24};widths(modern,{A:14,B:26},36);modern.mergeCells('A1:H1');put(modern,1,['2026年9月里程绩效核算']);modern.getRange('A1:H1').format.font={size:18,bold:true};modern.getRange('A1').format.rowHeight=36;
|
||||
modern.mergeCells('A2:H3');put(modern,2,['8月实际已发45,065.47元锁定。按现有里程核算,金额逐项取分后汇总;缺口补齐后重算。']);modern.getRange('A2:H3').format={font:{size:10,color:'#526671'},wrapText:true};modern.mergeCells('J1:N1');modern.getRange('J1').values=[['9月核算总额(元)']];modern.getRange('J1:N1').format.font={size:12,bold:true,color:'#167D8D'};modern.mergeCells('J2:N3');modern.getRange('J2').formulas=[['=N22']];modern.getRange('J2:N3').format.font={size:26,bold:true};modern.getRange('J2').setNumberFormat(money);
|
||||
put(modern,5,['业务员','部门',...d.categories.map(c=>c+'(元)'),'合计(元)']);modern.getRange('A5:N5').format={fill:'#183D50',font:{color:'#FFFFFF',bold:true},rowHeight:44,wrapText:true};
|
||||
d.people.forEach((p,i)=>{const r=i+6;put(modern,r,[p,deps(p)]);modern.getRange(`C${r}:M${r}`).formulas=[d.categories.map(c=>`=${pay('A'+r,null,c)}`)];modern.getRange(`N${r}`).formulas=[[`=SUM(C${r}:M${r})`]];modern.getRange(`A${r}:N${r}`).format.fill=i%2?'#FFFFFF':'#BFE5F2';});
|
||||
put(modern,22,['合计']);modern.getRange('C22:N22').formulas=[Array.from({length:12},(_,i)=>`=SUM(${col(i+3)}6:${col(i+3)}21)`)];modern.getRange('A22:N22').format={fill:'#EAF3F5',font:{bold:true},rowHeight:30};modern.getRange('C6:N22').setNumberFormat(money);
|
||||
modern.getRange('A26:B26').values=[['部门','发放金额(元)']];modern.getRange('A26:B26').format={fill:'#183D50',font:{bold:true,color:'#FFFFFF'},rowHeight:30};departments.forEach((dep,i)=>{modern.getRange(`A${27+i}`).values=[[dep]];modern.getRange(`B${27+i}`).formulas=[[`=SUMIF(${L('C')},A${27+i},${L('J')})`]];});modern.getRange(`B27:B${26+departments.length}`).setNumberFormat(money);modern.mergeCells('A35:N36');put(modern,35,[`本月327辆、341条考核记录;1,000个车辆日缺少所需来源,涉及176辆车。按已有记录累计并单列缺口,补数后重算累计、结转及补发。`]);modern.getRange('A35:N36').format.wrapText=true;modern.freezePanes.freezeRows(5);modern.freezePanes.freezeColumns(2);modern.tables.add(`A5:N${5+d.people.length}`,true,'SeptemberModernSummary');
|
||||
const personControls={};
|
||||
for(const p of d.people){const s=personal[p],own=gs.filter(g=>g.sales===p);widths(s,{A:22,B:28,C:14,D:22,E:22,F:22,G:22,H:22,I:22,J:22,K:22,L:22,M:25,N:22},own.length*10+20);s.mergeCells('A1:N1');put(s,1,[`${p} | ${deps(p)} | 9月绩效对账单`]);s.getRange('A1:N1').format.font={name:'宋体',bold:true,size:14};s.getRange('A1').format.rowHeight=22;s.mergeCells('A2:D2');put(s,2,[`本月考核及结转车辆:${own.length}辆`]);bold(s,2,4);s.mergeCells('E2:G2');s.getRange('E2').values=[['本月核算合计(元)']];s.getRange('H2').formulas=[[`=${pay('"'+p+'"')}`]];s.getRange('H2').setNumberFormat(money);bold(s,2,14);head(s,4,['车牌号','考核目标','月奖励',...Array.from({length:9},(_,i)=>`${i+1}月里程/目标`),'累计里程/目标','达标']);let r=5;
|
||||
for(const g of own){put(s,r,[g.plate,rec(g)['考核目标']||meta.vehicles[g.plate]?.[4]||'',g.reward,...g.history.map(([t,a])=>t===null?'-':`${a.toFixed(2)}/${t.toFixed(2)}`),`${g.cum_actual.toFixed(2)}/${g.cum_target.toFixed(2)}`,g.cum_qualified?'达标':g.gap_days?'已有里程未达标':'未达标']);grid(s,`A${r}:N${r}`);s.getRange(`A${r}`).format.font={bold:true};s.getRange(`B${r}:N${r}`).format.wrapText=true;s.getRange(`A${r}:N${r}`).format.rowHeight=34;r++;head(s,r,['','发放项','金额','说明','结转余额']);s.mergeCells(`E${r}:G${r}`);r++;
|
||||
const items=d.ledger.filter(l=>l.plate===g.plate&&l.sales===p);for(const l of items){put(s,r,['',l.category,null,l.virtual?'历史结转,本月无考核记录':l.category==='结转'?'整月结转,不与当月奖励重复':'奖励基准×考核天数/对应月天数']);s.getRange(`C${r}`).formulas=[[`='9月奖金发放记录'!J${l.id+1}`]];s.mergeCells(`D${r}:G${r}`);grid(s,`A${r}:G${r}`);r++;}
|
||||
if(!items.length){put(s,r,['','本月不发',0,g.gap_days?'已有里程不足,待补数据后再判定':'未满足本月奖励、结转或补发条件']);s.mergeCells(`D${r}:G${r}`);grid(s,`A${r}:G${r}`);r++;}
|
||||
put(s,r,['','本月合计',null,g.gap_days?`本月缺口${g.gap_days}天,补数后重算`:'已计入个人汇总',`上期${g.previous_carry}期,本期剩余${g.carry_left}期`]);s.getRange(`C${r}`).formulas=[[`=${pay('"'+p+'"','"'+g.plate+'"')}`]];s.mergeCells(`E${r}:G${r}`);grid(s,`A${r}:G${r}`);bold(s,r,3);r+=2;
|
||||
}put(s,r,['合计']);s.getRange(`C${r}`).formulas=[['=H2']];bold(s,r);s.getRange(`C5:C${r}`).setNumberFormat(money);personControls[p]={sheet:s.name,total:`C${r}`};
|
||||
}
|
||||
// Review all changed views in the same visual order as August.
|
||||
previews.length=0;previews.push([mainRules.name,'A1:F18'],[monthly.name,'A1:H12'],[calc.name,'A1:M12'],[track.name,'A1:G12'],[ledger.name,'A1:O12'],[oldSum.name,'A1:C19'],[oldSum.name,`A${row-7}:C${row+3}`],[modern.name,'A1:N36']);for(const s of Object.values(personal))previews.push([s.name,'A1:N13']);for(const name of ['业务考核视图','逐日取数依据','里程缺口','规则与名单','历史结算资格','8月已发台账'])previews.push([name,name==='业务考核视图'?'J1:Q12':name==='规则与名单'?'A1:B16':'A1:J12']);
|
||||
return {oldTotal,personControls};
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,91 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import {Workbook,SpreadsheetFile} from '@oai/artifact-tool';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import path from 'node:path';
|
||||
import {applyAugustLayout} from './august_layout.mjs';
|
||||
const dir=path.dirname(fileURLToPath(import.meta.url));
|
||||
const d=JSON.parse(await fs.readFile(path.join(dir,'september_calculation.json'),'utf8'));
|
||||
const w=Workbook.create(),previews=[];
|
||||
const meta=JSON.parse(await fs.readFile(path.join(dir,'../hydrogen-history-20260916/july_style_metadata.json'),'utf8'));
|
||||
const department=p=>[...new Set(d.groups.filter(g=>g.sales===p).map(g=>g.department))].join('、');
|
||||
const personalName=p=>(department(p).includes('、')?'跨部门':department(p).replace('业务',''))+'-'+p;
|
||||
const money='#,##0.00;[Red](#,##0.00);0.00';
|
||||
const col=n=>{let s='';for(;n;n=Math.floor((n-1)/26))s=String.fromCharCode(65+(n-1)%26)+s;return s;};
|
||||
const esc=s=>String(s).replaceAll('"','""');
|
||||
const put=(s,r,a)=>s.getRange(`A${r}:${col(a.length)}${r}`).values=[a.map(x=>x===undefined?null:x)];
|
||||
function sh(name,cols,rows){const s=w.worksheets.add(name);s.showGridLines=true;s.getRange(`A1:${col(cols)}${rows}`).format={font:{name:'宋体',size:11,color:'#000000'},columnWidth:14,rowHeight:19,verticalAlignment:'center'};return s;}
|
||||
function widths(s,vals,rows=20){for(const [c,v] of Object.entries(vals))s.getRange(`${c}1:${c}${rows}`).format.columnWidth=v;}
|
||||
function head(s,r,a){put(s,r,a);s.getRange(`A${r}:${col(a.length)}${r}`).format={fill:'#D9E1F2',font:{name:'宋体',size:11,bold:true,color:'#000000'},wrapText:true,horizontalAlignment:'center',rowHeight:34,borders:{preset:'all',style:'thin',color:'#000000'}};}
|
||||
function table(s,h,rs,ws={},nums=[]){s.getRange(`A1:${col(h.length)}${rs.length+1}`).format={font:{name:'宋体',size:11,color:'#000000'},rowHeight:19,verticalAlignment:'center',borders:{preset:'all',style:'thin',color:'#000000'}};head(s,1,h);if(rs.length)s.getRange(`A2:${col(h.length)}${rs.length+1}`).values=rs;for(const c of nums)s.getRange(`${c}2:${c}${rs.length+1}`).setNumberFormat(money);widths(s,ws,rs.length+1);s.freezePanes.freezeRows(1);}
|
||||
const mainRules=sh('考核奖励规则',6,70),monthly=sh('里程明细9月',24,d.records.length+1),calc=sh('9月计算过程',29,d.groups.length+1),track=sh('车辆考核追踪',44,d.groups.length+1),ledger=sh('9月奖金发放记录',18,d.ledger.length+6),oldSum=sh('9月汇总',3,250),sumSheet=sh('9月汇总(新版界面)',14,38);
|
||||
const personal={};for(const p of d.people)personal[p]=sh(personalName(p),14,d.groups.filter(x=>x.sales===p).length*10+20);
|
||||
const detail=sh('业务考核视图',31,d.records.length+1),daily=sh('逐日取数依据',16,d.daily.length+1),gap=sh('里程缺口',10,d.gaps.length+1),rules=sh('规则与名单',9,205),history=sh('历史结算资格',9,d.historical_months.length+1),aug=sh('8月已发台账',10,d.august_ledger.length+3);
|
||||
const dr=d.daily.length+1,lr=d.ledger.length+1,rr=d.records.length+1;
|
||||
const L=c=>`'9月奖金发放记录'!$${c}$2:$${c}$${lr}`;
|
||||
const pay=(p,plate=null,cat=null)=>`SUMIFS(${L('J')},${L('B')},${p}${plate?`,${L('A')},${plate}`:''}${cat?`,${L('E')},"${cat}"`:''})`;
|
||||
table(rules,['项目','确认口径'],[
|
||||
['测试差值阈值(km)',50],['本月自然日数',30],['锁定8月实际已发(元)',45065.47],
|
||||
['测试日判定','仅9月名单车辆:每日仪表−GPS严格大于50公里。'],
|
||||
['测试日里程','取当日GPS。'],['非测试日里程','有纯氢(含真实0)取纯氢,无纯氢取仪表。'],
|
||||
['候选无法判定','缺仪表或GPS时不强判;有纯氢取纯氢,无纯氢兜底GPS。'],
|
||||
['数据缺失','来源空白保留缺口,按已有里程累计;不自动将普通车辆改取GPS。'],
|
||||
['重叠交接日','交接日计前段,后段次日起算;调整天数、目标及奖金折算。'],
|
||||
['历史结算','1—8月已发及原8月扣减锁定,不追扣、不重复补发。'],
|
||||
['历史里程','同车同负责人累计包含全部历史考核月,中间无考核不清零;8月原有缺口仍保留。'],
|
||||
['新政策车辆','沿用新合同+新提车.xls原始39辆名单,本月考核匹配36辆。'],
|
||||
['人员归属','按9月考核记录负责人分组;不因车辆转交自动转移旧负责人历史。'],
|
||||
['离职及亏损','沿用原规则:伍仲文、岑彦、赵连飞、钱婧不发不转交;亏损暂不处理。'],
|
||||
['补数更新','取数、里程、金额及汇总含公式。新增数据后需重新运行完整核算,以重新生成发放资格。']
|
||||
],{A:25,B:91});
|
||||
head(rules,19,['9月新政策考核目标','月奖励(元)']);
|
||||
[['交投40辆4.5T普货',150],['交投190辆4.5T冷链车',150],['羚牛136辆4.5T冷链车',300],['恒运50辆4.5T普货',300],['羚牛100辆18T',150]].forEach((r,i)=>put(rules,20+i,r));
|
||||
head(rules,27,['新政策车牌','9月是否有考核']);d.policy_plates.forEach((p,i)=>put(rules,28+i,[p,d.records.some(r=>r['车牌号']===p)?'是':'否']));
|
||||
rules.getRange('D1:E1').values=[['9月测试候选车牌','9月是否有考核']];rules.getRange('D1:E1').format={fill:'#D9E1F2',font:{bold:true},rowHeight:38};
|
||||
rules.getRange(`D2:E${d.tests.length+1}`).values=d.tests.map(p=>[p,d.records.some(r=>r['车牌号']===p)?'是':'否']);widths(rules,{D:23,E:23,G:25,H:82},205);
|
||||
rules.getRange('G1:H1').values=[['数据来源','文件']];let sr=2;for(const [key,v]of Object.entries(d.sources))rules.getRange(`G${sr}:H${sr++}`).values=[[key,path.basename(v.path)]];
|
||||
rules.getRange('G10:H10').values=[['8月基线','里程任务考核_8月核算_纯氢修正版.xlsx,按用户确认锁定45,065.47元。']];
|
||||
previews.push(['规则与名单','A1:B16']);
|
||||
table(daily,['考核源表行','车牌号','业务负责人','日期','仪表(km)','GPS(km)','纯氢(km)','测试候选','仪表−GPS(km)','测试日判定','采用来源','计入里程(km)','缺口标识','仪表来源单元格','GPS来源单元格','纯氢来源单元格'],d.daily.map(x=>[x.source_row,x.plate,x.sales,new Date(x.date+'T00:00:00Z'),x.meter,x.gps,x.hydrogen,x.test_candidate?'是':'否',null,null,null,null,null,x.provenance.meter||'未覆盖',x.provenance.gps||'未覆盖',x.provenance.hydrogen||'未覆盖']),{A:13,B:17,C:14,D:15,E:16,F:16,G:16,H:12,I:20,J:16,K:27,L:20,M:13,N:90,O:90,P:90},['E','F','G','I','L']);
|
||||
daily.getRange(`D2:D${dr}`).setNumberFormat('yyyy-mm-dd');
|
||||
daily.getRange(`I2:M${dr}`).formulas=d.daily.map((_,i)=>{const r=i+2;return[
|
||||
`=IF(AND(ISNUMBER(E${r}),ISNUMBER(F${r})),E${r}-F${r},"")`,
|
||||
`=IF(H${r}="否","不适用",IF(AND(ISNUMBER(E${r}),ISNUMBER(F${r})),IF(I${r}>'规则与名单'!$B$2,"是","否"),"未判定"))`,
|
||||
`=IF(J${r}="是",IF(ISNUMBER(F${r}),"测试日GPS","缺失"),IF(ISNUMBER(G${r}),"非测试日纯氢",IF(AND(H${r}="是",J${r}="未判定"),IF(ISNUMBER(F${r}),"测试名单无纯氢GPS","缺失"),IF(ISNUMBER(E${r}),"非测试日仪表","缺失"))))`,
|
||||
`=IF(K${r}="缺失","",IF(K${r}="非测试日纯氢",G${r},IF(K${r}="非测试日仪表",E${r},F${r})))`,
|
||||
`=IF(K${r}="缺失",1,0)`];});
|
||||
daily.freezePanes.freezeColumns(3);previews.push(['逐日取数依据','A1:M12']);
|
||||
const recordHeaders=[...d.headers,'月度目标里程','奖励基准','缺口天数','新政策','测试候选','已有里程口径','核算源行','原考核天数','原应考核里程','取数说明'];
|
||||
table(detail,recordHeaders,d.records.map(r=>[...d.headers.map(h=>['实际行驶里程(km)','完成率(%)','是否达标','应考核里程(km)'].includes(h)?null:r[h]),r['月度目标里程'],r['月奖励'],null,r['新政策']?'是':'否',r['测试候选']?'是':'否',null,r['源表行'],r['原考核天数'],r['原应考核里程(km)'],r['取数方式']]),{A:18,D:16,E:14,F:35,G:32,H:36,J:30,K:22,L:22,N:20,O:22,P:16,Q:25,V:20,W:18,X:15,Y:12,Z:14,AA:48,AB:14,AC:16,AD:22,AE:65},['N','O','W','AD']);
|
||||
detail.getRange(`N2:Q${rr}`).formulas=d.records.map((r,i)=>{let j=i+2;return[`=ROUND(V${j}*M${j}/'规则与名单'!$B$3,2)`,`=ROUND(SUMIFS('逐日取数依据'!$L$2:$L$${dr},'逐日取数依据'!$A$2:$A$${dr},AB${j}),2)`,`=ROUND(O${j}/N${j}*100,2)`,`=IF(X${j}=M${j},"待补里程",IF(O${j}>=N${j},"达标",IF(X${j}>0,"已有里程未达标","未达标")))`];});
|
||||
detail.getRange(`X2:X${rr}`).formulas=d.records.map((_,i)=>[`=SUMIFS('逐日取数依据'!$M$2:$M$${dr},'逐日取数依据'!$A$2:$A$${dr},AB${i+2})`]);
|
||||
detail.getRange(`AA2:AA${rr}`).formulas=d.records.map((_,i)=>[`=IF(X${i+2}>0,"仅累计已有数据,缺失不代表实际零里程","数据齐全")`]);
|
||||
detail.getRange(`Q2:Q${rr}`).conditionalFormats.add('containsText',{text:'待补',format:{fill:'#FFF2CC',font:{color:'#9C5700'}}});
|
||||
detail.freezePanes.freezeColumns(1);previews.push(['业务考核视图','J1:Q12']);
|
||||
table(gap,['车牌号','业务负责人','日期','考核源表行','需补来源','仪表(km)','GPS(km)','纯氢(km)','测试候选','缺口原因'],d.gaps.map(x=>[x.plate,x.sales,new Date(x.date+'T00:00:00Z'),x.source_row,x.needed,x.meter,x.gps,x.hydrogen,x.test_candidate?'是':'否',x.reason]),{A:18,B:16,C:17,D:17,E:15,F:18,G:18,H:18,I:16,J:68},['F','G','H']);gap.getRange(`C2:C${d.gaps.length+1}`).setNumberFormat('yyyy-mm-dd');previews.push(['里程缺口','A1:J12']);
|
||||
table(history,['考核月','车牌号','原业务负责人','考核天数','当时奖励基准','截至8月已结算','考核目标(km)','已修正里程(km)','结算含义'],d.historical_months.map(x=>[x.month,x.plate,x.sales,x.days,x.reward,x.settled?'是':'否',x.target,x.actual,x.settled?'已发或已核定扣减,不重复补发':'可按后续累计达标判定补发']),{A:13,B:18,C:18,D:16,E:22,F:22,G:22,H:22,I:48},['E','G','H']);previews.push(['历史结算资格','A1:I12']);
|
||||
table(aug,['车牌号','业务负责人','发放类别','考核月','原应发(元)','原扣减(元)','实际已发(元)','奖励基准','折算分子','折算分母'],d.august_ledger.map(l=>[l.plate,l.sales,l.category,l.assessment_month,l.gross_amount,l.withheld,l.amount,l.base,l.days,l.month_days]),{A:18,B:16,C:22,D:13,E:20,F:20,G:22,H:18,I:16,J:16},['E','F','G','H']);put(aug,d.august_ledger.length+2,['合计']);aug.getRange(`G${d.august_ledger.length+2}`).formulas=[[`=SUM(G2:G${d.august_ledger.length+1})`]];previews.push(['8月已发台账','A1:J12']);
|
||||
const layout=applyAugustLayout({w,d,meta,mainRules,monthly,oldSum,sumSheet,ledger,calc,track,personal,detail,previews,put,head,table,widths,col,pay,L,money});
|
||||
const correction=JSON.parse(await fs.readFile(path.join(dir,'cumulative_fix_audit.json'),'utf8'));
|
||||
const correctionSheet=sh('9月修正对照',7,correction.changes.length+12);
|
||||
put(correctionSheet,1,['中断月份累计承接修正']);
|
||||
put(correctionSheet,3,['原核算金额',correction.previous_total,'修正后金额',correction.corrected_total]);
|
||||
correctionSheet.mergeCells('A5:G6');put(correctionSheet,5,['原程序遇到上月无考核记录时重置累计,历史補发却仍查更早月份。现按同车同负责人全部考核月累计判定,历史已发不变。']);correctionSheet.getRange('A5:G6').format.wrapText=true;
|
||||
head(correctionSheet,8,['车牌号','业务负责人','发放类别','原金额','修正金额','变化金额','原因']);
|
||||
correction.changes.forEach((r,i)=>{put(correctionSheet,i+9,[r.plate,r.sales,r.category,r.old,r.new,null,r.new===0?'纳入中断前历史后累计未达标,取消误补发':'纳入中断前历史后累计达标,补发本月']);correctionSheet.getRange(`F${i+9}`).formulas=[[`=E${i+9}-D${i+9}`]];});
|
||||
widths(correctionSheet,{A:18,B:16,C:22,D:18,E:18,F:18,G:64},correction.changes.length+12);correctionSheet.getRange(`D9:F${correction.changes.length+8}`).setNumberFormat(money);correctionSheet.getRange('B3:D3').setNumberFormat(money);previews.push(['9月修正对照','A1:G18']);
|
||||
w.recalculate();
|
||||
if(Math.abs(sumSheet.getRange('J2').values[0][0]-d.summary.amount)>1e-7)throw Error('Summary amount mismatch');
|
||||
if(Math.abs(aug.getRange(`G${d.august_ledger.length+2}`).values[0][0]-45065.47)>1e-7)throw Error('Historical actuals changed');
|
||||
const computedDaily=daily.getRange(`K2:M${dr}`).values;
|
||||
d.daily.forEach((x,i)=>{let [mode,v,missing]=computedDaily[i];if(mode!==x.selection||missing!==(x.included===null?1:0)||x.included!==null&&Math.abs(v-x.included)>1e-8||x.included===null&&v!==''&&v!==null)throw Error('Daily selection mismatch '+i);});
|
||||
const recVals=detail.getRange(`N2:Q${rr}`).values;
|
||||
d.records.forEach((r,i)=>{if(Math.abs(recVals[i][0]-r['应考核里程(km)'])>1e-6||Math.abs(recVals[i][1]-r['实际行驶里程(km)'])>1e-6)throw Error('Record mileage mismatch '+i);});
|
||||
if(Math.abs(oldSum.getRange(layout.oldTotal).values[0][0]-d.summary.amount)>1e-7)throw Error('Vertical summary mismatch');
|
||||
for(const [p,info] of Object.entries(layout.personControls)){const expected=d.ledger.filter(l=>l.sales===p).reduce((a,l)=>a+l.amount,0);if(Math.abs(w.worksheets.getItem(info.sheet).getRange(info.total).values[0][0]-expected)>1e-7)throw Error('Personal mismatch '+p);}
|
||||
await fs.writeFile(path.join(dir,'layout_controls.json'),JSON.stringify(layout,null,2));
|
||||
const errors=await w.inspect({kind:'match',searchTerm:'#REF!|#DIV/0!|#VALUE!|#NAME\\?|#N/A|#NUM!|#NULL!|#SPILL!|#CALC!',options:{useRegex:true,maxResults:20},maxChars:3000});await fs.writeFile(path.join(dir,'formula_scan.ndjson'),errors.ndjson);
|
||||
const snap=await w.inspect({kind:'table',range:'9月汇总(新版界面)!J1:N3',include:'values,formulas',maxChars:1600});await fs.writeFile(path.join(dir,'summary_inspect.ndjson'),snap.ndjson);
|
||||
for(const [i,[name,range]]of previews.entries()){const img=await w.render({sheetName:name,range,scale:1});await fs.writeFile(path.join(dir,`preview_${i}.png`),new Uint8Array(await img.arrayBuffer()));}
|
||||
await fs.writeFile(path.join(dir,'preview_index.json'),JSON.stringify(previews));
|
||||
const out=await SpreadsheetFile.exportXlsx(w);await out.save(path.join(dir,'里程任务考核_2026年9月_含缺口核算.xlsx'));
|
||||
console.log(JSON.stringify({amount:d.summary.amount,sheets:w.worksheets.items.length,daily:d.daily.length,records:d.records.length}));
|
||||
@@ -0,0 +1,110 @@
|
||||
"""September mileage selection and awards, with January-August settlements locked."""
|
||||
from pathlib import Path
|
||||
from collections import defaultdict,Counter
|
||||
from decimal import Decimal,ROUND_HALF_UP
|
||||
import json,sys,copy,re,hashlib
|
||||
import openpyxl
|
||||
P=Path(__file__).resolve().parent;REPO=P.parents[1];H=P.parent/'hydrogen-history-20260916'
|
||||
sys.path.insert(0,str(REPO));import calc_engine as e
|
||||
def money(v):return float(Decimal(str(v)).quantize(Decimal('.01'),rounding=ROUND_HALF_UP))
|
||||
def load(p):return json.loads(p.read_text())
|
||||
def dump(name,x):(P/name).write_text(json.dumps(x,ensure_ascii=False,indent=2,default=str))
|
||||
prepared=load(P/'prepared_records.json');aug=load(H/'august_calculation.json')
|
||||
confirmed=dict(test_rule='沿用8月:名单内每日仪表减GPS严格大于50公里为测试日,取GPS;非测试日优先纯氢(含0),无纯氢取仪表;名单内无法判定且无纯氢时兜底GPS',test_threshold_km=50,august_actual_paid=45065.47,overlaps='交接日计前段,后段次日起算',new_policy='沿用上月原始39辆名单,按9月车型档位匹配',missing='沿用8月按已有里程累计并单列缺口;不将缺失解释为实测0',history='1—6月修正后的里程继续承接;1—8月已发及扣减锁定,不重复补发、不追扣')
|
||||
assert aug['stats']['amount']==45065.47
|
||||
ap=H/'里程任务考核_8月核算_纯氢修正版.xlsx'
|
||||
wb=openpyxl.load_workbook(ap,read_only=True,data_only=True)
|
||||
assert wb['8月汇总(新版界面)']['M22'].value==45065.47
|
||||
rr=list(wb['8月奖金发放记录'].values);wb.close()
|
||||
aug_rows={(r[0],r[1],r[4]):r for r in rr[1:] if r[0] and r[0]!='合计' and isinstance(r[9],(int,float))}
|
||||
for l in aug['ledger']:
|
||||
r=aug_rows[l['plate'],l['sales'],l['category']];assert r[9]==l['amount'] and r[8]==l['withheld']
|
||||
daily=prepared['daily'];basis=Counter();gaps=[];byrow=defaultdict(list)
|
||||
for x in daily:
|
||||
t,g,h=x['meter'],x['gps'],x['hydrogen'];flag=False
|
||||
if x['test_candidate']:flag=(Decimal(str(t))-Decimal(str(g))>50) if t is not None and g is not None else None
|
||||
if flag is True:mode,v='测试日GPS',g
|
||||
elif h is not None:mode,v='非测试日纯氢',h
|
||||
elif x['test_candidate'] and flag is None:mode,v='测试名单无纯氢GPS',g
|
||||
elif t is not None:mode,v='非测试日仪表',t
|
||||
else:mode,v='缺失',None
|
||||
needed='GPS' if x['test_candidate'] and h is None and flag is None else '仪表'
|
||||
x.update(test_flag=flag,comparison=money(t-g) if t is not None and g is not None else None,selection=mode if v is not None else '缺失',included=v,needed=needed if v is None else '',reason=('测试候选无纯氢且无法判定,需GPS' if needed=='GPS' else '无纯氢且无仪表,普通车辆不自动以GPS替代') if v is None else '')
|
||||
if v is None:gaps.append(x)
|
||||
basis[x['selection']]+=1;byrow[x['source_row']].append(x)
|
||||
records=prepared['records']
|
||||
for r in records:
|
||||
if r['销售经理']=='刘念念':r['部门名称']='业务一部'
|
||||
ds=byrow[r['源表行']];km=money(sum(Decimal(str(x['included'])) for x in ds if x['included'] is not None));gap=sum(x['included'] is None for x in ds)
|
||||
target=r['应考核里程(km)'];reward=r['月奖励'];q=km>=target and target>0
|
||||
r.update(月份=9,实际行驶里程_km=km,是否达标='达标' if q else '未达标',奖励基准_达标=reward,奖励基准_未达标=0,奖励基准_结转=reward,奖励基准_补发=reward,奖金=reward*r['考核天数']/30 if q else 0,多跑=max(0,km-target),可结转=int(max(0,km-target)//r['月度目标里程']),缺口天数=gap,状态='待补里程' if gap else '数据齐全',取数方式=';'.join(f'{k}{v}天' for k,v in Counter(x['selection'] for x in ds).items()))
|
||||
r['实际行驶里程(km)']=km;r['完成率(%)']=money(km/target*100)
|
||||
# Rebuild original history only for unchanged monthly target/reward/day metadata;
|
||||
# then apply the saved corrected mileage and actual-settlement state.
|
||||
D={m:e.read_file(REPO/f'租赁任务考核_2026年{m}月.xlsx' if m<7 else REPO/'7月绩效重算_多源考核里程_20260817/租赁任务考核_2026年7月.xlsx',m) for m in range(1,8)}
|
||||
G={m:e.grp(D[m],m) for m in D};ad={};e.calc_jan_carryover(G[1])
|
||||
for m in range(2,8):e.calc_month(m,G,ad,G[m-1],G[m])
|
||||
for x in load(H/'comparison.json'):G[x['month']][(x['plate'],x['sales'])]['实际']=x['corrected']
|
||||
states=load(H/'historical_states.json')
|
||||
for s in states:
|
||||
g=G[s['month']][(s['plate'],s['sales'])]
|
||||
g.update(cum_t=s['cum_target'],cum_a=s['cum_actual'],cum_q=s['cum_actual']>=s['cum_target'] and s['cum_target']>0,累计已发结转=s['used'],可结转=s['available'])
|
||||
settled={(r['assessment_month'],r['plate'],r['owner']) for r in load(H/'historical_paid.json')}
|
||||
settled.update((l['assessment_month'],l['plate'],l['sales']) for l in aug['ledger'] if l['gross_amount']>0)
|
||||
aug_carry={(l['plate'],l['sales']) for l in aug['ledger'] if l['category']=='结转' and l['amount']>0}
|
||||
G[8]=e.grp(aug['records'],8)
|
||||
for x in aug['groups']:
|
||||
k=x['plate'],x['sales'];old=G[7].get(k,{})
|
||||
if k not in G[8]:
|
||||
assert x['virtual'];G[8][k]=dict(recs=[],应考核=x['target'],实际=x['actual'],奖金=0,天数=x['days'],有达标=False,目标km=old['目标km'],奖励达标=x['reward'],奖励未达标=0,奖励结转=x['reward'],奖励补发=x['reward'],部门=x['department'],销售=x['sales'],车牌=x['plate'],虚拟=True)
|
||||
g=G[8][k]
|
||||
g.update(cum_t=x['cum_target'],cum_a=x['cum_actual'],cum_q=x['cum_qualified'],可结转=x['carry_left'],累计已发结转=old.get('累计已发结转',0)+int(k in aug_carry))
|
||||
for m in range(1,9):
|
||||
for k,g in G[m].items():
|
||||
g[f'{m}月已发']=(m,*k) in settled
|
||||
for bm in range(1,m):g.pop(f'补发{bm}月',None)
|
||||
before=copy.deepcopy(G)
|
||||
G[9]=e.grp(records,9);data={};e.calc_month(9,G,data,G[8],G[9],cumulative_history=True)
|
||||
policy=set(prepared['policy_plates']);price=aug['rules']['new_policy']['award_by_target']
|
||||
for k,g in G[9].items():
|
||||
if k[1]=='刘念念':g['部门']='业务一部'
|
||||
if g.get('虚拟') and k[0] in policy:
|
||||
last=next((r for m in range(8,0,-1) for r in (aug['records'] if m==8 else D[m]) if (r['车牌号'],r['销售经理'])==k),None)
|
||||
assert last is not None;reward=price[last['考核目标']]
|
||||
g.update(奖励达标=reward,奖励结转=reward,奖励补发=reward,结转=reward)
|
||||
for item in data[9]['结转']:
|
||||
if (item['车牌'],item['销售'])==k:item['额']=reward
|
||||
departed=set(aug['departed']['people']);ledger=[];excluded=[]
|
||||
cats=['当月','结转']+[f'补发{m}月' for m in range(1,9)]+['累计补发9月']
|
||||
for cat in cats:
|
||||
for item in data[9].get(cat,[]):
|
||||
k=(item['车牌'],item['销售']);g=G[9][k];m=int(re.search(r'补发(\d+)月',cat).group(1)) if '补发' in cat else 9
|
||||
if k[1] in departed:excluded.append(dict(plate=k[0],sales=k[1],category=cat,amount=money(item['额'])));continue
|
||||
if cat=='结转':base,days,den=g['奖励结转'],1,1
|
||||
elif cat=='当月':
|
||||
rs=[r for r in g['recs'] if r['是否达标']=='达标'];assert len({r['奖励基准_达标'] for r in rs})==1
|
||||
base,days,den=rs[0]['奖励基准_达标'],sum(r['考核天数'] for r in rs),30
|
||||
else:base,days,den=G[m][k]['奖励补发'],G[m][k]['天数'],e.DAYS[m]
|
||||
assert m==9 or (m,*k) not in settled
|
||||
exact=base*days/den;assert abs(exact-item['额'])<1e-7
|
||||
gap=sum(r['缺口天数'] for r in g['recs']);history_gap=sum(r.get('缺口天数',0) for r in aug['records'] if (r['车牌号'],r['销售经理'])==k)
|
||||
ledger.append(dict(id=len(ledger)+1,plate=k[0],sales=k[1],department=g['部门'],category=cat,assessment_month=m,base=base,days=days,month_days=den,amount=money(exact),gross_amount=money(exact),withheld=0,unrounded=exact,virtual=bool(g.get('虚拟')),gap_days=gap,history_gap_days=history_gap,status='按现有里程计算' if gap or history_gap else '数据齐全'))
|
||||
groups=[]
|
||||
for k,g in sorted(G[9].items(),key=lambda kv:(kv[0][1],kv[0][0])):
|
||||
if k[1] in departed:continue
|
||||
prev=G[8].get(k,{});pays=[l for l in ledger if (l['plate'],l['sales'])==k]
|
||||
groups.append(dict(plate=k[0],sales=k[1],department=g['部门'],days=g['天数'],target=money(g['应考核']),actual=money(g['实际']),monthly_target=g['目标km'],reward=g['奖励达标'],qualified=g['有达标'],cum_target=money(g['cum_t']),cum_actual=money(g['cum_a']),cum_qualified=g['cum_q'],previous_carry=prev.get('可结转',0),carry_left=g['可结转'],used_carry=g['累计已发结转'],virtual=bool(g.get('虚拟')),total=money(sum(l['amount'] for l in pays)),gap_days=sum(r['缺口天数'] for r in g['recs']),history_gap_days=sum(r.get('缺口天数',0) for r in aug['records'] if (r['车牌号'],r['销售经理'])==k),history=[[money(G[m][k]['应考核']),money(G[m][k]['实际'])] if k in G[m] else [None,None] for m in range(1,10)]))
|
||||
people=sorted({g['sales'] for g in groups});total=money(sum(l['amount'] for l in ledger))
|
||||
summary=dict(amount=total,unrounded_total=money(sum(l['unrounded'] for l in ledger)),records=len(records),vehicles=len({r['车牌号'] for r in records}),groups=len(groups),ledger_items=len(ledger),people=len(people),daily=len(daily),gap_days=len(gaps),gap_records=sum(r['缺口天数']>0 for r in records),gap_vehicles=len({x['plate'] for x in gaps}),wholly_missing_records=sum(r['缺口天数']==r['考核天数'] for r in records),basis_days=dict(basis),test_days=sum(x['test_flag'] is True for x in daily),unknown_test_days=sum(x['test_candidate'] and x['test_flag'] is None for x in daily),new_policy_vehicles=len(policy&{r['车牌号'] for r in records}),categories={c:money(sum(l['amount'] for l in ledger if l['category']==c)) for c in cats},excluded_departed_amount=money(sum(x['amount'] for x in excluded)))
|
||||
assert len({(l['plate'],l['sales'],l['category']) for l in ledger})==len(ledger)
|
||||
assert abs(sum(g['total'] for g in groups)-total)<1e-7
|
||||
for l in ledger:
|
||||
if l['category']=='结转':assert not any(y['plate']==l['plate'] and y['sales']==l['sales'] and y['category'] in ('当月','累计补发9月') for y in ledger)
|
||||
for s in prepared['sources'].values():assert hashlib.sha256(Path(s['snapshot']).read_bytes()).hexdigest()==s['sha256']
|
||||
paid_schedule=[dict(month=m,plate=k[0],sales=k[1],days=g['天数'],reward=g['奖励补发'],settled=(m,*k) in settled,target=money(g['应考核']),actual=money(g['实际'])) for m,gs in before.items() for k,g in gs.items()]
|
||||
display_categories=['当月','结转','累计补发9月']+[f'补发{m}月' for m in range(8,0,-1)]
|
||||
confirmed['september_department_overrides']={'刘念念':'业务一部'}
|
||||
result={**prepared,**dict(confirmed_rules=confirmed,daily=daily,records=records,gaps=gaps,groups=groups,ledger=ledger,people=people,categories=display_categories,summary=summary,historical_months=paid_schedule,august_ledger=aug['ledger'],excluded=excluded)}
|
||||
dump('september_calculation.json',result);dump('confirmed_rules.json',confirmed);dump('calculation_summary.json',summary)
|
||||
print(json.dumps(summary,ensure_ascii=False,indent=2))
|
||||
print('Missing groups:',json.dumps([dict(plate=r['车牌号'],sales=r['销售经理'],days=r['缺口天数'],known=r['实际行驶里程(km)']) for r in records if r['缺口天数']],ensure_ascii=False))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"amount": 40211.67,
|
||||
"unrounded_total": 40211.68,
|
||||
"records": 341,
|
||||
"vehicles": 327,
|
||||
"groups": 369,
|
||||
"ledger_items": 145,
|
||||
"people": 14,
|
||||
"daily": 8737,
|
||||
"gap_days": 1000,
|
||||
"gap_records": 178,
|
||||
"gap_vehicles": 176,
|
||||
"wholly_missing_records": 6,
|
||||
"basis_days": {
|
||||
"非测试日纯氢": 6267,
|
||||
"测试日GPS": 250,
|
||||
"测试名单无纯氢GPS": 148,
|
||||
"非测试日仪表": 1072,
|
||||
"缺失": 1000
|
||||
},
|
||||
"test_days": 250,
|
||||
"unknown_test_days": 159,
|
||||
"new_policy_vehicles": 36,
|
||||
"categories": {
|
||||
"当月": 23692.01,
|
||||
"结转": 13350.0,
|
||||
"补发1月": 0.0,
|
||||
"补发2月": 0.0,
|
||||
"补发3月": 0.0,
|
||||
"补发4月": 0.0,
|
||||
"补发5月": 33.55,
|
||||
"补发6月": 0.0,
|
||||
"补发7月": 260.0,
|
||||
"补发8月": 526.12,
|
||||
"累计补发9月": 2349.99
|
||||
},
|
||||
"excluded_departed_amount": 1710.0
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"test_rule": "沿用8月:名单内每日仪表减GPS严格大于50公里为测试日,取GPS;非测试日优先纯氢(含0),无纯氢取仪表;名单内无法判定且无纯氢时兜底GPS",
|
||||
"test_threshold_km": 50,
|
||||
"august_actual_paid": 45065.47,
|
||||
"overlaps": "交接日计前段,后段次日起算",
|
||||
"new_policy": "沿用上月原始39辆名单,按9月车型档位匹配",
|
||||
"missing": "沿用8月按已有里程累计并单列缺口;不将缺失解释为实测0",
|
||||
"history": "1—6月修正后的里程继续承接;1—8月已发及扣减锁定,不重复补发、不追扣",
|
||||
"september_department_overrides": {
|
||||
"刘念念": "业务一部"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"previous_total": 44361.69,
|
||||
"corrected_total": 40211.67,
|
||||
"changes": [
|
||||
{
|
||||
"plate": "粤A08190F",
|
||||
"sales": "秦挺",
|
||||
"category": "补发7月",
|
||||
"old": 1000.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3039",
|
||||
"sales": "钟祥",
|
||||
"category": "补发1月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3039",
|
||||
"sales": "钟祥",
|
||||
"category": "补发2月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3039",
|
||||
"sales": "钟祥",
|
||||
"category": "补发3月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3039",
|
||||
"sales": "钟祥",
|
||||
"category": "补发4月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3039",
|
||||
"sales": "钟祥",
|
||||
"category": "补发5月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3039",
|
||||
"sales": "钟祥",
|
||||
"category": "补发6月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3629",
|
||||
"sales": "钟祥",
|
||||
"category": "补发1月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3629",
|
||||
"sales": "钟祥",
|
||||
"category": "补发2月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3629",
|
||||
"sales": "钟祥",
|
||||
"category": "补发3月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3629",
|
||||
"sales": "钟祥",
|
||||
"category": "补发4月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3629",
|
||||
"sales": "钟祥",
|
||||
"category": "补发5月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP3629",
|
||||
"sales": "钟祥",
|
||||
"category": "补发6月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP7068",
|
||||
"sales": "钟祥",
|
||||
"category": "补发1月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP7068",
|
||||
"sales": "钟祥",
|
||||
"category": "补发2月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP7068",
|
||||
"sales": "钟祥",
|
||||
"category": "补发3月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP7068",
|
||||
"sales": "钟祥",
|
||||
"category": "补发4月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP7068",
|
||||
"sales": "钟祥",
|
||||
"category": "补发5月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGP7068",
|
||||
"sales": "钟祥",
|
||||
"category": "补发6月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGQ3155",
|
||||
"sales": "钟祥",
|
||||
"category": "补发1月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGQ3155",
|
||||
"sales": "钟祥",
|
||||
"category": "补发2月",
|
||||
"old": 53.57,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5535",
|
||||
"sales": "尚建华",
|
||||
"category": "累计补发9月",
|
||||
"old": 0,
|
||||
"new": 10.0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5553",
|
||||
"sales": "钟祥",
|
||||
"category": "补发5月",
|
||||
"old": 106.45,
|
||||
"new": 0
|
||||
},
|
||||
{
|
||||
"plate": "粤AGR5553",
|
||||
"sales": "钟祥",
|
||||
"category": "补发6月",
|
||||
"old": 150.0,
|
||||
"new": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"kind":"notice","message":"Cell search matched 0 entries."}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"oldTotal": "B109",
|
||||
"personControls": {
|
||||
"何文斌": {
|
||||
"sheet": "一部-何文斌",
|
||||
"total": "C20"
|
||||
},
|
||||
"刘念念": {
|
||||
"sheet": "一部-刘念念",
|
||||
"total": "C230"
|
||||
},
|
||||
"吴纬涛": {
|
||||
"sheet": "三部-吴纬涛",
|
||||
"total": "C297"
|
||||
},
|
||||
"尚建华": {
|
||||
"sheet": "二部-尚建华",
|
||||
"total": "C179"
|
||||
},
|
||||
"秦挺": {
|
||||
"sheet": "五部-秦挺",
|
||||
"total": "C200"
|
||||
},
|
||||
"童军林": {
|
||||
"sheet": "综合部-童军林",
|
||||
"total": "C25"
|
||||
},
|
||||
"董剑煜": {
|
||||
"sheet": "二部-董剑煜",
|
||||
"total": "C45"
|
||||
},
|
||||
"谯云": {
|
||||
"sheet": "二部-谯云",
|
||||
"total": "C85"
|
||||
},
|
||||
"赵伟军": {
|
||||
"sheet": "综合部-赵伟军",
|
||||
"total": "C70"
|
||||
},
|
||||
"赵小峰": {
|
||||
"sheet": "综合部-赵小峰",
|
||||
"total": "C10"
|
||||
},
|
||||
"赵波": {
|
||||
"sheet": "综合部-赵波",
|
||||
"total": "C55"
|
||||
},
|
||||
"金可鹏": {
|
||||
"sheet": "三部-金可鹏",
|
||||
"total": "C25"
|
||||
},
|
||||
"钟祥": {
|
||||
"sheet": "一部-钟祥",
|
||||
"total": "C466"
|
||||
},
|
||||
"陈高伟": {
|
||||
"sheet": "一部-陈高伟",
|
||||
"total": "C215"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"status": "failed_on_commit_recheck",
|
||||
"amount": 40211.67,
|
||||
"source_and_audit_tables_preserved": 6,
|
||||
"corrected_payout_items_verified": 145,
|
||||
"personal_totals_verified": 14,
|
||||
"august_style_samples_match": true,
|
||||
"formula_errors": [],
|
||||
"previous_status": "passed",
|
||||
"recheck_date": "2026-10-09",
|
||||
"failure": {
|
||||
"sheet": "业务考核视图",
|
||||
"cell": "P7",
|
||||
"baseline": 57.79,
|
||||
"current": 57.8,
|
||||
"note": "首次失败位置;未更改工作簿。原有其他验证字段为此前验证记录。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
p=Path(__file__).parent/'build_september.mjs'
|
||||
s=p.read_text()
|
||||
if "applyAugustLayout" in s: raise SystemExit('Already patched')
|
||||
shutil.copy2(p,p.with_suffix('.before-layout.txt'))
|
||||
shutil.copy2(p.parent/'里程任务考核_2026年9月_含缺口核算.xlsx',p.parent/'before-layout.xlsx.bak')
|
||||
s=s.replace("import path from 'node:path';", "import path from 'node:path';\nimport {applyAugustLayout} from './august_layout.mjs';")
|
||||
s=s.replace("const w=Workbook.create(),previews=[];", "const w=Workbook.create(),previews=[];\nconst meta=JSON.parse(await fs.readFile(path.join(dir,'../hydrogen-history-20260916/july_style_metadata.json'),'utf8'));\nconst department=p=>[...new Set(d.groups.filter(g=>g.sales===p).map(g=>g.department))].join('、');\nconst personalName=p=>(department(p).includes('、')?'跨部门':department(p).replace('业务',''))+'-'+p;")
|
||||
s=s.replace("s.showGridLines=false;", "s.showGridLines=true;")
|
||||
s=s.replace("color:'#172D3D'},columnWidth:16,rowHeight:22", "color:'#000000'},columnWidth:14,rowHeight:19")
|
||||
s=s.replace("font:{bold:true},wrapText:true,horizontalAlignment:'center',rowHeight:38", "font:{name:'宋体',size:11,bold:true,color:'#000000'},wrapText:true,horizontalAlignment:'center',rowHeight:34,borders:{preset:'all',style:'thin',color:'#000000'}")
|
||||
s=s.replace("function table(s,h,rs,ws={},nums=[]){head(s,1,h);", "function table(s,h,rs,ws={},nums=[]){s.getRange(`A1:${col(h.length)}${rs.length+1}`).format={font:{name:'宋体',size:11,color:'#000000'},rowHeight:19,verticalAlignment:'center',borders:{preset:'all',style:'thin',color:'#000000'}};head(s,1,h);")
|
||||
start=s.index('const sumSheet=sh(');end=s.index('const dr=d.daily.length',start)
|
||||
s=s[:start]+"""const mainRules=sh('考核奖励规则',6,70),monthly=sh('里程明细9月',24,d.records.length+1),calc=sh('9月计算过程',29,d.groups.length+1),track=sh('车辆考核追踪',44,d.groups.length+1),ledger=sh('9月奖金发放记录',18,d.ledger.length+6),oldSum=sh('9月汇总',3,250),sumSheet=sh('9月汇总(新版界面)',14,38);
|
||||
const personal={};for(const p of d.people)personal[p]=sh(personalName(p),14,d.groups.filter(x=>x.sales===p).length*10+20);
|
||||
const detail=sh('业务考核视图',31,d.records.length+1),daily=sh('逐日取数依据',16,d.daily.length+1),gap=sh('里程缺口',10,d.gaps.length+1),rules=sh('规则与名单',9,205),history=sh('历史结算资格',9,d.historical_months.length+1),aug=sh('8月已发台账',10,d.august_ledger.length+3);
|
||||
"""+s[end:]
|
||||
s=s.replace("${L('D')},\"${cat}\"", "${L('E')},\"${cat}\"")
|
||||
# Remove superseded reader layouts; the source calculation tables remain unchanged.
|
||||
start=s.index('table(ledger,');end=s.index('table(gap,',start);s=s[:start]+s[end:]
|
||||
start=s.index('widths(sumSheet,');end=s.index('w.recalculate();',start)
|
||||
s=s[:start]+"const layout=applyAugustLayout({w,d,meta,mainRules,monthly,oldSum,sumSheet,ledger,calc,track,personal,detail,previews,put,head,table,widths,col,pay,L,money});\n"+s[end:]
|
||||
s=s.replace("sumSheet.getRange('C4')", "sumSheet.getRange('J2')")
|
||||
s=s.replace("range:'9月汇总!A2:D6'", "range:'9月汇总(新版界面)!J1:N3'")
|
||||
s=s.replace("const errors=await w.inspect", "if(Math.abs(oldSum.getRange(layout.oldTotal).values[0][0]-d.summary.amount)>1e-7)throw Error('Vertical summary mismatch');\nfor(const [p,info] of Object.entries(layout.personControls)){const expected=d.ledger.filter(l=>l.sales===p).reduce((a,l)=>a+l.amount,0);if(Math.abs(w.worksheets.getItem(info.sheet).getRange(info.total).values[0][0]-expected)>1e-7)throw Error('Personal mismatch '+p);}\nawait fs.writeFile(path.join(dir,'layout_controls.json'),JSON.stringify(layout,null,2));\nconst errors=await w.inspect")
|
||||
p.write_text(s)
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Assemble September assessment intervals and daily sources, before selecting mileage."""
|
||||
from pathlib import Path
|
||||
from datetime import date,timedelta
|
||||
from collections import Counter
|
||||
import json
|
||||
P=Path(__file__).resolve().parent
|
||||
d=json.loads((P/'inputs.json').read_text())
|
||||
records=d['records'];byrow={r['源表行']:r for r in records};changes=[]
|
||||
for x in d['overlaps']:
|
||||
later=max((byrow[z[0]] for z in x['rows']),key=lambda r:r['考核开始日期'])
|
||||
old=later['考核开始日期'][:10];new=str(date.fromisoformat(x['date'])+timedelta(days=1))
|
||||
changes.append(dict(plate=later['车牌号'],sales=later['销售经理'],source_row=later['源表行'],old_start=old,new_start=new,reason='用户确认交接日计前段,后段次日起算'))
|
||||
later['考核开始日期']=new
|
||||
from sys import path
|
||||
path.insert(0,str(P.parents[1]))
|
||||
from calc_engine import RULES
|
||||
prices={'交投40辆4.5T普货':150,'交投190辆4.5T冷链车':150,'羚牛136辆4.5T冷链车':300,'恒运50辆4.5T普货':300,'恒运50辆4.5T 普货':300,'羚牛100辆18T':150}
|
||||
seen=set();daily=[]
|
||||
for r in records:
|
||||
p=r['车牌号'];a=date.fromisoformat(r['考核开始日期'][:10]);b=date.fromisoformat(r['考核结束日期'][:10]);days=(b-a).days+1
|
||||
assert 0<days<=30
|
||||
rule=RULES[r['考核目标']]
|
||||
r['原考核天数']=r['考核天数'];r['原应考核里程(km)']=r['应考核里程(km)']
|
||||
r['考核天数']=days;r['应考核里程(km)']=round(rule['km']*days/30,2)
|
||||
r['月度目标里程']=rule['km'];r['月奖励']=prices[r['考核目标']] if p in d['policy_plates'] else rule['good']
|
||||
r['新政策']=p in d['policy_plates'];r['测试候选']=p in d['tests']
|
||||
r['实际行驶里程(km)']=None;r['完成率(%)']=None;r['是否达标']=None
|
||||
for i in range(days):
|
||||
day=str(a+timedelta(days=i));assert (p,day) not in seen;seen.add((p,day))
|
||||
vals={k:x.get(p,{}).get(day) for k,x in d['matrices'].items()}
|
||||
daily.append(dict(source_row=r['源表行'],plate=p,sales=r['销售经理'],date=day,test_candidate=r['测试候选'],**vals,provenance={k:x.get(p,{}).get(day) for k,x in d['provenance'].items()}))
|
||||
out=dict(records=records,daily=daily,overlap_adjustments=changes,sources=d['sources'],tests=d['tests'],policy_plates=d['policy_plates'],metadata=d['vehicle_metadata'],headers=d['headers'])
|
||||
(P/'prepared_records.json').write_text(json.dumps(out,ensure_ascii=False,indent=2))
|
||||
print(json.dumps(dict(records=len(records),unique_vehicle_days=len(seen),overlap_adjustments=changes,policy_records=sum(r['新政策'] for r in records),targets=dict(Counter(r['考核目标'] for r in records))),ensure_ascii=False,indent=2))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
[["考核奖励规则","A1:F18"],["里程明细9月","A1:H12"],["9月计算过程","A1:M12"],["车辆考核追踪","A1:G12"],["9月奖金发放记录","A1:O12"],["9月汇总","A1:C19"],["9月汇总","A103:C113"],["9月汇总(新版界面)","A1:N36"],["一部-何文斌","A1:N13"],["一部-刘念念","A1:N13"],["三部-吴纬涛","A1:N13"],["二部-尚建华","A1:N13"],["五部-秦挺","A1:N13"],["综合部-童军林","A1:N13"],["二部-董剑煜","A1:N13"],["二部-谯云","A1:N13"],["综合部-赵伟军","A1:N13"],["综合部-赵小峰","A1:N13"],["综合部-赵波","A1:N13"],["三部-金可鹏","A1:N13"],["一部-钟祥","A1:N13"],["一部-陈高伟","A1:N13"],["业务考核视图","J1:Q12"],["逐日取数依据","A1:J12"],["里程缺口","A1:J12"],["规则与名单","A1:B16"],["历史结算资格","A1:J12"],["8月已发台账","A1:J12"],["9月修正对照","A1:G18"]]
|
||||
@@ -0,0 +1,103 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import {Workbook,SpreadsheetFile} from '@oai/artifact-tool';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import path from 'node:path';
|
||||
const dir=path.dirname(fileURLToPath(import.meta.url));
|
||||
const d=JSON.parse(await fs.readFile(path.join(dir,'september_calculation.json'),'utf8'));
|
||||
const w=Workbook.create(),previews=[];
|
||||
const money='#,##0.00;[Red](#,##0.00);0.00';
|
||||
const col=n=>{let s='';for(;n;n=Math.floor((n-1)/26))s=String.fromCharCode(65+(n-1)%26)+s;return s;};
|
||||
const esc=s=>String(s).replaceAll('"','""');
|
||||
const put=(s,r,a)=>s.getRange(`A${r}:${col(a.length)}${r}`).values=[a.map(x=>x===undefined?null:x)];
|
||||
function sh(name,cols,rows){const s=w.worksheets.add(name);s.showGridLines=false;s.getRange(`A1:${col(cols)}${rows}`).format={font:{name:'宋体',size:11,color:'#172D3D'},columnWidth:16,rowHeight:22,verticalAlignment:'center'};return s;}
|
||||
function widths(s,vals,rows=20){for(const [c,v] of Object.entries(vals))s.getRange(`${c}1:${c}${rows}`).format.columnWidth=v;}
|
||||
function head(s,r,a){put(s,r,a);s.getRange(`A${r}:${col(a.length)}${r}`).format={fill:'#D9E1F2',font:{bold:true},wrapText:true,horizontalAlignment:'center',rowHeight:38};}
|
||||
function table(s,h,rs,ws={},nums=[]){head(s,1,h);if(rs.length)s.getRange(`A2:${col(h.length)}${rs.length+1}`).values=rs;for(const c of nums)s.getRange(`${c}2:${c}${rs.length+1}`).setNumberFormat(money);widths(s,ws,rs.length+1);s.freezePanes.freezeRows(1);}
|
||||
const sumSheet=sh('9月汇总',14,38),ledger=sh('9月奖金发放记录',14,d.ledger.length+4),calc=sh('9月计算过程',22,d.groups.length+1),track=sh('车辆考核追踪',26,d.groups.length+1),detail=sh('业务考核视图',31,d.records.length+1),daily=sh('逐日取数依据',16,d.daily.length+1),gap=sh('里程缺口',10,d.gaps.length+1),rules=sh('规则与名单',9,205),history=sh('历史结算资格',9,d.historical_months.length+1),aug=sh('8月已发台账',10,d.august_ledger.length+3);
|
||||
const personal={};for(const p of d.people)personal[p]=sh(p+'-9月对账',14,d.groups.filter(x=>x.sales===p).length*8+20);
|
||||
const dr=d.daily.length+1,lr=d.ledger.length+1,rr=d.records.length+1;
|
||||
const L=c=>`'9月奖金发放记录'!$${c}$2:$${c}$${lr}`;
|
||||
const pay=(p,plate=null,cat=null)=>`SUMIFS(${L('J')},${L('B')},${p}${plate?`,${L('A')},${plate}`:''}${cat?`,${L('D')},"${cat}"`:''})`;
|
||||
table(rules,['项目','确认口径'],[
|
||||
['测试差值阈值(km)',50],['本月自然日数',30],['锁定8月实际已发(元)',45065.47],
|
||||
['测试日判定','仅9月名单车辆:每日仪表−GPS严格大于50公里。'],
|
||||
['测试日里程','取当日GPS。'],['非测试日里程','有纯氢(含真实0)取纯氢,无纯氢取仪表。'],
|
||||
['候选无法判定','缺仪表或GPS时不强判;有纯氢取纯氢,无纯氢兜底GPS。'],
|
||||
['数据缺失','来源空白保留缺口,按已有里程累计;不自动将普通车辆改取GPS。'],
|
||||
['重叠交接日','交接日计前段,后段次日起算;调整天数、目标及奖金折算。'],
|
||||
['历史结算','1—8月已发及原8月扣减锁定,不追扣、不重复补发。'],
|
||||
['历史里程','承接1—6月纯氢修正和原7—8月里程;8月原有缺口仍保留。'],
|
||||
['新政策车辆','沿用新合同+新提车.xls原始39辆名单,本月考核匹配36辆。'],
|
||||
['人员归属','按9月考核记录负责人分组;不因车辆转交自动转移旧负责人历史。'],
|
||||
['离职及亏损','沿用原规则:伍仲文、岑彦、赵连飞、钱婧不发不转交;亏损暂不处理。'],
|
||||
['补数更新','取数、里程、金额及汇总含公式。新增数据后需重新运行完整核算,以重新生成发放资格。']
|
||||
],{A:25,B:91});
|
||||
head(rules,19,['9月新政策考核目标','月奖励(元)']);
|
||||
[['交投40辆4.5T普货',150],['交投190辆4.5T冷链车',150],['羚牛136辆4.5T冷链车',300],['恒运50辆4.5T普货',300],['羚牛100辆18T',150]].forEach((r,i)=>put(rules,20+i,r));
|
||||
head(rules,27,['新政策车牌','9月是否有考核']);d.policy_plates.forEach((p,i)=>put(rules,28+i,[p,d.records.some(r=>r['车牌号']===p)?'是':'否']));
|
||||
rules.getRange('D1:E1').values=[['9月测试候选车牌','9月是否有考核']];rules.getRange('D1:E1').format={fill:'#D9E1F2',font:{bold:true},rowHeight:38};
|
||||
rules.getRange(`D2:E${d.tests.length+1}`).values=d.tests.map(p=>[p,d.records.some(r=>r['车牌号']===p)?'是':'否']);widths(rules,{D:23,E:23,G:25,H:82},205);
|
||||
rules.getRange('G1:H1').values=[['数据来源','文件']];let sr=2;for(const [key,v]of Object.entries(d.sources))rules.getRange(`G${sr}:H${sr++}`).values=[[key,path.basename(v.path)]];
|
||||
rules.getRange('G10:H10').values=[['8月基线','里程任务考核_8月核算_纯氢修正版.xlsx,按用户确认锁定45,065.47元。']];
|
||||
previews.push(['规则与名单','A1:B16']);
|
||||
table(daily,['考核源表行','车牌号','业务负责人','日期','仪表(km)','GPS(km)','纯氢(km)','测试候选','仪表−GPS(km)','测试日判定','采用来源','计入里程(km)','缺口标识','仪表来源单元格','GPS来源单元格','纯氢来源单元格'],d.daily.map(x=>[x.source_row,x.plate,x.sales,new Date(x.date+'T00:00:00Z'),x.meter,x.gps,x.hydrogen,x.test_candidate?'是':'否',null,null,null,null,null,x.provenance.meter||'未覆盖',x.provenance.gps||'未覆盖',x.provenance.hydrogen||'未覆盖']),{A:13,B:17,C:14,D:15,E:16,F:16,G:16,H:12,I:20,J:16,K:27,L:20,M:13,N:90,O:90,P:90},['E','F','G','I','L']);
|
||||
daily.getRange(`D2:D${dr}`).setNumberFormat('yyyy-mm-dd');
|
||||
daily.getRange(`I2:M${dr}`).formulas=d.daily.map((_,i)=>{const r=i+2;return[
|
||||
`=IF(AND(ISNUMBER(E${r}),ISNUMBER(F${r})),E${r}-F${r},"")`,
|
||||
`=IF(H${r}="否","不适用",IF(AND(ISNUMBER(E${r}),ISNUMBER(F${r})),IF(I${r}>'规则与名单'!$B$2,"是","否"),"未判定"))`,
|
||||
`=IF(J${r}="是",IF(ISNUMBER(F${r}),"测试日GPS","缺失"),IF(ISNUMBER(G${r}),"非测试日纯氢",IF(AND(H${r}="是",J${r}="未判定"),IF(ISNUMBER(F${r}),"测试名单无纯氢GPS","缺失"),IF(ISNUMBER(E${r}),"非测试日仪表","缺失"))))`,
|
||||
`=IF(K${r}="缺失","",IF(K${r}="非测试日纯氢",G${r},IF(K${r}="非测试日仪表",E${r},F${r})))`,
|
||||
`=IF(K${r}="缺失",1,0)`];});
|
||||
daily.freezePanes.freezeColumns(3);previews.push(['逐日取数依据','A1:M12']);
|
||||
const recordHeaders=[...d.headers,'月度目标里程','奖励基准','缺口天数','新政策','测试候选','已有里程口径','核算源行','原考核天数','原应考核里程','取数说明'];
|
||||
table(detail,recordHeaders,d.records.map(r=>[...d.headers.map(h=>['实际行驶里程(km)','完成率(%)','是否达标','应考核里程(km)'].includes(h)?null:r[h]),r['月度目标里程'],r['月奖励'],null,r['新政策']?'是':'否',r['测试候选']?'是':'否',null,r['源表行'],r['原考核天数'],r['原应考核里程(km)'],r['取数方式']]),{A:18,D:16,E:14,F:35,G:32,H:36,J:30,K:22,L:22,N:20,O:22,P:16,Q:25,V:20,W:18,X:15,Y:12,Z:14,AA:48,AB:14,AC:16,AD:22,AE:65},['N','O','W','AD']);
|
||||
detail.getRange(`N2:Q${rr}`).formulas=d.records.map((r,i)=>{let j=i+2;return[`=ROUND(V${j}*M${j}/'规则与名单'!$B$3,2)`,`=ROUND(SUMIFS('逐日取数依据'!$L$2:$L$${dr},'逐日取数依据'!$A$2:$A$${dr},AB${j}),2)`,`=ROUND(O${j}/N${j}*100,2)`,`=IF(X${j}=M${j},"待补里程",IF(O${j}>=N${j},"达标",IF(X${j}>0,"已有里程未达标","未达标")))`];});
|
||||
detail.getRange(`X2:X${rr}`).formulas=d.records.map((_,i)=>[`=SUMIFS('逐日取数依据'!$M$2:$M$${dr},'逐日取数依据'!$A$2:$A$${dr},AB${i+2})`]);
|
||||
detail.getRange(`AA2:AA${rr}`).formulas=d.records.map((_,i)=>[`=IF(X${i+2}>0,"仅累计已有数据,缺失不代表实际零里程","数据齐全")`]);
|
||||
detail.getRange(`Q2:Q${rr}`).conditionalFormats.add('containsText',{text:'待补',format:{fill:'#FFF2CC',font:{color:'#9C5700'}}});
|
||||
detail.freezePanes.freezeColumns(1);previews.push(['业务考核视图','J1:Q12']);
|
||||
table(ledger,['车牌号','业务负责人','部门','发放类别','对应考核月','奖励基准(元)','折算分子','折算分母','核算金额(元)','本期列入(元)','本月缺口天数','8月缺口天数','依据状态','本月无考核记录'],d.ledger.map(l=>[l.plate,l.sales,l.department,l.category,l.assessment_month,l.base,l.days,l.month_days,null,null,l.gap_days,l.history_gap_days,l.status,l.virtual?'是':'否']),{A:18,B:16,C:18,D:22,E:16,F:20,G:16,H:16,I:22,J:22,K:18,L:18,M:24,N:22},['F','I','J']);
|
||||
ledger.getRange(`I2:J${lr}`).formulas=d.ledger.map((_,i)=>[`=ROUND(F${i+2}*G${i+2}/H${i+2},2)`,`=I${i+2}`]);put(ledger,lr+1,['合计']);ledger.getRange(`I${lr+1}:J${lr+1}`).formulas=[[`=SUM(I2:I${lr})`,`=SUM(J2:J${lr})`]];ledger.getRange(`I${lr+1}:J${lr+1}`).setNumberFormat(money);previews.push(['9月奖金发放记录','A1:J12']);
|
||||
table(calc,['车牌号','业务负责人','部门','9月目标(km)','9月已有里程(km)','截至8月累计目标','截至8月累计里程','累计目标(km)','累计里程(km)','累计是否达标','上期可用结转','本期结转余额','月度目标(km)','奖励基准(元)','9月缺口天数','8月缺口天数','无本月考核','当月(元)','结转(元)','历史补发(元)','累计补发9月(元)','本月合计(元)'],d.groups.map(g=>[g.plate,g.sales,g.department,g.target,g.actual,Math.round((g.cum_target-g.target)*100)/100,Math.round((g.cum_actual-g.actual)*100)/100,null,null,null,g.previous_carry,g.carry_left,g.monthly_target,g.reward,g.gap_days,g.history_gap_days,g.virtual?'是':'否',null,null,null,null,null]),{A:18,B:16,C:18,D:20,E:23,F:24,G:24,H:20,I:22,J:20,K:20,L:20,M:21,N:20,O:18,P:18,Q:19,R:19,S:19,T:22,U:24,V:22},['D','E','F','G','H','I','N','R','S','T','U','V']);
|
||||
calc.getRange(`H2:J${d.groups.length+1}`).formulas=d.groups.map((_,i)=>{const r=i+2;return[`=F${r}+D${r}`,`=G${r}+E${r}`,`=IF(AND(H${r}>0,I${r}>=H${r}),"达标","已有里程未达标")`];});
|
||||
calc.getRange(`R2:V${d.groups.length+1}`).formulas=d.groups.map((_,i)=>{const r=i+2;return[`=${pay('B'+r,'A'+r,'当月')}`,`=${pay('B'+r,'A'+r,'结转')}`,`=${Array.from({length:8},(_,m)=>pay('B'+r,'A'+r,`补发${m+1}月`)).join('+')}`,`=${pay('B'+r,'A'+r,'累计补发9月')}`,`=SUM(R${r}:U${r})`];});
|
||||
calc.freezePanes.freezeColumns(2);previews.push(['9月计算过程','A1:J12']);
|
||||
table(track,['车牌号','负责人',...Array.from({length:9},(_,i)=>[`${i+1}月目标(km)`,`${i+1}月里程(km)`]).flat(),'累计目标(km)','累计已有里程(km)','上期结转','本期结转余额','9月核算金额','9月缺口天数'],d.groups.map(g=>[g.plate,g.sales,...g.history.flat(),g.cum_target,g.cum_actual,g.previous_carry,g.carry_left,null,g.gap_days]),{A:18,B:15,U:22,V:24,W:18,X:22,Y:22,Z:20},['U','V','Y']);
|
||||
track.getRange(`Y2:Y${d.groups.length+1}`).formulas=d.groups.map((_,i)=>[`=${pay('B'+(i+2),'A'+(i+2))}`]);track.freezePanes.freezeColumns(2);previews.push(['车辆考核追踪','A1:H12']);
|
||||
table(gap,['车牌号','业务负责人','日期','考核源表行','需补来源','仪表(km)','GPS(km)','纯氢(km)','测试候选','缺口原因'],d.gaps.map(x=>[x.plate,x.sales,new Date(x.date+'T00:00:00Z'),x.source_row,x.needed,x.meter,x.gps,x.hydrogen,x.test_candidate?'是':'否',x.reason]),{A:18,B:16,C:17,D:17,E:15,F:18,G:18,H:18,I:16,J:68},['F','G','H']);gap.getRange(`C2:C${d.gaps.length+1}`).setNumberFormat('yyyy-mm-dd');previews.push(['里程缺口','A1:J12']);
|
||||
table(history,['考核月','车牌号','原业务负责人','考核天数','当时奖励基准','截至8月已结算','考核目标(km)','已修正里程(km)','结算含义'],d.historical_months.map(x=>[x.month,x.plate,x.sales,x.days,x.reward,x.settled?'是':'否',x.target,x.actual,x.settled?'已发或已核定扣减,不重复补发':'可按后续累计达标判定补发']),{A:13,B:18,C:18,D:16,E:22,F:22,G:22,H:22,I:48},['E','G','H']);previews.push(['历史结算资格','A1:I12']);
|
||||
table(aug,['车牌号','业务负责人','发放类别','考核月','原应发(元)','原扣减(元)','实际已发(元)','奖励基准','折算分子','折算分母'],d.august_ledger.map(l=>[l.plate,l.sales,l.category,l.assessment_month,l.gross_amount,l.withheld,l.amount,l.base,l.days,l.month_days]),{A:18,B:16,C:22,D:13,E:20,F:20,G:22,H:18,I:16,J:16},['E','F','G','H']);put(aug,d.august_ledger.length+2,['合计']);aug.getRange(`G${d.august_ledger.length+2}`).formulas=[[`=SUM(G2:G${d.august_ledger.length+1})`]];previews.push(['8月已发台账','A1:J12']);
|
||||
widths(sumSheet,{A:18,B:26,C:20,D:20,E:20,F:20,G:20,H:20,I:20,J:20,K:20,L:20,M:22,N:22},38);
|
||||
put(sumSheet,2,['2026年9月里程绩效核算']);sumSheet.getRange('A2').format.font={size:16,bold:true};
|
||||
put(sumSheet,4,['按现有里程核算(元)']);sumSheet.getRange('C4').formulas=[[`=SUM(${L('J')})`]];sumSheet.getRange('C4').format.font={size:16,bold:true};sumSheet.getRange('C4').setNumberFormat(money);
|
||||
put(sumSheet,5,[`本月${d.summary.gap_days}个车辆日缺少所需来源,涉及${d.summary.gap_vehicles}辆车;补数后可能改变达标、结转和补发。`]);
|
||||
put(sumSheet,6,['8月已发45,065.47元锁定;交接日计前段;本月新政策按上月名单。金额按每条发放项取分后汇总。']);
|
||||
head(sumSheet,8,['业务负责人','部门',...d.categories.map(x=>x+'(元)'),'合计(元)']);
|
||||
d.people.forEach((p,i)=>{const r=i+9;const dep=[...new Set(d.groups.filter(g=>g.sales===p).map(g=>g.department))].join('、');put(sumSheet,r,[p,dep,...Array(12).fill(null)]);sumSheet.getRange(`C${r}:M${r}`).formulas=[d.categories.map(cat=>`=${pay('A'+r,null,cat)}`)];sumSheet.getRange(`N${r}`).formulas=[[`=SUM(C${r}:M${r})`]];if(i%2===0)sumSheet.getRange(`A${r}:N${r}`).format.fill='#EDF3FA';});
|
||||
const totalRow=9+d.people.length;put(sumSheet,totalRow,['合计']);sumSheet.getRange(`C${totalRow}:N${totalRow}`).formulas=[Array.from({length:12},(_,i)=>`=SUM(${col(i+3)}9:${col(i+3)}${totalRow-1})`)];sumSheet.getRange(`C9:N${totalRow}`).setNumberFormat(money);sumSheet.getRange(`A${totalRow}:N${totalRow}`).format.font={bold:true};
|
||||
put(sumSheet,totalRow+3,['考核记录',d.summary.records,'考核车辆',d.summary.vehicles,'测试日',d.summary.test_days]);put(sumSheet,totalRow+4,['本月缺口天数',d.summary.gap_days,'整段缺里程记录',d.summary.wholly_missing_records,'新政策车辆',d.summary.new_policy_vehicles]);
|
||||
put(sumSheet,totalRow+6,['缺口明细见“里程缺口”,逐日取数可查仪表/GPS/纯氢及原始单元格。现有未达标不等于补齐后仍未达标。']);
|
||||
previews.push(['9月汇总',`A1:N${totalRow+6}`]);
|
||||
for(const p of d.people){const s=personal[p];const gs=d.groups.filter(g=>g.sales===p);widths(s,{A:18,B:28,C:18,D:23,E:23,F:23,G:23,H:23,I:23,J:23,K:23,L:23,M:27,N:20},gs.length*8+20);put(s,1,[p+' 9月绩效对账单']);s.getRange('A1').format.font={size:14,bold:true};put(s,2,['按现有里程核算(元)']);s.getRange('C2').formulas=[[`=${pay('"'+esc(p)+'"')}`]];s.getRange('C2').setNumberFormat(money);head(s,4,['车牌号','考核目标','9月奖励基准',...Array.from({length:9},(_,i)=>`${i+1}月里程/目标`),'累计里程/目标','9月缺口天数']);let row=5;
|
||||
for(const g of gs){const r=d.records.find(r=>r['车牌号']===g.plate&&r['销售经理']===p);put(s,row,[g.plate,r?.['考核目标']||'无本月考核,承接历史结转',g.reward,...g.history.map(([t,a])=>t===null?'—':`${a.toFixed(2)}/${t.toFixed(2)}`),`${g.cum_actual.toFixed(2)}/${g.cum_target.toFixed(2)}`,g.gap_days]);s.getRange(`A${row}:N${row}`).format={fill:'#EDF3FA',rowHeight:32,wrapText:true};row++;const ls=d.ledger.filter(l=>l.plate===g.plate&&l.sales===p);
|
||||
if(!ls.length){put(s,row++,['','本月未列入',0,g.gap_days?'已有里程不足,待补数据后再判定':'现有条件未达到奖励资格']);}
|
||||
for(const l of ls){put(s,row,['',l.category,null,l.status]);s.getRange(`C${row}`).formulas=[[`='9月奖金发放记录'!J${l.id+1}`]];row++;}
|
||||
put(s,row++,['','结转余额',g.carry_left,`上期可用${g.previous_carry}期,本期剩余${g.carry_left}期`]);row++;
|
||||
}s.getRange(`C5:C${row}`).setNumberFormat(money);previews.push([s.name,'A1:N13']);}
|
||||
|
||||
w.recalculate();
|
||||
const oldMeter=daily.getRange('G2').values[0][0];
|
||||
const oldMileage=detail.getRange('O2').values[0][0];
|
||||
const oldBase=ledger.getRange('F2').values[0][0];
|
||||
const oldTotal=sumSheet.getRange('C4').values[0][0];
|
||||
daily.getRange('G2').values=[[oldMeter+10]];
|
||||
ledger.getRange('F2').values=[[oldBase+30]];
|
||||
w.recalculate();
|
||||
if(Math.abs(detail.getRange('O2').values[0][0]-oldMileage-10)>1e-7)throw Error('Mileage recalc failed');
|
||||
if(Math.abs(sumSheet.getRange('C4').values[0][0]-oldTotal-30)>1e-7)throw Error('Payment recalc failed');
|
||||
daily.getRange('G2').values=[[oldMeter]];
|
||||
ledger.getRange('F2').values=[[oldBase]];
|
||||
w.recalculate();
|
||||
if(Math.abs(sumSheet.getRange('C4').values[0][0]-d.summary.amount)>1e-7)throw Error('Restore failed');
|
||||
await fs.writeFile(path.join(dir,'recalculation_probe.json'),JSON.stringify({passed:true,mileage_input_change:10,payment_input_change:30,restored:true,output_file_unchanged:true}));
|
||||
console.log('Recalculation probes passed, restored inputs; final workbook unchanged.');
|
||||
@@ -0,0 +1 @@
|
||||
{"passed":true,"mileage_input_change":10,"payment_input_change":30,"restored":true,"output_file_unchanged":true}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"records": 341,
|
||||
"vehicles": 327,
|
||||
"test_roster": 146,
|
||||
"assessed_test_vehicles": 49,
|
||||
"policy_roster": 39,
|
||||
"assessed_policy_vehicles": 36,
|
||||
"assessed_vehicle_days": 8737,
|
||||
"covered_days": {
|
||||
"meter": 7587,
|
||||
"gps": 8459,
|
||||
"hydrogen": 6436
|
||||
},
|
||||
"matrix_stats": {
|
||||
"meter": {
|
||||
"vehicles": 1024,
|
||||
"nonblank_days": 14627
|
||||
},
|
||||
"gps": {
|
||||
"vehicles": 1024,
|
||||
"nonblank_days": 29127
|
||||
},
|
||||
"hydrogen": {
|
||||
"vehicles": 1024,
|
||||
"nonblank_days": 7677
|
||||
}
|
||||
},
|
||||
"overlap_days": 3,
|
||||
"date_errors": [],
|
||||
"people": {
|
||||
"赵伟军": 13,
|
||||
"赵小峰": 1,
|
||||
"赵波": 10,
|
||||
"童军林": 4,
|
||||
"陈高伟": 42,
|
||||
"刘念念": 40,
|
||||
"钟祥": 96,
|
||||
"何文斌": 3,
|
||||
"尚建华": 21,
|
||||
"谯云": 12,
|
||||
"金可鹏": 1,
|
||||
"吴纬涛": 58,
|
||||
"秦挺": 40
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"status": "passed",
|
||||
"amount": 40211.67,
|
||||
"ledger_items": 145,
|
||||
"vehicle_days": 8737,
|
||||
"gaps": 1000,
|
||||
"source_files_unchanged": true,
|
||||
"daily_selection_checked": true,
|
||||
"ledger_independently_reconciled": true,
|
||||
"duplicate_historical_backpay": false,
|
||||
"formula_errors": []
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
from pathlib import Path
|
||||
from decimal import Decimal
|
||||
import json,openpyxl
|
||||
P=Path(__file__).parent
|
||||
f=P/'里程任务考核_2026年9月_含缺口核算.xlsx'
|
||||
after=openpyxl.load_workbook(f,data_only=True)
|
||||
with (P/'before-layout.xlsx.bak').open('rb') as source:
|
||||
before=openpyxl.load_workbook(source,data_only=True)
|
||||
d=json.loads((P/'september_calculation.json').read_text())
|
||||
controls=json.loads((P/'layout_controls.json').read_text())
|
||||
def eq(a,b):
|
||||
if isinstance(a,(int,float)) and isinstance(b,(int,float)):return abs(a-b)<1e-7
|
||||
return a==b
|
||||
for name in ['业务考核视图','逐日取数依据','里程缺口','规则与名单','历史结算资格','8月已发台账']:
|
||||
a,b=before[name],after[name]
|
||||
for row in a:
|
||||
for c in row:
|
||||
if name=='规则与名单' and c.coordinate=='B12':continue
|
||||
if name=='业务考核视图' and c.column==4 and a.cell(c.row,5).value=='刘念念':
|
||||
assert b[c.coordinate].value=='业务一部'
|
||||
continue
|
||||
assert eq(c.value,b[c.coordinate].value),(name,c.coordinate,c.value,b[c.coordinate].value)
|
||||
assert eq(after['9月汇总(新版界面)']['J2'].value,d['summary']['amount'])
|
||||
assert eq(after['9月汇总'][controls['oldTotal']].value,d['summary']['amount'])
|
||||
for i,l in enumerate(d['ledger'],2):
|
||||
new=[c.value for c in after['9月奖金发放记录'][i]]
|
||||
for k,v in {0:l['plate'],1:l['sales'],4:l['category'],5:l['assessment_month'],6:l['amount'],9:l['amount'],10:l['base'],11:l['days'],12:l['month_days']}.items():assert eq(new[k],v),(i,k,new[k],v)
|
||||
for i,r in enumerate(d['records'],2):
|
||||
s=after['里程明细9月'];assert eq(s[f'J{i}'].value,r['应考核里程(km)']);assert eq(s[f'K{i}'].value,r['实际行驶里程(km)']);assert s[f'X{i}'].value==r['缺口天数']
|
||||
for p,c in controls['personControls'].items():
|
||||
expected=sum(l['amount'] for l in d['ledger'] if l['sales']==p)
|
||||
assert eq(after[c['sheet']][c['total']].value,expected)
|
||||
errors=[(s.title,c.coordinate,c.value) for s in after for row in s for c in row if c.data_type=='e']
|
||||
assert not errors,errors
|
||||
aug=openpyxl.load_workbook(P.parent/'hydrogen-history-20260916/里程任务考核_8月核算_纯氢修正版.xlsx',data_only=True)
|
||||
for old,new in [('里程明细8月','里程明细9月'),('8月奖金发放记录','9月奖金发放记录'),('8月计算过程','9月计算过程')]:
|
||||
for cell in ['A1','A2','B1']:
|
||||
a,b=aug[old][cell],after[new][cell]
|
||||
assert a.font.name==b.font.name and a.font.sz==b.font.sz,(old,new,cell,'font')
|
||||
assert a.fill.fgColor==b.fill.fgColor,(old,new,cell,'fill')
|
||||
assert a.border.left.style==b.border.left.style,(old,new,cell,'border')
|
||||
assert after['9月汇总(新版界面)']['A5'].fill.fgColor.rgb=='FF183D50'
|
||||
result=dict(status='passed',amount=d['summary']['amount'],source_and_audit_tables_preserved=6,corrected_payout_items_verified=len(d['ledger']),personal_totals_verified=len(controls['personControls']),august_style_samples_match=True,formula_errors=errors)
|
||||
(P/'layout_verification.json').write_text(json.dumps(result,ensure_ascii=False,indent=2));print(json.dumps(result,ensure_ascii=False,indent=2))
|
||||
@@ -0,0 +1,64 @@
|
||||
from pathlib import Path
|
||||
from collections import defaultdict,Counter
|
||||
from decimal import Decimal,ROUND_HALF_UP
|
||||
import json,hashlib
|
||||
import openpyxl
|
||||
P=Path(__file__).resolve().parent
|
||||
def load(p):return json.loads(p.read_text())
|
||||
def cents(x):return float(Decimal(str(x)).quantize(Decimal('.01'),rounding=ROUND_HALF_UP))
|
||||
d=load(P/'september_calculation.json');inp=load(P/'inputs.json');aug=load(P.parent/'hydrogen-history-20260916/august_calculation.json')
|
||||
seen=set();byrow=defaultdict(list);sources=Counter();tests=set(inp['tests'])
|
||||
for x in d['daily']:
|
||||
k=x['plate'],x['date'];assert k not in seen;seen.add(k)
|
||||
t=inp['matrices']['meter'].get(k[0],{}).get(k[1]);g=inp['matrices']['gps'].get(k[0],{}).get(k[1]);h=inp['matrices']['hydrogen'].get(k[0],{}).get(k[1])
|
||||
is_test=k[0] in tests and t is not None and g is not None and Decimal(str(t))-Decimal(str(g))>Decimal(50)
|
||||
unknown=k[0] in tests and (t is None or g is None)
|
||||
expected=g if is_test else h if h is not None else g if unknown else t
|
||||
assert x['included']==expected,(k,x['included'],expected)
|
||||
byrow[x['source_row']].append(expected);sources[x['selection']]+=1
|
||||
for r in d['records']:
|
||||
vals=byrow[r['源表行']]
|
||||
assert len(vals)==r['考核天数']
|
||||
assert r['实际行驶里程(km)']==cents(sum(Decimal(str(v)) for v in vals if v is not None))
|
||||
assert r['缺口天数']==sum(v is None for v in vals)
|
||||
assert r['新政策']==(r['车牌号'] in inp['policy_plates'])
|
||||
history={(r['month'],r['plate'],r['sales']):r for r in d['historical_months']}
|
||||
prior={(g['plate'],g['sales']):g for g in aug['groups']}
|
||||
payments=defaultdict(dict)
|
||||
for l in d['ledger']:
|
||||
k=l['plate'],l['sales'];assert l['category'] not in payments[k];payments[k][l['category']]=l
|
||||
assert l['amount']==cents(Decimal(str(l['base']))*Decimal(str(l['days']))/Decimal(str(l['month_days'])))
|
||||
if l['assessment_month']<9:assert not history[(l['assessment_month'],*k)]['settled']
|
||||
for g in d['groups']:
|
||||
k=g['plate'],g['sales'];ps=payments[k];p=prior.get(k)
|
||||
assert abs(g['cum_target']-sum(t or 0 for t,a in g['history']))<.02
|
||||
assert abs(g['cum_actual']-sum(a or 0 for t,a in g['history']))<.02
|
||||
if g['previous_carry']>=1:
|
||||
assert '结转' in ps and '当月' not in ps and '累计补发9月' not in ps
|
||||
else:assert '结转' not in ps
|
||||
if '累计补发9月' in ps:assert not g['qualified'] and g['cum_qualified']
|
||||
if not g['virtual']:
|
||||
for m in range(1,9):
|
||||
old=history.get((m,*k));eligible=old and not old['settled'] and g['cum_qualified']
|
||||
assert (f'补发{m}月' in ps)==bool(eligible),(k,m)
|
||||
assert len(seen)==8737
|
||||
assert cents(sum(l['amount'] for l in d['ledger']))==d['summary']['amount']
|
||||
for x in d['sources'].values():assert hashlib.sha256(Path(x['path']).read_bytes()).hexdigest()==x['sha256']
|
||||
fp=P/'里程任务考核_2026年9月_含缺口核算.xlsx'
|
||||
if fp.exists():
|
||||
w=openpyxl.load_workbook(fp,data_only=True,read_only=True)
|
||||
assert cents(w['9月汇总(新版界面)']['J2'].value)==d['summary']['amount']
|
||||
rows=list(w['业务考核视图'].values)
|
||||
for row,r in zip(rows[1:],d['records']):
|
||||
assert row[14]==r['实际行驶里程(km)'] and row[23]==r['缺口天数']
|
||||
if r['缺口天数']==r['考核天数']:assert row[16]=='待补里程'
|
||||
wr=list(w['逐日取数依据'].values)
|
||||
for row,x in zip(wr[1:],d['daily']):
|
||||
if x['included'] is None:assert row[11] in (None,'')
|
||||
else:assert abs(row[11]-x['included'])<1e-8
|
||||
errors=[(s.title,c.coordinate,c.value) for s in w for row in s for c in row if c.data_type=='e']
|
||||
assert not errors,errors[:10]
|
||||
w.close()
|
||||
result=dict(status='passed',amount=d['summary']['amount'],ledger_items=len(d['ledger']),vehicle_days=len(seen),gaps=len(d['gaps']),source_files_unchanged=True,daily_selection_checked=True,ledger_independently_reconciled=True,duplicate_historical_backpay=False,formula_errors=errors)
|
||||
(P/'verification.json').write_text(json.dumps(result,ensure_ascii=False,indent=2));print(json.dumps(result,ensure_ascii=False,indent=2))
|
||||
else:print('Independent calculation checks passed; waiting for workbook export.')
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user