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,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))