"""C09构造存款账。Python 3.10+标准库;默认在脚本旁输出JSON,无网络或仓库依赖。""" from datetime import date, timedelta from decimal import Decimal, ROUND_HALF_UP, getcontext from pathlib import Path import argparse import json getcontext().prec = 50 D = Decimal def money(x): return format(D(x).quantize(D('0.01'), rounding=ROUND_HALF_UP), '.2f') def raw(x): return format(D(x), 'f') def days(a, b): a, b = date.fromisoformat(a), date.fromisoformat(b) if b < a: raise ValueError('终点早于起点') return (b-a).days def interest(p, r, n, denominator=365): return D(p)*D(r)*D(n)/D(denominator) def entry(inputs, tables, comparison, checks): assert all(checks.values()), checks return {'precision': 'Decimal十进制50位;金额单位元,积数单位元·日;除明确入账事件外内部不舍入,展示到分ROUND_HALF_UP;日期含起不含止;全部利率为构造,不是报价。', 'inputs': inputs, 'tables': tables, 'comparison': comparison, 'checks': checks} def c089(): # 4月20日冲正的价值日回到4月12日;账面余额与重建计息余额分开。 events = [(1,'2000'),(1,'-500'),(5,'3000'),(5,'-1000'),(10,'-4000'), (12,'500'),(17,'6000'),(17,'-1000'),(20,'-500'),(23,'-2500'),(28,'1000')] actual = [(n,a) for n,a in events if n not in (12,20)] booked = D('10000'); effective = D('10000'); total = D(0); booktotal = D(0) rows=[] for n in range(1,31): booked += sum((D(a) for k,a in events if k==n),D(0)) effective += sum((D(a) for k,a in actual if k==n),D(0)) total+=effective; booktotal+=booked rows.append({'date':f'2026-04-{n:02}', 'book_balance':raw(booked), 'interest_balance':raw(effective), 'day_product':raw(effective), 'cumulative_product':raw(total)}) compressed=[] for lo,hi in [(1,5),(5,10),(10,17),(17,23),(23,28),(28,31)]: p=D(rows[lo-1]['interest_balance']) compressed.append({'start':f'2026-04-{lo:02}', 'end': '2026-05-01' if hi==31 else f'2026-04-{hi:02}', 'days':hi-lo,'balance':raw(p),'product':raw(p*(hi-lo))}) sum_comp=sum((D(r['product']) for r in compressed),D(0)) return entry({'opening':'10000','period':['2026-04-01','2026-05-01'],'events':[{'booking_date':f'2026-04-{n:02}','value_date':'2026-04-12' if n==20 else f'2026-04-{n:02}','amount':a} for n,a in events], 'assumptions':['日终余额;当日全部交易先净额入余额','冲正回溯价值日,12日误入500元从12日至19日均剔除'],'annual_rate':'0.001','denominator':365}, {'daily':rows,'compressed':compressed}, {'effective_product':raw(total),'book_product':raw(booktotal),'correction_product':raw(booktotal-total),'interest':money(interest(total,'0.001',1)),'wrong_end_balance_product':raw(effective*30),'wrong_end_balance_interest':money(interest(effective*30,'0.001',1))}, {'daily_equals_compressed':total==sum_comp,'correction_500_times_8':booktotal-total==D(500)*8,'zero_rate_zero':interest(total,0,1)==0,'same_day_net_1500':D(2000)-500==1500}) def c090(): p=D(100000); r=D('0.0018'); q1=interest(p,r,90,360); posted=D(money(q1)) q2parts=[(11,p+posted),(81,p)] q2=sum((interest(b,r,n,360) for n,b in q2parts),D(0)) daily_wrong=p*(1+r/360)**90-p rows=[{'start':'2025-12-21','end':'2026-03-21','days':90,'principal':raw(p),'interest_raw':raw(q1),'event':'3月20日结息;构造3月21日入账45元'}, {'start':'2026-03-21','end':'2026-04-01','days':11,'principal':raw(p+posted),'interest_raw':raw(interest(p+posted,r,11,360)),'event':'已入账利息尚未转出'}, {'start':'2026-04-01','end':'2026-06-21','days':81,'principal':raw(p),'interest_raw':raw(interest(p,r,81,360)),'event':'4月1日仅转出前季45元'}] return entry({'principal':'100000','annual_rate':raw(r),'denominator':360,'q1_days':90,'q2_days':92,'credit_date':'2026-03-21','transfer_date':'2026-04-01','transfer_amount':money(posted),'assumptions':['每季末月20日结息,次日入账并计入本金是构造','期内应计息不入本;第二季新息6月21日入账']}, {'segments':rows,'events':[{'date':'2026-03-20','state':'未入账应付息','interest':'45.00','principal':'100000.00'}, {'date':'2026-03-21','state':'已入账','interest':'0.00','principal':'100045.00'}, {'date':'2026-04-01','state':'前息已转出','interest':'0.00','principal':'100000.00'}, {'date':'2026-06-21','state':'第二季结息入账','interest':money(q2),'principal':money(p+D(money(q2)))}]}, {'q1':money(q1),'q2':money(q2),'q2_raw':raw(q2),'q2_keep_all_prior_interest':money(interest(p+posted,r,92,360)),'q2_no_prior_interest':money(interest(p,r,92,360)),'wrong_q1_daily_compound':money(daily_wrong),'wrong_q1_daily_compound_raw':raw(daily_wrong),'final_account':money(p+D(money(q2))),'cash_transferred':money(posted),'total_assets':money(p+posted+D(money(q2)))}, {'q1_45':posted==45,'q2_days_close':11+81==92,'assets_close':p+D(money(q2))+posted==D('100091.00'),'zero_rate_no_transfer':interest(p,0,90,360)==0}) def c091(): periods=[('2026-03-21','2026-04-01','80000'),('2026-04-01','2026-05-01','100000'),('2026-05-01','2026-06-21','100000')] rows=[] for a,b,p in periods: n=days(a,b); old='0.002' if a<'2026-05-01' else '0.001' rows.append({'start':a,'end':b,'days':n,'balance':p,'product':raw(D(p)*n),'uniform_rate':'0.001','uniform_interest_raw':raw(interest(p,'0.001',n,360)),'segmented_rate':old,'segmented_interest_raw':raw(interest(p,old,n,360))}) prod=sum(D(x['product']) for x in rows); uniform=interest(prod,'0.001',1,360); split=sum(D(x['segmented_interest_raw']) for x in rows) return entry({'currency_account':'人民币个人账户构造样本','rate_dates':[{'date':'2026-03-21','rate':'0.002'},{'date':'2026-05-01','rate':'0.001'}],'settlement_date':'2026-06-20','credit_date_assumed':'2026-06-21','denominator':360,'rule_A':'结息日利率对本周期全部积数定率','rule_B':'另一份纯构造合同,按挂牌生效日分段,不称工行个人活期规则'}, {'matrix':rows}, {'product':raw(prod),'uniform':money(uniform),'segmented':money(split),'difference_raw':raw(split-uniform),'difference':money(split-uniform),'missing_rule':'拒绝选定唯一应付金额;只保留情景A/B','equal_rate_scenario':'全部0.001时两种规则均为'+money(uniform)}, {'days_92':sum(x['days'] for x in rows)==92,'same_product':prod==D('8980000'),'difference_old_product':abs(split-uniform-interest(D('3880000'),'0.001',1,360))=6:rate=D('.001');state='构造违约生效后存入' elif m==5:state='构造宽限内存入,保留定期' n=days(a,end); val=interest(amount,rate,n) rows.append({'scheduled_month':m,'actual_date':a if amount else None,'amount':raw(amount),'days':n if amount else 0,'product':raw(amount*n),'rate':raw(rate) if amount else None,'state':state,'interest_raw':raw(val)}) return rows def c092(): rows=recurring_rows('按时'); product=sum(D(x['product']) for x in rows); val=interest(product,'.012',1) monthrows=[{'month':m,'remaining_months':13-m,'amount':'1000','interest_raw':raw(D(1000)*D('.012')*D(13-m)/12)} for m in range(1,13)] month=sum(D(x['interest_raw']) for x in monthrows) return entry({'monthly_amount':'1000','dates':'2026年每月1日至共同到期2027-01-01','annual_rate':'.012','daily_contract':'实际日数/365,末期一次舍入','monthly_alternative':'另立每月/12合同,不能拼进实际日模型'}, {'tranches':rows,'alternative_monthly':monthrows}, {'principal':'12000','product':raw(product),'daily_interest_raw':raw(val),'daily_interest':money(val),'monthly_interest':money(month),'wrong_full_year_interest':money(D(12000)*D('.012')),'wrong_excess':money(D(12000)*D('.012')-val)}, {'twelve_thousand':sum(D(x['amount']) for x in rows)==12000,'last_month_31':rows[-1]['days']==31,'month_total_78':sum(x['remaining_months'] for x in monthrows)==78,'zero_amount_zero':interest(0,'.012',365)==0}) def c093(): variants={mode:recurring_rows(mode) for mode in ['按时','补齐','未补齐']}; totals={} for mode,rs in variants.items(): val=sum(D(x['interest_raw']) for x in rs);totals[mode]={'principal':raw(sum(D(x['amount']) for x in rs)),'interest_raw':raw(val),'interest':money(val)} late=variants['未补齐'];wrong=sum(interest(x['amount'],'.001',x['days']) for x in late) return entry({'schedule':'2026年1月至12月每月1日计划1000元;2027-01-01共同支取','missing_month':4,'timely_catchup':'5月1日一笔2000,标记4月补款与5月款各1000','unfilled':'4月0,5月1日1000,6月至12月各1000;无第二笔4月补款','assumed_breach_effective':'2026-06-01;5月原始款仍定期;此认定时点需银行确认','fixed_rate':'.012','active_rate':'.001','denominator':365}, variants, {'totals':totals,'timely_loss_raw':raw(D(totals['按时']['interest_raw'])-D(totals['补齐']['interest_raw'])),'wrong_all_late_path_active':money(wrong),'wrong_late_path_gap':money(D(totals['未补齐']['interest_raw'])-wrong),'principal_gap_is_not_interest':'1000'}, {'timely_principal_unchanged':totals['按时']['principal']==totals['补齐']['principal'],'unfilled_principal_11000':D(totals['未补齐']['principal'])==11000,'prior_money_not_repriced':all(x['rate']=='0.012' for x in late if x['scheduled_month'] in (1,2,3,5)),'timely_30_day_delay':variants['按时'][3]['days']-variants['补齐'][3]['days']==30}) def c094(): dates=['2026-01-01','2026-04-01','2026-07-01','2026-10-01','2027-01-01']; rows=[]; accrued=D(0); recovered=D(0) for k in range(4): p=D(120000)-D(30000)*k;n=days(dates[k],dates[k+1]);val=interest(p,'.012',n);accrued+=val;recovered+=30000 rows.append({'start':dates[k],'end':dates[k+1],'days':n,'principal':raw(p),'product':raw(p*n),'interest_raw':raw(val),'cumulative_interest_raw':raw(accrued),'principal_withdrawal':'30000','remaining_principal':raw(p-30000),'cash_received':money(D(30000)+(D(money(accrued)) if k==3 else 0))}) def allowed(scheduled, requested): if D(requested)!=D(scheduled):raise ValueError('与约定本金领取表不符,拒绝按正常合同继续算') rejected=False try:allowed('30000','40000') except ValueError:rejected=True return entry({'opening_principal':'120000','annual_rate':'.012','denominator':365,'withdrawal_schedule':'每季末30000元;2026-04-01/07-01/10-01/2027-01-01','settlement':'利息到期一次支付,期间不入本','out_of_schedule':'4月1日要求40000元,拒绝本模型'}, {'quarters':rows}, {'interest':money(accrued),'interest_raw':raw(accrued),'last_cash':rows[-1]['cash_received'],'total_principal_recovered':raw(recovered),'total_cash':money(recovered+D(money(accrued))),'wrong_original_principal_interest':'1440.00','wrong_excess':money(D(1440)-accrued)}, {'principal_conservation':recovered==120000 and rows[-1]['remaining_principal']=='0','days_close':sum(x['days'] for x in rows)==365,'extra_withdrawal_rejected':rejected,'zero_rate_zero':sum(interest(x['principal'],0,x['days']) for x in rows)==0}) def c095(): ends=['2026-03-01','2026-05-01','2026-07-01','2026-09-01','2026-11-01','2027-01-01']; takes=[120,0,0,360,0,240] unpaid=D(0);received=D(0);wrongp=D(60000);rows=[] for k,(end,take) in enumerate(zip(ends,takes),1): opening=unpaid;unpaid+=120;unpaid-=take;received+=take;wi=wrongp*D('.002');wrongp+=wi rows.append({'period':k,'date':end,'opening_uncollected':raw(opening),'new_due':'120','paid':str(take),'closing_uncollected':raw(unpaid),'cumulative_paid':raw(received),'deposit_principal':'60000','wrong_compound_interest_raw':raw(wi),'wrong_compound_principal_raw':raw(wrongp)}) return entry({'principal':'60000','annual_rate':'.012','term':'2026-01-01至2027-01-01一年','assumed_period_rule':'六期各两个月,每期60000×0.012×2/12=120;仅构造付款频率','takes':takes,'rule':'未领息可后领,不计复息;到账外部现金按零收益记录'}, {'six_periods':rows}, {'total_due':'720.00','total_paid':money(received),'ending_uncollected':money(unpaid),'maturity_principal':'60000.00','last_cash':'60240.00','all_cash_including_prior_receipts':'60720.00','wrong_compound_total_interest':money(wrongp-60000),'wrong_excess':money(wrongp-D(60720)),'all_uncollected_scenario':{'principal':'60000.00','uncollected_interest':'720.00','cash_paid':'0.00'}}, {'due_equals_paid_plus_unpaid':D(720)==received+unpaid,'unpaid_nonnegative':all(D(x['closing_uncollected'])>=0 for x in rows),'principal_constant':all(x['deposit_principal']=='60000' for x in rows),'zero_interest_no_due':D(60000)*0*D(2)/12==0}) def c096(): def choose(end, rates, comparison='none', active='.001'): # 本教学合同明示达到边界即进入新档;缺对应档不能退到别档。 if end<'2026-01-01':raise ValueError('支取日早于存入日') key=12 if end>='2027-01-01' else 6 if end>='2026-07-01' else 3 if end>='2026-04-01' else 0 if key not in rates:raise ValueError('缺少持有期限对应档,拒绝替代') rate=D(rates[key])*(D('.6') if key else D(1)) if comparison=='none':return rate if comparison=='floor':return max(rate,D(active)) if comparison=='cap':return min(rate,D(active)) raise ValueError('比较约束未知,拒绝选择唯一利率') rates={0:'.001',3:'.012',6:'.015',12:'.018'} start='2026-01-01';p=D(50000);ends=[('2个月','2026-03-01','.001','活期'),('恰3个月','2026-04-01','.0072','三月档1.2%×60%'),('8个月','2026-09-01','.009','六月档1.5%×60%'),('15个月','2027-04-01','.0108','一年档1.8%×60%')] rows=[{'holding':label,'start':start,'end':end,'days':days(start,end),'selected_rate':rate,'band':band,'interest_raw':raw(interest(p,choose(end,rates),days(start,end))),'interest':money(interest(p,choose(end,rates),days(start,end)))} for label,end,rate,band in ends] split=[] for a,b,r in [('2026-01-01','2026-04-01','.001'),('2026-04-01','2026-07-01','.0072'),('2026-07-01','2026-09-01','.009')]: split.append({'start':a,'end':b,'days':days(a,b),'wrong_rate':r,'wrong_interest_raw':raw(interest(p,r,days(a,b)))}) comp=[] for active in ['.005','.010']: disc=D('.009');a=D(active) comp.append({'active_rate':active,'no_comparison_rate':raw(disc),'explicit_floor_rate':raw(max(disc,a)),'explicit_cap_rate':raw(min(disc,a)),'floor_interest':money(interest(p,max(disc,a),243)),'cap_interest':money(interest(p,min(disc,a),243))}) refused=[] for label,end,rt,rule in [('缺六月档','2026-09-01',{k:v for k,v in rates.items() if k!=6},'none'),('比较条款未知','2026-09-01',rates,'unknown'),('支取日早于存入','2025-12-31',rates,'none')]: try:choose(end,rt,rule) except ValueError as exc:refused.append({'case':label,'result':str(exc)}) return entry({'principal':'50000','date_start':start,'discount':'.6','rate_table':rates,'rate_table_date_assumed':'各情景采用各自支取日的同值构造表;并非已确认真实合同定价日','band_contract_assumed':'不足3个日历月活期.001;达到3未满6取三月档.012;达到6未满12取六月档.015;达到12取一年档.018;折后对全部实存天数计息/365;恰等边界由本构造明确包含','comparison_clause':'主模型明示没有活期保底或封顶;条件表分别独立增加明示保底/封顶;真实约束未知则拒算'}, {'holdings':rows,'wrong_segmented_eight_months':split,'comparison_clause_tests':comp,'refused_inputs':refused}, {'eight_month_correct':rows[2]['interest'],'wrong_eight_month_segmented':money(sum(D(x['wrong_interest_raw']) for x in split)),'missing_comparison_clause':'不能自动max或min,真实条款未知拒绝唯一金额','boundary_before_three_months':{'date':'2026-03-31','rate':'.001'},'boundary_exact_six_months':{'date':'2026-07-01','rate':'.009'}}, {'three_month_90_days':rows[1]['days']==90,'eight_month_243_days':rows[2]['days']==243,'year_band_caps_tenor_not_interest':rows[3]['selected_rate']=='.0108','floor_not_cap':D(comp[1]['explicit_floor_rate'])>D(comp[1]['explicit_cap_rate']),'missing_unknown_reverse_refused':len(refused)==3,'exact_three_selected':choose('2026-04-01',rates)==D('.0072'),'exact_six_selected':choose('2026-07-01',rates)==D('.009')}) def c097(): p=D(100000); initial=p;rows=[];start=date(2026,1,1);realized=D(0) for k in range(1,13): end=start+timedelta(days=7);val=D(money(interest(p,'.009',7,360)));realized+=val rows.append({'period':k,'start':start.isoformat(),'end':end.isoformat(),'days':7,'principal':money(p),'rate':'.009','interest_credited':money(val),'interest_to_principal':money(val),'cash_paid':'0.00','new_principal':money(p+val)});p+=val;start=end residual=D(money(interest(p,'.001',6,360))) rows.append({'period':13,'start':start.isoformat(),'end':'2026-04-01','days':6,'principal':money(p),'rate':'.001','interest_credited':money(residual),'interest_to_principal':'0.00','cash_paid':money(p+residual),'new_principal':'0.00'}) ordinary=interest(initial,'.009',90,360) return entry({'principal':'100000','start':'2026-01-01','withdraw':'2026-04-01','notice_created':'2026-03-25','notice_days':7,'actual_days':90,'ordinary_rate':'.009','denominator':360,'automatic_contract':'纯构造协议:每7日结息到分并转本金,12轮;第13段不足7日仅按.001结息,不冒称任何银行智能存款'}, {'automatic_events':rows,'ordinary_events':[{'date':'2026-01-01','event':'存入','principal':'100000','interest_to_principal':'0'}, {'date':'2026-03-25','event':'建立七天通知','principal':'100000','interest_to_principal':'0'}, {'date':'2026-04-01','event':'按约支取,90天一次结息','principal':'0','cash_paid':money(initial+ordinary)}]}, {'ordinary_interest':money(ordinary),'ordinary_cash':money(initial+ordinary),'auto_interest':money(realized+residual),'auto_cash':money(p+residual),'completed_seven_day_cycles':12,'residual_days':6,'wrong_direct_power_interest':money(initial*((1+D('.009')*7/360)**12-1)),'difference_explanation':'直接幂遗漏余下6天且没有逐期到分,不等于普通通知合同'}, {'time_close':12*7+6==90,'ordinary_no_rollover':len([x for x in rows if x['interest_to_principal']!='0.00'])==12,'auto_cash_close':p+residual==initial+realized+residual,'zero_rate_no_growth':initial*((1+D(0))**12)==initial}) def c098(): p=D(150000);notice=D(60000);n=90 def partition(take): take=D(take);remaining=p-take if take<50000 or (remaining!=0 and remaining<50000):raise ValueError('低于构造适用最低支取/留存要求') valid=min(take,notice);excess=max(take-notice,D(0));unused=max(notice-take,D(0)) vi=interest(valid,'.009',n,360);ei=interest(excess,'.001',n,360);ui=interest(unused,'.001',n,360) return {'actual_withdrawal':raw(take),'valid_withdrawal':raw(valid),'excess_withdrawal':raw(excess),'unused_notice_retained':raw(unused),'other_retained':raw(remaining-unused),'total_retained':raw(remaining),'valid_interest':money(vi),'excess_interest':money(ei),'cash_interest':money(vi+ei),'withdrawal_cash':money(take+D(money(vi+ei))),'unused_notice_accrual_not_cash':money(ui)} over=partition(80000);under=partition(50000);rejected=[] for take in [40000,110000]: try:partition(take) except ValueError:rejected.append(take) return entry({'principal':'150000','start':'2026-01-01','notice_date':'2026-03-25','agreed_withdraw':'2026-04-01','notice_amount':'60000','actual_days':n,'notice_rate':'.009','active_rate':'.001','denominator':360,'bank_scope':'本篇分区依建行PDF通知存款第(5)项;差额不足部分自起存至支取日活期;保留本金以后如何计息需后续事件','assumptions':['未通知其余保留本金不在本次清息','少取未用通知额的活期应计息只记应计,不假定本次入账']}, {'over_80000':over,'under_50000':under}, {'wrong_all_80000_notice_interest':money(interest(80000,'.009',n,360)),'wrong_excess':money(interest(20000,'.008',n,360)),'unnotified_80000_active_interest':money(interest(80000,'.001',n,360)),'rejected_withdrawals':rejected}, {'over_principal_close':D(over['actual_withdrawal'])+D(over['total_retained'])==p,'under_principal_close':D(under['actual_withdrawal'])+D(under['unused_notice_retained'])+D(under['other_retained'])==p,'both_minimums_hold':D(over['total_retained'])>=50000 and D(under['actual_withdrawal'])>=50000,'invalid_inputs_rejected':len(rejected)==2}) def c099(): # A与B为互斥本金分片;重复导入A窗口是另一压力测试。 windows=[('A',D(100000),'2026-01-05','2026-01-09'),('B',D(50000),'2026-01-07','2026-01-12')] rows=[];excluded=D(0);effective=D(0) for n in range(1,21): dt=f'2026-01-{n:02}';a=D(100000) if '2026-01-05'<=dt<'2026-01-09' else D(0);b=D(50000) if '2026-01-07'<=dt<'2026-01-12' else D(0);net=D(150000)-a-b;excluded+=a+b;effective+=net rows.append({'date':dt,'principal':'150000','A_excluded':raw(a),'B_excluded':raw(b),'excluded_product':raw(a+b),'effective_product':raw(net),'cumulative_effective':raw(effective)}) def union_product(ws): total=D(0) for n in range(1,21): dt=f'2026-01-{n:02}';by_slice={} for key,amount,a,b in ws: if a<=dt