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:
shishengliang
2026-10-09 18:21:45 +08:00
parent 3ed3548b9d
commit 65904c8270
108 changed files with 1368801 additions and 3 deletions
@@ -0,0 +1,90 @@
"""Read-only source coverage audit. Does not calculate or overwrite awards."""
from pathlib import Path
from datetime import datetime, timedelta
from collections import Counter
import json, re, warnings
import openpyxl
warnings.filterwarnings('ignore', category=UserWarning, module='openpyxl')
OUT = Path(__file__).resolve().parent
REPO = OUT.parents[1]
HROOT = Path('/tmp/mileage-hydrogen-review-0916/氢能源行驶里程')
METER = Path('/Users/shishengliang/Downloads/车辆里程查询_20260101-20260731_1024辆.xlsx')
JAN = Path('/Users/shishengliang/Code/lingniu/氢能车用氢行驶里程统计 1.17.xlsx')
def read(p, sheet=None):
w = openpyxl.load_workbook(p, read_only=True, data_only=True)
s = w[sheet] if sheet else w.worksheets[0]
s.reset_dimensions()
r = list(s.values)
w.close()
return r
def norm(x):
return re.sub(r'\s+', '', str(x or '')).upper()
h = {}
bad_files = []
for p in HROOT.rglob('*.xlsx'):
if int(p.parent.name[:-1]) > 6:
continue
rr = read(p)
headers = list(rr[0])
if '车牌号码' not in headers:
bad_files.append(str(p))
continue
for row in rr[1:]:
r = dict(zip(headers,row))
plate = norm(r.get('车牌号码'))
if not plate:
continue
d = str(r['日期范围'])[:10]
val = r.get('氢能源行驶里程(km)')
if val is not None and str(val).strip():
key = (plate,d)
assert key not in h, ('duplicate hydrogen',key)
h[key] = float(val)
jan_status = 'not read'
try:
rr=read(JAN)
assert '车牌号码' in rr[0]
for row in rr[1:]:
r=dict(zip(rr[0],row)); plate=norm(r.get('车牌号码'))
if plate and r.get('氢能源行驶里程(km)') is not None:
h[(plate,str(r['日期范围'])[:10])] = float(r['氢能源行驶里程(km)'])
jan_status='read'
except (OSError,AssertionError) as e:
jan_status=f'{type(e).__name__}: {e}'
(OUT/'hydrogen_daily.json').write_text(json.dumps([[p,d,v] for (p,d),v in sorted(h.items())],ensure_ascii=False))
# GPS and meter acquisition superseded by the user's monthly minimum rule.
t={};meter_months=Counter()
records=[]; summary=[]
for m in range(1,7):
rr=read(REPO/f'租赁任务考核_2026年{m}月.xlsx','业务考核视图')
month=[]
for rownum,row in enumerate(rr[1:],2):
r=dict(zip(rr[0],row)); plate=norm(r.get('车牌号'))
if not plate:
continue
def as_date(v):
return v.date() if isinstance(v,datetime) else datetime.fromisoformat(str(v)).date()
start,end=as_date(r['考核开始日期']),as_date(r['考核结束日期'])
dates=[(start+timedelta(days=i)).isoformat() for i in range((end-start).days+1)]
mh=[d for d in dates if (plate,d) not in h]
mt=[d for d in dates if (plate,d) not in t]
both=[d for d in mh if (plate,d) not in t]
x=dict(month=m,source_row=rownum,plate=plate,sales=r.get('销售经理'),start=str(start),end=str(end),days=len(dates),hydrogen_missing_dates=mh,meter_missing_dates=mt,both_missing_dates=both,original_assessment_km=r.get('实际行驶里程(km)'),test_decision='not applicable before April' if m<4 else 'pending GPS and complete same-period meter basis')
assert len(mh) in (0,len(dates)), 'Partial hydrogen needs a daily fallback decision'
x['hydrogen_km'] = round(sum(h[(plate,d)] for d in dates), 2) if not mh else None
x['candidate_non_test_km'] = x['hydrogen_km'] if not mh else x['original_assessment_km']
x['meter_comparison_km'] = x['original_assessment_km']
x['meter_basis'] = '用户确认历史考核源表实际里程即完整仪表区间合计'
x['test_decision'] = '不再采用测试车辆判定;按同组月合计取小值'
month.append(x);records.append(x)
summary.append(dict(month=m,records=len(month),records_with_hydrogen_gaps=sum(bool(x['hydrogen_missing_dates']) for x in month),records_with_meter_gaps=sum(bool(x['meter_missing_dates']) for x in month),records_with_both_gaps=sum(bool(x['both_missing_dates']) for x in month),both_missing_record_days=sum(len(x['both_missing_dates']) for x in month)))
result=dict(jan17_status=jan_status,meter_nonblank_vehicle_days_by_month=dict(sorted(meter_months.items())),summary=summary,records=records,invalid_archive_files=bad_files)
(OUT/'source_coverage.json').write_text(json.dumps(result,ensure_ascii=False,indent=2,default=str))
print(json.dumps({'jan17_status':jan_status,'summary':summary},ensure_ascii=False,indent=2))
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,178 @@
[
{
"plate": "粤A01013F",
"sales": "刘念念",
"category": "累计补发8月",
"old": 1000.0,
"new": 0,
"difference": -1000.0
},
{
"plate": "粤A02298F",
"sales": "刘念念",
"category": "累计补发8月",
"old": 1000.0,
"new": 0,
"difference": -1000.0
},
{
"plate": "粤A05378F",
"sales": "秦挺",
"category": "补发6月",
"old": 1000.0,
"new": 0,
"difference": -1000.0
},
{
"plate": "粤A05378F",
"sales": "秦挺",
"category": "补发7月",
"old": 677.42,
"new": 0,
"difference": -677.42
},
{
"plate": "粤A05809F",
"sales": "刘念念",
"category": "累计补发8月",
"old": 1000.0,
"new": 0,
"difference": -1000.0
},
{
"plate": "粤A05968F",
"sales": "秦挺",
"category": "累计补发8月",
"old": 1000.0,
"new": 0,
"difference": -1000.0
},
{
"plate": "粤AGP2011",
"sales": "钟祥",
"category": "累计补发8月",
"old": 0,
"new": 260.0,
"difference": 260.0
},
{
"plate": "粤AGP2011",
"sales": "钟祥",
"category": "结转",
"old": 260.0,
"new": 0,
"difference": -260.0
},
{
"plate": "粤AGP3607",
"sales": "刘念念",
"category": "当月",
"old": 0,
"new": 234.84,
"difference": 234.84
},
{
"plate": "粤AGP3607",
"sales": "刘念念",
"category": "结转",
"old": 260.0,
"new": 0,
"difference": -260.0
},
{
"plate": "粤AGP5378",
"sales": "刘念念",
"category": "累计补发8月",
"old": 0,
"new": 101.61,
"difference": 101.61
},
{
"plate": "粤AGP5378",
"sales": "刘念念",
"category": "结转",
"old": 150.0,
"new": 0,
"difference": -150.0
},
{
"plate": "粤AGP6639",
"sales": "尚建华",
"category": "当月",
"old": 0,
"new": 111.29,
"difference": 111.29
},
{
"plate": "粤AGP6639",
"sales": "尚建华",
"category": "结转",
"old": 150.0,
"new": 0,
"difference": -150.0
},
{
"plate": "粤AGP9706",
"sales": "尚建华",
"category": "累计补发8月",
"old": 0,
"new": 117.42,
"difference": 117.42
},
{
"plate": "粤AGP9706",
"sales": "尚建华",
"category": "结转",
"old": 260.0,
"new": 0,
"difference": -260.0
},
{
"plate": "粤AGQ5819",
"sales": "吴纬涛",
"category": "当月",
"old": 0,
"new": 150.0,
"difference": 150.0
},
{
"plate": "粤AGQ5819",
"sales": "吴纬涛",
"category": "结转",
"old": 150.0,
"new": 0,
"difference": -150.0
},
{
"plate": "粤AGQ5838",
"sales": "刘念念",
"category": "当月",
"old": 0,
"new": 150.0,
"difference": 150.0
},
{
"plate": "粤AGQ5838",
"sales": "刘念念",
"category": "结转",
"old": 150.0,
"new": 0,
"difference": -150.0
},
{
"plate": "粤AGR5098",
"sales": "尚建华",
"category": "当月",
"old": 0,
"new": 150.0,
"difference": 150.0
},
{
"plate": "粤AGR5098",
"sales": "尚建华",
"category": "结转",
"old": 150.0,
"new": 0,
"difference": -150.0
}
]
@@ -0,0 +1,180 @@
import fs from 'node:fs/promises';
import {Workbook,SpreadsheetFile,FileBlob} from '@oai/artifact-tool';
const root='/Users/shishengliang/Documents/羚牛/里程考核核算/8月';
const sup='/Users/shishengliang/Code/lingniu/mileage-bonus/outputs/hydrogen-history-20260916', file=sup+'/里程任务考核_8月核算_纯氢修正版.xlsx';
const july='/Users/shishengliang/Code/lingniu/mileage-bonus/7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx';
if(globalThis.process.argv.includes('--preview')){
for(const [p,names,prefix]of [[july,['考核奖励规则','7月汇总','一部-何文斌'],'july'],[file,['8月汇总'],'aug_before']]){
const w=await SpreadsheetFile.importXlsx(await FileBlob.load(p));
for(const name of names){const b=await w.render({sheetName:name,range:name.includes('汇总')?'A1:F16':'A1:M14',scale:1});await fs.writeFile(`${sup}/${prefix}_${name}.png`,new Uint8Array(await b.arrayBuffer()));}
}
globalThis.process.exit(0);
}
const d=JSON.parse(await fs.readFile(sup+'/august_calculation.json','utf8'));
const meta=JSON.parse(await fs.readFile(sup+'/july_style_metadata.json','utf8'));
const w=Workbook.create(), previews=[], controls={people:{},ledgerRows:{}};
const c=n=>{let s='';for(;n;n=Math.floor((n-1)/26))s=String.fromCharCode(65+(n-1)%26)+s;return s;};
const money='#,##0.00;[Red](#,##0.00);0.00';
const dataEnd=d.ledger.length+1;
const L=col=>`'8月奖金发放记录'!$${col}$2:$${col}$${dataEnd}`;
const sum=(sales,cat,plate)=>`SUMIFS(${L('J')},${L('B')},${sales}${cat?`,${L('E')},"${cat}"`:''}${plate?`,${L('A')},${plate}`:''})`;
const sumG=(sales,cat,plate)=>`SUMIFS(${L('G')},${L('B')},${sales}${cat?`,${L('E')},"${cat}"`:''}${plate?`,${L('A')},${plate}`:''})`;
const departed=d.departed||{people:[],transfers:[],transfer_map:{},legacy_items:[],reason:''};
const departedSet=new Set(departed.people||[]);
// 主表按原负责人过滤:仅剔除四名离职人员的 records/分组,同车其他负责人的区间保留,不按车牌整删。
const dr=[...d.records].filter(r=>!departedSet.has(r['销售经理'])).sort((a,b)=>(a['销售经理']+a['车牌号']).localeCompare(b['销售经理']+b['车牌号'],'zh'));
const groups=d.groups.filter(g=>!departedSet.has(g.sales));
const dispVehicles=new Set(dr.map(r=>r['车牌号'])).size;
const dispQualified=dr.filter(r=>r['是否达标']==='达标').length;
const dispGapDays=dr.reduce((a,r)=>a+(r['缺口天数']||0),0);
const transferByReceiver=new Map();
for(const t of (departed.transfers||[])){const a=transferByReceiver.get(t.receiver)||[];a.push(t);transferByReceiver.set(t.receiver,a);}
const transferLedgerId=t=>{const l=d.ledger.find(x=>x.transfer&&x.plate===t.plate&&x.sales===t.receiver);return l?l.id:null;};
const clean=v=>v===undefined?null:typeof v==='string'&&v.startsWith('=')?"'"+v:v;
function sheet(name,n=13,end=30){
const s=w.worksheets.add(name),r=s.getRange(`A1:${c(n)}${end}`);
r.format={font:{name:'宋体',size:11,color:'#000000'},rowHeight:19,columnWidth:14,verticalAlignment:'center'};
s.showGridLines=true;return s;
}
function write(s,row,vals){s.getRange(`A${row}:${c(vals.length)}${row}`).values=[vals.map(clean)];}
function grid(s,a){s.getRange(a).format.borders={preset:'all',style:'thin',color:'#000000'};}
function header(s,row,vals){write(s,row,vals);const a=`A${row}:${c(vals.length)}${row}`;s.getRange(a).format={font:{name:'宋体',size:11,bold:true,color:'#000000'},fill:'#D9E1F2',horizontalAlignment:'center',wrapText:true,rowHeight:34};grid(s,a);}
function widths(s,ws,end=100){for(const [col,v]of Object.entries(ws))s.getRange(`${col}1:${col}${end}`).format.columnWidth=v;}
function bold(s,row,n=3){s.getRange(`A${row}:${c(n)}${row}`).format.font={name:'宋体',bold:true,size:11,color:'#000000'};}
function table(s,h,rows,ws={},numbers=[]){header(s,1,h);if(rows.length)s.getRange(`A2:${c(h.length)}${rows.length+1}`).values=rows.map(r=>r.map(clean));grid(s,`A1:${c(h.length)}${rows.length+1}`);widths(s,ws,rows.length+1);for(const col of numbers)s.getRange(`${col}2:${col}${rows.length+1}`).setNumberFormat(money);}
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 personalName=p=>(d.departments[p].includes('、')?'跨部门':d.departments[p].replace('业务',''))+'-'+p;
// 与7月相同的工作表次序,新版汇总紧邻旧版汇总。
const rules=sheet('考核奖励规则',6,68), detail=sheet('里程明细8月',23,dr.length+1), process=sheet('8月计算过程',25,groups.length+1), tracking=sheet('车辆考核追踪',40,groups.length+1), ledger=sheet('8月奖金发放记录',14,dataEnd+2), old=sheet('8月汇总',3,170), modern=sheet('8月汇总(新版界面)',13,36);
const ps={};for(const p of d.people)ps[p]=sheet(personalName(p),13,groups.filter(g=>g.sales===p).length*8+10);
table(rules,['考核目标','月度目标里程(km)','当月达标','当月未达标','结转','补发/累计补发'],[
['交投40辆4.5T普货',3000,'150元×天数折算','不发','150元整月','150元×折算'],['交投190辆4.5T冷链车',3000,'150元×天数折算','不发','150元整月','150元×折算'],['羚牛136辆4.5T冷链车',5000,'260元×天数折算','不发','260元整月','260元×折算'],['恒运50辆4.5T普货',5000,'260元×天数折算','不发','260元整月','260元×折算'],['羚牛100辆18T',6000,'1000元×天数折算','不发','1000元整月','1000元×折算']
],{A:38,B:18,C:22,D:20,E:20,F:24});
header(rules,9,['8月名单对应考核目标','月目标不变','名单当月奖励','未达标','名单结转','8月补发基准']);
let rn=10;for(const [name,amount]of Object.entries(d.rules.new_policy.award_by_target)){const km=name.includes('18T')?6000:name.startsWith('交投')?3000:5000;write(rules,rn++,[name,km,`${amount}元×天数折算`,'不发',`${amount}元整月`,`${amount}元×折算`]);}grid(rules,'A9:F14');
rules.mergeCells('A16:F18');write(rules,16,['名单标9月,本次适用于8月;名单外沿用原金额。折算、结转、补发规则不变,历史月补发仍使用历史基准。亏损暂不处理。']);rules.getRange('A16:F18').format.wrapText=true;
rules.mergeCells('A20:F22');write(rules,20,[`本表按每条发放项取分:应发合计${d.stats.gross_amount.toFixed(2)}元,实发合计${d.stats.amount.toFixed(2)}元,保留原表扣减9.68元。应发未逐项取分总和为${d.stats.unrounded_total.toFixed(2)}元,与应发相差${Math.abs(d.stats.rounding_difference).toFixed(2)}元。1—7月实际已发保留,不追扣、不重复补发。1—6月按纯氢与原考核里程取小值,无纯氢则保留原里程。来源为输入数据目录及项目1—7月历史源表,7月采用20260817重算版本。`]);rules.getRange('A20:F22').format.wrapText=true;
header(rules,24,['名单车牌','原考核表行号','8月月奖励(元)','业务员','部门','考核目标']);
for(const [i,v]of d.rules.new_policy.matched.entries()){const r=d.records.find(r=>r['车牌号']===v.plate);write(rules,i+25,[v.plate,v.source_rows.join('、'),v.monthly_bonus,r['销售经理'],r['部门名称'],v.target]);}grid(rules,'A24:F55');rules.getRange('F25:F55').format.wrapText=true;rules.getRange('A25:F55').format.rowHeight=34;
previews.push(['考核奖励规则','A1:F18']);
table(detail,['车牌号','部门名称','销售经理','客户名称','合同编号','考核目标','月度目标里程','月奖励金额','考核天数','8月应考核里程','8月实际行驶里程','完成率','8月是否达标','8月奖金(天数折算)','多跑里程','可结转月数','考核状态','源表行','取数方式','新政策','考核开始日期','考核结束日期','说明'],dr.map(r=>[r['车牌号'],r['部门名称'],r['销售经理'],r['客户名称'],r['合同编号'],r['考核目标'],r['月度目标里程'],r['奖励基准_达标'],r['考核天数'],r['应考核里程(km)'],r['实际行驶里程(km)'],r['实际行驶里程(km)']/r['应考核里程(km)'],r['是否达标'],null,r['多跑'],r['可结转'],r['考核状态'],r['源表行'],r['取数方式'],r['新政策'],new Date(r['考核开始日期']+'T00:00:00Z'),new Date(r['考核结束日期']+'T00:00:00Z'),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:14,N:20,O:16,P:14,Q:14,R:10,S:25,T:12,U:16,V:16,W:65},['H','J','K','N','O']);
detail.getRange(`N2:N${dr.length+1}`).formulas=dr.map((_,i)=>[`=IF(K${i+2}>=J${i+2},ROUND(H${i+2}*I${i+2}/31,2),0)`]);detail.getRange(`L2:L${dr.length+1}`).setNumberFormat('0.00%');detail.getRange(`U2:V${dr.length+1}`).setNumberFormat('yyyy-mm-dd');previews.push(['里程明细8月','A1:H12']);
table(ledger,['车牌号','业务员','部门','客户名称','发放类型','对应考核月','考核应发','客户盈亏','不发/扣减','实发金额','奖励基准','折算分子','折算分母','原发放编号','说明'],d.ledger.map(l=>{const r=rec(l);const note=l.withhold_reason||'';return[l.plate,l.sales,l.department,r['客户名称']||meta.vehicles[l.plate]?.[7]||'',l.category,l.assessment_month,null,'暂不处理',null,null,l.base,l.days,l.month_days,l.id,note];}),{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},['G','I','J','K']);
ledger.getRange(`G2:G${dataEnd}`).formulas=d.ledger.map((_,i)=>[`=ROUND(K${i+2}*L${i+2}/M${i+2},2)`]);ledger.getRange(`I2:I${dataEnd}`).values=d.ledger.map(l=>[l.withheld]);ledger.getRange(`J2:J${dataEnd}`).formulas=d.ledger.map((_,i)=>[`=G${i+2}-I${i+2}`]);ledger.getRange(`F2:F${dataEnd}`).setNumberFormat('0"月"');write(ledger,dataEnd+1,['合计']);for(const col of ['G','I','J'])ledger.getRange(`${col}${dataEnd+1}`).formulas=[[`=SUM(${col}2:${col}${dataEnd})`]];bold(ledger,dataEnd+1,15);ledger.getRange(`G${dataEnd+1}:K${dataEnd+1}`).setNumberFormat(money);ledger.mergeCells(`A${dataEnd+3}:O${dataEnd+4}`);ledger.getRange(`A${dataEnd+3}`).values=[[`金额为实发:考核应发(G)−不发/扣减(I)=实发(J)。保留原表扣减9.68元,亏损暂不处理。`]];ledger.getRange(`A${dataEnd+3}:O${dataEnd+4}`).format.wrapText=true;ledger.getRange(`A${dataEnd+3}:O${dataEnd+4}`).format.font={name:'宋体',size:10,color:'#C00000'};previews.push(['8月奖金发放记录','A1:O12']);
const cats=['结转',...Array.from({length:7},(_,i)=>`补发${i+1}月`),'当月','累计补发8月'];
table(process,['车牌号','销售经理','部门','8月记录数','8月总天数','8月应考核','8月实际','8月有达标','8月奖金','累计应考核','累计实际','累计达标','可结转',...cats.map(x=>x==='当月'?'当月奖金':x==='结转'?'结转发放':x),'8月发放合计','无本月考核记录'],groups.map(g=>[g.plate,g.sales,g.department,g.rows,g.days,g.target,g.actual,g.qualified?'是':'否',d.records.filter(r=>r['车牌号']===g.plate&&r['销售经理']===g.sales).reduce((a,r)=>a+r['奖金'],0),g.cum_target,g.cum_actual,g.cum_qualified?'达标':'未达标',g.carry_left,...cats.map(()=>null),null,g.virtual?'是':'否']),{A:16,B:12,C:16,D:14,E:14,F:18,G:18,H:14,I:16,J:18,K:18,L:14,M:12,X:18,Y:22},['F','G','I','J','K',...Array.from({length:11},(_,i)=>c(i+14))]);
process.getRange(`N2:W${groups.length+1}`).formulas=groups.map((g,i)=>cats.map(cat=>`=${sum(`B${i+2}`,cat,`A${i+2}`)}`));process.getRange(`X2:X${groups.length+1}`).formulas=groups.map((g,i)=>[`=SUM(N${i+2}:W${i+2})`]);previews.push(['8月计算过程','A1:M12']);
const th=['车牌号','车架号','归属公司','车型','考核目标','月度奖励','业务员',...Array.from({length:8},(_,i)=>[`${i+1}月应考核`,`${i+1}月实际`,`${i+1}月达标`]).flat(),'累计应完成','累计实际','累计达标','客户名称','客户是否亏损','考核应发','最终发放','未发放原因','本月无考核记录'];
table(tracking,th,groups.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])=>[t,a,t===null?'':a>=t?'✓':'✗']),g.cum_target,g.cum_actual,g.cum_qualified?'✓':'✗',r['客户名称']||v[7]||'','暂不处理',null,null,g.withhold_reason?g.withhold_reason:(g.total?'':g.virtual?'本月无新增发放':'未满足本月奖励、结转或补发条件'),g.virtual?'是':'否'];}),{A:16,B:24,C:38,D:18,E:28,F:14,G:14,AI:36,AJ:18,AK:16,AL:16,AM:40,AN:22},['F','AF','AG','AK','AL']);
tracking.getRange(`AK2:AL${groups.length+1}`).formulas=groups.map((g,i)=>[`=${sumG(`G${i+2}`,null,`A${i+2}`)}`,`=${sum(`G${i+2}`,null,`A${i+2}`)}`]);for(let i=0;i<groups.length;i++)tracking.getRange(`A${i+2}:AN${i+2}`).format.fill=i%2?'#E8E8E8':'#F2F2F2';tracking.freezePanes.freezeRows(1);tracking.freezePanes.freezeColumns(7);previews.push(['车辆考核追踪','A1:G12']);
// 7月的纵向分类汇总格式。离职人员不列入汇总;接收人的转入项已在其名下计入。
const summaryPeople=d.people.filter(p=>!departedSet.has(p));
const summaryItems=(p,cat)=>d.ledger.filter(l=>l.sales===p&&(!cat||l.category===cat));
const summaryFormula=(p,cell,cat)=>sum(cell,cat);
widths(old,{A:28,B:26,C:20},170);write(old,1,['一、实发明细(按发放类别)']);bold(old,1);rn=3;
function section(title,cat){write(old,rn++,[title]);bold(old,rn-1);header(old,rn++,['销售人员','车辆数','金额']);const start=rn;const people=summaryPeople.filter(p=>summaryItems(p,cat).length);
for(const p of people){write(old,rn,[p,null,null]);old.getRange(`B${rn}`).formulas=[[`=COUNTIFS(${L('B')},A${rn}${cat?`,${L('E')},"${cat}"`:''})`]];old.getRange(`C${rn}`).formulas=[[`=${summaryFormula(p,`A${rn}`,cat)}`]];rn++;}
write(old,rn,['总计',null,null]);for(const col of ['B','C'])old.getRange(`${col}${rn}`).formulas=[[`=SUM(${col}${start}:${col}${rn-1})`]];grid(old,`A${start}:C${rn}`);bold(old,rn);rn+=2;
}
for(const cat of d.categories.filter(cat=>d.ledger.some(l=>l.category===cat)))section('实发-'+cat,cat);
write(old,rn,['考核应发合计',null,null]);old.getRange(`C${rn}`).formulas=[[`=SUM(${L('G')})`]];bold(old,rn);rn+=2;
write(old,rn++,['二、亏损处理']);bold(old,rn-1);write(old,rn++,['亏损暂不处理。']);rn++;
write(old,rn++,['三、最终发放']);bold(old,rn-1);rn++;section('最终发放明细',null);
write(old,rn++,['8月最终发放(按销售人员)']);bold(old,rn-1);header(old,rn++,['销售人员','部门名称','最终发放']);let start=rn;
for(const p of summaryPeople){write(old,rn,[p,d.departments[p],null]);old.getRange(`C${rn}`).formulas=[[`=${summaryFormula(p,`A${rn}`)}`]];rn++;}grid(old,`A${start}:C${rn-1}`);write(old,rn,['合计',null,null]);old.getRange(`C${rn}`).formulas=[[`=SUM(C${start}:C${rn-1})`]];bold(old,rn);rn+=2;
write(old,rn++,['8月最终发放(按部门)']);bold(old,rn-1);header(old,rn++,['部门名称','最终发放']);const depts=[...new Set([...groups.map(g=>g.department),...d.ledger.map(l=>l.department)])].sort();start=rn;
for(const dep of depts){write(old,rn,[dep,null]);old.getRange(`B${rn}`).formulas=[[`=SUMIF(${L('C')},A${rn},${L('J')})`]];rn++;}grid(old,`A${start}:B${rn-1}`);write(old,rn,['合计',null]);old.getRange(`B${rn}`).formulas=[[`=SUM(B${start}:B${rn-1})`]];bold(old,rn);rn+=2;
write(old,rn++,['总览']);bold(old,rn-1);for(const [label,col]of [['考核应发','G'],['不发/扣减','I'],['最终发放','J']]){write(old,rn,[label,null]);old.getRange(`B${rn}`).formulas=[[`=SUM(${L(col)})`]];rn++;}bold(old,rn-1);old.getRange(`C1:C${rn}`).setNumberFormat(money);old.getRange(`B${start}:B${rn}`).setNumberFormat(money);controls.oldTotal=`B${rn-1}`;previews.push(['8月汇总','A1:C19'],['8月汇总',`A${rn-7}:C${rn-1}`]);
// 保留上版深色表头、人员横向分类和部门汇总界面。
modern.showGridLines=false;modern.getRange('A1:M36').format={font:{name:'PingFang SC',size:11,color:'#183D50'},columnWidth:16,rowHeight:24};widths(modern,{A:14,B:26},36);
modern.mergeCells('A1:H1');write(modern,1,['2026年8月里程绩效核算']);modern.getRange('A1:H1').format.font={size:18,bold:true};modern.getRange('A1').format.rowHeight=36;
modern.mergeCells('A2:H3');write(modern,2,['金额为实发,按每条发放记录四舍五入到分后汇总。亏损暂不处理。部门汇总按发放记录归属。']);modern.getRange('A2').values=[['1—6月历史里程已修正,1—7月实际已发保留。修正前后差额见8月调整对照。金额按发放项取分后汇总。']];modern.getRange('A2:H3').format={font:{size:10,color:'#526671'},wrapText:true};
modern.mergeCells('J1:M1');modern.getRange('J1').values=[['8月核算总额(元)']];modern.getRange('J1:M1').format.font={size:12,bold:true,color:'#167D8D'};modern.mergeCells('J2:M3');modern.getRange('J2').formulas=[['=M22']];modern.getRange('J2:M3').format.font={size:26,bold:true};modern.getRange('J2').setNumberFormat(money);
write(modern,5,['业务员','部门',...d.categories.map(cat=>cat+'(元)'),'合计(元)']);modern.getRange('A5:M5').format={fill:'#183D50',font:{color:'#FFFFFF',bold:true},rowHeight:44,wrapText:true};
for(const [i,p]of summaryPeople.entries()){const row=i+6;write(modern,row,[p,d.departments[p],...Array(11).fill(null)]);modern.getRange(`C${row}:L${row}`).formulas=[d.categories.map(cat=>`=${summaryFormula(p,`A${row}`,cat)}`)];modern.getRange(`M${row}`).formulas=[[`=SUM(C${row}:L${row})`]];modern.getRange(`A${row}:M${row}`).format.fill=i%2?'#FFFFFF':'#BFE5F2';}
write(modern,22,['合计']);modern.getRange('C22:M22').formulas=[Array.from({length:11},(_,i)=>`=SUM(${c(i+3)}6:${c(i+3)}21)`)];modern.getRange('A22:M22').format={fill:'#EAF3F5',font:{bold:true},rowHeight:30};modern.getRange('C6:M22').setNumberFormat(money);
modern.getRange('A26:B26').values=[['部门','发放金额(元)']];modern.getRange('A26:B26').format={fill:'#183D50',font:{bold:true,color:'#FFFFFF'},rowHeight:30};for(const [i,dep]of depts.entries()){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+depts.length}`).setNumberFormat(money);
modern.mergeCells('A35:M36');modern.getRange('A35').values=[[`本月${dispVehicles}辆、${dr.length}条考核记录,其中${dispQualified}条达标;新口径下${dispGapDays}个车辆日所需来源缺失,按已有记录累计并单列缺口,补数后应重算累计、结转及补发。本次实发${d.stats.amount.toFixed(2)}元,保留原表扣减9.68元。`]];modern.getRange('A35:M36').format.wrapText=true;modern.freezePanes.freezeRows(5);modern.freezePanes.freezeColumns(2);previews.push(['8月汇总(新版界面)','A1:M36']);
const modernTable=modern.tables.add(`A5:M${5+summaryPeople.length}`,true,'AugustModernSummary');modernTable.showFilterButton=true;
// 个人对账单按7月方式逐车展开,增加8月列,发放项链接本次8月账单。
// 接收人:本人车辆 + 接收转入的离职人员8月当月达标车辆与奖金;离职人员:原计算额审计、本人实发0。
for(const p of d.people){const s=ps[p],ownGroups=groups.filter(g=>g.sales===p),audit=d.person_audit.find(r=>r[0]===p)||[p,'',0,0,0,''];
const inTransfers=transferByReceiver.get(p)||[],isDeparted=departedSet.has(p);const legacyByPlate=new Map();
if(isDeparted){for(const l of (departed.legacy_items||[])){if(l.sales!==p)continue;const a=legacyByPlate.get(l.plate)||[];a.push(l);legacyByPlate.set(l.plate,a);}}
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:25,M:12},(ownGroups.length+inTransfers.length)*8+12);
s.mergeCells('A1:M1');write(s,1,[`${p} | ${d.departments[p]} | 8月绩效对账单${isDeparted?'(离职审计,实发0)':''}`]);s.getRange('A1:M1').format.font={name:'宋体',bold:true,size:14};s.getRange('A1').format.rowHeight=22;
s.mergeCells('A2:D2');write(s,2,[`本月考核及结转车辆:${ownGroups.length}辆${inTransfers.length?`;接收转入车辆:${inTransfers.length}辆`:''}`]);bold(s,2,4);s.mergeCells('E2:G2');s.getRange('E2').values=[['本月实发合计(元)']];s.getRange('H2').formulas=[[`=${sum(`"${p}"`)}`]];s.getRange('H2').setNumberFormat(money);bold(s,2,13);
if(isDeparted){s.mergeCells('A3:M3');s.getRange('A3').values=[[`已离职:原计算额${audit[2].toFixed(2)}元(含当月、结转及历史补发)不再按旧口径支付,本人实发0元;8月当月达标奖励已按新规则转交接收人,原计算项与接收人追溯见离职人员处理表。`]];s.getRange('A3:M3').format={font:{name:'宋体',size:10,bold:true,color:'#C00000'},wrapText:true,rowHeight:30};}
else if(inTransfers.length){const sumT=inTransfers.reduce((a,t)=>a+t.amount,0);s.mergeCells('A3:M3');s.getRange('A3').values=[[`本月含接收转入的离职人员8月当月达标奖励${sumT.toFixed(2)}元,已并入本月实发合计;转入车辆与源考核明细见离职人员处理表。`]];s.getRange('A3:M3').format={font:{name:'宋体',size:10,bold:true,color:'#C00000'},wrapText:true,rowHeight:30};}
header(s,4,['车牌号','考核目标','月奖励',...Array.from({length:8},(_,i)=>`${i+1}月里程/目标`),'累计里程/目标','达标']);let row=5;
for(const g of ownGroups){const r=rec(g),items=isDeparted?(legacyByPlate.get(g.plate)||[]):d.ledger.filter(l=>l.plate===g.plate&&l.sales===p);write(s,row,[g.plate,r['考核目标']||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?'达标':'未达标']);grid(s,`A${row}:M${row}`);s.getRange(`A${row}`).format.font={bold:true};s.getRange(`B${row}`).format.wrapText=true;s.getRange(`A${row}:M${row}`).format.rowHeight=34;row++;
header(s,row,['','发放项','金额','说明','结转余额']);s.mergeCells(`E${row}:G${row}`);row++;
if(items.length){for(const l of items){if(isDeparted){const note=l.category==='当月'?'旧当月口径,已由8月达标记录重新生成后转交接收人':'离职人员结转/历史补发取消,本人不发';write(s,row,['',l.category,l.gross_amount,note,null]);}else{const bnote=l.transfer?'8月当月达标奖励,接收转入':(l.virtual?'历史结转,本月无考核记录':l.category==='结转'?'整月结转,不与当月奖励重复':'奖励基准×考核天数/对应月天数');write(s,row,['',l.category,null,bnote,null]);s.getRange(`C${row}`).formulas=[[`='8月奖金发放记录'!J${l.id+1}`]];}s.mergeCells(`D${row}:G${row}`);grid(s,`A${row}:G${row}`);row++;}}
else{write(s,row,['','本月不发',0,isDeparted?'本人无发放项':'未满足本月奖励、结转或补发条件',null]);s.mergeCells(`D${row}:G${row}`);grid(s,`A${row}:G${row}`);row++;}
write(s,row,['','本月合计',null,isDeparted?'实发0,原计算额不再支付':'已计入个人汇总',`上期${g.previous_carry}期,本期剩余${g.carry_left}期`]);s.getRange(`C${row}`).formulas=[[isDeparted?'=0':`=${sum(`"${p}"`,null,`"${g.plate}"`)}`]];s.mergeCells(`E${row}:G${row}`);grid(s,`A${row}:G${row}`);bold(s,row,3);row+=2;
}
if(inTransfers.length){header(s,row,['车牌号','原考核目标','8月里程/目标','发放项','转入金额','说明']);s.mergeCells(`F${row}:M${row}`);row++;
for(const t of inTransfers){const g=d.groups.find(x=>x.plate===t.plate&&x.sales===t.owner_sales)||{},r=rec({plate:t.plate,sales:t.owner_sales}),lid=transferLedgerId(t);write(s,row,[t.plate,r['考核目标']||meta.vehicles[t.plate]?.[4]||'',`${(g.actual||0).toFixed(2)}/${(g.target||0).toFixed(2)}`,t.category,null,'离职人员当月达标转入(接收)']);s.getRange(`E${row}`).formulas=[[`='8月奖金发放记录'!J${lid+1}`]];s.getRange(`E${row}`).setNumberFormat(money);s.mergeCells(`F${row}:M${row}`);grid(s,`A${row}:E${row}`);row++;}
write(s,row,['转入合计',null,null,null,null,null]);s.getRange(`E${row}`).formulas=[[`=SUM(E${row-inTransfers.length}:E${row-1})`]];s.getRange(`E${row}`).setNumberFormat(money);bold(s,row,5);row+=2;
}
write(s,row,['合计',null,null]);s.getRange(`C${row}`).formulas=[['=H2']];bold(s,row);s.getRange(`C5:C${row}`).setNumberFormat(money);controls.people[p]={sheet:personalName(p),total:`C${row}`,groups:ownGroups.length};previews.push([personalName(p),`A1:M${Math.min(13,row)}`]);
}
// 客观缺失日:全部所需来源缺失的记录保留“待核实”,不写成实测0,也不使用“暂算”表述。
const empty=new Set(d.records.filter(r=>r['缺口天数']===r['考核天数']).map(r=>r['源表行']));
dr.forEach((r,i)=>{if(empty.has(r['源表行'])){detail.getRange(`M${i+2}`).values=[['待核实']];detail.getRange(`W${i+2}`).values=[['全部所需来源缺失,已有计入0为累计值而非实测0,补数后重算。']];}});
const comparison=JSON.parse(await fs.readFile(sup+'/comparison.json','utf8'));
const changes=JSON.parse(await fs.readFile(sup+'/bonus_changes.json','utf8'));
const summary=JSON.parse(await fs.readFile(sup+'/recalculation_summary.json','utf8'));
const paid=JSON.parse(await fs.readFile(sup+'/historical_paid.json','utf8'));
const states=JSON.parse(await fs.readFile(sup+'/historical_states.json','utf8'));
const rev=sheet('历史里程修正',10,comparison.length+1);
table(rev,['月份','车牌号','业务负责人','原考核里程(km)','纯氢里程(km)','修正里程(km)','减少里程(km)','取数说明','考核区间','历史源表行'],comparison.map(x=>[x.month,x.plate,x.sales,x.original,x.hydrogen,null,null,x.note,x.intervals,x.source_rows]),{A:9,B:17,C:14,D:20,E:20,F:20,G:20,H:36,I:66,J:16},['D','E','F','G']);
rev.getRange(`F2:G${comparison.length+1}`).formulas=comparison.map((_,i)=>{let r=i+2;return[`=IF(ISNUMBER(E${r}),MIN(D${r},E${r}),D${r})`,`=D${r}-F${r}`];});
rev.freezePanes.freezeRows(1);rev.freezePanes.freezeColumns(3);rev.tables.add(`A1:J${comparison.length+1}`,true,'HydrogenRevision');
rev.getRange('L1').values=[['来源及取数口径']];rev.getRange('L2').values=[['纯氢:氢能源行驶里程9.14.rar,1月17日由补充同名文件替换。']];rev.getRange('L3').values=[['原里程:8月原表历史月份数据,已逐车逐人逐月核对历史考核源表。']];rev.getRange('L4').values=[['按原考核区间合计,同车同月同负责人先汇总后取小值。真实0按0。']];rev.getRange('L5').values=[['7—8月里程沿用原表。纯氢源数据变更后需重新运行核算。']];rev.getRange('L1:L5').format.columnWidth=88;
const delta=sheet('8月调整对照',7,70);widths(delta,{A:19,B:16,C:22,D:18,E:18,F:18,G:65},70);
write(delta,1,['8月纯氢里程修正结果']);bold(delta,1,7);
header(delta,3,['项目','原表实发(元)','修正实发(元)','差额(元)']);
let rr=4;for(const [cat,v] of Object.entries(summary.categories)){write(delta,rr,[cat,v.old,null,null]);delta.getRange(`C${rr}`).formulas=[[`=SUMIFS(${L('J')},${L('E')},A${rr})`]];delta.getRange(`D${rr}`).formulas=[[`=C${rr}-B${rr}`]];rr++;}
write(delta,14,['合计']);delta.getRange('B14:D14').formulas=[['=SUM(B4:B13)','=SUM(C4:C13)','=C14-B14']];bold(delta,14,4);delta.getRange('B4:D14').setNumberFormat(money);
write(delta,16,['1—7月已发保留,不追扣;已支付的考核月份不重复补发。']);
write(delta,17,['保留原表何文斌粤AGP5613当月奖励扣减9.68元。']);
write(delta,18,['结转取消后,按8月当月达标或累计达标条件重新判定本期奖励类别。']);
header(delta,20,['车牌号','业务负责人','发放类别','原实发(元)','新实发(元)','差额(元)','变化原因']);
changes.forEach((x,i)=>{const row=21+i;write(delta,row,[x.plate,x.sales,x.category,x.old,x.new,null,x.category==='结转'?'修正后上期可用结转不足':x.new===0?'修正后累计里程未达标,取消本次补发':'结转取消后,重新判定本期达标奖励']);delta.getRange(`F${row}`).formulas=[[`=E${row}-D${row}`]];});
delta.getRange(`D21:F${20+changes.length}`).setNumberFormat(money);grid(delta,`A20:G${20+changes.length}`);
const hist=sheet('历史已发台账',9,paid.length+16);
table(hist,['发放月','考核月','车牌号','考核负责人','实际接收人','发放类别','已发金额(元)','历史最终表','源表行'],paid.map(x=>[x.payment_month,x.assessment_month,x.plate,x.owner,x.recipient,x.category,x.amount,x.source,x.source_row]),{A:12,B:12,C:18,D:16,E:16,F:21,G:20,H:45,I:12},['G']);hist.freezePanes.freezeRows(1);
header(hist,paid.length+4,['月份','历史最终合计','逐项金额合计','取分差额']);
summary.payment_controls.forEach((x,i)=>write(hist,paid.length+5+i,[x.month,x.final_total,x.row_total,x.rounding_difference]));hist.getRange(`B${paid.length+5}:D${paid.length+11}`).setNumberFormat(money);
write(hist,paid.length+13,['历史最终合计原样保留。逐项取分与总额取分差0.01—0.03元不作追调;不影响已发资格。']);
const st=sheet('历史结转状态',10,states.length+1);
table(st,['月份','车牌号','业务负责人','累计应考核(km)','修正累计实际(km)','累计获得期数','实际已用期数','可用结转期数','本考核月已发','历史无考核结转'],states.map(x=>[x.month,x.plate,x.sales,x.cum_target,x.cum_actual,x.earned,x.used,null,x.paid?'是':'否',x.virtual?'是':'否']),{A:10,B:18,C:15,D:23,E:23,F:19,G:19,H:19,I:19,J:23},['D','E']);
st.getRange(`H2:H${states.length+1}`).formulas=states.map((_,i)=>[`=MAX(0,F${i+2}-G${i+2})`]);st.freezePanes.freezeRows(1);
previews.push(['历史里程修正','A1:H12'],['8月调整对照','A1:D18'],['8月调整对照','A20:G32'],['历史已发台账','A1:I12'],['历史结转状态','A1:J12']);
w.recalculate();
comparison.forEach((x,i)=>{if(Math.abs(rev.getRange(`F${i+2}`).values[0][0]-x.corrected)>1e-6)throw Error('Mileage min mismatch');});
if(Math.abs(delta.getRange('D14').values[0][0]-summary.difference)>1e-6)throw Error('Revision difference mismatch');
w.recalculate();
if(Math.abs(modern.getRange('M22').values[0][0]-d.stats.amount)>1e-6)throw Error('Total changed');
if(Math.abs(ledger.getRange(`G${dataEnd+1}`).values[0][0]-d.stats.gross_amount)>1e-6||Math.abs(ledger.getRange(`I${dataEnd+1}`).values[0][0]-d.stats.withheld_amount)>1e-6||Math.abs(ledger.getRange(`J${dataEnd+1}`).values[0][0]-d.stats.amount)>1e-6)throw Error('Ledger totals mismatch');
d.ledger.forEach((l,i)=>{const g=ledger.getRange(`G${i+2}`).values[0][0],ii=ledger.getRange(`I${i+2}`).values[0][0],j=ledger.getRange(`J${i+2}`).values[0][0];if(Math.abs(g-l.gross_amount)>1e-6||Math.abs(ii-l.withheld)>1e-6||Math.abs(j-l.amount)>1e-6)throw Error('Ledger row mismatch '+l.id);});
// 离职四人姓名不得出现在两版汇总与整张发放表可见文字中。
for(const nm of (departed.people||[])){for(const [sh,rng] of [['8月汇总','A1:C210'],['8月汇总(新版界面)','A1:M40'],['8月奖金发放记录',`A1:O${dataEnd+5}`]]){const vals=(w.worksheets.getItem(sh).getRange(rng).values||[]).flat().join('|');if(vals.includes(nm))throw Error(`离职姓名残留: ${nm} @ ${sh}`);}}
const probeIndex=d.ledger.findIndex(l=>l.days===l.month_days);
const probeCell=`K${probeIndex+2}`;
const probe=ledger.getRange(probeCell).values[0][0];ledger.getRange(probeCell).values=[[probe+1]];w.recalculate();if(Math.abs(modern.getRange('M22').values[0][0]-(d.stats.amount+1))>1e-6)throw Error('Formula update failed');ledger.getRange(probeCell).values=[[probe]];w.recalculate();
const errors=await w.inspect({kind:'match',searchTerm:'#REF!|#DIV/0!|#VALUE!|#NAME\\?|#N/A|#NUM!|#NULL!',options:{useRegex:true,maxResults:20},maxChars:2000});await fs.writeFile(sup+'/restyle_formula_scan.json',errors.ndjson);
for(const [i,[name,range]]of previews.entries()){const img=await w.render({sheetName:name,range,scale:1});await fs.writeFile(`${sup}/restyle_${i}.png`,new Uint8Array(await img.arrayBuffer()));}
await fs.writeFile(sup+'/restyle_controls.json',JSON.stringify({...controls,previews},null,2));
const output=await SpreadsheetFile.exportXlsx(w);await output.save(file);console.log('Saved restyled workbook; total '+d.stats.amount.toFixed(2)+'.');
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
{
"scope": "1—6月",
"group_by": [
"车辆",
"月份",
"业务负责人"
],
"mileage": "按原考核区间汇总纯氢里程,与8月原表对应月考核里程取小值",
"missing_hydrogen": "保留原考核里程,并注明无纯氢数据",
"historical_payment": "1—7月最终表作为实际已发,保留不追扣;已发月份不重复补发;扣除实际已使用结转",
"july_august": "7—8月考核里程沿用原表",
"gps_meter": "不再取数或判定测试车辆"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,51 @@
from pathlib import Path
import shutil
O=Path(__file__).resolve().parent
S=Path('/Users/shishengliang/Documents/羚牛/里程考核核算/8月/资料检查/运行支持')
s=(S/'restyle_august.mjs').read_text()
s=s.replace("const sup=root+'/资料检查/运行支持', file=root+'/8月核算结果/里程任务考核_8月核算.xlsx';",f"const sup={str(O)!r}, file=sup+'/里程任务考核_8月核算_纯氢修正版.xlsx';")
s=s.replace("d.ledger.map(()=>[0])","d.ledger.map(l=>[l.withheld])")
s=s.replace("Math.abs(ii)>1e-6","Math.abs(ii-l.withheld)>1e-6")
s=s.replace('本次无停发扣减','保留原表扣减9.68元').replace('无停发扣减','保留原表扣减9.68元')
s=s.replace('本次应发=实发','本次实发')
s=s.replace('7月复算62,616.73元与历史一致。','1—7月实际已发保留,不追扣、不重复补发。1—6月按纯氢与原考核里程取小值,无纯氢则保留原里程。')
s=s.replace("const note='';return[l.plate", "const note=l.withhold_reason||'';return[l.plate")
s=s.replace("modern.getRange('A2:H3').format=", "modern.getRange('A2').values=[['1—6月历史里程已修正,1—7月实际已发保留。修正前后差额见8月调整对照。金额按发放项取分后汇总。']];modern.getRange('A2:H3').format=")
extra=r'''
const comparison=JSON.parse(await fs.readFile(sup+'/comparison.json','utf8'));
const changes=JSON.parse(await fs.readFile(sup+'/bonus_changes.json','utf8'));
const summary=JSON.parse(await fs.readFile(sup+'/recalculation_summary.json','utf8'));
const paid=JSON.parse(await fs.readFile(sup+'/historical_paid.json','utf8'));
const states=JSON.parse(await fs.readFile(sup+'/historical_states.json','utf8'));
const rev=sheet('历史里程修正',10,comparison.length+1);
table(rev,['月份','车牌号','业务负责人','原考核里程(km)','纯氢里程(km)','修正里程(km)','减少里程(km)','取数说明','考核区间','历史源表行'],comparison.map(x=>[x.month,x.plate,x.sales,x.original,x.hydrogen,null,null,x.note,x.intervals,x.source_rows]),{A:9,B:17,C:14,D:20,E:20,F:20,G:20,H:36,I:66,J:16},['D','E','F','G']);
rev.getRange(`F2:G${comparison.length+1}`).formulas=comparison.map((_,i)=>{let r=i+2;return[`=IF(ISNUMBER(E${r}),MIN(D${r},E${r}),D${r})`,`=D${r}-F${r}`];});
rev.freezePanes.freezeRows(1);rev.freezePanes.freezeColumns(3);rev.tables.add(`A1:J${comparison.length+1}`,true,'HydrogenRevision');
rev.getRange('L1').values=[['来源及取数口径']];rev.getRange('L2').values=[['纯氢:氢能源行驶里程9.14.rar,1月17日由补充同名文件替换。']];rev.getRange('L3').values=[['原里程:8月原表历史月份数据,已逐车逐人逐月核对历史考核源表。']];rev.getRange('L4').values=[['按原考核区间合计,同车同月同负责人先汇总后取小值。真实0按0。']];rev.getRange('L5').values=[['7—8月里程沿用原表。纯氢源数据变更后需重新运行核算。']];rev.getRange('L1:L5').format.columnWidth=88;
const delta=sheet('8月调整对照',7,70);widths(delta,{A:19,B:16,C:22,D:18,E:18,F:18,G:65},70);
write(delta,1,['8月纯氢里程修正结果']);bold(delta,1,7);
header(delta,3,['项目','原表实发(元)','修正实发(元)','差额(元)']);
let rr=4;for(const [cat,v] of Object.entries(summary.categories)){write(delta,rr,[cat,v.old,null,null]);delta.getRange(`C${rr}`).formulas=[[`=SUMIFS(${L('J')},${L('E')},A${rr})`]];delta.getRange(`D${rr}`).formulas=[[`=C${rr}-B${rr}`]];rr++;}
write(delta,14,['合计']);delta.getRange('B14:D14').formulas=[['=SUM(B4:B13)','=SUM(C4:C13)','=C14-B14']];bold(delta,14,4);delta.getRange('B4:D14').setNumberFormat(money);
write(delta,16,['1—7月已发保留,不追扣;已支付的考核月份不重复补发。']);
write(delta,17,['保留原表何文斌粤AGP5613当月奖励扣减9.68元。']);
write(delta,18,['结转取消后,按8月当月达标或累计达标条件重新判定本期奖励类别。']);
header(delta,20,['车牌号','业务负责人','发放类别','原实发(元)','新实发(元)','差额(元)','变化原因']);
changes.forEach((x,i)=>{const row=21+i;write(delta,row,[x.plate,x.sales,x.category,x.old,x.new,null,x.category==='结转'?'修正后上期可用结转不足':x.new===0?'修正后累计里程未达标,取消本次补发':'结转取消后,重新判定本期达标奖励']);delta.getRange(`F${row}`).formulas=[[`=E${row}-D${row}`]];});
delta.getRange(`D21:F${20+changes.length}`).setNumberFormat(money);grid(delta,`A20:G${20+changes.length}`);
const hist=sheet('历史已发台账',9,paid.length+16);
table(hist,['发放月','考核月','车牌号','考核负责人','实际接收人','发放类别','已发金额(元)','历史最终表','源表行'],paid.map(x=>[x.payment_month,x.assessment_month,x.plate,x.owner,x.recipient,x.category,x.amount,x.source,x.source_row]),{A:12,B:12,C:18,D:16,E:16,F:21,G:20,H:45,I:12},['G']);hist.freezePanes.freezeRows(1);
header(hist,paid.length+4,['月份','历史最终合计','逐项金额合计','取分差额']);
summary.payment_controls.forEach((x,i)=>write(hist,paid.length+5+i,[x.month,x.final_total,x.row_total,x.rounding_difference]));hist.getRange(`B${paid.length+5}:D${paid.length+11}`).setNumberFormat(money);
write(hist,paid.length+13,['历史最终合计原样保留。逐项取分与总额取分差0.01—0.03元不作追调;不影响已发资格。']);
const st=sheet('历史结转状态',10,states.length+1);
table(st,['月份','车牌号','业务负责人','累计应考核(km)','修正累计实际(km)','累计获得期数','实际已用期数','可用结转期数','本考核月已发','历史无考核结转'],states.map(x=>[x.month,x.plate,x.sales,x.cum_target,x.cum_actual,x.earned,x.used,null,x.paid?'是':'否',x.virtual?'是':'否']),{A:10,B:18,C:15,D:23,E:23,F:19,G:19,H:19,I:19,J:23},['D','E']);
st.getRange(`H2:H${states.length+1}`).formulas=states.map((_,i)=>[`=MAX(0,F${i+2}-G${i+2})`]);st.freezePanes.freezeRows(1);
previews.push(['历史里程修正','A1:H12'],['8月调整对照','A1:D18'],['8月调整对照','A20:G32'],['历史已发台账','A1:I12'],['历史结转状态','A1:J12']);
w.recalculate();
comparison.forEach((x,i)=>{if(Math.abs(rev.getRange(`F${i+2}`).values[0][0]-x.corrected)>1e-6)throw Error('Mileage min mismatch');});
if(Math.abs(delta.getRange('D14').values[0][0]-summary.difference)>1e-6)throw Error('Revision difference mismatch');
'''
s=s.replace('w.recalculate();\nif(Math.abs(modern',extra+'\nw.recalculate();\nif(Math.abs(modern',1)
(O/'build_revision.mjs').write_text(s)
shutil.copy2(S/'july_style_metadata.json',O/'july_style_metadata.json')
@@ -0,0 +1,163 @@
from pathlib import Path
import sys,json,copy,re,hashlib
from collections import defaultdict,Counter
from decimal import Decimal,ROUND_HALF_UP
from datetime import date,timedelta
import openpyxl
OUT=Path(__file__).resolve().parent
REPO=OUT.parents[1]
sys.path.insert(0,str(REPO))
import calc_engine as e
SUP=Path('/Users/shishengliang/Documents/羚牛/里程考核核算/8月/资料检查/运行支持')
old=json.loads((SUP/'august_calculation.json').read_text())
source_workbook=SUP.parents[1]/'8月核算结果/里程任务考核_8月核算.xlsx'
sw=openpyxl.load_workbook(source_workbook,read_only=True,data_only=True)
srr=list(sw['8月奖金发放记录'].values)
existing_deductions={(r[0],r[1],r[4]):float(r[8] or 0) for r in srr[1:] if r[0] and r[0]!='合计' and isinstance(r[8],(int,float))}
source_actual_total=sw['8月汇总(新版界面)']['M22'].value
sw.close()
coverage=json.loads((OUT/'source_coverage.json').read_text())
hydrogen_daily={(p,d):v for p,d,v in json.loads((OUT/'hydrogen_daily.json').read_text())}
def money(x):return float(Decimal(str(x)).quantize(Decimal('.01'),rounding=ROUND_HALF_UP))
def dump(name,obj):(OUT/name).write_text(json.dumps(obj,ensure_ascii=False,indent=2,default=str))
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)}
D[8]=copy.deepcopy(old['records'])
G={m:e.grp(D[m],m) for m in D}
e.calc_jan_carryover(G[1]);all_data={}
for m in range(2,9):e.calc_month(m,G,all_data,G[m-1],G[m])
# Verify exact historical monthly baseline against the authoritative August data.
for x in old['groups']:
k=(x['plate'],x['sales'])
for m in range(1,9):
expected=[money(G[m][k]['应考核']),money(G[m][k]['实际'])] if k in G[m] else [None,None]
assert expected==x['history'][m-1],(k,m,expected,x['history'][m-1])
by=defaultdict(list)
for r in coverage['records']:by[(r['month'],r['plate'],r['sales'])].append(r)
comparison=[]
for (m,p,s),rs in sorted(by.items()):
g=G[m][(p,s)];a=money(sum(float(r['original_assessment_km'] or 0) for r in rs))
assert a==money(g['实际'])
assert all(r['hydrogen_km'] is None for r in rs) or all(r['hydrogen_km'] is not None for r in rs),('mixed coverage',m,p,s)
dates={str(date.fromisoformat(r['start'])+timedelta(days=i)) for r in rs for i in range(r['days'])}
overlap=sum(r['days'] for r in rs)-len(dates)
h=None if rs[0]['hydrogen_km'] is None else money(sum(hydrogen_daily[p,d] for d in dates))
n=a if h is None else min(a,h)
comparison.append(dict(month=m,plate=p,sales=s,original=a,hydrogen=h,corrected=n,reduction=money(a-n),overlap_days=overlap,note='无纯氢数据,保留原考核里程' if h is None else (f'考核区间重叠{overlap}天,纯氢按日期去重' if overlap else '同考核区间汇总后取小值'),intervals=';'.join(r['start']+'至'+r['end'] for r in rs),source_rows=','.join(str(r['source_row']) for r in rs)))
paid=set();carry_paid=set();paid_rows=[];payment_controls=[]
final_totals=[21212.26,21152.14,56907.10,86848.08,92912.56,93696.88,62616.73]
for m in range(1,8):
path=REPO/f'里程任务考核_{m}月核算.xlsx' if m<7 else REPO/'7月绩效重算_多源考核里程_20260817/里程任务考核_7月核算.xlsx'
w=openpyxl.load_workbook(path,read_only=True,data_only=True);rr=list(w[f'{m}月奖金发放记录'].values);w.close()
subtotal=0
for rn,row in enumerate(rr[1:],2):
r=dict(zip(rr[0],row));p=r.get('车牌号');s=r.get('业务员');cat=r.get('发放类型');amount=r.get('实发金额')
if not p or not isinstance(amount,(int,float)) or amount<=0:continue
if m==7 and (s=='钱婧' or s in {'赵连飞','伍仲文','岑彦'} and cat!='当月'):continue
bm=int(re.search(r'补发(\d+)月',cat).group(1)) if '补发' in cat else m
assert (p,s) in G[bm],('payment owner missing',m,r)
paid.add((bm,p,s))
if cat=='结转':carry_paid.add((m,p,s))
recipient={'赵连飞':'刘念念','伍仲文':'钟祥','岑彦':'钟祥'}.get(s,s) if m>=6 and cat=='当月' else s
paid_rows.append(dict(payment_month=m,assessment_month=bm,plate=p,owner=s,recipient=recipient,category=cat,amount=amount,source=path.name,source_row=rn))
subtotal+=amount
payment_controls.append(dict(month=m,final_total=final_totals[m-1],row_total=money(subtotal),rounding_difference=money(subtotal-final_totals[m-1])))
assert all(abs(x['rounding_difference'])<=.03 for x in payment_controls),payment_controls
# Control: paid-ledger locking with original mileage reproduces the existing August
# non-departed calculation. This separates mileage effects from accounting changes.
B=copy.deepcopy(G)
for m in range(1,8):
for k,g in B[m].items():
prev=B.get(m-1,{}).get(k,{})
used=prev.get('累计已发结转',0)+int((m,*k) in carry_paid)
earned=int(max(0,g['cum_a']-g['cum_t'])//g['目标km']) if g['目标km'] else 0
g['累计已发结转']=used;g['可结转']=max(0,earned-used);g[f'{m}月已发']=(m,*k) in paid
for bm in range(1,m):g.pop(f'补发{bm}月',None)
B[8]=e.grp(D[8],8);bd={};e.calc_month(8,B,bd,B[7],B[8])
bp={}
for cat,items in bd[8].items():
for item in items:
k=(item['车牌'],item['销售'])
if k[1] in old['departed']['people']:continue
value=item['额']
if B[8][k].get('虚拟'):value=next(x['reward'] for x in old['groups'] if (x['plate'],x['sales'])==k)
bp[(*k,cat)]=money(value)
assert bp=={(l['plate'],l['sales'],l['category']):l['amount'] for l in old['ledger']},'Original mileage plus locked actuals failed baseline reproduction'
# Preserve historical assessment scope and paid virtual periods. Recompute cumulative mileage,
# but never regenerate Jan-Jul payment flags from revised eligibility.
N=copy.deepcopy(G)
for x in comparison:N[x['month']][(x['plate'],x['sales'])]['实际']=x['corrected']
states=[]
for m in range(1,8):
for k,g in N[m].items():
prev=N.get(m-1,{}).get(k,{})
g['cum_t']=prev.get('cum_t',0)+g['应考核'];g['cum_a']=prev.get('cum_a',0)+g['实际']
g['cum_q']=g['cum_t']>0 and g['cum_a']>=g['cum_t']
used=prev.get('累计已发结转',0)+int((m,*k) in carry_paid)
earned=int(max(0,g['cum_a']-g['cum_t'])//g['目标km']) if g['目标km']>0 else 0
g['累计已发结转']=used;g['可结转']=max(0,earned-used)
g[f'{m}月已发']=(m,*k) in paid
for bm in range(1,m):g.pop(f'补发{bm}月',None)
states.append(dict(month=m,plate=k[0],sales=k[1],cum_target=money(g['cum_t']),cum_actual=money(g['cum_a']),earned=earned,used=used,available=g['可结转'],paid=(m,*k) in paid,virtual=bool(g.get('虚拟'))))
N[8]=e.grp(D[8],8);new_data={};e.calc_month(8,N,new_data,N[7],N[8])
# August policy reward for virtual groups comes from authoritative existing August group.
oldgroups={(x['plate'],x['sales']):x for x in old['groups']}
for k,g in N[8].items():
if g.get('虚拟'):
g['结转']=oldgroups[k]['reward']
for f in ['奖励达标','奖励结转','奖励补发']:g[f]=oldgroups[k]['reward']
for item in new_data[8]['结转']:
if (item['车牌'],item['销售'])==k:item['额']=g['结转']
ledger=[];departed=set(old['departed']['people'])
for cat in old['categories']:
for item in new_data[8].get(cat,[]):
k=(item['车牌'],item['销售']);g=N[8][k]
if k[1] in departed:continue
m=int(re.search(r'补发(\d+)月',cat).group(1)) if '补发' in cat else 8
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),31
else:base,days,den=N[m][k]['奖励补发'],N[m][k]['天数'],e.DAYS[m]
assert m==8 or (m,*k) not in paid,('duplicate backpay',m,k)
exact=base*days/den;assert abs(exact-item['额'])<1e-7
ledger.append(dict(id=len(ledger)+1,plate=k[0],sales=k[1],owner_sales=k[1],department=g['部门'],source_department=g['部门'],assessment_month=m,category=cat,base=base,days=days,month_days=den,gross_amount=money(exact),amount=money(exact),withheld=0,unrounded=exact,unrounded_net=exact,virtual=bool(g.get('虚拟')),payment_status='released',payment_status_text='正常发放',withhold_reason='',transfer=False,receiver='',transfer_from=''))
for l in ledger:
deduction=existing_deductions.get((l['plate'],l['sales'],l['category']),0)
l['withheld']=deduction;l['amount']=money(l['gross_amount']-deduction);l['unrounded_net']=l['unrounded']-deduction
if deduction:l['withhold_reason']='保留8月原表现有扣减';l['payment_status_text']='原表扣减保留'
for l in old['ledger']:
l['amount']=money(l['gross_amount']-existing_deductions.get((l['plate'],l['sales'],l['category']),0))
old['stats']['amount']=money(sum(l['amount'] for l in old['ledger']))
assert old['stats']['amount']==source_actual_total
d=copy.deepcopy(old);d['ledger']=ledger;groups=[]
for k,g in sorted(N[8].items(),key=lambda kv:(kv[0][1],kv[0][0])):
pays=[l for l in ledger if (l['plate'],l['sales'])==k];total=money(sum(l['amount'] for l in pays))
groups.append(dict(plate=k[0],sales=k[1],department=g['部门'],rows=len(g['recs']),days=g['天数'],target=money(g['应考核']),actual=money(g['实际']),qualified=g['有达标'],cum_target=money(g['cum_t']),cum_actual=money(g['cum_a']),cum_qualified=g['cum_q'],previous_carry=N[7].get(k,{}).get('可结转',0),carry_left=g['可结转'],virtual=bool(g.get('虚拟')),reward=g['奖励达标'],total=total,gross_total=total,withheld_total=0,withhold_reason=oldgroups.get(k,{}).get('withhold_reason',''),history=[[money(N[m][k]['应考核']),money(N[m][k]['实际'])] if k in N[m] else [None,None] for m in range(1,9)]))
d['groups']=groups
for g in groups:
pays=[l for l in ledger if (l['plate'],l['sales'])==(g['plate'],g['sales'])]
g['gross_total']=money(sum(l['gross_amount'] for l in pays));g['withheld_total']=money(sum(l['withheld'] for l in pays))
for r in d['person_summary']:
for i,cat in enumerate(d['categories'],2):r[i]=money(sum(l['amount'] for l in ledger if l['sales']==r[0] and l['category']==cat))
r[-1]=money(sum(r[2:-1]))
for r in d['person_audit']:
if r[0] not in departed:r[2]=r[4]=money(sum(l['amount'] for l in ledger if l['sales']==r[0]))
total=money(sum(l['amount'] for l in ledger));unrounded=money(sum(l['unrounded'] for l in ledger))
d['stats'].update(amount=total,gross_amount=total,withheld_amount=0,unrounded_total=unrounded,unrounded_net_total=unrounded,rounding_difference=money(total-unrounded),net_rounding_difference=money(total-unrounded),display_groups=sum(g['sales'] not in departed for g in groups),virtual_carry_records=sum(g['virtual'] for g in groups))
d['withholding']['net_amount']=total
gross=money(sum(l['gross_amount'] for l in ledger));withheld=money(sum(l['withheld'] for l in ledger))
d['stats'].update(gross_amount=gross,withheld_amount=withheld,withheld_records=sum(l['withheld']>0 for l in ledger),unrounded_net_total=money(sum(l['unrounded_net'] for l in ledger)))
d['stats']['rounding_difference']=money(gross-unrounded)
d['historical_revision']=dict(paid_locked=True,old_august_amount=old['stats']['amount'],refresh='纯氢来源或取数口径变更后需重新运行核算;表内金额及汇总公式自动计算。')
changes=[]
oldpay={(l['plate'],l['sales'],l['category']):l['amount'] for l in old['ledger']};newpay={(l['plate'],l['sales'],l['category']):l['amount'] for l in ledger}
for k in sorted(oldpay.keys()|newpay.keys()):
a,b=oldpay.get(k,0),newpay.get(k,0)
if a!=b:changes.append(dict(plate=k[0],sales=k[1],category=k[2],old=a,new=b,difference=money(b-a)))
summary=dict(old_amount=old['stats']['amount'],new_amount=total,difference=money(total-old['stats']['amount']),comparison_groups=len(comparison),reduced_groups=sum(x['reduction']>0 for x in comparison),missing_groups=sum(x['hydrogen'] is None for x in comparison),reduced_km=money(sum(x['reduction'] for x in comparison)),categories={cat:dict(old=money(sum(l['amount'] for l in old['ledger'] if l['category']==cat)),new=money(sum(l['amount'] for l in ledger if l['category']==cat))) for cat in d['categories']},payment_controls=payment_controls,changes=len(changes))
assert total==money(sum(r[-1] for r in d['person_summary']))
assert len(newpay)==len(ledger)
assert money(sum(x['difference'] for x in changes))==summary['difference']
dump('august_calculation.json',d);dump('comparison.json',comparison);dump('historical_paid.json',paid_rows);dump('historical_states.json',states);dump('bonus_changes.json',changes);dump('recalculation_summary.json',summary)
dump('confirmed_rules.json',dict(scope='1—6月',group_by=['车辆','月份','业务负责人'],mileage='按原考核区间汇总纯氢里程,与8月原表对应月考核里程取小值',missing_hydrogen='保留原考核里程,并注明无纯氢数据',historical_payment='1—7月最终表作为实际已发,保留不追扣;已发月份不重复补发;扣除实际已使用结转',july_august='7—8月考核里程沿用原表',gps_meter='不再取数或判定测试车辆'))
print(json.dumps(summary,ensure_ascii=False,indent=2))
@@ -0,0 +1,96 @@
{
"old_amount": 50997.73,
"new_amount": 45065.47,
"difference": -5932.26,
"comparison_groups": 2308,
"reduced_groups": 1594,
"missing_groups": 302,
"reduced_km": 450477.23,
"categories": {
"当月": {
"old": 26723.21,
"new": 27519.34
},
"结转": {
"old": 12990.0,
"new": 11460.0
},
"补发1月": {
"old": 0.0,
"new": 0.0
},
"补发2月": {
"old": 0.0,
"new": 0.0
},
"补发3月": {
"old": 0.0,
"new": 0.0
},
"补发4月": {
"old": 0.0,
"new": 0.0
},
"补发5月": {
"old": 0.0,
"new": 0.0
},
"补发6月": {
"old": 1150.0,
"new": 150.0
},
"补发7月": {
"old": 827.42,
"new": 150.0
},
"累计补发8月": {
"old": 9307.1,
"new": 5786.13
}
},
"payment_controls": [
{
"month": 1,
"final_total": 21212.26,
"row_total": 21212.27,
"rounding_difference": 0.01
},
{
"month": 2,
"final_total": 21152.14,
"row_total": 21152.15,
"rounding_difference": 0.01
},
{
"month": 3,
"final_total": 56907.1,
"row_total": 56907.08,
"rounding_difference": -0.02
},
{
"month": 4,
"final_total": 86848.08,
"row_total": 86848.09,
"rounding_difference": 0.01
},
{
"month": 5,
"final_total": 92912.56,
"row_total": 92912.54,
"rounding_difference": -0.02
},
{
"month": 6,
"final_total": 93696.88,
"row_total": 93696.88,
"rounding_difference": -0.0
},
{
"month": 7,
"final_total": 62616.73,
"row_total": 62616.76,
"rounding_difference": 0.03
}
],
"changes": 22
}
@@ -0,0 +1,168 @@
{
"people": {
"何文斌": {
"sheet": "一部-何文斌",
"total": "C20",
"groups": 3
},
"刘念念": {
"sheet": "二部-刘念念",
"total": "C235",
"groups": 46
},
"吴纬涛": {
"sheet": "三部-吴纬涛",
"total": "C360",
"groups": 71
},
"尚建华": {
"sheet": "二部-尚建华",
"total": "C270",
"groups": 53
},
"秦挺": {
"sheet": "五部-秦挺",
"total": "C145",
"groups": 28
},
"童军林": {
"sheet": "综合部-童军林",
"total": "C10",
"groups": 1
},
"董剑煜": {
"sheet": "二部-董剑煜",
"total": "C125",
"groups": 24
},
"谯云": {
"sheet": "二部-谯云",
"total": "C95",
"groups": 18
},
"赵伟军": {
"sheet": "综合部-赵伟军",
"total": "C25",
"groups": 4
},
"金可鹏": {
"sheet": "三部-金可鹏",
"total": "C235",
"groups": 46
},
"钟祥": {
"sheet": "跨部门-钟祥",
"total": "C322",
"groups": 63
},
"陈高伟": {
"sheet": "一部-陈高伟",
"total": "C20",
"groups": 3
}
},
"ledgerRows": {},
"oldTotal": "B94",
"previews": [
[
"考核奖励规则",
"A1:F18"
],
[
"里程明细8月",
"A1:H12"
],
[
"8月奖金发放记录",
"A1:O12"
],
[
"8月计算过程",
"A1:M12"
],
[
"车辆考核追踪",
"A1:G12"
],
[
"8月汇总",
"A1:C19"
],
[
"8月汇总",
"A88:C94"
],
[
"8月汇总(新版界面)",
"A1:M36"
],
[
"一部-何文斌",
"A1:M13"
],
[
"二部-刘念念",
"A1:M13"
],
[
"三部-吴纬涛",
"A1:M13"
],
[
"二部-尚建华",
"A1:M13"
],
[
"五部-秦挺",
"A1:M13"
],
[
"综合部-童军林",
"A1:M10"
],
[
"二部-董剑煜",
"A1:M13"
],
[
"二部-谯云",
"A1:M13"
],
[
"综合部-赵伟军",
"A1:M13"
],
[
"三部-金可鹏",
"A1:M13"
],
[
"跨部门-钟祥",
"A1:M13"
],
[
"一部-陈高伟",
"A1:M13"
],
[
"历史里程修正",
"A1:H12"
],
[
"8月调整对照",
"A1:D18"
],
[
"8月调整对照",
"A20:G32"
],
[
"历史已发台账",
"A1:I12"
],
[
"历史结转状态",
"A1:J12"
]
]
}
@@ -0,0 +1 @@
{"kind":"notice","message":"Cell search matched 0 entries."}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,12 @@
{
"total": 45065.47,
"difference": -5932.26,
"sheets": 23,
"ledger_items": 170,
"missing_hydrogen_groups": 302,
"overlap_days_deduplicated": 14,
"formula_errors": [],
"august_detail_unchanged": true,
"historical_paid_no_duplicate": true,
"monthly_baseline_reproduced": true
}
@@ -0,0 +1,46 @@
from pathlib import Path
import json,openpyxl
from collections import Counter
P=Path(__file__).resolve().parent
d=json.loads((P/'august_calculation.json').read_text());s=json.loads((P/'recalculation_summary.json').read_text())
comp=json.loads((P/'comparison.json').read_text());paid=json.loads((P/'historical_paid.json').read_text())
w=openpyxl.load_workbook(P/'里程任务考核_8月核算_纯氢修正版.xlsx',data_only=True)
orig=openpyxl.load_workbook('/Users/shishengliang/Documents/羚牛/里程考核核算/8月/8月核算结果/里程任务考核_8月核算.xlsx',data_only=True)
assert w.sheetnames[:len(orig.sheetnames)]==orig.sheetnames
def same(a,b):
if a in (None,'') and b in (None,''):return True
if isinstance(a,(int,float)) and isinstance(b,(int,float)):return abs(a-b)<1e-8
return a==b
for row in w['里程明细8月']:
for c in row:assert same(c.value,orig['里程明细8月'][c.coordinate].value),('August assessment records changed',c.coordinate,c.value,orig['里程明细8月'][c.coordinate].value)
assert abs(w['8月汇总(新版界面)']['M22'].value-s['new_amount'])<1e-7
rr=list(w['8月奖金发放记录'].values)
for row,l in zip(rr[1:],d['ledger']):
assert (row[0],row[1],row[4])==(l['plate'],l['sales'],l['category'])
assert row[6]==l['gross_amount'] and row[8]==l['withheld'] and row[9]==l['amount']
assert abs(w['8月调整对照']['D14'].value-s['difference'])<1e-7
for row,x in zip(list(w['历史里程修正'].values)[1:],comp):
assert row[5]==x['corrected']
assert x['corrected']<=x['original']
if x['hydrogen'] is None:assert row[4] is None and row[5]==x['original']
if x['hydrogen']==0:assert row[5]==min(0,x['original'])
for l in d['ledger']:
if l['assessment_month']<8:
assert not any(r['plate']==l['plate'] and r['owner']==l['sales'] and r['assessment_month']==l['assessment_month'] for r in paid)
errors=[]
for sh in w:
for row in sh:
for c in row:
if c.data_type=='e':errors.append((sh.title,c.coordinate,c.value))
assert not errors,errors[:10]
# Independent cumulative check: same contiguous monthly chain as the established engine;
# every historical correction reduces only the matching owner and interval.
for g in d['groups']:
target=actual=0
for t,a in g['history']:
if t is None:target=actual=0
else:target+=t;actual+=a
assert abs(g['cum_target']-target)<.03 and abs(g['cum_actual']-actual)<.03,(g['plate'],g['sales'])
result=dict(total=s['new_amount'],difference=s['difference'],sheets=len(w.sheetnames),ledger_items=len(d['ledger']),missing_hydrogen_groups=s['missing_groups'],overlap_days_deduplicated=sum(x['overlap_days'] for x in comp),formula_errors=errors,august_detail_unchanged=True,historical_paid_no_duplicate=True,monthly_baseline_reproduced=True)
(P/'verification.json').write_text(json.dumps(result,ensure_ascii=False,indent=2))
print(json.dumps(result,ensure_ascii=False,indent=2))
@@ -0,0 +1,43 @@
from pathlib import Path
import json
P=Path(__file__).resolve().parent
s=json.loads((P/'recalculation_summary.json').read_text())
text=f'''# 8月纯氢里程修正
已完成。8月原表实发{s['old_amount']:,.2f}元,修正后{s['new_amount']:,.2f}元,减少{-s['difference']:,.2f}元。原正式文件未覆盖,新版为本目录“里程任务考核_8月核算_纯氢修正版.xlsx”。
## 已执行口径
- 仅修正1—6月历史里程,按车辆、月份、业务负责人匹配8月原表历史数据。
- 纯氢按原考核区间日期集合汇总,同组重叠日期去重,再与原月考核合计取小值。14个重叠车人日期已去重。
- 无纯氢数据的302组保留原里程,并标注“无纯氢数据”;真实0参与取小值。
- 1—7月最终核算表视为实际已发,不追扣、不重复补发;实际已消耗结转从后续余额中扣除。
- 7—8月考核里程沿用原表。本次不再获取GPS或仪表、不再做测试车辆判定。
- 保留当前Excel何文斌粤AGP5613的9.68元扣减。旧JSON金额51,007.41元未反映此扣减,本次对照以当前Excel50,997.73元为准。
- 四名离职人员8月全部不发、不转交,沿用原表。
## 结果在8月表中的体现
- 原有计算过程、车辆追踪、个人对账单及两版汇总均已更新。
- “历史里程修正”:原里程、纯氢里程、取小值结果、减少公里及缺失说明。
- “8月调整对照”:分类前后金额与22条变化项,其中包括同车奖励类别改列。
- “历史已发台账”“历史结转状态”:已发资格和已用结转的追溯依据。
## 核对与数据边界
- 原里程结合实际已发台账可逐项复现旧8月金额,确保本次差异来自里程修正。
- 已核对导出后汇总、170条发放记录、无重复历史补发、缺失与0的区别,公式错误0处。
- 8月考核明细保持原样,纯氢1月17日已使用项目目录补充文件。
- 原8月仍有121个车辆日来源缺口,此为原表已有事项,本次未替换7—8月数据。如补齐,应另行重算受影响的8月里程和奖金。
- 原历史表5月粤AGR3208(尚建华)里程为-0.1公里,纯氢0公里;按用户取小值规则保留-0.1,未擅自改写原数。
- 历史明细逐项取分与最终合计取分差0.01—0.03元,最终已发合计原样保留,不作追调。
- 本文件为本次核算结果。更换纯氢源数据后需重新运行核算流程;表内金额及汇总公式可自动计算,但修改对照页不会自动重新判定全部奖金资格。
按本次已确认规则,不再缺少阻碍1—6月修正的数据。
'''
(P/'资料核对与待补项.md').write_text(text)
# Remove superseded GPS/meter claims from the reusable source auditor.
a=P/'audit_sources.py';t=a.read_text();start=t.index("lines=['# 历史纯氢里程修正")
t=t[:start]+"print(json.dumps({'jan17_status':jan_status,'summary':summary},ensure_ascii=False,indent=2))\n"
t=t.replace(" x['test_decision'] = '1—3月不做测试判定' if m<4 else '待同区间GPS,仪表合计已确认'"," x['test_decision'] = '不再采用测试车辆判定;按同组月合计取小值'")
a.write_text(t)
@@ -0,0 +1,32 @@
# 8月纯氢里程修正
已完成。8月原表实发50,997.73元,修正后45,065.47元,减少5,932.26元。原正式文件未覆盖,新版为本目录“里程任务考核_8月核算_纯氢修正版.xlsx”。
## 已执行口径
- 仅修正1—6月历史里程,按车辆、月份、业务负责人匹配8月原表历史数据。
- 纯氢按原考核区间日期集合汇总,同组重叠日期去重,再与原月考核合计取小值。14个重叠车人日期已去重。
- 无纯氢数据的302组保留原里程,并标注“无纯氢数据”;真实0参与取小值。
- 1—7月最终核算表视为实际已发,不追扣、不重复补发;实际已消耗结转从后续余额中扣除。
- 7—8月考核里程沿用原表。本次不再获取GPS或仪表、不再做测试车辆判定。
- 保留当前Excel何文斌粤AGP5613的9.68元扣减。旧JSON金额51,007.41元未反映此扣减,本次对照以当前Excel50,997.73元为准。
- 四名离职人员8月全部不发、不转交,沿用原表。
## 结果在8月表中的体现
- 原有计算过程、车辆追踪、个人对账单及两版汇总均已更新。
- “历史里程修正”:原里程、纯氢里程、取小值结果、减少公里及缺失说明。
- “8月调整对照”:分类前后金额与22条变化项,其中包括同车奖励类别改列。
- “历史已发台账”“历史结转状态”:已发资格和已用结转的追溯依据。
## 核对与数据边界
- 原里程结合实际已发台账可逐项复现旧8月金额,确保本次差异来自里程修正。
- 已核对导出后汇总、170条发放记录、无重复历史补发、缺失与0的区别,公式错误0处。
- 8月考核明细保持原样,纯氢1月17日已使用项目目录补充文件。
- 原8月仍有121个车辆日来源缺口,此为原表已有事项,本次未替换7—8月数据。如补齐,应另行重算受影响的8月里程和奖金。
- 原历史表5月粤AGR3208(尚建华)里程为-0.1公里,纯氢0公里;按用户取小值规则保留-0.1,未擅自改写原数。
- 历史明细逐项取分与最终合计取分差0.01—0.03元,最终已发合计原样保留,不作追调。
- 本文件为本次核算结果。更换纯氢源数据后需重新运行核算流程;表内金额及汇总公式可自动计算,但修改对照页不会自动重新判定全部奖金资格。
按本次已确认规则,不再缺少阻碍1—6月修正的数据。
@@ -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
}
}
@@ -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.')