From 9f9238c088f0110ed6f8049614824cc2edfcf9a7 Mon Sep 17 00:00:00 2001 From: MrWaradana Date: Mon, 2 Feb 2026 13:08:35 +0700 Subject: [PATCH] remove the acquisition_year_ref --- src/equipment/model.py | 1 - src/modules/equipment/Eac.py | 118 ++++++------------ src/modules/equipment/Prediksi.py | 16 ++- .../equipment/__pycache__/Eac.cpython-311.pyc | Bin 16823 -> 15665 bytes .../__pycache__/Prediksi.cpython-311.pyc | Bin 50135 -> 53849 bytes src/modules/equipment/insert_actual_data.py | 19 ++- 6 files changed, 59 insertions(+), 95 deletions(-) diff --git a/src/equipment/model.py b/src/equipment/model.py index 8152bde..454c16c 100644 --- a/src/equipment/model.py +++ b/src/equipment/model.py @@ -60,7 +60,6 @@ class EquipmentTransactionRecords(Base, DefaultMixin, IdentityMixin): tahun = Column(Integer, nullable=False) seq = Column(Integer, nullable=False) is_actual = Column(Integer, nullable=False) - acquisition_year_ref = Column(Integer, nullable=True) raw_cm_interval = Column(Float, nullable=False) raw_cm_material_cost = Column(Float, nullable=False) raw_cm_labor_time = Column(Float, nullable=False) diff --git a/src/modules/equipment/Eac.py b/src/modules/equipment/Eac.py index 030804f..0ad48ee 100644 --- a/src/modules/equipment/Eac.py +++ b/src/modules/equipment/Eac.py @@ -18,12 +18,9 @@ class Eac: try: # Mendapatkan koneksi dari config.py connections = get_connection() - if isinstance(connections, tuple): - connection, connection_wo_db = connections - else: - connection = connections - connection_wo_db = None - + connection = ( + connections[0] if isinstance(connections, tuple) else connections + ) if connection is None: print("Database connection failed.") return None @@ -31,15 +28,15 @@ class Eac: # Membuat cursor menggunakan DictCursor cursor = connection.cursor(cursor_factory=DictCursor) - # Query untuk mendapatkan data2 dasar dari LCCA DB + # Query untuk mendapatkan data2 dasar query_inflation_rate = """ select (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'inflation_rate') as inflation_rate , (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'discount_rate') as discount_rate - , (SELECT acquisition_cost FROM lcc_ms_equipment_data WHERE assetnum = %s) as rc_total_cost_0 + , (select COALESCE(rc_total_cost,0) from lcc_equipment_tr_data ltd where assetnum = %s and seq = 0) as rc_total_cost_0 , (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'history_inflation_rate') as history_inflation_rate , (SELECT value_num / 100 FROM lcc_ms_master WHERE name = 'history_future_inflation_rate_annual') as history_future_inflation_rate_annual - , (SELECT acquisition_year FROM lcc_ms_equipment_data WHERE assetnum = %s) as acquisition_year_fallback + , (select COALESCE(forecasting_target_year, 2056) from lcc_ms_equipment_data where assetnum = %s) as forecasting_target_year ; """ cursor.execute(query_inflation_rate, (equipment_id, equipment_id)) @@ -48,36 +45,13 @@ class Eac: if not inflation_rate_result: print("Inflation rate tidak ditemukan.") return None - - # Fetch acquisition year from Maximo/Production DB if available - acquisition_year_maximo = None - if connection_wo_db: - try: - cursor_wo = connection_wo_db.cursor() - query_maximo = """ - SELECT CAST(DATE_PART('year', max(reportdate)) AS INTEGER) - FROM wo_maximo wm - WHERE wm.asset_assetnum = %s AND wm.asset_replacecost > 0 - GROUP BY CAST(DATE_PART('year', reportdate) AS INTEGER) - ORDER BY CAST(DATE_PART('year', reportdate) AS INTEGER) ASC - LIMIT 1 - """ - cursor_wo.execute(query_maximo, (equipment_id,)) - res_wo = cursor_wo.fetchone() - if res_wo and res_wo[0]: - acquisition_year_maximo = res_wo[0] - cursor_wo.close() - except Exception as e: - print(f"Error fetching from maximo DB: {e}") - - # Use Maximo year if available, else fallback - acquisition_year = acquisition_year_maximo if acquisition_year_maximo else inflation_rate_result["acquisition_year_fallback"] + inflation_rate = inflation_rate_result["inflation_rate"] history_inflation_rate = inflation_rate_result["history_inflation_rate"] history_future_inflation_rate = inflation_rate_result["history_future_inflation_rate_annual"] disc_rate = inflation_rate_result["discount_rate"] rc_total_cost_0 = inflation_rate_result["rc_total_cost_0"] - + forecasting_target_year = inflation_rate_result["forecasting_target_year"] last_seq = 0 last_npv = 0 @@ -85,13 +59,13 @@ class Eac: query_data_actual = """ SELECT assetnum, tahun, seq, is_actual, rc_total_cost FROM lcc_equipment_tr_data - WHERE is_actual = 1 AND seq != 0 AND tahun >= %s + WHERE is_actual = 1 AND seq != 0 AND assetnum = %s ORDER BY seq; """ - cursor.execute(query_data_actual, (acquisition_year, equipment_id)) + cursor.execute(query_data_actual, (equipment_id,)) data_actual = cursor.fetchall() - + # Variabel untuk menyimpan hasil NPV dan PMT per baris npv_results = [] cumulative_values = [] # Menyimpan nilai kumulatif hingga baris ke-n @@ -105,36 +79,24 @@ class Eac: value / ((1 + history_inflation_rate) ** (i + 1)) for i, value in enumerate(cumulative_values) ) - # Recalculate seq based on new acquisition year - current_seq = row["tahun"] - acquisition_year - - if current_seq <= 0: - current_seq = 0 # Avoid negative or zero periods for PMT if year <= acquisition_year - # Menghitung PMT biaya maintenance # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = final_value, r = history_future_inflation_rate, n = current_seq - if current_seq > 0: - pmt_mnt_cost = -npf.pmt(history_future_inflation_rate, current_seq, final_value) - else: - pmt_mnt_cost = 0.0 + # dimana PV = final_value, r = history_future_inflation_rate, n = row["seq"] + pmt_mnt_cost = -npf.pmt(history_future_inflation_rate, row["seq"], final_value) # Menghitung PMT biaya disposal # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = 0.05 * rc_total_cost_0, r = disc_rate, n = current_seq - # rc_total_cost_0 adalah biaya akuisisi awal - eac_disposal_cost = -npf.pmt(disc_rate, current_seq, 0, 0.05 * rc_total_cost_0) if current_seq > 0 else 0.0 + # dimana PV = 0.05 * rc_total_cost_0, r = disc_rate, n = row["seq"] + # rc_total_cost_0 adalah biaya akuisisi awal (seq = 0) + eac_disposal_cost = -npf.pmt(disc_rate, row["seq"], 0, 0.05 * rc_total_cost_0) if row["seq"] > 0 else 0.0 # Menghitung PMT biaya akuisisi # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = rc_total_cost_0, r = disc_rate, n = current_seq - # rc_total_cost_0 adalah biaya akuisisi awal + # dimana PV = rc_total_cost_0, r = disc_rate, n = row["seq"] + # rc_total_cost_0 adalah biaya akuisisi awal (seq = 0) # disc_rate adalah discount rate dari database - # current_seq adalah periode ke-n - if current_seq > 0: - pmt_aq_cost = -npf.pmt(disc_rate, current_seq, rc_total_cost_0) - else: - pmt_aq_cost = 0.0 + # row["seq"] adalah periode ke-n + pmt_aq_cost = -npf.pmt(disc_rate, row["seq"], rc_total_cost_0) eac = pmt_mnt_cost + pmt_aq_cost npv_results.append( @@ -169,31 +131,32 @@ class Eac: ), ) - last_seq = row["seq"] # Keep logical linking if needed, but calculations rely on current_seq + last_seq = row["seq"] last_npv = float(final_value) # Commit perubahan connection.commit() # Query untuk mendapatkan data dengan seq dan is_actual = 0 - # Filter by acquisition_year_ref to match current acquisition_year query_data_proyeksi = """ SELECT assetnum, tahun, seq, is_actual, rc_total_cost FROM lcc_equipment_tr_data - WHERE assetnum = %s AND is_actual = 0 - AND (acquisition_year_ref = %s OR acquisition_year_ref IS NULL) + WHERE assetnum = %s AND is_actual = 0 AND tahun <= %s ORDER BY seq; """ - cursor.execute(query_data_proyeksi, (equipment_id, acquisition_year)) + cursor.execute(query_data_proyeksi, (equipment_id, forecasting_target_year)) data_proyeksi = cursor.fetchall() - cumulative_values = [] # Menghitung NPV dan PMT secara bertahap untuk data proyeksi + # NOTE: sebelumnya kode mencoba menggeser PV proyeksi menggunakan npf.pv + sign flips, + # yang dapat menghasilkan nilai pemeliharaan yang sangat besar (meledak). Sebaiknya hitung + # nilai diskonto dari biaya proyeksi menggunakan offset waktu yang benar (last_seq) dan + # tambahkan ke last_npv. Kemudian hitung pembayaran tahunan tingkat (PMT) selama sisa + # jumlah periode (remaining_periods). Ini menjaga nilai pemeliharaan tahunan proyeksi tetap konsisten dan mencegah lonjakan eksponensial. for idx, row in enumerate(data_proyeksi): # Menyimpan nilai kumulatif hingga baris ke-n cumulative_values.append(row["rc_total_cost"]) - # Nilai proyeksi yang didiskontokan menggunakan offset eksponen dari urutan aktual terakhir # sehingga offset tahun berlanjut dari aktual yang sudah diproses. # Rumus NPV: NPV = Σ [Ct / (1 + r)^t] @@ -208,43 +171,37 @@ class Eac: # Total NPV pada titik proyeksi ini = NPV aktual terakhir + biaya proyeksi yang didiskontokan final_value = float(last_npv) + float(discounted_proj) - # Recalculate seq based on new acquisition year - current_seq = row["tahun"] - acquisition_year - if current_seq <= 0: - current_seq = 1 # Fallback, though for projection it should be > 0 - # Gunakan seq penuh (jumlah periode dari akuisisi) untuk menghitung pembayaran tahunan pemeliharaan. # Menggunakan hanya selisih dari seq aktual terakhir # (sisa_periode) mengamortisasi seluruh nilai sekarang selama # sejumlah periode yang sangat kecil untuk proyeksi pertama dan menghasilkan lonjakan. - # Menggunakan current_seq menjaga periode amortisasi konsisten dengan perhitungan lain + # Menggunakan row["seq"] menjaga periode amortisasi konsisten dengan perhitungan lain # dan mencegah lonjakan setelah tahun berjalan. # amortisasi adalah proses pencatatan biaya aset selama masa manfaatnya. - periods = int(current_seq) + periods = int(row["seq"]) if int(row.get("seq", 0)) > 0 else 1 # Menghitung PMT # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = final_value, r = disc_rate, n = current_seq + # dimana PV = final_value, r = disc_rate, n = row["seq"] # periods adalah jumlah periode # final_value adalah PV pada titik proyeksi periods pmt_mnt_cost = -float(npf.pmt(disc_rate, periods, final_value)) - eac_disposal_cost_proyeksi = -npf.pmt(disc_rate, current_seq, 0, 0.05 * rc_total_cost_0) if current_seq > 0 else 0.0 + eac_disposal_cost_proyeksi = -npf.pmt(disc_rate, row["seq"], 0, 0.05 * rc_total_cost_0) if row["seq"] > 0 else 0.0 # menghitung PMT biaya akuisisi # Rumus PMT: PMT = PV * [r(1 + r)^n] / [(1 + r)^n – 1] - # dimana PV = rc_total_cost_0, r = disc_rate, n = current_seq - # rc_total_cost_0 adalah biaya akuisisi awal + # dimana PV = rc_total_cost_0, r = disc_rate, n = row["seq"] + # rc_total_cost_0 adalah biaya akuisisi awal (seq = 0) # disc_rate adalah discount rate dari database - # current_seq adalah periode ke-n - pmt_aq_cost = -float(npf.pmt(disc_rate, current_seq, rc_total_cost_0)) + # row["seq"] adalah periode ke-n + pmt_aq_cost = -float(npf.pmt(disc_rate, row["seq"], rc_total_cost_0)) eac = float(pmt_mnt_cost) + float(pmt_aq_cost) - npv_results.append( { - "seq": current_seq, + "seq": row["seq"], "year": row["tahun"], "npv": final_value, "pmt": pmt_mnt_cost, @@ -323,7 +280,6 @@ class Eac: } else: positives = [r for r in rslt if float(r.get("eac", 0)) > 0] - if positives: lowest_eac_record = min(positives, key=lambda x: float(x["eac"])) else: diff --git a/src/modules/equipment/Prediksi.py b/src/modules/equipment/Prediksi.py index e897268..92b2a5b 100644 --- a/src/modules/equipment/Prediksi.py +++ b/src/modules/equipment/Prediksi.py @@ -151,7 +151,7 @@ class Prediksi: # connection.close() # Fungsi untuk menyimpan data proyeksi ke database - async def __insert_predictions_to_db(self, data, equipment_id, token, acquisition_year_ref=None): + async def __insert_predictions_to_db(self, data, equipment_id, token): try: connection, connection_wo_db = get_connection() if connection is None: @@ -173,7 +173,6 @@ class Prediksi: id, seq, is_actual, - acquisition_year_ref, tahun, assetnum, rc_cm_material_cost, rc_cm_labor_cost, @@ -185,7 +184,7 @@ class Prediksi: rc_predictive_labor_cost, created_by, created_at ) VALUES ( - %s, %s, 0, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW() + %s, %s, 0, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'Sys', NOW() ) """ @@ -236,7 +235,7 @@ class Prediksi: check_existence_query = """ SELECT id FROM lcc_equipment_tr_data - WHERE assetnum = %s AND tahun = %s AND is_actual = 0 AND (acquisition_year_ref = %s OR (acquisition_year_ref IS NULL AND %s IS NULL)) + WHERE assetnum = %s AND tahun = %s AND is_actual = 0 """ update_query = """ @@ -257,10 +256,10 @@ class Prediksi: for _, row in data.iterrows(): # Check if data exists - cursor.execute(check_existence_query, (equipment_id, int(row["year"]), acquisition_year_ref, acquisition_year_ref)) + cursor.execute(check_existence_query, (equipment_id, int(row["year"]))) existing_record = cursor.fetchone() - if existing_record: + # print("Update existing record") # Update existing record record_id = existing_record[0] cursor.execute(update_query, ( @@ -281,7 +280,6 @@ class Prediksi: ( str(uuid4()), # id int(max_seq), # seq - int(acquisition_year_ref) if acquisition_year_ref is not None else None, # acquisition_year_ref int(row["year"]), equipment_id, float(row.get("rc_cm_material_cost", 0)) if not pd.isna(row.get("rc_cm_material_cost", 0)) else 0.0, @@ -835,7 +833,6 @@ class Prediksi: try: # Update acquisition year first acquisition_year_ref = await self.__update_equipment_acquisition_year(assetnum) - # Mengambil data dari database df = self.__fetch_data_from_db(assetnum) if df is None: @@ -846,6 +843,7 @@ class Prediksi: # Tahun proyeksi future_years = list(range(df["year"].max() + 1, par_tahun_target + 1)) + print("future_years", future_years) # Hasil prediksi predictions = {"year": future_years} @@ -1090,7 +1088,7 @@ class Prediksi: # Insert hasil prediksi ke database try: await self.__insert_predictions_to_db( - predictions_df, assetnum, token, acquisition_year_ref + predictions_df, assetnum, token ) except Exception as e: print(f"Error saat insert data ke database: {e}") diff --git a/src/modules/equipment/__pycache__/Eac.cpython-311.pyc b/src/modules/equipment/__pycache__/Eac.cpython-311.pyc index 35cc311eda92c433e8a8b3c2371301c7351bed79..337131275f9427471eee3f353aa109abb7b1b23b 100644 GIT binary patch delta 3244 zcmb_edrVu`89&Fy#`yge`dv?EPP$5u;CX{x3ZskfC?)x@eyO8>j2mWG@~Vw$E(lO_?VnId)Cc8-YyW}sD5 zuYA9IzVrRg_d36Ou0QxbygRLaTcwf%d@a>p89m#1SN*X_?!XgQE3Ue(Qs}JrLm6Gd zOSqDh6ovMg>=F)ga9Fe;0^nVIZ(I;ydnvXX(VAE@Bjuz?9a~zk0zgX^AjadnfTuw( z;H0$FT9^WSDb`Cj^;s9zWt%!L!+P1KK3jox`KHd7VO>G=@(rK7oQAU$4S5Bv;R`KF5x*a2L}hw;ui2)UdL%ta?&2Lj#i;p_mo#b zz*;sJUvmvZTKxlAkwXo-A@$Gb+07Eb%c{Z}PR8kI&8CUdFNkpm@x5^=0B3)+aU$V z0C47F8|FMt8U0S?Y%MBemMZeNEg@bp2d{@YaY~b?)|DEqj5hP-g=*~O*sXlWSLqoS zORXC`M0eyieV%eFyHT!s8|5mlQNtqIvJ>fCwxYBZ#l^Btlv3Et`3=;w?k!!>rF5hc z5v@UUb#BuJ-Au(0bbXJl0Tal;+qVYUl}vU{kJja`nXiP+yOP=Qzj84Gu>A$$%1B}^ z)7GWjj=YlpA7qC+lD+(^BfALi<3|cxWl{RO@!BE7RLyRos!m*z(m$oG0TxEOJ?dWI z0c`aqDXIWd!#j%(dn@ZhzmVBbxJ_F%0BFEFzw0FD{l;h#PJ+wg0Wb-Dwr+-eLeK+M zKx7vqmHHpvWOO__#8RC-p%=P(JG*L_p)i|ZBje$rM3Su!_^4qfaRCVia}_nt($wTg zl!;Q2WHQReFI}JxQ{E&MiPKavdJ!vu^fxpuuz9=B;RF*MiX_?5cr46Dm{^nzPemh4 zJ+(K`y6TN6Vn+enqI+fIOJ?pq4zC%B^By^ zj>_YxL+G%v4z-nk0T{yo(5kHi)!N$~xXPSlXgraO=E}M*4@D=~(L_8aPO^;8 zjpFutbjQ9|#OP6@!&71=n{vVtLw|7m#FB$irXEi)2P}T4V1kpFC$joCka977ScllNQiO$)u}GE_KAg#}l5PPwiGL-^Az43A~c zj|+*3mGcwA`H8oBZ=Je5uq0mU`bfSUT4wJjGDin9-g8;+IiV{5du2+{x*ux+<(Z#M zO)YvBqra+MN@QH!Sy#7E(LJYFHCLw2WXu6H;QnKeRN~PJN*4~MGQ8G3+b!4|79B$S zNg;45qdc8eo)(m+R~?ma^t|4aarmCCiuy39e@r|r%2G&lES@3P3p;b`?B7?jH^HE>K7{d=M;Z6+i$#(G5fM+-+Nt) zb(#8Lwmz6K2j|3VT01)DzJywT&i- z#aXGOPmvCJ|1E|T3i*C1VGa_ieC|6AZK$jEPtbuX>)vn-;7R5sBF`gI8m4mVhW+^K#R184Excy17BDF3lEdu!g&+shtO{V)6j^T znu2gOJ<;?RD2);51R7}W2#ks)fpxG&zEpYyN_yZr*!hwEr1^maj-j{qkHBH1KCt&d z3vnhTg5;SKk4NK7ARmB;eMID|cv`?1ndl%Qgr*NP7?0s$PRS%D!_SH$MOYOj7b2r^ z<~!JYB)xXvpJE*JVXz4Y)wJ)GevLR7MMv9Pr3ArTMBi(#Z6YCZ@`(tOjE3>Snaq_C w$>c*$5?DU3SI8*e(|+`@-3fn;zG#o_XGt<8 delta 4306 zcmcInS!^4}8J^`W9@3IpiMO~C4~Y_W*^(2>whmiLBu5r4O7amWWf(4XBorwxDaes` zt2Aw3#VAn11GG-+HhOTKLo%zw`}Gvo)$KmIyV`2(F!4N&`<=fyd4^n=PzBx;!kfJ?4R?n@q+lZ-Sf zILT8I0C*`ci^(_%FJ%BPibDQPcSd9G^vo2%l?oas#|LKJ!m zO#^sKOu`s=i)gQ)Z!OiQiUwYBVIiiPBVnb&IBwvrB}xx*igh@0ms6YQ)W%sj+cJuT zw{ujXI(`V<)dri3u>PEh$ zbEzE_!bAgh1M>=sEQ4SJXM&g2j@E}FwoJMNuu22(+}k8VC^qS^l_jefn8rw}ia2ZuFgrNy)R0Ai^Lu_otU8pSp1`8*)hbjah9HrB*U|Fu^D=rjnCuy;G_qS_Ru-=!)rcE z#w5#sg7?U8lVa-0(ClK2?!~2mm-Ig;C^%*~N6f;v4NrUI;^ZH;;WqQytGNBJFDFez z&a?O)^(xth_15ZhggjJ<))ATI;FFXAnrd{TYPzK1DmU$lij7Pc7Qr9b4HG;$GC_|` zjgETPyK5Q9D#y)7Tz-LJC!r;ilR`tTt#@1O~l#YQaq{b}+4i0^=}V~vn-BF(ds9lC zbv6nly;)_3f3~cwjsFtgwziZS-Rs6{m2IWNSZ#Fp%11;@)D)X zlp4*Gpr-B)<=&v&Ytb7|-SDTW!3;GhP=i^!=Z?L7!`_~@cV_IJsmHQLdma!qhu1uB z>92dm&+NWOKh*!oE124|l>IyYEB^Ef<<3xULG1VM8fhekyqD6ulv%L!eK>#!>S?M! zL-h;0I;zM0ycFx=4;45RKt=$4Tp~CQ;C?!a*cdS)`5b^V1RB=bvi5z#ez;)W--&8q zGOrG2ji%M%yi8F~3R-)<66mVGHFkMSu=<4dzMD3oZ7i+zXS9An>xVh3S9P4wwcP{9 z30PqZt>o3f(XiHdEh@AgN!J}oX*R3t*6KH^okF!U+u%$+fdnYWn*K^(s_(9=@%rO} zZ{+qN;Y>K~Vlpm9a5AaU)aa(ugG#Ig3=t=U^Xx{L6~gQr6W33_b@oHe&GFl^+bike zucY0E^j@I$0WJn^A%_tn&{h&I*w0kP!o=Q7T zWgMr3x>G65XSLR=qv=|2rq=u3(2bU~uQ%iCP1p9Oq$ohW?GD+nK{jkUnzOVM{=*i5 zImb^XPNL2Ba_^K5d@LWZpw}n$GE6-Kjf0iomX1KotxEM^m-Lp&FxW1=)hx#&?Q%TQ zb<%{GPj%|4KIx}M`&5tg(;oR$-}*=NKcz$i{K)mbJSUxrBw@^bk~j{3>i!LJ0$%gH z?g-+&{tPD1B9g!-HchkW|0-Y^L}1F@M)=_?jqA0~;IU~$66ov%TQak-)7wr2;7d)f z{yTxm^^o@zAqG+pf7N`gp*gibf57%0{6sPd#FwC_K zSGFPdi2w=|A3{fAhi{pvf$#WwiOuyt`hHI+Vpwzu{<5vVmzew!;3ryNw!0 zd``>8mmXZ>D2W3H9P*dYL}LBP17Av!yVE_L$lWVF`xHG`e*u2n)2+Y~>;?E`Pje?W zo>MP`*hC~4VrLRL8K$i0?5kKzjEKiyVl{{1=|gtn3S2xib1)(&aJ~8H#pe< diff --git a/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc b/src/modules/equipment/__pycache__/Prediksi.cpython-311.pyc index 4eed65bad557acd8cf2ad1ac2ef265b7c96fffb7..a3c4aa3919757b31fdc768ac28ee1e68812259eb 100644 GIT binary patch delta 7613 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 P{&jBaV?KQ6%D? delta 5132 zcmdrPYjjk_dFJl!-ADHQ-pwYvNp?dXgb*GE5h6$|APH zyWh_M)p~qzl&|_Fk1;8*>0U`|>iH6mc6;M?m;AV&wbjFtY-@@Q8e3khD zLwDf}DAQZuhaqm*IqPwFOK*(*Fob8Y8Cpy1u%%=(!Ro4PFtc=;={pvoPsy(X^)d?? zb-^V#Vk%~q%VAM@5Ub!q_?AGS>YV?VIAIkQ=RQ7z? zugbTS_o9H#-iHDp4jrHUktM$m`AqhYeC2y_k8i}rsTZv7O*5aH*)5+`6!cP%X!bf`ba_!sD4)%^T{@S$PiC;$uOk9_Dd)e6!R*UKng>RL$BNtPq#82OD7FNx{y zx{7fNd^he8UIqKd=i>;RA3uWhXsYhVJk(Z&@tD}DDuaP-AR(zq;K*BD+J8?*eh%5;bjiTG(EKGq~It4#Vqfp_&XGwfXi)5aA&M)g-_+68u<4p_&ou{?86j)AKKb`+)3%Q zozS?5Vo5bnAN4y<(>66fsY07n(G4#~okOUixURm9=R;wkPH1Tkh50{F)^bWNuu3kyv=VUL#nb#o=Zpy{IE+P|Y(1GYo3PsYJsHM9!>W zXFxI%Az3LQoN~Ye$t3jmlPn|?O_@1MGKof#;_Y^h;nceZCA2ObwcJX^WMo27=R&aI z`CNK#+RUQ4$&k{_yE_Bv)EqOHm9P@?#pJlQh;rf}Ud&2XG6O~`TrOhK3c=0UHXEmv z>_S0G6q#s3O< zz_6zN^9;>CQjVC@<)sxYRJ`OjM)HJ7sc9gA zkdvx?cfVwEr4eqKtYEiCpDX2thopaJRzzP%O)}vm?ErY*Nq1CUzL=k8>##xE z@)Ootp}E=+^@XjsiAt)8O+_UM7T z*#IrSWc@dZ%NZojH6YGS#L%;y?9>c_#qOC<`f_>ftyiDM@b_1~W&Xj$u5Fva+(C;l z{$mP0BOopdhF9`5dGnvrrmCTZ57h@F0)G$fyGB5lzvb z{%Zuk*b&9@nElm}%;>*RUUgFR-{|0kCI!W)=!81{bBbN1fQsh7fc9O>u?4Q~>UOO% zH-?1}zaqHYM~v{T3;oW!Qzrr`WC*UGze9fy!RCloMwR{|fov*bI`y&;V ztRE?ssKgdij2>A$tpoUbDWDGXpToR8IrtN3+vCO8;OBepsxQ1zno;Uf0^-KjAYUg0 zA8rd1$S(^Cacx7J(8h=As8Nx4Rx7PX>xIUaaD=~3haplZkV^;w$?})>ZJ}Ta1!Vfl z_pZ{hgdUgIw~)ytGxx^cW?V?FDsC`7sE29$?zVqUPK?OMe6zbTs;gf?&D$M z>l>5}SU0*4HNaP|m*Yj?jcsAAIb-^e6^_LwU@b8F16U13`-k4IES`ifR8Bn5{(Ae- zrZbf_J(V@vr(7tn>@J_w%g8Gx;Y)W{ADw?NdL+7c%B4|vc8{$)$rC{Ta~CI(Gov2r z8TC+i;tx;kufbOL9cFkeTE1G*3R;iCfzxPD-7G)|oSN#Q&n+C51@;5#F$ zE9WrG$x{DRqx_Ts6a17>JJqW^_b}c$enn3nL-7JkasR{| z(m2QDJV>CIGM)2j=CQJKCFXfpc5WQC_&ipSp7X4e;y%rMrR;o(c|Ie%z$ggLPayu0 z+W)t7E#IIx=Kn-Lmbj{}E*ylH5BgaxUHmNg;9y{khoYM)bC3c(Z7!jhNWn1*5|@Ko ziY4~0fwGK(;fM0@RCwUf1C~XMqM#S0L2@a6^U!;)X>!G|UUUl>JAQZ}i!aA=k3Y%a z58;&)V{*q*p)`KT^@ggCE6B!cY)xK`M#1+dR^sAV+wV9AcSHQ$JT_t2alj`ncof)9 zK5Xo!_R)agODQ-B8%|aYzn4gjD}-RAIl@n%8k(r+{<};t5($yqK?##6sD|%P4q