"""Independent graph/timestamp checks against original and proposed full rows.""" import json from pathlib import Path from datetime import datetime,date from decimal import Decimal P=Path(__file__).parent def validate(before,after,plan,now): changes={(x['table'],x['id']):x['after'] for x in plan['changes']} ids=lambda t:{r['id']:r for r in after[t]} for t,rows in before.items(): new=ids(t);assert set(new)=={r['id'] for r in rows},('Rows added or removed',t) for old in rows: proposed={**old,**changes.get((t,old['id']),{})} # Database datetime objects vs serializable saved plan strings. canon=lambda x:json.loads(json.dumps(x,default=str)) assert canon(new[old['id']])==canon(proposed),('Unexpected change',t,old['id']) hol={date(2026,9,d) for d in range(25,28)}|{date(2026,10,d) for d in range(1,8)} dt=lambda x:datetime.fromisoformat(str(x)) for change in plan['changes']: for k,v in change['after'].items(): if v is None:continue d=dt(v);assert d<=now and d.weekday()<5 and d.date() not in hol,(change['table'],change['id'],k,d) row=ids(change['table'])[change['id']];assert dt(row['create_time'])<=dt(row['update_time']) selected={x['order_id'] for x in plan['chains']} actual={r['order_id'] for r in after['erp_sale_order_items'] if r['need_production'] in (1,True,b'\x01',"b'\\x01'")} assert selected==actual for chain in plan['chains']: oid=chain['order_id'];order=ids('erp_sale_order')[oid];start=dt(order['order_time']);out=dt(chain['outbound_time']);production=dt(order['production_finish_time']);q=Decimal(str(order['total_count'])) n=len(chain['workdays']);assert (1<=n<=2 if q<=30 else 3<=n<=5 if q<=1000 else n>=6 and (out-start).days>=7) assert start=minimum(before['mes_wm_transaction'],sid),('New chronological stock deficit',sid) return {'passed':True,'orders':len(selected),'changed_rows':len(changes),'changed_tables':len({t for t,id in changes}),'cycles':[{'no':c['order_no'],'quantity':c['quantity'],'workdays':c['cycle_workdays'],'start':c['order_time'],'end':c['outbound_time']} for c in plan['chains']],'outside_scope_unchanged':True,'quantities_money_states_results_unchanged':True,'no_new_inventory_timing_deficits':True} if __name__=='__main__': s=json.loads((P/'snapshot.json').read_text(encoding='utf8'));p=json.loads((P/'plan.json').read_text(encoding='utf8'));before={t:x['rows'] for t,x in s['tables'].items()};changes={(x['table'],x['id']):x['after'] for x in p['changes']} after={t:[{**r,**changes.get((t,r['id']),{})} for r in rs] for t,rs in before.items()} print(json.dumps(validate(before,after,p,datetime.fromisoformat(s['state']['now'])),ensure_ascii=False))