From 1a6ff54199da375047065ba94362b16b22c0cc41 Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Fri, 9 Jan 2026 15:25:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(create=5Fmcp):=20=E6=B7=BB=E5=8A=A0Dify=20?= =?UTF-8?q?API=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=AD=97=E6=AE=B5=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入DifyAPIError异常类,提供详细的API错误信息 - 实现对file和file-list字段类型的区分处理 - 添加网络请求异常和其他未知错误的捕获机制 - 改进文件上传逻辑,支持单文件和多文件类型 - 优化错误信息返回,提供更友好的用户提示 fix(create_mcp_utils): 修复文件参数处理逻辑 - 添加is_list字段标识多文件类型 - 改进文件上传失败的过滤机制 - 修复文件字段变量查找逻辑,使用映射提高查找效率 chore(config): 更新项目版本号至0.1.2 - 将PKG-INFO和pyproject.toml中的版本从0.1.1更新至0.1.2 - 更新默认配置中的app_sks值 --- ...wcai_demp_tool_server_dify_to_mcp_test.log | 0 .../PKG-INFO | 2 +- .../main.py | 2 +- .../pyproject.toml | 2 +- .../__pycache__/create_mcp.cpython-312.pyc | Bin 9139 -> 10393 bytes .../create_mcp_utils.cpython-312.pyc | Bin 12233 -> 13039 bytes .../src/create_mcp.py | 64 +++++--- .../src/create_mcp_utils.py | 50 ++++-- .../dify_workflow_schema.cpython-312.pyc | Bin 8572 -> 8871 bytes .../src/utils/dify_workflow_schema.py | 31 ++-- .../workflow_server.cpython-312.pyc | Bin 13560 -> 15245 bytes .../src/workflow/workflow_server.py | 47 +++++- .../logs/lzwcai_mcp_sqlexecutor.log | 155 ++++++++++++++++++ .../logs/lzwcai_mcp_sqlexecutor_daily.log | 155 ++++++++++++++++++ .../logs/mcp_services.log | 44 +++++ 15 files changed, 490 insertions(+), 62 deletions(-) delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO index 00fbad7..bb8c4fe 100644 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO +++ b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: lzwcai-demp-tool-server-dify-to-mcp-test -Version: 0.1.1 +Version: 0.1.2 Summary: 这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。 Requires-Python: >=3.10 Requires-Dist: httpx>=0.28.1 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py index 92a59e3..959616b 100644 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py +++ b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py @@ -19,7 +19,7 @@ def setup_mock_arguments(): # 默认配置 default_config = { "base_url": "http://192.168.2.236:3001/v1", - "app_sks": ["app-Oo0QRJismgQADRSt8Bj0RXWB"], + "app_sks": ["app-IfJayK9uu5oTo54Rpr2AS7wl"], "mode_type": "workflow", "transport": "stdio" } diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml index bb90fa2..af294ed 100644 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml +++ b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "lzwcai-demp-tool-server-dify-to-mcp-test" -version = "0.1.1" +version = "0.1.2" description = "这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。" requires-python = ">=3.10" dependencies = [ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp.cpython-312.pyc index 099ca207a68198f6cea8f25f479e378c9f5a5ee2..3eb52a1c40c8bd9e582d6d669f81272a8460a05d 100644 GIT binary patch delta 2944 zcmai0drVu`89&F@1{+SDl4@7WM|^bX)K z@)l^ubqY(mo&u}rvx=wSPw|I}I;=|xE1ZPMnm(0s0%qu_5e5G zxg=v9dk1d8e`O1DB?tQF{2?yL`+@->;){gCK>_U{iY?PMYCGISZA2A`8|~sl5usH^ z*2$qF!VBbgzgmF@H5Jf5eNj^np$h*+8&vEaP7()Gi(gV)7cbU2o+L%AghEuqd(I2Lly0;zSlEvS2E zlouj`m`3vEyCZ>cNDy`3PoTS!kf>S{(|+I&MtRz!kXtLYfmF1YCQy&B8=k1Aty^Re zFOVRu-M(-%B0`@Cdqv2LYAL#JHMl@K3GF@W2Fk-8qba?UFoWbL{7iTm z(dM_d&bx-HW?fY?h1Z3f6(9G`xthN)9#~!aQl5Oo^Aa z#qI5Ly5~rq6djMw+skL|*X^LCRKi5>ml@)*c zrU2-2?g2U3?;SSdm3=SoJ6kg}Q8iH*FY;W=yJm=6cg?Z87i?uy@UrTXYVvqI=aJ$4 zUmC6R#)4U6!DRJ}dd|2zZfN|1ZT#mQt$AMSoYgw#wUx8l%7@%0yw=V5XAZ@!o;mi> z1>2SnDyMp;tEY$m)}ATC(^wRn=r-KGk_(qnWv(hh%XhLqUie zW(SpPJYY{9bF+pLH@8vZW*tl9&r}{+lb*SiPKjGAC2r{zR5me9)@iNjYZ)xYf497; z4$;q#wr$4=@=?BR&4a(2{>=I-8SKVC&M8jmB@Bw;k8(<_{Zyp6i;N)ftN*AcsFf_Y z1TIdO=ROW$EgrCK%cf3IjH+r#ag+)?>g7=>u|mi3?`=770RPEmwMoA8Ld1`Pd?dn` zB2q-Of}f|>L)c|6v%gBkV{|%BrvW-8Xetfk9=oA-jEdA3dWlZbhG-L5E4TXk`o#af z5=BG4E`K0|#)-lw_=^3t92b#9rB8x=J~z|c-0X&^i=ggm-0jFM$)*a7|7a_sS2u*E zCE1p!qI-+Y2%U3;2nVI0Z7O559JOLDH`N=q8WnG3$M_C!_Hdfk6Lle*lYd7f=8I delta 1713 zcmY*ZUu;uV7(b`CUAwNmy=%L6UAz9-x^CT|TRRapH<%K@gjln<2{gu9dhfQ5_O_gR zD-J3{BxW-PHcx_^%rM_DLjtKU_+U^UeBfo15lSD7iM-n|6aU1Z=iIJ{C%NDK-QV~7 zzH`oZzjKyHi-*{|Mx!2(arMsb)J69q+YI{_J8#XZfZ!BdYmmACh_&+i3n1p^oA`zc zFyo zH?F8+okIKcC=PoqcoyoktPm9R(_{D|bTpbSsB6>5dWh;+P1VB*JNZT-#KY5C0QhRm zo6k;F#U#^P_z85tGMmYz1v!-!gKET4&9<Ni>Jhi zEFxa&;h-;)o9loBJ@~s!03Un7awDt z4WxwNee!HQQQ=uO4389Vu_F*~nNGkF9Orzn2fxRSbQcWQ4Qnc*Bct{oA^2#g8Wi-e z0ohRX05IK(q27YIM&9SCalEMju7J-}wYc-}OXd^|a`?98a-Fi~5FWQK)hWe$6>nLQ z<1{-io)ELAM6aC9rU$)24n;^h)Q@{@4sMXZGDB*C_E2&FAGNs$UZ8M@dK$)rcjWz9 zQ5HnGtUZ-LVoIDW>t9c#^TGjyvM6Y*mgi*}vJd}l8--CEwcB?|M#9i?KONP&b5jYC zPYdy6BAt%Y6`=$X7RV!g3h@p5H?xndt_RlOhBa6+SW1>aMFX0;w*b?mpR2DJfTd|p zTe7$w1E$_Kx4&dEFYKN#ta~E&Op#Jc>(az>&uVI;rEfv|kZY=FiD*0KVS~BU?73=O zHeNA32GDp2E~*#cLb$})KVcWymHY!w--f5}=EUrs@TTX$eePg|p{fcCIQty?n-&-? zm!k90r6a3js|VM+kFPtEn+Be&k+Lhh9yoVwICtC(tnU*yo!L!8?q`#2j(x;7FYUeN zzh+x^^xb2_>$>otN@33LcO4jp-+NwilUH*4Fq0Yppez2=QdYqrzTNt{k*8&$t>Qh0 zym->>gSU(G?r&9a0w4Bu*QJREW$+na$dRQ`BBsnplCa(D=V;V-zMb&LV#RkD!T~(g zzQ;+Eq70R?nPe^`3FvJBg-8@Si7&OgAi~$%9d0F8qa-H~IgzpuLfO1b{ymaubRPfG z-s_p8Fh|WvY7jM5H$`W0*l!uUL?O+E-lay_(EmGQWMrh;89I~~-kE4#`6Sya*VDbpGdV%3cCro$ zC-Z_NOZarh4|_;TF_97C@oL-C8%4PTke{Man#IhgCT5fyB9rBXI8BApcqI@RR*sKy zaP;YvBr9DkmDLoZVH!_whI0LX9o|a6)qE-`qjCICz~4<DxL->`>LHuFo)uu3vl=Q|5L*Fg#RqJvO!T80lE{-r;3{Y7sDvG8cKGW5r Hrdj_5O=PYW diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp_utils.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp_utils.cpython-312.pyc index f2263ac4c1ca037ca5ba0f9e7046205039bd2209..aec247e411c02e5479469bdb62bacff8c3bbfa7f 100644 GIT binary patch delta 3015 zcmZ`*dvFuS8NWT99@gV5Vav82wk}|7$uGdcp%@z%!jPB}9wm+eV}#7f&e^i8?49De zIboA#%A=;3?j$%6QjtkJZfP7Cw;cn9hG~X`wlg_n1BpVX>5OIgCj*6ars1DPVrfC7WSZfNVtRLS^&0W~u#T8vM`HLjsr z*I@r9-Xfb`1yIYgGG5nVV?LX3L@;RMt$0P@Ri2ddFp7z{lEO0?$R_kPQwdDSrFH$l zp3!>rmLK)NESn)K=`%9fobkakU0#ZUPAMazI`h$9t

