From 0902091f085fa1d5d3532bf7ddb57f52d511ff9e Mon Sep 17 00:00:00 2001 From: aaron <> Date: Tue, 14 Apr 2026 20:51:38 +0800 Subject: [PATCH] 1 --- .../__pycache__/recommender.cpython-313.pyc | Bin 16105 -> 16237 bytes .../__pycache__/screener.cpython-313.pyc | Bin 14431 -> 31373 bytes backend/app/engine/recommender.py | 16 +- backend/app/engine/screener.py | 724 ++++++++++++++---- .../llm/__pycache__/prompts.cpython-313.pyc | Bin 4394 -> 4709 bytes .../__pycache__/tool_executor.cpython-313.pyc | Bin 10186 -> 12377 bytes .../app/llm/__pycache__/tools.cpython-313.pyc | Bin 2410 -> 2679 bytes backend/app/llm/prompts.py | 5 + backend/app/llm/tool_executor.py | 40 + backend/app/llm/tools.py | 12 + frontend/.next/app-build-manifest.json | 5 + frontend/.next/server/app-paths-manifest.json | 5 +- frontend/.next/server/webpack-runtime.js | 2 +- frontend/.next/trace | 1 + 14 files changed, 640 insertions(+), 170 deletions(-) diff --git a/backend/app/engine/__pycache__/recommender.cpython-313.pyc b/backend/app/engine/__pycache__/recommender.cpython-313.pyc index c493ec5c10c14eea7b3d4dd1184aa0887c2f9234..f0bd9d9cdae6fda177e65e5ee37bc409c5fe50c9 100644 GIT binary patch delta 665 zcmaix%Wo246vgMxFd#5IMks`50inEKr8LyGHWk|nm~Ox|A%aF-m5{h-e5|zYYOba+ zZdjQv8WTg*s2fQ)GOW5V?u=X0m^7tL7skZ<@eeqQncq40+%sQhe%=55z&J7(bR1oG z@;`3pXWtq}QP}J8v8RB&mGW%xXp4_HQBI5mjoWy} zX>7)=)gyE7bM6oV_kgpycM8u7$2#6-Jf5QAS$1sg6t)Y0PA>EKHNW`K3cJR#mwOMT zIrRsD=u@by68e)+T2S|W%W)F>dMGWa_oY=Ai9uMR&>#n8UOkvmF)l#G3WGUdyZ|h- z#O?~u97UxryEKXp$_7c%ON{O8HHwpMDG}FLcy$$7P$J%nBn;I+Jk*FZ9!er(S*Tch qd$W&KaIu%0eQ*v{ndYM!meBZ delta 690 zcmXw#PiPZC6vpSxW;boJ+5BltNSefK(zH#|CL7u`fmW(QBi)25t~nP^BA%i*!5R>> zDp<=16%P^=q&Kml7QK~T%=TjFLA-k@B7~;WgBRZ{?FaF*rlY*6IEH$hYw!435}h>mOrOs9F{eU? zb#qyJWq@3vOK~RT8^hVUtdJ>smWdOC<_X_MhOOn3Fv;5aWwu%fvb1)c-rr(Pt;9S@ ziAe>Gz12b%zblN{p5UzQZ-qCYrUXI`Nt67N{cE*nwdYGe@kchwugXUb8c>c#@$#f# zSsUe-<$uR3z-luW9tL4k(N*#p7U!fL0qJW>w+p-HVR2sCjbF;4*i%5K(w@6AjG~i~ zv8Xcxx+rx@HqNt94MJxQIL`ytnAAE&?wEE-kJ(KZ=pNZrWX@s!j2Y!Ab3A4V)lu~e zW;RxJ3;TnByzdb4dfzQ__6yZuTPr=bLAkv%yDp>_0B%8R`j}YIyGB*;wQg)?x~bw{ Kf_zuSFZ~bcpQqmd diff --git a/backend/app/engine/__pycache__/screener.cpython-313.pyc b/backend/app/engine/__pycache__/screener.cpython-313.pyc index b2f800e76c8592b435dc6664a8676251c07ffdf8..a1a743b4e51ad0d60636bc633a1f80d8385dd831 100644 GIT binary patch literal 31373 zcmc(|33yZ2)hK#2S+gZewg$_V<=L{qBgWXwGuRk{4@yW(;s{#?gDsOIJAn+X2}uwk z33ikGh-MN^+K3Lp+@y&UGSL6;d!hHc_q#fkQ>iLx>0e~y_r34d1k&OE-*@j@Yabm+ zHZt_xci(*nv(G+zuf6u#^V(~zy>(NqR#5Q#Qu}q^xdRmSC;Z@_%(&;~bpb`aO3@TU z(E?i7CSU~c7PSdmMU2RXVd6G%tAvpdzoadrRmw<-Kch|7Dre-xFKtt_Dj8*~icz(y z88r!$wPm(y7)`5|(Y9tWS*2uBOpQPr zK8%?zq7{2`Y2_Zv5;3jHr4EP>2$(!ty(fQc@CMBhAkB#P2>PSizt&itWNN4Y{08Sg^nnSP^kuM=_AYa9_5vWR1q1=-{_)Xi{|D0--?}z2GX1mH zW=2lL&IJDasrUNvA77hz{`$xZ*CtL*{nN)&XT5*^^n^ljZQ{g?@58D0em?!)*_op+ z|M}DJD{MA;$aTn8*J7J`kmx#752l0?43tabs=lR5FVO$l{|5u}^*kD5l;VCHSv>{@TpzBep8r zwTmB3`9HmW7E1mJd~(%ftEj78T-OLlrd|qMe{s~VP+UL%3?z>|`_tH`r=T9V^s#5( zpML+MZAoqEd`u0E7!=1=-&m@M*Vgx5NL0?X&t8W-V<%pm{_MT$UmTyh_|DWDXRlrS z@XwzDEicASJ$-HBB`()LjXbSTOuafj_2PR|pNw*?u-G>Jnh$DYDsYygXxFf3zmsv< zrcVEG`jam(^JhQ5{@(f6dv8vi{t(JHb?n)xk3NcxdZFe?;@Id*wO~DrRsnZnq1fdEL(b?%{stkgIFJ$sBYIbq%=&4!IcT&@khIc&(?~>F(M;IMn5F zbq@_P9thKuup!3f?&;dgxSR(EhlkDq=suSl6S}%Qef!+beve%gwe-1%7-x_3NE+-> zLn5-buYbtJba{pbyAOJz*@;Gn621SnJ}kXUNpq69bQbXVb{}#G8et^ha~npPLzZYG=r4D{ zZ%Dyk$YF$@K{-SYsY5^;o5gp;pLbXM33kgyaMn-`A#JV}m)wo6+!VTsdAfEV+dTv& zkIzwVxkC=4Ly;IA&?5$a+)=_qC*QQC<6!c;*^kM~VqYHt_EUp&0l@4VGw8s3$q3EcbABGgB z3vGb`0CyDDK?y`8CqL*qC^$P5j!@q)@rcS&I{Kg!H zldzh=5Kae90xR{!E3wz!Y_UB7oQTaSzDELn*gM){ ztF^(705&1hKMcfinE{T$l_{#3*Z-pOL_eF`KJ#}Ykj)G(2=jUs^5B>!=~XHDRy;1T zW9BnB%YG{dhuhP|-aJXZpnsehq7+KC#v_73;; z_jJu~8i}WSka0yt{VsP@w9hpZ&2qus<4Aln(lvDCkSi)C6iA0WkYA50D(QDU>gtbX zaQhXzn)D*lR~Q_940eQQR!XOWoe1WkboTf2`=O}7!}S#fY*KpqaIfHDESONhL=_xn zn0^jLRdIj6bFZu4gN!Fml!+(0p~m5HF`gQylNof^cwkS`?IL8>ILW?fF;uX7s0LdY zMtqI)(4iWadtaa1RpaSqT)6eBK6E6iW`^BBB?iFOhFJ^QAV=h>f!3U%MyOlal(sOW zDH_@Gdu!R4BG5jv^@><=yzOXPP*?Kd;j<52+8r#}ez_;8r6ssC5hD9C&iUl zbk%{!L%NocwkfIRN?y~1GL*L=Y}nvcUXf*=?*DgWU3eG@ys*z@t8iS3swg7mI1-ObY7!Jd7=j{UQgpif|pSPR0|YSvmE%&!R? zo)CN?{a8AFWL6}|Sts}lC6KQd+z?TjW}rc)y^>cuZVBbB3}c2Vg|C|w*IzN#j4Lmj zLdK48R>w%|lro1WCVT6oto4eeI$|u0n2PZ)C;x_AlBf79B{3+xioax1DnkOeX;QZA zs--Lfe+7jRa}hvl6|0of@y+Q4Llj7YGb)K)o z=M1TBUO}X2@x=0A$AfInLtX_Ce)>CO-|;)cWzB3^^F&>E`F3{s_K!nlChuS50(W5_~EBg>>S`Wm#}%M|kIMcIWQkJ@*IicLw)% zhxZ;}_Z|p34+gXQelNd2EpGwPr|@?dhO!&O2IE`1nt@4U(~K`hL3--XSW;0 zzZ7JmtIt5!ylxppe3>O^&l7*Ct3ZE?puIr+3#4VM8pUf*{I_k!`n166xJ_i49qWpoAIuL;`)hcj{Y$)hO zp|8*_ZWXyDJPO}r;{Q_=6+F@6TyO#E+zz-#FKN{z%le==DqtnV~)KSd) zl5bkZm)}O!P_PfXGqk+3luw)7#RAu_NR|CS?I-p~JTyfm%Ac901vQS$JJ$VaC`Upf zvirZ%N>a;w(08&I3OR6x(*z!^PXgHz9W=|r`k>} z2KxD&QhLCOd5Gb&<)_p_(5uowS0+{o7z%nz11auMSl+Z*~DX+=@T=G!Su={F{a>5h0{yxpY~c|3)-l-b2$ zjQ0}gck>^|-oVY$nk2l@tn&P&!4%xt7adFoSR($0$&P*c#8@!`|ZmDNH=-kdpd88=7XrJq)% zj4Csa4Mx?XIjgRjUk*{Ck0XdE)+^lkHI&=xFzZ3OwgqMCk_eXV3p|LN}cM`0&G)m)rP!f>1xbT;LxWJPp&Y0jk*gRM!r9#&{yOr zy3>r%1DoHJb}PCArDQJUYZ~-h0(bHMG~8runuELKF1TSFg5JTATBDpF(k)oq>FSbR z%;(F$`99{5ps^mk%DKO0p*L7!HyRd1HI1HZ)idj-AJN>KsTD}cQ3OR3WDR0|U{i&@yh1WYlABBMo7S8bbm<-?iRo|`%`0`@4W=)mkH ziSgpw)BdB1`fA(sD*+qG8^KJVum4CFhz7w-Bl*48>FMjXai$WvN|zokQLG<0-C9T#P*rmN6ro-HmOPtyZz5+7>(iF((+09sNGY z^Fi&vNpfS}H$b=zGJgHVbxr9Dx3e;??4h}i}xdwPaFAP0_8!zc*lv*H?yYaFDF*x^K_ z-F-}V*TADQTHv{&YBcy5d=v~*(5fYcQd{gTFje~L%o{)8+7-%*GJB-d_OH)<&o(TM ze+}2CWSP*waG8Ob(Db=aK(lfEqxS&~7t7&`X6_w6(&g&k*X0}_0(1>X#QReN)CiD_ z&m{55Z_R+S&)wyQP8pZa6ZnB19v=sv{3g+!fw|GiZ;{*Sp6P+2L{%I3sNLujMKl;OAZF(*UB_sID(*oU}Wh#mmu2bfW! zBIwj)P!2&;d!i!u;A5QnMLhsbA0GfmJYe!P#PpKUEbD{(n8C+949ZA2j48tav_76b zH|WqHd3+e4LhW$kBMW2Z3<=bY9G({>?j8g+gdQtp-jo)VGOk1Y&Ti1Vz&z)KIsmh; zezy}UY>*l1deqrJ?D9kCp^>xc51{pEZ#6=^72GsD4`u2`9i%6gJO!PBHnmiJ-ty0Hk>Ca=#C0N)stH1 zYS-cJ{$a2P;~=1F(2opbdJvKu_H_)b}XN$neoWICyaQ5X^pu7!-pf#|77!BzwCc zrv=AGR0=3y6c2jrkT{`!x|@etgIU$^&g0Q(ltHYiI&5lWO^qQ_)2Jk(Ee>ldS#4#YaZ+3NJF|6k zZNy%AKL1?)=#Ef!8R&&_3qS*4-zs4B#i$A}|HVM53KFW3rIWIzh$hP`Pbf1gf7%eN zSr@8WAFSMPsXeIem=x2XLDCn7b+xRnHl(W`X}gkbe0k$YdqimnEA!y*irz5#MBq`j zcxkw}g)MFg7q4fF*I(Lj*%2&WA1tO>+^9L2-`YkN{6>nvATf%7~1df)wLtRYnesHKdHXJwSy5>{2Rs>(o1NVRliV??St{>1So5|!34DO*A+?W!@) zcRy<^50tXTnvsoOJ1fqHlKl#cV zaz&n568$Y5ul>@^Y4xrDsXN&5aA>PNWH!0qKRhko)THueA9BDI!5A3vO^q%D)OtygmMeeS^h zthFg@UBy~gg{*5rxoe--K|#0*`lTR)pV7Q zk_gqdtBEN1Y62qESc#BMDpK3vv$!;#626--*G$lx6%+;w>sf2Vxa*^V3j>$d{&M)T z<6oW(E#1XhcLnczfVDml&fm-C?+teMgz{Zpp?4K4%e`taVQJ(i9t^7sSarcWBLDXB zX0~Ky$hIn^UJY?UZ6T{J^6zBTWk@J=J&{rI*9=N&I8hZel#e%htAdK=St%vYzNMfn zR^P#ZowY6rTbHxet-T=;*)zRcLC0txbB9!=r03sKQF0BZzOMhEVf?$HhRwnHEx|2a!R&`8 zWzH#OE@}IlpbR%gvh*XZSM)_*DTaw7n%uC)27eJmx;5VQ5xwDb^XcY@ zG57T0vBQ4B*c1Mxtg&)beAQ4IHrQE%Jy1TrBHXZ^ZCHP4^W~0E!#yFxPOmbewfepj z=wl0(O>AZh*0b6TUP)wQ>zAEhcD^q0x!yW(=0G6p%s^lVYh5vES{c0eKCe2`yy^?f z7nWB_eUi5nXB7UrGirYyYpR{p)m<(GQfro+f8zWT6U-NfKR$d}@?R9cQh);cSL*PV z``In`2OoGSw52P&`eAnU!!I~S8&9{4wfLN4EB)E5zHCxm9_&5f75>jav3|>!%fDP6 zq#a?plchU@dz|4tz3iUe;J*Frp1$y&0d~(o(EVg+&r@OgJ1qU3SB8C#w;nk2KtOQj z!N4juzh%<6e3qg%3bzWqs#&o}ZjKlVP7jO?_%{bSLWWxJW|$!4`moH*%FGd?HIi+D zE)vPFM87T@|MDs#1!ZqNaps9&)e7hn1#2RC#c#EpX$xdd2ts)+H*%B~(4{No${Thn zyZF{RL42kdt$C-|f8gyJ7!%_qL2c8dcv(c519l64yEQDLwC0qSBq(eqy`t z!9d;lmFHFlYJ$yem)Bly2wHZ8RriEc_xw8;0|4B=-|FBKK@m*PZ^mHr|K&lp_37mg zt`dB{wrNkb@ShuZ3*aN#q>`rgy?S(pno}rbBqG2LmAW(b)#&9e{eg8>gAigAW(<%LwI%wRml#&Cdv)M<@O?=+>n;?glY52q*5PPVH>I%)8v9;T)w1k(*igXhNHLP)TGCmLb(}? z0VL_v(I7rnCvwgOMaiAo8un@_wVg0VrZZchY!buP~PjX(t3PjVh1P?cs~g9m4M&qFeNY*Ituxs zIjaD=Fs)KN2ahK>!D7{6qYaCp7TvZTISarV2`o}!qfH!aXF{3oFgt{ayg>m#n@KHl zm~-Qpl@M+r;T)DcF5DrCw;%_dpMVz8R?@O_p)Kip8(okTjM6qVEJu+bP+AzzBmFoj zO7xTB1$upP0s;)Up^ia7nG+j0?KVT~3#~?eM=6{#T_?ca2a73b6T)+YVH> z%nyYgQVHQJ(u7wnKxsva@Ox4EG2>qBSWMeV&%nOAC~XT@@->|3*EtAFS3*NmZhI#P zx4Fk*r>k&e(2Ei+Rk`C4$YJqaa!9B(=H)`#sq9>vr~!k+0?LG3hd6O8qq=i_B31x#2L7muyT&1(GdI541`kQ@Az?Puxs8YIK-uNIhab~$NKUd@ z+z$N*jn6tec(9K9cwh2OZ-lkXj^hLrEt|lPT?hS}!A}%f;NODlT|9iNLk}UX4XMU( zI->AFkt%sW=>kikyFMjn2OpPw)9W2Zz_h_pf17fr++Dx69!l^mj8x)JDtGMLhA#jP zB9{fr`BZJjosaOQp7{O$;`DCpqyGSBlv7Xd!eQLlJI_pyK8MG3iOz(kG{)Tb zpZ^m+*H+H}@5DpTtY%P@Kg}hXS{)Umlzg9ka@w&MTfF?SPBt;4y zRbwrqMEMZY*X<(p5GQDM^}a2Cd_4QT&5$2xGr7$WP{m1vIop0Uf3ku5)C}>5in#`e zW^%GvP=>_;P;O0HgbQGBt&J+V)+ZE1RYOh&<>B0y93;wzK#1${pg`Q|83gIQoayr% z>;hr8i)q5-D743VJOb*uImfJ-B@j*2VyQ!cS3mKJH|zx5;Ihrt4n7x~IM!n0&X&ZF zfN)1k_z!ZjWaY3D5a8*X`1ug-y8={l++lm7-vaH8{TyXq#OwuNB7TPs4QdtAsiB3c z6E(;90V0||Cz3z5!k-mq&LWl{pz}65?_iSbIZ3!)>tUoAWu6now;AeBnIB?$H#+;# z8A1mcSx%86cY%U|98*u#sSrI#RJ?u1_Ki^q(L_Y`@exN_t;^Ys>Y1o29!OdRx+0AK zBVBl)rLiZP)rF1BcP2a!6V>qGgIsrIK1Vt}0VmOoxlW-;bc%Q{kuae6<8a0g)$@Ht z583cRiCzQDAsoC(C1Ae`G%Pvs{Q3Iv^bH*9cQG&}Q=GC$L(b~pv3pQDz)>BhAEU|; zo219JcX%HIivVSBND(09A_PhiQuHo<{7ln=q^-CHc@aROGCY4nv@YMn6fdI#I~0l> zm|@N!h&nzw5pqDU)z@WuS>_xD>*q|eTm?jRsjV0fQ*uL# z8^+4sKA4OXF)--kJu^8ykP}WxMdKGpH;z^^;<=W+grC1a=bzE}2%M-8#`3@5*Dvu) z33GiHKhcZe3={JL1beW5-c1FQCZ5yE)FO6t3<5w!!HO|LeQN@p%-7=PzY5{F!%aN% z@U4oHEd5&C0>aZdN)3=>j%kvAbFO9;gtKbcteSD@gg%tD(z^}{d+PA1!>>2^nAe*89j~qU zt=4oSgVL4w*M+jmz3afAS2wN;S(bRWMRLphPqDd;-Zqf(`VMkOdqSG(h&JEX8`72r zvOxa?Z$J@InSE8^+$uJ=Dx_L8E?`x4@WxX#OUL(yikgF}0#Jk%HI8>oh_=YTi`CjeG@ey?RcrA9tPWEc3Cu-WBfZIK*XIA<}Nvv^z>%BknWN82FRyv2cg z$2&uowNOBf#Wx(%ltdt-Ay!*Fri^6e`wdVLe8{L0dW*KuUmwzx@?i*7r zF_i_1LZ(GhGpdXfRE7(h*n*}} zRV1%GoY%zWHI2%z=9YzXE7{!2Kr<94w|P`b>a~o`DH|2Si1FoyKvO9_uRNz5-yW`5 z&sMCD6xjpSY*F)Q+Z9knSjLA~`|^pMq2kqJ+afteUrQ*bG*EuN>Ri?MV7PJYtm3PE5`un;1$6NIL0+uv~pI7Au7sRJXzc@ zzG87@(tU zrT&M;cTN^G&FV2A8zBbAADOf@&*nhD4Fl3+M4ySuub4HXFPE}b&RWoyM_Ec{^U-Ie z%(mGA^w}`15Pd~Zg4ts9l~B2rv!&phEu)I7!$l3hE^3%9hi~ut*-A>C7giOqsv>{G zq^kUyMmM@Tq$z&qaJYOmTMpjUzt!e^10wu2f*U);lyT9mz2dl{U{)wl>LNOr-&kE$ zFl$j@%lLzrTG+J(k6CkTd;8(M79e+d7eAE*|!8v zE6NLRiD1^Ag%f?2pg8w$xAKXa0cJJSGHg4s&9^dO49LgL6Lk~CLG!9f)#~4?vj2S+ zG^jcM<|&2M;FtOJ^lIV%mWA&BE@`LW6RV$g4TXt1RCaWaiXZ{3`-9m8qM*p@T|nKY1v4(?oKLm&kxNW_8VTACElyG%CTnXWs38|@&)+FfS^CB7ua!^4^a)85cv^E73kvhADkb{EO zrG&>Z?NCBEmopFz7dYtSIY<%`tR4uLbl@g&-a}^-S=BCx7pHBXoRpeXE&yYolX?S{ zLrI$mjVi#Yiu)mFyjUV$BYy`NPL9mM zEL9$z& >IF(=O(|8TLroWunnH>*p%rw{cjlJrr34HYZi|=pX$UV|AZ<|`<`C2l zU%~?^B?LZlfp#k<7(p_$0QDtFc@}8T(zos05^88YDHT`3j5H<8cWCI+6qwXn;M+}^ zQW8nhpK@uG5RSRTXOa@iUOub?b~N=Sw%E^p#N9q%tALBxYHQ&Fw&{x>z-<|L`Pt$* zTUE|Z_4)^and7Hy6-#R2j*P^O2sMe@5NcxlO$f2kXW+Iwu+NQsej4_ufUOPgON)K> zgQ=0Du}?pNJn#y+1QxgsEsg~*R{Q=l1o*u-uYLA%>_z|7`B(llaun{&i+y$hkbocT zf$<8E&+t+=Kt1)!`Kj-X&G^tX7Xo=I34XXdZswKuxKeWo&}jJQC$FD>74B+_J@<1? zh&1!x;UW72vT0%d1B_ngC*VZI`}_9ohg%HLSgaeQ1aN7=AXvOH=Mmzk;LJcX@^X`3 zt)9W5&%B4>Fr`uZ8p!jf)iXFH#|0*dy*6xAIs3>TW8}x^1i+bw|M2{6H77q&@bVM5 z_yDC!QGHxK<=M~lxes#om{D>2+WOiV1IB~5u672-O0Irn;)|3Z9VS!mZX|B6w#9ueUTijrXKgnOuD9Yy7MmmI+y74IGFITBT% z8>cCgR9M_v4vZ=Pipb@-<0AH|%-``lgDVEeTzL9CS_q%NyJFTrAQR-B2yU&8K<^%* zX0?<$6Sg!;^{J*4O{4WEmXB%cLbQv%*7*{BRXqXSH$Xy{3}Dcx{)^E zK(lRN*#`D#U=y=8q+bdfF0tIV0W5!5v2A1{0A%Hi-aDr94f!{oc>;C=BU>XvVE7#DTN)?Kta+U`Gom#6)`ye@DM>B9?vT0=10X3PpA+ES z_?HYyrXPJGY^-2`-E0jRmtHkmPd@?nF001tLdHg~Dw1tE-8R-nwg%btV7Q|+`U-(y zzdW$^ZFNdkX5U&M+iwqazg=@%a6G`f@o!V|Y@a1;u4K)Xfde7)va30Hr#r_w{doZf zwmVQES*FvLF-tJN9x6I(sW$^_$N;7CZwk@FvCxI}cHu$nmKInTiAbRb|L}((L zt1|Vd?u61Sxu(qY4v%)9Tn!xHP3Vf$oa+aXks+K~F8Cf&J*4KXHwM&jlu7_xTYNUg zl>9@BFxh~L7Q;1O!a3J?6+sH%`Q|@pAWN>V%Z4>cm~xGm2re;1VH|lJiNhnbgy=Aa zf&Y>xVpR_H5+R)d_m|2XB7NeRI?sdg{7et5?7LJPUk7i(KY2h$PxuHui6gvKA`Ef> zIKtBr82H{H1l5q4Bm9S)`tbZ46*tpd2Z!nc$A&8s`t8yi^D9 z-SZzhap9m_>=4I~iKgp!LRu@P(p?zAE7@TWaHsTIT6mZA8IV2;dw1G?&*cyGF#k#9 zFTG=WF8@2Fm+f|uUIcrI-N%U!`9%TPcHn+Rup=iP&xK~jLltpYu19ekzTl4iFXi~Yl-UxZbkaOm%Mf_F*dg@)ax9HV`vFjSm9ys!twcw zp+3G(c0meq?Ad^OIJ}%MzE|Mt!xXO%BZQk%YA-h)j_}ESgMij@;hl*VftofX`A-kb z0&u~mu$Qu1hTn(ca#z>gkpX?w{8ykJuD=xxI2)tl0cTw;y7k0eT04U?_HD}o zR6lid8#t?k+zl}fytl=S-Efzv19+MF&n}^ZoF3*Ujz_6iB{r%ZO!!d!ypP-jcus&f z2E#ECw-d+_R@X8o0SzMrbj)+$^B^fU+sFvvoucv;sHix6h*=dai0dJU#Jze29PNPX zfmc;00PrqrPa&lK8{SbBEZO|YV-tn`j(6`ndtYGJc;UJG!ljL1qx{KZeCC?)AC9akVTWg+hA4(` z$Nvp{b~)3H0Z*gThfX~>z__}PFnY)@Dn`8|Hq2p00N?gZj)$#7P^=p`Y~cjON!|}2#QiVGyj1jM4{F?5`V^-66Rf9$c*qGL~ivG-}? zn9-+u+2YN(YO?wsJ(C$WRk0vLT_5NOnrg$QC9G*l$g~tpu+0Tf3TxQBh&3+?bOh+2 zxh`yOWX+8sbCWmosxjZ^8ha{iw6jKgpg2$;G**X=^{lZzWLyFqG9Vv5qYj%YSyN@8 zKF|;})r3tAtf?VnYV@kFD$OCKJunceZcV8gOGsUn@|hb_+Y{vg249vrsym%KmK%~< zzz%=i+tNVY+p37(=*t*e0jE)P*{93L%6&y+RlZ(US4s@pN!1+&b9Po^3+T=p&l$&! zVS6)cZ=Tq9S#a@S#AHMI_OZss2@z{t?%fj604sm;$%r<`ryEm_Ng`Rm!@r!j!1w4J zj`r{0I0TetyTF4x*Vpx}GV%Kv;E-cXb3~FNh{yjoJHUArmQ3TJ=8?1Q_y62^C54;*h?1o<$)c~R#?oS$ey zkvsMvaWLe#aTY>2boS#oD+A96MXr>CyX#J^Jh9T(_LZ_cqR&3vG}iR|_8Ss7oCK|< z%3>5Sh5Y|qZa9I9SLzcX%08eRo>gn&WsfRQr_9adj2&p5TkI&nGUyz_VwZzwfnu*1 zTk&f+e8QJoOUljs5faQ<#&9tqjlK!8f<~v(En8@<&ae3?cFRD44dl~g4+N>_KeXtM zsf3Ucq}sGN!G(xu3E?(GJBlGZDGLS(bUfS$;j$EXc|2SR;oR;8`8$w4bKxLAQ=~w1 zI~BwZQsAC%n#m(z4K4k$B)_8-m?{sk8>w% zp_;h^zswHw{T!SbuO1xW_^7@h@Tf$-#4X0SHgoGR5eJdA85dsYT@4r|fQAACrayuo zmo@JEGC596#6fZ9f*)kR!zAb&#@_ZX;2ELfD&rtX1=fO|HmqqFFGSV0>2&j0v+tq6 z&XB$)tY0!-H?r+2S@EQr(bACA7~!_tCnOPh*64jdonK`@0DDY>PPp&5}1-8 z$0rQQbLRvILU2$enMIB-C1CCrpBo9tJCo4|FrMDDfqR zFK+jZaT)uhcG-63eMm(voCQJX^f52@;}@i{$X&+qZ)_g&ef{01r(S=Jc=2A~qhByz zLX=&=wF2O!;`7`jco}qF#wI8R&jW|V<3K}UHKL+voLw=9syb3cy;u1mUZ}F=`H4ia-lAV&AXa&Fq8m{%h zzA*2hVXu|QL$v@5?4J@aYPjk~GY<)Tf(0;K;|G`Xqh18xn$#=Bc6ebArX`fDT96Vr zcaz8^a{)@=s?7up{;WVcO5kEWuq@36%|s5+VVFb52C{d=a~uoPVM@tE`~QLtvqMX2 zA~zi+DZ9@^f8;K{cYY6QNYnwd;rxfT@S7|=uLS3g^2lzST+D?&w{A`-a2zC5pORO? zmN>p6-UAYWEIuEHp2r`LZ3jwp+~sK@Kqat1ixvP3_9?@h3$>`U{n?h%dUy#{A;E225O)#46a(&(RNOoAAhh%@RF$Pm z3EW1yQC6PPx8Y*E#7IUqGkq_vaG0PM+f%7>WY9voQVi^;y^36&GX2V1aKQ;T4}+!g z^eY#Lq49Q3?LibE1<*!}4IQX-&e)eT^+gp2^DsEWH~~++@;NBXI89l7Er=cxN-9|1 zxm(*G7gg7B7qyqd(hks|?&2oIrH!u%Uz#rbIZ({ljFY265?%qhSCB?MnT z_8F?jK0SB+`1^pnwsaUP4l$J=rvHvebl`xv1odGA)zqsWKsm`RA*FDSe(c5fW8=@o zPWeE|#v$d<5pE$WMf1w;{rjRKP~(S3MTJ2XR7 zg?I-+g3g=BK)eDD$RiXekI2;Cu0Xq8#dbxP zAJ&zzy0U=sgU)c{RiyRyTiK=u)7Zg`yUHt9Uf_o=<-fik5&6EU?Yc$IHIol zrzaw6Q&??f)mAv$tW=NYqW;WM^w#n-%l+ROeMoS%wTDvQtjC$^U-1Fn2=Gz@4;;Y)_*1TIdg96)adj(pOC{I!~}z^^}|b{eG*nM_^U2I z`_a^~S7S$ybAhm&gZ0VO`)D(Rt8$7yfvoBzeZqDocIrF;O<#PMs2fgBz4QZqReu3c zOg(dI>SsTgdhxC4PktQx{3Kb+hif7KgvAZFhI3eA7eAbN^$W;9p7QjEGp9d5mge=J z5&^(4@UZ;P+Z=m#`r?mcM^D9``v?-i4=qk#d~0gtU9Q~MKR*V10&@Ythf#tQ|DhL> z2JW!C?)_;ZlWP}W27Y4drFUcA56MsPfW8HO(1v4j01vF~6^+0F4MYEX1#E{@^?$B)@orTv5DY;YU}-kC9)E;mURWz0YGm{EVv?q=V$m(Gh2Q zUb9O%VaBtNDT5t>I~VY4@WD@WBukH7q(=4I`~rrCFm7grq3a zIHfwF3g)zbCEFg6>qj3C$qR1_JRFiIAvaITwp^8}!%{OVHT%|uq}Bxd+OK5mB63Yw zZeisX-`K?x96T3MwPW?E%2NTtzYHE5|HUlMNEz&30M)^7}Myf*wPv_w@s?HO)0ezO?^bEJ=JoeWz-c? z<^{!hoYX`IH3*_4s1ge92Ha$?j7qzJL+S47ic0ZkZQzQ!sAmW2nT?z#QaBeP?izRnFGrI3AC)UvOsmanP0KTro*>fj%! z2SU^Xf1oz7)P}2aZCIZBYk4l5(>eH4@5J2sYI>lurr;mWVccbk~?- zR)il{vx;UV=z-)q1BX{izGajte^ySs3dBs(!TV`EP;|)3g6?6 zx5#_8SJ))5_$0R|c+Ea8kO;E;4e*mAf~@gf6P0Y^M&iF*d<%nSwGx5WUyMOoP*3Hu zOEwVy)sQ7QSYe$^<4~<17U)KYa^dv$=TH8T=le5ysIh2qBD- z0yr&#@2ZxGU2N4_@^MvXm_;BcEtI49=>6Zq9U{irLxOw(n2F`jir{@!VFq(Fg>6;> V9{{M*&Pve-l`8*M4nETO|2OBCujc>& literal 14431 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 CAm7aZ diff --git a/backend/app/engine/recommender.py b/backend/app/engine/recommender.py index e221cbe7..4d4dfebf 100644 --- a/backend/app/engine/recommender.py +++ b/backend/app/engine/recommender.py @@ -245,9 +245,9 @@ async def _load_today_from_db() -> dict: from sqlalchemy import text import json - # 加载市场温度 + # 加载市场温度(按 trade_date 取最新交易日) result = await db.execute( - text("SELECT * FROM market_temperature ORDER BY created_at DESC LIMIT 1") + text("SELECT * FROM market_temperature ORDER BY trade_date DESC LIMIT 1") ) mt_row = result.fetchone() market_temp = None @@ -264,12 +264,14 @@ async def _load_today_from_db() -> dict: temperature=m["temperature"], ) - # 加载推荐(按 ts_code 去重,取最新一条) + # 加载推荐(取最近一个有数据的日期,按 ts_code 去重) result = await db.execute( - text("SELECT * FROM recommendations WHERE date(created_at) = :today " - "AND id IN (SELECT MAX(id) FROM recommendations WHERE date(created_at) = :today GROUP BY ts_code) " - "ORDER BY score DESC"), - {"today": today} + text("SELECT * FROM recommendations " + "WHERE date(created_at) = (SELECT date(created_at) FROM recommendations ORDER BY created_at DESC LIMIT 1) " + "AND id IN (SELECT MAX(id) FROM recommendations " + " WHERE date(created_at) = (SELECT date(created_at) FROM recommendations ORDER BY created_at DESC LIMIT 1) " + " GROUP BY ts_code) " + "ORDER BY score DESC") ) rows = result.fetchall() recommendations = [] diff --git a/backend/app/engine/screener.py b/backend/app/engine/screener.py index 7243106a..d7f80ecd 100644 --- a/backend/app/engine/screener.py +++ b/backend/app/engine/screener.py @@ -1,11 +1,16 @@ -"""趋势突破统一筛选器 +"""趋势突破统一筛选器(自上而下方案) -三阶段管道:全市场批量预筛 → 资金流过滤 → 逐股深度分析 -评分公式:趋势&时机30% + 资金流25% + 供需20% + 板块共振15% + 市场温度10% +三阶段管道: + Step 1: 板块定位 — 找到有资金流入的热门板块 (3-5个) + Step 2: 板块内选股 — 在热门板块成分股中筛出有资金流入的候选 (30-50只) + Step 3: 深度分析 — 供需 + 价格行为 + 趋势 (10-15只推荐) -自动检测是否在交易时段: - - 盘中模式:用前一日 Tushare 数据 + 腾讯实时行情混合筛选 - - 盘后模式:用当日 Tushare 完整数据筛选 +评分公式:供需关系 40% + 价格行为 35% + 趋势 25% +板块和资金流作为前置过滤条件,不参与评分。 + +数据源: + - 盘中模式:Tushare 日线 + 腾讯实时行情混合 + - 盘后模式:Tushare 当日完整数据 """ import logging @@ -47,23 +52,40 @@ async def run_screening(trade_date: str = None) -> dict: market_temp_score = market_temp.temperature - # ── 板块热度(用于板块共振评分) ── - logger.info("=== 板块热度扫描 ===") + # ── Step 1: 板块定位 ── + logger.info("=== Step 1: 板块定位 ===") all_sectors = scan_hot_sectors(trade_date) - hot_sectors = all_sectors[:settings.top_sector_count] + + # 前置过滤:只保留有资金流入 + 非末期的板块 + hot_sectors = [ + s for s in all_sectors + if s.capital_inflow > 0 and s.stage not in ("end",) + ][:settings.top_sector_count] + + if not hot_sectors: + logger.info("无合格热门板块(需要资金流入+非末期),回退到全部板块") + hot_sectors = all_sectors[:settings.top_sector_count] + + for s in hot_sectors: + logger.info(f" 目标板块: {s.sector_name} 涨幅{s.pct_change}% 资金{s.capital_inflow:.0f}万 " + f"涨停{s.limit_up_count} 阶段={s.stage}") # 盘中用实时行情更新板块涨幅和涨停数 if intraday: hot_sectors = await intraday_sector_scan(hot_sectors) - # ── 趋势突破三阶段管道 ── - logger.info("=== 趋势突破扫描 ===") - candidates = await scan_trend_breakout( - trade_date=trade_date, - market_temp=market_temp, - hot_sectors=hot_sectors, - intraday=intraday, - ) + # ── Step 2: 板块内选股 ── + logger.info("=== Step 2: 板块内选股 ===") + candidates = await _select_from_hot_sectors(hot_sectors, trade_date, intraday) + + if not candidates: + logger.info("=== Step 2 无候选,回退到全市场扫描 ===") + candidates = await scan_trend_breakout( + trade_date=trade_date, + market_temp=market_temp, + hot_sectors=hot_sectors, + intraday=intraday, + ) if not candidates: logger.info("=== 筛选完成: 0 只股票 ===") @@ -74,8 +96,9 @@ async def run_screening(trade_date: str = None) -> dict: "scan_mode": scan_mode, } - # ── 构建推荐列表 ── - recommendations = _build_trend_recommendations( + # ── Step 3: 供需 + 价格行为 + 趋势评分 ── + logger.info("=== Step 3: 深度分析 ===") + recommendations = _build_recommendations( candidates, market_temp, hot_sectors, market_temp_score, intraday, ) @@ -96,147 +119,498 @@ async def run_screening(trade_date: str = None) -> dict: } -def _build_trend_recommendations( +async def _select_from_hot_sectors( + hot_sectors: list[SectorInfo], + trade_date: str, + intraday: bool, +) -> list[dict]: + """Step 2: 从热门板块成分股中选出有资金流入的候选 + + 流程: + 1. 收集所有热门板块的成分股代码 + 2. 用 get_daily_all + get_daily_basic 过滤市值/换手率 + 3. 用 get_moneyflow_batch 过滤主力净流入 > 0 + 4. 对候选做入场信号初筛(只需满足任一信号类型) + """ + from app.data.tushare_client import tushare_client + from datetime import datetime, timedelta + import pandas as pd + + if not trade_date: + trade_date = tushare_client.get_latest_trade_date() + + # 收集热门板块成分股代码 + sector_member_codes: set[str] = set() + sector_code_map: dict[str, str] = {} # ts_code -> sector_name + for s in hot_sectors: + try: + members_df = tushare_client.get_ths_members(s.sector_code) + if not members_df.empty and "con_code" in members_df.columns: + codes = members_df["con_code"].tolist() + sector_member_codes.update(codes) + for c in codes: + sector_code_map[c] = s.sector_name + except Exception as e: + logger.warning(f"获取板块 {s.sector_name} 成分股失败: {e}") + + if not sector_member_codes: + logger.info("Step 2: 无板块成分股数据") + return [] + + logger.info(f"Step 2: 热门板块共 {len(sector_member_codes)} 只成分股") + + # 过滤市值/换手率/ST/次新 + stock_basic = tushare_client.get_stock_basic() + exclude_codes = set() + if not stock_basic.empty: + st_codes = set(stock_basic[stock_basic["name"].str.contains("ST", na=False)]["ts_code"]) + exclude_codes.update(st_codes) + cutoff = (datetime.now() - timedelta(days=settings.min_list_days)).strftime("%Y%m%d") + new_codes = set(stock_basic[stock_basic["list_date"] > cutoff]["ts_code"]) + exclude_codes.update(new_codes) + + # 行业映射 + industry_map = {} + if not stock_basic.empty: + for _, row in stock_basic.iterrows(): + industry_map[row["ts_code"]] = row.get("industry", "") + + # 用 daily_basic 过滤 + basic = tushare_client.get_daily_basic(trade_date) + if basic.empty: + logger.info("Step 2: daily_basic 无数据") + return [] + + basic["circ_mv"] = basic["circ_mv"] / 10000 # 万元 → 亿元 + + filtered_basic = basic[ + (basic["ts_code"].isin(sector_member_codes)) & + (~basic["ts_code"].isin(exclude_codes)) & + (basic["circ_mv"] >= settings.min_circ_mv) & + (basic["circ_mv"] <= settings.max_circ_mv) & + (basic["turnover_rate"] >= settings.min_turnover_rate) & + (basic["turnover_rate"] <= settings.max_turnover_rate) + ].copy() + + logger.info(f"Step 2 基本面过滤: {len(sector_member_codes)} 只 → {len(filtered_basic)} 只") + + if filtered_basic.empty: + return [] + + # 资金流过滤:主力净流入 > 0 + mf = tushare_client.get_moneyflow_batch(trade_date) + if mf.empty: + logger.info("Step 2: 资金流数据为空,跳过资金过滤") + candidate_codes = set(filtered_basic["ts_code"].tolist()) + else: + mf["main_net_inflow"] = ( + (mf["buy_elg_amount"] - mf["sell_elg_amount"]) + + (mf["buy_lg_amount"] - mf["sell_lg_amount"]) + ) + total = ( + mf["buy_elg_amount"] + mf["sell_elg_amount"] + + mf["buy_lg_amount"] + mf["sell_lg_amount"] + + mf["buy_md_amount"] + mf["sell_md_amount"] + + mf["buy_sm_amount"] + mf["sell_sm_amount"] + ) + mf["inflow_ratio"] = (mf["main_net_inflow"] / total.replace(0, float("nan")) * 100).fillna(0) + + mf_positive = mf[ + (mf["ts_code"].isin(set(filtered_basic["ts_code"]))) & + (mf["main_net_inflow"] > 0) + ].sort_values("main_net_inflow", ascending=False) + + candidate_codes = set(mf_positive["ts_code"].tolist()) + + # 构建资金流查找表 + mf_lookup = {} + for _, row in mf_positive.iterrows(): + mf_lookup[row["ts_code"]] = { + "main_net_inflow": float(row["main_net_inflow"]), + "inflow_ratio": float(row.get("inflow_ratio", 0)), + } + + logger.info(f"Step 2 资金流过滤: → {len(candidate_codes)} 只主力净流入 > 0") + + if not candidate_codes: + return [] + + # 构建候选列表 + import numpy as np + candidates = [] + for ts_code in candidate_codes: + name = "" + if not stock_basic.empty: + row = stock_basic[stock_basic["ts_code"] == ts_code] + if not row.empty: + name = row.iloc[0]["name"] + + sector_name = sector_code_map.get(ts_code, industry_map.get(ts_code, "")) + b_row = filtered_basic[filtered_basic["ts_code"] == ts_code] + turnover_rate = float(b_row.iloc[0]["turnover_rate"]) if not b_row.empty else 0 + circ_mv = float(b_row.iloc[0]["circ_mv"]) if not b_row.empty else 0 + pe = float(b_row.iloc[0]["pe"]) if not b_row.empty and pd.notna(b_row.iloc[0].get("pe")) else None + pb = float(b_row.iloc[0]["pb"]) if not b_row.empty and pd.notna(b_row.iloc[0].get("pb")) else None + volume_ratio = float(b_row.iloc[0]["volume_ratio"]) if not b_row.empty and pd.notna(b_row.iloc[0].get("volume_ratio")) else None + + try: + mf_info = mf_lookup.get(ts_code, {}) + except NameError: + mf_info = {} + + candidates.append({ + "ts_code": ts_code, + "name": name, + "sector": sector_name, + "turnover_rate": turnover_rate, + "circ_mv": circ_mv, + "pe": pe, + "pb": pb, + "volume_ratio": volume_ratio, + "main_net_inflow": mf_info.get("main_net_inflow", 0), + "inflow_ratio": mf_info.get("inflow_ratio", 0), + }) + + logger.info(f"Step 2 候选: {len(candidates)} 只") + return candidates + + +def _build_recommendations( candidates: list[dict], market_temp: MarketTemperature, hot_sectors: list[SectorInfo], market_temp_score: float = 0, intraday: bool = False, ) -> list[Recommendation]: - """从趋势突破扫描结果构建推荐列表 + """Step 3: 对候选做供需 + 价格行为 + 趋势深度分析 - 评分公式:趋势&时机30% + 资金流25% + 供需20% + 板块共振15% + 市场温度10% + 评分公式:供需关系 40% + 价格行为 35% + 趋势 25% + 板块和资金流已在前置过滤中处理。 """ + from app.data.tushare_client import tushare_client + from app.analysis.technical import add_all_indicators + from app.analysis.breakout_signals import ( + classify_entry_signal, + score_supply_demand, + analyze_volume_pattern, + EntrySignal, + ) + from app.analysis.signals import generate_signals + from app.analysis.capital_flow import _score_valuation + + # 名称和行业映射 + stock_basic = tushare_client.get_stock_basic() + name_map = {} + industry_map = {} + if not stock_basic.empty: + for _, row in stock_basic.iterrows(): + name_map[row["ts_code"]] = row["name"] + industry_map[row["ts_code"]] = row.get("industry", "") + recommendations = [] + total = len(candidates) + signal_counts = {"breakout": 0, "pullback": 0, "launch": 0, "none": 0} - for stock in candidates: - ts_code = stock["ts_code"] - name = stock["name"] - sector = stock["sector"] - entry_signal_type = stock.get("entry_signal_type", "none") - entry_signal_score = stock.get("entry_signal_score", 0) - tech_signal = stock.get("tech_signal") + for idx, stock in enumerate(candidates): + ts_code = stock.get("ts_code", "") + if not ts_code: + continue - # 各维度得分 - trend_timing_score = stock.get("trend_timing_score", 50) - supply_demand_score = stock.get("supply_demand_score", 50) - capital_score = stock.get("capital_score", 50) - position_score = stock.get("position_score", 50) - valuation_score = stock.get("valuation_score", 50) + name = stock.get("name") or name_map.get(ts_code, ts_code) + sector = stock.get("sector") or industry_map.get(ts_code, "") - # 板块共振评分 - sector_score = _score_sector_resonance(sector, hot_sectors) - sector_stage = _get_sector_stage(sector, hot_sectors) + try: + # 获取 120 日 K 线 + df = tushare_client.get_stock_daily(ts_code, 120) + if df.empty or len(df) < 30: + continue - # 综合评分(新权重) - final_score = ( - trend_timing_score * 0.30 + - capital_score * 0.25 + - supply_demand_score * 0.20 + - sector_score * 0.15 + - market_temp_score * 0.10 - ) + # 添加技术指标 + df = add_all_indicators(df) - # 风险乘数 - if tech_signal: - if tech_signal.rally_pct_5d > 20: - final_score *= 0.65 - elif tech_signal.rally_pct_5d > 15: - final_score *= 0.80 + # ── 入场信号分类 ── + entry_signal = classify_entry_signal(df) + signal_type = entry_signal["signal_type"] + if signal_type == EntrySignal.NONE: + signal_counts["none"] += 1 + continue + signal_counts[signal_type.value] += 1 - if sector_stage == "end": - final_score *= 0.70 - elif sector_stage == "late": - final_score *= 0.88 + # ── 三维度评分 ── - if market_temp_score < 30: - final_score *= 0.75 + # 1. 供需关系评分 (40%) + supply_demand_score = score_supply_demand(df) - # 入场信号高置信度奖励 - if entry_signal_score >= 80: - final_score *= 1.10 + # 2. 价格行为评分 (35%) + price_action_score = _score_price_action(df, entry_signal) - # 确定信号和等级 - level = _score_to_level(final_score) - signal = "HOLD" - if entry_signal_type != "none" and entry_signal_score >= 50 and position_score >= 30 and final_score >= 60: - signal = "BUY" + # 3. 趋势评分 (25%) + trend_score = _score_trend(df) - # 价格参考 - entry_price = None - target_price = None - stop_loss = None - if tech_signal: - entry_price = tech_signal.support_price - target_price = tech_signal.resist_price - stop_loss = tech_signal.stop_loss_price + # 综合评分 + final_score = ( + supply_demand_score * 0.40 + + price_action_score * 0.35 + + trend_score * 0.25 + ) - # 根据入场信号类型调整参考价 - details = stock.get("entry_signal_details", {}) - if entry_signal_type == "breakout" and details.get("resist_level"): - entry_price = details["resist_level"] - target_price = round(entry_price * 1.05, 2) - elif entry_signal_type == "pullback" and details.get("support_price"): - entry_price = details["support_price"] - target_price = round(entry_price * 1.05, 2) - elif entry_signal_type == "launch" and details.get("resist_level"): - entry_price = round(details["resist_level"] * 1.01, 2) - target_price = round(details["resist_level"] * 1.08, 2) + # 风险乘数 + tech_signal = generate_signals(ts_code, name) - # 生成推荐理由 - reasons = _generate_reasons(stock, tech_signal, market_temp, intraday) + if tech_signal: + if tech_signal.rally_pct_5d > 20: + final_score *= 0.65 + elif tech_signal.rally_pct_5d > 15: + final_score *= 0.80 - # 风险提示 - risk_note = _generate_risk_note(market_temp, tech_signal, stock) + # 板块末期惩罚(板块信息来自 hot_sectors) + sector_stage = _get_sector_stage(sector, hot_sectors) + if sector_stage == "end": + final_score *= 0.70 + elif sector_stage == "late": + final_score *= 0.88 - rec = Recommendation( - ts_code=ts_code, - name=name, - sector=sector, - score=round(final_score, 1), - market_temp_score=round(market_temp_score, 1), - sector_score=round(sector_score, 1), - capital_score=round(capital_score, 1), - technical_score=round(stock.get("technical_score", 50), 1), - position_score=round(position_score, 1), - valuation_score=round(valuation_score, 1), - signal=signal, - entry_price=entry_price, - target_price=target_price, - stop_loss=stop_loss, - reasons=reasons, - risk_note=risk_note, - level=level, - strategy="trend_breakout", - entry_signal_type=entry_signal_type, - ) - recommendations.append(rec) + # 市场温度风控 + if market_temp_score < 30: + final_score *= 0.75 + elif market_temp_score < 50: + final_score *= 0.88 + + # 高置信度入场信号奖励 + if entry_signal.get("signal_score", 0) >= 80: + final_score *= 1.10 + + # 估值评分(辅助参考,不参与主评分) + pe = stock.get("pe") + pb = stock.get("pb") + valuation_score = _score_valuation(pe, pb) + + # 确定信号和等级 + level = _score_to_level(final_score) + signal = "HOLD" + position_score = tech_signal.position_score if tech_signal else 50 + if (signal_type != EntrySignal.NONE + and entry_signal.get("signal_score", 0) >= 50 + and position_score >= 30 + and final_score >= 60): + signal = "BUY" + + # 价格参考 + entry_price = None + target_price = None + stop_loss = None + if tech_signal: + entry_price = tech_signal.support_price + target_price = tech_signal.resist_price + stop_loss = tech_signal.stop_loss_price + + details = entry_signal.get("details", {}) + st = signal_type.value + if st == "breakout" and details.get("resist_level"): + entry_price = details["resist_level"] + target_price = round(entry_price * 1.05, 2) + elif st == "pullback" and details.get("support_price"): + entry_price = details["support_price"] + target_price = round(entry_price * 1.05, 2) + elif st == "launch" and details.get("resist_level"): + entry_price = round(details["resist_level"] * 1.01, 2) + target_price = round(details["resist_level"] * 1.08, 2) + + # 生成推荐理由 + reasons = _generate_reasons(stock, entry_signal, tech_signal, df, intraday) + risk_note = _generate_risk_note(market_temp, tech_signal, stock) + + # 量价模式 + vol_pattern = analyze_volume_pattern(df) + + rec = Recommendation( + ts_code=ts_code, + name=name, + sector=sector, + score=round(final_score, 1), + market_temp_score=round(market_temp_score, 1), + sector_score=round(_get_sector_heat(sector, hot_sectors), 1), + capital_score=round(_score_capital_simple(stock), 1), + technical_score=round(trend_score, 1), + position_score=round(position_score, 1), + valuation_score=round(valuation_score, 1), + signal=signal, + entry_price=entry_price, + target_price=target_price, + stop_loss=stop_loss, + reasons=reasons, + risk_note=risk_note, + level=level, + strategy="trend_breakout", + entry_signal_type=signal_type.value, + ) + recommendations.append(rec) + + if len(recommendations) >= settings.top_stock_count: + break + + except Exception as e: + logger.debug(f"深度分析 {ts_code} 失败: {e}") + continue + + # 让出控制权(同步函数中无法 await,跳过) + # idx % 10 == 0 的让步在 _select_from_hot_sectors 的上层 async 函数中处理 + + logger.info( + f"Step 3 入场信号分布: " + f"突破={signal_counts['breakout']} 回踩={signal_counts['pullback']} " + f"启动={signal_counts['launch']} 无信号={signal_counts['none']} " + f"(共分析{total}只)" + ) return recommendations -def _score_sector_resonance(sector_name: str, hot_sectors: list[SectorInfo]) -> float: - """板块共振评分 (0-100)""" - for s in hot_sectors: - if s.sector_name == sector_name: - score = 40 # 在热门板块列表中 - score += s.heat_score * 0.3 # 板块热度贡献 - if s.stage == "early": - score += 30 - elif s.stage == "mid": - score += 20 - elif s.stage == "late": - score += 5 - return min(score, 100) - return 10.0 # 不在热门板块 +# ── 价格行为评分 ── -def _get_sector_score(sector_name: str, hot_sectors: list[SectorInfo]) -> float: - """获取板块在热门板块中的得分""" - for s in hot_sectors: - if s.sector_name == sector_name: - return s.heat_score - return 30.0 +def _score_price_action(df, entry_signal: dict) -> float: + """价格行为学评分 (0-100) + + 维度: + - 入场信号类型质量 (40): 突破型/回踩型/启动型各自的得分 + - K线形态强度 (30): 突破日/回踩日的K线实体占比、下影线、收盘位置 + - 支撑阻力位质量 (30): 关键价格位置的测试情况 + """ + score = 0 + last = df.iloc[-1] + details = entry_signal.get("details", {}) + signal_type = entry_signal.get("signal_type") + + # 入场信号类型质量 (40) + signal_score = entry_signal.get("signal_score", 0) + score += signal_score * 0.40 + + # K线形态强度 (30) + day_range = last["high"] - last["low"] + if day_range > 0: + # 实体占比(实体/全振幅) + body = abs(last["close"] - last["open"]) + body_ratio = body / day_range + if body_ratio > 0.7: + score += 20 # 大实体,方向明确 + elif body_ratio > 0.4: + score += 12 + elif body_ratio > 0.2: + score += 6 + + # 收盘位置(越接近高点越好) + close_position = (last["close"] - last["low"]) / day_range + if close_position > 0.8: + score += 10 # 收在上部 20% + elif close_position > 0.6: + score += 6 + elif close_position > 0.4: + score += 3 + + # 支撑阻力位质量 (30) + if signal_type and signal_type.value == "breakout": + # 突破型:阻力位被突破的力度 + breakout_pct = details.get("breakout_pct", 0) + vol_ratio = details.get("volume_ratio", 1) + if breakout_pct > 2 and vol_ratio > 2: + score += 30 # 强力突破 + elif breakout_pct > 1 and vol_ratio > 1.5: + score += 20 + elif breakout_pct > 0: + score += 10 + + elif signal_type and signal_type.value == "pullback": + # 回踩型:支撑位的精确度 + support_ma = details.get("support_ma", "") + shrink = details.get("volume_shrink_ratio", 1) + if support_ma == "MA20" and shrink < 0.6: + score += 30 # 精确回踩 MA20 且大幅缩量 + elif support_ma == "MA20": + score += 22 + elif support_ma == "MA10" and shrink < 0.6: + score += 18 + else: + score += 10 + + elif signal_type and signal_type.value == "launch": + # 启动型:整理的充分度 + range_pct = details.get("price_range_pct", 10) + shrink = details.get("volume_shrink_ratio", 1) + if range_pct < 3 and shrink < 0.4: + score += 30 # 极度缩量窄幅整理 + elif range_pct < 5 and shrink < 0.6: + score += 20 + else: + score += 10 + else: + score += 10 + + return min(score, 100) + + +# ── 趋势评分 ── + + +def _score_trend(df) -> float: + """趋势评分 (0-100) + + 维度: + - 均线排列 (40): MA5>MA10>MA20>MA60 + - 更高高点/更高低点结构 (35): 近 20 日价格结构 + - MA20 方向 (25): MA20 是否持续上行 + """ + import pandas as pd + score = 0 + last = df.iloc[-1] + + # 均线排列 (40) + ma_cols = [c for c in ["ma5", "ma10", "ma20", "ma60"] if c in df.columns] + if len(ma_cols) >= 4 and not any(pd.isna(last[c]) for c in ma_cols): + if last["ma5"] > last["ma10"] > last["ma20"] > last["ma60"]: + score += 40 # 完美多头 + elif last["ma5"] > last["ma10"] > last["ma20"]: + score += 28 + elif last["ma5"] > last["ma20"]: + score += 15 + elif "ma5" in df.columns and "ma20" in df.columns: + if not pd.isna(last["ma5"]) and not pd.isna(last["ma20"]) and last["ma5"] > last["ma20"]: + score += 15 + + # 更高高点/更高低点结构 (35) + if len(df) >= 20: + recent = df.tail(20) + # 检查高点抬升 + first_10_high = recent["high"].iloc[:10].max() + second_10_high = recent["high"].iloc[10:].max() + # 检查低点抬升 + first_10_low = recent["low"].iloc[:10].min() + second_10_low = recent["low"].iloc[10:].min() + + if second_10_high > first_10_high and second_10_low > first_10_low: + score += 35 # 既抬高点又抬低点,最健康 + elif second_10_high > first_10_high: + score += 20 # 至少高点抬升 + elif second_10_low > first_10_low: + score += 12 # 至少低点抬升 + + # MA20 方向 (25) + if "ma20" in df.columns and len(df) >= 5: + ma20_now = last["ma20"] + ma20_5d = df.iloc[-5]["ma20"] + if not pd.isna(ma20_now) and not pd.isna(ma20_5d) and ma20_5d > 0: + ma20_pct = (ma20_now - ma20_5d) / ma20_5d * 100 + if ma20_pct > 2: + score += 25 + elif ma20_pct > 1: + score += 18 + elif ma20_pct > 0: + score += 10 + + return min(score, 100) + + +# ── 辅助函数 ── def _get_sector_stage(sector_name: str, hot_sectors: list[SectorInfo]) -> str: @@ -247,6 +621,41 @@ def _get_sector_stage(sector_name: str, hot_sectors: list[SectorInfo]) -> str: return "mid" +def _get_sector_heat(sector_name: str, hot_sectors: list[SectorInfo]) -> float: + """获取板块热度得分""" + for s in hot_sectors: + if s.sector_name == sector_name: + return s.heat_score + return 30.0 + + +def _score_capital_simple(stock: dict) -> float: + """资金流简单评分(仅基于已有数据,不额外调 API)""" + main_net = stock.get("main_net_inflow", 0) or 0 + inflow_ratio = stock.get("inflow_ratio", 0) or 0 + + score = 0 + if main_net > 10000: + score += 60 + elif main_net > 5000: + score += 45 + elif main_net > 2000: + score += 30 + elif main_net > 0: + score += 15 + + if inflow_ratio > 15: + score += 40 + elif inflow_ratio > 10: + score += 30 + elif inflow_ratio > 5: + score += 20 + elif inflow_ratio > 0: + score += 10 + + return min(score, 100) + + def _score_to_level(score: float) -> str: if score >= 80: return "强烈推荐" @@ -259,38 +668,45 @@ def _score_to_level(score: float) -> str: def _generate_reasons( - stock: dict, tech: TechnicalSignal | None, - market: MarketTemperature, intraday: bool = False, + stock: dict, entry_signal: dict, tech: TechnicalSignal | None, + df, intraday: bool = False, ) -> list[str]: """生成推荐理由""" + import pandas as pd reasons = [] - entry_type = stock.get("entry_signal_type", "none") - signal_map = {"breakout": "突破型", "pullback": "回踩型", "launch": "启动型"} - entry_label = signal_map.get(entry_type, "") + signal_type = entry_signal.get("signal_type") + details = entry_signal.get("details", {}) + signal_map = {EntrySignal.BREAKOUT: "突破型", EntrySignal.PULLBACK: "回踩型", EntrySignal.LAUNCH: "启动型"} + entry_label = signal_map.get(signal_type, "") # 入场信号 - if entry_label: - details = stock.get("entry_signal_details", {}) - if entry_type == "breakout": + if entry_label and signal_type: + st = signal_type.value + if st == "breakout": breakout_pct = details.get("breakout_pct", 0) vol_ratio = details.get("volume_ratio", 0) reasons.append(f"放量突破20日阻力位(涨幅{breakout_pct:.1f}%,量比{vol_ratio:.1f}倍)") - elif entry_type == "pullback": + elif st == "pullback": support = details.get("support_ma", "") shrink = details.get("volume_shrink_ratio", 0) reasons.append(f"缩量回踩{support}支撑(量能收缩至{shrink:.0%})") - elif entry_type == "launch": + elif st == "launch": range_pct = details.get("price_range_pct", 0) shrink = details.get("volume_shrink_ratio", 0) reasons.append(f"高位缩量整理{range_pct:.1f}%后即将变盘(量缩至{shrink:.0%})") # 供需分析 - vol_trend = stock.get("volume_trend", "") - ds_ratio = stock.get("demand_supply_ratio", 1) - if ds_ratio > 1.5: - reasons.append(f"需求主导(上涨均量/下跌均量={ds_ratio:.1f})") - elif vol_trend == "expanding": - reasons.append("量能逐步放大,资金持续介入") + if len(df) >= 10: + recent = df.tail(10) + up_days = recent[recent["pct_chg"] > 0] + down_days = recent[recent["pct_chg"] <= 0] + if len(up_days) > 0 and len(down_days) > 0: + avg_up_vol = up_days["vol"].mean() + avg_down_vol = down_days["vol"].mean() + if avg_down_vol > 0: + ds_ratio = avg_up_vol / avg_down_vol + if ds_ratio > 1.5: + reasons.append(f"需求主导(上涨均量/下跌均量={ds_ratio:.1f})") # 资金流 main_net = stock.get("main_net_inflow", 0) @@ -302,19 +718,7 @@ def _generate_reasons( # 板块 sector = stock.get("sector", "") if sector: - reasons.append(f"所属板块【{sector}】") - - # 技术面 - if tech: - tech_reasons = [] - if tech.ma_bullish: - tech_reasons.append("均线多头排列") - if tech.macd_golden: - tech_reasons.append("MACD金叉") - if tech.pullback_support: - tech_reasons.append("缩量回踩支撑") - if tech_reasons: - reasons.append("技术面: " + "、".join(tech_reasons)) + reasons.append(f"所属热门板块【{sector}】") return reasons[:3] diff --git a/backend/app/llm/__pycache__/prompts.cpython-313.pyc b/backend/app/llm/__pycache__/prompts.cpython-313.pyc index bd78c2eab176f1e59e7e277ed3f626a732838037..986ccbc2a946e827dbf07a28196cc2441b3601da 100644 GIT binary patch delta 365 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 RF0TL_41PuMmkolC{{nuaf9n7M delta 1797 zcmZuxO-vg{6rNeH*T%$lz<(G!U;-qV@EcOnx)1_!OjD^Qfw*c5mE+pRu>v-lHAPLN zK+~#+mP;~+p#B|tXcMW2N_)wvk`19zE`>@xbZ z7iuPI6U0IKq#ioqnsC1$wGb;ArE!Oq#D9gc3FAu`H(;b-jKb(_B9mUj3n3PgI!{PJ z;@jcIW)J$B@FpbiC_hIHn-Dn|HBd8oWN&^#+Osn<;5%|N^=TfnkMa>^lsfr`%C|ge z2~jU!vb^mM!$dm_^$xiIjVzIE*{|8G3LX5}<+~fX6Ioff*Y&{tK-p*;+GsfYXnxa` z*f^AU5+?4xE!X)iSO1PIxg9%J0mMDH<$4)jo~^pD7y_5KXv|9AcSZZjT^enbS4<#% zg50MqAe9M?w#j9RY*|LSY)0F19gVijWxwEqfOeT2p&N05ZEQKM;XmS*x89=wq7BktIWM+PLjveFg zI2#Q|el$Sn*S>NV=~M*7u)+qA9zhsH_|FYHhI%ur+SBV6HJdIKnW|G&mouqSN;QKn zgbx#`)}txHkj83MN}uPy)ONlp;#iD0)o3BoeReRRKLbX(%(7{S#29RRaxhR3-5|uY zIoBl#)}-P&PJR4$PcMz|ptq66`G7Y_$N1~spkkE7Z+JV;iNzF!DGWC_b0EYK#sG9F zGtCkps0x6nY_~K#N(>>c{o%c4I{XU$Uqrw|$}aKg`jCQ6VdH$c{*AVA5OwElDqlzz zvZZ9cFg;UTU^z4p>tScFp&aLqfPZ2V#r=h!!nxLbDa%-KK^4oTE2+7;Y$2nYS#iFQ zVLdoQ)L}hX#}!m1`1L@u(e?)XN0`#S4!BGf%sj??e7)iHB9_WtMG#5Zc9Q}^cjh2% zwNy-BOTLv-^J#V^icdU(ebqvQah~>57`q6;d;Xa~T+HoZ&izP7rQtkDI=i z=rI4drMQU2UJgs_J=C592;2X63_Qr*L|8;vLUHiSG7>03D_)6Wav8 d_bwaXZ_0iR^by)xa diff --git a/backend/app/llm/__pycache__/tools.cpython-313.pyc b/backend/app/llm/__pycache__/tools.cpython-313.pyc index d64b94e2e5878ed137cda096282ff8972d9ebbea..642eefe9c0646e00f906b730ebd81679f5707b5c 100644 GIT binary patch delta 347 zcmaDQ^j(DSGcPX}0}yOGawoG+WFnsgqt!-rc}9g`#$c6T)nFC_27QKLRz2omHXvr# zW76j=VoX=lRNw5!c$>BUmPmSPNqkXiVophBZfbmHUP@+iYH{fnpn)&?wm)!9_dD_$Ztb5Iq9bGThH$s#>ZfJbAeItn5F##w7 z{yv46_b+4G=!V73AkJe|-7mD)S`+4QB)rfdTWLQI&f$)3d}4|F{v5EsARJeU0d zqp=Eup!5R~tq)8LGC|A_BouDQDcq2e`^e5B&G(U=LzL?yD>p0K2Y;4eCO)P{t|D2W FRsb+#s|o-B delta 76 zcmew^@=A#BGcPX}0}$Lid^K~K&_q57Mx~AF@{An%97T-jDw?XB(-?2FP8Q(E;*tZZ bVg%yiqnlee4lwfOGP^VJF*R}($p8fbCwCHi diff --git a/backend/app/llm/prompts.py b/backend/app/llm/prompts.py index b1053d18..7df91ac6 100644 --- a/backend/app/llm/prompts.py +++ b/backend/app/llm/prompts.py @@ -42,6 +42,11 @@ CHAT_SYSTEM_PROMPT = """\ 3. 搜索股票代码 4. 基于数据给出专业的市场分析和投资建议 +重要提醒: +- 回答用户关于"今天市场怎么样"之类的问题时,必须调用 get_realtime_indices 获取实时指数数据 +- 盘中时段(9:30-15:00)必须使用实时数据,盘后时段使用当日收盘数据 +- 不要使用过时的数据,必须先调用工具获取最新数据再回答 + 回答要求: 1. 使用工具获取最新数据后再回答,不要凭空编造数据 2. 分析要结合 A 股市场特点(资金驱动、板块轮动、情绪周期) diff --git a/backend/app/llm/tool_executor.py b/backend/app/llm/tool_executor.py index 3d6b13fa..4444493c 100644 --- a/backend/app/llm/tool_executor.py +++ b/backend/app/llm/tool_executor.py @@ -33,6 +33,8 @@ async def execute_tool(name: str, arguments: dict) -> str: return await _get_stock_technical_signal(arguments["ts_code"]) elif name == "get_sector_performance": return await _get_sector_performance(arguments["sector_name"]) + elif name == "get_realtime_indices": + return await _get_realtime_indices() else: return json.dumps({"error": f"未知工具: {name}"}, ensure_ascii=False) except Exception as e: @@ -146,3 +148,41 @@ async def _get_sector_performance(sector_name: str) -> str: return json.dumps({"matched": False, "available_sectors": data}, ensure_ascii=False, default=str) data = _clean_for_json([s.model_dump() for s in matched]) return json.dumps({"matched": True, "sectors": data}, ensure_ascii=False, default=str) + + +async def _get_realtime_indices() -> str: + """获取指数实时行情数据(盘中用腾讯实时数据)""" + from app.data.tencent_client import get_index_realtime + from app.config import is_market_session + + is_trading = is_market_session() + + try: + index_data = await get_index_realtime() + if not index_data: + return json.dumps({"error": "获取指数数据失败"}, ensure_ascii=False) + + # 格式化数据 + results = [] + for ts_code, data in index_data.items(): + results.append({ + "ts_code": ts_code, + "name": data.get("name", ts_code), + "price": round(data.get("price", 0), 2), + "pct_chg": round(data.get("pct_chg", 0), 2), + "volume": data.get("volume", 0), + "amount": data.get("amount", 0), + "high": round(data.get("high", 0), 2), + "low": round(data.get("low", 0), 2), + "open": round(data.get("open", 0), 2), + "pre_close": round(data.get("pre_close", 0), 2), + }) + + return json.dumps({ + "is_realtime": is_trading, + "mode": "盘中实时" if is_trading else "盘后收盘", + "indices": results + }, ensure_ascii=False, default=str) + except Exception as e: + logger.error(f"获取实时指数失败: {e}") + return json.dumps({"error": f"获取指数数据失败: {e}"}, ensure_ascii=False) diff --git a/backend/app/llm/tools.py b/backend/app/llm/tools.py index bb240030..85247ca7 100644 --- a/backend/app/llm/tools.py +++ b/backend/app/llm/tools.py @@ -138,4 +138,16 @@ CHAT_TOOLS = [ }, }, }, + { + "type": "function", + "function": { + "name": "get_realtime_indices", + "description": "获取指数实时行情数据,包括上证指数、深证成指、创业板指的实时涨跌幅和成交数据。盘中时段返回实时数据,盘后返回当日收盘数据", + "parameters": { + "type": "object", + "properties": {}, + "required": [], + }, + }, + }, ] diff --git a/frontend/.next/app-build-manifest.json b/frontend/.next/app-build-manifest.json index 687ee4ea..1d48e9dc 100644 --- a/frontend/.next/app-build-manifest.json +++ b/frontend/.next/app-build-manifest.json @@ -20,6 +20,11 @@ "static/chunks/webpack.js", "static/chunks/main-app.js", "static/chunks/app/sectors/page.js" + ], + "/_not-found/page": [ + "static/chunks/webpack.js", + "static/chunks/main-app.js", + "static/chunks/app/_not-found/page.js" ] } } \ No newline at end of file diff --git a/frontend/.next/server/app-paths-manifest.json b/frontend/.next/server/app-paths-manifest.json index 092d16ac..1c039125 100644 --- a/frontend/.next/server/app-paths-manifest.json +++ b/frontend/.next/server/app-paths-manifest.json @@ -1,5 +1,6 @@ { + "/_not-found/page": "app/_not-found/page.js", "/page": "app/page.js", - "/sectors/page": "app/sectors/page.js", - "/recommendations/page": "app/recommendations/page.js" + "/recommendations/page": "app/recommendations/page.js", + "/sectors/page": "app/sectors/page.js" } \ No newline at end of file diff --git a/frontend/.next/server/webpack-runtime.js b/frontend/.next/server/webpack-runtime.js index 0a157f59..2854777e 100644 --- a/frontend/.next/server/webpack-runtime.js +++ b/frontend/.next/server/webpack-runtime.js @@ -125,7 +125,7 @@ /******/ /******/ /* webpack/runtime/getFullHash */ /******/ (() => { -/******/ __webpack_require__.h = () => ("b931683b67a20916") +/******/ __webpack_require__.h = () => ("37ba5b2074c7bffb") /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ diff --git a/frontend/.next/trace b/frontend/.next/trace index 0b809950..3ba30e11 100644 --- a/frontend/.next/trace +++ b/frontend/.next/trace @@ -19,3 +19,4 @@ [{"name":"client-success","duration":31,"timestamp":6463491542014,"id":1659,"parentId":3,"tags":{},"startTime":1776070438877,"traceId":"89894813de057625"},{"name":"add-entry","duration":18590,"timestamp":6463567258119,"id":1664,"parentId":1663,"tags":{"request":"next-app-loader?name=app%2Fpage&page=%2Fpage&appPaths=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776070514593,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":18185,"timestamp":6463567314790,"id":1670,"parentId":1669,"tags":{},"startTime":1776070514650,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":18392,"timestamp":6463567314592,"id":1669,"parentId":1668,"tags":{},"startTime":1776070514650,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":64563,"timestamp":6463567283085,"id":1668,"parentId":1662,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/components/market-temp.tsx","layer":"ssr"},"startTime":1776070514618,"traceId":"89894813de057625"},{"name":"make","duration":120202,"timestamp":6463567232800,"id":1663,"parentId":1662,"tags":{},"startTime":1776070514568,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":1177,"timestamp":6463567355604,"id":1672,"parentId":1671,"tags":{},"startTime":1776070514691,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":4,"timestamp":6463567356795,"id":1674,"parentId":1671,"tags":{},"startTime":1776070514692,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":669,"timestamp":6463567356809,"id":1675,"parentId":1671,"tags":{},"startTime":1776070514692,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":15,"timestamp":6463567357499,"id":1676,"parentId":1671,"tags":{},"startTime":1776070514693,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":3,"timestamp":6463567357524,"id":1677,"parentId":1671,"tags":{},"startTime":1776070514693,"traceId":"89894813de057625"},{"name":"optimize","duration":1811,"timestamp":6463567356791,"id":1673,"parentId":1671,"tags":{},"startTime":1776070514692,"traceId":"89894813de057625"},{"name":"module-hash","duration":339,"timestamp":6463567359262,"id":1678,"parentId":1671,"tags":{},"startTime":1776070514695,"traceId":"89894813de057625"},{"name":"code-generation","duration":4700,"timestamp":6463567359614,"id":1679,"parentId":1671,"tags":{},"startTime":1776070514695,"traceId":"89894813de057625"},{"name":"hash","duration":1327,"timestamp":6463567365183,"id":1680,"parentId":1671,"tags":{},"startTime":1776070514700,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":108,"timestamp":6463567366509,"id":1681,"parentId":1671,"tags":{},"startTime":1776070514702,"traceId":"89894813de057625"},{"name":"module-assets","duration":55,"timestamp":6463567366608,"id":1682,"parentId":1671,"tags":{},"startTime":1776070514702,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":2031,"timestamp":6463567366669,"id":1683,"parentId":1671,"tags":{},"startTime":1776070514702,"traceId":"89894813de057625"},{"name":"seal","duration":14701,"timestamp":6463567355012,"id":1671,"parentId":1662,"tags":{},"startTime":1776070514690,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":147729,"timestamp":6463567222614,"id":1662,"parentId":1660,"tags":{"name":"server"},"startTime":1776070514558,"traceId":"89894813de057625"},{"name":"emit","duration":6240,"timestamp":6463567370371,"id":1684,"parentId":1660,"tags":{},"startTime":1776070514706,"traceId":"89894813de057625"},{"name":"webpack-invalidated-server","duration":184278,"timestamp":6463567192803,"id":1660,"parentId":3,"tags":{"trigger":"src/components/market-temp.tsx"},"startTime":1776070514528,"traceId":"89894813de057625"},{"name":"add-entry","duration":6066,"timestamp":6463567381604,"id":1687,"parentId":1686,"tags":{"request":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/compiled/@next/react-refresh-utils/dist/runtime.js"},"startTime":1776070514717,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":3749,"timestamp":6463567386691,"id":1695,"parentId":1694,"tags":{},"startTime":1776070514722,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":3820,"timestamp":6463567386627,"id":1694,"parentId":1693,"tags":{},"startTime":1776070514722,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":10305,"timestamp":6463567386384,"id":1693,"parentId":1685,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/components/market-temp.tsx","layer":"app-pages-browser"},"startTime":1776070514722,"traceId":"89894813de057625"},{"name":"add-entry","duration":16831,"timestamp":6463567381642,"id":1691,"parentId":1686,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fapp-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-page.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Flayout-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Frender-from-template-context.js%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070514717,"traceId":"89894813de057625"},{"name":"add-entry","duration":16840,"timestamp":6463567381637,"id":1689,"parentId":1686,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070514717,"traceId":"89894813de057625"},{"name":"add-entry","duration":16876,"timestamp":6463567381633,"id":1688,"parentId":1686,"tags":{"request":"./node_modules/next/dist/client/app-next-dev.js"},"startTime":1776070514717,"traceId":"89894813de057625"},{"name":"read-resource","duration":9136,"timestamp":6463567389382,"id":1697,"parentId":1696,"tags":{},"startTime":1776070514725,"traceId":"89894813de057625"},{"name":"postcss-process","duration":64249,"timestamp":6463567398561,"id":1699,"parentId":1698,"tags":{},"startTime":1776070514734,"traceId":"89894813de057625"},{"name":"postcss-loader","duration":65413,"timestamp":6463567398535,"id":1698,"parentId":1696,"tags":{},"startTime":1776070514734,"traceId":"89894813de057625"},{"name":"css-loader","duration":17453,"timestamp":6463567463984,"id":1700,"parentId":1696,"tags":{"astUsed":"true"},"startTime":1776070514799,"traceId":"89894813de057625"},{"name":"build-module-css","duration":93258,"timestamp":6463567389288,"id":1696,"parentId":1692,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/globals.css.webpack[javascript/auto]!=!/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[2]!/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[14].oneOf[12].use[3]!/Users/aaron/source_code/astock-agent/frontend/src/app/globals.css","layer":null},"startTime":1776070514725,"traceId":"89894813de057625"},{"name":"build-module-css","duration":103118,"timestamp":6463567385282,"id":1692,"parentId":1685,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/globals.css","layer":"app-pages-browser"},"startTime":1776070514721,"traceId":"89894813de057625"},{"name":"build-module","duration":24,"timestamp":6463567488510,"id":1701,"parentId":1692,"tags":{},"startTime":1776070514824,"traceId":"89894813de057625"},{"name":"add-entry","duration":106912,"timestamp":6463567381640,"id":1690,"parentId":1686,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext-themes%2Fdist%2Findex.mjs%22%2C%22ids%22%3A%5B%22ThemeProvider%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fglobals.css%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fauth-guard.tsx%22%2C%22ids%22%3A%5B%22AuthGuard%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fnav.tsx%22%2C%22ids%22%3A%5B%22SidebarNav%22%2C%22MobileBottomNav%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fuser-menu.tsx%22%2C%22ids%22%3A%5B%22UserMenu%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fhooks%2Fuse-auth.tsx%22%2C%22ids%22%3A%5B%22AuthProvider%22%5D%7D&server=false!"},"startTime":1776070514717,"traceId":"89894813de057625"},{"name":"make","duration":108785,"timestamp":6463567379780,"id":1686,"parentId":1685,"tags":{},"startTime":1776070514715,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":818,"timestamp":6463567536641,"id":1703,"parentId":1702,"tags":{},"startTime":1776070514872,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":4,"timestamp":6463567537483,"id":1705,"parentId":1702,"tags":{},"startTime":1776070514873,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":45,"timestamp":6463567537497,"id":1706,"parentId":1702,"tags":{},"startTime":1776070514873,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":5,"timestamp":6463567537556,"id":1707,"parentId":1702,"tags":{},"startTime":1776070514873,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":2,"timestamp":6463567537568,"id":1708,"parentId":1702,"tags":{},"startTime":1776070514873,"traceId":"89894813de057625"},{"name":"optimize","duration":753,"timestamp":6463567537477,"id":1704,"parentId":1702,"tags":{},"startTime":1776070514873,"traceId":"89894813de057625"},{"name":"module-hash","duration":303,"timestamp":6463567538776,"id":1709,"parentId":1702,"tags":{},"startTime":1776070514874,"traceId":"89894813de057625"},{"name":"code-generation","duration":1111,"timestamp":6463567539085,"id":1710,"parentId":1702,"tags":{},"startTime":1776070514874,"traceId":"89894813de057625"},{"name":"hash","duration":1923,"timestamp":6463567541763,"id":1711,"parentId":1702,"tags":{},"startTime":1776070514877,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":75,"timestamp":6463567543685,"id":1712,"parentId":1702,"tags":{},"startTime":1776070514879,"traceId":"89894813de057625"},{"name":"module-assets","duration":36,"timestamp":6463567543753,"id":1713,"parentId":1702,"tags":{},"startTime":1776070514879,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":2845,"timestamp":6463567543792,"id":1714,"parentId":1702,"tags":{},"startTime":1776070514879,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-generateClientManifest","duration":72,"timestamp":6463567547602,"id":1716,"parentId":1685,"tags":{},"startTime":1776070514883,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-createassets","duration":133,"timestamp":6463567547545,"id":1715,"parentId":1685,"tags":{},"startTime":1776070514883,"traceId":"89894813de057625"},{"name":"seal","duration":12694,"timestamp":6463567535975,"id":1702,"parentId":1685,"tags":{},"startTime":1776070514871,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":169320,"timestamp":6463567379369,"id":1685,"parentId":1661,"tags":{"name":"client"},"startTime":1776070514715,"traceId":"89894813de057625"},{"name":"emit","duration":3724,"timestamp":6463567548714,"id":1717,"parentId":1661,"tags":{},"startTime":1776070514884,"traceId":"89894813de057625"},{"name":"webpack-invalidated-client","duration":359667,"timestamp":6463567194032,"id":1661,"parentId":3,"tags":{"trigger":"src/components/market-temp.tsx"},"startTime":1776070514529,"traceId":"89894813de057625"}] [{"name":"client-success","duration":24,"timestamp":6463567565533,"id":1718,"parentId":3,"tags":{},"startTime":1776070514901,"traceId":"89894813de057625"},{"name":"handle-request","duration":46645,"timestamp":6463567584576,"id":1719,"tags":{"url":"/?_rsc=1p60s","isTurbopack":false},"startTime":1776070514920,"traceId":"89894813de057625"},{"name":"memory-usage","duration":2,"timestamp":6463567631342,"id":1720,"parentId":1719,"tags":{"url":"/?_rsc=1p60s","memory.rss":"270925824","memory.heapUsed":"182629352","memory.heapTotal":"230932480"},"startTime":1776070514967,"traceId":"89894813de057625"},{"name":"client-hmr-latency","duration":446000,"timestamp":6463567196675,"id":1721,"parentId":3,"tags":{"updatedModules":["[project]/src/app/globals.css"],"page":"/","isPageHidden":false},"startTime":1776070514983,"traceId":"89894813de057625"},{"name":"add-entry","duration":11796,"timestamp":6463645731098,"id":1728,"parentId":1726,"tags":{"request":"next-app-loader?name=app%2Fpage&page=%2Fpage&appPaths=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776070593066,"traceId":"89894813de057625"},{"name":"build-module","duration":13379,"timestamp":6463645738972,"id":1729,"parentId":1727,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/next-app-loader.js?name=app%2Frecommendations%2Fpage&page=%2Frecommendations%2Fpage&appPaths=%2Frecommendations%2Fpage&pagePath=private-next-app-dir%2Frecommendations%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!","layer":"rsc"},"startTime":1776070593074,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":8077,"timestamp":6463645757509,"id":1732,"parentId":1731,"tags":{},"startTime":1776070593093,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":8221,"timestamp":6463645757373,"id":1731,"parentId":1730,"tags":{},"startTime":1776070593093,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":9495,"timestamp":6463645757142,"id":1730,"parentId":1729,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/recommendations/page.tsx","layer":"rsc"},"startTime":1776070593092,"traceId":"89894813de057625"},{"name":"add-entry","duration":39170,"timestamp":6463645730681,"id":1727,"parentId":1726,"tags":{"request":"next-app-loader?name=app%2Frecommendations%2Fpage&page=%2Frecommendations%2Fpage&appPaths=%2Frecommendations%2Fpage&pagePath=private-next-app-dir%2Frecommendations%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776070593066,"traceId":"89894813de057625"},{"name":"build-module","duration":677,"timestamp":6463645780303,"id":1740,"parentId":1725,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Frecommendations%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=true!","layer":"ssr"},"startTime":1776070593116,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":5507,"timestamp":6463645785286,"id":1743,"parentId":1742,"tags":{},"startTime":1776070593121,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":5592,"timestamp":6463645785207,"id":1742,"parentId":1741,"tags":{},"startTime":1776070593121,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":11423,"timestamp":6463645784914,"id":1741,"parentId":1740,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/recommendations/page.tsx","layer":"ssr"},"startTime":1776070593120,"traceId":"89894813de057625"},{"name":"make","duration":75507,"timestamp":6463645729038,"id":1726,"parentId":1725,"tags":{},"startTime":1776070593064,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":1113,"timestamp":6463645806378,"id":1745,"parentId":1744,"tags":{},"startTime":1776070593142,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":10,"timestamp":6463645807526,"id":1747,"parentId":1744,"tags":{},"startTime":1776070593143,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":1748,"timestamp":6463645807556,"id":1748,"parentId":1744,"tags":{},"startTime":1776070593143,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":6,"timestamp":6463645809320,"id":1749,"parentId":1744,"tags":{},"startTime":1776070593145,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":3,"timestamp":6463645809336,"id":1750,"parentId":1744,"tags":{},"startTime":1776070593145,"traceId":"89894813de057625"},{"name":"optimize","duration":2731,"timestamp":6463645807518,"id":1746,"parentId":1744,"tags":{},"startTime":1776070593143,"traceId":"89894813de057625"},{"name":"module-hash","duration":314,"timestamp":6463645811019,"id":1751,"parentId":1744,"tags":{},"startTime":1776070593146,"traceId":"89894813de057625"},{"name":"code-generation","duration":1369,"timestamp":6463645811341,"id":1752,"parentId":1744,"tags":{},"startTime":1776070593147,"traceId":"89894813de057625"},{"name":"hash","duration":680,"timestamp":6463645813424,"id":1753,"parentId":1744,"tags":{},"startTime":1776070593149,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":42,"timestamp":6463645814104,"id":1754,"parentId":1744,"tags":{},"startTime":1776070593149,"traceId":"89894813de057625"},{"name":"module-assets","duration":28,"timestamp":6463645814141,"id":1755,"parentId":1744,"tags":{},"startTime":1776070593149,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":2555,"timestamp":6463645814170,"id":1756,"parentId":1744,"tags":{},"startTime":1776070593149,"traceId":"89894813de057625"},{"name":"seal","duration":12182,"timestamp":6463645805812,"id":1744,"parentId":1725,"tags":{},"startTime":1776070593141,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":90687,"timestamp":6463645728145,"id":1725,"parentId":1723,"tags":{"name":"server"},"startTime":1776070593063,"traceId":"89894813de057625"},{"name":"emit","duration":3124,"timestamp":6463645818851,"id":1757,"parentId":1723,"tags":{},"startTime":1776070593154,"traceId":"89894813de057625"},{"name":"webpack-invalidated-server","duration":98654,"timestamp":6463645723667,"id":1723,"parentId":3,"tags":{"trigger":"manual"},"startTime":1776070593059,"traceId":"89894813de057625"},{"name":"add-entry","duration":16359,"timestamp":6463645826441,"id":1760,"parentId":1759,"tags":{"request":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/compiled/@next/react-refresh-utils/dist/runtime.js"},"startTime":1776070593162,"traceId":"89894813de057625"},{"name":"add-entry","duration":17572,"timestamp":6463645826471,"id":1762,"parentId":1759,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070593162,"traceId":"89894813de057625"},{"name":"add-entry","duration":21201,"timestamp":6463645826481,"id":1764,"parentId":1759,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fapp-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-page.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Flayout-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Frender-from-template-context.js%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070593162,"traceId":"89894813de057625"},{"name":"add-entry","duration":27507,"timestamp":6463645826467,"id":1761,"parentId":1759,"tags":{"request":"./node_modules/next/dist/client/app-next-dev.js"},"startTime":1776070593162,"traceId":"89894813de057625"},{"name":"add-entry","duration":27506,"timestamp":6463645826473,"id":1763,"parentId":1759,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext-themes%2Fdist%2Findex.mjs%22%2C%22ids%22%3A%5B%22ThemeProvider%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fglobals.css%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fauth-guard.tsx%22%2C%22ids%22%3A%5B%22AuthGuard%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fnav.tsx%22%2C%22ids%22%3A%5B%22SidebarNav%22%2C%22MobileBottomNav%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fuser-menu.tsx%22%2C%22ids%22%3A%5B%22UserMenu%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fhooks%2Fuse-auth.tsx%22%2C%22ids%22%3A%5B%22AuthProvider%22%5D%7D&server=false!"},"startTime":1776070593162,"traceId":"89894813de057625"},{"name":"build-module","duration":2700,"timestamp":6463645877968,"id":1766,"parentId":1765,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Frecommendations%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!","layer":"app-pages-browser"},"startTime":1776070593213,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":13098,"timestamp":6463645881748,"id":1769,"parentId":1768,"tags":{},"startTime":1776070593217,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":13189,"timestamp":6463645881667,"id":1768,"parentId":1767,"tags":{},"startTime":1776070593217,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":20774,"timestamp":6463645881442,"id":1767,"parentId":1766,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/recommendations/page.tsx","layer":"app-pages-browser"},"startTime":1776070593217,"traceId":"89894813de057625"},{"name":"add-entry","duration":92570,"timestamp":6463645826484,"id":1765,"parentId":1759,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Frecommendations%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070593162,"traceId":"89894813de057625"},{"name":"make","duration":95391,"timestamp":6463645823703,"id":1759,"parentId":1758,"tags":{},"startTime":1776070593159,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":2710,"timestamp":6463645922609,"id":1771,"parentId":1770,"tags":{},"startTime":1776070593258,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":4,"timestamp":6463645925343,"id":1773,"parentId":1770,"tags":{},"startTime":1776070593261,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":49,"timestamp":6463645925360,"id":1774,"parentId":1770,"tags":{},"startTime":1776070593261,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":5,"timestamp":6463645925423,"id":1775,"parentId":1770,"tags":{},"startTime":1776070593261,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":3,"timestamp":6463645925440,"id":1776,"parentId":1770,"tags":{},"startTime":1776070593261,"traceId":"89894813de057625"},{"name":"optimize","duration":1577,"timestamp":6463645925335,"id":1772,"parentId":1770,"tags":{},"startTime":1776070593261,"traceId":"89894813de057625"},{"name":"module-hash","duration":721,"timestamp":6463645928124,"id":1777,"parentId":1770,"tags":{},"startTime":1776070593263,"traceId":"89894813de057625"},{"name":"code-generation","duration":1768,"timestamp":6463645928878,"id":1778,"parentId":1770,"tags":{},"startTime":1776070593264,"traceId":"89894813de057625"},{"name":"hash","duration":4005,"timestamp":6463645933130,"id":1779,"parentId":1770,"tags":{},"startTime":1776070593268,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":164,"timestamp":6463645937134,"id":1780,"parentId":1770,"tags":{},"startTime":1776070593272,"traceId":"89894813de057625"},{"name":"module-assets","duration":44,"timestamp":6463645937287,"id":1781,"parentId":1770,"tags":{},"startTime":1776070593273,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":4248,"timestamp":6463645937334,"id":1782,"parentId":1770,"tags":{},"startTime":1776070593273,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-generateClientManifest","duration":98,"timestamp":6463645942352,"id":1784,"parentId":1758,"tags":{},"startTime":1776070593278,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-createassets","duration":270,"timestamp":6463645942293,"id":1783,"parentId":1758,"tags":{},"startTime":1776070593278,"traceId":"89894813de057625"},{"name":"seal","duration":23697,"timestamp":6463645919922,"id":1770,"parentId":1758,"tags":{},"startTime":1776070593255,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":120130,"timestamp":6463645823510,"id":1758,"parentId":1739,"tags":{"name":"client"},"startTime":1776070593159,"traceId":"89894813de057625"},{"name":"emit","duration":17838,"timestamp":6463645943655,"id":1785,"parentId":1739,"tags":{},"startTime":1776070593279,"traceId":"89894813de057625"},{"name":"compile-path","duration":238323,"timestamp":6463645723748,"id":1724,"tags":{"trigger":"/recommendations","isTurbopack":false},"startTime":1776070593059,"traceId":"89894813de057625"},{"name":"webpack-invalidated-client","duration":191072,"timestamp":6463645771281,"id":1739,"parentId":3,"tags":{"trigger":"manual"},"startTime":1776070593107,"traceId":"89894813de057625"}] [{"name":"client-success","duration":9,"timestamp":6463645965129,"id":1786,"parentId":3,"tags":{},"startTime":1776070593300,"traceId":"89894813de057625"},{"name":"handle-request","duration":302489,"timestamp":6463645711244,"id":1722,"tags":{"url":"/recommendations?_rsc=19zvn","isTurbopack":false},"startTime":1776070593047,"traceId":"89894813de057625"},{"name":"memory-usage","duration":1,"timestamp":6463646013781,"id":1787,"parentId":1722,"tags":{"url":"/recommendations?_rsc=19zvn","memory.rss":"276774912","memory.heapUsed":"189029680","memory.heapTotal":"232685568"},"startTime":1776070593349,"traceId":"89894813de057625"},{"name":"client-hmr-latency","duration":246000,"timestamp":6463645771331,"id":1788,"parentId":3,"tags":{"updatedModules":[],"page":"/","isPageHidden":false},"startTime":1776070593355,"traceId":"89894813de057625"},{"name":"handle-request","duration":10611,"timestamp":6463646020065,"id":1789,"tags":{"url":"/recommendations?_rsc=1808t","isTurbopack":false},"startTime":1776070593355,"traceId":"89894813de057625"},{"name":"memory-usage","duration":6,"timestamp":6463646030862,"id":1790,"parentId":1789,"tags":{"url":"/recommendations?_rsc=1808t","memory.rss":"276856832","memory.heapUsed":"190297496","memory.heapTotal":"232685568"},"startTime":1776070593366,"traceId":"89894813de057625"},{"name":"add-entry","duration":14485,"timestamp":6463650195214,"id":1798,"parentId":1795,"tags":{"request":"next-app-loader?name=app%2Frecommendations%2Fpage&page=%2Frecommendations%2Fpage&appPaths=%2Frecommendations%2Fpage&pagePath=private-next-app-dir%2Frecommendations%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776070597531,"traceId":"89894813de057625"},{"name":"add-entry","duration":27113,"timestamp":6463650194980,"id":1796,"parentId":1795,"tags":{"request":"next-app-loader?name=app%2Fpage&page=%2Fpage&appPaths=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776070597530,"traceId":"89894813de057625"},{"name":"build-module","duration":24892,"timestamp":6463650207972,"id":1799,"parentId":1797,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/next-app-loader.js?name=app%2Fsectors%2Fpage&page=%2Fsectors%2Fpage&appPaths=%2Fsectors%2Fpage&pagePath=private-next-app-dir%2Fsectors%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!","layer":"rsc"},"startTime":1776070597543,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":2054,"timestamp":6463650236064,"id":1802,"parentId":1801,"tags":{},"startTime":1776070597571,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":2172,"timestamp":6463650235952,"id":1801,"parentId":1800,"tags":{},"startTime":1776070597571,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":2861,"timestamp":6463650235687,"id":1800,"parentId":1799,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/sectors/page.tsx","layer":"rsc"},"startTime":1776070597571,"traceId":"89894813de057625"},{"name":"add-entry","duration":44633,"timestamp":6463650195209,"id":1797,"parentId":1795,"tags":{"request":"next-app-loader?name=app%2Fsectors%2Fpage&page=%2Fsectors%2Fpage&appPaths=%2Fsectors%2Fpage&pagePath=private-next-app-dir%2Fsectors%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776070597531,"traceId":"89894813de057625"},{"name":"build-module","duration":648,"timestamp":6463650247474,"id":1813,"parentId":1794,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fsectors%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=true!","layer":"ssr"},"startTime":1776070597583,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":5759,"timestamp":6463650251260,"id":1816,"parentId":1815,"tags":{},"startTime":1776070597587,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":5843,"timestamp":6463650251185,"id":1815,"parentId":1814,"tags":{},"startTime":1776070597587,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":13601,"timestamp":6463650250925,"id":1814,"parentId":1813,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/sectors/page.tsx","layer":"ssr"},"startTime":1776070597586,"traceId":"89894813de057625"},{"name":"make","duration":80165,"timestamp":6463650192398,"id":1795,"parentId":1794,"tags":{},"startTime":1776070597528,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":1456,"timestamp":6463650274536,"id":1818,"parentId":1817,"tags":{},"startTime":1776070597610,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":5,"timestamp":6463650276010,"id":1820,"parentId":1817,"tags":{},"startTime":1776070597611,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":1314,"timestamp":6463650276026,"id":1821,"parentId":1817,"tags":{},"startTime":1776070597611,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":12,"timestamp":6463650277356,"id":1822,"parentId":1817,"tags":{},"startTime":1776070597613,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":3,"timestamp":6463650277380,"id":1823,"parentId":1817,"tags":{},"startTime":1776070597613,"traceId":"89894813de057625"},{"name":"optimize","duration":2739,"timestamp":6463650276004,"id":1819,"parentId":1817,"tags":{},"startTime":1776070597611,"traceId":"89894813de057625"},{"name":"module-hash","duration":425,"timestamp":6463650279569,"id":1824,"parentId":1817,"tags":{},"startTime":1776070597615,"traceId":"89894813de057625"},{"name":"code-generation","duration":1821,"timestamp":6463650280006,"id":1825,"parentId":1817,"tags":{},"startTime":1776070597615,"traceId":"89894813de057625"},{"name":"hash","duration":3235,"timestamp":6463650282756,"id":1826,"parentId":1817,"tags":{},"startTime":1776070597618,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":70,"timestamp":6463650285991,"id":1827,"parentId":1817,"tags":{},"startTime":1776070597621,"traceId":"89894813de057625"},{"name":"module-assets","duration":41,"timestamp":6463650286051,"id":1828,"parentId":1817,"tags":{},"startTime":1776070597621,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":1889,"timestamp":6463650286099,"id":1829,"parentId":1817,"tags":{},"startTime":1776070597621,"traceId":"89894813de057625"},{"name":"seal","duration":15512,"timestamp":6463650273847,"id":1817,"parentId":1794,"tags":{},"startTime":1776070597609,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":101964,"timestamp":6463650191136,"id":1794,"parentId":1792,"tags":{"name":"server"},"startTime":1776070597526,"traceId":"89894813de057625"},{"name":"emit","duration":2462,"timestamp":6463650293134,"id":1830,"parentId":1792,"tags":{},"startTime":1776070597628,"traceId":"89894813de057625"},{"name":"webpack-invalidated-server","duration":110209,"timestamp":6463650185671,"id":1792,"parentId":3,"tags":{"trigger":"manual"},"startTime":1776070597521,"traceId":"89894813de057625"},{"name":"build-module","duration":674,"timestamp":6463650303286,"id":1840,"parentId":1839,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/build/webpack/loaders/next-flight-client-entry-loader.js?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fsectors%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!","layer":"app-pages-browser"},"startTime":1776070597639,"traceId":"89894813de057625"},{"name":"add-entry","duration":3947,"timestamp":6463650300761,"id":1838,"parentId":1832,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Frecommendations%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"add-entry","duration":4608,"timestamp":6463650300717,"id":1833,"parentId":1832,"tags":{"request":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/compiled/@next/react-refresh-utils/dist/runtime.js"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"add-entry","duration":5718,"timestamp":6463650300755,"id":1835,"parentId":1832,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"add-entry","duration":7426,"timestamp":6463650300759,"id":1837,"parentId":1832,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fapp-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-page.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Flayout-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Frender-from-template-context.js%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"add-entry","duration":7490,"timestamp":6463650300751,"id":1834,"parentId":1832,"tags":{"request":"./node_modules/next/dist/client/app-next-dev.js"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"add-entry","duration":7489,"timestamp":6463650300757,"id":1836,"parentId":1832,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext-themes%2Fdist%2Findex.mjs%22%2C%22ids%22%3A%5B%22ThemeProvider%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fglobals.css%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fauth-guard.tsx%22%2C%22ids%22%3A%5B%22AuthGuard%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fnav.tsx%22%2C%22ids%22%3A%5B%22SidebarNav%22%2C%22MobileBottomNav%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fuser-menu.tsx%22%2C%22ids%22%3A%5B%22UserMenu%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fhooks%2Fuse-auth.tsx%22%2C%22ids%22%3A%5B%22AuthProvider%22%5D%7D&server=false!"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"next-swc-transform","duration":3836,"timestamp":6463650307630,"id":1843,"parentId":1842,"tags":{},"startTime":1776070597643,"traceId":"89894813de057625"},{"name":"next-swc-loader","duration":3904,"timestamp":6463650307564,"id":1842,"parentId":1841,"tags":{},"startTime":1776070597643,"traceId":"89894813de057625"},{"name":"build-module-tsx","duration":7188,"timestamp":6463650307275,"id":1841,"parentId":1840,"tags":{"name":"/Users/aaron/source_code/astock-agent/frontend/src/app/sectors/page.tsx","layer":"app-pages-browser"},"startTime":1776070597643,"traceId":"89894813de057625"},{"name":"add-entry","duration":18586,"timestamp":6463650300763,"id":1839,"parentId":1832,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fsectors%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776070597636,"traceId":"89894813de057625"},{"name":"make","duration":21869,"timestamp":6463650297506,"id":1832,"parentId":1831,"tags":{},"startTime":1776070597633,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":695,"timestamp":6463650320768,"id":1845,"parentId":1844,"tags":{},"startTime":1776070597656,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":2,"timestamp":6463650321473,"id":1847,"parentId":1844,"tags":{},"startTime":1776070597657,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":21,"timestamp":6463650321484,"id":1848,"parentId":1844,"tags":{},"startTime":1776070597657,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":2,"timestamp":6463650321513,"id":1849,"parentId":1844,"tags":{},"startTime":1776070597657,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":2,"timestamp":6463650321524,"id":1850,"parentId":1844,"tags":{},"startTime":1776070597657,"traceId":"89894813de057625"},{"name":"optimize","duration":528,"timestamp":6463650321469,"id":1846,"parentId":1844,"tags":{},"startTime":1776070597657,"traceId":"89894813de057625"},{"name":"module-hash","duration":276,"timestamp":6463650322535,"id":1851,"parentId":1844,"tags":{},"startTime":1776070597658,"traceId":"89894813de057625"},{"name":"code-generation","duration":1136,"timestamp":6463650322816,"id":1852,"parentId":1844,"tags":{},"startTime":1776070597658,"traceId":"89894813de057625"},{"name":"hash","duration":1850,"timestamp":6463650324834,"id":1853,"parentId":1844,"tags":{},"startTime":1776070597660,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":56,"timestamp":6463650326683,"id":1854,"parentId":1844,"tags":{},"startTime":1776070597662,"traceId":"89894813de057625"},{"name":"module-assets","duration":25,"timestamp":6463650326736,"id":1855,"parentId":1844,"tags":{},"startTime":1776070597662,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":1545,"timestamp":6463650326763,"id":1856,"parentId":1844,"tags":{},"startTime":1776070597662,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-generateClientManifest","duration":84,"timestamp":6463650328875,"id":1858,"parentId":1831,"tags":{},"startTime":1776070597664,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-createassets","duration":134,"timestamp":6463650328828,"id":1857,"parentId":1831,"tags":{},"startTime":1776070597664,"traceId":"89894813de057625"},{"name":"seal","duration":9563,"timestamp":6463650320125,"id":1844,"parentId":1831,"tags":{},"startTime":1776070597655,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":32462,"timestamp":6463650297245,"id":1831,"parentId":1812,"tags":{"name":"client"},"startTime":1776070597633,"traceId":"89894813de057625"},{"name":"emit","duration":4593,"timestamp":6463650329717,"id":1859,"parentId":1812,"tags":{},"startTime":1776070597665,"traceId":"89894813de057625"},{"name":"compile-path","duration":148984,"timestamp":6463650185807,"id":1793,"tags":{"trigger":"/sectors","isTurbopack":false},"startTime":1776070597521,"traceId":"89894813de057625"},{"name":"webpack-invalidated-client","duration":93831,"timestamp":6463650241307,"id":1812,"parentId":3,"tags":{"trigger":"manual"},"startTime":1776070597577,"traceId":"89894813de057625"}] +[{"name":"client-success","duration":1,"timestamp":6463650336291,"id":1860,"parentId":3,"tags":{},"startTime":1776070597672,"traceId":"89894813de057625"},{"name":"handle-request","duration":174462,"timestamp":6463650176982,"id":1791,"tags":{"url":"/sectors?_rsc=17yb1","isTurbopack":false},"startTime":1776070597512,"traceId":"89894813de057625"},{"name":"memory-usage","duration":1,"timestamp":6463650351484,"id":1861,"parentId":1791,"tags":{"url":"/sectors?_rsc=17yb1","memory.rss":"246808576","memory.heapUsed":"202610472","memory.heapTotal":"237928448"},"startTime":1776070597687,"traceId":"89894813de057625"},{"name":"client-hmr-latency","duration":121000,"timestamp":6463650241848,"id":1863,"parentId":3,"tags":{"updatedModules":[],"page":"/recommendations","isPageHidden":false},"startTime":1776070597698,"traceId":"89894813de057625"},{"name":"handle-request","duration":48050,"timestamp":6463650360495,"id":1862,"tags":{"url":"/sectors?_rsc=165d9","isTurbopack":false},"startTime":1776070597696,"traceId":"89894813de057625"},{"name":"memory-usage","duration":1,"timestamp":6463650408591,"id":1864,"parentId":1862,"tags":{"url":"/sectors?_rsc=165d9","memory.rss":"247119872","memory.heapUsed":"203961360","memory.heapTotal":"237928448"},"startTime":1776070597744,"traceId":"89894813de057625"},{"name":"handle-request","duration":357028,"timestamp":6465423399730,"id":1865,"tags":{"url":"/sectors","isTurbopack":false},"startTime":1776072370736,"traceId":"89894813de057625"},{"name":"memory-usage","duration":1,"timestamp":6465423757890,"id":1866,"parentId":1865,"tags":{"url":"/sectors","memory.rss":"105775104","memory.heapUsed":"178173064","memory.heapTotal":"184565760"},"startTime":1776072371094,"traceId":"89894813de057625"},{"name":"add-entry","duration":60279,"timestamp":6465423984939,"id":1878,"parentId":1872,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Frecommendations%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"add-entry","duration":60392,"timestamp":6465423984943,"id":1880,"parentId":1872,"tags":{"request":"next-client-pages-loader?absolutePagePath=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error.js&page=%2F_not-found%2Fpage!"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"add-entry","duration":65756,"timestamp":6465423983744,"id":1873,"parentId":1872,"tags":{"request":"/Users/aaron/source_code/astock-agent/frontend/node_modules/next/dist/compiled/@next/react-refresh-utils/dist/runtime.js"},"startTime":1776072371320,"traceId":"89894813de057625"},{"name":"add-entry","duration":65930,"timestamp":6465423984941,"id":1879,"parentId":1872,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fsectors%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"add-entry","duration":69061,"timestamp":6465423984930,"id":1875,"parentId":1872,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"add-entry","duration":73453,"timestamp":6465423984936,"id":1877,"parentId":1872,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fapp-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-page.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Flayout-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Frender-from-template-context.js%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"add-entry","duration":73524,"timestamp":6465423984933,"id":1876,"parentId":1872,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fnode_modules%2Fnext-themes%2Fdist%2Findex.mjs%22%2C%22ids%22%3A%5B%22ThemeProvider%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp%2Fglobals.css%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fauth-guard.tsx%22%2C%22ids%22%3A%5B%22AuthGuard%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fnav.tsx%22%2C%22ids%22%3A%5B%22SidebarNav%22%2C%22MobileBottomNav%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fcomponents%2Fuser-menu.tsx%22%2C%22ids%22%3A%5B%22UserMenu%22%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fhooks%2Fuse-auth.tsx%22%2C%22ids%22%3A%5B%22AuthProvider%22%5D%7D&server=false!"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"add-entry","duration":74845,"timestamp":6465423984916,"id":1874,"parentId":1872,"tags":{"request":"./node_modules/next/dist/client/app-next-dev.js"},"startTime":1776072371321,"traceId":"89894813de057625"},{"name":"make","duration":81249,"timestamp":6465423978546,"id":1872,"parentId":1871,"tags":{},"startTime":1776072371315,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":4142,"timestamp":6465424102803,"id":1882,"parentId":1881,"tags":{},"startTime":1776072371439,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":5,"timestamp":6465424106973,"id":1884,"parentId":1881,"tags":{},"startTime":1776072371443,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":78,"timestamp":6465424106992,"id":1885,"parentId":1881,"tags":{},"startTime":1776072371443,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":11,"timestamp":6465424107089,"id":1886,"parentId":1881,"tags":{},"startTime":1776072371443,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":4,"timestamp":6465424107111,"id":1887,"parentId":1881,"tags":{},"startTime":1776072371443,"traceId":"89894813de057625"},{"name":"optimize","duration":953,"timestamp":6465424106966,"id":1883,"parentId":1881,"tags":{},"startTime":1776072371443,"traceId":"89894813de057625"},{"name":"module-hash","duration":1073,"timestamp":6465424108721,"id":1888,"parentId":1881,"tags":{},"startTime":1776072371445,"traceId":"89894813de057625"},{"name":"code-generation","duration":5180,"timestamp":6465424109807,"id":1889,"parentId":1881,"tags":{},"startTime":1776072371446,"traceId":"89894813de057625"},{"name":"hash","duration":4746,"timestamp":6465424117176,"id":1890,"parentId":1881,"tags":{},"startTime":1776072371453,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":113,"timestamp":6465424121922,"id":1891,"parentId":1881,"tags":{},"startTime":1776072371458,"traceId":"89894813de057625"},{"name":"module-assets","duration":48,"timestamp":6465424122028,"id":1892,"parentId":1881,"tags":{},"startTime":1776072371458,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":3023,"timestamp":6465424122079,"id":1893,"parentId":1881,"tags":{},"startTime":1776072371458,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-generateClientManifest","duration":78,"timestamp":6465424125840,"id":1895,"parentId":1871,"tags":{},"startTime":1776072371462,"traceId":"89894813de057625"},{"name":"NextJsBuildManifest-createassets","duration":122,"timestamp":6465424125799,"id":1894,"parentId":1871,"tags":{},"startTime":1776072371462,"traceId":"89894813de057625"},{"name":"seal","duration":25246,"timestamp":6465424101986,"id":1881,"parentId":1871,"tags":{},"startTime":1776072371438,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":153095,"timestamp":6465423974163,"id":1871,"parentId":1868,"tags":{"name":"client"},"startTime":1776072371310,"traceId":"89894813de057625"},{"name":"emit","duration":7662,"timestamp":6465424127275,"id":1896,"parentId":1868,"tags":{},"startTime":1776072371464,"traceId":"89894813de057625"},{"name":"webpack-invalidated-client","duration":177038,"timestamp":6465423958410,"id":1868,"parentId":3,"tags":{"trigger":"manual"},"startTime":1776072371295,"traceId":"89894813de057625"},{"name":"add-entry","duration":17455,"timestamp":6465424149371,"id":1901,"parentId":1898,"tags":{"request":"next-app-loader?name=app%2Frecommendations%2Fpage&page=%2Frecommendations%2Fpage&appPaths=%2Frecommendations%2Fpage&pagePath=private-next-app-dir%2Frecommendations%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776072371486,"traceId":"89894813de057625"},{"name":"add-entry","duration":17463,"timestamp":6465424149374,"id":1902,"parentId":1898,"tags":{"request":"next-app-loader?name=app%2Fsectors%2Fpage&page=%2Fsectors%2Fpage&appPaths=%2Fsectors%2Fpage&pagePath=private-next-app-dir%2Fsectors%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776072371486,"traceId":"89894813de057625"},{"name":"add-entry","duration":17716,"timestamp":6465424149332,"id":1899,"parentId":1898,"tags":{"request":"next-app-loader?name=app%2F_not-found%2Fpage&page=%2F_not-found%2Fpage&appPaths=&pagePath=..%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error.js&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776072371486,"traceId":"89894813de057625"},{"name":"add-entry","duration":17914,"timestamp":6465424149367,"id":1900,"parentId":1898,"tags":{"request":"next-app-loader?name=app%2Fpage&page=%2Fpage&appPaths=%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend%2Fsrc%2Fapp&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&rootDir=%2FUsers%2Faaron%2Fsource_code%2Fastock-agent%2Ffrontend&isDev=true&tsconfigPath=tsconfig.json&basePath=&assetPrefix=&nextConfigOutput=standalone&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1776072371486,"traceId":"89894813de057625"},{"name":"make","duration":40653,"timestamp":6465424140149,"id":1898,"parentId":1897,"tags":{},"startTime":1776072371476,"traceId":"89894813de057625"},{"name":"chunk-graph","duration":1045,"timestamp":6465424182091,"id":1915,"parentId":1914,"tags":{},"startTime":1776072371518,"traceId":"89894813de057625"},{"name":"optimize-modules","duration":2,"timestamp":6465424183146,"id":1917,"parentId":1914,"tags":{},"startTime":1776072371519,"traceId":"89894813de057625"},{"name":"optimize-chunks","duration":1306,"timestamp":6465424183157,"id":1918,"parentId":1914,"tags":{},"startTime":1776072371519,"traceId":"89894813de057625"},{"name":"optimize-tree","duration":3,"timestamp":6465424184473,"id":1919,"parentId":1914,"tags":{},"startTime":1776072371521,"traceId":"89894813de057625"},{"name":"optimize-chunk-modules","duration":3,"timestamp":6465424184486,"id":1920,"parentId":1914,"tags":{},"startTime":1776072371521,"traceId":"89894813de057625"},{"name":"optimize","duration":1888,"timestamp":6465424183141,"id":1916,"parentId":1914,"tags":{},"startTime":1776072371519,"traceId":"89894813de057625"},{"name":"module-hash","duration":213,"timestamp":6465424185568,"id":1921,"parentId":1914,"tags":{},"startTime":1776072371522,"traceId":"89894813de057625"},{"name":"code-generation","duration":1196,"timestamp":6465424185787,"id":1922,"parentId":1914,"tags":{},"startTime":1776072371522,"traceId":"89894813de057625"},{"name":"hash","duration":1121,"timestamp":6465424187908,"id":1923,"parentId":1914,"tags":{},"startTime":1776072371524,"traceId":"89894813de057625"},{"name":"code-generation-jobs","duration":55,"timestamp":6465424189028,"id":1924,"parentId":1914,"tags":{},"startTime":1776072371525,"traceId":"89894813de057625"},{"name":"module-assets","duration":68,"timestamp":6465424189079,"id":1925,"parentId":1914,"tags":{},"startTime":1776072371525,"traceId":"89894813de057625"},{"name":"create-chunk-assets","duration":159,"timestamp":6465424189150,"id":1926,"parentId":1914,"tags":{},"startTime":1776072371525,"traceId":"89894813de057625"},{"name":"seal","duration":9520,"timestamp":6465424181620,"id":1914,"parentId":1897,"tags":{},"startTime":1776072371518,"traceId":"89894813de057625"},{"name":"webpack-compilation","duration":53151,"timestamp":6465424139452,"id":1897,"parentId":1870,"tags":{"name":"server"},"startTime":1776072371476,"traceId":"89894813de057625"},{"name":"emit","duration":2461,"timestamp":6465424192618,"id":1927,"parentId":1870,"tags":{},"startTime":1776072371529,"traceId":"89894813de057625"},{"name":"compile-path","duration":236858,"timestamp":6465423958555,"id":1869,"tags":{"trigger":"/_not-found","isTurbopack":false},"startTime":1776072371295,"traceId":"89894813de057625"},{"name":"webpack-invalidated-server","duration":236514,"timestamp":6465423959548,"id":1870,"parentId":3,"tags":{"trigger":"manual"},"startTime":1776072371296,"traceId":"89894813de057625"}]