#!/usr/bin/env python3
"""Independent original-table DTO and native/raw write verifier; no network or DB writes."""
import base64, copy, hashlib, json, math, re
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo

HERE = Path(__file__).resolve().parent
PLAN = json.loads((HERE/'log-http-plan.json').read_text())
COUNT = 0
def check(ok, why):
    global COUNT
    COUNT += 1
    if not ok: raise AssertionError(why)
def load(lane, name): return json.loads((HERE/'private'/lane/name).read_text())
def exact(actual, expected, where=''):
    check(type(actual) is type(expected), where+' type')
    if isinstance(expected, dict):
        check(set(actual)==set(expected), where+' keys')
        for k,v in expected.items(): exact(actual[k],v,where+'/'+str(k))
    elif isinstance(expected,list):
        check(len(actual)==len(expected),where+' length')
        for i,v in enumerate(expected): exact(actual[i],v,where+'/'+str(i))
    else: check(actual==expected,where+' value')
def number(v):
    v=float(v)
    return int(v) if v.is_integer() else v
def rnd(v,d=0): return number(math.floor(v*10**d+0.5)/10**d)
def response(d,msg='获取信息成功',nested=False):
    r={'code':0,'message':msg,'data':d}
    return response(r) if nested else r

def oracle(s):
    # All arithmetic comes from complete own source rows, never from HTTP output.
    st=s['staff_full'][0]; di=s['dishes'][0]; rec=s['recommend'][0]; food=s['food'][0]; recipe=s['dish'][0]
    u=st['uuid']; name=st['name']; nm=[name]; day='2026-10-08'
    meals=sorted(s['menu'],key=lambda r:int(r['meal_times']))
    powers=[number(float(di['energy'])*float(m['number'])) for m in meals]
    energy=sum(powers); carb=sum(float(di['carbohydrate'])*float(m['number']) for m in meals); carb=number(carb)
    total=number(rec['total_power']); recommend=85 if total<=1000 else None
    check(recommend==85 and powers==[100,200,300], 'source prerequisites')
    weight=number(sum(float(m['weight'])*float(m['number'])*float(recipe['food_weight'])/float(di['raw_flag_weight'])*float(food['edible_part'])/100/(float(m['weight'])*float(m['number']))*(float(di['weight'])*float(m['number'])) for m in meals))
    protein=number(sum(float(recipe['food_weight'])/float(di['raw_flag_weight'])*float(m['weight'])*float(food['edible_part'])/100*float(food['protein'])/100*float(m['number']) for m in meals))
    fiber=number(sum(float(di['dietary_fiber'])*float(m['number']) for m in meals))
    check(weight==600 and protein==60 and fiber==30, 'complete fixture independent derived values')
    small={'name':name,'head_img':'','uuid':u}; out={}
    d={'real':[energy],'tui':[total],'czd':[100],'real_avg':energy,'czd_avg':100,'tui_avg':total,'que_date_null_num':0,'que_date_lack_num':1,'que_date':[[]],'plus_110_day':[],'plus_120_day':[],'few_80_day':[],'few_90_day':[],'plus_110_day_str_uuid':'','plus_120_day_str_uuid':'','few_80_day_str_uuid':'','few_90_day_str_uuid':'','x':nm}
    out['get_day_teampower']=response(d)
    d=copy.deepcopy(d)
    for k in ['plus_120_day','few_80_day','plus_120_day_str_uuid','few_80_day_str_uuid']: del d[k]
    d.update(real=[carb],tui=[number(rec['carbohydrate_weight'])],real_avg=carb,tui_avg=number(rec['carbohydrate_weight']),que_date_lack_num=0)
    out['get_day_teamnutrient']=response(d)
    va=number(sum(float(di['va'])*float(m['number']) for m in meals)); va_tui=1500 # original other_recommend[18].va[sex=1]
    d={'real':[va],'czd':[rnd(va/va_tui*100,2)],'real_avg':va,'czd_avg':rnd(va/va_tui*100,2),'que_date_null_num':0,'que_date_lack_num':1,'que_date':[[]],'few_90_day':[{'name':name,'img':'','staff_uuid':u}],'few_90_day_str_uuid':u,'x':nm,'tui_avg':va_tui}
    out['get_day_team_microelement']=response(d)
    d={'rational_avg':100,'nimiety_avg':0,'lack_avg':0,'x':nm,'ratio_rational_data':[100],'ratio_nimiety_data':[0],'ratio_lack_data':[0],'heli_80_data':[small],'heli_60_80_data':[],'heli_60_data':[],'heli_80_data_uuid_arr':u,'heli_60_80_data_uuid_arr':'','heli_60_data_uuid_arr':'','skipping_meals':[['未就餐1天']]}
    out['get_stage_teampower']=response(d);out['get_stage_team_nutrient']=response(copy.deepcopy(d))
    d=copy.deepcopy(d);del d['nimiety_avg'];del d['ratio_nimiety_data'];d.update(rational_avg=0,lack_avg=100,ratio_rational_data=[0],ratio_lack_data=[100],heli_80_data=[],heli_60_data=[small],heli_80_data_uuid_arr='',heli_60_data_uuid_arr=u)
    out['get_stage_team_microelement']=response(d)
    out['get_stage_team_microelement_popout']=response({'return_data':[{'name':name,'head_img':'','avg':va,'heli_day':0,'heli_day_zhanbi':0}],'title':'相对合理人数','color':'#91CEA1'})
    base={'uuid':u,'name':name,'head_img':''}
    person={**base,'shipower':powers[0],'ratio':100,'chazhi':0,'total_power':number(rec['morning_power']),'menu_date':day}
    modal={k:{'yanse':c,'title':t,'msg':[]} for k,c,t in [('piangao','#f29197','摄入偏高人数'),('guogao','#f29197','摄入过高人数'),('piandi','#e9c738','摄入偏低人数'),('guodi','#e9c738','摄入过低人数')]}
    out['TeamThreeMeals']=response([[person],{'sj_sum':powers[0],'tj_sum':powers[0],'z_ratio':100},modal,nm,[100],[powers[0]],[powers[0]],0],nested=True)
    ratio=rnd(weight/recommend*100,2);person={**base,'weight':weight,'chazhi':abs(weight-recommend),'chongzu':ratio,'tuijian':recommend,'meal_times':[1,2,3],'que':'','menu_date':day,'recommend_num':recommend}
    out['TeamDietaryPattern']=response([[person],{'sj_sum':weight,'tj_sum':recommend,'cz_sum':ratio},{'yanse':'#F29197','data':[],'title':'需要增加谷类人数'},[weight],nm,0,[rnd(weight/recommend*100)],[recommend],1],nested=True)
    p={'staff_uuid':u,'name':name,'head_img':'','menu_date':day,'yiban_protein':protein,'yiban_ratio':100,'youzhi_protein':0,'youzhi_ratio':0,'zong_protein':protein,'yingyangpin':0,'youzhi_num':0,'dongwu_num':None,'dongwu_ratio':0,'doulei_num':None,'doulei_ratio':0,'yingyang_num':0,'yingyang_ratio':0,'gulei_num':protein,'gulei_ratio':0,'qita_num':0,'qita_ratio':0}
    modal={'staff_uuid':u,'name':name,'head_img':'','youzhi_num':0,'youzhi_ratio':0,'dongwu_num':0,'dongwu_ratio':0,'doulei_num':0,'doulei_ratio':0,'yingyang_num':0,'yingyang_ratio':0,'yiban_num':protein,'yiban_ratio':100,'gulei_num':protein,'gulei_ratio':100,'qita_num':0,'qita_ratio':0}
    out['TeamProtein']=response([[p],1,1,[modal]])
    out['TeamDf']=response([[{**base,'dietary_fiber':fiber,'chazhi':abs(fiber-25),'que':'','ratio':rnd(fiber/25*100,2),'meal_times':[1,2,3]}],{'sj_sum':fiber,'z_ratio':rnd(fiber/25*100,2)},[],nm,[fiber],[rnd(fiber/25*100,2)],0],nested=True)
    p={'uuid':u,'power_sum':powers[0],'power_ratio':100,'day':1,'heli_day':1,'heli_ratio':100,'guoduo_day':0,'guoduo_ratio':0,'buzu_day':0,'buzu_ratio':0,'name':name,'head_img':''}
    modal={'heli':{'yanse':'#89D363','data':[p],'title':'相对合理人数'},'xuyao':{'yanse':'#2493e7','data':[],'title':'需要改善人数'},'zhongdian':{'yanse':'#E9C738','data':[],'title':'重点关注人数'}}
    out['StageTeamThreeMeals']=response([[p],modal,{'heli':100,'guoduo':0,'buzu':0},[100],[0],[0],nm],nested=True)
    p={'staff_uuid':u,'name':name,'head_img':'','menu_date':day,'youzhi_protein':None,'youzhi_ratio':0,'zong_protein':protein,'yingyangpin':0,'day':1,'hl_day':0,'hl_ratio':0,'bz_day':1,'bz_ratio':100}
    out['StageTeamProtein']=response([[p],{'num':0,'list':[]},{'num':0,'list':[]},{'num':1,'list':[{'name':name,'head_img':'','hl_day':0,'hl_ratio':0}]},{'cz':0,'bz':100}])
    p={'uuid':u,'shiji':weight,'chazhi':abs(weight-recommend),'chongzudu':rnd(weight/recommend*100),'que_zao':0,'que_wu':0,'que_wan':0,'que_all':1,'day':1,'name':name,'head_img':'','minute_sort':None,'tuijian':recommend}
    out['StageDietaryPattern']=response([[p],[{'yanse':'#F29197','data':[],'title':'需增加谷类摄入量人数'}],{'shiji':weight,'tuijian':recommend},[weight],nm,[rnd(weight/recommend*100)]],nested=True)
    table={'power_status':'合理','weight_status':'保持','gusuan':0,'tui_data':total,'shiji_data':energy,'shanshi_shiji':energy,'ying':0,'cha':energy-total,'chazhibi':0,'bm':0}
    out['Onepower']=response({'x_data':[0,total,energy,energy,0,0,0,0],'table_data':table},'请求成功')
    nutrient_keys=['carbohydrate_ratio','protein_ratio','fat_datio'];sum_keys=['sum_carbohydrate','sum_protein','sum_fat'];actual=[carb,protein,number(sum(float(di['fat'])*float(m['number']) for m in meals))];full=dict(zip(nutrient_keys,actual))
    quan={'ratio':dict.fromkeys(nutrient_keys,100),'ying_radio':dict.fromkeys(nutrient_keys,0),'quan_ratio_no_plus':dict.fromkeys(nutrient_keys,100),'actual':full,'recommend':dict(full),'status':dict.fromkeys(nutrient_keys,'合理'),'color':dict.fromkeys(nutrient_keys,'#89D363')}
    every={k:{} for k in ['ratio','actual','recommend','status','color']}
    for i,m in enumerate(meals):
        key=str(i+1);part=[.3,.4,.3][i];a=[number(float(di[f])*float(m['number'])) for f in ['carbohydrate','protein','fat']];r=[rnd(v*part,2) for v in actual];rat=[rnd(rnd(v/t,2)*100) for v,t in zip(a,r)];status='过低' if rat[0]<70 else '过高' if rat[0]>130 else '合理';color={'过低':'#E9C738','过高':'#F29197','合理':'#89D363'}[status]
        every['ratio'][key]=dict(zip(sum_keys,rat));every['actual'][key]=dict(zip(sum_keys,a));every['recommend'][key]=dict(zip(sum_keys,r));every['status'][key]=dict.fromkeys(sum_keys,status);every['color'][key]=dict.fromkeys(sum_keys,color)
    out['Onenutrient']=response({'quan':quan,'every':every},'请求成功')
    out['Morepower']=response({'avg':{'shiji_avg':energy,'tui_avg':total//2,'czd_avg':100},'table':{'heli_day':1,'plus_110_day':0,'plus_120_day':0,'few_80_day':0,'few_90_day':0,'plus_day':0,'few_day':0,'heli_zhanbi':100,'plus_zhanbi':0,'few_zhanbi':0},'map':{'czd_data':[100,0],'shiji_data':[energy,0],'tui_data':[total,0]},'que_date':[[],['未就餐']],'x':[day,'2026-10-09'],'que_date_lack_num':1,'que_date_null_num':1})
    d=dict(zip(['zao','wu','wan'],powers));d.update(morning_ratio=100,noon_ratio=100,night_ratio=100);d.update({f: number(rec[f]) for f in ['morning_power','noon_power','night_power']});d.update(staff_uuid=u,zao_jiantou=1,wu_jiantou=1,wan_jiantou=1);out['DietaryPattern']=response(d)
    d={f:number(rec[f]) for f in ['total_power','recommend_power','morning_power','noon_power','night_power']};d['all_power']=d['total_power'];arr=[d]
    for m,power in zip(meals,powers): arr.append({'sum_power':power,'data':[{'meal_times':int(m['meal_times']),'power':power,'staff_uuid':u,'number':number(m['number']),'name':di['name'],'file_path':'','total_weight':number(float(di['weight'])*float(m['number']))}]})
    arr.extend([dict(zip(['morning_power','noon_power','night_power','all_power'],powers+[energy])),dict.fromkeys(['morning_jiantou','noon_jiantou','night_jiantou'],2)]);out['FoodRecord']=response(arr)
    p={'uuid':u,'que_zao':0,'que_wu':0,'que_wan':0,'que_all':1,'day':1}
    for k in ['gulei','jingzhiguwu','quanguwu','zadou','shulei','shucai_zong','shense','qianse','jungu','haizao','shuiguo','xuqinshui','qinlei','zhu','niu','yang','shui','dan','dounai','jianguo']: p[k]=100 if k in ['gulei','jingzhiguwu'] else 0
    p.update(name=name,head_img='',meal_times=3)
    out['StageIntakeFrequencies']=response([[p],{'gulei':{'title':'需增加谷类摄入频率','yanse':'#F29197','renshu':0}},{'shiji':100,'tuijian':100},nm,[100]],nested=True)
    return out

def run():
    result={'HTTP':0,'log_rows':{},'full_DTO':{},'preserved_initial_red':{},'sentinel_deltas':{},'fixture_CAS':{},'negative_cases':0,'assertions':0}
    expected_by_lane={}
    for lane in ['B','C']:
        s=load(lane,'batch-before-complete.json');expected=oracle(s);expected_by_lane[lane]=expected
        initial=load(lane,'batch-before20.json');completed=copy.deepcopy(initial)
        completed['dishes'][0]['ssb']=s['dishes'][0]['ssb'];completed['dishes'][0]['raw_flag_weight']=s['dishes'][0]['raw_flag_weight']
        before_cmp=copy.deepcopy(s);del before_cmp['sentinel'];del completed['sentinel'];exact(before_cmp,completed,lane+' only two authorized fixture fields')
        journal=load(lane,'fixture-complete-retry2-journal.json' if lane=='B' else 'fixture-complete-journal.json');check(journal['committed'] and len(journal['events'])==2,'two committed CAS')
        for i,e in enumerate(journal['events']):
            field=['ssb','raw_flag_weight'][i];value=[1,'100.00'][i];sql='UPDATE ydy_dishes SET '+field+'=?'+(', update_time=update_time' if lane=='B' else '')+' WHERE uuid=? AND create_by=? AND '+field+'=?'
            check(e['queryString']==sql and e['binds']==[value,'C8EAA110-0002-4000-8000-202610110002','CDXNR11A',0] and e['success'] and e['rowCount']==1 and e['errorInfo'][0]=='00000','exact approved CAS SQL/binds')
        if lane=='B':
            failed=load(lane,'fixture-complete-journal.json');check(failed['committed'] is False and len(failed['events'])==1 and failed['events'][0]['rowCount']==1,'original ONUPDATE protected rollback')
            exact(journal['before'],failed['before'],'retry original full source after rollback')
        result['fixture_CAS'][lane]={'committed_UPDATE':2,'rolled_back_UPDATE':1 if lane=='B' else 0,'other_full_columns_unchanged':True}
        attempts=[]
        for f in sorted((HERE/'private'/lane).glob('http-attempts-group*.json')): attempts+=json.loads(f.read_text())
        check(len(attempts)==25,lane+' HTTP25');result['HTTP']+=len(attempts)
        wires={};prior_logs=[];all_new=[]
        for attempt in attempts:
            w=load(lane,'wire-'+attempt['label']+'.json');wires[w['label']]=w
            check(attempt['complete'] and base64.b64decode(attempt['body_base64']).decode()==w['body'], 'wire body exact')
            check(hashlib.sha256(w['body'].encode()).hexdigest()==w['body_sha256'],'body SHA')
            expected_status=404 if w['label']=='Onenutrient-positive' or (lane=='B' and w['label']=='StageTeamProtein-positive') else 200
            check(w['status']==expected_status and w['source_before']==w['source_after'] and w['source_unchanged'],'own source protected '+lane+'/'+w['label'])
            check(w['logs_before']==prior_logs and w['old_logs_unchanged'],'previous logs protected')
            action=w['path'].split('/')[-1];case=next(c for c in PLAN['cases'] if c['path']==w['path']);new=w['new_logs'];expected_count=0 if action=='Onenutrient' and '-positive' in w['label'] else case['log_rows']
            check(len(new)==expected_count,'actual exact log count')
            native=[e for e in w['native'] if e['layer']=='actual PDOStatement.execute' and re.match(r'^INSERT\b',e['queryString'])]
            check(len(native)==(1 if new else 0),'one original insertAll execute')
            if new: check(native[0]['success'] and native[0]['rowCount']==len(new) and native[0]['bound']==[] and native[0]['execute_params'] in [None,[]],'native literal SQL execution')
            check(len(w['parsed_native_log_rows'])==len(new),'parsed exact rows')
            if native:
                sql=native[0]['queryString'];fields=PLAN['log_SQL_contract']['insert_fields_order'];prefix='INSERT INTO `ydy_initial_menu_log` ('+' , '.join('`'+k+'`' for k in fields)+') VALUES '
                check(sql.startswith(prefix),'independent SQL prefix/11 ordered columns')
                token=r"'[^'\\]*'|-?[0-9]+";row_re=re.compile(r'\(\s*((?:'+token+r')(?:\s*,\s*(?:'+token+r')){10})\s*\)')
                tail=sql[len(prefix):].strip();raw_rows=[];offset=0
                while offset<len(tail):
                    m=row_re.match(tail,offset);check(m is not None,'independent SQL tuple grammar')
                    values=[x[1:-1] if x.startswith("'") else int(x) for x in re.findall(token,m.group(1))];raw_rows.append(dict(zip(fields,values)));offset=m.end()
                    if offset<len(tail):
                        sep=re.compile(r'\s*,\s*').match(tail,offset);check(sep is not None,'independent SQL separator');offset=sep.end();check(offset<len(tail),'no trailing separator')
                exact(raw_rows,w['parsed_native_log_rows'],'independent SQL/raw parsed parity')
                prepared=[e for e in w['native'] if e['layer']=='actual PDO.prepare' and e.get('operation')=='approved-log-insert'];check(len(prepared)==1 and prepared[0]['queryString']==sql and prepared[0]['prepared'],'actual prepare SQL exact')
            for e in w['native']:
                if e['layer']=='actual PDOStatement.execute':check(e['success'],'all actual native executions succeed')
                if re.match(r'^\s*(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|MERGE|TRUNCATE)\b',e.get('queryString',''),re.I):check(e['queryString'].startswith('INSERT INTO `ydy_initial_menu_log` ('),'only scoped log native write')
            for i,row in enumerate(new):
                check(set(row)==set(PLAN['log_SQL_contract']['insert_fields_order'])|{'id','customer_uuid'},'exact13 log fields')
                check(row['customer_uuid']==PLAN['log_SQL_contract']['default_customer_uuid'],'default customer_uuid')
                check(isinstance(row['id'],int) and row['id']>0,'generated log id')
                check(bool(re.fullmatch(r'[A-F0-9]{8}(-[A-F0-9]{4}){3}-[A-F0-9]{12}',row['uuid'])),'GUID format')
                t=datetime.fromisoformat(row['create_time']).replace(tzinfo=ZoneInfo('Asia/Shanghai')).timestamp();check(math.floor(w['started_epoch'])-1<=t<=math.ceil(w['completed_epoch'])+1,'time within case')
                for k,v in case['log_values'][i].items():
                    if k in ['uuid','create_time']: continue
                    if isinstance(v,dict):v=v['lane_'+lane]
                    check(str(row[k])==str(v),'exact log '+k)
                    check(str(w['parsed_native_log_rows'][i][k])==str(v),'parsed native log '+k)
                for k,v in w['parsed_native_log_rows'][i].items(): check(str(row[k]).removesuffix('.000000')==str(v),'native/readback '+k)
            all_new+=new;prior_logs=w['logs_after']
        check(len(all_new)==97 and len({r['uuid'] for r in all_new})==97 and len({r['id'] for r in all_new})==97,'97 unique own logs')
        final=load(lane,'final-complete.json');check(final['logs']==all_new and final['own_rows_unchanged'],'final exact original logs')
        af=load(lane,'batch-after-complete.json');result['sentinel_deltas'][lane]={k:{'before':v,'after':af['sentinel'][k]} for k,v in initial['sentinel'].items() if v!=af['sentinel'][k]}
        result['log_rows'][lane]=len(all_new);result['full_DTO'][lane]=[]
        for action,expected_dto in expected.items():
            label=action+('-complete' if action in ['Onenutrient','TeamDietaryPattern','StageDietaryPattern','TeamProtein','StageTeamProtein'] else '-positive')
            exact(json.loads(wires[label]['body']),expected_dto,lane+'/'+action)
            result['full_DTO'][lane].append(action)
        resource=load(lane,'resource-check.json');check(all(resource[k] for k in ['own_worker_absent','own_port_closed','original_parent_present','request_absent','cache_absent']) and resource['AppleDouble']==[],'resources cleaned')
        result['preserved_initial_red'][lane]={a:json.loads(wires[a+'-positive']['body']) for a in ['Onenutrient','TeamDietaryPattern','StageDietaryPattern','TeamProtein','StageTeamProtein']}
    exact(expected_by_lane['B'],expected_by_lane['C'],'independent DTO parity')
    for action in expected_by_lane['B']:
        suffix='-complete' if action in ['Onenutrient','TeamDietaryPattern','StageDietaryPattern','TeamProtein','StageTeamProtein'] else '-positive'
        check(load('B','wire-'+action+suffix+'.json')['body']==load('C','wire-'+action+suffix+'.json')['body'],'complete body byte parity '+action)
    # Verifier counterexamples alter an independently expected DTO; none are accepted.
    dto=expected_by_lane['B']['get_day_teampower']
    for bad in [None,{},[],{'code':'0','message':dto['message'],'data':dto['data']},{**dto,'unexpected':1},{**dto,'data':{**dto['data'],'real':[0]}},{**dto,'data':{**dto['data'],'real':['600']}},{**dto,'data':{**dto['data'],'real':[600.0]}},{**dto,'data':{**dto['data'],'que_date':[]}},{**dto,'data':{**dto['data'],'x':[]}}]:
        try:exact(bad,dto,'counterexample')
        except AssertionError:result['negative_cases']+=1
        else:raise AssertionError('Verifier accepted counterexample')
    result['assertions']=COUNT;result['status']='20 selected full DTO per lane pass; initial red preserved; fixedscale issue remains open'
    (HERE/'verification20.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n')
    print(json.dumps({k:v for k,v in result.items() if k not in ['preserved_initial_red','full_DTO']},ensure_ascii=False))
if __name__=='__main__':run()