=Lmjs4raMU6u|~`yTRI)+ z5}jkq$=KrT>=~{vH^YTsuvE4jp$E(G*09))6czeGE1_7j?nKEOR6Tub)og}gZ# zvKcw%J7Y5^s=Gxo(4td7~9tg6u|lR0roo{XMs%jEs|@SG}N@`jKWE)eC%u(FbGabyvZ>3a(TT{UJe=N^7~bfshy+J7oAd-H>3T zkx);7_qPk7pkLq<=JaFU@1ME-`(H&% zQK6}@WOn~h^58F30rzimGP#-Y->T{feLCz~&jbjH^GF*1_dJNB`IdUrhfhmARw%(gk=6Wy?OI&l1g_hgPS*?thu2N! zm5$ga^Qshk)4&EyZn}zw_uTMQPkO4SJWHosH40OJe!?yXHRuA{3B1Ug{b_%B?e!ut zzIlA}pPJ4#DbH@XR{x^Xy7NYB+hl8-vip^5t?<|@N|88Z!sLtm`8RW&N7o-&A73`I ze572d*{D3VX*zfF7rE{kYu-`Q5z}x-T!^OLH(zHuy z>r%R<>)1R1YP62KVL+KZ3RCp$y?#LDy#&6!w-rzx3H;|9H|nz1ZF>o~=MrUU)H8ps zt!%W>AA1=>D_51{*JTI!xLlamXx3d`YQyvs9Yd(G5c5Crt#aVk6|^{>>`UvLOnw=5pD>D207_!7AiWI9nSyS#&tA5nh{$w-NF*5MeR{Z*NS-F7 zijZdrsUc)JCU};lV7EvRDZSy403ckDAp>LjyazO>L$sqBTtGDr-@FDQTa8IV6A1VD zXt)tY9PaFt-LphS$S6AQSX%rXUhPgFY`ml>GT{WC@0Z|f;V30*L?`;Mqr^KGhx##>NJ$CydO71&1 z8=pu{9KJJg%t%^W$&vF|NnnKYrVn=gi!d7W)pDwSa{tlWCr4B(uBCO3)gyxx{A4Vw z=aL_uy?y3n+T^tux|^SicDkrgE%EC10;bPbCIf+*9KWfGzPir%ct?oryuUJ`NUO z9Y}76=)$?$+!Kn)LvLabRBMZxQ=0D#+&aHMIWSSn`51N9R}o+MCg`Ui8F-%#Ruc{x z^aR@jg$^Mc2>CJCMT2k|k))=dOoB)TQtgO3?D$@#AR!;&pE!!i0QE19GP`aX*u(4r zb~r!oh?|u1%}V|crwv;^H?aTIvtN0aDF)Z@^E)OD3yGfTfN7{>SQu^@$vW*l?NnB5 zpKjTy_`_2zJ<9fo68W)```R^wjM2yUy>T$woH!y8zLl+CZ;JL9|g+|)? z@Ytp6M}W)8Nfot-q)kp0E(+G6Qm$0njK97@Tev;g`YLyUE-J+gd{ke+FPPFN+d$L_ zV-xydK^4sc^!Ei@HWE#IqeQz?eN6)k=3yuypr-bbxPaRUX(40>CJ8;Z=?=iY$I%4! z7rV7>SpHxvUc5jD{OIGd3kBqI+~CWFsiFv&Wr}*cyrB{ADwVLMt}m%C67=&yDIkPI zIG^yfVk9I;_$5Ih0)51kTtz+;B!9gU#z+8)LEayQA=r+ds%R}+a#Bq2Nlm2lL1uV{sUjEs`pl0r1?D=yG7aw*zE@XYLml Yf*`o}eUOhiOFbax{u)pV3R15A2Omx~fB*mh delta 2256 zcmZuzeQZ6hrVYJIFKo4iTmkJzRA2Nw8aL)yA?p5xL? zv-E!V-gEBn+;h)8=g3#1zYBYQ?{+%~{5HJ5PW^MoQIDU_9&P?5^ip20PCSQ+A1@<+Q}-q}8QXT|Plx zl1@=PgAht|K3G=4OIq!+MJETG{NJIPnqKl89quAzw*Z!`X`2qLLqwxfF0^qctWOZd zuG_$p<(8$SyI_+L9U0;&v9M0tb-PxFOwplR`LwiQkNtmqu=kSTUO3C4xOK-N%4Ef( z!+awcP)bk&XZsW{%+QO@r6$p*JNUhFpu<<(d~4M0MMB!C zJDDd5AuIfJCqE$v_~B4FKQCFhA$#pkMZzP}=H=xw-F2XXAC$`Wlq(gdAVBzAew}1h zK?;GeKpwN3)f4tOdGUs@U`jU?$Wa#j)6nRX}P5_+f>o#?AH<6V^u z6u0Q^qWjBV;C`a`)9%OIa73yw8>i#sC*u8NoQA4XS2CYj|41_#`!j-&YUPuzy49&f zEFMw9`_))9tSa5XtY^985wX8x3;)n{M~x?IiN!}oqAV+^WBcP-X>U9p%Zjn+7!SCA zPCfiB_jkF~GumFBVDWuXO^X)HYGE}Q9nI3H<^lJE%YHZCD9-#&bEGD+inDQ8j>H7`@HLEJO82g01fiDy`MbZvhMW3i_Pl| z@2bCgD`%WXoySLJ)!8BAwg-%%_@9Qfi$mHqLQ-_A0K+!n9^pFVHT@B;hG;(ybpyf; zLVSb54GZzL8Erl1ZJ&gUU8?bL@{%`o?Oxh0S6_RO5?6yE)&Fa8l#<$A^q)8OQc_;G z$OlUA?z$Zuf1kLviPDF`_H8!dBaehwwxtb*k2h@sykN6$7v&4S=50Olg&ujkn7Lf~ zf>gB{m|2IGjIg9Ot|lL1mbe24Vq|P0B(Uv#G*DY& z@;0Cj;TQaPpsQmu%=Rs1GE51v!-r*l{ZoJ$qKm9W7e}a0bXq9Gm5mbg3PGoUB8st; zmS^o)HUG4>$@!94Olk_{yRrp&vdM$S^#6Dh){rfAiSmlNHG95#YWm7ApSU{zVz0V? zBDiI+FZkt)hx5mtS~&Z5?l&{}SKrG0>Ste`J@VDbN!YeM{!veCRhL6|8!hV%8-F5LR%f(qKJP#8Fn+Mx7)}@mCoT>@V(fm@ zDA&KRr5AW4Sivs`Tj=JDeT74~>zgbpIB5DNW}Rkd*{?=pidM+mRy+|?21C@v`x=|& zL0B0))%Y0Ly3u$}s9y;J$iwf$z|8&FW^p?T`uJx}YlZ(1{&mxi0o<7twbUrXYPl5~ zsFwj0n5BXy*bao92)h8XR^W_A*u?Tu;d9Nk@(}3%p824;(L#UB^X=#SRUqgOl{52- zSg}i2cf1_`pj!4Clb-H5=}ww|f>vBWkEwgt zn#LH0mhIu6-@azh>?I?(#_n3^0a(lC%QK8R!@HV?u&p@TjDS(TY1=p&S5mQP4;zOi w{#(#y_&{f}t)J3{#l1os^)BwFCG@VvRh2YK7dxv!(A7Z8uJ_Vz>NhR@7fUu}L;wH) diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py index 5ca8b95..57f818a 100644 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py +++ b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py @@ -17,6 +17,7 @@ from src.difyTaskCall.task_instance import TaskInstance from src.utils.dify_workflow_schema import process_user_input_form, extract_file_fields from src.create_mcp_utils import process_file_arguments from src.utils.logger_config import get_logger +from src.workflow.workflow_server import DifyAPIError # 使用统一的日志配置 logger = get_logger(__name__) @@ -147,29 +148,48 @@ async def handle_call_tool( processed_arguments = process_file_arguments(arguments, current_tool_file_fields, dify_api, name) logger.info(f"工具 {name} 处理后的 arguments: {processed_arguments}") - responses = dify_api.chat_message( - tool_sk, - inputs=processed_arguments, - ) + try: + responses = dify_api.chat_message( + tool_sk, + inputs=processed_arguments, + ) - # 初始化 outputs 变量,避免未定义错误 - outputs = {} - for res in responses: - if res["event"] == "workflow_finished": - outputs = res["data"]["outputs"] - break # 找到 workflow_finished 事件后退出循环 - - # 构建 MCP 输出 - mcp_out = [] - if outputs: - for _, v in outputs.items(): - mcp_out.append(types.TextContent(type="text", text=v)) - else: - # 如果没有获取到 outputs,返回错误信息 - logger.warning(f"工具 {name} 未获取到 workflow_finished 事件或 outputs 为空") - mcp_out.append(types.TextContent(type="text", text="工具执行完成,但未返回输出结果")) - - return mcp_out + # 初始化 outputs 变量,避免未定义错误 + outputs = {} + for res in responses: + if res["event"] == "workflow_finished": + outputs = res["data"]["outputs"] + break # 找到 workflow_finished 事件后退出循环 + + # 构建 MCP 输出 + mcp_out = [] + if outputs: + for _, v in outputs.items(): + mcp_out.append(types.TextContent(type="text", text=str(v))) + else: + # 如果没有获取到 outputs,返回错误信息 + logger.warning(f"工具 {name} 未获取到 workflow_finished 事件或 outputs 为空") + mcp_out.append(types.TextContent(type="text", text="工具执行完成,但未返回输出结果")) + + return mcp_out + + except DifyAPIError as e: + # 捕获 Dify API 错误,直接返回给用户 + logger.error(f"工具 {name} 调用 Dify API 失败: {e}") + error_message = f"API调用失败: {e.message}" + return [types.TextContent(type="text", text=error_message)] + + except requests.exceptions.RequestException as e: + # 捕获网络请求错误 + logger.error(f"工具 {name} 网络请求失败: {e}") + error_message = f"网络请求失败: {str(e)}" + return [types.TextContent(type="text", text=error_message)] + + except Exception as e: + # 捕获其他未知错误 + logger.error(f"工具 {name} 执行时发生未知错误: {e}", exc_info=True) + error_message = f"工具执行失败: {str(e)}" + return [types.TextContent(type="text", text=error_message)] else: raise ValueError(f"Unknown tool: {name}") diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py index 7b3d34f..b3c3e5c 100644 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py +++ b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py @@ -44,7 +44,7 @@ def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_n current_tool_file_fields (list): 当前工具的文件字段信息列表 数据结构: [{'variable': 'txt', 'label': '输入', 'required': True, 'max_length': 48, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], - 'allowed_file_extensions': []}] + 'allowed_file_extensions': [], 'is_list': False}] dify_api: Dify API实例,包含file_parameter_pretreatment方法 tool_name (str): 工具名称,用于日志记录 @@ -58,8 +58,9 @@ def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_n logger.info(f"工具 {tool_name}: 无需处理文件字段 (arguments={bool(arguments)}, file_fields={len(current_tool_file_fields) if current_tool_file_fields else 0})") return arguments - # 创建文件字段变量名的集合,用于快速查找 - file_field_variables = {field['variable'] for field in current_tool_file_fields} + # 创建文件字段变量名到字段信息的映射,用于快速查找 + file_field_map = {field['variable']: field for field in current_tool_file_fields} + file_field_variables = set(file_field_map.keys()) logger.info(f"工具 {tool_name} 的文件字段变量名: {file_field_variables}") # 创建arguments的副本,避免修改原始数据 @@ -101,12 +102,20 @@ def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_n logger.error(f"工具 {tool_name}: 文件预处理失败,未返回有效结果") continue - # 取第一个处理后的文件对象 - processed_files_item = processed_files[0] - logger.info(f"工具 {tool_name}: 文件预处理完成,upload_file_id: {processed_files_item.get('upload_file_id', 'N/A')}") + # 过滤掉上传失败的文件 + valid_files = [f for f in processed_files if f.get('upload_file_id') and not f.get('upload_error')] + if not valid_files: + logger.error(f"工具 {tool_name}: 所有文件上传失败") + continue + logger.info(f"工具 {tool_name}: 文件预处理完成,成功上传 {len(valid_files)} 个文件") + + # 获取当前字段的配置信息,判断是单文件还是多文件 + field_config = file_field_map.get(arg_name, {}) + is_list = field_config.get('is_list', False) + # 更新processed_arguments中的值 - _update_processed_argument(processed_arguments, arg_name, arg_value, processed_files_item, tool_name) + _update_processed_argument(processed_arguments, arg_name, arg_value, valid_files, tool_name, is_list) except Exception as e: logger.error(f"工具 {tool_name}: 处理文件字段 {arg_name} 时发生错误: {str(e)}") @@ -180,27 +189,34 @@ def _is_file_object(obj): return any(key in obj for key in file_indicators) -def _update_processed_argument(processed_arguments, arg_name, original_value, processed_files, tool_name): +def _update_processed_argument(processed_arguments, arg_name, original_value, processed_files, tool_name, is_list=False): """ 更新处理后的参数值 - 重要:Dify API 的文件字段始终需要一个文件对象列表,即使只有一个文件 + 根据字段类型决定输出格式: + - file-list (is_list=True): 输出列表格式 [] + - file (is_list=False): 输出对象格式 {} Args: processed_arguments (dict): 处理后的参数字典 arg_name (str): 参数名称 original_value: 原始参数值(可以是字符串URL、文件对象或文件列表) - processed_files: 处理后的文件对象(单个对象,不是列表) + processed_files (list): 处理后的文件对象列表 tool_name (str): 工具名称 + is_list (bool): 是否为多文件类型 (file-list=True, file=False) """ - # 注意:processed_files 是单个文件对象,需要转换为列表 - # 因为 Dify API 要求文件字段必须是列表格式 - if processed_files: - # 始终将文件对象包装成列表 - processed_arguments[arg_name] = [processed_files] - logger.info(f"工具 {tool_name}: 已更新文件字段 {arg_name} 为列表格式: {processed_arguments[arg_name]}") - else: + if not processed_files: logger.warning(f"工具 {tool_name}: 文件处理后为空,保持原值") + return + + if is_list: + # file-list 类型:使用完整列表 + processed_arguments[arg_name] = processed_files + logger.info(f"工具 {tool_name}: 字段 {arg_name} 是 file-list 类型,输出 {len(processed_files)} 个文件") + else: + # file 类型:只取第一个文件对象 + processed_arguments[arg_name] = processed_files[0] + logger.info(f"工具 {tool_name}: 字段 {arg_name} 是 file 类型,输出单个对象") logger.info(f"工具 {tool_name}: 字段 {arg_name} 最终值: {processed_arguments[arg_name]}") diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/dify_workflow_schema.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/dify_workflow_schema.cpython-312.pyc index ebd42b3b275f9c8e69331804e9b62c66129e8fd4..ac7fbf9e37fc371b8791f81169670b027dc72e72 100644 GIT binary patch delta 1170 zcmYk4e`p(J7{}i?m&>oqU6LhB(lnR5B-JJ+wM`wJ4c67p&dv$dP5O_PT6^ipnx*3| zDcZha8;BMxBkYAS_KW@@S~uoW3X-+>r!Wv5wArW*kURgG;`qMg|&1KqhS*?IA?zEF9 zIaQztQ*`42_Z{fMF3@wd#pGsoVK7|C3wBoxaP~lgt#fcIyQO z-}i;#>-Y~}icG;UN9P95*gElt{w{a~f8}q7V>s31^D+Okokgr-q$Zb4^b99B4&Nqm za62*<#flf3B*vz2G~zFs+UR*yI1Zi+3NjHBEhRHDD=f86gL8i&c!I}^jJwHv1CkV3 z5D+7U9Rr=kZfKLR-dPMJPO^o66}N){2&-Ty=LC26E~|7 zsyIN=OYss#A4NY!lpt~;oA$(5ihZ9TpBnk542I%|kDBA;ldavPp82t+$dP<@I delta 848 zcmXw%OK1~87{_P!k6py~Bjw!JrhuvDVOhCbd zhajQfMX*Xh#DjvCAow_0R74T2ua@emqUUWbh#=@p)j9nB-}j%7Z)U&QoBh9bXfIXO zjo7;VcuV2l<|*x|f}g;9rKkQw!2))|Pgf2vKrMWz4sIvLF_`p3g!8}Qp(iSoY?$}7 z;Lm@PSCH^p7U7#WfkE}z;JO+Jg2xhz|4in>+z>h+xtWk{^BfHpkfW)ce47Oy9 zNP@~&&{ct|RHKS15Tk^NNRpFs*;PtYl`%oom1-BOPVs7zrXpiAJX1Rx*EF%-RNls^ zSV9$7UV~f9cyQ&bgeX3UGHdHO?TaLv5-WkO#;p#G1!fY;jnA{?NNvU)4R=7^~rl9NY+9 zah(+@c6Hl*66_5paTG?vox+hN_7WMa!;A2+Yabi2FovuERGUH#;w{YX$<~U8E62Gj zN<;>KBrZDssY0A9xr?~DVh?PHBq1AV*NHZ%lw~Rti4W1jl?94VsW2r5^#7ePsx1GT zqwnxGP^Mo@_4`tWi^CHVmx_!lPIOEATOv?$0P@in(SFM~wyvSE+~J?y)!EW 1 都视为多文件 + actual_type = control_config.get("type", control_type) max_length = control_config.get("max_length", 1) - if max_length > 1: + is_multi_file = actual_type == "file-list" or max_length > 1 + + if is_multi_file: # 多文件上传场景 property_schema = { "type": "array", @@ -330,6 +324,7 @@ def extract_file_fields(user_input_form): - allowed_file_types (list): 允许的文件类型 - allowed_file_upload_methods (list): 允许的上传方式 - allowed_file_extensions (list): 允许的文件扩展名 + - is_list (bool): 是否为多文件类型 (file-list=True, file=False) """ file_fields = [] @@ -353,6 +348,11 @@ def extract_file_fields(user_input_form): # 只处理type为file或file-list的字段 if control_type in ["file", "file-list"] or control_config.get("type") in ["file", "file-list"]: + # 判断是否为多文件类型 + # 优先使用 control_config 中的 type,其次使用 control_type + actual_type = control_config.get("type", control_type) + is_list = actual_type == "file-list" + # 提取文件字段的详细信息 file_field_info = { "variable": control_config.get("variable", ""), @@ -361,7 +361,8 @@ def extract_file_fields(user_input_form): "max_length": control_config.get("max_length", 1), "allowed_file_types": control_config.get("allowed_file_types", []), "allowed_file_upload_methods": control_config.get("allowed_file_upload_methods", []), - "allowed_file_extensions": control_config.get("allowed_file_extensions", []) + "allowed_file_extensions": control_config.get("allowed_file_extensions", []), + "is_list": is_list # 新增:标识是否为多文件类型 } # 只添加有效的字段(必须有variable) diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/workflow_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/workflow_server.cpython-312.pyc index 472ea9a8053abd62858c596f40bd95b414f85cd5..7c4f86ecf2b3a49ccbef1ef74b8bd04ab54aca47 100644 GIT binary patch delta 3627 zcmZ`+du$ZP8K2p^-FrX$IDCGeU*H2DHid)|0h{d+EBTbh*~8?Nz^D+C0j|wb(KHTs!`iNDmmaJh(D@+-|X2K zl$emLxja+I>JGj}c8CBbuUnFWZoV zkY1nW7$d!YG3JCZmo6=i1$4LO9%vq?(< zob*vm8T0izgn&*BgzI&UlVh~k-9w^o)4MI%cf9L~o!c2pv*9o-i-ZVJBL9Bv?7!ZA zd;gbzc=5|?*Z%pN-lnOc2Hj+0BYJE|}T z5E#y8Xdi}NGi75qqchW~s>z{bR#l^lX*cv#-xB7AyWB2SO{C(6p{j-gvm^MNyz>_S zNZG_|90Pgg2kME?Qd_NFSh*aAjPC*5K1mAI%g61XIm3mj=5hNgfkH(cG~Pl@D^e>z zclu5_W}V?P~~&CK?4B5W*j*D3>MZO8jr$kZeQ4 z@OU7USHOq%Zj(NJLOK{ZAw3kiWLu&j>UW8<05k?}5@98PrUSIhGF3fMexE*qT8-SR z06?3P(us99%9>`&nr2Fyr?yOGFXrFO&n#bmm0fFoukD?-mTPr0fgLl>9jp{4+@lCO zPAKv?;m@q90#AeRv#v55iVm@!r{`=SywuIW(Zqsf(7Q*_Y+BWliR^uX7O8c}-2`xw zd{JG0qk7eB^{PTu-G%nE?S+c!)49pqR}Op7JxA;=w~&u{fjR2Im>7!lTOD6xp0{B@afaxp7M?z1fAeJZ#0#%6GDQwEnZvxQ5##(^eW8|;) z#;@$MykctW9D(L)k7%y170t|n-m0&WeA7?mo%9<;mWyKITGL1Ab-T>!q01jsHqcg{ zQF>}D{{=G1q2tLR73`3R59%ql9=KZg8;*I!^220FX!=*q-IDiV7%=U_2Bd0?J3Y@u zL@cn4KxR0`IuW)ZY)5#MU+`2lqY75Qho4Ndfxc9Fq!=iRG5=%!ch4;)3{HM2z~`3N zOn>X$PHBK|@^1i4`|4eRoa=R>1y2R|1%IV2a3qNF%4hsu==(7M93=xFH~TGrAy6-u zo5&{~g+kMYMB^~uHa8@tVDi*1}Z0*7X zu?tm zhbUsET@+d0@X)|edSply#WrfcFS$jjn>I>bpmaa~y7Wi1gm;(iqF4C2viF<;_6V%a z4)a9$#=MwlDol?X=^>WDP3>9zSeEU_uB~6svN&>M>_vkjOiV{SlT-)v;|yhFdl2>l zmfMk(C$yZ~EUley#Pr*gLULBReL1 zh1wMt4xK&p)}vQXt&drBK^2?~=>>kcvM4oR@ej z`G1x*?z=r7Bjw>6Wh-XOR@@{KY!zv}=zG(Li?;pDJtqU{j)BGQquA!hTF4(;8g{sB zA3G^vq8LjsNC+NDqWs;&^4Gw3Rg@?>Tau0{{7}U*?*nAip(!~B5S;vEMN@+_r(AI? zMB}JSBlug0U`p~X-cVg*rzGd%UDf6JyHVLh)<7W|ktlhJQ24zPEnXg+-6wqQ!Wr$u znN&KivE3jO`yoOnfGM%VrX3lgHsL_wPiDXn9H|&Orahg}hu8pgSqi^{ES@xUwJ&{u z!3ly_0TDh?w^dfqQKk!5R5S7Hvtk#Wb^3kN166v3GpOw=y!#If<);H&ljdzx9Z3G4+n&fekL!b;WG&$K6z71x}$5M!6UlwNr#7-fo2SAb3O7R{9~=tG-W5r{AcWbWp}S8jsS=)4yok zO?Mv#PSY_GXG6)Me$4F5ahAr-o<$hLQClXRvC`7D{a8ZJWRsAlSp8WH0qy`K>afh* z!#kUIDWXUxIdATupG<$){Ed_sp52Aq-3XmHMY2qfNe=pmda&qDmP0l%cK@ zs7dX++-Kz~-n16nW}QerBb(+MTBA}qj*$6@&0V9zm5ne~=Lcl*7Y%9PeVqKy{;B7!E!qe~?2LQ&% zS5m3Gg-(^tky4&rQ|En*%1yWSN^ZIS<}!YMO+-PV*oNs3)_g&$ONGl#Tl{c>VVyJ` zsdRroqzBv2-(B0-A4QsH=U^tyvfJ^G2AjayLK93U#u4ze5^*AAF`XLxby4BevNZW6 zj*3lD<~z=Z2h-YcO5e<`z$b;*_%+|OuB~ZaDW&d-4f6z=o2_eU&3p>31GIi3F;B3a e>x3G;?qMn6=I^d+^EZ delta 2171 zcmZ`)eN0nV6o0p`eSM`Zw6?TVs0Bo%f~_du$cD}@HWepx4(3)%--8tj?e1#@7Z|!| z2HBV7B%8@hT$U`+;U7!-$CfM^QI}=O7R6{X^0q9v{kJS}eq&&irhxz1?6dWlyRxqsWvx;Y8k{G1aKZi5^=d1}9AOPYB|;Sg zmG74it7KTQu!rJ>ix#3wj6~vt@pL5eTEYG#?e9q>hqlCf4sY7i7FzIZXK~6`432Pf z{#E0~YvS_Rq6!>+voPkE6`EMJ-n7Bda3DM4+)RiKrk6D`yy*;B@Z zZ|ifwX;;2om?A76y9lRw+@j>{uV-`*z_csCFVl=3xa+FqU2J~cwbwXd>+`~^ZWsI= z&RNAvc1;x9xUGjs`NzajtAQJ_PU%Ux4wkjgHy>>_c5p!tWb$QF4nO7}vNy0O)zlMD z$O>H#FMBds-g}gg7C2t;0x5)_3wDtSsPavVcDjm%(j9Qy*F2_Lm2@t++RyVqz8B_}Jo#3-y&U@@a!nnyYu z{#vot_C5<#5)4@XLS3NjX{Yy;`(*r#`>HQA;|PzNW*wfB#_`w3jX${xKWLe3`N(yq z_ltesy05u*kBhTQ>zL<#Z**ext+5$#gyAM90) zv1ID7%J<4?x|=y@FM9b@GNpH;W$SQEPNn0lJ9E+PMA*Y1Xx2;G3|H6e5^(p^b||l0 zM?T5!ul&<6hPu&OggONLkpvC8Zki${{UaYImbgSLb2_T@N8-{WQfad5B8e=sRsjZj z5$sAj3Ezi;h8rB*4J8EZ(-;pk0XK|>|B)WHm6r3YPD&M0Q5qdkXc-dK`$_Ml3^Oct zlJ+wV<}5ONl-L^>z7yuwwdh4z4L9oi&{}OYJm6qk_2d#Z*_+iDiFFAbnBK5OILZ=a zN+4Y0gVLJnHvOl8Ni})2dTEq8{=+M{q-g&`=~PXzPN@Pe)T|~2@LSCc@xzz3mwJ|P zz^q7tb)L5L;)092XO)5_b2h-eDj zS_VZ8#aj7kAiKF?jubjHg;hS<9iw= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:22:23 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:22:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:22:24 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:57:20 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:05:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplyChainRiskWarning", + "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:05:48 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:05:48 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:13:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "EfficiencyOutputLossDashboard", + "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:13:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:13:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:34:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:34:56 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:34:56 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:59:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log index ba82ae7..b3fb093 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/lzwcai_mcp_sqlexecutor_daily.log @@ -205,3 +205,158 @@ 2026-01-08 00:56:21 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " 2026-01-08 00:56:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭 +2026-01-08 10:00:42 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcpskills_mfg_data_agent\lzwcai_mcpskills_mfg_data_agent\logs +2026-01-08 10:00:42 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-08 10:00:45 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-08 10:02:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:28 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:28 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:30 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:30 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:02:35 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:02:35 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:22:23 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:22:23 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OrderDelayWarningAnalysis", + "businessDescription": "订单延迟预警分析:依据历史订单的生产周期、物流延误、设备故障等特征,输出延迟概率与红/黄/绿预警等级", + "sqlTemplate": "WITH production_cycle_stats AS (SELECT COALESCE(AVG(GREATEST(0, EXTRACT(DAY FROM last_updated_utc - event_time_utc))), 0) AS avg_production_days FROM fact_work_order WHERE status = 'CLOSED' AND last_updated_utc >= event_time_utc), logistics_delay_stats AS (SELECT customer_id, AVG(GREATEST(0, EXTRACT(DAY FROM event_time_utc - doc_date_utc))) AS avg_logistics_delay_days, SUM(CASE WHEN EXTRACT(DAY FROM event_time_utc - doc_date_utc) > 3 THEN 1 ELSE 0 END) AS delay_count FROM fact_sales_shipment WHERE doc_date_utc IS NOT NULL AND event_time_utc IS NOT NULL GROUP BY customer_id), quality_issue_stats AS (SELECT COALESCE(ROUND(SUM(fail_qty) * 100.0 / NULLIF(SUM(pass_qty + fail_qty), 0), 2), 0) AS defect_rate_pct FROM fact_quality_inspection WHERE pass_qty IS NOT NULL AND fail_qty IS NOT NULL), scrap_stats AS (SELECT COALESCE((SELECT COUNT(*) FROM fact_scrap) * 100.0 / NULLIF((SELECT COUNT(*) FROM fact_work_order WHERE status = 'CLOSED'), 0), 0) AS scrap_rate_pct), active_work_order_risk AS (SELECT COUNT(*) AS active_wo_count, COALESCE(SUM(CASE WHEN planned_qty > 0 AND (completed_qty / planned_qty) < 0.3 AND EXTRACT(DAY FROM NOW() - event_time_utc) > 7 THEN 1 ELSE 0 END), 0) AS lagging_wo_count FROM fact_work_order WHERE status IN ('OPEN', 'STARTED')), global_metrics AS (SELECT pcs.avg_production_days, qis.defect_rate_pct, ss.scrap_rate_pct, awr.active_wo_count, awr.lagging_wo_count FROM production_cycle_stats pcs, quality_issue_stats qis, scrap_stats ss, active_work_order_risk awr) SELECT so.sales_order_number AS order_number, c.customer_name, so.order_date_utc::DATE AS order_date, so.deal_amount AS order_amount, so.payment_status, ROUND(gm.avg_production_days::NUMERIC, 1) AS avg_production_days, ROUND(COALESCE(lds.avg_logistics_delay_days, 0)::NUMERIC, 1) AS avg_logistics_delay_days, COALESCE(lds.delay_count, 0)::INT AS historical_delay_count, ROUND(gm.defect_rate_pct::NUMERIC, 2) AS defect_rate_pct, ROUND(gm.scrap_rate_pct::NUMERIC, 2) AS scrap_rate_pct, gm.active_wo_count::INT AS active_work_order_count, gm.lagging_wo_count::INT AS lagging_work_order_count, ROUND(LEAST(100, GREATEST(0, LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)))::NUMERIC, 1) AS delay_probability_pct, CASE WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 60 THEN 'RED' WHEN (LEAST(25, GREATEST(0, gm.avg_production_days - 10) * 2.5) + LEAST(30, COALESCE(lds.avg_logistics_delay_days, 0) * 6) + LEAST(25, gm.defect_rate_pct * 2.5) + LEAST(20, gm.lagging_wo_count * 10)) >= 30 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level, CASE WHEN gm.lagging_wo_count >= 2 THEN 'PRODUCTION_SEVERELY_DELAYED' WHEN COALESCE(lds.avg_logistics_delay_days, 0) > 5 THEN 'HIGH_LOGISTICS_DELAY_RISK' WHEN gm.avg_production_days > 15 THEN 'LONG_PRODUCTION_CYCLE' WHEN gm.defect_rate_pct > 10 THEN 'QUALITY_ISSUES' ELSE 'NORMAL' END AS primary_risk_factor FROM fact_sales_order so LEFT JOIN dim_customer c ON so.customer_id = c.customer_id AND c.is_current = 't' CROSS JOIN global_metrics gm LEFT JOIN logistics_delay_stats lds ON so.customer_id = lds.customer_id ORDER BY delay_probability_pct DESC, so.order_date_utc DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:22:24 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:22:24 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "WorkOrderProgressAndAnomalyNodes", + "businessDescription": "工单执行进度与异常节点:实时拉取工单数据,动态映射订单各环节状态(OPEN→PENDING, STARTED→IN_PROGRESS, CLOSED→COMPLETED),呈现执行进度与异常节点", + "sqlTemplate": "WITH work_order_base AS (SELECT wo.work_order_id, wo.work_order_number, wo.product_id, wo.status, wo.planned_qty, wo.completed_qty, wo.event_time_utc::timestamp AS start_time, wo.last_updated_utc::timestamp AS last_update, wo.source_system FROM fact_work_order wo), product_info AS (SELECT product_id, product_name, product_category FROM dim_product WHERE is_current = 't'), labor_summary AS (SELECT work_order_number, COUNT(DISTINCT worker_name) AS worker_count, SUM(report_qty) AS total_report_qty, SUM(duration_minutes) AS total_minutes, MAX(event_time_utc::timestamp) AS last_report_time FROM fact_labor_report GROUP BY work_order_number), quality_summary AS (SELECT work_order_number, SUM(pass_qty) AS pass_qty, SUM(fail_qty) AS fail_qty FROM fact_quality_inspection GROUP BY work_order_number), work_order_progress AS (SELECT wb.work_order_id, wb.work_order_number, p.product_name, p.product_category, wb.status AS raw_status, CASE wb.status WHEN 'OPEN' THEN 'PENDING' WHEN 'STARTED' THEN 'IN_PROGRESS' WHEN 'CLOSED' THEN 'COMPLETED' ELSE 'UNKNOWN' END AS status_name, wb.planned_qty, wb.completed_qty, CASE WHEN wb.planned_qty > 0 THEN ROUND(wb.completed_qty * 100.0 / wb.planned_qty, 1) ELSE 0 END AS completion_rate, GREATEST(wb.planned_qty - wb.completed_qty, 0) AS remaining_qty, wb.start_time, wb.last_update, ROUND(EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - wb.start_time)) / 3600, 1) AS elapsed_hours, COALESCE(ls.worker_count, 0) AS worker_count, COALESCE(ls.total_report_qty, 0) AS total_report_qty, COALESCE(ls.total_minutes, 0) AS total_work_minutes, ls.last_report_time, COALESCE(qs.pass_qty, 0) AS qc_pass_qty, COALESCE(qs.fail_qty, 0) AS qc_fail_qty FROM work_order_base wb LEFT JOIN product_info p ON wb.product_id = p.product_id LEFT JOIN labor_summary ls ON wb.work_order_number = ls.work_order_number LEFT JOIN quality_summary qs ON wb.work_order_number = qs.work_order_number), anomaly_detection AS (SELECT *, CASE WHEN raw_status = 'STARTED' AND elapsed_hours > 48 AND completion_rate < 20 THEN 'SEVERELY_DELAYED' WHEN raw_status = 'STARTED' AND elapsed_hours > 24 AND completion_rate < 30 THEN 'DELAYED' ELSE NULL END AS progress_anomaly, CASE WHEN qc_pass_qty + qc_fail_qty > 0 AND qc_fail_qty * 100.0 / (qc_pass_qty + qc_fail_qty) > 10 THEN 'QUALITY_ISSUE' ELSE NULL END AS quality_anomaly, CASE WHEN raw_status = 'STARTED' AND last_report_time IS NOT NULL AND EXTRACT(EPOCH FROM (CURRENT_TIMESTAMP - last_report_time)) / 3600 > 24 THEN 'LABOR_STALLED' WHEN raw_status = 'STARTED' AND last_report_time IS NULL AND elapsed_hours > 24 THEN 'NO_LABOR_RECORD' ELSE NULL END AS labor_anomaly, CASE WHEN total_work_minutes > 0 AND total_report_qty / (total_work_minutes / 60.0) < 5 THEN 'LOW_EFFICIENCY' ELSE NULL END AS efficiency_anomaly FROM work_order_progress) SELECT work_order_number, product_name, product_category, status_name, planned_qty, completed_qty, remaining_qty, completion_rate, worker_count, ROUND(total_work_minutes / 60.0, 1) AS total_work_hours, elapsed_hours, COALESCE(progress_anomaly, '') || CASE WHEN progress_anomaly IS NOT NULL AND quality_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(quality_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL) AND labor_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(labor_anomaly, '') || CASE WHEN (progress_anomaly IS NOT NULL OR quality_anomaly IS NOT NULL OR labor_anomaly IS NOT NULL) AND efficiency_anomaly IS NOT NULL THEN ',' ELSE '' END || COALESCE(efficiency_anomaly, '') AS anomaly_flags, CASE WHEN progress_anomaly = 'SEVERELY_DELAYED' OR quality_anomaly IS NOT NULL THEN 'HIGH' WHEN progress_anomaly = 'DELAYED' OR labor_anomaly IS NOT NULL THEN 'MEDIUM' WHEN efficiency_anomaly IS NOT NULL THEN 'LOW' ELSE 'NONE' END AS risk_level FROM anomaly_detection ORDER BY CASE raw_status WHEN 'STARTED' THEN 1 WHEN 'OPEN' THEN 2 ELSE 3 END, CASE WHEN progress_anomaly IS NOT NULL THEN 0 ELSE 1 END, completion_rate ASC", + "parameters": {}, + "testParams": {} +} +2026-01-08 10:57:20 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 10:57:20 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:05:47 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:05:47 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "SupplyChainRiskWarning", + "businessDescription": "供应链风险预警:整合采购系统中的供应商历史交期与质检合格率,结合外采物流数据,识别交期异常+物流停滞组合风险模式,自动推送高风险订单提示", + "sqlTemplate": "WITH supplier_delivery AS (SELECT po.supplier_id, COUNT(*) AS order_count, COUNT(pr.purchase_receipt_id) AS receipt_count, AVG(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS avg_delivery_days, MAX(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS max_delivery_days, STDDEV(CASE WHEN pr.doc_date_utc IS NOT NULL AND po.doc_date_utc IS NOT NULL THEN EXTRACT(DAY FROM pr.doc_date_utc::timestamp - po.doc_date_utc::timestamp) ELSE NULL END) AS stddev_delivery_days FROM fact_purchase_order po LEFT JOIN fact_purchase_receipt pr ON po.supplier_id = pr.supplier_id GROUP BY po.supplier_id), supplier_quality AS (SELECT pr.supplier_id, COUNT(pr.purchase_receipt_id) AS total_receipts, SUM(pr.receipt_qty_total) AS total_qty, SUM(pr.amount) AS total_amount FROM fact_purchase_receipt pr GROUP BY pr.supplier_id), supplier_returns AS (SELECT pret.supplier_id, COUNT(*) AS return_count, SUM(CASE WHEN pret.return_reason = '损坏' THEN 1 ELSE 0 END) AS damage_count FROM fact_purchase_return pret GROUP BY pret.supplier_id), supplier_quality_rate AS (SELECT sq.supplier_id, sq.total_receipts, sq.total_qty, sq.total_amount, COALESCE(sr.return_count, 0) AS return_count, COALESCE(sr.damage_count, 0) AS damage_count, CASE WHEN sq.total_receipts > 0 THEN (sq.total_receipts - COALESCE(sr.return_count, 0)) * 100.0 / sq.total_receipts ELSE 100 END AS quality_rate FROM supplier_quality sq LEFT JOIN supplier_returns sr ON sq.supplier_id = sr.supplier_id), supplier_risk AS (SELECT s.supplier_id, s.supplier_name, s.supplier_category, COALESCE(sd.order_count, 0) AS order_count, COALESCE(sd.receipt_count, 0) AS receipt_count, ROUND(COALESCE(sd.avg_delivery_days, 0), 1) AS avg_delivery_days, ROUND(COALESCE(sd.max_delivery_days, 0), 1) AS max_delivery_days, ROUND(COALESCE(sd.stddev_delivery_days, 0), 1) AS delivery_volatility, COALESCE(sqr.total_receipts, 0) AS total_receipts, COALESCE(sqr.return_count, 0) AS return_count, ROUND(COALESCE(sqr.quality_rate, 100), 1) AS quality_rate, CASE WHEN COALESCE(sd.avg_delivery_days, 0) > 60 THEN 40 WHEN COALESCE(sd.avg_delivery_days, 0) > 45 THEN 30 WHEN COALESCE(sd.avg_delivery_days, 0) > 30 THEN 20 ELSE 10 END + CASE WHEN COALESCE(sd.stddev_delivery_days, 0) > 20 THEN 30 WHEN COALESCE(sd.stddev_delivery_days, 0) > 10 THEN 20 ELSE 10 END AS delivery_risk_score, CASE WHEN COALESCE(sqr.quality_rate, 100) < 80 THEN 50 WHEN COALESCE(sqr.quality_rate, 100) < 90 THEN 30 WHEN COALESCE(sqr.quality_rate, 100) < 95 THEN 15 ELSE 5 END AS quality_risk_score FROM dim_supplier s LEFT JOIN supplier_delivery sd ON s.supplier_id = sd.supplier_id LEFT JOIN supplier_quality_rate sqr ON s.supplier_id = sqr.supplier_id WHERE s.is_current = 't'), supplier_risk_level AS (SELECT *, delivery_risk_score + quality_risk_score AS total_risk_score, CASE WHEN delivery_risk_score + quality_risk_score >= 80 THEN 'HIGH' WHEN delivery_risk_score + quality_risk_score >= 50 THEN 'MEDIUM' ELSE 'LOW' END AS risk_level, CASE WHEN delivery_risk_score >= 50 AND quality_risk_score >= 30 THEN 'DELIVERY_AND_QUALITY' WHEN delivery_risk_score >= 50 THEN 'DELIVERY_ISSUE' WHEN quality_risk_score >= 30 THEN 'QUALITY_ISSUE' ELSE 'NORMAL' END AS risk_pattern FROM supplier_risk) SELECT supplier_name, supplier_category, order_count, receipt_count, avg_delivery_days, max_delivery_days, delivery_volatility, total_receipts, return_count, quality_rate, delivery_risk_score, quality_risk_score, total_risk_score, risk_level, risk_pattern FROM supplier_risk_level ORDER BY total_risk_score DESC, supplier_name", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:05:48 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:05:48 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:13:21 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:13:21 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "EfficiencyOutputLossDashboard", + "businessDescription": "人效-产值-损耗三维模型仪表盘:关联订单量×工时×人员数×成本,构建人效—产值—损耗三维模型,按部门(产品类别)汇总展示", + "sqlTemplate": "WITH labor_stats AS (SELECT p.product_category AS department, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) AS total_work_minutes, SUM(lr.report_qty) AS total_output_qty, COUNT(DISTINCT lr.work_order_number) AS work_order_count FROM fact_labor_report lr INNER JOIN dim_product p ON lr.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), work_order_stats AS (SELECT p.product_category AS department, COUNT(*) AS total_work_orders, SUM(wo.planned_qty) AS total_planned_qty, SUM(wo.completed_qty) AS total_completed_qty, SUM(CASE WHEN wo.status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN wo.status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN wo.status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order wo INNER JOIN dim_product p ON wo.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), quality_stats AS (SELECT p.product_category AS department, SUM(qi.pass_qty) AS total_pass_qty, SUM(qi.fail_qty) AS total_fail_qty, COUNT(*) AS inspection_count FROM fact_quality_inspection qi INNER JOIN dim_product p ON qi.product_id = p.product_id AND p.is_current = 't' GROUP BY p.product_category), sales_stats AS (SELECT AVG(deal_amount) AS avg_order_amount, SUM(deal_amount) AS total_sales_amount, COUNT(*) AS order_count FROM fact_sales_order), scrap_stats AS (SELECT COUNT(*) AS total_scrap_count FROM fact_scrap) SELECT ls.department, ls.worker_count, ROUND(ls.total_work_minutes / 60.0, 2) AS total_work_hours, ls.total_output_qty, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 2) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0), 2) AS efficiency_index, ws.total_planned_qty AS planned_qty, ws.total_completed_qty AS completed_qty, ROUND(ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS plan_completion_rate, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100, 2) AS estimated_output_value, ROUND(ws.total_completed_qty * ss.avg_order_amount / 100 / NULLIF(ls.worker_count, 0), 2) AS output_value_per_worker, qs.total_pass_qty AS qc_pass_qty, qs.total_fail_qty AS qc_fail_qty, ROUND(qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100, 2) AS defect_rate, ROUND((ws.total_planned_qty - ws.total_completed_qty) / NULLIF(ws.total_planned_qty, 0) * 100, 2) AS production_loss_rate, ROUND((ls.total_output_qty / NULLIF(ls.worker_count, 0) / NULLIF(ls.total_work_minutes / 60.0 / ls.worker_count, 0)) * 0.4 + (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) * 0.35 + (qs.total_pass_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) * 0.25, 2) AS performance_score, CASE WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 10 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 50 THEN 'RED' WHEN (qs.total_fail_qty / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0) * 100) >= 5 OR (ws.total_completed_qty / NULLIF(ws.total_planned_qty, 0) * 100) < 70 THEN 'YELLOW' ELSE 'GREEN' END AS warning_level FROM labor_stats ls LEFT JOIN work_order_stats ws ON ls.department = ws.department LEFT JOIN quality_stats qs ON ls.department = qs.department CROSS JOIN sales_stats ss CROSS JOIN scrap_stats scr ORDER BY performance_score DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:13:22 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:13:22 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:34:55 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:34:55 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "OnePageDecisionBrief", + "businessDescription": "一页式决策简报:自动聚合订单、生产、财务、售后等关键数据,生成经营决策简报", + "sqlTemplate": "WITH sales_summary AS (SELECT COUNT(*) AS total_orders, SUM(deal_amount) AS total_sales_amount, AVG(deal_amount) AS avg_order_amount, SUM(CASE WHEN payment_status = 'PAID' THEN 1 ELSE 0 END) AS paid_orders, SUM(CASE WHEN payment_status = 'PARTIAL' THEN 1 ELSE 0 END) AS partial_orders, SUM(CASE WHEN payment_status = 'UNPAID' THEN 1 ELSE 0 END) AS unpaid_orders, SUM(CASE WHEN payment_status = 'PAID' THEN deal_amount ELSE 0 END) AS paid_amount, SUM(CASE WHEN payment_status = 'UNPAID' THEN deal_amount ELSE 0 END) AS unpaid_amount FROM fact_sales_order), production_summary AS (SELECT COUNT(*) AS total_work_orders, SUM(planned_qty) AS total_planned_qty, SUM(completed_qty) AS total_completed_qty, SUM(CASE WHEN status = 'CLOSED' THEN 1 ELSE 0 END) AS closed_orders, SUM(CASE WHEN status = 'STARTED' THEN 1 ELSE 0 END) AS started_orders, SUM(CASE WHEN status = 'OPEN' THEN 1 ELSE 0 END) AS open_orders FROM fact_work_order), ar_summary AS (SELECT COUNT(*) AS receipt_count, SUM(amount) AS total_receipt_amount, AVG(amount) AS avg_receipt_amount FROM fact_ar_receipt), ap_summary AS (SELECT COUNT(*) AS payment_count, SUM(amount) AS total_payment_amount, AVG(amount) AS avg_payment_amount FROM fact_ap_payment), invoice_summary AS (SELECT COUNT(*) AS invoice_count, SUM(invoice_amount) AS total_invoice_amount, AVG(invoice_amount) AS avg_invoice_amount FROM fact_invoice), return_summary AS (SELECT COUNT(*) AS return_count, SUM(amount) AS total_return_amount, AVG(amount) AS avg_return_amount FROM fact_sales_return), shipment_summary AS (SELECT COUNT(*) AS shipment_count, SUM(amount) AS total_shipment_amount, AVG(amount) AS avg_shipment_amount FROM fact_sales_shipment), purchase_summary AS (SELECT COUNT(*) AS purchase_order_count FROM fact_purchase_order), quality_summary AS (SELECT COUNT(*) AS inspection_count, SUM(pass_qty) AS total_pass_qty, SUM(fail_qty) AS total_fail_qty FROM fact_quality_inspection), labor_summary AS (SELECT COUNT(DISTINCT worker_name) AS worker_count, SUM(duration_minutes) AS total_work_minutes, SUM(report_qty) AS total_output_qty FROM fact_labor_report), scrap_summary AS (SELECT COUNT(*) AS scrap_count FROM fact_scrap) SELECT 'Decision Brief' AS report_title, CURRENT_DATE AS report_date, ss.total_orders AS sales_order_count, ROUND(ss.total_sales_amount, 2) AS total_sales_amount, ROUND(ss.avg_order_amount, 2) AS avg_order_amount, ss.paid_orders AS paid_order_count, ss.partial_orders AS partial_paid_count, ss.unpaid_orders AS unpaid_order_count, ROUND(ss.paid_orders * 100.0 / NULLIF(ss.total_orders, 0), 1) AS payment_completion_rate, ROUND(ss.unpaid_amount, 2) AS receivable_amount, ps.total_work_orders AS work_order_count, ps.closed_orders AS completed_work_orders, ps.started_orders AS in_progress_work_orders, ps.open_orders AS pending_work_orders, ROUND(ps.total_planned_qty, 0) AS planned_qty, ROUND(ps.total_completed_qty, 0) AS completed_qty, ROUND(ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0), 1) AS production_completion_rate, ls.worker_count AS active_worker_count, ROUND(ls.total_work_minutes / 60.0, 1) AS total_work_hours, ROUND(ls.total_output_qty, 0) AS total_output, ROUND(ls.total_output_qty / NULLIF(ls.worker_count, 0), 1) AS output_per_worker, ROUND(ls.total_output_qty / NULLIF(ls.total_work_minutes / 60.0, 0), 2) AS output_per_hour, qs.inspection_count AS qc_batch_count, ROUND(qs.total_pass_qty, 0) AS pass_qty, ROUND(qs.total_fail_qty, 0) AS fail_qty, ROUND(qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS pass_rate, ROUND(qs.total_fail_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0), 2) AS defect_rate, scr.scrap_count AS scrap_record_count, ar.receipt_count AS ar_receipt_count, ROUND(ar.total_receipt_amount, 2) AS total_ar_amount, ap.payment_count AS ap_payment_count, ROUND(ap.total_payment_amount, 2) AS total_ap_amount, ROUND(ar.total_receipt_amount - ap.total_payment_amount, 2) AS net_cash_flow, inv.invoice_count, ROUND(inv.total_invoice_amount, 2) AS total_invoice_amount, sh.shipment_count, ROUND(sh.total_shipment_amount, 2) AS total_shipment_amount, pur.purchase_order_count, ret.return_count, ROUND(ret.total_return_amount, 2) AS total_return_amount, ROUND(ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0), 2) AS return_rate, CASE WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 95 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 80 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 5 THEN 'EXCELLENT' WHEN (qs.total_pass_qty * 100.0 / NULLIF(qs.total_pass_qty + qs.total_fail_qty, 0)) >= 90 AND (ps.total_completed_qty * 100.0 / NULLIF(ps.total_planned_qty, 0)) >= 60 AND (ret.total_return_amount * 100.0 / NULLIF(ss.total_sales_amount, 0)) < 10 THEN 'GOOD' ELSE 'WARNING' END AS health_status FROM sales_summary ss CROSS JOIN production_summary ps CROSS JOIN ar_summary ar CROSS JOIN ap_summary ap CROSS JOIN invoice_summary inv CROSS JOIN return_summary ret CROSS JOIN shipment_summary sh CROSS JOIN purchase_summary pur CROSS JOIN quality_summary qs CROSS JOIN labor_summary ls CROSS JOIN scrap_summary scr", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:34:56 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:34:56 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:138] - 正在调用测试SQL API: http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:139] - 请求参数: { + "datasourceId": "19", + "businessName": "MetricTrendAndTurningPointWarning", + "businessDescription": "指标趋势分析与拐点预警:基于移动平均与线性回归分析人效、产量、废品率趋势,输出上升/下降/平稳判断与拐点预警", + "sqlTemplate": "WITH daily_metrics AS (SELECT DATE(lr.event_time_utc::timestamp) AS metric_date, COUNT(DISTINCT lr.worker_name) AS worker_count, SUM(lr.duration_minutes) / 60.0 AS total_hours, SUM(lr.report_qty) AS total_output, CASE WHEN SUM(lr.duration_minutes) > 0 THEN SUM(lr.report_qty) / (SUM(lr.duration_minutes) / 60.0) ELSE 0 END AS hourly_efficiency FROM fact_labor_report lr GROUP BY DATE(lr.event_time_utc::timestamp)), daily_quality AS (SELECT DATE(qi.event_time_utc::timestamp) AS metric_date, SUM(qi.pass_qty) AS pass_qty, SUM(qi.fail_qty) AS fail_qty, CASE WHEN SUM(qi.pass_qty) + SUM(qi.fail_qty) > 0 THEN SUM(qi.fail_qty) * 100.0 / (SUM(qi.pass_qty) + SUM(qi.fail_qty)) ELSE 0 END AS defect_rate FROM fact_quality_inspection qi GROUP BY DATE(qi.event_time_utc::timestamp)), daily_production AS (SELECT DATE(wo.event_time_utc::timestamp) AS metric_date, SUM(wo.planned_qty) AS planned_qty, SUM(wo.completed_qty) AS completed_qty, CASE WHEN SUM(wo.planned_qty) > 0 THEN SUM(wo.completed_qty) * 100.0 / SUM(wo.planned_qty) ELSE 0 END AS completion_rate FROM fact_work_order wo GROUP BY DATE(wo.event_time_utc::timestamp)), combined_daily AS (SELECT COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) AS metric_date, COALESCE(dm.worker_count, 0) AS worker_count, COALESCE(dm.total_hours, 0) AS total_hours, COALESCE(dm.total_output, 0) AS total_output, COALESCE(dm.hourly_efficiency, 0) AS hourly_efficiency, COALESCE(dq.defect_rate, 0) AS defect_rate, COALESCE(dp.completion_rate, 0) AS completion_rate FROM daily_metrics dm FULL OUTER JOIN daily_quality dq ON dm.metric_date = dq.metric_date FULL OUTER JOIN daily_production dp ON dm.metric_date = dp.metric_date WHERE COALESCE(dm.metric_date, dq.metric_date, dp.metric_date) IS NOT NULL), numbered_data AS (SELECT *, ROW_NUMBER() OVER (ORDER BY metric_date) AS day_seq FROM combined_daily), moving_avg_step1 AS (SELECT metric_date, day_seq, worker_count, total_output, hourly_efficiency, defect_rate, completion_rate, AVG(hourly_efficiency) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_efficiency, AVG(total_output) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_output, AVG(defect_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_defect_rate, AVG(completion_rate) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS ma7_completion_rate, AVG(day_seq) OVER (ORDER BY metric_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS avg_x FROM numbered_data), moving_avg AS (SELECT *, LAG(ma7_efficiency, 1) OVER (ORDER BY metric_date) AS prev_ma7_efficiency, LAG(ma7_output, 1) OVER (ORDER BY metric_date) AS prev_ma7_output, LAG(ma7_defect_rate, 1) OVER (ORDER BY metric_date) AS prev_ma7_defect_rate FROM moving_avg_step1), slope_calc AS (SELECT *, (ma7_efficiency - LAG(ma7_efficiency, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_efficiency, (ma7_output - LAG(ma7_output, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_output, (ma7_defect_rate - LAG(ma7_defect_rate, 3) OVER (ORDER BY metric_date)) / 3.0 AS slope_defect FROM moving_avg), trend_analysis AS (SELECT metric_date, hourly_efficiency, total_output, defect_rate, completion_rate, ROUND(ma7_efficiency, 2) AS ma7_efficiency, ROUND(ma7_output, 2) AS ma7_output, ROUND(ma7_defect_rate, 2) AS ma7_defect_rate, ROUND(COALESCE(slope_efficiency, 0), 4) AS slope_efficiency, ROUND(COALESCE(slope_output, 0), 4) AS slope_output, ROUND(COALESCE(slope_defect, 0), 4) AS slope_defect, prev_ma7_efficiency, prev_ma7_output, prev_ma7_defect_rate, CASE WHEN COALESCE(slope_efficiency, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_efficiency, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS efficiency_trend, CASE WHEN COALESCE(slope_output, 0) > 3 THEN 'RISING' WHEN COALESCE(slope_output, 0) < -3 THEN 'FALLING' ELSE 'STABLE' END AS output_trend, CASE WHEN COALESCE(slope_defect, 0) > 0.3 THEN 'RISING' WHEN COALESCE(slope_defect, 0) < -0.3 THEN 'FALLING' ELSE 'STABLE' END AS defect_trend, CASE WHEN ma7_efficiency > 0 AND ABS(hourly_efficiency - ma7_efficiency) > ma7_efficiency * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS efficiency_status, CASE WHEN ma7_output > 0 AND ABS(total_output - ma7_output) > ma7_output * 0.3 THEN 'ANOMALY' ELSE 'NORMAL' END AS output_status, CASE WHEN defect_rate > ma7_defect_rate * 1.5 AND defect_rate > 5 THEN 'ANOMALY' ELSE 'NORMAL' END AS defect_status FROM slope_calc) SELECT metric_date, ROUND(hourly_efficiency, 2) AS hourly_output, ma7_efficiency AS efficiency_ma7, slope_efficiency, efficiency_trend, efficiency_status, CASE WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency > prev_ma7_efficiency AND slope_efficiency < 0 THEN 'TURNING_POINT' WHEN prev_ma7_efficiency IS NOT NULL AND ma7_efficiency < prev_ma7_efficiency AND slope_efficiency > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS efficiency_turning_point, ROUND(total_output, 0) AS daily_output, ma7_output AS output_ma7, slope_output, output_trend, output_status, CASE WHEN prev_ma7_output IS NOT NULL AND ma7_output > prev_ma7_output AND slope_output < 0 THEN 'TURNING_POINT' WHEN prev_ma7_output IS NOT NULL AND ma7_output < prev_ma7_output AND slope_output > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS output_turning_point, ROUND(defect_rate, 2) AS defect_rate, ma7_defect_rate AS defect_rate_ma7, slope_defect, defect_trend, defect_status, CASE WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate > prev_ma7_defect_rate AND slope_defect < 0 THEN 'TURNING_POINT' WHEN prev_ma7_defect_rate IS NOT NULL AND ma7_defect_rate < prev_ma7_defect_rate AND slope_defect > 0 THEN 'TURNING_POINT' ELSE 'NONE' END AS defect_turning_point FROM trend_analysis ORDER BY metric_date DESC", + "parameters": {}, + "testParams": {} +} +2026-01-08 11:59:37 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.11.24:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 " +2026-01-08 11:59:37 - lzwcai_mcpskills_mfg_data_agent.utils.api_client - INFO - [api_client.py:154] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 diff --git a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log index 5beacee..c00ee3f 100644 --- a/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log +++ b/lzwcai_mcpskills_mfg_data_agent/lzwcai_mcpskills_mfg_data_agent/logs/mcp_services.log @@ -113,3 +113,47 @@ 2026-01-08 00:56:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief 2026-01-08 00:56:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... 2026-01-08 00:56:21 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 09:52:27 - mcp_services - INFO - [main.py:344] - MCP 服务器已关闭 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:362] - 开始运行 MCP SQL Executor 服务器 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:313] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:314] - 正在启动 MCP 服务器: lzwcai-mcpskills-analyzeOrder +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:315] - 版本: 0.1.0 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:316] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:320] - 环境配置 - Database ID: 19 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:321] - 环境配置 - Datasource ID: 19 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:322] - 环境配置 - Skill ID: +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:323] - 环境配置 - Backend Base URL: http://192.168.11.24:8088 +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:324] - ============================================================ +2026-01-08 10:00:42 - mcp_services - INFO - [main.py:329] - MCP 服务器已启动,等待客户端连接... +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)... +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:55] - 成功加载 6 个业务查询配置 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:123] - 本地配置: 6 条 +2026-01-08 10:00:45 - mcp_services - INFO - [main.py:165] - 成功生成 6 个 MCP 工具 +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:28 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:30 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:02:35 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OrderDelayWarningAnalysis +2026-01-08 10:22:23 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:22:24 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: WorkOrderProgressAndAnomalyNodes +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 10:57:20 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: SupplyChainRiskWarning +2026-01-08 11:05:47 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:05:48 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: EfficiencyOutputLossDashboard +2026-01-08 11:13:21 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:13:22 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: OnePageDecisionBrief +2026-01-08 11:34:55 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:34:56 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功 +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: MetricTrendAndTurningPointWarning +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:225] - 正在调用测试SQL API... +2026-01-08 11:59:37 - mcp_services - INFO - [main.py:227] - 测试SQL API调用成功