"""Article 7: limited 2025/2026 resident tax examples with independently audited sources.""" import argparse,csv,hashlib,io,json from pathlib import Path ROOT=Path(__file__).resolve().parents[1] DATA=ROOT/'articles/data/crypto-tax' ARTICLE=ROOT/'articles/crypto-tax-japan-guide.html' MODELS=[dict(salary_yen=4000000,salary_income_yen=2760000,social_insurance_yen=600000),dict(salary_yen=8000000,salary_income_yen=6100000,social_insurance_yen=1200000),dict(salary_yen=12000000,salary_income_yen=10050000,social_insurance_yen=1800000)] PROFITS=[100000,500000,1000000,3000000] BRACKETS=[(1950000,5,0),(3300000,10,97500),(6950000,20,427500),(9000000,23,636000),(18000000,33,1536000),(40000000,40,2796000),(None,45,4796000)] def basic_deduction(total,year): if total<0 or year not in (2025,2026):raise ValueError('Unsupported income/year') thresholds=[(1320000,950000),(3360000,880000),(4890000,680000),(6550000,630000),(23500000,580000)] if year==2025 else [(4890000,1040000),(6550000,670000),(23500000,620000)] thresholds += [(24000000,480000),(24500000,320000),(25000000,160000)] return next((value for ceiling,value in thresholds if total<=ceiling),0) def income_tax(taxable): if taxable<0:raise ValueError('Negative taxable income') base=taxable//1000*1000 for ceiling,rate,offset in BRACKETS: if ceiling is None or base{label}') for salary in [4000000,8000000,12000000]: selected=[r for r in rows if r['tax_year']==year and r['salary_yen']==salary] title=f'給与年収{salary//10000}万円・暗号資産を全額申告するモデル(円)' body=''.join(''+f'{r["crypto_profit_yen"]:,}{r["income_tax_increase_yen"]:,}{r["reconstruction_tax_increase_yen"]:,}{r["national_payment_increase_yen"]:,}{r["resident_income_levy_increase_yen"]:,}{r["total_increase_yen"]:,}'+'' for r in selected) parts.append(f'
{body}
{title}
利益所得税本体の増分復興税の増分国税納付増分(端数処理後)住民税所得割の増分合計増分
') return '\n'.join(parts) def outputs(): rows=calculate() result=dict(status='verified_for_declared_model_scope',calculated_on_utc='2026-11-06',model_assumptions='Single employed resident, no dependants or other deductions/income; eligible pre-reconstruction tax credits explicitly zero; fixed social insurance; crypto treated as miscellaneous in these hypothetical facts only; all crypto included on return, no exemption determination; resident standard rates 6%+4%, constant per-capita/forest levies cancel; no insurance premium increase',scenarios=rows) files={'results.json':json.dumps(result,ensure_ascii=False,indent=2)+'\n'} buffer=io.StringIO(newline='');fields=['tax_year','salary_yen','social_insurance_yen','crypto_profit_yen','income_tax_increase_yen','reconstruction_tax_increase_yen','national_payment_increase_yen','resident_income_levy_increase_yen','total_increase_yen'];w=csv.DictWriter(buffer,fieldnames=fields,lineterminator='\n');w.writeheader();w.writerows({k:r[k] for k in fields} for r in rows);files['results.csv']=buffer.getvalue() for name in ['calculate_crypto_tax.py','audit_crypto_tax.py']:files[name+'.txt']=(ROOT/'scripts'/name).read_text() source=ARTICLE.read_text();start='';end='' if source.count(start)!=1 or source.count(end)!=1:raise ValueError('Missing table markers') updated=source[:source.index(start)+len(start)]+'\n'+tables(rows)+'\n'+source[source.index(end):] return files,updated def plot(): import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt plt.rcParams.update({'svg.fonttype':'none','svg.hashsalt':'article7-tax','font.size':12}) rows=calculate();fig,ax=plt.subplots(figsize=(9,5.4)) for offset,salary,color in [(-.25,4000000,'#176b57'),(0,8000000,'#a66a12'),(.25,12000000,'#296da3')]: values=[r['total_increase_yen']/10000 for r in rows if r['tax_year']==2026 and r['salary_yen']==salary] ax.bar([i+offset for i in range(4)],values,width=.24,label=f'Salary JPY {salary/1000000:g}m',color=color) ax.set_xticks(range(4),['100k','500k','1m','3m']);ax.set(title='2026 model | Enacted rules effective Dec 1',xlabel='Crypto profit (JPY)',ylabel='Additional tax (JPY 10,000)');ax.legend();ax.grid(axis='y',alpha=.2);fig.tight_layout();fig.savefig(DATA/'tax-models.svg',metadata={'Date':None});fig.savefig(DATA/'tax-models.png',dpi=160);plt.close(fig) p=DATA/'tax-models.svg';p.write_text('\n'.join(line.rstrip() for line in p.read_text().splitlines())+'\n') def main(): p=argparse.ArgumentParser(description=__doc__);p.add_argument('--check',action='store_true');p.add_argument('--plots',action='store_true');a=p.parse_args();files,text=outputs() if a.check: if ARTICLE.read_text()!=text or any((DATA/k).read_text()!=v for k,v in files.items()):raise SystemExit('Stale tax outputs') print('24 scope-limited scenarios reproduce exactly') else: ARTICLE.write_text(text) for k,v in files.items():(DATA/k).write_text(v) if a.plots:plot() if __name__=='__main__':main()