"""A reusable, blank-input decision model, separate from empirical company data.""" import csv import json FIELDS = [ ('R0','原方案净实现收入','元/销售单位','财务/渠道结算'), ('V0','原方案变动成本','元/销售单位','财务/已核BOM及履约'), ('Rn','新方案净实现收入','元/销售单位','财务/渠道结算'), ('Vn','新方案变动成本','元/销售单位','已核配方、包材、制造、装箱、物流和预期报损;不重复计项'), ('F','一次增量投入','元','报价、打样、必要验证、试产和印版'), ('Qn','真正新增销量','销售单位/同一分析期','研究或经营估计;不含从旧品转移'), ('Qc','从原方案转移销量','销售单位/同一分析期','经营估计;与新增互斥'), ] SCRIPT = r'''(() => { const names = ['R0','V0','Rn','Vn','F','Qn','Qc']; const state = document.querySelector('#model-state'); let origin = 'blank'; const calculate = (v) => { const u0=v.R0-v.V0, un=v.Rn-v.Vn, du=un-u0; const value=v.Qn*un+v.Qc*du-v.F; return {u0,un,du,value,breakEven:du>0 ? Math.ceil(v.F/du) : null}; }; const fmt = n => Number(n).toLocaleString('zh-CN',{maximumFractionDigits:2}); function update() { const rows=names.map(k=>[k,document.getElementById(k).value.trim()]); if(rows.some(([k,v])=>v==='')) { state.textContent='输入未齐,不计算;空值不按0处理。';document.querySelector('#sensitivity').innerHTML='';return; } const v=Object.fromEntries(rows.map(([k,x])=>[k,Number(x)])); if(Object.values(v).some(x=>!Number.isFinite(x)||x<0)||!Number.isInteger(v.Qn)||!Number.isInteger(v.Qc)) {state.textContent='输入应为非负有限数;销量为非负整数。';document.querySelector('#sensitivity').innerHTML='';return;} const result=calculate(v); const badge=origin==='synthetic'?'算术假设示例,非企业参数':'本地输入试算,参数真实性未核验'; state.textContent=badge+'。原单位贡献 '+fmt(result.u0)+';新单位贡献 '+fmt(result.un)+';单位增量 '+fmt(result.du)+' 元。该分析期增量贡献 '+fmt(result.value)+' 元。'+(result.breakEven===null?'同销量替代下,单位增量≤0,不能靠增加替代销量覆盖正的一次投入。':'仅同销量替代情景,覆盖F至少需要 '+fmt(result.breakEven)+' 个销售单位。')+'此模型未含资金时间价值及未声明的固定运营变化,不等于量产或盈利验收。'; const scenarios=[['当前输入',v],['新净收入降低10%',{...v,Rn:v.Rn*.9}],['新变动成本增加10%',{...v,Vn:v.Vn*1.1}],['新增销量减半',{...v,Qn:Math.floor(v.Qn*.5)}],['无真正新增销量',{...v,Qn:0}]]; document.querySelector('#sensitivity').innerHTML='
'+scenarios.map(([name,x])=>{const z=calculate(x);return ''}).join('')+'
单因素敏感性(其他条件固定)新单位贡献/元分析期增量贡献/元
'+name+''+fmt(z.un)+''+fmt(z.value)+'
'; window.modelResult={...result,origin,inputs:v,empiricalVerified:false}; } names.forEach(k=>document.getElementById(k).addEventListener('input',()=>{origin='user-unverified';delete window.modelResult;update();})); document.getElementById('example').onclick=()=>{ const v={R0:20,V0:15,Rn:21,Vn:15.5,F:1000,Qn:100,Qc:1000}; names.forEach(k=>document.getElementById(k).value=v[k]);origin='synthetic';update(); }; document.getElementById('reset').onclick=()=>{names.forEach(k=>document.getElementById(k).value='');origin='blank';delete window.modelResult;update();}; update(); })();''' def build_model(out,shell): model={'schema':'ksf.incremental-feasibility.v1','unit':'元/统一销售单位,销量为同一分析期', 'empiricalCompanyData':False,'fields':{k:{'label':label,'value':None,'unit':unit,'sourceNeeded':source} for k,label,unit,source in FIELDS}, 'formulas':{'u0':'R0-V0','un':'Rn-Vn','deltaUnit':'un-u0','incrementalContribution':'Qn*un+Qc*(un-u0)-F', 'sameVolumeBreakEven':'ceil(F/(un-u0)) only when un-u0>0; Qn=0'}, 'limitations':['不以零售价代净实现收入','费用口径互斥,不能重复计税/运费/报损','新销量与旧品转移销量分开','仅单期贡献模型,未含资金时间价值和额外固定运营变化']} (out/'商业回推模型.json').write_text(json.dumps(model,ensure_ascii=False,indent=2)+'\n',encoding='utf8') with (out/'商业回推输入模板.csv').open('w',encoding='utf-8-sig',newline='') as f: w=csv.writer(f);w.writerow(['变量','含义','实际值(留空待核)','单位','需要的依据']) for k,label,unit,source in FIELDS:w.writerow([k,label,'',unit,source]) inputs=''.join(f'' for k,label,unit,_ in FIELDS) body='''

先统一口径,再看增量

O1拆配比与装箱,O2拆印版及旧包材消化,O3拆拣货/错发/物流;依报价归入变动成本或一次投入,已含费用不重复加入。所有输入默认为空;本页仅本地计算、不保存、不上传。

统一使用同一销售单位及分析期。u₀=R₀−V₀,uₙ=Rₙ−Vₙ;增量贡献=Qₙ×uₙ+Q꜀×(uₙ−u₀)−F。若只替代原销量,且单位增量为正,盈亏临界=⌈F/(uₙ−u₀)⌉。

空白输入模板 CSV · 模型及口径 JSON · 返回可行性报告

'''+inputs+'''

算术示例专供检验公式:20/15 → 21/15.5,F=1000,新增100、转移1000。不是公司收入、成本、销量预测或售价建议。用户修改参数后的真实性仍需另核。

' (out/'商业回推演算器.html').write_text(shell('商业回推|空值模型与敏感性试算',body),encoding='utf8')