From 6190fae22fe8768bdedeae1f55ff68b4c149664a Mon Sep 17 00:00:00 2001 From: jeremygan2021 Date: Sat, 10 Jan 2026 01:58:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/main.cpython-312.pyc | Bin 25052 -> 31701 bytes __pycache__/main.cpython-313.pyc | Bin 14854 -> 30201 bytes main.py | 207 ++++++++++++++++++------------- 3 files changed, 124 insertions(+), 83 deletions(-) diff --git a/__pycache__/main.cpython-312.pyc b/__pycache__/main.cpython-312.pyc index 40cba764e9fbcbece1d300c8a4114e32a0dfd1f9..e3e45f02214bb5a11e8960a1f221d913109d8e0b 100644 GIT binary patch delta 13708 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 delta 7158 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 VC4-@>L3t@Jp{rVXsag&6{{s*h;r##r diff --git a/__pycache__/main.cpython-313.pyc b/__pycache__/main.cpython-313.pyc index 086e800a060702f8c855ee6d230e7fc70fdacb17..7e1b1028a1e8c64b270fffe7eaead9b954a64947 100644 GIT binary patch literal 30201 zcmd_T2~<>9zBhVmo|#9P3I(AM5u759K?Fr6q{?@7rc0-@ox4pL+-+SwQ*xK`5@4nxEpE^|q!KAyt z{?>YHy`${&-}89({{8>^zjxiTSd1JV??y*!LpR6$nk@2T_7k_3YdP*T$8)@*oZGGt z6x)@8a=S`US;lf2Yq#qJ9i!Fd`t1h6u-zyanO#$E+HMxijMkQ01PkKo z$}QW&gfQl&FAv`yAw)3RP#(EGN{HGXEktjR5n}vtVue`5(U!*vaqJy0#ItvTkbt+b zJaPL1VS$yaU$Br{dFx!f^~g%A72FyVZ}H-?Gi zqrNdrbTA!0CQl=zy98MlAuA%c!NG@))%YR6e5$KrLs~3Q#K&YPQH!@QuFe%IE4+ zwkTFBghllkuy0zWko?ixNi{ozVP%Rhs@EXgX1=&k0dq?sMsB?hE+yn5*b+F5$79#9C&P)>(jy5O6ysWj-jMPvXRf>8 zx;w;m#U0YWZ&vzsv(jIQI1dKPB2#?5+`9ap`V}bE{j*}NzC)~rkXXwR>szy8-F1iZ zIRdfv)~6v>X#s=2WQ1v zSHGTbTf2+n9QSfGGiu8>2piaYqp-1lW6gKuHVwWp?)LL8jKRC>@2+_?=q^)yd;Mm< zgMWw@3e_01ch?-1eS+`62-#AXVH!_(Xy?!c1OvW=pIHoU`X zoQ}3Ft&N?N^~6_rvW%lc;ZUbUOB;62HY_%Z&{k8y`Ai z3l~*oJF1z>)P=+2y+aatd5)A4?dDy-@7pJ;j@gKICgsK{F73EE02#~ z_)sD*y*zR3H5Opp(>wM0W0LF1S0-QY@1nJS>e9>O$IefmzcPL1Z7JB>=cnF2&w`C# zIX=eV;K zKKhuE)2}>@JcWfU$mSiA_qof6#!{F*^#imIBd0FCH}U2b!G@4Fosf+eOITP+R2~c# zck1J#6X#!J9S6&#u{Ot6pD^iO{={^14_}eer%*-kBjASMet{_;!Hr=_q{p4S4d?|(dg z@)!#+_Tj+z_nwi$o;p0)C-wT|>xakBofGm=NYrW>)un)p!lj94-eRs3$9g7SykHBHf($%6e&Qq}sjLHsCD*~@TUM810(L(*mtG6J=^a(sNKH{f~9KW;s}o*aDI zrjr7G_&mN2AyX~@J}!7Nuc@~$PF?!0M6!>Hk?_U$CAl6uB8MG6dU@je@7WBJ`+I}q zFH5BxfAzVkOVS5F+4lxU3JZXs@L=Tl3m;9NeVmcgJe5?MGEbkI_$SZAb7!S2 zdw($9Ga!-gy*B>z5k_J-jKBAqsH!fhlswvtks*B3D7E4jsfF1kjl(3ab5-$)o6?Mh!9j$Hco$_#@aeK0#TEkG` zaJ3ZgbF{idV?~=|zoX36ozE{TI^01;H`q)P= zqQ^!3z3sw-&28-uvmlcnJ~#37GeU`6WNAQ;Up~buJMKA&xkNOS?BDBbYHD@0ureX7 z@yFkry7(xXvD;GGr$!y8vbF2uwT>`3(kj& z%KQa)x`d5XeT%5A?r^%`u0my`3Osjo{9|e8VmOYy-zREVy_x6umA6p_Mxx&*Kk$cp zba3*cf01H8J&5T~)YP^=h&-yPZM8du_Wd25tVt$LcqU)HJayqIVH0Ii467BsK%_0T z*M+y}^P@2;jc3_ovagq>FItJDzV}a8mrvodUP*6cH6$};~#_|eN# z@AzlO$B$1xFJ&|R_*2v8-epmy&OSeJ{2aaxsql|KI{x<2@wd++)nYkSTKHeSICb=h zRIW1v2n{z@ed$Y@e&fpY8$V#)SfeH$J4dZgpDIc-^?ILF%NO25Z?Q;||9oNWivP2= zse~c~6gE?C1WafIU;bxloQm2`=fTbfhyT4-)C$hdF2U8PL_ql=c8mD9TmK2p@HA)V zXkn+vq2oBaLbf%*w?$DWdBLm+O~daBgs$ABkh#JdN_oyB<8POeSGZJl$U%DSoXjoM zP)E#p0eX>@SLZtT5R$j+;U30u zF2kRykN2L%l8Tv4iumiEBgRQ7V~(pTZCSyd zUvH}QJI-vf=|x>rYjbm3>s~ROCtkfHtqxT*9c&E+C`PS6Euw04l!!8oiD2@ z;rWKzZ6$n>y~fUqn$}L|ez&OF-|Dg%g>NB0!2v@w60gwUZfqBvVwl^x-{AuL)Udam zI1EY-UkH2c6-~|UU9P5v{gkh$deC`Dvy@d)Wg{ z!MXoXi^H{SOZ&qv;*#9Ux;p5+Y(EyaypBU+Oe5y$u1;rz^I(Tl=#&a2JdE^6*5b91% zM0m8v%SX&9K6A>jIjyJUx;cD~%Z;e`o^k|DD;P;#?Mq$#^VBsy{eoWAs449D`d=qx zdeo!Q@uxS8W@dY;Mi(yjl>Z_o@uq=GT>N_@XO8b(cUE&Vl1nT6T^wxJ7p)puw9dC^ z-Om?o@LAG&d6e|{BVQ)lJX=QNlTQD`s4cfYY;zv&kVDM%zG2Z=-^^?v(o$<1&s zW8>{)&K%y`?F-KtEFL!Hd)4{3P;ah#*$(tbi{RvUR#+R`T`s4z02701Z4{i;4OVwo zW24jUZtiMpJJg*~A_(n*_2GSBg#$5ME!NI8k)B)qFivyzoS@)20n?YD%H-W0=P;oD@>bnI()Ijcl7jX{U&Py?&CzN6jU%0vudd%N7NE;KHU$_}S!I?(C{ z0BTsCzkIc*Z)v48XFq+w>NdQIYFBn8XoF$v{ACKg*P-bw+img zHl*olZ)gx&sSt2=xLobjArMZ$^$1)AnwEt}$#{&6$H{mCh8Ww>Aaz=UqZ8l#UVJ(f z=_my_NCwqYAZZaZ$mWJbi95=TS|T2;7&TfREyEChbUVg$PlYGDf5pkA!|KJOsSA6y z_3rby`}Pm37mj9R_f&bd_46kyht=7m>6ty{o`n8ICsT&inIR#6u3jkRN!iUmNSfjE zmkbhKJ!@!aSXZir(4ip>7qt*NG^F987mO`xn?VdKYHFg1NAB_-WX^h8I1H*vjA(T= zbzyot)Zp)MQPu2pis2oOL;Hc+8yfeaC!B4pF>NNHm!kKPL4q;iI2qJs0?ERJlVq@N zdWNV|WIRR2oUS-QZe;Y}&rK`?x+2&Mi*vl{7|*_b_enH-&S+vvPjPR8XHg%TI%PD| z)>G}-)!%q>&(GC1si`TYSxv2n1AUXXp1?ATjTVKAt5d+vo*<0CqV2+>jc?VZOy%kj zR(if3l-QLu1iR)u^1|+j!}1+WX&#_rSMo|;Rj9Obl|>wv${{_|oWx+20f&-vsqCux zAPKLQ!Ht!KqxdH;(*PPuH`34G2z1oN%c=8vIfhe_&gBL`9Kp6`gik|gD_4~!i5t_o zIzSrfsfm!O;5)=OD(EUxA-(`ebDxzeh(Ob@KBA<7mP5F|wB^#c%3q<(e!(b8idL5% zq+5bMRsU`2)+Ng6@EW^<*An~*!?v1T9mcH)Y42ST@WTSngwyD4yV|LM=oa}FskbUB z5lx~)gtvyOX`16}v6g5W+uGgEZarU8QBquME$GgamZ%nIXD9ef>z2)dbqcIyHYPQ! zI-S;ObQ9ODvaPjt-&N|sYSu(+7$R96)&q{V)+Vbg@(hjcXzp|hR$zLe6=Git!6a7; zec*v*w1L)6ECbF?T2DnSD_xVQ0|fCqFk!E#a~^ayVs3?y)qMz)f6H<)0!%A|T`or( ztAiL;=0bkWjz*_sw`}o?VI`{}D6H0W2(7M8NO4X39S0ly1r&Au+KOtHx2myA5EV{0 zH66oJY%UigQP_s2y_mCP03du1A!wR$r=pp9xY4NSp7QI_3H?bwkIwAXjfO|}s%MmO zhJ@?U3rC`Je9<|>(Yd`_;C|xUC%!!vkv0;M<%`G~RA1cUC93cj5eYMDgC%h^Ht|f| z$+~Cv3^b0$#GffYSw3LA7PI8{DlTr(jrgQzOg9s`n8mkOa;6w>Oy#h#%B!yWeL6f( z@A)&JRBXyf%u-*>(l-x|EGzXbEB&c{WZV6|ZTEYl8*XtLOX5Uy{PmbcBQZ;SF-r!w z45__DZTJslZi4ThZ=1P@L{9AbR^{ScNN;{*}~!iyc9aWxcX&J&!Rv5SI#Hbs?9DV#+p;YAoWh44CFZpzIn4TV@>Zzn{gB4WS0qx z9@UUNcEfyJHLvFla?R%THwOLXa=XlE6|-BpLAe&fPzM%EyF}%3T(t_z9)`fK*X(90 z8*j>jX*Z$OEN|tF8SFt z^DUL~Vv1bC{JCJsYX85bmf}s6mR+?LZY)16cJ?sjhXslCSGX(89v&zW`o6^yFB17h)`Wy;dbVbRC;Bt2O4(S-zHu=pMe0t0`%us}RewrB@UR>q<>#;iE44yzknqm|hI zCTqKEt+61_T5`bYvNmBqlW`Ly;LR>5uwp+EyQ#TWtXI~8^khtS$C zfJ^FjTAN$jz|pi=Y27~Lj|WFEI@W9m9KdM zI$I%IK4i_VrQEFr*1gVV$SACmb-B&BJkMI$deG{hzp%(#vjOJ`8ejpp(%c3=)RL7R zY%t^x*3Ny-R&ti0zqQqEr8%?{FrhE)&nR+N<1TDMo1 zRasd%37lD5nyl4TR4sqetsCH!=W;#_23(4D@3xYflF*7<%Xn*5b*;6^UQuB!s@gKk zucEBH#F{nv=9`%l=by}CMcZ0aZQo(tygM`;^O&X%=u*Kzcu}GdOL&Jm=U-s_mbev2 zne zY;dx|y`c~l2SWjdem!=Z_BJ^-iYfCK%kMpjRNZIYUDVqJozL8+9~k}H)cm#QlGBlZi;6dzE*4#M zytCzvdZ`p9fkacnOE5%zGm!p1Oe&%Z09@4Jqi+LV7mfEg+PWmzF0@hT{S;aaJ&#T% zd=#~g4(w4i*%F08@_viF&9&9FMHLOTMVs+`iI&QuT@5=*Y9za;XvMAqn>0lYrZVA> zs218EhVT`ps6xL6#~ws$6tKb7wV#UBBn(mN5sm|3beM-f9RsFMe7KlCMPsAG)zpg5 z%ISvm7HTgIEQ}b*gt$!&A>Mi^@zy4i;W|3qVnipTOKlCoc}z4r4`RN@q)kW=QsmbC zZej*$@|gFL(5ES6NC;md{2TalFUL^p;bx+^n7E#bQ5cor1lDdEdgyAdcV~@nbIr9v ze%Mm$E95U0^%o3o_C+rHB5WC~J*8et?M#@-qVFl0N#Hd4qveOoJ(_FkILN0?tnXWY zd}Gg+UxSxXjfO>f()*f0L_~XvPHKBKqiG8V3eFwy@cog`+%vem*Eplr8)C;2lKP9! zm7gsi)C>y4$;(HQ*ZPvz4z0Sna5%YSIH9z+Y&1N=Q}(m)r31+$;Y&xCF83VrMWhda z1l0ldG(3EPP6q@_ipnz(IQ@R@~Aa)pzWG=rqrI z^hDCK!G9QP8(vWEDISf<7*v7v`XVOdrin{mITOZNQb)`gK6A#vK5zcr-prz(n>XK# z=BzpB55s~HeX0-t7ZrMmSbHOO@xVjASleK_FE+Q=cmp!_1s-#MvDakvs;z(g4dfYN zcEulmvsuBduq*y>JsiB8!VvqVIc7$M_kaFI&n48tKZ1*@RJbcKcAg6>-Jt#?XYE#< z@`sB}WEL2~}^L->^dpF{$h6{=v) zz`i=W76|u#KWC(;ih}bo(0fCmW`w;VIwL-baR6WOF~i3)Z=5jrgwN{}0iVcueWJhz zMnm^l4<1c7A1`G$1nW@wEkA13h2nE+d&cu!XZ)R!kENVkCcBZ3W0cvhlEXXJqt2#) z4J=SyK+L#no!t5^3*yFa0Ix>z?G`>^DG0*(Zk3yDTEH!F?X&)UP9wj7!p&EDJ}Fpw zyE>5X*Oj9Dj-^Qcn@f=rQi?!ZFfQvw)XyxJi@Cfut4-Gb^`&8{e2#R#u3b`tC93>4 zxmV}oQu#FSLRLPVM8kF+zmT`6Iae4)bGSVWqxoKcpR*p2>p{EE{Gu9g#&aLJOmgnk z3O>UHo^-yxL)c8#|Ne0V`(nR6%m2>yzuAcU%9moZNANj$Iyo;uz}^0uh6Bbd>HZJn zR){HANIVEOPQg>m;=vsi#R|AJfs?fZL`YU%vx(!cX(E=RhpU+5DuautNZ_ANZro{flZ)ovRq;j z9LQV zlKiG=O$&{8_(>=(`Telch58}EfS+(0e!?X(nqWXxhwYB)%oV_4v zOxXOP|FEfRDFRM>^y2u7gQQsT(TfOpWDr^xQd9VKFD|@4@!b1T0=AGCQ9a8NVchm?Gi7lkrnBhROIJFhoOu9l_QHCLz&8W`p!aiWX)Gj4qL4gmt!egfcNu z%yL7YQ20OK=_Xm)SNIZ%CkdO~_`)p&)lhqrXd8+nX3iYrIXJLxI6iMQJhImSM65A- z&8sgSy!gP?aPN-$K7YhpSMN3Mxu(8uy9w_iX=?c$FJ5@%@UErK4a)iq2|B5>|T`t{zPWbU#}PcEr1Q z?QEaa^nvuV%_FJnz1izWlT!O1I=lMZ`hl9!1<9ka2{$7RvBqA*Oblm;9?>WI^ojkp zpX)Pj#)A*}-2%=Gg&0)nmX?c5>Q#@%E$H9ii?j8bMiY|z@AD;O_gaW?h<|C_^{B-I z-yV*-ySMm;Il>cvyt1DkSaEi@&%F3}B{Wq$wa2?hBVs)DzKDe*5jJ0h&6~6JTEsTS zcDz(~JvwX9G8|nDtrkn9C+qm0{>Fjgv#mZ0NMgvEP8mldV?AxY$VDTOIljmo?~?nj zMb`bE^6p)CZbSbDO2PDXDV+Xl3O|pmyIFvW&8+tGLcWCDi%rB1tpj@$XB|)uM0)P; zse&aeqNnuBh}2&7jlvB>Z6k%{o~|?9C%XqS1|IP(TzM^im3L=Dula^P?1ZJyG8&W6 zA9u1sZllp;>p-e6IX_@eUFco3!k4;oG%0Ps;!DaKNhba$EP?0mIE*w*_4LgTTHC}bi zABF!!knZSZj*ix4a?VTHvII$)^PyR_qz0kkbt@VBYNw$-66B7-;<3l2uq)=@!bo zuuD8|9Fxq!%@fCHm)h6(jqPZ6X4VxOTeX*B$hq9D&$EYk_!tT z!6Xw45MC!qU1|tU&w278`zcZIQFcqdq9kO5*){Sgbx@8$S;_1i>+YCiH1+#mm196I z!XyX4q`_R7%l|p6uA%Y~3m-#uWuY*GqrQw2|7&u`-Y8nf#91X75=8v2uwCC_b)a=_ z&cnw+8WGPYkTgOH1r@VIHRvpalw2T{C_yUWkSfeK0v7~z16&bx*fo|OdxSg=_@uR9 z9~@xoAV0uqdo{n~$rX~va6X-=%B@n}DqYf> zkH?p}kZOmwBmq?Mi^#qg2HY4;QW4Nhrow0DsnM>B^RVNL-DN9+vIo1%wWwtzpTTqG zk*+9vR86Mr7ktZKHzbN#byOhs$gb;Pqk7&OkUlz0=<9&o=1wzToz0&R_V@#>Msr~{ z&4q3yQ#y+*ip(H|GrU6-&TLiK$_j)26gF@gppZq1%nnkHV!XH2|M*1>flWvzYzRml z80=ue1b2HQS%mDi?M8 zq5X^%e)?IoC3!#zjl@BsiME!y+>p>TIVDwCk{^f)YfwbsSn@Zu|GW)4t498ILw)~O zWJ0W`Wf9e^^zSGrk~{3L*yo;=aG)#vd*DIo5Tz_?C1)HoH#9;`xqUyR0(8h*lDa`# z-3bT(Ns&)C3DIa?Nj4D!s^$5!^!&F?LM&{vU=%pv`Gp(81bie+im53S=CQq25h@b1 zpkZ)#+ArDoZ^+6o*%&MuZ26vqTWf_WO7}Dwhha?Xh`IrzTb+Yv`al2czy7ORwQ18P zY0pOA#rOcHUn^fLeQuHpg<6tmoTLSY5l_xWoKgCOj&OFHr#EoOhHok zCyGRvUlMUpQT+SC(E1miBab)9AWZ<_0Wv;;A*#T2NCFTh3i&a4V6EbWSIF1UDPaG2 zucPrn(ZG~Cad4$k)Y3p|b2IgDTeP61(!EY`2FajuO2QHnn~2(WsH6yPAsL}W4YP=0 z>_ywLOz0_wOLoj6{<8J`_+t;699A!i7SI`LrNgKwtxKIjcw)2qTxq``c` z)MuLEw6W$HGZ!9pqP4HJxBj+8Ye>Q#V?^|cNBSN){+(X+M0oV5Is8OfU)hu8*Ujkz z8Q08N*Q3)emUv6=^=0q!Hn;ksAMlzVxRI3RwJ!H1tr$rvyeN1VtQ|@p+TtzR>04hj zvVNEMo?YH`yS)kbjU;@_m+-COguT5MS@ZMBrF(>jlZc=M;m)}w_~`to~^ex+VEvD@Elwa>Zyq-FcN^y}VHr*-D6{puxo?Cq}8Ip>wH@9T43&zEk zp)Ox;$w*A;&2?yqTX!qCn55p~QFC-(g>=Nqw{Z1UwUnBxj2d2$>(O10j`KWxYW+aLaCF9KdiG${VERDP-~nIy zsv+G~Q{-uRX8VowEFw;BzhTYvC_FhQE!gQM;*{l9G#9rJBC(jHndMwU z@(l>T?m2752^OzyoiAhkXm0++gg2^uxuyLX{Rf}V`@9HeEc#o{;uw}DHVgwLAq`@- z2;&TAFc|-E8>hKqp@NL@{-r))R3AB_Px9%LuIW=oO_9&ub86SHDdoEvfw3E&bW!u8 zMOPi4F7{=VdGEc?7hd->echj-&Wr^9>q-{l^;x=x%$#sCH*8*p z*S{_`R+v=(dRLL5LaX{Ir-0?BS`AD;k1WlNOKIH&teG{h)3nOi<5HOa|7MTa{r!Z$V4|=DE)tHX9&%?ju8Pu)$At;KR_4;dJ`LuHhqyp}_kNTmF1Bpn8m-p&&&s_JE<-0fs`$#|9Z&xm1i% zSU8JOz*32t(1JPVk>fyE^39AyfW@;16en2!#f2~u+9EAuB=8PlB;uc9%U!hrySFNP$ zco@ykFfil8&~0>k51lH19hcQ0@Eow{|j&D=Z`zz0ls)MmSdjwYoW=mZY_AHi$^=ktFpDZZaXZ zA7T2@rL=+p*0L2$Sc$}cgFpAvFbG|)`#Nx`4*)q0z@?GEWm{icZ^Lbq#*iw3P1ujS zdQ}tQQFj8DjlS$Auh8X-KHxPUxUn#EVBh(rzJ=>N(Vkt;q+E^c)nak?>^#11{`gm2 zzHGaP=?q9fQ6#bApXEg6ba zM$Hi?D*Gy*to}y8VxXvha_3@<0I-y30a(_;{Nd`&;duR6Yb>>@ejHJxDNR!SBvt{- zPm(k+yG=}vFKtk;o#0sjMUUhdKzRVtcx-f`!=;B;*m2ZwMaans$o45Dd*rE}$@Xc@ zne)_PH$ZxLrC+vBEZm$2(z3a7>aSZsL*@9-V-Zr$r#nQlc`7-tS)rPwd+P6zrjB1A_dMjr*o}}Lz`RWu113JHE&#sd z+N?v6dE~+0G^&$Br09XYntTPHLZh5XgHv6mA`?IydxwCywC;6Ao^6^+M}qbqvP#Gy z+j$U6rKA#PvqD9UgumA8=C1DULw?shV|TJ&Q%g#M6x&Ffjp_8SwK914CmF2dNJnCV z!rQqxrF)4VklZ08D?k}L*^=oN6`4%xRob;0O$InHLATDm3?K&uDcsQ}Y5Cy7nW+o! zO}x@WzE@6-KmOW8|AleScPHL@W%B&nbm)lP*&>)R-G<=3Gf1))IART)vCU4Gb#=5s zcZ-yyacia!PVRMtTW{0!6^Wf};;X^*Dd=C4t%oN|%IeqQ*(Lo(Kefs%#OG zR@3(=!#NT_hJHRF4<^r>BLPgK^lp*wD`XG|lAw=S(s7z2&sz(B+#C`Dh((KZKmvE; zu!2L1D>UG^NvkVFU1}30OM*G3jSwP>^PdbdAu4wJ8>m}^EeKoyjPA~a(Zk&~GUcW0 z!Pr5a*O-4zT`;38R%o`}SvIHj!x3-H#;dVc_1E+`0TCsO7SQ;ewVj~|J;1_AFrA3r4!&4Co4`>^_Gl5Nodva?*5&}{}CELXACC|&zi1BWDYc4 zi&%0!e$hqe49Arzij+5@PQ6(P%MKhJh9#kvERnU!>ya@Y`>6%}j#H@+2SvtwHHoIr z)!uvSeYtyl@%MWp?!OVA+`seWL5~{2fnwJ5t?`r(o2&z+!=@aZJUCI;SLaQv_ck4lWu-Kci4 zqFT|vek5s$FKNkO<+CM?;x$7Hhf_B6?ipK&fO zHhTH{hZned?;EwmooMK5xMoSEa_sKg{bb#B%ff+`*DTrBW2~bKQXyX&U6eJNxOg

*`Dt!(3|^lpvdFX< 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 G^#2RhU1FyI delta 5649 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 Gng1X0#3`Zx diff --git a/main.py b/main.py index b87a79b..9cd98de 100644 --- a/main.py +++ b/main.py @@ -11,12 +11,29 @@ import random import uuid import os import difflib +from concurrent.futures import ProcessPoolExecutor from dotenv import load_dotenv load_dotenv() app = FastAPI() +# 进程池全局变量 +process_pool = None + +@app.on_event("startup") +async def startup_event(): + global process_pool + # RK3588 有 8 个核心,预留一些给数据库和系统,使用 6 个核心进行计算 + process_pool = ProcessPoolExecutor(max_workers=6) + print("ProcessPoolExecutor initialized with 6 workers") + +@app.on_event("shutdown") +async def shutdown_event(): + if process_pool: + process_pool.shutdown() + print("ProcessPoolExecutor shutdown") + # Database connection parameters DB_CONFIG = { "host": os.getenv("DB_HOST", "121.43.104.161"), @@ -25,7 +42,6 @@ DB_CONFIG = { "password": os.getenv("DB_PASSWORD", "123gsdh"), "database": os.getenv("DB_NAME", "gsdh") } - # 商业领域同义词库 (Business Thesaurus) - 用于解决模糊语义匹配 BUSINESS_THESAURUS = { # 核心意图: [关联行业/关键词列表] @@ -42,14 +58,21 @@ BUSINESS_THESAURUS = { "供应链": ["物流", "仓储", "采购", "原材料", "制造", "工厂", "代工", "OEM"], "人力": ["招聘", "猎头", "培训", "HR", "劳务", "派遣", "灵活用工"], + # 短视频与互联网专项扩展 + "自媒体": ["抖音", "快手", "视频号", "小红书", "B站", "直播", "带货", "种草", "网红", "KOL", "KOC", "MCN", "内容创作", "剪辑", "拍摄", "流量", "完播率", "点赞", "评论", "转发", "DOU+", "投流", "橱窗", "小黄车", "团购", "同城号", "剧情号", "知识号", "颜值号", "三农号"], + "互联网": ["电商", "平台", "流量", "运营", "产品", "用户增长", "裂变", "留存", "转化", "GMV", "DAU", "MAU", "PV", "UV", "SEO", "SEM", "ASO", "投放", "拉新", "促活", "留存", "变现", "闭环", "私域", "公域", "矩阵", "账号", "内容", "社群", "小程序", "H5", "Web", "App", "iOS", "Android", "中台", "SaaS", "PaaS", "IaaS", "云原生", "微服务", "低代码", "零代码", "敏捷", "DevOps", "CI/CD"], + "电商": ["购物", "订单", "支付", "物流", "仓储", "采购", "原材料", "制造", "工厂", "代工", "OEM"], # AI 行业专项扩展 - "AI": ["大模型", "算法", "算力", "芯片", "数据", "数字人", "机器人", "智能", "自动化", "Agent", "RAG", "AIGC"], + "AI": ["大模型", "算法", "算力", "芯片", "数据", "数字人", "机器人", "智能", "自动化", "Agent", "RAG", "AIGC", "智能体"], + "智能体": ["Agent", "Copilot", "数字员工", "LangChain", "LlamaIndex", "AutoGPT", "Coze", "Dify", "扣子", "工作流", "Workflow", "编排", "RAG", "知识库", "向量", "Embedding", "工具调用", "Function Call", "多智能体", "Multi-Agent", "Swarm", "CrewAI", "AutoGen"], "大模型": ["OpenAI", "GPT", "文心", "通义", "Llama", "微调", "训练", "部署", "推理", "Token", "向量", "Prompt", "提示词"], "算力": ["GPU", "显卡", "英伟达", "H800", "4090", "服务器", "云计算", "智算中心", "租赁", "托管"], "芯片": ["半导体", "集成电路", "英伟达", "华为昇腾", "寒武纪", "FPGA", "ASIC"], "数据": ["标注", "清洗", "采集", "语料", "数据集", "版权", "向量数据库"], "数字人": ["直播", "短视频", "IP", "形象", "克隆", "配音", "虚拟人", "元宇宙"], - "具身智能": ["机器人", "机械臂", "无人机", "自动驾驶", "传感器", "视觉", "雷达", "端侧模型"] + "具身智能": ["机器人", "机械臂", "无人机", "自动驾驶", "传感器", "视觉", "雷达", "端侧模型"], + "AIGC": ["生成式AI", "文本生成", "图像生成", "视频生成", "音乐生成", "代码生成"], + "AI短剧": ["短剧", "视频", "内容创作", "剪辑", "拍摄", "流量", "完播率", "点赞", "评论", "转发"] } def compute_expert_score(text_a: str, text_b: str) -> float: @@ -102,6 +125,66 @@ def compute_expert_score(text_a: str, text_b: str) -> float: # total = 0.6 -> 属于高匹配 return min(base_score + semantic_boost, 1.0) +def calculate_matches_task(user_industry: str, user_vision: str, others: List[Dict]) -> Dict: + """ + CPU 密集型匹配任务,将在子进程中运行。 + """ + matches = { + "customers": [], # My Industry vs Their Vision + "partners": [], # My Vision vs Their Industry + "peers": [] # My Industry vs Their Industry + } + + for other in others: + # Handle potential None values safely + other_ind_comp = other.get('industry_company') or '' + other_bus_scope = other.get('business_scope') or '' + other_industry = f"{other_ind_comp} {other_bus_scope}".strip() + other_vision = other.get('vision_2026') or "" + + # 3.1 Customers (They need me) + # My Industry (Supply) matches Their Vision (Demand) + if user_industry and other_vision: + score = compute_expert_score(user_industry, other_vision) + if score > 0.15: + matches["customers"].append({**other, "score": score}) + + # 3.2 Partners (I need them) + # My Vision (Demand) matches Their Industry (Supply) + if user_vision and other_industry: + score = compute_expert_score(user_vision, other_industry) + if score > 0.15: + matches["partners"].append({**other, "score": score}) + + # 3.3 Peers (Same industry) + # My Industry matches Their Industry + if user_industry and other_industry: + score = compute_expert_score(user_industry, other_industry) + if score > 0.2: + matches["peers"].append({**other, "score": score}) + + # 4. Sort and Limit + for key in matches: + matches[key].sort(key=lambda x: x["score"], reverse=True) + + # Limit to top 5 per category + matches[key] = matches[key][:5] + + # Hide sensitive info + for p in matches[key]: + # Safe phone masking + p_phone = p.get('phone', '') + if len(p_phone) >= 7: + p['phone'] = p_phone[:3] + "****" + p_phone[-4:] + else: + p['phone'] = "****" + + p['location'] = "???" # Hidden location + p['unlocked'] = False + + return matches + + # Initialize Connection Pool try: postgreSQL_pool = psycopg2.pool.ThreadedConnectionPool(1, 20, **DB_CONFIG) @@ -132,20 +215,34 @@ class AddUserRequest(BaseModel): payment_channel: Optional[str] = None def get_db_connection(): - try: - conn = postgreSQL_pool.getconn() + max_retries = 5 + for attempt in range(max_retries): + conn = None try: - with conn.cursor() as cur: - cur.execute('SELECT 1') - return conn - except (psycopg2.OperationalError, psycopg2.InterfaceError): - # Connection is dead, remove it from pool and create a new one - postgreSQL_pool.putconn(conn, close=True) - return postgreSQL_pool.getconn() - except Exception as e: - # If pool is exhausted or DB is down - print(f"Error getting DB connection: {e}") - raise e + conn = postgreSQL_pool.getconn() + if conn.closed: + # Should not happen with getconn() usually, but just in case + postgreSQL_pool.putconn(conn, close=True) + continue + + try: + with conn.cursor() as cur: + cur.execute('SELECT 1') + return conn + except (psycopg2.OperationalError, psycopg2.InterfaceError, psycopg2.DatabaseError): + # Connection is dead, remove it from pool + if conn: + postgreSQL_pool.putconn(conn, close=True) + # Loop will continue to get next connection + continue + except Exception as e: + if conn: + postgreSQL_pool.putconn(conn, close=True) + print(f"Error getting DB connection (attempt {attempt+1}): {e}") + if attempt == max_retries - 1: + raise e + + raise Exception("Failed to get a valid database connection after retries") def release_db_connection(conn): if conn: @@ -294,21 +391,21 @@ def get_tablemates(cur, location: str, exclude_id: str, user_vision: str = "", u sim = compute_expert_score(user_vision, cand_vision) # Weight this score cand["score"] += sim * 1.0 - if sim > 0.3: # Threshold for "similarity" + if sim > 0.25: # Threshold for "similarity" (Adjusted to 0.25 for better precision) cand["match_type"].append("志同道合 (愿景相似)") # 2. Cross Match: My Industry matches Their Vision (I can help them) if user_industry and cand_vision: sim = compute_expert_score(user_industry, cand_vision) cand["score"] += sim * 1.5 # Give higher weight to potential business match - if sim > 0.3: + if sim > 0.25: cand["match_type"].append("潜在合作 (您的行业匹配对方愿景)") # 3. Cross Match: Their Industry matches My Vision (They can help me) if user_vision and cand_industry: sim = compute_expert_score(user_vision, cand_industry) cand["score"] += sim * 1.5 - if sim > 0.3: + if sim > 0.25: cand["match_type"].append("潜在贵人 (对方行业匹配您的愿景)") # Sort by score descending @@ -358,7 +455,6 @@ def resource_match(req: ResourceMatchRequest): # 1. Fetch current user # Optimize: Only fetch necessary fields - # Points are now in checkin_info cur.execute(""" SELECT gd.new_id, gd.name, gd.phone, ci.social_point as points, gd.industry_company, ci.business_scope, ci.vision_2026 @@ -392,68 +488,14 @@ def resource_match(req: ResourceMatchRequest): cur.close() release_db_connection(conn) - # 3. Calculate Matches (In-Memory Python) - # difflib.SequenceMatcher is O(N*M), running it 3 times for every user is expensive. - # We can optimize by pre-calculating and caching, or just doing it efficiently. - - matches = { - "customers": [], # My Industry vs Their Vision - "partners": [], # My Vision vs Their Industry - "peers": [] # My Industry vs Their Industry - } - - # Optimization: Pre-compile SequenceMatcher objects if possible, but ratio() needs both strings. - # We use a threshold to quickly filter obvious non-matches if we had embeddings. - # For now, we stick to string matching but handle None values gracefully. - - for other in others: - # Handle potential None values safely - other_ind_comp = other.get('industry_company') or '' - other_bus_scope = other.get('business_scope') or '' - other_industry = f"{other_ind_comp} {other_bus_scope}".strip() - other_vision = other.get('vision_2026') or "" - - # 3.1 Customers (They need me) - # My Industry (Supply) matches Their Vision (Demand) - if user_industry and other_vision: - # Quick length check optimization: if length difference is huge, ratio will be low - score = compute_expert_score(user_industry, other_vision) - if score > 0.2: - matches["customers"].append({**other, "score": score}) - - # 3.2 Partners (I need them) - # My Vision (Demand) matches Their Industry (Supply) - if user_vision and other_industry: - score = compute_expert_score(user_vision, other_industry) - if score > 0.2: - matches["partners"].append({**other, "score": score}) - - # 3.3 Peers (Same industry) - # My Industry matches Their Industry - if user_industry and other_industry: - score = compute_expert_score(user_industry, other_industry) - if score > 0.3: - matches["peers"].append({**other, "score": score}) - - # 4. Sort and Limit - for key in matches: - matches[key].sort(key=lambda x: x["score"], reverse=True) - # Limit to top 20 for display performance - matches[key] = matches[key][:20] - - # Hide sensitive info by default - for p in matches[key]: - # Safe phone masking - p_phone = p.get('phone', '') - if len(p_phone) >= 7: - p['phone'] = p_phone[:3] + "****" + p_phone[-4:] - else: - p['phone'] = "****" - - p['location'] = "???" # Hidden location - p['unlocked'] = False - # Clean up internal fields to reduce JSON size - # p.pop('new_id', None) + # 3. Calculate Matches (Using Process Pool for Concurrency) + # 将 CPU 密集型计算提交给进程池,避免阻塞主进程和 GIL 锁竞争 + if process_pool: + future = process_pool.submit(calculate_matches_task, user_industry, user_vision, others) + matches = future.result() # Wait for result (blocks this thread, but not the whole server) + else: + # Fallback if pool not initialized + matches = calculate_matches_task(user_industry, user_vision, others) return { "success": True, @@ -480,7 +522,6 @@ def unlock_contact(req: UnlockRequest): cur = conn.cursor(cursor_factory=RealDictCursor) # 1. Check User Points - # Points are now in checkin_info, queried by gsdh_id (which we can get from phone via gsdh_data join) cur.execute(""" SELECT ci.social_point as points FROM checkin_info ci