From 1f59cfecd34d2cb8f156a1b9112ace0b40a59173 Mon Sep 17 00:00:00 2001 From: James Jennett-Wheeler Date: Mon, 23 Jun 2025 11:06:51 +0100 Subject: [PATCH] Initial version --- .gitignore | 1 + .idea/.gitignore | 3 + .idea/PlanningScraper.iml | 10 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + application.py | 157 ++++++++++++++++++ database.db | Bin 0 -> 196608 bytes monitor-planning.py | 122 ++++++++++++++ scrape-my-application.py | 52 ++++++ scrape-new-applications.py | 56 +++++++ search-db.py | 18 ++ weeklyList.py | 57 +++++++ workingHours.py | 62 +++++++ 15 files changed, 564 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/PlanningScraper.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 application.py create mode 100644 database.db create mode 100644 monitor-planning.py create mode 100644 scrape-my-application.py create mode 100644 scrape-new-applications.py create mode 100644 search-db.py create mode 100644 weeklyList.py create mode 100644 workingHours.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad62966 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/driver diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/PlanningScraper.iml b/.idea/PlanningScraper.iml new file mode 100644 index 0000000..defcddb --- /dev/null +++ b/.idea/PlanningScraper.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c795a61 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e08f11c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/application.py b/application.py new file mode 100644 index 0000000..460dae8 --- /dev/null +++ b/application.py @@ -0,0 +1,157 @@ +import time +from datetime import datetime +from sqlite3 import Cursor +import re +from typing import List + +from exceptiongroup import catch +from prettytable import PrettyTable + +from selenium.webdriver.chrome.webdriver import WebDriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait as Wait + +timeout = 5 +base_url = "https://app.bathnes.gov.uk/webforms/planning" +current_date = datetime.today().strftime('%Y-%m-%d') + +HEADER_START = re.compile(r']+font-weight-bold[^>]+>') +CLOSE_TAGS = re.compile(r']+>') +REMAINING_TAGS = re.compile(r']+>') + + +class Application: + @staticmethod + def CreateTableIfNotExists(cursor: Cursor, reset = False): + if reset: + cursor.execute("DROP TABLE IF EXISTS applications;") + + create_table = """ CREATE TABLE IF NOT EXISTS applications ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + reference TEXT NOT NULL, + dateScraped TEXT NOT NULL, + dateDecided TEXT, + caseOfficer TEXT, + description TEXT, + decision TEXT, + num_documents INTEGER + ); """ + cursor.execute(create_table) + + @staticmethod + def PrintTable(applications: List): + table = PrettyTable(['Ref', 'Scrape Date', 'Decision Date', 'Decision', 'Case Officer', 'Docs', 'Description']) + for application in applications: + if type(application) is Application: + table.add_row([application.reference, application.dateScraped, application.dateDecided, application.decision, application.caseOfficer, application.num_documents, application.description]) + table.align = "l" + print(table) + + def __init__(self, cursor: Cursor, reference: str): + self.cursor = cursor + self.reference = reference + self.url = f"{base_url}/details.html?refval={self.reference.replace('/', '%2F')}" + self.raw_data_map = {} + self.new_documents_found = False + + cursor.execute("SELECT dateScraped, dateDecided, caseOfficer, description, decision, num_documents FROM applications WHERE reference = ?", (self.reference,)) + result = cursor.fetchall() + + if len(result) == 0: + insert_application = "INSERT INTO applications (reference, dateScraped) VALUES(?,?)" + cursor.execute(insert_application, (self.reference, current_date)) + self.dateScraped = current_date + self.dateDecided = "" + self.caseOfficer = "" + self.description = "" + self.decision = "" + self.num_documents = 0 + else: + self.dateScraped = result[0][0] + self.dateDecided = result[0][1] or "" + self.caseOfficer = result[0][2] or "" + self.description = result[0][3] or "" + self.decision = result[0][4] or "" + self.num_documents = result[0][5] or 0 + + def scrape_portal(self, browser: WebDriver, force: bool = False, count_documents: bool = False): + if not force and self.caseOfficer: + print(f"Already parsed {self.reference}") + return + + print(f"Parsing {self.reference}") + + browser.get(self.url) + + details = Wait(browser, timeout=timeout).until(EC.visibility_of_element_located((By.ID, "details"))) + self.__html_to_map(details.get_attribute('innerHTML')) + + important_dates = Wait(browser, timeout=timeout).until(EC.invisibility_of_element_located((By.ID, "importantDates"))) + self.__html_to_map(important_dates.get_attribute('innerHTML')) + + self.__parse_raw_data() + + update_sql = "UPDATE applications SET dateDecided = ?, caseOfficer = ?, description = ?, decision = ? WHERE reference = ?" + self.cursor.execute(update_sql, (self.dateDecided, self.caseOfficer, self.description, self.decision, self.reference)) + + if count_documents: + self.__count_documents(browser) + + def __html_to_map(self, html: str): + details = HEADER_START.sub('', html) + details = CLOSE_TAGS.sub('', details) + details = REMAINING_TAGS.sub('\t', details) + + for detail_raw in details.split(""): + detail = detail_raw.strip() + if detail: + k_v = detail.split('\t', 1) + + if len(k_v) == 2: + self.raw_data_map[k_v[0]] = k_v[1] + else: + print(f"Error parsing: {detail}") + + def __parse_raw_data(self): + self.description = self.raw_data_map["Proposal"].replace('\n', '
') + self.caseOfficer = self.raw_data_map["Case Officer Name"] + + try: + self.decision = self.raw_data_map["Decision"] + except KeyError: + self.decision = None + + try: + decision_date = self.raw_data_map["Decision Made"].split('/') + self.dateDecided = f"{decision_date[2]}-{decision_date[1]}-{decision_date[0]}" + except KeyError: + self.dateDecided = None + + def __count_documents(self, browser: WebDriver): + documents_button = Wait(browser, timeout=timeout).until(EC.element_to_be_clickable((By.ID, "tab_documents_Section"))) + documents_button.click() + + documents_frame = Wait(browser, timeout=20).until(EC.visibility_of_element_located((By.ID, "iframe"))) + browser.switch_to.frame(documents_frame) + + Wait(browser, timeout=60).until(EC.none_of(EC.text_to_be_present_in_element((By.ID, "documents_info"), "No documents found"))) + new_num_documents = int(browser.find_element(by=By.ID, value="documents_info").text.split(" of ")[1].replace(" documents", "")) + + browser.switch_to.default_content() + + if new_num_documents > self.num_documents: + self.num_documents = new_num_documents + self.new_documents_found = True + + update_sql = "UPDATE applications SET num_documents = ? WHERE reference = ?" + self.cursor.execute(update_sql, (self.num_documents, self.reference)) + + + def __str__(self): + return (f'Application: {self.reference}\n' + f'Date Decided: {self.dateDecided}\n' + f'Case Officer: {self.caseOfficer}\n' + f'Description: {self.description}\n' + f'Decision: {self.decision}' + f'\nDocument Count: {self.num_documents}' if self.num_documents > 0 else "") diff --git a/database.db b/database.db new file mode 100644 index 0000000000000000000000000000000000000000..063bde31f43c0c68a649627622b1070964891040 GIT binary patch literal 196608 zcmeFa33O!VbsmVd0Teb;q(o5^)vwh80jROms|5v!+R$k9LiGXyXtpScA`4XopxA{f zu$D%H)RI?dQj|AkCgb?T651yQN|- zTQ641HT%z9ORLL^>&wpi;`yt~j{O^FBIbnrQ_MS8uCFg&T3&TlR^d)WYJLQxcJJ07UxyDYR zT(9XVn_THS^~kw%ozL=`)LtsVw|u@2tz{<4za-y8wDNNy;lkhX2zu&!a za{aMqpchw8jrqyO*dAB_Ia=)WEP&Cy>U{pX{MJMi=I<`#ZF+WaJbzPGuLpO4+6 z-#P~$#m{#)X(W#{)A;$W<`{myv-t>qKHTiX&xbs^{vFfpYN8=gs z=J9jbo50T@@16KL==I}gPXj+<+xRi?8T{y{C-i0T`R8N? zPG;a_22N(+WCl)V;A93)X5eH7PG;a_22N(+WCl)V;JaZ49_;KM>#}oTy1q&N|8VCw zJGy&Dh6iuQ{=2@f_l=$!>HXV1uk<|F{Zn0)&Tn>}>G0hD;+TTh8MT z<*iade_SnOt4`r=y-=pqpc=|XN~OvkE^+dOoeHvjC}D{1v|Yeu#af+ywUw=Aw+buE ztJkiqd+(_8ILEvvWt?-#WW+e<7t33XnsYf@trlzcFtcp~H6xme9FZAK4&yFV?_}|w z`!i}#W5wX)W!$Ji&8=jMTLtG@rB)~v_LsJ^W&8~<+^C_3rdru?E?>LhOx(oZmP*-L z%~?uKI`xXPS;F&?rN-Cg_X?#FCT6=r-?A@GPV$_$-$TZktM$_wkEtb|OD67j>hhHa zjly1JGk0n-wyXS*@h!DJ=~To+qW95ev0AI+ohy}U_@m@?n$s^0bd2MD4nFb;-m`P5 zh1sPW*DvU;YHiw#y%{bPYq{-gRc0RRnU{T+bS7>UOZif9v*4^$D|NN8X%{b-u~NAL z<*H56a#=FaNhD_D@l1;6(wSJQlZOLisf28TPkth zA>DYbQo;#YdB)kzmJ6jC_G76b2g%GcT~;m70V~&w*^-lMRO`hOPRJyltzN~vVSe_; zMya^P_s{O~%AC8YRwfm-GV>R*@VzoUKy!Wjg&d{Z$BuI|XN_fqjqTH{;kNsuy=Qkgu34W%IshIrrP)ys|gl#|%OYv&sxTyxdR zUfIFUtkXb}&Q76@SB|YG-oV#zLe`Lj`f6impyM1Q{`Xu`rzex*({uHF$Y2*PLtd4g zWh_9gQa0O*7Rx_C3(mw-m14P2-FGe&pdCszXX1RJwp+Q4l-6o-YpYP5Y(G9JK0fK$ ztLGP;i7REiSQ)#TmYtSH{!+ppBsZ7dUmqUxd+d)wp3cHvrFy$YmyiOJj= z+-)4CYNfJ?pU_%+SjVcfRjK6h@h;TMZlUgMHR|{jA3(ZcWpSq|wfXXeTtyyFw>D3G zQQmdN(St7S+oP+}p2lXW`n%Y_5yny2%|g!U!D5ibD&^nuGjoL^N%*NY`2f*aaPlM= zS-IA$*dUE6-ds%qDbN{b5vMjd7w4e`3nk~7Y@<}yuZSaqGvJSo#-qLk{mwI3fo$2& zR`XL%y;>;H4A$i^;)(6o;0f7%y5Da04*jQA%5KoS7yS1Qn?Qb*VzpAM5mVM1!oB{x0gTux3o>uLJadO8#5p9%iSpJ!71=Q;kFp`Y`7&AiJmioYhDY|Yul zj^s(MHL7>8YoYxMwHj%h8GT|;-xf3D%TKG3&&BD~9C=*z{^aMG?Q3ROAE#YtjN?-8YMOu{SYEw4R&Aay+1d^J(7%s@-lx3RT$@XJ=N5 z*8GKxjK88LwU(`IJFt-RmA&BGY?Law+w^3-zx@UCDE%dQ0GVvEs%G7{VP98pW}MCJ z2K`6&0o_tPTke2Wkv}*amAh&+u{kTbBBZD~sA`8E?7k>F^tFbz{vy4IfI?Z;lHLr? z-;_}OvdvD`{5xvR#RwlBJ=gI-=MxVG{hb(5cvlDi(0Y1ufN2`r+PqQCcQ(H4^B3Z+8i>9$&_F_SiD#m z03z>5_`rY1y_}5#T>TlcS-3$Ze2zreI49vH8ty22ME(569hj|Vh!pXkW zC<3@CHg=-7V0;F!(TvM}Q~N%=e#5zO{;B1q^(!~7dzbpziyv>E5uGtV=gux&UAftA z*2IJ8$(0u0IqO);lIE67*m9*>-*#Xg0D_ zOF#^q^DzIndDL0BOO;Iq8R#@8>9Aoe^fs{f%eUo-?QIv}3ebm*-94Nz2d@M@z2$7s zIfSLIn{y&Ehna+Z16@|D0=c%oM((0f)*;2fOi zP$_9o;JQ2G1SWCXxd`+El4i!a0soQK2o}WB{w|EsnxlW44E`3=3Ex8hGBty>-)K}( zrKmm&40N%Fedlw{-ZLc56Nxw@Pit4#+jp&-Xn|AUpw!@o$$qR-aU-E!kfxW5<=a37 zofSBib!W-Jg=GaoVc)%oKRVa2BvtZs6?8bUmaFX2s^7p*+TXmEv@2FH(=6n1Om^{A zGXDybc#EYQ*Qf^7yWP!`eY*J)dlQL-o1VRXEkq^77tTX`pKnxe7p_;z(`E>ZnAkj9 zwOH8zB8Dl6rx)Uc`~VRPfShElx4>Z&OTaDZ7EWyko`WN}CfrNlJ@w))V5mwi*VvW$ znjl1UuSjQF!^nJ!175{O5l4tY%E`r*mDL;1EPGFN$<}IiFUg9{rLncrA`S`U(%wS-xA?*~R`Ka2Vc64V&3ZcJj-Nd9SFYODDpY&ULTU zvQWIvg)A@)te(GtqF0ab%(--0RR(nPKp&gUoGahp!k8rEs^xOT-hm1 z&gdoh)Tmm5MQsDkhoA2Fg0%!NfmR_#;Qqntm`?6E+ccj9Lnv@exkWUMwC^$Hb;$1$ zE>J=V_R?+IUpW4E*oWVR?4rwWmm4`8I5;?Drf5;g))Cf?R2e+^VHTM{=c0LPVllBWe9-b&3Htno3);U8Wn9dheja_d|2iRAgXNP4*y*2gN zO!63y)fl5ZTNEUY( z04^gCw2T!L7kr~pEY%&H!QCQc+9tzUWRn~NvwC)r7ka#TN!dv8sL+l32}mMmjgay? z*|Lq3L%d>^{OApXYDS1E7_E%(1D#;#A2vA{_2ZzOv7ihpKyL+!WQ99Q6#&#V2sH|9 z05gS1gVe^-WHA>Iad{x=I9YNBjhK(b)Fz%(NVhP;Ijx~6eEU@08jz#>5j`A|!y#(XwQZ1#fG_JjB0kXO7~407%jq2Ec6y0ZYzd$9 zW}jmxaXvFYyLf5!db^T}FSOVcEel;Q;3RBTAwlvEZ94>P0a0O7lTsw1hQ-|h`o&=V z6!t12AGAzZ(alO8Xx_}?79b($Uj~;~7}i@PZ;`MVVy0My-2yB+Px1eUI@de88l&Fu ze;nQ&`op0g9qfqxX9IuO|4;fq-S_uS{obh?y??dmU-YbZpTlMU$?$Iv|I+YJ4!=IU zJ$!k1Vz__kTSLD%^piuc4{Z-!9-0{HAN)!fgSwY*f(Q85&KeX zGj=idM67q<-wgcxz~34;7|0DQ4J;itoqutE*L>euIqnBf%W+S;Q81c&NIV&9oTQ~C zXw{fpt?^<=s*JfvBtNW}YufUAUuzVnFBK5(Dc0D2(~zAd6KF#j5ks5K z2viB`Pr-D8$Y}Y7g)YKdC?Y&cs6T~vw9H68$P+Z?y`&T$jLq;v+5)G*{@E&%V#mR6 z?2??0&m$YOEyxgo$RAg-c?%9NSJHb$Ef)es;VCi6rEMGn zY#2C^*kKg(3uK%erg#xSrrZJ&zbeqd(*$|U5}6P=GDTIIj1_e;M~S_HmW-6S(;`8< zFG{E=4Ro=O2%#akq}7U!N3(5!ZLcB-&=eC~)hh+gL`g)~G z2J9LPe3&59&L+VC$Xtpi-0b_X_QVTZJ$*8aw3z#Hh}jA^V%GjJu#PX!;f%07%}&cF-?Qyo8SzSlU{SK>N5-tt4XMw1mfcETqZNSx_rq+ zYKmDI+fKzKHg5 zlA1pr@Ma&iBToDq1Jndht3JktShAvqm&}A8zxbRSKNvGetg2Kuk-L(&$7mhDeRA_S z@1uxFCv1A7om5WInfJ&7AKX$;&7|*hfw6l6Ym8hYI8uaSY>E<-BrPoIVG6UjHjsSQlVxYlDxXsdp+R#{cLGdZ(zvYfkL3ca9d%?|W1U7iFHz zK|Y5I#zfFOImB5&HWbObfOepeE|Qnv?2~4N1k?_MngNy{^ef~`A-^H3^YLDMw`?4p z=Y~TRGQ9M3V&U76+sYIK5WdJe|E{nBo$k_UJgm;_K z-t7`?Selx(><(dJCZh)F4$&DkZVJ#TXVirHx0pa}twDPeOhr$?#xP2S=AMuh>`^~t z9*hf<2p5kIX%U3Tsq8?Nxiy7jKqJFFjaIVdf!W#yz|jhsIpjyH z6didQ<^@3Xk`bq7BJG^F)pvu`$%Sw~EFzd#56v_nbiUJf2$J-D01|R09k9lX*(=$=- za=`Rui=H(aX}`7oTy_VEx&Uw1-6?p>H23R(VW;hwB4Hin%I-d?KzAlhLrm-hV6TIY zhh#Lb4(icFdCP&(tTE7QY;g>N9?hsdL#`8fT3ie+VLm6YY(j>(D!>#*m5z@A7o>p! z7&Hnz7JOMz^{~`U2GJ(7EYhCBErBPiN3Y;KHnsrA7Xbc4mjX=ELyl3Rq<{%ifNG z9WdPcnU06MY8}0QzvqWWKQ!{+5C7KihlhT3==R|E#Qu5g;{)G6aJv7$>_6T2@l#&+ z?{@u%u3GPpbo_GH|Jn5qyT01><*s7am9B}d{?2cqO5n#kU+pY(E_aS~_ICDmKl&Hf zdp{ZZTRa0^Z-%$L>%CKKG#5FxAqswO+D){h$)Wm?{2c6jQ^~M}<6Sm!C^DW1045E@ zxeUrpt<3Q_PB)cGlo%|%A)7G*IW3nPu6_;r5xz*c7 ziX`x7&Ttd3#hE4SIv8dW*w+GFNb%KjNQiY1or0^*Sg^re#?$h~-ffxrL_8|J+cj@& zTjG(g!qPDKHhTy;!>nU9XkHnSUe1u0^mu|_fM<{m0ds|itr4R0V?5HNSIIDznvN=V zh#zGHwvJiMlr~%gHot|08#^kivkcW5Kve;mCu<9SFs1f@$QO#j_WW+amO^&xUK!_T zoGFfg029cH`@)fHi02mOBJMfQ*}-C_G-|&H4YKwS4-BaxJ=461|$G@CwSEifbf8c zOtnFXSvic9V3pZ078jgD#5hy*tC|5kK9f|#zodqbB9;iGre*k7bV{y`2>6IiU|7LX z^IRYKZhTdXL4Mz_*OgEbLuL0Q=I4);k*kF=;tag>v?%~8NRFg)s+)FZC|y_t z0!m&AT5RpR>;#;yi6}@cMB;cxXtjpDm!Cm70foj87ZmrKolkg7dK@#MN4ON{N4q&$ z++->lwYVXg>i7yt!&C8Dp6zk3CeKSjK8J25z*8LlQm|jkCJyeF8RsTrOmqwazj;$O z$ip*g$WQ7aM;zb#8*>y8O>&@^b=x20$DGTl z$3zvvw#8suWd=i6bZGc>Rhb&7^I=-9E?>O4w(LDIInXhQ0!D99BvWdBZg%Oy(v7g( z#|68BQ3A4A9gww?$Vf#7Z4ojv3RR@*myEDtSH4_$kf)Iv?oxLI>I~3=j<>|KtBje{%BQzlby7ZCz9z#B;oh<0%y6FI`;@5n*of zQlV1aLhSt#r6=WXuR~N(T01AS!o0F-;bB+>0=MLr%K6_Z-i6Gd%6Ed75q4G9xqbPR zBU!b$G4cjsMq$U3ISC0!_pz&|!vj>vxc4hgg2=M|1d zdpy{rtJ*NTb)Ix^!}zqeap~OR5-={~;CDctokS7$D2jz#ZL&`>fe`|H?ShguLYsha zX`}!r=raqH8qh)ZkkW5X`}R$9KSUj5a_}V(vpDeB8>m-DWeGX$JHcA+EM>vwwr9{+ zXGyIY8T}-KV14HiRR%0y-qMnMV@i zY)OLY2aj+VFrJwQkP^;CkqneRny?jZbQo@u98g2*)`ZTquc_Fd?65TzPj0WIN8)zE z6|2~pu+3hTere~I2$0D~&~mURg62aFkD?SBwlm36Lz!|GS?U-m(r4r8xeNoIlSeU6 zY@8R9T4L()3_=v)f-~8IF}0f1Y`JX0jAFn~NlYP*yxY*uMF-ClG^va+yO}Nc3f>i{zkrjQ?VF z>rogSvZ0}`s8o=1=8RTAfDyS`Kyme&PAb`c=i|I(l6z_~7eZtBfcTY-PBU^;@&`(5 zcmv^-`Cgz=VX}>KALHjvep;Tpkcxuz6U#eAfRbxFSe=%1FIznF1I;tk%N=8}{z$P#5 zf@o!}{Yp~Cx=aNdQc2g13iLDW1=<31Nwu*7S{5czSuND2*9)jKFK!?Y38WavXw&QM z)b=UO$_jb#w<)+!7uArWyal-@VE_Prh=gJz(>mt&*}tmU@c`bj)dE}_m=V}&))Qf9 zvO$LlqSvQl8!QX-%AVAduxn}!i*Bo>yEpa`QCQxml~zXPX2g00w?(+ZYFRLiUfL0; z=smd9oy zAyGP>P76ALNQ5tPah*93TJVZKwP&3+uKq2a%v*ICrEhRDC}$B~$K>Q1o(3WZG*e`j zr)e181r!*;nJ&ztTuhgbdif`L&zx)CQhNqjQW1M*DHxaVH;t9`bsXa~xHaAuTaNty zcXcjxj6N{(g`w2oUmNU={m{UV4)pc^NZ;@Gy?p8yPF?B!*S$Z`d%EYFJ$Jf)t^1&B z4Hy5_uKliOyB4}0?fm1;U+eszI)A8huk%)CrW1Vy=>Kp0pBtZK>3#N%-{H^Uo6X_R zh~;uHCMg{Ah!TWnv(E>}0VoH>otDaBQ?7-qm;@z`P;!qdm7`Ne`8(><8X{;DEBIj5~gWVtYKtP7WQF`^Q`dYTmh!0$)MtrB0z~q zGx4~ZUnC?=!dpe9U>4}c-Qo^Ph}A;i>q-AYKCQ^P;%iZ-jRoV8WRZenU$%`D$QksK zy|L;@Ddpl-DZyEO`sS6@(Q_=g5%rsHpfq)%C5H-}N0>)@FYi3V_Z@$U%}IB`je?qk z)CRWm7P58eKnL*`WmvAFM0|`_*wEPR*faIYn%ZwFt8xM9-bX(PS+sZpODpU&&ZEPV zQqyTzl*$g4Xq90_U>Xu+^au8n=dSY8&v{Y|?=B#TAha0X&9u0v$1JLbE0|2ciGfEN z^v%-(Urll+S?xDYqM$w{~%1E_%Ku#A*fRZm@iT2^L{R|eQg)Ultp zj=c+EiU}h&8Ch$(d*BS_!8eJ|Slqz~1t(l^rY2WJI4gp~M0_q2F;jB^oa31DltkrH zGQs!&JaD^K2+5U9d{-kZmmCdG4c!tKVFp5@ct#~!7+og{pa zD1>WSX97kzCnjl6Yzx{Lwub`3JU?112uAKEFlm9=|HPO-aH%DI{ethV;!Q8-K?*D9mf8Ukf#qJwj z6P-#&K}-5XIgyk;2bC=z?AU8XJyB5(SH{MIT1%3 zg$V=z)~i6TO016BAE3^Pf#^{&ABP*F$SET1Obx<6>2SW!H0Ari%lAna>8?=2qXZUC+RiH4zz-P_b93PJHgO91h zfij+`!x2c53=d^dv}&=sL9ExB+vpmKv0)dl8M~KehruwQ54s0PWOzT3T2LFA-1R($ zPqT#GWo&(&T1(|vc8eQxfw)viS+mmp$sx8}Eo*ljWse=KY>2jdO=F{psFkp_9V8lr zwZ?5_7g^d23OJhc|Pdi$z9R3YHJ{tTZ+Ae*IlnrpPSgr{3z)1cD3aqL5HXoO z0Ac8@Ao7uGP-6(9Mx>As3=~y~yffpVK76SF0}PPi+BO2`$k!lU=B6R`P!a=i0KgIr zI!a2CVE#$=;EPFxG%74hCIB8afEzg`St8DZQ-iIAfGSdH0=m)r$P4PtRdMrtG{|XN zLy(C8)r>gwO@J($MT8HJ%UEjLRO*P;g;)da8NIUzPkrV1lVpvA#KoKmM2C@eN$sTo zYQdC%B6TwX>ce}VdS1r=`p2|27j+l|W-o+SeV+)HTTUcKN5sn5#FyZ2?~QujAuk_5lbF9RD4YX1*sbFbchQ!JdKcZ*_mO`^URq?ap^Eb)V_(Kmozq{g1cifp{G4(2`9_ z7(~n`f@LppRF0V@L4p;@-R~IwRHsCcL6ZRnRusb z-WHZU9LPoTdiHpFUGG5|#~e6I!^bgq2*@k#W}KGc7|fPtMg{mZjLQ@5*VpcQLMrYy zpO+%5WSW^@L%`;RW6p<8#eu5MnZ+W6enkug`(Wc-7e3{9&&T4i?UQJHSSG4MA1^e?*NzPbl^XYcTNIp^Kf=Oyagg6}L)#~v9G zX^BDTorSzkp74ag9xY3ipo>h(oDW2zLuDMDX*gg}R9G@EhUA&-T?u_y;tw053hzYotob5n z$P@DUac^497=o}7GZq*rlDXg10m;lYcf3yKEIWJeN%5+abJ95TYFO((7!UjK3C_bw z*bn!rYn0Rp@4H-|W_t*8pvlSzZQ}Y<-91x)7^HO~TC5{=_@iMNTjF*GHJ|2e{V9yYA+WVQKV;7Dj( zK;;{lIE`Z_$R;84I=v2cWCmrcrczOKoFNQx{)<`ZpfJ_oe{$ae!y5_(5h%@OO=v=8 zANnGXVcfedsvwn!N~xa@WYyWivenQr!D>q~`oa^~oaQYJVju8GjRB)z4lQsa6xqrb z1P5xqQwE<#UVa1)6wIVD3qk_fUnkPnfy80z;)>cC)%Ae3aTu7R28+qZCV)og>S+p9 zf{I5mYm+|L1#V1*J~K(+3bm9WMDms11TC_PhyQ23{wp!-L4q zY<(RGhN{=GmoP<$AMxNi+wl0UCDIip!KE_mWGO`iaH(D47E)*975MjlTJ1Yi8m6~@{zb}^YZ6p9S-)@Iz;EpXO38h8kG%&hiL^D4-thB zomK|CkO3>I1;*-IzS6qRi0j6a;<&*6AQzf;8C_K>P4D5a#94#2<3K#xbC3gpr9l)W z0j|NjPKp=@F}dQE&=(16$A1WOB!|$iq&;#fz0@l*mCbI^bLqJ#Mp@UN%F9LO;oizp zXK8F{&7wr`4QwvIBtvx%pm8NBRf;6IlFi?i<&}Mzq3hF%R{~XS5z2YakWGh{;D-)Z z87sH;mHb-NmTL6O1f*|ks2&+(IBnDpk`_m}ZVZ9NloR|lX*>pV%oV)W8Qg=_-?ue; zLcc`*|8Unz$7p$^Ff23=r^PhC~o!UKhMTu2t+m;3UxEQ#WTOg=Ns14v<^@PpUZT9N@6(xKqZWi*g=+OvyPjdP@;C-6c zJ>i2rckdhGU0#up+B+(N*mR=ozY<3d%i(rvL4W#%j^9FryO;1wolNHP^bqbV^|Xi0aKcDQKS zI7w{Q&}@UI$2XQ}`4Fzh`cXS~b_AUoQ%}Osub{azr@W}P?3C}y%+}DZ+06p7@q{e_ znJ}6`$82ES3ht*}-bzcYd`&e{1i=78p*jn@1mG}rHWzFa?GhWvWfdK(=~c+!9ti>0_l6 zd7uSzn~y3X3hp`X0}z5)&$naei$R!xUco4bx2;!z-g(VgZ-l|7$%6&)uj0Ty$aN0| zn-+rQ)8XzeyIHs3;IJrODo6+Ri>kvsGMeC1J%t{c1;q6(P!G0J4Iicnb26fMja$Tk zlfHiAud3D;@p&#B z3R(WS06oAVTowwmg6voX!4BkHPFj0igs|ndA?$2Iu3>~i_&?b`4{<7`K;|aT!-`_0 z2zrhrUX7rpzZgkqt8&;Y;_0Ef3ELC(`0!FCa3gwhCOOB83qLQJajsznq2JdKB1VJ- zw+G)ld4M&5djwO%QzSPBSp|@w5ZJgIF<7W_4ZsgZ$Ko1FYZAbLWn>OlY=>h^;L8D07Jf%&RhMz-SL|p z9~t@D@Oy^pgI^o|%^85#ESRiUP+{tfJt(O)G)k^|XZdtb?n@n2CaMpS+cHFZukq?0>#52JSn$rsLN^D* zU`s`^LuTxShF4|L=&{_x_JybhYzhYZ>OS)&XOXwGNzo9bsMy{t5nPBDjCAfQniI31 z0xEJgKyd>3%ovE4_66eJMaWZux@GXqdC!qp!@XV^d>Tq4beFmr#EpO!Ujqn?*4Orw z1$Ux9cQ}ecObdrDxPZpm;pTz@&nr$Og(2b|boh8x=q0#%05b##5*ftOrBq!bUYP;V z@Gym4lhV*7S(UfL}r;y;fLyS34H?W3wX-#Rl=%u4hP3s%r7WyEOpQ8Dt5b^*$m3m(tKBoK9pD~R_ z)pE31zuu=j>7aJD`3W&@=b$XZPrMy?^pCr>)Ed{Z6&Kze_JR0`(P8cxMQLqW#({6~ z{6H9Uh=+rIkeFOyN)x(UM9v^sUFnFBto9?lPkHF626=Y2sql|E5YoKmZ|8IM;q4D) zsfWen)6>gAZ;$mGEt~ZoQ*ZOETB`{3bDD~pO_2C4S}RhTfkl##Y|G-?FG$~D-PZn& zX2}ZPd-^Fu=5$fLBKYYS!xCiX4fqeIMCZlm@wDXXfXEnh%MeprWj&eAQ=W898#_2F z=Xx&EWQp6A*sVZ`IadBT#ArC7cFMQ9Sg4gG+(T6OVAO2A5k=o&gu`scoJm;(gj){H zzp0>A(#^;-Kxhr=B77pY8j9o#hk&G6$X#~b4hC*cj5;=-`D)P)gFo5vMmT=H z;Jr%}qDni7QycM>D=Y0LUp!?iL|dgSQ0$Xrm{##THsPj&#DdkuQ7@dL%`8Zq5qd+C zm@C}b5b zc<0xbLdYuODT_M<#2Bz*42#Y<60|94m@t%a*nh-|2B$@MDZ~|LYKCcNxtvERnj1Ni z)kWEMQgW0vG(hrolOT=a0gz|Vn83tbKpPnG;2Fp-*S+_9Lg7B<0nLQl;JWB`7=981 zd4Ivfh-I~L1EFl^gJ*CBb3A=A2D@&ZM2S@d1~B3_bdEN4u6ZDM2ab2PInB-u+Ek^Z zaXd?bF1I8vwHdfHi$1QzrktXyR_d>vf1o9s3Ae=8ke>?4u9X;_cFvWeUs)!#R$<-+ z?-7Y&ds3*9wZX7WwqJ2Q6VuS$$^RbRf;rTcaE3>1i?bp;!$Ab@Tu*N2Rg6z z^!^V$|M%!$ANkGU|7F-2_@$m|*Y|e*dgs-F5A^?~{?Wetsb9rqU+n)e`d3MzlmE2M zfH&+(GpMuPxRi9vVWC4Sz2XT=Fb7E25O3rP7KmcM?kR%=H9FOhcUqeUqu3pMx`c^ng%#whz&0h`Auw(W#TPgBsEVDa+K(q{E{EY>1j~ z$Tafn!LrHuK<+e^-k|oRhIU_66hl2UbvYB=nF@jeqdmxZE}!csUwcSG4Rave3P0Cz z;k8kr;uqO~Hes>PD}Su}FBapo49N)@{Oo>Yx*DYId19!2;62 zmtYA>tu@9@yEw?uX@$>%hBA-`7tvUO`9GNR1~9lPKjwv|87^4yb2sA#6d+edPdPx( zp~ZK>JvN0}6e4E^oo*GiZi?FmUWPLB$iHG;QL9zp?@+q91VDWhoqcFdQGjS^<56OB z80H%+@XM&9xQbRlM;A)J?8U_^^*YrdL5H5B_aqg^agA|e8ogoxFe9s-a8|O~ra@*I zI3M^Nz2qrl5j)Z@bM_X!53*99Z0eFk^lMvRb(gM(I3np_z5Wd>2t_MotK$#(0wT+p zH8PthbpXaTsId7=?gCoDK|q_{UH%ov$N8IPN;yYC5>w7EO*s^gfks_J%{I3i=bTV_ z)TNaRHv$L6i+gB11ehY}ZL!x2UcyikxAb%ZNd;{*!5{~lzJ?d!z8!`!o_!YEOolFZ ziP@ib=4yl|VKfyx>b}RLKc7@=cMz6aZ36Qx`M?|Tq&cc)C zWp7TBDPLhv&z(y~WUIu}X6kHrv^Bw;9a4sSTiX@sxp6${NjmP`RF90xYmcW72?g1G zR8>tK^kr-BPalOo(y-#2i0_3khxDi%^RB7&Lkv7zWTou&Lml&S0}-|T-FgFy$ILf$ zqG?Si&&V#sor$d{0#~z$v#4I+!tx^G;6n406buN3j@MQ_bPt4CJobHkN`1~tq&`ft z*$`EP&IAhyHeNletKcBs^RNk?f!sjIK(r*p0x_|-D@(eT8ZKNxsw)KIqQ<3wVu6W| zC3pLmuw7dn^`wg~J?#n>TU=Z189XGYx^WvCtLF0{iMoTh2<-V*Bfx?l9M=tGa{IsX zxg`XiAik9rm9CB-zZQ8U@i`y~hmLyQrcu9u@b?x;oeFZGvZb)<8KeedeCH{pi2?yI zc(NSa2E)JRD52ACvkMkVV+OF)hmvFK9f`b-Ow_>~|9bP%w zTJaoD8eE?9o>uZ{-i^>E=@x3)D4k+Ya+rQ``CUzIj#i~?;b=-h?#85KX!AXubh|uz zu&Cq{`cXv4C(G}EIYsGb&~ntmVF&sGzK3QRBr@SO70YJ9C0S_&W_Jn*F0^{8>`^@j z`z?19_!H4F(0-u%8X9w0YIt+VFo){r&zBGwSD7PJjz@&gw5ME;N%;B}T4QfJL>r*7M~hI*R>#kKs%NIs zjq~w{&O67}jYb2iy+ir`Lmgl3=>AmK_mBMh;gzBH#ZLAAbpJ%(Pxf8x{<)5?j;2Q+ z9{IzOUmf{7Bi}z#8u`S?2S)~mzcu`e!#^?n+Hhg`!tmMQuAzT5^o^k(9@-!JjpbuYvG>P12mbZI&kX#?z$*i<+?V-x z@Pa2j(9XT44q_^*e_OouAX<52Mg*{BVv19+aAs!soZ0qX5M$phg5Lqxb2!+1DUc^D z5m?_0v7sHdli?x0nxCq;H6cKMc)t)$MEi?Pd`YmXYxY3vz znB>-yB}|tYAeJ`+>BT!)kV}C-2lbg?;{-Jtp#sXkf;jLu7}M)g!hS*>Lvb^@Dkv+s-Q)VHv5?wNpN^x@I?65IOAy zog-m>4>eX`+AIM@)OJxjmR3G@n>;eaQSlQBZ;!N?vxQ<3d`yXryGVB2YA{~}*Oy|$ zAirEAd3J^zm4B!`s?zExja@z=-QtgqMG4d@=b+JCLOYpMI}Z492tWGOAtcn6kDlD~S*Kd2f*+1lQ{p$R`CeG&D0MOfoL(-PN+^c{fq^7gZZ@CM{+%AP`OP zsK@&_Q<02&kIMkk@C?E-E#h+laRw0snJFYIWWrPFgUyR#=9g-*p&L{D9^+? zK6Go^*bD~30t9OzgoPLbKKvR)YI#&B_1)mSTC;amRU4!YtYa$zI!(!MetTVuZ1c#< z-+K>x=x7Hel6*~9Q6t1pvl^;IQX!yX_~c5~{;n40>kK(Y`=xCQ0E|7Ag1L^JejG6# z=1pPpZnJcdQad0AWpZnRiD$^0NO>^8@*aUJ|k20jT?=J4aA7ixvsEAJz^o4*Flp9+i!a`y$RP~-F zD1tMzk;RSy|7-hI{z(PWZvZBqOJ$B7JmE$3Wp+m`AF|-oA5@ zfsJYSp)h>Hfwxu>CN&?5Na837#`EMe_F5tYi)OlYw=P18SZGntZxwOq)dct?5e_6p zfr+tY^)i{-OT@lSH^*j13<^O^>cqg|EB)1FeAC*+)(uN71ivbakUjYg#AVGU*i(ORb0qi7TvFE#k^s~pO73cE!xSnMEe4(P}pVow|$L)5K& z+r&hr#h4>`Gp0e1!gR2OOUm#d8;bahG>&55ahLlm@Wq_|wng-ten^79$|W+GR~JA_ z1Zz2jJ7vd{|NlVeH#$b2gI|9&_K#z^fjj+Or@qwtAA7&j^9Mb5yZ=ttZ+Bhj{6=S2 z$EQYiMy`xJG15EyZ-#$<_-_t-!!HbfY}iYCH`y{7_jD}>EE9-``||EjF24U6@Or&< zp80d$W8y8l=#Ug4oLitt8)Z;_EYWvx`wuvA)1Zk(t{80M+pto|I4YD&lraNBF$MKz zVLofO{rw)Ae<2BX(o?-MUEq(QwqdHRKJZZEkSsqY#b~!1sZN5#J0s_8zL5;t%$7Mf z!hx%>UkX@`-p4&@5;oaXjD`rCMw-yU=D=a*v$i$zM;N$o>N{NYp5`S_Hs2{ro`|Yu ziKjy{pIZG5W&bn2!FZPDD+`(%8aJY8CO1(sx9v?fm$E#s*`5}g0!mKp{mleO0DsHz$aWxIkjZumbSj^rWRvu5?mQR2AdT^G<9Xb%sK{ooC;i2Iw$Wrnms^fZhY(mWux6XD`dke7eyNNFq=+8{v9TbgNnfmc%9}3=BlYd8cDQuvLifivxB% zE@X_5bpjt4A#|Vy*v7P`3XU)?vGBQ~29!h(P7LK`ky0lLdf)2TQveG=Md|QJHh%6R zz=u_0@UuM`h(RkPD;(nO@L>J?9L=c}Ak$J-9OZ^u3p0b}Y8FCvOB0zjcRlHTHh!+M;b04bGF+Oq385_ndB4k04Huz z$~Svosw{z7Y>6XGGLqzX&%nUOMvl4*HqvF(mGE)625g*ThBtC(?WH35sBf4I6gZ$~ zR!WywZ+ll*ri~RvWhJ8sy5dQ@@MDP%k6J<7=%B44I|=IrA00_qbsa#s_p&Dq#U@{0 z6Lcs^6lS5R!SQ4;yDMCZ)Q}RKp3(?WJ5L`%*2x3_isK2Q)M*uXzgfsa!kP8E z(n(P$5zHATFe0dja9%WNsqC@{BMB1${&24q>sli5Gp^eaKAMB=A`UTwA3E_*Hpz0B zZ$^wtbgU*eC+W)+0yrkR705#rycJ~pq9gaAJhY_=bb~CtfYbq|eQz_xr=r@r0Y7%pZ#A5|QT&jH9bRBp5O9+@0%=D1rU}zJ@ z>nK>EA|sYA!**eM2dGj?cO_+vwbg(fb^!KKE>{5Nq?TMzqLwXZ14)n~%!jG$01+@o zZ1Z-qjb(@)iMC1@n6{uwwk0!qMwfS;_Ldo}KJ7g#bDDBdsc?1qX6vNRO~;eVIu{Y= zh}4$GP)IhT%kYd5@mx(7C6LQGP?&}DDf)3;I_5tA-%!`PJGy?h>)oUKBR@3!3xii< z|JA@(`u|qn&z}1A?)|QR*Yzu1Ki&0}u5#D4uBonA=eIh)+434=8aEdt<$#20xHl?8UB+Td{IhUL(p*)x<_^I`TwTUeo0zm*2cE8BYp#0&JAI!sG z@!}87cXcC}cZ1h!2l%xYycc+a&wZcdv!EJ_Q|(#@4$;t>&k`YV+ewiTK0(^SJZJDh zMp@IYE+wy}PX0q@V+x0rgEZ znjqDMwSdt=Y7Vn3nI!ZXL1T1d?o^G@Yf>}I+7Y{k2I+^U)G8B!l4beP=FV$YA2QO4 zKqL^dWb#Y80g;JVU!^b%t-3B7L0ZtnB5cn*rq^_rMBN3VBzO=eS-%CTE}0jkxj}aW zGK3kxIvPq=7qqwuO!LLUMinLLw2xdQj$B#!RwdWHV|CVN1_K~8j2Dc!n zeT-$om{<@pGrzN$HEavQT!7Bbpx0(vK+K}HVK^m&taX}7MfgFV0XbkE3k{rj&wJ8F z$a=*n1SceA>Mz7AZhO)J)@>>ti+RAyZ^A0V^TO7o^oKw9)zr(`e24cL(FzBT2&3!* zio>p7TWr+|>FM}<3#K0^>*1ryt|B&-op6k3WE^NKSTZgw-MFbCGEU4TmeZ>6g+nZW zU7{V=%x+n&7j+gjAAhz`FHl<{h%87`GGcw;p*7Kgymt0K@JLHoM?h5GS?|+awWVni zT%={pt}NmOTIYqdr6F$+`0p6+29Xu|W_0K`K_S7wwiTo#dC34GAi_qipcp3Z-SIvp zAm-~=1bv8u&4JK|&@77MfS8hsr%S>x%vGz#n`MMBn9c&4(sdO(&?Q0%c)n2EMOrkf z1yChfs8X|76hf{k4w)8@S1~Pybnf7+zBQ8~L@%$h)UbB=7zGQMr|Tf~o11Z7Q4jsR z=uSkhDV5KnatwI@sf@rLJs~QDF1CpZmvp^LM2C`V-m~Jo6ITJWryGwHk?9Bww*`41 zbsiulG78I&0NMHsOK7NV0P&)oL}QmZFT{ejc#FuSVR;}rjtw@bJ#JH^<)HUj@003< zZs-?^3fA4n3$+Y5N^#@0Lh< z?-9WUTp<4;5HR{hCR-%^#Y5Z`rsOd2Pyi7MdN#s}Gd%DE9kH$->=^3r-HH7vqW@p* zjrG)f9_)U&>jyjE7u$iMk)llpDiWgU82y+8%rHD$==AKmmVMcZhqz+m>jJAeQk|6n$Zkpwymu zs#%_~Yi~7@SyPzJJG?EP-_zb%F>)!FI7IZpm`fOA&VB;KZWLa_n#B;W14t!^-^K`FJ%yv zG+PZY?g0x#z)S6~$7q*OlSO{39U~J^iV3=p|1BG#ps9qx^LC(7$Agt1Pz7z+eFLpl|_?V(2G$Cu~Wi z(n8pw^pi$XLk>)#JX=G$WI;^CjY55om`Z}9m3xsq)X3K+<#71ihNwyLGjiWB6h=cj zE*$1VdErBy@Gr0(8uF&$@fMLK0wYn%NSQl=p+`cjL_k2XJ{F-|y*aNSLQ^verx&7% zOp-=GwIvE~hm(V${CBbjlc`Pg~a(RW4NYYe{G=jGU8pKN|! z6cPQ5bl8)@Oth$k0k9slxV-SYk&L8Wchc&rIxNTCGVyB!OIHy%Bn_{bSwi|4kopq;o&8uT@h7$>X;hju1it4DKi`VYIe?s93tZx#5Vp z!@?2V7&?GWD9JAg^>3r(Um<#+hzJwm>fl~NUfnGK#7ePJ0RsdxZ7DuKOw&&X0pb_} z7^Y$km2^Z^Y8N9R2m!Ma!X6?(MBEx?qZf-0ALa5;hCYl_ z;ccQ@nJANPGieRq`j*{&2ZTyM4iS8ZN6TEKj{8{x+nX8t}EXe@A#gP-yiPe|M4J?mz(w`LIv zMl-zwACRJDHMC`gwP5owPpmvM326yRW98jYO)vptVv`l{=49&8X2wYkq{{dk7Rgv}CM#RqCnV79l+J7GvC{^?qW2Qd zS+Y4Oa~9bIO=qoFcFD}V1~&nosqOjM2wqEIJvijB@vzgwndvOXIBk>z5IacH)`9R< z6#SQK6v%)4^_%q6eFFEQQ(0XVj2Dgoc^#?AE*D<()(70ifO)~p1V*cIi;Ujv^6 zH6;rsMWE2Ah3uM_*yKWnOt_)IHnm0A*RHb;8j>19w|YJHy)wUw+k8dv4R;|jX4u}g z5Oz#^A)+MN%l;%GsLJvvMVepH+8Vlajy5iqSI(Ct0Ge|tU1xxHa2htnu@I`J`>Mt^ zwOO6ii_axa#X$&_tvl-@v^BIzrcGp!<-6XFet9u;s8}WZ027&4OUnCS(3cnY;$GS- z3B%@>G8~|aCy>+=ksd2~$H!*aOc2>u8xPm5VhKqHXjLL4Toi6BO#2>1c#o=NJmcO^ zLakr07)+A2traU1g7kr{$L*Y0=FrrIhj5L82Fq@vO8NV$2NFy!NM8u-dV_jbk>2+U zfe~gym(fdTGJ#A>+$JoF$oWQxl{%`U-csaPL^dx_Ej|<^(dd14(fkLMhmeS+9VvsP z!xB!|d6f$Y&oeD#$yu#r^G9a34y<;J9jW|l-W>+e&fXGMa#U`EW~q&p9En)-77112 zE}Zynlu;7N)J*JZcCUsU%%y!$Ddg# znKe`yx;P?!wZ0<8RppGFAYp%nJN;0RBdh_A*P*?cbnB)KKw8CCT1T>Lf6BWnM`DS2 z*%EO$@8L%xV;zaB*$tG;fS74w-9;|%g#u0}ChQ^(h)oXGta-O3yZGJ>CC#JxlINCn zc5ygtLA2#Q^l+RQJ%JGmDapsf7X%= zQ^WZLhR@z3H>)FaoGm#BSEQ%0{qQ72-*is5aiSGPrIuJ0d~_A=Bcjo=MKnIbd=QKv z9=WDo%-dsubJputg3blo-b$FDTd-CYo0LdgiMo-v_0~Bp5pPD>Yg9^3XeY{;;I3oC zHX!1`iUS&iLTU4`UJA`AuOXv3xTHptBs%4=(b#*8WG+8dv-NPCgd`l7V_^pA&w>V0 znIesg))Q-0W&TAfYh@Uj?vyVU=xgB5!Cqx~&iCHih9n?HT7U@};`MKFFcah=-ZugD8-Rgkgc$N;GuOY84>f+!GoAHU1B&3Fz@re+15fO|4 zSE?3;880q+}j;`Mrd8O;)!@t(K-0@qnm-_##|K+}a(D%ZruMHQ6{^y~!jvBN2eQofs z3|0r99*hr;#C|9COR=xUUXN|XF2+6(>mK;cfu9@rv4JlRe0Ja?1MltsJAm2mvH1GMm0vpAlrU zfe4be>pC)mMGyxmCilEA@TM9&Q0z<@G&Bj#+Dil)njAA_4}q;@Jnb`a6U7(FNk$R? z3bFNx;aUC^8-}QmG{QKr0g&s9!>bRVZ474}ofl2l_+pvhYroM1;3LKB?LF>&j$i$p zcSRC)G9V!cfAs}bB+pFA|zvGyhld=I@F#N_dwS9Hg2GD4o) z1{kSsO2cC_kL4p1_{8IBcgjJg?i_xCxEnuVr^nOkzbQxkVS#>~rjNlpwD;A+}R)ty_48-X;#gTIm z`1V6Qx7n>`q@o^^i67%JL~(5Hu850ad}cQ5Gfpg_2#z46TOu73Y1pbDcGkKO(M$e2 zD-=L54aDfBCG<=GJ5% za*{J?Dn}u#&vB<+{Wma48+vMTQ38W#mYbb2h=#LGPs3p|&N5&iZF{l4_}G7oKU9{f7m6uwQvwwL{2vDqOr z{+e0#;~fe3OxukYv(t)i9I#@Y_Z3k@RGoSBdnDE@s+O4oF+{D7x|%$^s9oBGI|_IJ zh>So__CKbL;@`bt^kt?(BV0Qu62EPnZ2&$UlwPj}l zzarNJ^k4|;uifGkas3v%xUkqJ$bG7To2bSG1t?~ezDaDTPjUwc`s%cELk6Y3i3i|a z^u2i!aQ786u*NQAf?8}GUyft>4w7ttW={vmu1U$&n6n^1WHxJ#H(}?{dx5-$b|o$) z&s@D_;MhQ-f9Xw@>Tl$B-bAyH_YxWjs?&>*VYqmmw=H{@rWw0m$vl=>3}5h)%+v7! z{75=tZf6G2$hGWN87LuA{3-|tf{p;tFTT#-qwS^cLnhl^F^?=L+9JXJ9Hr(dQ3P9R zr04_}K2lrQt%07h=ByyUO1S6+BwZ_(D68>2I9s==#g;-z?PKPh_CC*M@3`(;kRe*= zt1E34GbCOEjIBXO@-QPiqRt$!OpVWER<*Q%ioD>HS4_@G>G?sY_X-a&dGD53=JW8& zA}sSvh%X*tptnGXKF+wxWaaHxBFtoT%v0`FFk_1MgWuIi*EcCTl$IhV+Ae;P{{0cA z=)|g^7sK*H60=%SG6J4AiaBP=(1Bo*gOXf^aUcz=2F@S24ALnjymEy3|GKVpbbX`i z%1Gz%!r<=>z7YG9*#1DdZ|c!`*kf z*SnM555AE@d-9#{${DCM2SmQ@DEStrp0pu#iveTKZ;uq{wV~uGr086rv0Fe1Z8iJl ziO=aL9>;RKgw*$4iaG>COeE`7u?%y31)kK5Hy>d=b57IXCNojoD6LhQ5r84!_GrM+ z|Bd0gpJ=P)qj#{;K=$_Mq(Mlq{#>%STxk}B6wId(ugOoBA_F4` z97(uECl+lq-^Hsl_8dpq(L6V_#V@o^01*oy9C$<25?uXSUOR&;9uffd*7lJ`h6Gg!u;|dE^(kA{tZSi;h$hurZayr!s`;S24```FnOx|F zL|2j0zJ`bqm+wg zLLNG_WIV(kc&i6^@1xBJ#oT;dF^HxT6#2Q)8atq=v&~IzBSdI2=S#wy2+A7RZzh_e zA}Or+k*kI5u38zV9NR=%Zc`9MQ=bYiL~Xl3X*BTI*%G|}$Dq@|9pa}1d0oA-2RdyS zZ^c3h8A-AA%%tp$K7H##+!s-vB;h#jFD&pRG$u1a8kZ3zRP*D%)a+ zO6r;4lOdNaS9ZBG35-tYXcp7N#WjuOu(D3mnpI4hv!x=ZVrFqym4x>B<|xm^xHqcg zP$snAiq2AGjh`MgKqiQD42Lrb0?B7QaFtVu9O;tI{m(Z?`iZmntX3V!Ia&-OJ4>z< zcX!d9hfpMV98@twk{nGcfF(rsvd}B;#hSyi;+|@0lmhocNV*x`b0I`;Ecz*OBsN%2 zm$3t6g(Zd~$!_?q$_xssc}b;{+?3{}=8#CRdwpE+;HJU%8Xm0)sO4h@s+e0s)-h;; zaKK-$QmZSI1;9b!Z(hQ%eP?s4!78Z$_(3chl8|$#NfxLwPF-Nx-r^>ZPrJ(-!B6e5 z=9q5gPcnF4;6oAUrW;<~(zL1Gh2|hn<=N&t#r{mAacQ{4YX{yhxsL6=PXWtR*&ZC~ zrsv-R=?kUFK)~1?_$cDXr;C#Wumv-G=8Zg02q>$#qUbbz0 zOD+)FH^8BluQD0Y1SA*C4kA$k8)NKNwp4DQ`KUx?6Vre|0Rjde&6?uSr?)-a2|mV& zT}JW4Mj=c2IEp3kQV)@mlf8AiFE)42C|pQ=6%Dniuxgl z2IV|qZmCHuQKs!^iO}xa4ENMKM!LS$(YrnJM?L>+q%iaYLqmgKjQw)#Jp(`0-+k(@ zoqB)Qw}xNt{h8js-uwC9&-6ao`<|XZ>G`#uzuWVJUr_b4-Da z1xc1$008@KgN>jEwj6m8Tgc!+AP~iE1?Lup9h?iQsaQMm2z{A~3mtM$Uy4bmGvt`> zkR@4AJ4(|0A*pmx+=Ri~TYZlZJZiBxIKRrSzyiQ6jH?hZQtE^rBZ1_Qqq?OHH##=n z+70H7G~dHY&GdjzCPS-q$j)BYnUVJad`C^JNw+cJimthsYrdODG~Vn~fCy4rLO3Nb zqGSX^k8PS*I1NMX1jkTXxG5{6wtLY3g8hu@+;o}SFsu*s+wnAU_d{v z3cN<-GO|g_npTd)q*E&txZ4!fW)^E~sB)N6nmc11=opDJXGqg%iLB(*Ju*N2lAbz; z7RlX$Oj)ZtD-`P*_E4VYEW3I#rj=2ZmMr}b+tRZ3&Ok`ID;K*fRGHNC< zNR>d17bO7GycnY#`*xSjD$WhY6i~e?FDX&mCDRx?)kph9f%cc}V?z!TGYineO%VV+ z%N_t8k99?P2iMQyZ$fjxRk5v(48qL<)ljf2u(j>6wv;+qLjw4DSghuD^9-NF@!NuS zCQ_k&8n8z5;SxkP6y6&zNJ1ch4XEy*excNC(-Dc~y`-LpPKeOKQ-V7%768E1Q@d0G!yglr^RX}%j9v&HeAD1WM-o?Dq6lH`&#L! zFE$@nV^RI25_6#p6PUe)(6I!UkWLac=?Hb~m1CsAWUVE0&G+&Ul1*2HeiFI$;o3&w zV*!-i);O)$5Svbc9b~jnpoGvb9W)pV+V~1XlQ2X}6BrgskRKI#YuU{jZ&&sjo*3S8 z1;)?szqJO?ch=p#q2^=iTu!T9j!Kn?Wewya5a6k>o;rkqG89z9JXPO=qEV^CB4`eR zb_e23Gar~u5VS4&nvb$=ak{yuG+rXgwn)a!Ru9*8r0SM+^P8&xiV2=+Gcgb-h^Nxh z}4f7$Us$5hAY z`p6Ft{pjG24n7?F?7&w4#lCm+{%+5|=>CO)KkfSWoge6!!et#^y_psts)_bgu$hpR z9-UJX0$*RD&Zt)Z98}sD_BgSb>Lj;N3WQW9fbxj@Qi(BJt+e_~Fr>TXLMyPb3z!)N zGF*`&)4_G~E6=PWQb;(jPRnA|8{QD%$V3(4NIY0=rg;0FeeFqMCYTTJl0|AIl$RjN z1R*^b5G_&-N}|dXJ|me8>ED}7CT8(UDI*2+mi6BkUdzv4K;;O%Q!LB z5i6Ub_aE15h4$N2X@tyDf$b6V0wUCNH64I-5fExp%yVlXn@a~M1* zp$$*G%gv;$YI9O5f##D@y2f7BYj5&Q1{aPDOt1mS`$K5KN#H(X`mM}c2L;*#O_$%r zJ(%Pjnwm>Q*=k8dCR>WygE>za0>#HD$U)0}NmSoO>dPu%BDh7Q+*o!3mRmOnLTN*L zAJ-Xb45aTS)TMGWj)D8hRTJ}++f3EJ+)gUUOVj2S;i)bBL6j)aKno%jvTu$y<9ben zuwf3_0pUuAqP5@EbE16eGtF6^lCcA@JCf5o2jYhi{y`FCc2ke=Bo_AaY8E-Q@%VVR z0*x+WFXJ$itO(S#wW&uJEPRQC5Sj(mBQ!Yo*`8%Hl1uL)^|(kDh{l@uLajd!OoK(MzR zErCUXL!y_jDj?+69Sz{EohDIVs-1%=pnD5uNKRh0ImNDfQu=zv({SBG zrzhct*iE)u!@Fj?vOBAkgB%Ok2IAka#!%iAoPaZjIs%_0KjGs zhu|!`m|I(@VmWz<<~b2K-acapD4+?Y@rJ-jD8v6)OuA$=<#{S7Jp>8Y66NMy3?)+J zIuP{mzTVR89t~-#b&-*Nw|m}-meoRY!xEQw zt&(mexz#)w8ANl-J_|SNsR?C4%eQGPLGoUrog77%|NA5tcm6mJo3}tj*NUEyp3}jW|pC9wWVqhSjN61dSTftrYg)n zMgXwv%3v}Es0X6XiouK1S}Bbo=7k#TaQ}-P4|ZfbMvJ2ljGP+!^5ExU|NFo}_ZQIj zZ?9*p>%q=%oO*BXi#=cK$PR1_ypgC~^O>gZEBP^jk<%$RBIU>pWO`*m;^??AhL4HS3Tno&Cmh?8*nYqpj)~;7HlD!1^*49X(2A)_6bxmrfNT$a zc7>}9@CHs4ZtI_3pdXU6MOP|(5e=@W|4@thBfsQr#%Bz~Q%q4u`AAaPzSca?TIy_C z1=AhwywncUi}&BMt^OYrwLh@`doa;Kn#mf!}~DOXDuZ*t`vCqkRq;g7foPtD%6xEgnzM6i<1OE%F}6>WCU zdNnl~pts>W-?etWJ4bB^mQ+q0odBYtQae>8O%e3S4$fUCxib?31tSt7Osy|n?=0ap zm?MX~hGEia_0gW83`e+TeLs$Dw7|ZbXMF>-pIKi%^3Qu#7mXgJLPc@fh#INgR2dWf0 zV%IKOTu3G{Q5UOOG_}Jx3wL31AOK#)hgYd+7bhk?lh8$$7>*xIWfmeL1d1)Vl*avZ z14a$*QLP|J5`R~(ATd}nfnYYs+|yg=d&W|68bQRDEm~v7Az?Dv%je%Dfogkf*h0RF z5oGHGI3o?z9|hG610f9zhO+c&C|rVFEyOb5=fO8FX3?YrNf#v|rQ&-Ko0k)tOS|;z z)yf9I65wjJLL*%f37ZwWSbcEKjd6Aa*%Z%Z}kNWQ!cGjSVfo=6sor5BPejWk<@ z35Qn%%~PX<|F+YWb#!zxkMN-$>Hi;p*}U5PFy~*4-BSu_4nP?BqF39eZB1#J=D!uR+*|;e!!#GYCA|stI-$ z;Uc>Wb8#M|>M{YhNcL8Us@e3E_UbKX0j<7Q}cNL*OFw0b?H zPRDI&t!1mp#VUA?F}sZ_&eD0JL!x*M0WQS4j2F0!QUIDFBHMD70hR5N7dNwLH%XKp zgwP>yi*TFkH`Z|k8CAN=lUQ3){1fRczcN+v_wRfbwQuSsspiqGha1n3I@Ut$lUI6jY< z4zy2pw=ff1+u=-YOK~D`dCFM_dkX4iv{e>ZwdQ_vfmdzp-c5TpFTcTvi<8f&5gyfJ$ltvy}lpUUs zH8a-CNTbCvZOO}wY%L?%bI!f*z5jmZ{{XrbWQ!8X5Y>&${Qv#;-@EVLbI(2ZIZ7|^ z;x>}sS0ENX=Q>5Z1uAZniRJLIgGfT#w+g)3+nmys*nCk`1oKnk_Q$9QMoxNGIANu1 z)AG|DF%d<1wZH!g00+c0Jb)ydE86C09}Srt#y+6bbMQz9+uw7%`gvth`)xu3oB)Mh z9xg;hds_iEv)E@1F%*tb9*0Jw1HFOqLSV(D#B!^KDIjp4xiWh(=4=U@+3eAXHX4fw>JY+HXetNdAv8xI1verv0^as~WrwaC zEsuQN5)=7{8almd5r`BO!%|O-<*c5`=`8A+S7cv;Q*f`kw!P*Ri=cdG-1_9)izs!G z?{aTpcXK3VluY|yTLIf;Z1(2W8#l9xNu0Z;2i(j@AHRBY=Hd&@+p^?OHF}ixGoDLA z@Y>JecB%**5TPxj2ooC|1*YhCx4pK(^G7BDh7%_iz|5^3i^4t7d_wL%*qGGQbsRn9 zWA}1?8ZiDHII-aAF79~cK2q6a&Q;tAiFq9SdbIXCFu>FwZEAF74&JtJM~}L9G#}TI z1?SAjAX$BYk=>G*mHsw^1~oSL2mV3pSPQcA8T)Lk;^orT(G_2Zlix$z{BWu>6egi5&TiwA}nsJJ*5VX{eb5z0A?1&%0J@&Bh?`v z!*!%pJ}raxKIBi$XOZcdYu<|BTH_O?q{;E3dzj$tEk616GDh+~krR)E-$$BrGU-E& zQ)V%$qGsI2jd0M;dF6q4ty=rxa+wvjd9_C&$G1dBF?XK;Cm}=gX;hby?m^FYN zlL>1e@|g07@W$Ymqbzl}xHj@Wk%rdt?^$5^XxvuY#Ov43%<_dPptzGiCCys%qdJDx z9UVgn`*Z&>T$i3H*_riaaA#MtH_Aw%SGTs|{~@3>QKSR@C6Y7+)KWifw<(?jvx%p` z8PBCs=J-a(VvG0|D_|>$FDhXUR}R4Vdi4d(TVqjS?=!mZ6_{7OKbLWXT8w9lJ(k$Y)Dh;?H zQQ3$;<&6(EXLZV(N6c%VPS8n-I9iwCZdMSC0~SytbcLJVm>e4Z(BGHIfN{rUU@LP@ zRTPi2jXy#$ir?RmRxly&s`@;bc&Hk%$O6B!RolS7gC7vto{iJZ8)oHJ>?0gxgE|c@ z0kr(F&+rX!o6KPy@U=2dF*DARPB?t3s>Sc%E7*8^3M6tDR;m~umJWb>X11`?BmnWJ z{F&xsVoru`n{6N8?gP1)VkG_GJ6wr0X2Yww-n?!$(vX=F^wrpZBVi^a4nw>M;AhRw z3Ad`A#C%8xkkS!m$N`YKPOUhis@y!(DRCxSZ2oEdFF1odA@KkB8)yZ?Nt7LGHV-xu zCX(!TT&swB5>W4;!jJ3m)R6Opt6JU*tA-HQdAJq_rYu{&9osa?<|uH*@vJB9V@@jW z6uBNzJ}Y8jp||K7>up{$YwTfTnNXv%|AfwBlvWqb=rTqspNYu3cmNZ@uDgcKjfhu| zzi2w7k!gO!jN~nLBndJ@kqs;R)0kdB9#V#_I~Vg6Zq|YpEpuUSfwh3aRS^`)7Vy__ zt+8{PK{ybI# zPf8p;Oqk;IwkjaSv57Utzmj1vf5ve6=d<`<^~%m>$K5}b!b`>fXkrXP2+~#;?yBb6 z&Ss~P$&z=tAt;iaa-dB_;$^6K$!32cCJ6gr$+P8iHk~sH_XR9lk&e}2%VCY56voCe za}gw~O2PPmt60e=-?DE@{1F3Yg@bg0Q0UcFV{zJA^R!}9*Vbk zeQ8$9WXBXq3|K=)V%X+wR3R3#=avBSE=o}X)vfprXW3{SfX*tChcbr*+p$vUBhBS= z6IlSh73HE61VyYq4L9Z-9MV-J-thkL-E$r$Q~(b7wH{YUadEB;MA-&jw)1wyW&N}D zxWXyCS8?TPFscX5RRe$un<5SXEqT`8Chkl$jwOP3?IMCbaK^?f!D%tpr-CC|Q&OBN zB~Y=8Z8?f~Ha24Q;aQx`b(m<_!aX~O5PvMP@d+zshokg=*$~PR0&&tX``IQIn}f_Z zOQ3NdvV$}o7!PoCBxL?QWWVsB0T!*2dd=QIxuK^5C?kQzQz%Z2|Jh~$tVVQHSYIS9 z15KqWJggAZ9b_sD$?+>Z6{ZOE?ZJCxEu3h~=uAu}LlZNW1>(miB*~jzpa_oSZ3yNI zp0H!gBPbw)QBG8^-WluFAsROKVGy*|OM#M42fwj4$v0G-K=c?02>QNHJ@ z_b@PCDu!}e(Z(PA)cZZ&WG4+Z%q{_fvy4<*Y#uGVlSebo55KC+GA0CarpTH|e7KCDaj@Ha8kxCB7pq;PB>$vxC*y!f9T8o(-=3L|Y@%3mQ43zJh zZypvafTvAR+K7AFs5$T#^;hB~62K3#s( z!0KY_0CJNvtJN3EAiu!xtFK`DZ>_{HKeY_VUU0o!0M%#3h^S&@1|~r>-W-X87#BJb zGc2K+x@7>v&x9)I`T`XiMZ`g`Xtu7rYZZ9B8q+4!?SiH9{AP8*8VB5o6+>G71M5ka z&^qV}HYDO$EylA5u4*0La9g`m&p4S?4?IpV#vRi`QzqCRQ(EUrlR`?p>_J#4z_-Tn zh_Z8|IWhs$LwtSM)vAh<0wfqES@uG@90ajowp1V3b5xA?X`o% zr>7J4~@&aklJSh6B;nYs?j3Z0l{w z;#&l*j(!V|Bp#*!Dp$w~H?@E~ctE)W$%ab3$WyQw!CS}p8pOg7g(5AgoDWU9!Hi^| zpuEl#Rgf;UW*Cmd_U4x5|EI5l|Nj%I<5S0ec;NR3P8>bo|0jnFhkoSHq26EUeX!?L z_w!vp+jT4Rlc}Fb|F7w*nPTRx$n5{K^xsQ=OZrawDt`Q?{HJle1<4}3m11K^_T@N} zgt26boKfSB)P|rxqwjmcJ}599UBd2+@Yy`5j;Y)e#L^z?x==uGeC|z|i@&TlZ9LF= zMApDy_k4wT1BqbGzeV{JG5<5KVgyBFwt8Bfc}eVL59zs>rAT#$d&k zJvE+fWd%wYMD-7AVJDJ`J#+cCalNj)83UDXspip0qi&`FZ20(Tm^`;yxrepB2{~gN z;xkE3m-LnNbUH%1MR^$sB_gfx2yC?CTJ6%<@dLCDLNp2&0Y+|A_~`DmHnD#r-%{D5 z5!QCe9BCWVPR~B~<`3EuN>*B`kCbnI%*^yuLScArVvoAe_LZhCE{(sQ4X4eCVI?o> zWdP0Ay_JwfRIc$gfXkv(cLg5o7CEW;!kQJ#$Qm_{`1*0arikMV92CjxQ2gsuONE&7 zjk0|~3DljqXKiQixCeVSEI5UHTalbx5jciL5GxMg8EAB9vWJWrJomf-iViJtbXtm%W-Kk2WAaMu67-fg5 zdb?ffJd`Ddvv#$SX+gG$zF4D6*qNwKPF$1@Cu?7en4x7z1*r?g{@(!i1@4!kTZ~!U zqa9#~@u);_sRboZD97hteNb!Ki!cM{Vx;5ac818c31e>!*;4}G);&bf0yMlzEk}l< z;^7tHmY_VBg=We$2X!9lLxrZ$Se8^{;#EuKIx9xlMM8gwp7l_MTHMBkRHTJ$TY8R| za4LtuX00+0dlEiYH+5jyM0 z`8Y6Y@O4o!;5{NAV7^9VS7ye0K=V}MM#l^da|g?aWGpr%13|1gi$I@mJt*rb-x{!Q zvXHRmV+?w{AK}7rt6$-pdptdtx~zJ|8YE|mS{!X-FVBFoQ4@P>xGLL&$AfGh^J_qH z$kpO6B--naF(w?fz*sPtI>kT4wFTr^#X<*>^K^C@HHtVda6Nvs1SkRjtatKpFk@}` zj#%cs%+YxkB5xqnRB(0mSl9UKCdN*mzvtLpgg1zOvLIAn9uhGB6n1_xxfD&5AIX z!REs{m!*(U+jPT;me2qyS_}%neNYa=exeA>l-pJ<4bFqU+sEV=2tU6nfYs(dzssriOk#i z->=>XwE1FlN49sqrSdBIJcKC|GF&lQFA?7n`&|>vA`0(3qH9hPlG_?7$|nd7>1z-Z zjBg6Tptjy2izR8%92_}lbm50i#0JaP-7T8;?Ysmkk(LqKHM}SlIHm1T z9Tz!kv3B4ow0{J(aHNUo{lZ-Vp^0C`#ufu6@b1{ie(Elbv(0rfR@cBhfM&LcE;&4Gw@my=UH3HvjCD=N@LMzMqT|E*xhx~oHRzuk z<8uD7%bg~G>GOO}7AO5h@HvI34+bbIya5_8YqgC6^)1qsV4*IADRW*nGzYLd^Q?o# zWE#;ri6kr%DuZat*wH@?dC2xWGjclP3}Ec9d1x&ogNFE4%#H_dufXp=Q)k~N`W(~? z0{lca$yv1chC4y*gCkY(ecK0%^8*nJJk4IjpJ2GTsV?5`w0)&Xg{OsshykCkLLpQr z2Pi_kOlOBfyHcJ9Mg!G;)YY1DQU3WlI!P}pzA=kugQGW#E>B<%VDK6|3YW zxWD?R9z7MnGB zm?5Lum(OFd#X`#Y>G)0qZ9y-V0&1I8ahMVjcS%S!Jpth`XpdhbY^uA9@sERHKbW z2qEGs2};s6grSN8V+*TPBh$ByV{Ltl-~pj^*R__#Q4EDrk-{)qnJl%XY;!YOOPcVM<7T%~kIn}Jn)1I5am6?)RXVzzUt{3?f%}Ii!~}=HPCIgMg}^Z96g>u}8FbaUYzy zLTo9b2$41cC+k*?XTbWl`R7D>W><|{_}a-2j6HQwTUdrg(6a$Cb3#;mNnjOoiz@` zbW*33!a?I*M3~w0)rB4S{HQxs-nxW6wS|Rk;cl)Y=;6$vq~Rt4sJmQYh@ilS1oaRm zv-8_r6&{C}z!~5enbPR;l7O|qeo598%AI`6&%iPn3v!`S0(guVPk?cmTC7rx>;f!Y zWm7`e1H?E(&1WM6_SN23)q4ou*D3j$q4ehQmbHMFUbpt z?R23axxOeWgdc&0>ZTcSB|~@{Kp7MT+hP5`uJ|QaEipW zvccATbyQ80Z?Bt`<|oX#JgRoE2p~2A_lpRT$Dhk}RQM0WOXZ`?O1mOw@b-WL9oQFR z?Enl*^;BY-VA?<9JC-*aaO8_t3H@fJy)#0%D{=27kP`HjaQ4E(S~ZLn%!I;-{K3eSkTYSjFIkoGO7{ZVJVo4J`4H=0g@S zMcoN&ew4b^S<^hiSkWI7+rta-t0l$tj6`` z$HjIGHBHaMViG~JP>3WE97?9j+Vli`qlJQ7L|q#&WzM=q8(;C;#xgpC$QA4xWPF8? zwU8$WlZj@Jm~k;I_7s$J$QZ-jspiLI&E~)9f-w%LQ%XWva1?gbio~znI*Po89rFwW zv&&KTS-ZDG9Wp^z!XpHa#(gVvlla8|Zpo%BZauM3YhsunUWNfTjy$?G#sH{@Mnx;= zjJpnR9UmE#|2DMyAnSsZC;2CMcaWNqv}oo>ECH=H~d&UY@^JFz!dHmMsE`Fva-O6ly<{xIH4W`4k=4I z=5dZIl<9Dz52vrBx}G_XY}>JS4t&qR z2af*y(MOI{`#;}5boj>(FC6;WzJJ^|*8813U+8&9_jcER%>4VzM*27KlTTzys0R4= z(E0me==@dFSJFf2-qdfTekS!jsh3lqz<>V|{ipFj06xemumT%{A|H}7@};pNzQ`kW z*Z|KBn=S8btT0J2d~Pdy1xp72{`u9)UC{SX$bmf0GSt+ri7X4-x1MhM&PYTSq|qO| z#f(gq#&VNI3E|$z$Tr-_KD@o|9NIYwB3!}1AfbnnwMD9e>A-OCu0-@EUJC9z#AzB- z;eU)p;EjZ___P^se!@}AApweh&?`ufn$liO4AN)-lwir^?vcq@501<2hsx$96@~5* zd;44=0*A-mpLe)h1(v|4kGo-Gp!tG{ktWqq20Rw;C?`-HBA%CX(0$0+N`Z3c-z7s* z&}R2llUK2xdn!04%i(0B&(47yR!a`X^(gElC2pdpWp81s8>+|)!;ZXil_aDEC5z!>=AcQS230tx;^s(>ta&W|WRgWkkZ7Qicp-_yp>z;{o5ilkVsmL1R`AOh_ z0U6RTFh58*!6->)(Rmoo>B3P9F>eU4P^EJYf(*bLi1bK#8c(Ydgc-KB1pSG8XcKu4 z1pQ{uT-D_U^M4)mZ>6OGF5!kDyg)(+2mhb}d zH49loNnm3qh`=Mxm@n_pX$+SIcypkX3#_B*A9$?cr~aaOEa=tLg`sfev?ohv>Y@;z z*Ylh`ec2m9k6!Ro=7AHT<4}RP^YvIf#Jjx0!HQXK7&Tq9z8(y^&BDK6Zi*Jj@e6-C z60==vOY6u2mncDqf~;!mRFoSq0yt9T2m`-z;>e@C``yX*!o4rPZw4z z5T#sX)h(SMYt+VrjKaFjE;EA%fpvkl^7ycCgLT1S$J{IS%(!l0u^SFS&kjo$-Ct`j;-wyFp6!LtcAK=5wHZ#IjWih1XY>N5$#(b8n#rWo27I@>KIvx)V(j zCx=E8v6~_R|MsRPdJdSi{ZjK4z5b4>(Sx3xaQ)D9#6YW{**TX(AYddx+#kg8h%a@T zZ@w(IA2M$~pCn7P$6@C?si~>g2hA9qH3B0@*ptt#e%{6HO|V@x z7?SO|UjY76^F_10?$_;=Pd2RX;XAFl-9ynGCccb97zu|Hif;U>E3VeJ=60{=HI42ocgKs#q`Pa$<+Ui=>BKYFQ-3|KAS$<{hscg zuHWi!$5D*tG# z1gNRO;W^UO26Q+Qi3o2Hl4{`S))TtW;m&^15f$Sk8JkxprIWAfKcj(L z{n|CU3gRh@AvYFf8qGcde$_g_MOo(lb;Q50bSv90vGrV#mB@GS8y^=rvvDbVN+81> zD7B#ZX<=Y;_AkT4SW;Z4nfaQC8I7f5a&V3Qg%UCIm;G(*5iry5t~Wjss5}ECv5Zxi zNDz_wb1jKwyx3=gcd17JF?Af9;}c1-)*ciY&*_PtRwmRq8K}&{U;}kEaufV-tXGvU z`Dy(N;nHF#XM9=M5-_Ey(b4X1T4gYH854k>FigQl7ZETCbp>GL*P!l-2*eEYJ%K7D z3^|2_$pm1pP>4`_{j&#iVdF4&=7d^b(IO=KL~-rbV-G5GFx?GQ8v%fy&gbMrLaLte z(GF0Z^R?B*opUDOk^~7QC`?plvMSiU>dsltF+m@83=mR+nWMP?q5?P)^UYI=Nfn?x znouMemlt#AqMyCOog?|EuXxSmV(_qTf~Un}gUCXXS0050{TCngH`bDneXrcYYq!2refe40j4*Fq8Ha!F59=**8Ktp!^Oi!_O5I$w7U zY%?8b1^ZC&puW*klEznCkBFP->}z==ZhOLIQ_`?x4S_!nQ*Jjuu8dV-wHTm5^N!Ya zNAoigcTiDtVWptR26zXoS%lmtR+IilG@dfqR^Jcu>>%Mz|x~A@_$-c1_ zEtz^AH$&$39SLI`qAIv>5%Wp_`OqT}HRp8rc8`nO{i%625S{`{tV!=>NIDZ=*Y~E#>dm-XTRKtm50%_-!Mx zr=&5AX&)lJ9q8Ji36tz^@Ig>Erzlg{n;CmXU?=Il5y@^hwga@2AHqs%e#8)w;Odcx z6a&^qsZ|MZ8VE?PBpI0!zjC#+2w7>_`_!(&p)VG-2#&lwSYRBJwnj+SDa{NN5_l#U z&6NF}Y@z*M04CnH@k3|Yg8LT%6wnn(TYTJtg{y*yy3Um4Fm@6S zePlqrCST~1gN{e`b&f`UK(j`dfWpsWspQc0d)#aXXT_f^LJLo*w9xE|jdybUH3lVD zDC$|>MVn^ZsmEm*&E9W*Jb0hZTFdkyguwd&X6Im^xNzEsLQHXF_^0`+tLvQuL0#9 z(pfMZm463yU2d_lAzQhO^_xGibD{p>TKO(m2H6Wc8{iYvv$xo5G4j=^_CHM9KlpF8 z@!I^g%(d_cCax_TomsYfY%WgDS=b6u5)vaR404ww<5i-D@vXt6j=quCcQFn*g#AY^FX8+u zI#i*zTzPX$8ZDyh{mpPEyQV{MZ)BUpGT@{yLko%Y0yN!}B30{_`g8hNFd_T?a45bj|oOAKXK>!4(>2&*2DNPE2@ttFb;xMLaHPJ_%Txwwi!qqJ3OPpIH(IUcHgx>Nbrw$97nVed&*~( zfItyE5|*{RNBbo3V?zS*NONO0Z4%rU)_9vp9apbMsa={?OIO5((>r!w=-pSe5RPQ!F2_kEJ zJG6q3p1{UCKs&VjSV4_Voq#zTq7|~U5m{Rke0;6#*=3Y5fb%+X7T^oe8VXTJa#BNTS~Vo8I*-VJ{?wOJU+Nmj z{BGtKGk-VpO_>$^_dS`O^lznqF8#Oo-=6R8d9ml?J<~l8b^n3<_s6=wz5BWD+3vCK zfv(?eOa$6K_XIoND1|S9@7a7Jm{k@ya!57&;GY8TCrT#OI zAbeQ_9#F8j1;qv}`oPWyi&}9oLETtMtROB`+3_dxq(EOI8pfz#U7)cqfy8ku$uwzE)Gnfz1?;0fwL$5xfpbFBS ziPc^Ci3qnk0aif;{7nFWd^@3f!U04R2o8>;dLvmyhpr5+%=KjX+W^_C$`%PxWvo1^ zOk2>@;S!ohB#Z#=FH6?Z61)w2wslTVoA5CCt)cV+D2E-~=FGWH1(``l0Mk7kvEpW;p zmo2U6A@GbHX|fguOVrJ1foy{Yy$&m!57~zP)6;bkYQ3Ryx*{~);7pXAR=IK^cEW#W zhh&7!thMC>8z9~{&3qIs+T#h!GZzt{Nj+GVt8#EnVwDr76g>wND;7V?k2}K^PfI2j zznzAx$=F~x&_3WpyA`uOK!$|d8iTBsBI&UNwE;+~AU@HR%JQi;A;1G+si@f=FDe5A z-&ZA8Qr5ki6V6{av9gt7W*Pew+Yce9cJ|nYFeE3URzuk&wklK_ixaxL@lTUR3EG@> zygUMbDmm6QKgK){*R_JU29>G8)~X|oxZ4G^ZVhmpl1M{RMHtU|&yWfS2|^^1=vzyK z&J$Udu_WdyA_?hAVeSjGTlxvB5|~4X5x*iP+=}oq9n=t4>t*a@5qGLW$B{besxe6e zCuOKOics@}$4BT@ma~^l17O(!$<^u`6nT8!2_`hBgR9atV6>&_)qDa`QN}|$i8_0$@4}8~h##5XhufawRoMC_*1Q6PP0tg4#2Y^jZdAq=W=hsnE zO8Y^ORUVJ}T!ex28mqNs?0MdtS8KnOfR#p*Q)DRQn;53NIpcspziqRkr#hxCwl(52 z!4=uyLtq`zJc zw6QW*yCCR zB9;ooXjjF6VANtKFgJ*!aS#C7HIwvYa9(YIT{lxnRbEl|>>3PH6tuDSM=>wm{?7;J z#5xZ(jCC%cmsqTIE=8O_&otu%lmLszn1*8Q=FscX;uo-~ExU-SpR| z%DuOGUhVzY$G_=#srO3nP;XDqZ}xn?=lgm-)$>XGJjMS@{0|UcBY|q`o(hRAG@~hL zf9dS?Z19Agnh)$MUMu;Wi0b4FlywFDMJT|*EoCJK@iaPmG9^6C{b`ZfX`3H?EP#3{ zGkC2Llfh$U*@bhRK3{IaYYP7{T+;yH=gR;M;W#MDj;NJDyBye9>IhUAEnv+0G2N)K z(%M+RO+60ZLR>qF1c%FyXzlFxsJ%Y2!@3!Gc=zLUv@K-sW$j=2W%YAz<@4D~IH;+q4t}dt z{c!@A<3Uyrj+!X~yh{}W%Tz)@0;%~E0_^+lo>v7G9>4w zv;`zD-8`yw;EDzUFT70g^f7aW;vnfxtfv(?f2_!^NV;@uB{J~dFQ$eQEu;4VxB{xU zJ=i>99uR6i@sl?RQ1cFiHqhj4ac8c&iq{M^MGA$xB)*D;OEPh^fPJAW)-3~xz8094 zmQV`h_$pp>Hixw^>aSq;L&)i5*UOujI%8|uB&@!@$tF~^sZiPX7s7V1u0E7`Sp~Gs zKESiwTG@WKthAC?BF3XLtE6~TtB-n22miju{${G>P3oKSkBz}#Si`<$^@Of55HhUT5)(KsF~ z$v_7iCH2I|A=?|9*2_)%uI^(9K`m$*&BW3iRSo;r)_oPHwHa76n2__=o!a%0n`luu zH}lCrC3jDbUXUJ?B^2K>cjHnzflW7-u6nMs`j0o;Xgxduf0P+5t1I<0{n}u~;? z7(sQh7mPTh(~!L;8nWhHI!?O#qBM-XM1WOL)b(Wyo|Iqp#Xw`Q7B&>sjx8uHvCuOWjX)zpwk@u0QPh!@n#u z^JRI?;N<{1!bm2cd{v1%xf1*%GW4&-HDm#*;Oa3G0A_%~wjIl`ZeQ7Enk$Uy9Ys%K z8dU)YlnV8XhL$3s-VH{;SpaJ}2-}h8ZJYbRUI@;ihzs_;`Gv++NG6mKv9P;G)^}N? zX)&)asejzth+ol8uoOo!L^LT-?%x_iZ#kB55hvW{XSqW$DPu&R!QE35H%010zs9R`~Nl6A-> z_n*v{uGY)*07@^GfuF{&hI+Pex+w2|oOU{rVj#Vc9eCj0wNoQO!iI(-Ilh&9#*xF^ zmzehxmx_)V(l}uW^#1Ae?m+dyhZ^tH(-n!z1kbW$R35b{h`${6LjB|^+jHddCUof= z5BL26@6tK)?+=5 z@q%jbbqaVyGebPS^|IW@^&O$-<-^%tbls^}FC_!(aeaz`C1Y?C4xK z)muUgH4gse+6FjPaQF3(BQD{4I)DH(lXneMl08`fMJGOi5|^b2U$bDr4%?6yzAUZ7 zF|hg$dJ0uoZ&|$j29qip=r-OJ{1x5yR{wIUz&3VqEgtfbJd=?FqpNH3>WCKraT--3 z?D@9!wY~tj_QH5p#lT{_JVa%`H0^UIxUJciHC=R5QwiDz`BGv~vUPm{ zZR7}^kX?GJp^|pFX%tJxFY@;MSK$2IL)7aSPMSCF?hBKrJsy5$u)+ z7gr5Tb1-;Zb9cdzTJUKw!dYu<0eF~c1HF)`0$Qz$vYmhmY7~hEY^^V zDhP&~a~mhUY{^ii|nle&X7(a-4~x7?1Txt6k&Tp&^S** z7>_VHxOVCD;E#(4(3ppypR$36$5CuVo@}Vz$^|zyRDVr-@lqEolVZCqRhPGwr*_7a$MeRw zR?5}v+(wxYU)k_~I6Z3sz?tXcNz8i*3s?L%Y#JnSC|oMO3`PZDNSr(lkiWfs0WB=> zB@7f0_y#Z}_2FPXbsfQ(1Od0*cOA=~>r~VTUke^bIlz2OTP2S_ZrX}-?-eo?$C@O& zx!R`p+^y@`;rSZn=N94HVpID|#k_xC_@HC;*lKw){%~^Hwd2-IPw-K*W>j_#YbJ^4 z5QBb5;tZ%7ChTh~ffLO4=O&qd`18LK^!yV3f98Cu>wCLKGXLV(<$=!)TsiWe`p+Ex z!r`eyOMQ*rKkQxa{?qQi)_o{*KK;4Ww`IO1b0_oR%t)p;{oCoE2e-eGekT3?^w*{S zF!d{`A5VQ}Y777UJE^~uzCZQ*so!_sK9v5Q^e?1;A&h*!_D_FNW}tDrmDYp3Q50t} z4^`--gFQl?@k2cZpCklA9V5nBY>)HmHQ0A6{%1+ez$=YY_R(Nd6Od;#nOaCHUd%<3 zsg`2EQf0|N`pLzu;5ohKw!LOTseJUBx7kciEQE3q>P9vIaUr9@TXZUKR_k|={i}nN zNmn3}ORWSOdfVIPwqP9}B$t{W1q%h8U4#GtI_?Tvrt|Crm2(ZxSIjM}`H$BaXr;_s zG);NXGCtugPQI~#U5>(NhD;%0IWJXel}D}`rgEUty0)okZXzN5kV{Zk+Y3Cf&ih*M ziahjStEM$CkX}hh)<#y!tvux-@P9@8-#GrSg#VjBu`a{{v_kO~s3@aKG_h|%1Y`51 z(L!!;j0_f&p~bYA0#8ws2C8MnoR_xOmvP80Z33sF>w^65!kr2;Tcm2h7v}rKMt~QD zQX2d-{*G#4><3pEPu-q1(J{b!8@`J!G9%26;Vb+ui9y-hs$^Tj54F(A29_A)wB-n~ zeK^&UWN+eohGBrL8;}E(kswQrJ99jSViIzaAil6>-Ht|Y@Ul$!NaLM$!Uc-#OuFPI zqH?2G3I6%z5a2P@QMM`kCp?VE_hUzuQFQ>MVF9%Q3+M_Y+U!P5U*^{c88&(Xs15@d z!bptOLMA0X9QiVh#2{4VKm~w@nugt+OKNtIn@%*JF^ioqBgT|k^^!w+ zd8g6PFZ`$^JyD2opYI2b?55XXw+b;t$UN>`2$}Iy=j}V2EAtO>RJYM+S|O{gv_yu$EZnz5!nd z?kQpewt#TtlNu!FrrwbG))yI^5AN!V++EfeSwNLwJlv6++8?mak{^G;hg#l99Slh8 zCD7-|su<`Z5mN7RMj3bz6ZX%w_dF158 z$#isITpVXT9+WYwIi@jTWP-{Xg3uT=Rs?in7kgcp6iT7L%HPCE*#xDauH)Nv{&5mR zGcrC%&sZH8-2vL-6|q1CYWqaNkfDHhjDSn-n}m%7%mMR~IxcjeAaf-ckc8p{i{RU+ zG>qub&>4_2Ykuz3XaMozi<}J&*hREQk|L+^tWv=ZcL}~1{*hZBzr8(o+j>fJTllaM zF{9$x_!_dDi&${ArKPc3D2mMI9z~@UC`ee%@?=zjv^-;xnIVXei-R8y={LA8TaYz` zrN^=z%ihrY&OA0Fi&wy`O`Ha_okHm|-WMK%tq4$yE90!(D=&lYicJYw#ce((D54-y zN2$lE|No<@u4gh=kCz4>?*E;`zkK-Oq38PkhrV;YpXoi(^K5ss>;FsrXzGipA5ML9 z>ep{3!CPBsXvdu1?x^;?Yq@M_OI%qz47FmiJX>~4h}q@kXV+;>9mNW$id*tz1L%-T96XZxZ8 z1dd(9W%uFQ$~ta=P8O0HsNB;`I{{!~OMJL;FV3aD89_ z;cWmx1v74aks45{sSo#r_IH6aa%=#6LG2;ra%P`UQoS5+E)nv!7TWN@#XA*rsb>H} zRnnOtU|=uoAAY4bY9*i#0nNNi<18>iJNgwmv7cV4J*n`pKc z8%p47IJeRxBF=T(H|_CY3vGX}Sce+6aS|M+_&6i#-s8zF!4hM2r;_^IVzI)GNTwrL zBuB5O>bk^-@E=uggh~fx6a6;2TKCD&PBo0|@#JJu2sGWs{%nuND439azP8GeQ}{mU zz-Z=^lMXqtDvXdY&gs^!voyA53%eSNt>dzAPBa&EJ5Qm?CO&zao1U@C6CM-~ARI9+ z7OGYUt2eN|4n+zjegJ~;v2wn4>x6L`%;Q`W=m}e)vOrZ9nL1Yd;-7I2)DR4%(ifWJ(3Qikrt9iVImWVitPXvADC`Oh)A-oGM zs*7uABd`TRs?hl=%5G%Jz@Wd3&>%aD$o4=I6{H&$A-pCIk8c^kF))2OJkbI?ICK1P zhR+L(dOo`>dO~K_;e?3KA@+H-49F8nUbrP|WBVP$t+7_GzM|LG%qv1sQY_WK6L^Hz zE+qthl*(7alL7ODiGgeeHtUk7u&?Vzk$-$dCS7##(4iv!qghn9r^@wtEQ=boF3anq zxIx4VgO?Uc+t?wM0W4z-V#u0d>IAS^|d>=>YsMge1uxp*T-? z3K*)sy`0C41lWOMH)y@f&(}~An)L{gyx667j<;SVOMrxXi;u+$+;HV)v+W+mTIGco zsDcgPj)ZWTYy1df_f0W0;pt!)LLLW+#p(8qNVx!}_s9XEtfM+X9d#s~#d*PWEl!&?X4UUr6ifTPasS?TYF=%rOzB|joiyNvm&=0rgB^7En!T6}P_|`3!$8ipY#>gXM{SfN*j#zV!JAm00 zEg5%ePEg|8M2TmdI9j$}BF%NglBkh32N`OiDY7L)#&iQVK{YlGHu@s=J9}NU-vx*~ zmCZ%vh^@`n0piK|3W6A}%>&DKENp>;g(IP_52P_V=;-AoHdlB%`A%BZo}bL+)>Ohf z?%v?s5y7;RZ=TQ_fjiK{5fsSg<^e0Hs(YlkA3JXB$%#Da^R*$xF#&B4yOkfe$3 zN~0r|FN{rJ=Wy{46W5Ju<6mi=Q9Ry=5fqR%h~Z7N8w0!TJPXjl1gW>i4$-?FG7>?> z{N$N)mv27S?igTij3<(#UGU3+8%(FX?6s|3V_81r?skkYQSA7A_C1A5R*xYlncNUr^Dzi)zj=01pfFF zWx@Eo&YllSJaeT*YVG58%oIlR2o7D zkvELi@}hZ%Kr3t_`*iE14yJisN|K9HFqYRR;t-I@Hcakf1;+x$5k|;1keoAbC7+wf zo?FHlA%+WiM65A7@c_6kWKl^vHOU+gj2Xr2X=Y0+AhahhLW z%vS@_Y;AK<03~86e8=#`738Q}zy!zmBYPg@&7-YTG6zF}b`8YgGnsJsL>Kx2Panjj z`C`F<@P14hMb{!*)D)r#nu;Zld=plwMkbnc6$NN#oVkXT5Fu85zPyUnfzw_(#fP-t zD6ztweDX`k(9h1^yz#_L^ZwR*b>|0W=a1(TbM8?xRQ%2-uD(x`Y_SWs=uP0HvR!}n z5Bg;*2m+D=1-&EYH+F8{=eOtg1qVX72k~>R;H-_vLOI#EUz-WA$Sl^%7tq-fq2&2Z zRwAqXkM#DSaTg1$FH}y+vHs8#q+b~OopYW$bK5q1ZH~5vrxdV`p_@Tr6RshDXP=`xY}?)HDuQly$PrjO${W z)6pjpG76jzk-kwe;6y@@Q%r>|iTrL~SSFZn4aplEdG&GiGfSYM$NHJY9OeOZij^`I z7i;U->r~DV#z9nuByy4wf67lxoq<|mQPFrG z!UW8pCP^?A9`$7b-9_q3i18>eXR;h1#$`SR256=Aply$da$FVxES&*+sr8;ita%=J zQ3JCQDi{e>UW?IP$>pQ3876=$)C>|hJbLIsjMSdFk}Q9oP5XGH;Ysn(=uaLAW||MT z24#_*2%z*pv*!&XShF_ARFC#KvZt2({#7{Cg2gbNS;l9IZtIjW`oJbX* zD2Qy!yN*dSaE(UXe*<+zQ+>Fm!4AR&9aXi1d$NLc3F4@Uq)gI<`Tw`4Pp7)_Q1^TA z*pmalJMgiiBS(Jv@Sh&~(xJ(|f8JN^{e16RyZ>4Dde?Vn9!{T5|8e?%PyfU8ccts; zkEV0!fzWwQoE_$%+d6}O@AT%{pnWvO;=&s)4zDbA2zD3i&DIQs(DJydjZWv zXx^`N6z{_cM?GV7ZXRxQpn|WL}!Eg6?QD1)2`0PN@q(_cV(TA4U(rIpuw-?Gk10Ue;?({2ZC{>&^c6j=O> zsAbKz&Wqt1Y!0dA%poRA@Naxb%e0W=i35kh0#brh2@BSds+7mE2n8)sRznz)&1aR9 zWoh#awyZuTHk(9V@ZK1D>a z+3+e(wzT=`P~%BE-lWQv*Rv(PsoghVi3W~QT824jE3Nm-(m(kzUHZ^}PuR-7xZSnt zbKB?>vxc5!;4`6Bid9{;xHRi7B3o>s^$mKT$IX2vV^MGhAYMpha4#~$5oCx^CTXU!t1nMsEoOjsn*m~;+y0Zydxi=uXqp_qxZP4eRmz!4e}K~~?`I?-j) zysxFrP6q?iZZSW8AmanVes*Z)o{_IOhmwOKThT*MAA^fza*L201-?$0)XFj9^j#-P z>le__hT<~Yo7mf1WO{me1j+|m z)r!bz9gD?(i^2O_+M@K-tA{oG$|GKl3BR~n+9-R;Sc@MTi@RMan*4tPl4xK7F=rlt z7@9V$3W0vXphXZ`+ak)h6`6GgvbN-eEEG%{R}E~U1}kEurSt{xm4MKO5QpwbhD*%v zt`qoQK#AN!_KC_$mB9|&=1pm_AkLJKeO(>{KS3@T)dCWZaQpzqfcD|eX2=c@*ONAU zB>a?*q+XfeJ!C6;=CdqC3JmYaKM@CCu~s~qi>y{!Q+l30Wi~rz`T+g`nz3@Qmx*rN ztc;qbo6H&kLxDHt=Bf)~XS-coF;b;HwHqMB-Nj*D!uQ<}*e`y;x z6Z>LAxitb2Fbm52MtaK(0%L?+Ot^%Lxk0{H&Yo7Gjb(=s@}r$>c6<~Z_Q*xu9vrn) zcRsiz6)=dKZa?9Ib(PYc7b#tP#o{Q)+y}m*KVK+|ozVGe81e8tHbd;oY0pQFatA@z z`Fa^}D_TomiUFy>gdr;eJ-}8O_T!$yc-^?KQ!VY5I@qWxJeQB}M%$i*fyLz-WZN^e57qTZGv=D zuwrFfT&q^KzOo$bJ!g9HrNTzamTlBNu8EO#Z8b=xAo_*r1G%b!? zdLO(b%%DTHn6ni?fxIwD7~xaZ0L0ExolqQ0>cSi37)6Pe9EQs=SRr(6Exh6tg0S*t z+^!;S<0e2iun^Jg4MRSh7W!;!@tbUIK(&keMKhp<%!;FWDyW&)mjKCgXSMk_@raES zyG1CGj{#TPn-<9@1N__`Ncy}=cM+-Y5a}mELU0_FXnA8{VOtvZt|zEE7UGn)%XkNP zxj^O-0}3T~6#yUzfZ(8TP$)1*%UV+Q7w+tf2`#j476rEfg27zco?okifr3B--*a{$OJy*2@znyR0)E95UL7ZDT_HyJ zP0pF6tB#&zqJf+p9!|P78%V+YRHf0NCO{=?1}h|s90p2nf(~k(H$mbumn)#NY?@F z)=OK+kiGXP*eWk*b(x|GTJ{zZ7Oxp9A2>giTPtwdji)XG<4&2ddAW5>%}>kdU{15r zHb&`>ufEOvbZWjS_6v_f9A0R}XGx_hl(Zijj)uypm+I*Ni=z1+L^~cX#1Mm_P)NP- z`Byf@(N;4Z$#_UsX#eRQK^877+vm)|iCNlSm7a70_aF+h;Wh}gwtlAB+d>CM7ML}w z#*!o1$%DAvDySuosMW_?a_DmopRej#o71%xf0j-nW7V-@5h`i>JiE+9b{-KT&MxUr z@5oMEyId=JCl`6TbQwSMR^`5D+PhomZ^)Sl2F*-B93^2U+AFsbW&*ovzYi&r=q9(-QcD zffhPC(s6ICnYX`JDj?=@6SfMM03&nwOsEVs?2KVF=^BN*i=nHxoZzHQ;RH~Yf(MmD*`wb(acdIi2W76;kdAJ zQA}xQ=$#qk$pD)Ly&#-5IBGPk;IH)$a2a45apYaFu?^zex`sRvy3Si#mt^q`H6|4^ z&LOezx^rh+6)ec5O=5u<7*`ZsWEE62vP3?!%|4imYiuIKfD|vaW^_=&fEg4XFu^s8 zusP!$BpxZV*UmjQ*I^IP%!W-g3@z$Hr#o7*E`*#T{z-ctz(1aK!o3|y@&HQ1@yOgV zn8FmW;Vbc+neep7H*Xq`h>U|;y}UYN8Z1cuKlPc^@gF{Z^@O_YZgdQCBJR{pqdLXS!FqSH4W)e52B;$g4Qj)I?4$q2-94@9R}S z85PruI00Gq;8jkb?d!&_UrLt|;!QMrW5C$G(iFhs!z>&&4VV=QNE#$t>>B`FQG8)Q zY#7rf6$F~tqFHmM7;Um7X~CFdDcVcg8DQfG;)Ns2Z`*9lOAJr*>lXVe7>8i^u&s<_ zP{cc>_SXfr?ZeT219^$sB7(B&0&yx#5)1PXYHXFI$1sn7`6U;Z@8XU@nH7+u=vY+O zQTh#A$XlZu?5JPZ8X1mb-1Cw!*yuS2Y!}KLq6=ed@ez$JmGLkO)|8{bazQ5Sgy~fU ziLBTvTdoXOhumIK<_;_dQWfS4;2!?vjl(S1BX9ZfnEpB7GpF%)Y865 zBTX%jz<6S1%!C`UR>hGw2UZdiKy~*9BbVpvtXH%9_PB(d^@m%}$SNo{&e{>jaxXaI7%BW1lN)M7 zhv#0@rpktxup5{%>Jh6u?rMPNT4*OEzNWTQ%T1x;ATA-w(X}zReSke`jH0PzPNm!0 z=cgq8WqhoXgNrhw*i+oIUQ_LTqOHA?>)+7Q1{EioZ!>dF^fNww4YwZW5O$G=4e4#f z!6!-018>u4Yk+6&bPhvgD?6+@_2D~+GyvNpaKzo>f<|Ovv=4>ta+jOR#maBca`;G+ z|4K}qJH;V%lai_f&rH&OYA6(4l7P?I3=5KSABx6`F&@J$w1B}%8EjO{lmgz0TPf}6 zmOrMl^$^84V_neBT3?6%Y%|CEM%(f4Uz6smttSQWJ8{>JG?rPx$#Z5mGRk#RuE9w$ zx+_2ZzI$MeoPSu{i0?i7L{uk4!w5W3TonNX&O=0Bm?hZI+ZHR=^0f1i-@4<9DH_kU&`AQ)11Fn1ij_`H2w^bB zl|bIeH50mp-iUAv=oW-FxgI8|2;RX-!pdjx!O#lz4jc#{4RJ_a*v=A!aE|8zm?2=H z2O-}uKPP#NO@ZKV@ORH{ubY0A1SvOH;hLZ+@rr|9bu00kH)}gMMK0L;T+!>;qbqo@ zbz364le@xmk0cUd(VouCb2<*e)hrL5OqEJEkiwJRLrmoe{yBpg8OGL5<5)>5_S4S@| zQ?(QtZ!85dukQ3yFWDpQxX>Hoo6sne85U#j9Hj?Z+6UxB1F(HN*=GYythtW;5;*f1x$0_8|EB7TSE^l@uH2%*aq-nSf8k_?P~{ zC)zzKXWllbq}+n@>+0KH#3het#&@%IQ|TMV`<+(cC1&2DLSp{7us8`T(QxRI%lQ8- z>0IhqdEn;;&L92GBTx7LdjI0#&mBH<=p%i<+V@=V=X>sSKa-ijS3aNm#G$!Eg+p)Y z`wxBpwD0freM{e+z7O{e_x1MvcJI&k{_WmoZ@KqFzzhBh|9kx7$EUwcIKi8G*=D8% z=~OI|Q_Y2sa5Ifhpo9Iz&H_z_er;}k}ynM7v1bD0|JRi$VvARpB^jci=hz z?wUMJ+-DnZw&b4b(aO$65q-=TkrJDOG;BpQT99>{@ZllZ>ryVGnL~i1DEfu>fL_ek zF1Vx74cdqDTJLFXYqay)h|WT3GCl*L%)tZXV(QsEPT^}X7|@F~TJ>JKyiFF^BpZ>n zxPlceW~NSCX0s^3A!IT;>|f+5K^%k6frFNU3Yc1XpR8O21rlkh1W?HWi0ZE3t$4*% zFhV?PrAQsd5&;k%9icHy6GJ(`EalZv>h7R_abVNBXz$M)nKbCzW%qxm_Xdrp<9@A;~XW1I=@1 z!l$02gqbA5*HlE9uV-%Tp!>zIaCRejgPW!~*Mdea1HTq5I8DbS&f$bux(#w+ijk$& z)Z>+n8~Uz!iL~wPXkr+?A?llv&R0!)yhqALYpo>SEewcF)F>W|1?inwPDjImA~vxh z^kP}u2;}qmvE1Ysj-PQI5@)tO>fzs(YTK*2L$@~(hT#D?l(1NEkEp&q9qiNpvsPE} zRBO{5+ot&+>cwBju?{GY30O;tBuVu3w_}VyYixi`Izbx<3?))6=0NWwN*=h+G(xI zqzyLu%-E-sFp6*gywtj5UYO}~Rsyd!{)L6L zzy1qz(NG%{%RBD%GWESR^Q~v){v*L{;XvmmibeJ*X!qK=3!|0KH@3Hcy?E}RPu9}x z6)5yD>oBH5{nl;(*096$ShI$A<+cNkd59jPkQ`y6CJsRhmb?$BrGT5DJFdV3Grs&M z_#V*=W5tX1OjFEuAxFVMz0GO&piNu43&xYL8}DsZWztSIO#9IZ~qv^xJ))ho<|lf-`PT2g@$ZFejq7sdCA6!b8}`LK4wv&wk_%?5Q8!q)K^+7GT5QU zTQq8%Kp>wm*yL(C!-7~N%Lb=$1b`9wLK&IUtygltu?=6>T9y&y8}HZsH3^zaEU5#P zx~tU}&_x=N0eI*zFk34(Lo^5-P;=C-2@fqwy3kXl6|S$bK*TVB!(&p8J0Rj_)-2|4 zH(>CN){=NV#a&e|$xT9}BMA|Tk!yDW2=9Qgg|eg}(Ge6#70JL?ZqkBpb~yfYxC&E% z8i2866WEOuMmaE$LjP(~lL`2hm1^~ON4o}qEH%^uQ2O7^pU*a=b&WC(-5PY?7f;H6 zMT*|R@d`C9J#Z=!vJpR{vp6tGp`OV(%NH6vl2flFROa1@k(03mi zJofoxtEngZezWiMec#vjX?*=_|9PD=(0s9_U9SdrkBF#KVH}cSH)rPB4*@!TKK=Km zZSi8Qynh6UZ|w7Tu^|8KU2xR7^`t;(84=<#SqsO>5NhEEV%zW61qZ2Lpsy;~Yoo^Y zE;tA`zieG_jDAwk-O>(Q`Ca5NVFro?NDsz9t&*+pu#X9t>+3NUQ09Srf}xPMpHg5O z0N&Yr9{f6KyCNH0TZPP{rVaB9S9{$H)FDN)wIiqp3zUylnV# z01LKB-OyYN?ToPc992WuWcZ`dLG&ar&bE9HY$DB}&?PbHAGBcehUk#r)fSq7Nv;YJ z3E#uC@|N4T0eqo?m#d1&+k<&)*!hOElDEE?{kyn^u5zW#&lc~AMiMD76a{P|@8;WW zNs6G%7w=^YyvG`|S_+)1t@R@ZTUAlNTV0cuG$_kkHRtlJmbM5x+5DirQwdS_R+*XSxyaX+D~HBztIdhafJr(hwReeDrt@D>m}B`pMT z9kZ8_8(prCVsSl>0A{7K?q!DQ=@PlY}oVxn1!`3jY{wpX_e2A1E z5GD@s-ekboLx1TB`X_rb`}d@#>oD zh0z3iu7(tlC<1g%6co+_3*V}(N|(KTcT)2myXZrV)iM&?ud8Jo=x4%eDMVxKghhfn zOie-Tn4Dpo^2;Tu6B>nfE<(+N73#qiD6-*&tel(exdUfT7NiZ4#LF6;yI8{7Z&|*s z?rVScb?}MZl$^dJuU?fX5yjY3aas?6uEwLYb8xA>@#6IKUICUAycdnS$i)~3H`O#!{_!3T??3Epq;{fj9 zg-|GW4Zo8_l&PI_Wo_}bb9k-edDMs-nL*#o0#~5~vX}2GCt@q!zEV#cDQj?WTz^QO zpd1ywX*v1v-i??0@s{>WJ~?cCxckRQ9z}QcorSU#^6N+=r$pn{Znpw3 z&G!oc1G%DDc|R`f2+)0?puG+D3)A^>9y5dZp(#dVaR&dwX8#d8%i+=b`TZ z-2E@Qe+(J{&vnmspXol<^?O}k?D~lt0(hYnV-x2txO~HOy&cbcc%X+ z{VVAoPk(3nIZ+Kfp89vGUrPN5$__r0N*zAiwoJbEuW#ZEG<$Y?HAo8{w3;$W(ii?N zrAm?r$Bc?xN&l_FD44#hV7}Ww6i1Cloc+Ui_y-(>+X#OVXN7&ki)*@T;QFvX0bB@_ z8=!RodZGg%Wxqmr62eIoQ4fSLWBs>EqhJ;;uh&+qW!%_Y7McAbyPI+kMy>{e6MD-C z*Mdkz)T|uYeY;G=VB-;)2sCv{@_GwkGv3Kwfc35}?qDY1^H(7+0%(r{CNdYZaLZ5# zLKm1kGiFWT2=bxHJ18-vqp;1ev|+`+6xK$f!x`9pn;Fia8BU@kNzot9IS5u+=`f0a zWEUCjqs(LRd1jMAO|DRM)=a)rlL6kdy`Yux$PQFcDZjN78PJ{GLu$+`c9|ro+Z6m| zQpkc?xl+Efjd&HsYdfklgjZ!ILuLkaQ_7n8G9{1g_UYZMc2}a%vwt@(N_l==a{dLe z#Wuc@+bp=S`&RKzPwj#?ByyTa{3dv(<81~#-aYNqp>V~hveXm29*u9r#Scprxi#QE z%~PA;Dyx9tHdXh6J94aF*63`#vb|Wtqk^r9FN(e; zi?leIr75VZ|ha(;4!IweI@WH{DuJo1n3YBSW6d2{6DCl2tU z&WV^CmmoHQ?00{EE$klEgU!lyg4S_>myZpxK-FE2nYI}ucU&Da1t3p1&7yyo*2L}+ zIem(+ozv4NUrIcEN-=LZ=>)==anS~-7Xha&uOog2D-#}&DImr4GM2{Z_K*AJrGf<- z_iLo7E9jvvE13X*q4W7KjYw!duO&kfSWX>3x2pjm%t$Ar_3SO6^?;YywFxDd^_AKN z)gzG|XNXSxtGx{&F9k*nW}1uw7GF9-0WPaG_;_UYW1`$==k?_o!@f1FOIk$&r2=-k z+}OYPbt&wd(F~eN5^?#u+k_9c<=sk`0n#$t!UKjK3UMVs9IOm+C8hi2hno-WYM)JaN zcno6W^1xs@J*UPAK8hiyfvAg80CoucZhJPc0h&lFnA>syRLa`sZo+#6kqAHC^oCJ} zP!bBEB4)Gkp7%ArT>@oKS+ODdM$k&T>_4z{tfyD{$T30)N9=@d?o$tgWs!}-F;L& z@?hXe9(uV+0HS{;kDQhda;J-Ma&-X{;4#&cOMbwlCb6GKUL%f8a`(;RD zWU8@jC|&o=?)`F_58XDih1!t#*=iqUbQWUouYg_a$Zl4OABSG8%Tb=2!b0J)jcr0v zh|EUPF%B}reD)$L5n)_$tTC4(#}}WNe-PIhNY$m+T*^?*&wN!Vo7D7G(zHhdP<2T^>)Wrvu#6_$B z4n-L;p?XZv(;h`|DR7e(k>N_bBQPFBY&vVQ)l6e#GlO&xT(PB0EvM8gwB`KC?$zP5 zgB&^jru*~Q{#mc(-4n7iMp|XF=D_@qCAYMvbrOgz6dx7$I6l(>AFEIR`XYfkxOV7Z zjRq&;iCgHK(j9joPfoN+9@mrXv2NBF5268(uaXL{fRqG09BR{4(kAL#sH1Fj$+ z()MdO2~HEp-a!2LXB?XhQplzfREB6xcYr!B3txF35!3@ltZay_`^(NSdsv@+Q@yeuX= z6q%)VHFDS7F8mB;7LGW6#aq>}+mwP1blA0Hk{u);1I&T*zFB*gwe0i<`4X|^Xsflx zTK<)LIQuNx>(t?oXo-iPYD=cXo-ypL!{4u4@qmzAt0zCc-DVn<0I zG@i)&cOOy*`nB7}9)c5-;6Ru5s)gmO3yBa#pl~hb$fc-fFS*c|PV*XRFFRQ(!$7xJ zTbOEYK93asJoJRwtzlN#CN5 z2;Td1y+;0wZC?_G`5NVW0Duw2Q1sHf0Q|`;fOY_va&a+R_}c~WK_#C%1}xc$szfV1 zh+c?P4MvSuh$#bS0JF(PbJBHIUa4O%YrJ(aq2%L+kK69>%I}d9;2RRZN(a8YiCF*+ zD}R^PbNl-)L2CCwSwJJLr_BNaK{8Nb%93^^4jjTiA#I*dzWiNpp0n!M%+-juf zSvbSQ#FyF&+NAha)=PF~m{|}cTud{@5Tc3bVQ%e!nu6nzrwt`nHc`Y1M`dEdF}ffv z=b_);FvJns*MA)7{(n;UrOT;fzuWaY#||C+w4Get|MJX4jy;^CHrRc>vjur zmJJ3U(Nn)PnRHA<$gI9q7PTjlc@Zy_D;KN6Icpxg2lC%0s`0WNzeY$dh6Gz1`*stK z4|j)pIi3E^IBXnUCi-9!{MEC=Y&M7=Lx0FQfA+>|h})y+5-t)cWWrXXh2cewXVP5U z%^MT&pn@=%%}xiI`Nqkq+yN<^U>lfxxDf`#6{j0&xr! z67P8+PYyz2UJ21{WIn(*yt@uA?U*c=p+L!^xrvFyfFXh~#rxk};auRxLTHSQWXlpj zNWRmB@>1o@d>N=9JQUBcF&e{~5rQpB3~d1Cz!|Uq054y;C)8(amE89+-|e=^4>_wA zBUEGu!Cd6+XriFDzPe-Mlji8|sF@xk;5wnrR$|zerO5QSh)VZ7AP%edu~@Am2u35g zCHR-?qo@(wwiP!_tHJo6g<3Y5r0pNu)Ca#tXpY&|nD3ZoE0|7xQN6Z8>;(mo!}rj+ z1t=ga6j3ZwTOIl-SSI+$YZUdO)E@bvh%TlcaVPhzor z!4&FO%5Znrd|LD=<7Tx+2M%-LqmCgwPEWh6L_J%Z&?*u`ps z(uQH#hKQE4NG6|P)c$0zpiZTVFd|!1zkz9JyKNdT?GBrXQE_qYna# zUwcM^h{d(Ch_A=j-PPSy)u&EX`CV6c=fw3~8a~k5XZMISvk=@WLvf`JR}#v=WfRR| z3@N`!m^7iPz`bUtFDnf$ei4FjaF8r?r!FMjC3&o4e`8dx5Qa0~rG{rLY&ie6vMn2-J1P# zb`@m;9^vCpJk1!Y?(5!Dz&XS0ot3HrkT{ zB&5z@?|VcQy-bS*_nuCdPCGygOt-9}d@|j5P5+e{nEda%cjtMvx^SSn01pbk7LP6Y+Ofv1pq?;;YKLF$pR<@NyH25?E=RFou2)^?ZG_45(>j=e&D z5Gj@svL^umLG?Uq0iow_pq(V-Cg%!d4}wZmhdsnTkW27Yh+XPtRRIF3x4P;4s8Bq# zt_r{P!Or9t1bJXLJ$pe?1>A4FAKcrf*iP%t>!6=YD8v8NAfkh4Z4H z^XGidy=5wGQSb2Jf#lD5qkUMNj4M|*Z>8EEplC>Fm#&M6PTXLEDs+boa6y|9$gu99 zAGEK-0YI;2taU6j7CIs7u=Xj%eQc@Irfmyb5j#~ zGPyV;kDwGV*LFhkh`%M@j%-G1u)@Ji&R*nw!NG|Wad${Gs z(BXYm9xWtw=6#hEPzkp`1#YoB{hp6DG+p(*{tlAW_qY4Io~VQ+#S!$4ZK3x-9Ks5N zt>9TE1Hj+lHfRq!@#p*70~Y)yAs6JtZPMqdW#ax$Ff8Q5jbcjpkSG4nb>g4~=EUwn@ullMaP)VSYKqY}n0+j?R2~-lOBv479l0YSaN&=Mx zOajG6?uNvrmxfJ?OE*CX%`j;hmkvaUJb{*6{Ea*#{E5TtTit1g`d$ cr8;rczuNOeJmEtFyiq 0 + print(" Number of new decided applications: " + str(len(weekly_list.new_applications))) + print(" Number of existing applications: " + str(len(weekly_list.existing_applications))) + print("") + + if there_were_newly_decided_applications: + notify("New decisions found", f"Council has uploaded {len(weekly_list.new_applications)} new decisions") + + _cursor.execute("SELECT reference FROM applications WHERE caseOfficer IS NULL") + newly_decided_applications = _cursor.fetchall() + + if len(newly_decided_applications) > 0: + print(f"Scrape Newly Decided Applications: {len(newly_decided_applications)}") + + for (application_ref, ) in newly_decided_applications: + _app = Application(_cursor, application_ref) + _app.scrape_portal(_browser) + + print("") + + return there_were_newly_decided_applications + +if __name__ == '__main__': + try: + with sqlite3.connect("database.db") as connection: + cursor = connection.cursor() + Application.CreateTableIfNotExists(cursor, reset_table) + + midday_checked = False + while True: + with sqlite3.connect("database.db") as connection: + application = Application(connection.cursor(), "25/00605/FUL") + + with webdriver.Chrome(options=web_opts) as browser: + application.scrape_portal(browser, force=True, count_documents=True) + + if application.new_documents_found: + notify("New Documents Found", f"Application now has {application.num_documents} documents") + print("") + + if is_working_hours(): + if not midday_checked and potential_midday_upload(): + midday_checked = update_other_applications() + if midday_checked: + print(f"New decisions found at: {datetime.now().strftime('%H-%M-%S')}" ) + + pause.minutes(refresh_rate_minutes) + else: + if update_other_applications(): + print(f"New decisions found at: {datetime.now().strftime('%H-%M-%S')}" ) + + next_start = next_working_hour() + print(f"Pausing until: {next_start}") + pause.until(next_start) + else: + if datetime.now().time() > time(19, 0, 0): + next_start = next_working_hour() + print(f"Pausing until: {next_start}") + pause.until(next_start) + + else: + pause.minutes(refresh_rate_minutes) + + + except KeyboardInterrupt: + print('Interrupted') + try: + sys.exit(130) + except SystemExit: + os._exit(130) + + except Exception as e: + print(f'Error found: {repr(e)}') + print(traceback.format_exc()) + notify("Error in planning monitor", repr(e)) + + try: + sys.exit(130) + except SystemExit: + os._exit(130) \ No newline at end of file diff --git a/scrape-my-application.py b/scrape-my-application.py new file mode 100644 index 0000000..9a11c4b --- /dev/null +++ b/scrape-my-application.py @@ -0,0 +1,52 @@ +import os +import sys +import time + +import pause +import requests + +from application import Application + +import sqlite3 +from selenium import webdriver + +from workingHours import is_working_hours, next_working_hour + +refresh_rate_minutes = 5 +api_url = 'https://hass.jennett-wheeler.co.uk/api/webhook/-Qx6jHsGLHwbBlJpLek5Nj8qS' + +if __name__ == '__main__': + try: + with sqlite3.connect("database.db") as connection: + cursor = connection.cursor() + + options = webdriver.ChromeOptions() + options.add_argument('--headless') + + application = Application(cursor, "25/00605/FUL") + num_documents = 18 + + while True: + if is_working_hours(): + with webdriver.Chrome(options=options) as browser: + application.scrape_portal(browser, force=True, count_documents=True) + + if num_documents < application.num_documents: + num_new_documents = application.num_documents - num_documents + num_documents = application.num_documents + requests.post(api_url) + print(f"New documents! {num_new_documents}") + + pause.minutes(refresh_rate_minutes) + + else: + next_start = next_working_hour() + print(f"Pausing until: {next_start}") + pause.until(next_start) + + except KeyboardInterrupt: + print('Interrupted') + try: + sys.exit(130) + except SystemExit: + os._exit(130) diff --git a/scrape-new-applications.py b/scrape-new-applications.py new file mode 100644 index 0000000..2618e4b --- /dev/null +++ b/scrape-new-applications.py @@ -0,0 +1,56 @@ +import os +import sys + +from application import Application +from weeklyList import WeeklyList + +import sqlite3 +from selenium import webdriver +import re + +search_past_week = 0 +search_num_weeks = 1 +reset_table = False + +TAG_RE = re.compile(r'<[^>]+>') + +if __name__ == '__main__': + try: + with sqlite3.connect("database.db") as connection: + cursor = connection.cursor() + Application.CreateTableIfNotExists(cursor, reset_table) + + options = webdriver.ChromeOptions() + options.add_argument('--headless') + + with webdriver.Chrome(options=options) as browser: + print("Scrape Weekly List(s)") + weeklyList = WeeklyList(cursor) + + for search_week_idx in range(search_past_week, min(search_past_week + search_num_weeks, 9)): # Council only allow latest 9 weeks + weeklyList.scrape(browser, search_week_idx) + + print("Number of new decided applications: " + str(len(weeklyList.new_applications))) + print("Number of existing applications: " + str(len(weeklyList.existing_applications))) + print("") + + cursor.execute("SELECT reference FROM applications WHERE caseOfficer IS NULL") + newly_decided_applications = cursor.fetchall() + + if len(newly_decided_applications) > 0: + print(f"Scrape Newly Decided Applications: {len(newly_decided_applications)}") + + for (application_ref, ) in newly_decided_applications: + application = Application(cursor, application_ref) + application.scrape_portal(browser) + + print("") + + print("Done") + + except KeyboardInterrupt: + print('Interrupted') + try: + sys.exit(130) + except SystemExit: + os._exit(130) \ No newline at end of file diff --git a/search-db.py b/search-db.py new file mode 100644 index 0000000..25979a8 --- /dev/null +++ b/search-db.py @@ -0,0 +1,18 @@ +from application import Application + +import sqlite3 + +with sqlite3.connect("database.db") as connection: + cursor = connection.cursor() + applications = [] + + print("This week's Application decisions:") + cursor.execute("SELECT reference FROM applications WHERE dateScraped >= '2025-06-23' ORDER BY dateDecided DESC") + + # print("Chris' Applications:") + # cursor.execute("SELECT reference FROM applications WHERE caseOfficer = 'Christopher Masters' ORDER BY dateDecided DESC") + + for (application_ref,) in cursor.fetchall(): + applications.append(Application(cursor, application_ref)) + + Application.PrintTable(applications) \ No newline at end of file diff --git a/weeklyList.py b/weeklyList.py new file mode 100644 index 0000000..b51a993 --- /dev/null +++ b/weeklyList.py @@ -0,0 +1,57 @@ +import time +from sqlite3 import Cursor +import re + +from selenium.webdriver.support.select import Select + +from application import Application + +from selenium.webdriver.chrome.webdriver import WebDriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait as Wait + +timeout = 5 +base_url = "https://app.bathnes.gov.uk/webforms/planning" +TAG_RE = re.compile(r'<[^>]+>') + +class WeeklyList: + def __init__(self, cursor: Cursor): + Application.CreateTableIfNotExists(cursor) + self.cursor = cursor + self.new_applications = [] + self.existing_applications = [] + + def scrape(self, browser: WebDriver, search_past_week = 0): + browser.refresh() + browser.get(f"{base_url}/search.html#weeklyList") + + # Bring up list of decided applications + search_button = Wait(browser, timeout=timeout).until(EC.element_to_be_clickable((By.ID, "weeklySearchBtn"))) + time.sleep(0.5) # Give a little extra time + + search_type = Select(browser.find_element(by=By.ID, value="weeklyListOption")) + search_type.select_by_value('decided') + search_week = Select(browser.find_element(by=By.ID, value="weeklyListBetween")) + search_week.select_by_index(search_past_week) + + week_str = search_week.options[search_past_week].text.split(" to ")[0] + print(f"Week: {week_str}") + + search_button.click() + + results = Wait(browser, timeout=timeout).until(EC.visibility_of_element_located((By.ID, "results-table"))) + + rows = results.find_elements(By.TAG_NAME, "tr") + for row in rows: + col = row.find_elements(By.TAG_NAME, "td")[0] + application_html = col.get_attribute('innerHTML').replace('\n', '
') + + application_ref_html = application_html.strip().split("
")[0].strip() + application_ref = TAG_RE.sub('', application_ref_html).replace("Application Reference: ", "") + + application = Application(self.cursor, application_ref) + if application.caseOfficer: + self.existing_applications.append(application) + else: + self.new_applications.append(application) diff --git a/workingHours.py b/workingHours.py new file mode 100644 index 0000000..509e72c --- /dev/null +++ b/workingHours.py @@ -0,0 +1,62 @@ +from datetime import time, datetime, timedelta + +def is_working_hours(date = datetime.now()): + if date.weekday() >= 5: + return False + + start = time(8, 0, 0) + end = time(18, 0, 0) + + current_time = date.time() + return start <= current_time <= end + +def potential_midday_upload(date = datetime.now()): + if date.weekday() >= 5: + return False + + midday_upload_time = time(14, 0, 0) + + current_time = date.time() + return midday_upload_time <= current_time + +def next_working_hour(date = datetime.now()): + if is_working_hours(date): + return date + + potential_start = date.replace(hour=8, minute=0, second=0, microsecond=0) + if date > potential_start: + potential_start += timedelta(days=1) + + while not is_working_hours(potential_start): + potential_start += timedelta(days=1) + + return potential_start + +if __name__ == '__main__': + # Test Times + assert is_working_hours(datetime(2025, 6, 20, 16,54, 0)) + assert is_working_hours(datetime(2025, 6, 20, 18,54, 0)) + assert is_working_hours(datetime(2025, 6, 20, 19,0, 0)) + assert not is_working_hours(datetime(2025, 6, 20, 19,1, 0)) + assert is_working_hours(datetime(2025, 6, 20, 8,0, 0)) + assert not is_working_hours(datetime(2025, 6, 20, 7,59, 59)) + + # Test Week Day + assert not is_working_hours(datetime(2025, 6, 21, 16,54, 0)) + assert not is_working_hours(datetime(2025, 6, 21, 18,54, 0)) + assert not is_working_hours(datetime(2025, 6, 21, 19,0, 0)) + assert not is_working_hours(datetime(2025, 6, 21, 19,1, 0)) + assert not is_working_hours(datetime(2025, 6, 21, 8,0, 0)) + assert not is_working_hours(datetime(2025, 6, 21, 7,59, 59)) + + print(next_working_hour(datetime(2025, 6, 20, 7,59, 59))) + print(next_working_hour(datetime(2025, 6, 21, 7,59, 59))) + print(next_working_hour(datetime(2025, 6, 22, 7,59, 59))) + print(next_working_hour(datetime(2025, 6, 23, 7,59, 59))) + print(next_working_hour(datetime(2025, 6, 24, 7,59, 59))) + + print(next_working_hour(datetime(2025, 6, 19, 19,1, 0))) + print(next_working_hour(datetime(2025, 6, 20, 19,1, 0))) + print(next_working_hour(datetime(2025, 6, 21, 19,1, 0))) + print(next_working_hour(datetime(2025, 6, 22, 19,1, 0))) + print(next_working_hour(datetime(2025, 6, 23, 19,1, 0))) \ No newline at end of file