From 6af4366a84377a31a1e20df5f6117af999281088 Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Wed, 26 Aug 2026 06:53:25 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E6=B8=85=E7=90=86worktree=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ISOLATION_TEST.md | 1 - .../__pycache__/__init__.cpython-312.pyc | Bin 606 -> 606 bytes .../conftest.cpython-312-pytest-9.0.3.pyc | Bin 8553 -> 8553 bytes .../test_auth.cpython-312-pytest-9.0.3.pyc | Bin 10631 -> 10631 bytes .../test_kpis.cpython-312-pytest-9.0.3.pyc | Bin 12086 -> 0 bytes .../test_maps.cpython-312-pytest-9.0.3.pyc | Bin 13627 -> 0 bytes backend/tests/test_kpi_422_baseline.py | 40 ++++ docs/cma-data-analysis-3-principles.md | 188 ++++++++++++++++++ .../cma-data-analysis-report-template.md | 92 +++++++++ 9 files changed, 320 insertions(+), 1 deletion(-) delete mode 100644 ISOLATION_TEST.md delete mode 100644 backend/tests/__pycache__/test_kpis.cpython-312-pytest-9.0.3.pyc delete mode 100644 backend/tests/__pycache__/test_maps.cpython-312-pytest-9.0.3.pyc create mode 100644 backend/tests/test_kpi_422_baseline.py create mode 100644 docs/cma-data-analysis-3-principles.md create mode 100644 docs/templates/cma-data-analysis-report-template.md diff --git a/ISOLATION_TEST.md b/ISOLATION_TEST.md deleted file mode 100644 index 13e41fa2..00000000 --- a/ISOLATION_TEST.md +++ /dev/null @@ -1 +0,0 @@ -worktree测试文件 diff --git a/backend/tests/__pycache__/__init__.cpython-312.pyc b/backend/tests/__pycache__/__init__.cpython-312.pyc index 47048b21947ed1594f0b70e05c9820affa0ddea5..3bcf3d6adc6d4b6f97bca6bc4842447fcd4241ba 100644 GIT binary patch delta 20 acmcb|a*u`kG%qg~0}$AC_ip44W&!{^Mg=AS delta 20 acmcb|a*u`kG%qg~0}z-;1aIUHW&!{>!344Z diff --git a/backend/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc b/backend/tests/__pycache__/conftest.cpython-312-pytest-9.0.3.pyc index 5a6f172a4d122e301b39af328f0eef9d1450b05d..80e7ac14a120d26b018275a3883a54aa418745ec 100644 GIT binary patch delta 20 acmaFq^wNp@G%qg~0}$AC_ip5lQv?7-)dk`J delta 20 acmaFq^wNp@G%qg~0}xmrY2C;jrw9N>?gkJ5 diff --git a/backend/tests/__pycache__/test_auth.cpython-312-pytest-9.0.3.pyc b/backend/tests/__pycache__/test_auth.cpython-312-pytest-9.0.3.pyc index 052169afb5cefc6122b139ff9f65df8e4e8a6d7f..f233ffadd32f5cf9b056cdf99f21953a077314eb 100644 GIT binary patch delta 20 ZcmZn~q$S5QoG`2*r3!iQ_m=JD~(HP?e^XS6i!Sy52huYj(Y6W)`w$ zw@)ax0!pY_VoDIE0?BSdL4Beoef+G{KkJw5jzC3f?IgeI)=;GU>N)rBJiM8B?KU*3 zI@&$=o;&B>xqI&HIp00^{4o*EgMJx7)|FJN|Az+;OLcOrSeJ+dS!DCe$6Gb#FSHiF8LY(e7wQ>=rYz?ikOx zxO+I_dxj(a(=PVB?wY(SF67n&7yR7)pPhf}ec4sj{Ni9+5Rk%RVmmOTAfzwS^Kn z?J?;wIYB@`w0C)YAeCgZVboN`0PrU{nd|039iWD8H*v`x;+DO=9^!e%)9thIUf}&U z-Uqy39$SX z4|U|1>Vcl*G~T#ZaGfsKc}_EX6nLYx=q~VmUPIa{39sRp^eVUw=`${4`HG%`r%x~> ztg`4O?xL^YE%+X9=D4E2;8)+$LdN~Ruu-#0;wkuhdE&)By{^;cH6T7sG-Uc>M$IZu zm#a^5>N?x_wU&fYD|n3AD|X3xhexgw(X{sm`ktg4GFQA8U1NeVlX?G-g;u&Tq(($SD`qMwmp2SO}MO6Ir!xBF0EY zS=J5eCsg#qzblptfCA2s|9XDtT?n0S1B)gXN-otwDiANRu z?<(+Gak?99pMrl{J!G4i9=v@ZPYq;3(QBk+BAu2Lc|b{KGXn`l*28dH>dB?jTB={w zed#PnsCt8>K9jSb8()$pabH3RcMB4@zmwDu&D;7>>^Dp93W!dGVd zKwht>icP1${a}+MX9l$VV54Eiw>#aAz;o4)fP9nttI#k~J1uN_FSc!T&s3~s$oqk~ z?)c#F;7IGwJKxPswRa5-PKkF9dFI5rS#isRxTPdCjkW_RiCae7C&Z>%0Y5Y42F96m zLTDP&7&m2Ar>QV&p2Xoyd9wo2@WkW!;ry|uNA^sJ+e$+7Xb+H*xNWp<sKD@H10x zVB8etSbY-ZS=CObh2|yr;oLIUh5DebtN9akUBpG)#6!HqNBn4jN$`|#CUm;&5?63% z;WfIm2z6)C3U?-6V|ON2F~w_lXED>A)sWh&f#%yJDVSRW&Z510VpbH&Gg9K(gR(ObRB-UOWLtxXAZ#-!;K5y z|Kk%6gD8*{AwtW!$A8Sh)+U zjYu{lxdF*GAj|xtXCSLG2dR7t1#Uvpgyd!<09QFBj-&<2El56%gwA3+)Er!f(gE$d zNbP)iX94;cY*_9AtUEYybt541D;fW%ho7DcZ+x$Q%cys%zUhNax6EzYG1u5MxAo?^ zhQ_&#@wqLx&TVe}drhD=y6ER(8<)69EI8y@+{DG|ubGSJ08dmB_K&p!DTz&EtrOz@ zSph#Y|=s(We2hd!Id^7JCU>_*@a{`5ETPQ7D{IJZgYjb zlSpnLol0UT1~)^4H($y8RS2SaRk8O zT-?gVt~>5vN>}K5?Mx{E^2<#gE-t#}rLB#McmGzoL9J4 zUDn^&3IZ{+cFJ|eZ=z=i-Z+JJiaz26eC8{M8-#)Y!JrkxIs{|@p9Qdw6>cCw4Fh8I z>T?J<7(osi46?>YXIpUhBAWPCI++s>XFeq3>)(rAgk+T9Gp|Uv(fSZUE0_A{5 zP%}d~wvjef{0LwRW{AZW@Q8vr^TAgDoqPMXT;9vj6A>wT4iQgLD1-_E4F-h^VF(6U zI&lcP!Rmxzbz<1ZS{+*-Cq0IY8CH=K3^`I$M$Zv~ran^EO?1_|i5eprB?N;|HzD|j z;TNy08@96u!JGSksGEEg+#n5_4-)izReT(icEIs;gm}#60WtIpaj~4HyHaHBu8@NI zxai~$bvGdRm7Sup2Y<_6B>Rx;2cn`9EJsOe_T52l0QNUBhKVY72iBbjaGd-V{Na1i z#*v<}1JlucL+)~zXfC>G41YIbd?-%*yezFCkMP%sAXGWIIFoz5X*UmBUf#OO^06^a> zh!rJuq`nX>Lo9LC5KA_vr$L6!DNv_pzfc-IqvesVC()oZH=SFm5|dyMZ0c z!D(7$Y_O&hPWXtNmNi-VGEQU7yKCh}?1sTY04l9muh?gQ1>08KY%7nVh!eO#FkS&J zw%XAJo4FIZuCo(kWJ8CXRr6 z3RICbqXkvuOrQeik48F32+}L8bP)6#^s(C#%&(OWf*Yxd%g_^H?kT95 zQ35NXPz-_73d0^h{P)(V{#^s$^4sa^G&ZLp2+ux1*b>d?z@ zH;TQ7!`fZm1JPC;p`g-a3E}Q=NIn6gV;8D5Nq$pPEjE`LeDKRL^9a8ETr-o z45J^kMp!j6QZQC{5qvy{Na!|1LOU?8sNKk5f{K;F)Lc$EibML+Z44*T%%Zdm*_8_B zr7371F#o8Xo~I7lQ<(O#B&2-`W^`ULbaPqMhT){O!>e2IDMx_BYaHR#%E$`!LL)Zz z^q}1|a$`>m+RxAzMs-wlIF*>I47ym}4}wrU{qD)LV~>iwpNiW#9P`Ilf?vY=5!dKf zO9$?s5xxiz2aYcw4QMWWixJd8*ag9yYX-ZlfD7gW4SC)ZYftPtmM;m7BZq(t8Gu3; zjO;*~C!ueUSU%x)maqp&9@`c9DJ7*rZlKwu`^#-K%R=|DE_!Wm0`_YIDVi9^Qkki` z^r-qoI+0F3CTH^7AvuJqPmkg;qq;qr?eE2!ZM#OT2Zn%|;-0|T0VJ4?aY;D9mgqKR z5Lh&Z>McMP-8|3#-R0svANx3-e}?-`Rr5NiWHBW_(%yq@VDvOduPa3}!=< zp)5bivy6lJBqO-5GJ@x}gFb6Ayx@p)`5usb_ex9K-}~#Wn|HQef9Kxv54UbD-uwGk z@Bi)hd>^3h3vpKUeJLf$&t%f6IT<9+7gLgy&dtR;)bO;JO3JB(4EGaxDJ81WizzuF z=Psw_5;HlG=J}I(`BLIiDk-GIY5IIhjTfH>d67vmlPtVEy!2#;;7B=zj+AT0DLAh> zC*2m=1+vE?yFunGvIk_ZMdm>ES!6HB{smv$uX6ZapG(e5`Mpp9Ydbvig)cu#TS0YC zlNVPV!qjw!rS=}+QQqkUy2!|^eot}S)_9hYjS>p0=N3B@cGjh9o2kIcXp>w@hpzpm zL+`VqQ*q96x`vq+T|!6Et+*8T$aBhA|vgw*pD{rF^uBWYcEY?m zeHXD$e2Rzkj$iRh8Ou}cd&dvG6TmWN?^w#%wT(}&;Vya}T~7$!bdc5v2qA%20$SSy z+ishnZb?tIw!!xC7d`NSh4Wr$fbEsHwn~flf4E!-jwI*PBh%UBNEW`i6_`oroFIMh zkD#~0K5(2nx55_2jtIKy)B;_PLDT-!K#3kL(cwAa>pw0;-;|--J zXz7V~NW^bfgki;~9WdL8_|d9dmvZ@xKvWYKGr6f`CNY=Hrc`f2O3tLvf*SgL@;)&8J|~Jf zQH|j&n@!4z)XVdk$zER^dsj+DWT$N(#Y~dmF9?BO_uiC*z-fZSaSy{NV~WP#yzP;SZO`s{FtP zhre|#gGvUiaswrqN;Q*RBg!;;70ns?1_Rpg#Mc(CExdYVX`;&aSGd9Q6p#wvU!JP+ zgBu+F*0l^O)rds-Rn(`fm9BAvJMiL+z3Wk zII$p>Y1@hUbyu=WJDN@`q&cymzzbm|s5!Av+fFQ`KT%J?iDCQr;V*!0TZm}yAN_y& z{xS0YF8LtNB6umrC@y-0&LXdP6rMnvuo9MjYUw#UXyaDGFe>(78FN$!d)xkQyDj!@ zfTw(b){$ZK{uHexY=G0lB>;%-wLHb9jM)Oa=o#a57d>9Dt+IWt% zJ)R@FB|X)~GXbnHro+%*K7sV9(1SfI>~Gn>JzD=h3qZD={yjkY_u%jPS09I%D!z5$E+d$-=*{{B_E#(&V?fWt@V8h+@dw0vnu1~`?06+K|Z zw46!h@f)M-U}}U~SaFC5<-{kD;F?jy<)Vnn1YL^w(TG?F5l7bxBCe7|{5HkINP2;& zZi<*ymmnr*WYs660s3KFLHYm}qyW!H5uO7krwajK%!R<9wls*Fs{jJ;GpV^1r?>|n zf=hbw5E86I1C~ESvHTekqamsVjRhFB&0Q;0WKaDKZY3waukj^reg2_RN%u0ePT`%lt!2nF{iMkVaM25e3Q@f6zVCa78q_TWH)hz}ho zh5kn5ZKs1hA<59zkAmMHTr0+hWx!^vYbj8T9j$Ord`GIpj@I}AV3+%VRQQ4AzAFF3 z28X|OErUu1tztPU)l7B`lw(I}_9`i8@XLKhS%U%FdOga?YHV=%Ks7c|;m%@>CTjdC zVDCbW&Q|zSccDgSH#q#QYZ+8BXcfy*sb;ckpd6c^*{h_W!QX`%X(kQkXWUuw42(Ye z)z4K}m;8oTKNt{cx{Ppxb=2g59C~O(>gP@rg@EQc76wZRz{4&l(z?kOCD8|Y~w+Oh42fXEh zKQFYod$v{2(=}E=@D#mDK=G2^2`WKYW10203CKe41fh4rSjOxfOBuU%4Cpo7Mb8L* z3K4CM6%wLCObKai!?)cw2Kv#TthEiUvw}tM0w3?p<62?+AO7=*Vm zx^43gA`|KJuz_LgtoI&RynN$wv$OtKB{Brgx~m_Yb(gQ?2WK5UxDn~AM*3=zBjsnS zk$B1d3n)8#V|FuiVCj#SK2-@EZSeQHi9XXrc>e}J00FlOH?({N2s*MOReorL!{54= zK_!D$xuMb(D%DJOjVROXRWxVl8w_Y~@;ww`)cC&gu_}MO!d<8gUqA}{@P#UWVS~fp zx}HPD8WAbKN{Ue4O4qmxEj-bO5K{iF@I>HYte%Kvz?)(8wdtbXOz__7u=8hc`iw0v z^klfyXu80Y`5*FR5nX8(p={Fy+bQVDf=!+*B;#6vT(dU*slk((_U-0X!#{y1Gc^&F zH(jRB15Xy(Mc4FXt!SEVtdjNGnGzQC<{!Qp=yU)5U_HFu1?$v9ldnSk$wQT2y zOF$oX3H#cvk#%g?)W~eDT}P48u26%0t!u3qTXnnW8PQOoTZ=>SLXWUt;WeKhHlu(R zOLn6uthcD1g3rfM6ofwxMnOP0fM{1ZNHA|iI0VQi(tv!-ey|%E0fKxEV;OT~Sn6c= z-5Ig5UGz-k)2kt$sL&_$D^UaawEHdDAs=jo2(|tjF*4Scg<&q?0S|yTZs$;HuT0mWSm}KJ-~GK zEIxJPDNVWj=}KT&2ZD7M(|Krz>FD&8xH`v#_igZi_W=4GFH1lw{E@N*sBDA7-@2AT zC4*MEr|$KL91LJ1}#vbX0pcZRj%*)Y2&ueuAwQK3kqe{P&XK`waIravDdCF zovHE=aV8t!o)nsu79utEf*|D_!G8T0qjTK@j%;BqX&vIYoja zg2x_C;@Ka;chh0nh(=h6(bcBQ7FHVY)eeY05>~c!Uk$L*v~LqiF~K4L0#nlls+yVv zR<_nPVP$K*-C-sAZ*o~`XXkwF0wj+MD?KLsP>eSydI44fxK{#3*yN{{y4WlppwBRD zvIonU-~l%M+U>J$i+!6udw5F`U}ej8ez*km;ojC|%of;1&ww+#=ox_0*o5?xO-R26 zdqQ%%eQm(VCh$jsVFdaF0Q&UM&845bz4h(#*6ZbaKe&1S-5+ef^{xf(U{r`^(mkjq ztn(l+GM$^7!@V2WH7fWrsiZiUp1U|STJX+`xoJ42Iy4Fgx|}m&E~`4^Tod@Q-K&CF zjA>K+Q>cOX2S^agh|eN<4#_8gw1^D-43*Ap&w=7dQiQemKwzIf61rtoc zJCmME&P}Iq?$+58lnY68ka+{Zd{b0V`J@|OwSDAz*^jFqdmrl8d$doI=N!|pU zaj?w0iJ4?NBNdJ{&c2USyo?j^4v=4aWWLo*e02G%)ySETbS8%5vt;gjAs$e<1UVC( zNT|Lg{5l+ zoGs7R8BFh7+#zXwu*Wrer%-1gdGDniOzNkjuJPsLs5gH17^;oe2P3XycP8r$B=3FY zLriG3utpH)$U>rW`mu3xmPGAN(1Yf~^hgK5pkXofl9+}AJYFM9^=NtID^T6Eh#H+q z!U>%DG&wO%|Hp+im>QD4o=Ik=FQu{z!*IBqG@u&7K`adeXqZ9H5_KWFVWidOpoV5k zKH#3F{c~P?32tB{NqPcEy@O@he|I=o=P%t1%U)%^8T>gD|2fnDFU<2*=J{W`MV3AG K9}H4r?7snQtW^;J diff --git a/backend/tests/test_kpi_422_baseline.py b/backend/tests/test_kpi_422_baseline.py new file mode 100644 index 00000000..ff809028 --- /dev/null +++ b/backend/tests/test_kpi_422_baseline.py @@ -0,0 +1,40 @@ +""" +基线测试:KPI创建接口缺少必填元数据字段时返回 HTTP 422。 + +场景:POST /api/cma/kpis 请求体不传 formula 字段(数据治理规则2: 元数据必填), +期望返回 HTTP 422,且 errors 中包含 formula 相关提示。 +""" +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session + +from tests.conftest import create_test_user, get_token_for_user, auth_header + + +class TestKpi422Baseline: + """KPI创建缺少必填元数据字段 → 422 基线测试""" + + def test_create_kpi_missing_formula_returns_422(self, client: TestClient, db: Session): + """不传 formula 字段时,创建KPI返回 422""" + create_test_user(db) + token = get_token_for_user(client) + + # 构造请求体:其余必填字段齐全,唯独不传 formula + payload = { + "kpi_code": "F_BASELINE_001", + "kpi_name": "基线测试收入指标", + "dimension": "finance", + "target_value": 1000000, + "unit": "元", + # 注意:故意不传 formula(必填元数据字段) + "data_source": "测试系统", + "data_owner": "测试管理员", + } + resp = client.post("/api/cma/kpis", headers=auth_header(token), json=payload) + assert resp.status_code == 422, f"期望422,实际 {resp.status_code}: {resp.text}" + + # 校验错误信息中包含 formula 字段 + # 注意:FastAPI HTTPException(detail=dict) 时响应体为 {"detail": {...}} + body = resp.json() + detail = body.get("detail", {}) + errors = detail.get("errors", []) if isinstance(detail, dict) else [] + assert any("formula" in e for e in errors), f"errors 应提及 formula: {body}" diff --git a/docs/cma-data-analysis-3-principles.md b/docs/cma-data-analysis-3-principles.md new file mode 100644 index 00000000..87e5c99f --- /dev/null +++ b/docs/cma-data-analysis-3-principles.md @@ -0,0 +1,188 @@ +# CMA 数据分析三原则(找异常 / 读动机 / 理关联)——交付方法论 + +> 版本:v1.0 | 2026-08-26 | 提出:yanxueBot(user-insight-9-perspectives 视角5) +> 落地:项目Bot + 财务Bot | 状态:交付方法论(P2,不新增功能,用现有CMA能力) +> 配套模板:`docs/templates/cma-data-analysis-report-template.md` + +## 0. 为什么做这套方法论 + +客户买了CMA,看到的不该是"一堆报表",而应该是"一个故事"。 +同一个数据,讲法不同,客户价值感知完全不同: + +- 讲数据:营业收入8.08万 → 客户:"哦,知道了"(无感) +- 讲故事:营业收入环比暴跌90%,触发红色预警,根因是渠道补贴冲减、真实毛利51.4%被账面口径掩盖 → 客户:"那怎么办?"(有行动) + +三原则就是把"数据→洞察"做成可复制的标准流程:**找异常(信号)→ 读动机(原因)→ 理关联(对策)**。 + +## 1. 三原则总览 + +| # | 原则 | 一句话 | 对应CMA能力 | 输出 | +|---|------|--------|------------|------| +| 1 | 找异常 | 偏离预期的数字=信号=机会 | 预警规则 alert_rules(static/trend_down)、KPI偏离看板、预测偏差告警 | 异常清单(KPI×偏离度×红黄绿) | +| 2 | 读动机 | 数据背后是活生生的人 | 业务访谈、场景还原、口径拆解(多模型对比) | 动机/口径解释(为什么会这样) | +| 3 | 理关联 | 串联行为与市场,理清因果 | KPI因果链(positive/negative)、模拟推演、四维度联动 | 因果链图+对策建议(所以怎么办) | + +记忆口诀:**"哪儿不对 → 为什么 → 牵一发动哪里"**。 + +--- + +## 2. 原则一:找异常(不只看常态) + +### 2.1 定义 +常态是背景,异常才是信号。客户最值钱的信息不是"这个月正常",而是"这个月不对劲"。 +异常=偏离预期(目标/阈值/趋势/历史同期)的数字。偏离越大,机会越大(无论好坏)。 + +### 2.2 CMA落地工具 +- **预警规则**(alert_rules):static(绿/黄/红阈值)+ trend_down(环比跌幅)双引擎,现行78条 +- **KPI看板/偏离视图**:实际值 vs 目标值,红黄绿一目了然 +- **预测偏差告警**(rule_type=forecast_deviation):实际 vs 模型预测,跑偏即报警 +- **多粒度对比**:月/季/年目标(target_calc_type)齐比,避免单粒度误判 + +### 2.3 落地步骤(30分钟) +1. 拉当期全量KPI实际值,按红黄绿筛出红/黄 +2. 环比/同比/目标三个维度排序,取Top5偏离 +3. 只保留"偏离有业务含义"的,剔除口径噪音(如导入错误、季节性) +4. 输出异常清单:KPI名 | 实际值 | 预期值 | 偏离度 | 红黄绿 | 首次出现时间 + +### 2.4 客户话术 +- 开场:"这个月有X个指标在警报区,我们一个个看。" +- 提问:"这个数字偏离了目标X%,您觉得是市场变了,还是口径变了?" +- 升级:"连续两个月trend_down,这不是偶发,是结构性问题。" + +--- + +## 3. 原则二:读动机(不只看表象) + +### 3.1 定义 +数据是人行为的痕迹。同一个"毛利率低",可能是成本高(真差),也可能是返利未确认(口径差)。 +不访谈、不还原场景,就敢下结论 = 报告是废纸。 + +### 3.2 CMA落地工具 +- **业务访谈**:按KPI问业务负责人"这个数怎么来的、最近变了什么" +- **口径拆解/多模型对比**:账面口径 vs 管理口径(如Model C),还原真实经营 +- **场景还原**:把数字放回业务场景(门店、客户、合同、渠道) +- **数据血缘/来源核查**:确认数字本身没错,再谈动机 + +### 3.3 落地步骤(60分钟) +1. 对异常清单每条,先做"口径自检":数字对了吗?口径对吗? +2. 再问"业务自检":最近有什么动作/事件影响它? +3. 访谈至少1个业务负责人,记录原话 +4. 输出动机解释:异常KPI → 表面原因 → 深层动机(谁、为什么、什么场景) + +### 3.4 客户话术 +- "账面看是XX,但您看这个口径拆解——实际是XX。" +- "我猜是XX原因导致的,对吗?"(给客户一个可确认/可纠正的假设,不要下结论) +- "这个数字背后是哪个业务动作?是主动调整还是被动结果?" + +--- + +## 4. 原则三:理关联(不只盯增长) + +### 4.1 定义 +单点数字会骗人,因果链不会。涨跌不只看自己,要看它牵动谁、被谁牵动。 +理清因果 = 找到真正的驱动杠杆,对策才有落点。 + +### 4.2 CMA落地工具 +- **KPI因果链**(kpi_causality):positive/negative 方向 + strength 强度 + lag_months 滞后(现行28条:positive 20 / negative 8) +- **模拟推演**(POST /api/cma/kpi-causality/simulate):改一个KPI,预测下游影响链 +- **四维度联动**:财务/客户/内部流程/学习成长 跨层传导(BSC四层泳道) +- **战略地图**:节点间连线即因果,从KPI追溯到OKR/战略 + +### 4.3 落地步骤(45分钟) +1. 对每个异常KPI,查上游(谁驱动它)+ 下游(它影响谁) +2. 标注强度(>0.7强相关)与方向,找出Top3驱动链 +3. 用模拟推演验证:"如果修复这个驱动,下游能改善多少" +4. 输出因果链图 + 对策建议:优先动"强驱动、可干预"的杠杆点 + +### 4.4 客户话术 +- "这个KPI不是孤立的——它由X驱动(强度0.9),又牵动Y。" +- "真正的问题不在表面这个数,在它上游的X。" +- "我们建议先动X:按模拟推演,X每改善10%,Y能改善约9%。" + +--- + +## 5. 《CMA数据分析报告》结构模板(含三原则应用位置) + +完整可复用模板见 `docs/templates/cma-data-analysis-report-template.md`,骨架如下: + +``` +一、经营总览(一页纸) + - 核心KPI仪表盘(红黄绿)+ 一句话结论【找异常入口】 + +二、异常发现【原则一·找异常】 + - 异常清单表:KPI | 实际 | 预期 | 偏离 | 红黄绿 + - Top3异常重点展开(趋势图+阈值线) + +三、动机解读【原则二·读动机】 + - 每条Top异常:口径自检结论 + 业务动机(谁/为什么/什么场景) + - 管理口径 vs 账面口径对比(如适用) + +四、因果关联【原则三·理关联】 + - 异常KPI的因果链图(上游驱动/下游影响,标注强度方向) + - 模拟推演结果:动哪个杠杆、影响多大 + +五、对策与行动 + - 按"强驱动+可干预"排序的3条建议(对应责任KPI) + - 下期目标修正建议(如需) + +六、附录 + - 数据口径说明 / 预警规则清单 / 访谈记录要点 +``` + +每章都在"讲一个故事":**先让客户看见异常(信号),再让他理解为什么(动机),最后带他看连锁反应和对策(关联)**。 + +--- + +## 6. 样例:陕酣客(陕西酣客文化传媒,entity 1)三原则试跑 + +> 用CMA现行真实数据(2026-08-26 查询):预警规则78条(static 39 + trend_down 39)、因果链28条(positive 20 + negative 8)、营业收入实际值2026-06~08。 + +### 6.1 找异常 +- 营业收入:2026-06 实际129.32 → 2026-07 81.08 → 2026-08 8.08 +- 环比:7月 -37%,8月 **-90%**;静态规则 red < 4000(触发红色);trend_down 阈值10%(远超) +- 毛利率:账面口径 0.13%(静态规则 red < 30,严重偏离) +- 结论:收入崩盘 + 毛利率异常 = 双红色信号,值得深挖 + +### 6.2 读动机 +- 毛利率账面0.13%的原因(访谈+口径拆解):白酒经销模式下,上游厂返利/补贴挂账未确认、渠道补贴冲减收入,账面口径失真 +- 调整为管理口径(Model C:还原厂补+剔除冲减)后,真实毛利率 **51.4%** +- 动机解读:不是经营变差,是"返利确认节奏"和"渠道补贴政策"两个业务动作主导了账面数字——数据背后的活人是酒厂结算员和渠道客户 + +### 6.3 理关联(因果链实证) +| 因果 | 方向 | 强度 | 含义 | +|------|------|------|------| +| 毛利率 → 净利润 | positive | 0.96 | 毛利是利润的第一驱动(强) | +| 上游厂补率 → 净利润 | positive | 0.9 | 厂补确认=真金白银 | +| 渠补率 → 净利润 | negative | 0.85 | 渠道补贴是利润黑洞 | +| 费用率 → 净利润 | negative | 0.8 | 费用管控第二杠杆 | +| 营业收入 → 净利润 | positive | 0.1 | 收入规模对利润贡献很弱(反直觉!) | + +- 洞察:表面看"收入崩了",因果链显示**利润真正的驱动是毛利率(0.96)和厂补率(0.9)**,而收入规模只贡献0.1——所以对策不是"冲收入",而是"确认厂补、压渠补、管费用" +- 模拟推演示意:厂补率每改善10%,净利润传导约 +9%;渠补率每压缩10%,净利润 +8.5% + +### 6.4 一句话故事 +"这个月收入掉了90%,账面毛利0.13%——但拆开口径,真实毛利51.4%;因果链告诉我们利润的命门是厂补确认和渠道补贴,不是收入规模。所以下月重点:催厂补、砍无效渠补。" + +--- + +## 7. 交付流程(标准三步) + +| 步骤 | 动作 | 工具 | 时间盒 | +|------|------|------|--------| +| Step 1 | 跑异常清单 | 预警/KPI看板 | 30min | +| Step 2 | 访谈+口径拆解 | 业务访谈/多模型 | 60min | +| Step 3 | 因果链+对策 | 因果链/模拟推演 | 45min | + +铁律:**没有访谈就写动机 = 编故事;没有因果链就写对策 = 拍脑袋。** + +## 8. 验收与自检清单 + +- [ ] 报告含异常清单(有红黄绿,不只有常态) +- [ ] 每条Top异常有动机解释(有访谈/口径依据,不是猜测) +- [ ] 每个对策能追溯到因果链(有强度/方向/推演支撑) +- [ ] 客户能一句话复述故事("收入掉了但真实毛利51.4%,命门是厂补") + +## 关联 +- 来源:user-insight-9-perspectives.md(视角5,一组数据一个故事) +- CMA能力:alert_rules(78条)/ kpi_causality(28条)/ simulate / 四维度BSC +- 案例:陕酣客(entity 1)—— 账面毛利0.13% → Model C 51.4% diff --git a/docs/templates/cma-data-analysis-report-template.md b/docs/templates/cma-data-analysis-report-template.md new file mode 100644 index 00000000..e59fb77f --- /dev/null +++ b/docs/templates/cma-data-analysis-report-template.md @@ -0,0 +1,92 @@ +# 《CMA数据分析报告》交付模板 + +> 用法:复制本模板,按客户数据填充。三原则贯穿全篇——找异常(第2章)、读动机(第3章)、理关联(第4章)。 +> 配套方法论:`docs/cma-data-analysis-3-principles.md` + +--- + +## 封面 +- 客户名称 / 报告期间 / 交付方(博海科技)/ 日期 + +## 一、经营总览(一页纸) +- 核心KPI仪表盘(表格或卡片,绿/黄/红三色标记) +- 一句话结论(用"数据+故事"句式,如:"收入-90%但真实毛利51.4%,利润命门是厂补确认") + +| KPI | 实际值 | 目标/阈值 | 状态 | 一句话 | +|-----|--------|-----------|------|--------| +| 营业收入 | 8.08 | red<4000 | 🔴 | 环比-90%,崩盘 | +| 毛利率 | 0.13%(账面) | red<30 | 🔴 | 口径失真,真实51.4% | +| ... | ... | ... | ... | ... | + +## 二、异常发现【原则一:找异常】 +### 2.1 异常清单 +| KPI | 实际值 | 预期值 | 偏离度 | 红黄绿 | 首次出现 | +|-----|--------|--------|--------|--------|----------| +| ... | ... | ... | ... | ... | ... | + +### 2.2 Top3异常展开(每个含:趋势图+阈值线+偏离说明) +1. 异常KPI A:趋势 + 触发规则 + 偏离解读 +2. 异常KPI B:... +3. 异常KPI C:... + +## 三、动机解读【原则二:读动机】 +### 3.1 口径自检 +- 数字准确性:来源表/导入批次/负责人确认 +- 口径说明:账面口径 vs 管理口径差异(如Model C还原) + +### 3.2 业务动机(每条Top异常) +| 异常KPI | 表面原因 | 深层动机(谁/为什么/场景) | 依据(访谈/口径) | +|---------|----------|--------------------------|-------------------| +| ... | ... | ... | ... | + +### 3.3 关键访谈记录要点 +- 访谈对象 / 时间 / 原话要点 + +## 四、因果关联【原则三:理关联】 +### 4.1 异常KPI因果链 +- 上游驱动(谁影响它):KPI | 方向 | 强度 | 滞后 +- 下游影响(它影响谁):KPI | 方向 | 强度 | 滞后 + +### 4.2 模拟推演 +- 场景:调整X KPI → 下游Y预测变化(表格) + +### 4.3 关键洞察 +- 反直觉发现(如"收入规模对利润贡献仅0.1,毛利贡献0.96") + +## 五、对策与行动 +### 5.1 优先行动(按强驱动+可干预排序) +| 序号 | 行动 | 驱动KPI | 因果依据 | 预期效果 | 责任KPI | +|------|------|---------|----------|----------|---------| +| 1 | ... | ... | 强度/方向 | ... | ... | + +### 5.2 下期目标修正建议(如需) +- 目标/阈值调整建议 + 理由 + +## 六、附录 +- 数据口径说明 / 预警规则清单 / 完整因果链图 / 访谈记录 / 数据来源 + +--- + +## 客户沟通话术库(按场景) + +**开场** +- "这个月有X个指标在警报区,我们一个个看。" +- "先看结论:整体X,但有两个信号值得注意。" + +**找异常** +- "这个数字偏离目标X%,您觉得是市场变了,还是口径变了?" +- "连续两个月下降,这不是偶发,是结构性问题。" + +**读动机** +- "账面看是XX,但口径拆解后实际是XX。" +- "我猜是XX导致的,对吗?"(给假设,让客户确认/纠正) +- "这是主动调整还是被动结果?" + +**理关联** +- "这个KPI不是孤立的——它由X驱动(强度0.9),又牵动Y。" +- "真正的问题不在表面这个数,在它上游的X。" +- "按模拟推演,X每改善10%,Y能改善约9%。" + +**收尾** +- "一句话总结:XX。(客户能复述,才算讲明白)" +- "下月重点盯X,我们把它写进预警规则。"