"""R12保单构造输入的独立算术底稿。仅用标准库,不载入网页计算引擎。 保费为负、领取为正;当前保留可退金额的机会成本也记负,但明确不是实际扣款。 全部合同权益、日期、金额、可办理选项均为构造已确认输入,不能应用于真实保单。 任意工作目录运行均将JSON写在脚本同目录。 """ from datetime import date from decimal import Decimal, ROUND_HALF_UP from pathlib import Path import json import math HERE = Path(__file__).resolve().parent def money(v): return float(Decimal(str(v)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)) def flow(day, amount, event): date.fromisoformat(day) return {'date': day, 'amount': money(amount), 'event': event} def premiums(start_year, n, amount, month_day='11-01'): return [flow(f'{start_year+k}-{month_day}', -amount, '构造已确认保费') for k in range(n)] def xnpv(flows, rate): origin = min(date.fromisoformat(f['date']) for f in flows) terms = [f['amount'] / (1+rate)**((date.fromisoformat(f['date'])-origin).days/365) for f in flows] return math.fsum(terms), math.fsum(abs(t) for t in terms) def calc(flows): assert any(f['amount'] < 0 for f in flows) and any(f['amount'] > 0 for f in flows) assert len({f['date'] for f in flows}) > 1 lo, hi = -.9, 10. fl = xnpv(flows, lo)[0] assert fl*xnpv(flows, hi)[0] < 0, '本构造例区间未变号' for _ in range(190): mid = (lo+hi)/2 fm = xnpv(flows, mid)[0] if fl*fm <= 0: hi = mid else: lo, fl = mid, fm root = (lo+hi)/2 residual, scale = xnpv(flows, root) assert abs(residual) < 1e-6 return {'flows': flows, 'xirr_pct': root*100, 'root_unrounded': root, 'xnpv_residual_yuan': residual, 'relative_residual': abs(residual)/max(1, scale), 'sum_negative_yuan': -sum(f['amount'] for f in flows if f['amount'] < 0), 'sum_positive_yuan': sum(f['amount'] for f in flows if f['amount'] > 0), 'basis': '真实日期日差/固定365;有限区间二分所得一个根,不声称多变号现金流唯一解'} def run(): out = {} hist = premiums(2022, 7, 10000)+[flow('2036-11-01', 85000, '条件末期净回款')] forward = [flow('2026-11-01', -28000, '放弃当日可退净现金的机会成本')]+premiums(2026, 3, 10000)+[flow('2036-11-01',85000,'条件末期净回款')] out['DJ2-100'] = {'inputs': {'decision_date':'2026-11-01','past_premium_total':40000,'current_net_surrender':28000, 'future_premiums_total':30000,'terminal_date':'2036-11-01','terminal_net_cash':85000}, 'historical':calc(hist),'forward_incremental':calc(forward), 'paths':[{'date':d,'exit':exit,'continue':cont,'incremental':cont-exit} for d,exit,cont in [('2026-11-01',28000,-10000),('2027-11-01',0,-10000),('2028-11-01',0,-10000),('2036-11-01',0,85000)]]} base = premiums(2026,3,20000) out['DJ2-101'] = {'inputs':{'guaranteed_terminal':82000,'non_guaranteed_extra':34000,'illustrated_terminal':116000, 'terminal_date':'2041-11-01','status':'保证与非保证标签均为构造条款分类,不来自真实产品'}, 'guaranteed':calc(base+[flow('2041-11-01',82000,'构造保证路径条件回款')]), 'illustrated':calc(base+[flow('2041-11-01',116000,'构造条件演示路径,非保证')]), 'wrong_double_add':calc(base+[flow('2041-11-01',198000,'误将演示总额再加保证金额')])} p = premiums(2026,3,12000,'11-17') out['DJ2-102'] = {'inputs':{'application_date':'2026-11-10','effective_date':'2026-11-17','tenth_year_start':'2035-11-17', 'tenth_anniversary':'2036-11-17','actual_receipt':'2036-11-24','terminal_yuan':46000}, 'date_map':[{'label':s,'date':d,'days_from_effective':(date.fromisoformat(d)-date(2026,11,17)).days} for s,d in [('生效日','2026-11-17'),('首年度最后日','2027-11-16'),('第二年度开始','2027-11-17'), ('第十年度开始','2035-11-17'),('第十周年','2036-11-17'),('实际到账','2036-11-24')]], 'actual':calc(p+[flow('2036-11-24',46000,'已确认到账')]), 'anniversary':calc(p+[flow('2036-11-17',46000,'周年当天到账对照')]), 'year_start_mistake':calc(p+[flow('2035-11-17',46000,'错用第十年度起点')])} out['DJ2-103'] = {'inputs':{'years':3,'annual_total_premium':12000,'annual_main_premium':10000, 'annual_rider_premium':2000,'terminal_date':'2036-11-01','main_terminal':40000}, 'rows':[{'date':f'{y}-11-01','total_premium':12000,'main_premium':10000,'rider_premium':2000} for y in range(2026,2029)], 'total':calc(premiums(2026,3,12000)+[flow('2036-11-01',40000,'主险条件回款;未发生附加险事故')]), 'main_only':calc(premiums(2026,3,10000)+[flow('2036-11-01',40000,'可明确归属主险的回款')]), 'unsplit_case':{'main_premium':None,'main_xirr_pct':None,'status':'缺少正式分项,禁止以比例猜拆'}} p = premiums(2026,3,15000) out['DJ2-104'] = {'inputs':{'terminal_date':'2036-11-01','surrender_net_cash':58000,'claim_net_cash':120000, 'exclusive_condition':'本构造条款明确结算路径互斥,同日不叠加,不用于判断真实事故理赔'}, 'surrender':calc(p+[flow('2036-11-01',58000,'未发生事故的构造退出路径')]), 'claim':calc(p+[flow('2036-11-01',120000,'仅假设事故符合条款且款项当日已付的条件路径')]), 'wrong_added':calc(p+[flow('2036-11-01',178000,'误将互斥路径相加')]), 'pending_claim':{'terminal_cash':None,'xirr_pct':None,'status':'待确定条件或到账日期,不填零也不先填赔款'}} p = [flow('2026-11-01',-50000,'一次性保费')] bonus = [flow(f'{y}-11-01',2000,'构造红利实际领到外部') for y in range(2027,2030)] out['DJ2-105'] = {'inputs':{'base_terminal':60000,'bonuses_each':2000,'bonus_count':3, 'included_terminal':66000,'separate_bonus_account_terminal':6300,'terminal_date':'2031-11-01'}, 'cash_paid':calc(p+bonus+[flow('2031-11-01',60000,'不含已外领红利的末期净额')]), 'included':calc(p+[flow('2031-11-01',66000,'明确已经包含留存红利的末期净额')]), 'separate_account':calc(p+[flow('2031-11-01',66300,'基础净额60000加未包含的红利账户6300')]), 'wrong_double':calc(p+bonus+[flow('2031-11-01',66000,'错误保留包含红利终值并虚增外部领取')])} current = [flow('2026-11-01',-30000,'当前可退净额的机会成本')] cont = premiums(2026,3,10000)+[flow('2036-11-01',80000,'继续缴费路径条件末期净额')] paid = [flow('2036-11-01',43000,'构造已确认减额交清路径条件末期净额')] out['DJ2-106'] = {'inputs':{'current_surrender_net':30000,'continue_terminal':80000,'paidup_terminal':43000, 'continue_claim_amount':100000,'paidup_claim_amount':60000,'future_premium_total':30000, 'status':'可办理选项、未来保费和变更后权益均为构造已确认输入,不能从当前现金价值线性折算'}, 'continue':calc(current+cont),'paid_up':calc(current+paid), 'continue_minus_paid_up':calc(premiums(2026,3,10000)+[flow('2036-11-01',37000,'同日期继续比交清多的条件净额')])} p = premiums(2026,3,20000) w = [flow('2031-11-01',15000,'构造已确认净领取')] out['DJ2-107'] = {'inputs':{'withdrawal_date':'2031-11-01','withdrawal_net':15000,'old_terminal':85000, 'updated_terminal':62000,'terminal_date':'2036-11-01','terminal_reduction':23000, 'unsupported_linear_guess_terminal':85000-15000}, 'no_withdrawal':calc(p+[flow('2036-11-01',85000,'未发生领取的原有效表')]), 'updated':calc(p+w+[flow('2036-11-01',62000,'领取后有效表净额')]), 'wrong_old_table':calc(p+w+[flow('2036-11-01',85000,'错误继续使用原终值')]), 'missing_updated':{'terminal':None,'xirr_pct':None,'known_flows':p+w,'status':'没有变更后有效利益数据,停止完整收益输出'}} days1 = (date(2027,2,1)-date(2026,11,1)).days days2 = (date(2027,5,1)-date(2027,2,1)).days interest1 = money(Decimal(20000)*Decimal('.06')*days1/365) interest2 = money(Decimal(20000)*Decimal('.06')*days2/365) loan = [flow('2026-11-01',19950,'贷款20000到账同时另付构造直接费50'), flow('2027-02-01',-interest1,'付首段利息'),flow('2027-05-01',-20000-interest2,'付本金及后段利息')] loan_no_fee = [flow('2026-11-01',20000,'不收直接费对照')]+loan[1:] policy = premiums(2024,3,10000)+[flow('2030-11-01',60000,'贷款已经还清的构造保单路径末期净额')] out['DJ2-108'] = {'inputs':{'principal':20000,'annual_simple_rate_pct':6,'direct_fee':50,'day_basis':365, 'start':'2026-11-01','interest_payment':'2027-02-01','repayment':'2027-05-01','policy_terminal':60000}, 'interest_rows':[{'start':'2026-11-01','end':'2027-02-01','days':days1,'principal':20000,'interest':interest1}, {'start':'2027-02-01','end':'2027-05-01','days':days2,'principal':20000,'interest':interest2}], 'loan':calc(loan),'loan_no_fee':calc(loan_no_fee),'policy_without_loan':calc(policy), 'results':{'total_days':days1+days2,'total_interest':money(interest1+interest2),'total_cost':money(interest1+interest2+50), 'total_outflows':money(20000+interest1+interest2),'net_funds_received':19950}, 'combined':{'flows':sorted(policy+loan,key=lambda f:f['date']),'same_day_2026_11_01_net':19950-10000,'xirr_pct':None, 'status':'贷款与保单合并出现多次符号变化;本文不输出综合年化,不声称单根唯一或成本可抵消'}} assert len(out) == 9 return out if __name__ == '__main__': data = run() target = HERE / 'calc_r12.json' target.write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') print(json.dumps({'articles':len(data),'output':str(target), 'max_absolute_xnpv_residual_yuan':max(abs(v['xnpv_residual_yuan']) for a in data.values() for v in a.values() if isinstance(v,dict) and 'xnpv_residual_yuan' in v)},ensure_ascii=False))