From b72e1ec51d0566614d370ceec2a9d31b1f22dd4a Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Tue, 27 Jan 2026 16:03:32 +0700 Subject: [PATCH] feat: Implement `/simulate-all` endpoint to run predictions and EAC for all equipment, and refine `is_actual` determination logic. --- .../__pycache__/router.cpython-311.pyc | Bin 12626 -> 15142 bytes .../__pycache__/service.cpython-311.pyc | Bin 30095 -> 30602 bytes src/equipment/router.py | 43 +++++++++++++++++- src/equipment/service.py | 8 ++++ .../insert_actual_data.cpython-311.pyc | Bin 51672 -> 51955 bytes src/modules/equipment/insert_actual_data.py | 14 +++++- 6 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/equipment/__pycache__/router.cpython-311.pyc b/src/equipment/__pycache__/router.cpython-311.pyc index 5ecac5aad57f17c4d755ba725a18821ba0e60d43..9b45157b40c3755a8f8dc245a7fb4d2e9a7ef902 100644 GIT binary patch delta 4526 zcmc&&eQaCR6@S;y&+$i`I&tE}u^qpBG>P-!ByCAb(l(?`0)@0G%?8A6NgcnJ)Wjd* zzUQbUy zoJDtY{Zx!Ri0m9AREm9MSl{mL5vs&pGkU>sMt_^p-Fay;sH^mL=j3WO$?eI@buohT z3Ibs8r#*sA?4HpHwVQMX^E!aj1vslIP7~l1Dnumq5cWa`NWYy~$| zB>#pjCGt6yX*q4_xF+wBA>BL>+K79-Lqe|7dW-&Id4n1;1lgao48$F#wfIJQo0wE?xRY%WgM`9 z`j%aw=P2lzg^o>n`$4ZEw^%1!g10$ueYfCOFG1HP>H7u!1mo*|)36I6E7@K0M|-w> zR+OiLk;qg~l0-R{h)Sz2V*;UWa?a#NJ>(~*e)kSIe|#4uoj|VYaX00ADA`8z=1Fch zupO(@<`HC2rv}MumH_G_#!_DcRq?T!${_Gr0~SMZEJ!SLc%H|*$V6!)cQ5n?$rGjX zXwT~Br5+C5Am)mJQW^(Io!NX9;X(3XMUUYC4B)%Ta>Z%eRUI~}Js>_E9wX)UQ3F-L zJIOKo&ath)W-CvG8H>T_Rg^VHMIjuVl26ZznC6k< zJ>%olp1R@Vuwm$AE2MBV5edrTO;lKbiT&g+)d#Bf0GG6O0V+4(AQ4XEp z3+bSr$6uoo`lGR$(vQnEzTlQ4DbU+Javp)4m+@odS-IGzi~ z(z8j+Asi3EO`;ZVpe~3MHICKLrjdsa0?AtE=_0|%l!$R0=Y`aHHOLa^=jAX5nw$M~4o`}g=i;6ebVZ*{HHJfN@2C`!dBbmS$ zjWhZ{ZlU&%14$O*ohD@RAA93nPr|VD190Y7pD~{?OU#B|Z?b<}*LA&LsTZ2|w|r^G#s1ezl;OudniiFr+068bwdoVkrLB1mTE-Pwb48R2IoC>MCEtj} z;Y@#NWXda7_MalA(E+%u2n~svP7;K6H31Q zxyHF{U3XM3TR!zPUp{>uDW3j}r+>}Uzrk2dPL(`+V0qt4&3eV6irJ*>J*FI*NYfXz zhmPc!IZ0`jp=Df&HCIBZSj@H3MJ3-h^k%E`Q%B<~k0_4bjH7qW(W}_*&^~oI&xtRH zE9|<%`8$XILx=ymQE~V)j-fTjkYZC?T6^W`y3KRBJY#EEv94IZ`q=K?U|1`l&y#hV z>%!w18^2;%cPw0zFD73fS00@FXeO*YHkX-+tj$D}wkWiWH@4=DDUO9)vn?q3wr;Cg zF>3$5f+xLE0>**aFf&^o(y87b-GGnem`)L~#uR88-LETjKmz#213CHy!_*wq{X?ZN z4z_NiCUxX?nr4{7kkkqH@EZ;{bJ0BHWv}>$YT0*cIbh$l4{^gz-Fr^+NFDcHW6y|_ z`=yfu9tS}KPgA0P40T$YOA*jTu^gbZ@+YfaC#d>G@ zMfEb7bRI@~$TjDSXn_pZ4)Qr`?z}bJ4_vY84{Nt^ zNf`Sv$<|JQs^9fpQ2m9gH?La%KUM8+AD4pJpOGOqRXy(>v(U_dX_)}1K6lr+;kcN0 z;ZLHUxm>a^Fq~;=7Rq9Gb_%49N zzsRx1Il%Wp<3H<%ppU;viR$j$#eIfaB@iz6Z!s>jCsI z`EBcU9d0B~`LM40~c$?Ao0NLEV-On=9yg*>D%;ApbI zH0&`AU`(SFLkt~Y)H|2e&&Q#_rpKFQ=MyrfuEca}xG27%d&9U3U?d{q9{dJ$X<;S( z8h)Q_un3`bl4+|Ze`+(3&)VKkmm$cI|36ZF=hf?02Rfcc14rRMJdDrOK2^IsoU@sX)3g3B=QD(R delta 2469 zcmc&!eP~-%6n{6#d(F~jq#x^tP0dHsrmsz#HtE;;k+p8Cb2`VIB0khM&AaPT^P2AF zrMBIq&P{P3``{un1(^tw=}<5-hW_JU4g^ug1j!%D(7yzA{|SQNIrk;&7y7S(oOkXy z=XcJzzkBYzx^#PX=Lc@LOM>60+H(5y(50P?wmb5rjvH%4(k0!QX*a@JhtPLqRHIYt z6uK+ZWkj@y(XDlhb!DcNFEmUm+~@3YnJkn}o;u+b5I z-l4YaqNB$gdi`0)mh@xgc)r(G=ZorAT;sO5@p7Cj=}mNye`oVmimG}IC3JxQZVxm( zunmn`=Xj^xeu8hOu#*p~l@eG_6Q%CXRm54uS>S{nRkr7e4#EJpzs z0l~#6a1n-G25B7FXx-NDLuCZy4Ow~Hu-i7;E0|IA_HCHGfZ2zPm(HXX_oyN&Ol$QH z8WYu=y1EUyACOZeBvhmIi0noroC2Wh@1tFM=Qi}ifDV?>U4Zr=x)sq8an{}25C?R} zQAzD7Oetf8RPi$vKNd`9o{%X2b%&Sq@(S08Hx47l`cXtcTywA}>b)rX_;apjGw3z7i6s4$w!GCgO9h2QUIqx^bRt2_m(1iw?YNX85M z-7PX%<*Ay{DxC6Fo6DsVmf#=O^eK;mferJ!H7g#~#++gW0`!A?zIIAM02|^rYKKXP z|559W9t1o|_l<0wRXHFMOQrYG|0MFWVN2Hq%3gZFKIp}ig;SKHsSNSNn`!tth3 zw|>NL9F}>0IqW5){6<)n4RBuJzl5P;VYtIfD#e&BASA)3Rh3NgbE=Pw@N4SHwE;AX ztO{#Dy#j?8m3^pT5#|A*x~;La@}TtuiV+kHMGl3DB9CGT#fu<{b@N#qpJ^kxoHnx4 z)q0AT2VTi+-lVK(pQCFg z8$**rPp3@wG=@x}z=wt53&8L$vtuamo-w>FV%mz1#VovAI3Gp(V%}uPJ;R-|&3E=M z=NNR5&(H*W2fPISxz`m*x@RZw`++C>8+f)~;miHE*4)H)Px}9adt-HP;roFzglGjm KbiwYxbNmMb1TO0U diff --git a/src/equipment/__pycache__/service.cpython-311.pyc b/src/equipment/__pycache__/service.cpython-311.pyc index 61a84c4abedc1c7a5d3205f5af504d1ab5218b63..05604914c2613381b9168a425ae5e1690e5070e9 100644 GIT binary patch delta 447 zcmeBw&Diyxk#9LKFBbz4q!(0VuFjpvC&9R8qk0Rgu5YSWs=Xvg1`N_zQnFfDqM}lQ zQ?*i}Qtg5KY#={6g)x{xGiUQ2)(J^`j6jvlK->((pLb1uA1*rieh?2+7SrTFD>3F; z#+u3eX5y27@(WDXQQ&1}s$rV^L0xR}0cHNlr>sOL$?(+!jbf@&2udv}Ey^oaNL0wl zEG|*VPg6+D$x%owE>11UE6pv|^V4K2Vgp)HBnl#YK!iVt$OI9AKthwbC;`OY2_m9^ zL@~%!3JMBEMnL+OV0vmvJkZE^xRFJIK&KyJk_M^_2da1hgbfT&d4#4~UJ+Kk$fI_J zM{V*hOEV$)OWg7oxfQQ)D_&qxEHVTtoh)x5wmCAxnUPflXvFEs3p3joV>WAK{bJGu znOOu9EiwQq1gi$y|BJ&WH$SB`C)KV<4=4*n48_Tty>dl3G)kCQ7?oC7eqaDnJ4ALQ P?~w#E(FwlE38ne~;zM<@ delta 104 zcmeBr&)EN(k#9LKFBbz4T;hq#Je)I;Pl9p6M)ej}!KhUGG?tXC7M7@}6vki%&FsxL zSSKWHmdOfWWEEm$U^qOvFuRR0dh?I$UrdZblYivNa_Intfrz2lcC&oG2nSOU)8y!K FeE^f-9^C){ diff --git a/src/equipment/router.py b/src/equipment/router.py index 20ac961..c78ac63 100644 --- a/src/equipment/router.py +++ b/src/equipment/router.py @@ -27,7 +27,8 @@ from src.equipment.service import ( delete, generate_all_transaction, get_top_10_economic_life, - get_maximo_by_assetnum + get_maximo_by_assetnum, + get_all_assetnums, ) from src.modules.equipment.Prediksi import main as prediksi_main from src.modules.equipment.Eac import Eac @@ -115,6 +116,46 @@ async def simulate_equipment(db_session: DbSession, assetnum: str): return StreamingResponse(event_generator(), media_type='text/event-stream') + +@router.get("/simulate-all") +async def simulate_all_equipment(db_session: DbSession): + """Run simulation (prediksi + EAC) for ALL equipment. + Returns SSE stream of progress. + """ + + async def event_generator(): + # fetch all assetnums + try: + assetnums = await get_all_assetnums(db_session=db_session) + except Exception as e: + yield f"data: {json.dumps({'status':'error', 'message': f'Failed to fetch assetnums: {str(e)}'})}\\n\\n" + return + + total = len(assetnums) + yield f"data: {json.dumps({'status':'started', 'message': f'Starting simulation for {total} assets'})}\\n\\n" + + success_count = 0 + error_count = 0 + + for idx, assetnum in enumerate(assetnums, start=1): + yield f"data: {json.dumps({'status':'working', 'step':f'Processing {idx}/{total}', 'assetnum': assetnum})}\\n\\n" + + try: + # Prediksi + await prediksi_main(assetnum=assetnum) + # EAC + eac = Eac() + await eac.hitung_eac_equipment(assetnum=assetnum) + success_count += 1 + except Exception as e: + error_count += 1 + yield f"data: {json.dumps({'status':'partial_error', 'assetnum': assetnum, 'message': str(e)})}\\n\\n" + continue + + yield f"data: {json.dumps({'status':'done', 'message':f'All simulations completed. Success: {success_count}, Errors: {error_count}'})}\\n\\n" + + return StreamingResponse(event_generator(), media_type='text/event-stream') + @router.get( "/count-remaining-life", response_model=StandardResponse[CountRemainingLifeResponse], diff --git a/src/equipment/service.py b/src/equipment/service.py index 69cbe13..c0dd0bf 100644 --- a/src/equipment/service.py +++ b/src/equipment/service.py @@ -704,6 +704,14 @@ async def update( return updated_data + +async def get_all_assetnums(*, db_session: DbSession) -> list[str]: + """Returns a list of all assetnums.""" + query = Select(Equipment.assetnum) + result = await db_session.execute(query) + return result.scalars().all() + + async def delete(*, db_session: DbSession, equipment_id: str): """Deletes a document.""" query = Delete(Equipment).where(Equipment.id == equipment_id) diff --git a/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc b/src/modules/equipment/__pycache__/insert_actual_data.cpython-311.pyc index 984ec589b9a480b698f5c2dd3465d6cf4230cee1..e1ed601a17fdaa3cee8571004941fc9d825d6daa 100644 GIT binary patch delta 2317 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 diff --git a/src/modules/equipment/insert_actual_data.py b/src/modules/equipment/insert_actual_data.py index 1767020..5f587fd 100644 --- a/src/modules/equipment/insert_actual_data.py +++ b/src/modules/equipment/insert_actual_data.py @@ -605,7 +605,11 @@ async def insert_lcca_maximo_corrective_data(): assetnum, # assetnum year, # tahun seq, # seq - (0 if year > current_year + 1 else 1), # is_actual + ( + (1 if year <= current_year - 1 else 0) + if datetime.now().month >= 12 + else (0 if year > current_year + 1 else 1) + ), # is_actual raw_cm_material_cost, # raw_cm_material_cost raw_cm_labor_time, # raw_cm_labor_time rc_cm_material_cost, # rc_cm_material_cost @@ -1127,9 +1131,15 @@ async def query_data(target_assetnum: str = None): total_elapsed = datetime.now() - overall_start pct_assets = (idx / total_assets) * 100 if total_assets else 100 # progress per asset + action_info = f"Inserted: {inserted_this_asset}" + if updated_this_asset > 0: + action_info = f"Updated: {updated_this_asset}" + if inserted_this_asset > 0: + action_info += f", Inserted: {inserted_this_asset}" + print( f"[{idx}/{total_assets}] Asset {assetnum} processed. " - f"Inserted: {inserted_this_asset}, Updated: {updated_this_asset}. " + f"{action_info}. " f"Asset time: {asset_elapsed.total_seconds():.2f}s. " f"Total Ins: {total_inserted}, Upd: {total_updated}. " f"Overall elapsed: {total_elapsed.total_seconds():.2f}s. "