From 63461ad93b718277602d70ef33e0c9c762867600 Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Thu, 6 Nov 2025 16:38:48 +0700 Subject: [PATCH] new insert maximo data script --- src/modules/config.py | 14 + src/modules/equipment/Prediksi.py | 88 +- .../__pycache__/Prediksi.cpython-311.pyc | Bin 25282 -> 28009 bytes .../insert_actual_data.cpython-311.pyc | Bin 20586 -> 36615 bytes src/modules/equipment/insert_actual_data.py | 752 +++++++++++++----- src/modules/equipment/run.py | 68 +- 6 files changed, 666 insertions(+), 256 deletions(-) diff --git a/src/modules/config.py b/src/modules/config.py index 5f57c73..68ea4ec 100644 --- a/src/modules/config.py +++ b/src/modules/config.py @@ -1,5 +1,19 @@ import psycopg2 +def get_production_connection(): + try: + # Konfigurasi koneksi database produksi + production_connection = psycopg2.connect( + dbname="digital_twin", + user="digital_twin", + password="Pr0jec7@D!g!tTwiN", + host="192.168.1.82", + port="1111", + ) + return production_connection + except Exception as e: + print("Error saat koneksi ke database produksi:", e) + return None def get_connection(): try: diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index c3d34e1..1acc5eb 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -1,4 +1,5 @@ import os +import asyncio import pandas as pd import numpy as np import numpy_financial as npf # Gunakan numpy-financial untuk fungsi keuangan @@ -22,8 +23,11 @@ load_dotenv() class Prediksi: - def __init__(self, RELIABILITY_APP_URL): - self.RELIABILITY_APP_URL = RELIABILITY_APP_URL + def __init__(self, RELIABILITY_APP_URL=None): + # Allow passing the URL or fallback to environment/default so callers can omit the parameter + self.RELIABILITY_APP_URL = RELIABILITY_APP_URL or os.getenv( + "RELIABILITY_APP_URL", "http://192.168.1.82:8000/reliability" + ) # Fungsi untuk mengambil data dari database def __get_param(self, equipment_id): @@ -42,7 +46,7 @@ class Prediksi: # Query untuk mendapatkan data query = """ SELECT - (select COALESCE(forecasting_target_year, 2060) from lcc_ms_equipment_data where assetnum = %s) AS forecasting_target_year + (select COALESCE(forecasting_target_year, 2056) from lcc_ms_equipment_data where assetnum = %s) AS forecasting_target_year """ cursor.execute(query, (equipment_id,)) par1 = cursor.fetchone() @@ -79,12 +83,18 @@ class Prediksi: raw_cm_material_cost AS cm_cost, raw_cm_labor_time AS cm_labor_time, raw_cm_labor_human AS cm_labor_human, + raw_pm_interval AS pm_interval, raw_pm_material_cost AS pm_cost, raw_pm_labor_time AS pm_labor_time, raw_pm_labor_human AS pm_labor_human, + raw_oh_interval AS oh_interval, raw_oh_material_cost AS oh_cost, raw_oh_labor_time AS oh_labor_time, raw_oh_labor_human AS oh_labor_human, + raw_predictive_material_cost AS predictive_material_cost, + raw_predictive_labor_time AS predictive_labor_time, + raw_predictive_labor_human AS predictive_labor_human, + raw_predictive_interval AS predictive_interval, "raw_loss_output_MW" AS loss_output_mw, raw_loss_output_price AS loss_price FROM lcc_equipment_tr_data @@ -180,11 +190,12 @@ class Prediksi: tahun, assetnum, raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human, - raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human, + raw_oh_interval, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human, + raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human, "raw_loss_output_MW", raw_loss_output_price , created_by, created_at ) VALUES ( - %s, %s, 0, 1, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW() + %s, %s, 0, 1, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW() ) """ @@ -239,9 +250,14 @@ class Prediksi: float(row["pm_cost"]), float(row["pm_labor_time"]), float(row["pm_labor_human"]), + float(row["oh_interval"]), float(row["oh_cost"]), float(row["oh_labor_time"]), float(row["oh_labor_human"]), + float(row["predictive_interval"]), + float(row["predictive_material_cost"]), + float(row["predictive_labor_time"]), + float(row["predictive_labor_human"]), float(row["loss_output_mw"]), float(row["loss_price"]), ) @@ -260,7 +276,7 @@ class Prediksi: connection.close() # Fungsi untuk menghapus data proyeksi pada tahun tertentu - def __update_date_lcc(self, equipment_id): + def __update_data_lcc(self, equipment_id): try: connections = get_connection() connection = ( @@ -368,16 +384,16 @@ class Prediksi: # ====================================================================================================================================================== - async def predict_equipment_data(self, p_equipment_id, token): + async def predict_equipment_data(self, assetnum, token): try: # Mengambil data dari database - df = self.__fetch_data_from_db(p_equipment_id) + df = self.__fetch_data_from_db(assetnum) if df is None: print("Data tidak tersedia untuk prediksi.") return # Mendapatkan tahun proyeksi dari DB - par_tahun_target = self.__get_param(p_equipment_id) + par_tahun_target = self.__get_param(assetnum) # Tahun proyeksi future_years = list(range(df["year"].max() + 1, par_tahun_target + 1)) @@ -420,7 +436,7 @@ class Prediksi: return np.abs(preds) # Mendapatkan rate dan tahun maksimal - rate, max_year = self.__get_rate_and_max_year(p_equipment_id) + rate, max_year = self.__get_rate_and_max_year(assetnum) pmt = 0 # Prediksi untuk setiap kolom @@ -450,34 +466,60 @@ class Prediksi: predictions_df = pd.DataFrame(predictions) # print(predictions_df) # Hapus data prediksi yang ada sebelumnya - self.__delete_predictions_from_db(p_equipment_id) + self.__delete_predictions_from_db(assetnum) # Insert hasil prediksi ke database try: await self.__insert_predictions_to_db( - predictions_df, p_equipment_id, token + predictions_df, assetnum, token ) except Exception as e: print(f"Error saat insert data ke database: {e}") # self.__insert_predictions_to_db(predictions_df, p_equipment_id) # Update data untuk total RiskCost per tahun - self.__update_date_lcc(p_equipment_id) + self.__update_data_lcc(assetnum) except Exception as e: print(f"Program dihentikan: {e}") -import asyncio +RELIABILITY_APP_URL = os.getenv("RELIABILITY_APP_URL", "http://192.168.1.82:8000/reliability") +async def main(RELIABILITY_APP_URL=RELIABILITY_APP_URL): + try: + connections = get_connection() + connection = connections[0] if isinstance(connections, tuple) else connections + if connection is None: + print("Database connection failed.") + return -if __name__ == "__main__": + cursor = connection.cursor(cursor_factory=DictCursor) + query_main = "SELECT DISTINCT(assetnum) FROM ms_equipment_master" + cursor.execute(query_main) + results = cursor.fetchall() - async def main(): - prediksi = Prediksi() - await prediksi.predict_equipment_data( - "A22277", - token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczOTUxODc4Ni4yOTM5ODUsImp0aSI6Ilo5clRUOFhGa3RweFZUQlBmNGxvRmciLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiNWUxNmY4YTgtMWEwMy00MTVjLWIwZjItMTVmZjczOWY1OGE4IiwibmJmIjoxNzM5NTE4Nzg2LCJjc3JmIjoiZWI0MjAzOTMtYTg1ZS00NDJjLWIyMjItZTU5MGU5MGVkYjkyIiwiZXhwIjoxNzM5NjA1MTg2LCJub25jZSI6IjVkZDdhOGYyMWIzZWUxZDZmYmI1YThhMDBlMmYyYjczIn0.3Jv943cU5FuxJ9K92JmVoOtTBqexF4Dke8TrrC4l0Uk", - ) - print("Selesai.") - asyncio.run(main()) + prediksi = Prediksi(RELIABILITY_APP_URL) + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc2MjQxODk5My4xNzI4NTYsImp0aSI6ImJ1OU0xQVlLSTZENTd2cC1OaDgtUlEiLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiMzg1NzJhOTItZjE2Yy00MWIyLThjNmYtYWZhNTcyMzhhNWU3IiwibmJmIjoxNzYyNDE4OTkzLCJjc3JmIjoiNjY5NzVjNDEtNTg0ZS00OGFkLWJjMmItMDNlZDEyZDM2ZDczIiwiZXhwIjoxNzYyNDI2MTkzLCJub25jZSI6ImYzMThkNDVkNmYzZWRjMzNiN2Q0MmE0MGRkNDJkNDRhIn0.elDnyaoeJ48oOIUdMRZjt7gGICmK-2Awg6Rbl_BZ1PQ" + + for idx, row in enumerate(results, start=1): + assetnum = row.get("assetnum") if hasattr(row, "get") else row[0] + if not assetnum or str(assetnum).strip() == "": + print(f"[{idx}/{len(results)}] Skipping empty assetnum") + continue + print(f"[{idx}/{len(results)}] Processing assetnum: {assetnum}") + try: + await prediksi.predict_equipment_data(assetnum, token) + except Exception as e: + print(f"Error processing {assetnum}: {e}") + + print("Selesai.") + except Exception as e: + print(f"Error getting database connection: {e}") + return + except Exception as e: + print(f"Error getting database connection: {e}") + return + + +asyncio.run(main()) diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 3543fb93cd6ff8b2c02bd8581fc55ef0ad3107eb..2d3140fdf7116b2cd98c18712635cdbcdc2275c3 100644 GIT binary patch delta 5434 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 TzKeZn%9No@Ad!EhLOl6Dg2JIJ delta 3395 zcmai0d2kcg8Q-_7W937(EL*l@8=v5aL)fy-;n)TnU$QOP*piKwV=J`lSkkU!Rx;Qw z6MH74F%1yl(K4k?OWRCRXiE;Y)0Tfy=;V)*PJ`J+ct3nh_i6!_4$iAUk)n3EZh_ym)geHrWa>00n;#Px9 zO?b^CH85L^+bYV4cW6|-?RnTie|H|DrjC1E@Hk6}6o&)~;)Xy8BhY{vfdSMC8bF<( z1=I^VKvvKL8Uz+FOE3Ut3t6L-Jv+q?E20pJ#6v;Vq=!2Ynt}UXp}NyybeQ6)`iCze z8Kcw%b>a z+^6YH_1LHXC(6g&>^cNdW2=w}V|EQHPhMsN8ep_CFRBT!CY6#Q?hJxd`r zDb)mO2-Ff#MOOor(g=Y>K-ox0T9{DkfQXYSu3Q<43GXp)F}0F6KZDdPP-)s}sQCc@ zx33G0;47W$ksHTcR=v3>f3Z9L6n@ENNxBO^Mq0%PYp}~w4H1wmj>4yj3d57iLH8v~ zD^l=ZEkR^X4i&GWQ4x+>UyppQgB7qor3=PnIh0UT7xka(Vk$=vB{3oL2c|`3HshTB z@&1a5=qkgn+sg1OwmS6HiO#GXqg4 z(XsCw)}7CTb|^Ysy$_XOzWQZ@)uWKIQi`y)CPH7JlS<81gwL$Ln%uwUH#Ew_YwX>< zBx`n786bj#04Zi#k-#k}Bt{b{RuSV$$sW>T}T0MUEPK8${y zWY&(-s0oj+$Jnu9P?w1vw%W>SQcOIdC}U&?jSvcg>QF*dl-QwoN;@IN0tuxRzrDe- zu9q01M$?puW)oqkRkN2+Pn6DShXd`y8yy{}4j*vXpg4~?x{A($RQy?};*UQ1kor(9 zk`TfV`g-Y323M7?qvvV7dt;wIuPlFYFkOo;ZXD26=N8{N0z={P_MJOX2R^;Cx}+JTo+zJ6C?DXYtT=zf zia*%dg^u90Bj=zC-x*1w`9-4hYXrVdq?kZ-R)OqN%26_9Ct`{i3_xvdCA{MR5XhGZ z-9g@GlT(F<`VEP35@gH_gpfcR*uS8q$UjI6@5g+yU>6}Ea5#RI=ZzUa)vhk0;CI6Ryl6iYUrdf?s zym(+bG$n&8d`M9CVjtJA;~PZL_X+%vz~c;WBU3ec?S$G6kTOn@4^}V{f^&d>crq)^ z__tiCPR*MIr*~RT@2;wgBXF{Xbl#)J`wB8RZkvj3n%3Sht^HB^lBs3c)PnnV*XfN* z72B38w&8=jzo~aDmAjV9T}ktvVPuak<+Ls5v|Z0>OY5|`o#>XucGJ>)!_s`Ku=>2` zjqbY)T?}nW7vocVYw;U<_vE)P`DMFbK&6J(73;0sJFYx6*?5!FZau?aBRji=Wh0R zyPG_7&o~En^oN6uDnG=HyXxKH4zSIg0KE;|Xnnn>tDoqcb%RdM>uYxRl6!1E;txk= ziBfLYr`AKcp181yywOwaUh9oXvZm&am532)~Cai*uKDem#dShZ!~KOSibsxouFX-IOFE5LZ4G)f4FOrQM8 zTzabIOv@sB%V@c2+;GFV;Z{yznx@RNy0n(cbKIqvysZ1wJYbq)3||8OVkv`hF}%cD zms#ueM?bZ&?R60#P9lSIBXa(nhD{soBigny1omG)4o_U??CdEkvR;o(Lr3 zatu#}&!bUnij<*N?1*f3r}TmNEC|Mw%OIzEmSU9Y=%+gA0ow{k?zAMfD}Mwz5|Q{X z0n-dZ=oV#IP_N`KA`UcvSce>G>a+2#%D?~Q+h=nrl(%3c*KHwiztl^mO+T%4E`TvB4INC 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 de99de032e453bc75d5953d82822bf6bb373fbfa..651c30ae79b68907a86cc598f89833d030fccfc9 100644 GIT binary patch literal 36615 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 G0sKEB!Rs^t delta 5739 zcmc&&Yj6|S6~4PG$BsVjDja{J@xZ0A;O}LBW>H-8BYA zs%Xnh428y-n>0)_N&KTjph+jrKwGAhwoLk?X?}FX1g0LQuX9&^ zAPkwL&9v*)ckeyt+|ZA3oiO?q-2!~P?YG3*mWS(tD5M}zoeP{hIzj*JL=HE$Yd?{06~Dexu%9U+SAp(gK8z)&4r>i zP)qMt%^7@DIXM~>o))Uwda5#7Gb~Y-gNk26aF`0TtX{pkxp_`#XxCkRMoo*n}&9OM4f#9eTiiA^EC8CJKemNM3 zge5tp4-7}-V9FGdBSR4kYdYf*EPaG&2vWwOpb|I|2?w!w65ku3y;uJ3{B3ArLXe5}j&Uq{i z`k@O6HFZ~~_+w+RLtv?e!l@h_l*fh@Ib{Jsx(57o>!dgw8NYg~DVNcYfZCH+|Fq_B zW*?3I{NsYkci{K=MmQZ_bZbPt-(sWO>2ZZ4%%LI! zgFu2Kf81MQW@TXD9Z}sBOYY`WFbs*XZnA~5fl!pq5NVamRLM)VJR4=nRLe3}F_OQw z7Bf2XW{YdCVbV*wm;naU!(dKwt}UO_O&RFGY%GVQay{`EXiT*yW{l=+MU&h?CT5D7 zB)xl{fnt2rC>c(HQ!%qrus}vpeUz7S-Zfr=S$l`hoc6>lOJqz-WUO;CWm*?)AbyFE zO(`Ukxy7Bik~x;AEZT#jX3ZezdkF&i&cYmk`Ru`0&$*T2j42c++(V3HIb)1k$j>{< z3++pIR^VARo?{8m20WX_%U{CF1zs-YnVnKztiXik!@N9N5arLhhuCc6VA6C`Hd0pKT;j&nvvJg*%Bzu&99Z8O8;p^xP-6hz8@4zqfFlZlLVujx< zJpbEFInIou6Wl&D&P10@GV=ATk@BPbCAQ+>n1NY#8Al!RXf$6AOU{^U-jBSfODUh% zgOE~5moQOx=ElLhXr5$%KeP5XUuvzW6C0yWsbJB^Fe91gsLQgcLykvdlx1z9=C|vt zJKOKo)F=i`GT}8@;%@3(wr^j}pQ5N8Y`06r*;WFT2kP=g>L_@md1c2!HU}?L=+Qj) z0Iy`Rmt@E`FU(~|iA#p40~9-!D0YBi$72;cKyeo55yg%#qd1xmTJxp&qyFZD+Wg0= z%?GtvoJZ8=e<`((tdKZDbHOx2z)Bwu*eM7k@C>KS2z{MPh|UkJd|1SwAs5B+CfW0^ zO>*bqWS+{W=VLX4!c|K)X3--Xvn*OjruP;U!uAUrAKiZc@riC>v%2=fhWt_=gE)i6 z;V-lKeL^o@30~rQ3JnzKzQINcPf-vkGy$a8?L8^BuM5+IG_Y{AK!YAaRO0hB0IxEc zFIp5Gbi|k_Bca8aI?Enq(H0PT&lY8Nqt_S)ozJJa?iE)1YWL!`z`VlYbx`1D3iQJx zQw4%>TFYMWdeVQWBEA(2kQ;$M`ziVqvLMyj3GcHg{C*Y@4;+*;eAuI-rY2UvVH z8B8;j|H6Lg{hqBZ+kT9Jsu_=Es6Ec$4lwxhhYhamRx7(NmtQZx-tmF&hH`7;Hg)5+ zsTP36SCjca8Qcj5<1y;j*AcL<5--qss)X!5RI=%rC9~YBI$L3undueROxJhbYG_d# zT40v7at@tT2K$IAPFHQFV=dnbxvmh7C_?Z=NLFM4a!I)MK#C8^y|@lm7v!oXkgL8= zk_~QhXV6F*0y*SR;AiCRu$44P&k{?pi&QsOlMfGZ z_)H=T>8q0B@hH7n`HzktIpxr735De#R)Ugd3Eo7FbZLyo7#R997>GB3OsZtzCKpgb z$AkW1aS&2+nyva@5JysF3+(1BelTz(914WQ@Iw+Afgv$8Jcfh*P*@4#jc^}@?G!*&A;SA;^$-O=h36@VqzemeDLW3*yqtPJpMU3QSDDd~RHhO+ zlCo$N?Q$cA@ep)RSr__|Kj1QwD>vGX&|$Unr3h*yG3X{gRr2F!q2ZwGPRdz0U1sbB zr=Lx;2XuPxCsk_`Rn5t&=7iBR!K+TsjJZHHm#F29YWaqbEq2whDq(3zS{hVK!;Hmo zUos+-O|^9`y1{Pfr+ToSxgx+FQ=rUu;yJ zwFzfO(%CU#O54cyj&JBp8<4X&4TqDd^sa^zZ8L>M7dO5VpXj>dZJcgNc-N|yiu(ps zzGf!ReR2I|Db2Cvwz~-69tE)HzL8uT_YuR16>*WX!ax)!b+nUrMmt|KC$TCtLM7qs zOgcMnI`6_b4myNm(h8zwm2mVeyqCwJnyBnfR(9W%5~Tyl(t%0y$E6hu0#JF3%t{cd zteVkwJ8XD(rz`*oXKT{gdSff7^iY)^hngp(c~Wxn?B;gP-phRKbgM<{uU99WZAoXF z>TJ7HT%AU(TtP204dKx$H=diTq;=NnRB^= zAn9pUJ*{_qHEH7$=uO2XrFZqE?xK4LAWh-^X5=naOS`An1HgMj%9IILPtw(+x_Y4U ziK|2{rIxzdlCCz@)s{9;YYdijL&h8-o^~Q{HPyRTvuCPDUA;Z&=~q4d)E*n9oAln= znd-Ibx?RcY-D$-5R?gJc&4Ld#wdqnS;I?{eL9}keOx?D799vhDwwipae$34PrJ)}v zE9*geW&K@~-dA%E0i-o>p!)c#%l+C&cZ6~pm8Ogp+th~Mq^D2y^vy+Oj{cn5xIO9Z zSG~K{UAt!~uZZ?3FlP*CR-1PxYxktBIT`iMU#Q-O>Q}|*)YAz3uXR(^aT>%Y&3e|X z3?w}})jfL?p1rDPFL-{}ZYr-xyPmLM_Hki`E}3HtT{9HZ8s3+AJbI%zd%t z<-?QQ%iP_z9J)KxT{*~Nm^zXZW0(wFS@Ub3Q_PpnRm) z1LY&Wvltqxm)WrfsVn$?o>Lq0`t@7_>4B2a>w%KsIjG-S)3Rb`5$9vHq}ErIGLFjR zcjsG}9#VK=J+qB$zu;_M{F2fC-(dPR$Ru_?TBA4c2gXe!5ov5VxEX&Rn((PW@*tVI tFpIY_Oi3Co!2_&;(ib3aH_N~tdI+)ZkdE 0: + truncate_query = "TRUNCATE TABLE lcc_equipment_tr_data" + cursor_db_app.execute(truncate_query) + + query_main = "SELECT DISTINCT(assetnum) FROM ms_equipment_master" + cursor_db_app.execute(query_main) + results = cursor_db_app.fetchall() + + if not results: + print("No assetnum found in ms_equipment_master") + return + + print(f"Found {len(results)} assetnum entries to process.") + + current_year = datetime.now().year + + for row in results: + asset_start = datetime.now() + assetnum = row["assetnum"] + + data_corrective_maintenance = get_recursive_query( + cursor_production, assetnum, worktype="CM" + ) + print(data_corrective_maintenance) + start_year = 2015 + end_year = 2056 + seq = 0 + for year in range(start_year, end_year): + # corrective_row = next( + # (r for r in data_corrective_maintenance if r["tahun"] == year), None + # ) + corrective_row = next( + (r for r in data_corrective_maintenance), None + ) + # if corrective_row: + insert_query = """ + INSERT INTO lcc_equipment_tr_data ( + id, assetnum, tahun, seq, is_actual, + raw_cm_material_cost, + raw_cm_labor_time, rc_cm_material_cost + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + """ + try: + # find corrective_row for the specific year (may be None) + # don't filter by tahun — take rows sequentially for each year in the range + corrective_row = ( + data_corrective_maintenance[seq] + if isinstance(data_corrective_maintenance, (list, tuple)) and seq < len(data_corrective_maintenance) + else None + ) + raw_cm_material_cost = ( + corrective_row["raw_corrective_material_cost"] + if corrective_row and corrective_row.get("raw_corrective_material_cost") is not None + else 0 + ) + raw_cm_labor_time = ( + corrective_row["man_hour_peryear"] + if corrective_row and corrective_row.get("man_hour_peryear") is not None + else 0 + ) + rc_cm_material_cost = raw_cm_material_cost + + cursor_db_app.execute( + insert_query, + ( + str(uuid4()), # id + assetnum, # assetnum + year, # tahun + seq, # seq + (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 + ), + ) + # commit per successful insert to allow continuing on later errors + connection.commit() + inserted_count += 1 + finished_data.append({"assetnum": assetnum, "year": year}) + print(f"Corrective data inserted for {assetnum} in year {year}") + except Exception as e: + # rollback the failed statement so the transaction is usable again + try: + connection.rollback() + except Exception: + pass + error_count += 1 + errors.append({"assetnum": assetnum, "year": year, "error": str(e)}) + print(f"Error inserting {assetnum} year {year}: {e}") + seq += 1 + + asset_elapsed = datetime.now() - asset_start + print(f"Processed asset {assetnum} in {asset_elapsed.total_seconds():.2f}s") + + # final commit for safety (no-op if nothing pending) + try: + connection.commit() + except Exception: + pass + except Exception as e: + print("Error saat menjalankan insert_lcca_maximo_corrective_data:", e) + try: + connection.rollback() + except Exception: + pass + finally: + total_elapsed = None + try: + total_elapsed = datetime.now() - start_time + except Exception: + pass + print("========Process finished and connection closed.========") + print(f"Inserted rows: {inserted_count}, Errors: {error_count}") + if total_elapsed is not None: + print(f"Total elapsed time: {total_elapsed.total_seconds():.2f}s") + if errors: + print(f"Sample error: {errors[0]}") + if connection or connection_wo_db or production_connection: + cursor_db_app.close() + cursor_wo.close() + cursor_production.close() + connection.close() + connection_wo_db.close() + production_connection.close() + -async def query_data(RELIABILITY_APP_URL: str, token: str): +async def query_data(): connection = None + connection_wo_db = None + connection_production_wo = None try: # Mendapatkan koneksi dari config.py connection, connection_wo_db = get_connection() - if connection is None or connection_wo_db is None: + connection_production_wo = get_production_connection() + if connection is None or connection_wo_db is None or connection_production_wo is None: print("Database connection failed.") return # Membuat cursor menggunakan DictCursor cursor = connection.cursor(cursor_factory=DictCursor) - cursor_wo = connection_wo_db.cursor(cursor_factory=DictCursor) + cursor_wo = connection_production_wo.cursor(cursor_factory=DictCursor) # TRUNCATE DATA - # truncate_query = "TRUNCATE TABLE lcc_equipment_tr_data" - # cursor.execute(truncate_query) + truncate_query = "TRUNCATE TABLE lcc_equipment_tr_data RESTART IDENTITY" + cursor.execute(truncate_query) # Query untuk mendapatkan semua data dari tabel `lcc_ms_equipment_data` - query_main = "SELECT * FROM lcc_ms_equipment_data" + # query_main = "SELECT * FROM lcc_ms_equipment_data" + query_main = "SELECT DISTINCT(assetnum) FROM ms_equipment_master" cursor.execute(query_main) # Fetch semua hasil query @@ -114,17 +389,32 @@ async def query_data(RELIABILITY_APP_URL: str, token: str): # Tahun sekarang current_year = datetime.now().year + total_assets = len(results) + processed_assets = 0 + total_inserted = 0 + overall_start = datetime.now() + print(f"Starting processing {total_assets} assets at {overall_start.isoformat()}") + # Looping untuk setiap assetnum - for row in results: + for idx, row in enumerate(results, start=1): assetnum = row["assetnum"] # Mengambil assetnum dari hasil query - forecasting_start_year = row["forecasting_start_year"] - 1 + if not assetnum or str(assetnum).strip() == "": + print(f"[{idx}/{total_assets}] Skipping empty assetnum") + continue + # forecasting_start_year = row["forecasting_start_year"] - 1 + forecasting_start_year = 2014 + + asset_start = datetime.now() + processed_assets += 1 + years_processed = 0 + inserted_this_asset = 0 # CM - recursive_results = get_recursive_query( - cursor_wo, assetnum, worktype="CM" - ) + data_cm = get_recursive_query(cursor_wo, assetnum, worktype="CM") # PM data_pm = get_recursive_query(cursor_wo, assetnum, worktype="PM") + # PDM = Predictive Maintenance + data_predictive = get_recursive_query(cursor_wo, assetnum, worktype="PDM") # OH data_oh = get_recursive_query(cursor_wo, assetnum, worktype="OH") # Data Tahun @@ -133,15 +423,28 @@ async def query_data(RELIABILITY_APP_URL: str, token: str): seq = 0 # Looping untuk setiap tahun for year in range(forecasting_start_year, current_year + 1): + years_processed += 1 # print(f"Processing assetnum {assetnum} in year {year}") - # Filter data berdasarkan tahun - recursive_row = next( - (r for r in recursive_results if r["year"] == year), None + # Filter data berdasarkan tahun (support both 'tahun' and 'year' column names) + data_cm_row = next( + (r for r in data_cm if (r.get("tahun") == year or r.get("year") == year)), + None, ) # CM Corrective Maintenance - data_pm_row = next((r for r in data_pm if r["year"] == year), None) - data_oh_row = next((r for r in data_oh if r["year"] == year), None) + data_pm_row = next( + (r for r in data_pm if (r.get("tahun") == year or r.get("year") == year)), + None, + ) + data_oh_row = next( + (r for r in data_oh if (r.get("tahun") == year or r.get("year") == year)), + None, + ) + data_predictive_row = next( + (r for r in data_predictive if (r.get("tahun") == year or r.get("year") == year)), + None, + ) data_tahunan_row = next( - (r for r in data_tahunan if r["year"] == year), None + (r for r in data_tahunan if (r.get("tahun") == year or r.get("year") == year)), + None, ) # Cek apakah data sudah ada @@ -159,28 +462,36 @@ async def query_data(RELIABILITY_APP_URL: str, token: str): continue if not data_exists: - print("Data not exists for assetnum", assetnum) # Insert data jika belum ada - if not recursive_row and not data_pm_row and not data_oh_row: + if not data_cm_row and not data_pm_row and not data_oh_row and not data_predictive_row: # Jika data recursive_row tidak ada insert_query = """ INSERT INTO lcc_equipment_tr_data ( id, assetnum, tahun, seq, is_actual, - raw_cm_interval, raw_cm_material_cost, - raw_cm_labor_time, raw_cm_labor_human + raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human , raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human - , raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human + , raw_oh_interval, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human + , raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human , "raw_loss_output_MW", raw_loss_output_price - ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s - , %s, %s, %s, %s - , %s, %s, %s - , %s, %s + , rc_cm_material_cost, rc_cm_labor_cost + , rc_pm_material_cost, rc_pm_labor_cost + , rc_oh_material_cost, rc_oh_labor_cost + , rc_predictive_labor_cost + , created_by, created_at + ) VALUES ( + %s, %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s + , %s, %s + , %s, %s + , %s, %s + , %s + , 'Sys', NOW() ) """ - api_data = await fetch_api_data( - assetnum, year, RELIABILITY_APP_URL, token - ) - print(api_data) cursor.execute( insert_query, ( @@ -189,62 +500,112 @@ async def query_data(RELIABILITY_APP_URL: str, token: str): year, # tahun seq, # seq 1, # is_actual - ( - api_data["data"][0]["actual_fail"] - if api_data - else 1 - ), # raw_cm_interval (minimal 1 karena minimal 1x OH) + 1, # raw_cm_interval (minimal 1 karena minimal 1x OH) 0, # raw_cm_material_cost 0, # raw_cm_labor_time 0, # raw_cm_labor_human 1, # pm interval set default 1 - 0, - 0, - 0, - 0, - 0, - 0, - ( - data_tahunan_row["total_lost"] + 0, # raw_pm_material_cost + 0, # raw_pm_labor_time + 0, # raw_pm_labor_human + 0, # raw_oh_interval set default 1 + 0, # raw_oh_material_cost + 0, # raw_oh_labor_time + 0, # raw_oh_labor_human + 0, # raw_predictive_interval set default 1 + 0, # raw_predictive_material_cost + 0, # raw_predictive_labor_time + 0, # raw_predictive_labor_human + ( # "raw_loss_output_MW" + # data_tahunan_row["total_lost"] + 0 if data_tahunan_row else 0 ), - ( - data_tahunan_row["rp_per_kwh"] + ( # raw_loss_output_price + # data_tahunan_row["rp_per_kwh"] + 0 if data_tahunan_row else 0 ), + 0, # rc_cm_material_cost + 0, # rc_cm_labor_cost + 0, # rc_pm_material_cost + 0, # rc_pm_labor_cost + 0, # rc_oh_material_cost + 0, # rc_oh_labor_cost + 0, # rc_predictive_labor_cost ), ) - print(f"Data inserted for {assetnum} in year {year}") + inserted_this_asset += 1 + total_inserted += 1 + # print minimal per-year insert log + # print(f"Inserted default data for {assetnum} year {year}") else: - print("Data exists for assetnum", assetnum) # Jika data recursive_row ada - # raw_cm_interval ambil dari reliability predict insert_query = """ INSERT INTO lcc_equipment_tr_data ( id, assetnum, tahun, seq, is_actual, - raw_cm_interval, raw_cm_material_cost, - raw_cm_labor_time, raw_cm_labor_human + raw_cm_interval, raw_cm_material_cost, raw_cm_labor_time, raw_cm_labor_human , raw_pm_interval, raw_pm_material_cost, raw_pm_labor_time, raw_pm_labor_human - , raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human + , raw_oh_interval, raw_oh_material_cost, raw_oh_labor_time, raw_oh_labor_human + , raw_predictive_interval, raw_predictive_material_cost, raw_predictive_labor_time, raw_predictive_labor_human , "raw_loss_output_MW", raw_loss_output_price - ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s - , %s, %s, %s, %s - , %s, %s, %s - , %s, %s + , "rc_cm_material_cost", "rc_cm_labor_cost" + , "rc_pm_material_cost", "rc_pm_labor_cost" + , "rc_oh_material_cost", "rc_oh_labor_cost" + , "rc_predictive_labor_cost" + , created_by, created_at + ) VALUES ( + %s, %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, %s, %s, + %s, %s, + %s, %s, + %s, %s, + %s, %s, + %s, + 'Sys', NOW() ) """ - api_data = await fetch_api_data( - assetnum, year, RELIABILITY_APP_URL, token - ) - print(api_data) - if api_data and "data" in api_data and api_data["data"]: - print("API data:", api_data["data"][0]["actual_fail"]) - else: - print( - f"No API data available for {assetnum} in year {year}" - ) + # Normalize row values to avoid inserting NULL and avoid division by zero + raw_cm_interval = data_cm_row.get("raw_cm_interval") if data_cm_row and data_cm_row.get("raw_cm_interval") is not None else 0 + raw_cm_material_cost = data_cm_row.get("raw_cm_material_cost") if data_cm_row and data_cm_row.get("raw_cm_material_cost") is not None else 0 + avg_cm_material_cost = (raw_cm_material_cost / raw_cm_interval) if raw_cm_interval else 0 + raw_cm_labor_time = data_cm_row.get("raw_cm_labor_time") if data_cm_row and data_cm_row.get("raw_cm_labor_time") is not None else 0 + raw_cm_labor_human = data_cm_row.get("raw_cm_labor_human") if data_cm_row and data_cm_row.get("raw_cm_labor_human") is not None else 0 + + raw_pm_interval = data_pm_row.get("raw_pm_interval") if data_pm_row and data_pm_row.get("raw_pm_interval") is not None else 0 + raw_pm_material_cost = data_pm_row.get("raw_pm_material_cost") if data_pm_row and data_pm_row.get("raw_pm_material_cost") is not None else 0 + raw_pm_labor_time = data_pm_row.get("raw_pm_labor_time") if data_pm_row and data_pm_row.get("raw_pm_labor_time") is not None else 0 + raw_pm_labor_human = data_pm_row.get("raw_pm_labor_human") if data_pm_row and data_pm_row.get("raw_pm_labor_human") is not None else 0 + + raw_oh_interval = data_oh_row.get("raw_oh_interval") if data_oh_row and data_oh_row.get("raw_oh_interval") is not None else 0 + raw_oh_material_cost = data_oh_row.get("raw_oh_material_cost") if data_oh_row and data_oh_row.get("raw_oh_material_cost") is not None else 0 + raw_oh_labor_time = data_oh_row.get("raw_oh_labor_time") if data_oh_row and data_oh_row.get("raw_oh_labor_time") is not None else 0 + raw_oh_labor_human = data_oh_row.get("raw_oh_labor_human") if data_oh_row and data_oh_row.get("raw_oh_labor_human") is not None else 0 + + raw_pdm_interval = data_predictive_row.get("raw_predictive_interval") if data_predictive_row and data_predictive_row.get("raw_predictive_interval") is not None else 0 + raw_pdm_material_cost = data_predictive_row.get("raw_predictive_material_cost") if data_predictive_row and data_predictive_row.get("raw_predictive_material_cost") is not None else 0 + raw_pdm_labor_time = data_predictive_row.get("raw_predictive_labor_time") if data_predictive_row and data_predictive_row.get("raw_predictive_labor_time") is not None else 0 + raw_pdm_labor_human = data_predictive_row.get("raw_predictive_labor_human") if data_predictive_row and data_predictive_row.get("raw_predictive_labor_human") is not None else 0 + + raw_loss_output_MW = data_tahunan_row.get("total_lost") if data_tahunan_row and data_tahunan_row.get("total_lost") is not None else 0 + raw_loss_output_price = data_tahunan_row.get("rp_per_kwh") if data_tahunan_row and data_tahunan_row.get("rp_per_kwh") is not None else 0 + + rc_cm_material_cost = data_cm_row.get("raw_cm_material_cost") if data_cm_row and data_cm_row.get("raw_cm_material_cost") is not None else 0 + rc_cm_labor_cost = data_cm_row.get("raw_cm_labor_time")*data_cm_row.get("rc_cm_labor_human")*data_tahunan_row.get("man_hour") if data_cm_row and data_cm_row.get("rc_cm_labor_cost") and data_cm_row.get("rc_cm_labor_human") and data_tahunan_row.get("man_hour") is not None else 0 + + rc_pm_material_cost = data_pm_row.get("raw_pm_material_cost") if data_pm_row and data_pm_row.get("raw_pm_material_cost") is not None else 0 + rc_pm_labor_cost = data_pm_row.get("raw_pm_labor_time")*data_pm_row.get("rc_pm_labor_human")*data_tahunan_row.get("man_hour") if data_pm_row and data_pm_row.get("rc_pm_labor_cost") and data_pm_row.get("rc_pm_labor_human") and data_tahunan_row.get("man_hour") is not None else 0 + + rc_oh_material_cost = data_oh_row.get("raw_oh_material_cost") if data_oh_row and data_oh_row.get("raw_oh_material_cost") is not None else 0 + rc_oh_labor_cost = data_oh_row.get("raw_oh_labor_time")*data_oh_row.get("rc_oh_labor_human")*data_tahunan_row.get("man_hour") if data_oh_row and data_oh_row.get("rc_oh_labor_cost") and data_oh_row.get("rc_oh_labor_human") and data_tahunan_row.get("man_hour") is not None else 0 + + rc_predictive_labor_cost = data_predictive_row.get("raw_predictive_labor_human")*data_tahunan_row.get("man_hour") if data_predictive_row and data_predictive_row.get("rc_predictive_labor_cost") and data_tahunan_row.get("man_hour") is not None else 0 + cursor.execute( insert_query, ( @@ -253,117 +614,74 @@ async def query_data(RELIABILITY_APP_URL: str, token: str): year, # tahun seq, # seq 1, # is_actual - ( - api_data["data"][0]["actual_fail"] - if api_data - else ( - recursive_row["raw_corrective_failure_interval"] - + 1 - if recursive_row - else 1 - ) - ), # raw_cm_interval nanti ambil dari API reliability predict - ( - recursive_row["raw_corrective_material_cost"] - if recursive_row - else 0 - ), # raw_cm_material_cost - ( - ( - recursive_row["raw_corrective_labor_time_jam"] - or 0 - ) - if recursive_row - else 0 - ), # raw_cm_labor_time - ( - ( - max( - recursive_row[ - "raw_corrective_labor_technician" - ], - 1, - ) - if recursive_row[ - "raw_corrective_labor_time_jam" - ] - else 0 - ) - if recursive_row - else 0 - ), # raw_cm_labor_human - 1, # raw_pm_interval - ( - data_pm_row["raw_corrective_material_cost"] - if data_pm_row - else 0 - ), # raw_pm_material_cost - ( - (data_pm_row["raw_corrective_labor_time_jam"] or 0) - if data_pm_row - else 0 - ), # raw_pm_labor_time - ( - ( - max( - data_pm_row[ - "raw_corrective_labor_technician" - ], - 1, - ) - if data_pm_row["raw_corrective_labor_time_jam"] - else 0 - ) - if data_pm_row - else 0 - ), # raw_pm_labor_human - ( - data_oh_row["raw_corrective_material_cost"] - if data_oh_row - else 0 - ), # raw_oh_material_cost - ( - (data_oh_row["raw_corrective_labor_time_jam"] or 0) - if data_oh_row - else 0 - ), # raw_oh_labor_time - ( - ( - max( - data_oh_row[ - "raw_corrective_labor_technician" - ], - 1, - ) - if data_oh_row["raw_corrective_labor_time_jam"] - else 0 - ) - if data_oh_row - else 0 - ), # raw_oh_labor_human - ( - data_tahunan_row["total_lost"] - if data_tahunan_row - else 0 - ) - / ( - recursive_row["raw_corrective_failure_interval"] + 1 - if recursive_row - else 1 - ), # raw_loss_output_MW - ( - data_tahunan_row["rp_per_kwh"] - if data_tahunan_row - else 0 - ), + raw_cm_interval, # raw_cm_interval + avg_cm_material_cost, # avg raw_cm_material_cost per interval + raw_cm_labor_time, # raw_cm_labor_time + raw_cm_labor_human, # raw_cm_labor_human + raw_pm_interval, # raw_pm_interval + raw_pm_material_cost, # raw_pm_material_cost + raw_pm_labor_time, # raw_pm_labor_time + raw_pm_labor_human, + raw_oh_interval, + raw_oh_material_cost, + raw_oh_labor_time, + raw_oh_labor_human, + raw_pdm_interval, + raw_pdm_material_cost, + raw_pdm_labor_time, + raw_pdm_labor_human, + raw_loss_output_MW, + raw_loss_output_price, + rc_cm_material_cost, + rc_cm_labor_cost, + rc_pm_material_cost, + rc_pm_labor_cost, + rc_oh_material_cost, + rc_oh_labor_cost, + rc_predictive_labor_cost, ), ) - print(f"Data inserted for {assetnum} in year {year}") + inserted_this_asset += 1 + total_inserted += 1 seq = seq + 1 + # commit per asset to persist progress and free transaction + try: + connection.commit() + except Exception: + try: + connection.rollback() + except Exception: + pass + + asset_elapsed = datetime.now() - asset_start + total_elapsed = datetime.now() - overall_start + pct_assets = (idx / total_assets) * 100 if total_assets else 100 + # progress per asset + print( + f"[{idx}/{total_assets}] Asset {assetnum} processed. " + f"Inserted this asset: {inserted_this_asset}. " + f"Asset time: {asset_elapsed.total_seconds():.2f}s. " + f"Total inserted: {total_inserted}. " + f"Overall elapsed: {total_elapsed.total_seconds():.2f}s. " + f"Progress: {pct_assets:.1f}%" + ) + + # periodic summary every 10 assets + if idx % 10 == 0 or idx == total_assets: + print(f"SUMMARY: {idx}/{total_assets} assets processed, {total_inserted} rows inserted, elapsed {total_elapsed.total_seconds():.1f}s") + # Commit perubahan - connection.commit() + try: + connection.commit() + except Exception: + try: + connection.rollback() + except Exception: + pass + + print(f"Finished processing all assets. Total assets: {total_assets}, total inserted: {total_inserted}, total time: {(datetime.now()-overall_start).total_seconds():.2f}s") except Exception as e: print("Error saat menjalankan query:", e) @@ -372,8 +690,20 @@ async def query_data(RELIABILITY_APP_URL: str, token: str): # Menutup koneksi print("========Process finished and connection closed.========") if connection or connection_wo_db: - cursor.close() - cursor_wo.close() - connection.close() - connection_wo_db.close() + try: + cursor.close() + except Exception: + pass + try: + cursor_wo.close() + except Exception: + pass + try: + connection.close() + except Exception: + pass + try: + connection_wo_db.close() + except Exception: + pass # print("========Process finished and connection closed.========") diff --git a/src/modules/equipment/run.py b/src/modules/equipment/run.py index 9facc14..244b631 100644 --- a/src/modules/equipment/run.py +++ b/src/modules/equipment/run.py @@ -1,47 +1,71 @@ -from .insert_actual_data import query_data -from .Prediksi import Prediksi -from .Eac import Eac import asyncio import time +# prefer package-relative imports, but allow running this file directly as a script +try: + from .insert_actual_data import query_data, insert_lcca_maximo_corrective_data, insert_ms_equipment_data + from .Prediksi import Prediksi + from .Eac import Eac +except ImportError: + # fallback when there's no parent package (e.g., python run.py) + from insert_actual_data import query_data, insert_lcca_maximo_corrective_data, insert_ms_equipment_data + from Prediksi import Prediksi + from Eac import Eac + # Panggil fungsi -async def main(assetnum, token, RELIABILITY_APP_URL): +async def main(): start_time = time.time() try: - await query_data(RELIABILITY_APP_URL, token) + await query_data() except Exception as e: print(f"Error in query_data: {str(e)}") return try: - prediksi = Prediksi(RELIABILITY_APP_URL) - await prediksi.predict_equipment_data( - assetnum, - token=token, - ) + prediksi = Prediksi() + await prediksi.main() except Exception as e: print(f"Error in predict_equipment_data: {str(e)}") return - try: - eac = Eac() - eac.hitung_eac_equipment(assetnum) - except Exception as e: - print(f"Error in hitung_eac_equipment: {str(e)}") - return + # try: + # eac = Eac() + # eac.hitung_eac_equipment(assetnum) + # except Exception as e: + # print(f"Error in hitung_eac_equipment: {str(e)}") + # return end_time = time.time() execution_time = end_time - start_time - print(f"EAC calculation finished in {execution_time:.2f} seconds.") - return f"EAC calculation finished in {execution_time:.2f} seconds." + # format execution time into h/m/s as needed + if execution_time >= 3600: + hours = int(execution_time // 3600) + minutes = int((execution_time % 3600) // 60) + seconds = execution_time % 60 + message = f"Insert & Prediction calculation finished in {hours}h {minutes}m {seconds:.2f}s." + elif execution_time >= 60: + minutes = int(execution_time // 60) + seconds = execution_time % 60 + message = f"Insert & Prediction calculation finished in {minutes}m {seconds:.2f}s." + else: + message = f"Insert & Prediction calculation finished in {execution_time:.2f} seconds." + + print(message) + return message + # print(f"EAC calculation finished in {execution_time:.2f} seconds.") + # return f"EAC calculation finished in {execution_time:.2f} seconds." +# if __name__ == "__main__": +# asyncio.run( +# main( +# "A22277", +# "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczOTUxODc4Ni4yOTM5ODUsImp0aSI6Ilo5clRUOFhGa3RweFZUQlBmNGxvRmciLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiNWUxNmY4YTgtMWEwMy00MTVjLWIwZjItMTVmZjczOWY1OGE4IiwibmJmIjoxNzM5NTE4Nzg2LCJjc3JmIjoiZWI0MjAzOTMtYTg1ZS00NDJjLWIyMjItZTU5MGU5MGVkYjkyIiwiZXhwIjoxNzM5NjA1MTg2LCJub25jZSI6IjVkZDdhOGYyMWIzZWUxZDZmYmI1YThhMDBlMmYyYjczIn0.3Jv943cU5FuxJ9K92JmVoOtTBqexF4Dke8TrrC4l0Uk", +# ) +# ) if __name__ == "__main__": asyncio.run( - main( - "A22277", - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTczOTUxODc4Ni4yOTM5ODUsImp0aSI6Ilo5clRUOFhGa3RweFZUQlBmNGxvRmciLCJ0eXBlIjoiYWNjZXNzIiwic3ViIjoiNWUxNmY4YTgtMWEwMy00MTVjLWIwZjItMTVmZjczOWY1OGE4IiwibmJmIjoxNzM5NTE4Nzg2LCJjc3JmIjoiZWI0MjAzOTMtYTg1ZS00NDJjLWIyMjItZTU5MGU5MGVkYjkyIiwiZXhwIjoxNzM5NjA1MTg2LCJub25jZSI6IjVkZDdhOGYyMWIzZWUxZDZmYmI1YThhMDBlMmYyYjczIn0.3Jv943cU5FuxJ9K92JmVoOtTBqexF4Dke8TrrC4l0Uk", - ) + main() )