From 795b34222a5b587c2c575a7703a07d1b7e7c3523 Mon Sep 17 00:00:00 2001 From: Deena <156876027+deenasun@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:40:07 -0800 Subject: [PATCH] 37 - [feat] Parse ORES data from HTML (#65) * [feat] scrape projects from ORES website and insert/update Supabase * [chore] commented out tests --- .../database_constants.cpython-312.pyc | Bin 838 -> 948 bytes .../__pycache__/nyiso_scraper.cpython-312.pyc | Bin 6860 -> 7382 bytes .../nyserda_scraper.cpython-312.pyc | Bin 6259 -> 6101 bytes api/webscraper/database.py | 160 ++++++++++++++++ api/webscraper/database_constants.py | 10 + api/webscraper/nyserda_scraper.py | 2 - api/webscraper/ores_scraper.py | 180 ++++++++++++++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 171 -> 171 bytes package.json | 1 + pnpm-lock.yaml | 9 +- 10 files changed, 357 insertions(+), 5 deletions(-) create mode 100644 api/webscraper/ores_scraper.py diff --git a/api/webscraper/__pycache__/database_constants.cpython-312.pyc b/api/webscraper/__pycache__/database_constants.cpython-312.pyc index ba453e1656c117fd25254ff64ff453a984992d43..da7845e01db4a97185cd4edd8721e04f8aaf7204 100644 GIT binary patch delta 557 zcmX@cwuQa^G%qg~0}vcttef7+%E0g##DM{BDC2V-kTIPhg&~D8g(-zOg(ZbGg)N1B z4to?xj2X-W$#BeJi(*XSOyNr5PT`ruoWcud@xfXAaFzg&l?zcQ2p7bnC7mORsghe$ z=oYU>WlB+gYEEi$Nl|8U6~9knUW%?$VsUDULU?9g$}e93w6x-k{GwDa_ZDk#eokW1 zEiU)e{F02+qTIxsiRTquG}()ofWCp~RzT=3Vg`y9v499*FfbIcfe3aWp~-fO(c>0l z@GT}+APxw=#TY(Wg7Lp{ZeqbLxuVp()bhlnoYZ(AqbR*HJ~1h&D77pzu_QA;uXwTn zQ*Heopcx;S85tQLh^pKW*SG;Bc|~pr$=wiB1EP=o91=_y7(_mZF>p!V;1syQB?Us9 zQa8AzKJv0F0p;XAh%iX#?QlI|a*-wAft1w^QK=gOA|KgVSlK@CFi2aX@tqh^1l$;; hy-`HC7$kIen4VC%$l_1HFiS-dri%;$MWR3-002X6n_vI{ delta 447 zcmdnOevGaDG%qg~0}y;wmQBxOW?*;>;=lkWl<_$R$e7NM!H~kRm@$eWg)xOGg*k;K zg*An34r>%+3VRAi3TFz}9Oe{mIEx3);)S#L;4FS9D}XtjEsCj9cafdexJm= z6a}Zm;?xv{@XWlFTdcwPIf+HLxZG3oOEOZ6auaiI@p@FI6y>Mpq$ZaXWhVdP^-oJH z&d4uH1#6i2MnSWP6=+lu&@&80AVZ5dKm;3*&}6;E=y8iN_!g5Z5C;TL4q*H*S)5vO zOQa|@FSR@|DJL}^$S6v$jL%Ihm|VnETR#D)?E^C-BjYC~1_=+Q3k)J3*cdp(Z?N;- z;E(_zPKl4)Y!YAzAqG+H8~h?SxcEM@vxqW%;9?LngYsFKnVCLtFo>C;@omxgZj7h` WQerR@!6s-+h%jGd5GWD_x(Wb2$aVSv diff --git a/api/webscraper/__pycache__/nyiso_scraper.cpython-312.pyc b/api/webscraper/__pycache__/nyiso_scraper.cpython-312.pyc index 7b91aaaa5cc249d68db5144419d359c382d669ca..dcee55d86a17a3edb4ed6aecdcb0a2cb74621829 100644 GIT binary patch delta 2401 zcmb_dTWlN06`kSovE_4@>6*aXT8wBNDOJpcg z9xg3QP%8x}P$Fni)7yX)&_;BSA~Mns7iv*|75VQ2RM)TwS^*q1{pjDuL6D;8Yi23Z zG4fTQ1Mu+7y?5t6X6DWx4&6U8@Ozib4)A#I-Pr8Uy8!qLnY4#_k9@(v^1%2w1QL)e zS^K=>f&+8w1*ZwfAQNhvKmM1UAHmk_f*ad7WOpX4MqECR?IY8koROJKKaugY^sQgo zJ94k=eVdiNnJ%I`;^?g7lbk;Q7yOb7BPY2r1|$zgUSco`5{oe?c`=5TVANNBuYVOb z!${7_i>aBcoWumpE+tblGf2LcPATbpt`LPy*9kdQRMJ<9+1K;Mh2^0+MOi3}CK8K_ zi}Bo2x{!}w&d(<<=W{vvvXait#*}rnwpr*>;jg=$YE*iIjUC7`}( z4-1bC+p6z@6_BCTNs6;Nj1SdgJa1Aw>i6M5a^$6{smZBPvG0kKTB#%tYm3$p~|K5rILM@;|clT@aCx2Kl1VLJ?>P= z-tYwLbnqtCHE6|U`2QD1R6ItJFsyW{+bN|n(`GHg z9;;f~U;GZUpt@}tf{v|!Ti$^Y7jj`&$W4Hxgct%W0WSfcx?vl#l5h~GZre_;@|ZRq zSBo-QN*cR~j$?_4e}%u|%lf};oR6!FmB&8dH$z%vSo0k!(VzLmnxOdxOLT+l+2QtY zbNe^X*SY-~cerGKD1=I`)m?wMH1Uw}SLiZb?XD>rGqlSe+TlmH`B9BO`Z)#JG2IH- zpl%Yn-3=yOGk-|mqBp~L{hBzgQ&{+r5o?3G1v6q8zn3`E@C%hwMD7M`cXfK>-1@otQzLiHTF;p7A#w(=y)AbQe_w6l z3(6Gn>MYR&a6r|48w2YDwdXdcwa`JGCt|@6>p{XpfDKiL^)AA@0ozrb(0d3E8_x7z z!u!A~8{3rhelpnySh42U_Y?jUU=w%Dzomag-|hZb(Vlrl|27d1kab?tql6Cuwx=p> z%&pJWR_;t|;ZZ$C#BsnzYWqGMxHV9J`uV%lTHgshf#s!F^n<_^+Hs28PO;W~-x=4B z0j^iKpMZersngvksJiX{fyY(1;~?bKpQp5jsNPFoEYVEPEywakouO$k*g-enCc2bL(_kh{Y&@=P^XB zP3Hp2XK-UpX4AMYI*DeSK!m_C0xw`FSTTtGA{ody#_W}JR*_Nb9JfCH4z@Y34l_S? zo!LoDZ6~I*#PqgfTKy9fho`sx#k>ab{dv{fcosi$0Cf{Mi{Z8vk^a>*8xa_>kyj5L zAwbrQT2Vkf?~9I*z*>=fEfDUBK+;!}M%P4_ux*Lhzp1Wsz3Ptd1;hEDro$>feJSt4(UAFpS7%HK`)X8hhR}FQjH=qc1hh1^mRK4AJ}X;~{_T z8Nz#*d#lya$aWa536W!I?152FL{dg1qT2uk2`5M4AN0}ryj0A}<7jmS&q;j?6%0BB zA$$Nl4}k9z5O@F_pMcnxCKEJ$Ndp*uXdPao8aChB^&hUhy>jD4%_gjwKehR9Ojj&&h(TsXK)N3XI{ufdnW%>XB delta 1697 zcma)+Z%kWN6u{qY`(8_5%j@gwA6|h`_DAV9DF4Qv$;Ptb0u|WET*jDMJNn#;{lo5i zU4cMoqM2jS;c`Df;y&L#t%k>4@->6yywwk=1lY^_t*38 zJw4~#bMCoo?O&g$`O0p$GIYFix$;K2%rHN~M{yVm!Y*bW)SPZhScj|$euzg*lzp2S zvPC&cd(=!>8nsXg(Gp5W)JiEvc}nLv3fM>oI@D>*yRec}GLh?XB}@f;V?3OQjQ-22 zs%UvfEE&^ck$CvcXd)bq4Qu2H@;L9(c;*-l72_K*kO5Y*p^pkHQ_LheQ|jiYkY+CE zkdIAngT|9ZLru8WpUjr9%9jlqWI`qui^ zwqXC&o=X>Mdy<-hhf~R?$0Rp6Jr%9{UIK7KY;$`GeMCJp-;G)x*s1QKyokq*{{ zj5NPhyjav5aT3AhE4_^{diPk2-hxSX1XLvQ8SAk-ifV;b7x|Tys0qX!(G5xDBdrS+bpA!rR(N<$jf7%y23U9oB@{t1Y$2a&Os1Z zB%LKa(-IZ){H?Tt$HRKH@hQ6Ep;OJ#3-cev5yaW4nW=|Y7KU<_^*Ny-+x?SJF+Z3S zp38P`i1NBvy&_hBC@+iEIk71l`oZPN_H8+QcTF3-FxxiM_P)D!;c$+Bel>V*J=nbx z?9K&4D}0FbJF81aw%M{WE4>Q#qpQa{m!_OoBEaQsqc}A}J0Tp8sTzit z1D^!w4qpJNgF@vf{QAIp6~s4UaoR@VqySyF_vZns6C$68@7e#EpgIIOxrk`xzmYbY zykNEr&_}VzVd$~ENsmB}%>;WV0AMq~VJG>{6=;FLo{7X=j6@G4b}OuRFuqE4$3g!Z zk-b&qiu;W2{M));;Az48AxsG8;%Sv0#eZ=x5%?0_2bZ9Bnaq36i_iEazj^#@m@3>M zE^iXKm)`O|M%CAzGK^mWJ68Q{_+{8?As@&A=ikS&6n(789g^ ka>%pe3?O-D5V1(E+HoAG@@vRS>C@ps@^+&EnRHiw0rqr{fB*mh diff --git a/api/webscraper/__pycache__/nyserda_scraper.cpython-312.pyc b/api/webscraper/__pycache__/nyserda_scraper.cpython-312.pyc index 545bde4d10e296fb5091cea9008c5f1c6454b951..cd283b49cc61163c0592b09d885f2087ef032b8f 100644 GIT binary patch delta 1025 zcmZ{i%WD%s9LINdH=8w^O|)r}ZLDn?+caArRnyu;#b>Ei6oe`YK4Qp@Now2;leHMF zk3YbJGI$V0ZAB~Sp{0lBB6t)Oi7aARd(g9&)Or+jCK1Jh3;WsM{2t%^&2MIT(}#Zf zjV!wu@;tb+Av=9r{={8K7jJ*E~UXjegdL$s$`~-nbOiGRK3VU zIzny&{KKIR2~Aa7Krov(blXYfGKL0iv5+?N=0#$yC>VOSVWI%74bocR9CRXuSgIQ* zo%;lvKM%_#Jn4K3WPCt;J-+Dc{pef&-nZWJZT!Xoe}7$MdeoWGsnKU=Do3sEy^GN; zvjYo>xkPoxq1T!fAG4xI7vuYDO8Z0cu2}6FvXsOU#|{MQ0uzbOC{s$cZ(C*9>e^H5 zh&;^Q%~g97m2RtJ7yjZ4NUqsot20Sb+rv2I>f^=V#(sh6T{n}P%2oS!y=t|hBej9V z!sy&+P1#!EmpOJY@Lgcy`#`Nnt%Z9&$A@S~HDz$tB*B5e()Mt;<(9j?cmAbo3jneC zx)gC-50QP_t$E#qC}Ze4%$Q_!jrpS0bVBsX+8mkWi0b(}Z$xj2&6o0*A!;bN$x#-K z64c1~Y0b)ZTE2#&gQAlnOwmOVAy6e0T1yw*6g?DCie8Eso?i7BjLnaJS16R&l(yYfaXpJ3babH;d})R?XTXC&F03@z;&U2Fo;^DB?7EF3xgG zxc~`!-zTfyr}!(9^&Q=Pu~2+27b!nFGI%VHwF`U8yUY96yCpljNI3>y%GvMgotY*={Rw*!=G@T8I^jW5=6>KTi#3TCVJ=I=@#tK_h<-s z7%AiZ9lZ-592M+r>0>HEA%c|DeeGfOLA7^wuA8;fS7;RY;D5Zmy>Hp+85#pDBPD&` zdU*4}&HaH+!OrBV0$4>#=H+mPeC|INOaT~SzvC6SdltZXq`cAA_Nu$p-sF{^WP4(X zUI2a(Dbt<8ljVcuGDFp@25zO_* zhHe&#A*;hz8FNIvX;i8$$H$J@4ZUKu99%O$ByBm5*A5-|77nbz;5$dP64>Y3(49^>i_@% diff --git a/api/webscraper/database.py b/api/webscraper/database.py index c84c752..d0c02b5 100644 --- a/api/webscraper/database.py +++ b/api/webscraper/database.py @@ -12,6 +12,7 @@ filter_nyiso_cluster_sheet, filter_nyiso_in_service_sheet, ) +from ores_scraper import query_ores_noi, query_ores_under_review, query_ores_permitted from utils.scraper_utils import ( create_update_object, update_kdm, @@ -413,6 +414,162 @@ def nyiso_in_service_to_database(): print(exception) +def ores_noi_to_database(): + database = [] + database.extend(query_ores_noi()) + for project in database: + existing_data = ( + supabase.table("Projects_duplicate") + .select("*") + .eq("project_name", project["project_name"]) + .execute() + ) + if len(existing_data.data) > 0: + existing_project = existing_data.data[0] + update_object = create_update_object(existing_project, project) + try: + response = ( + supabase.table("Projects_duplicate") + .update(update_object) + .eq( + "project_name", + project["project_name"], + ) + .execute() + ) + print("UPDATE", response, "\n") + except Exception as exception: + print(exception) + else: + try: + response = ( + supabase.table("Projects_duplicate").insert(project).execute() + ) + print("INSERT", response, "\n") + except Exception as exception: + print(exception) + + +def ores_under_review_to_database(): + database = [] + database.extend(query_ores_under_review()) + for project in database: + existing_data = ( + supabase.table("Projects_duplicate") + .select("*") + .eq("project_name", project["project_name"]) + .execute() + ) + if len(existing_data.data) > 0: + existing_project = existing_data.data[0] + update_object = create_update_object(existing_project, project) + # if the existing project has no kdms, add the dict first + if ( + existing_project["key_development_milestones"] is None + or len(existing_project["key_development_milestones"]) < 0 + ): + update_object["key_development_milestones"] = initial_kdm_dict + else: + update_object["key_development_milestones"] = existing_project[ + "key_development_milestones" + ] + + # update kdm for ores projects under review + update_object["key_development_milestones"] = update_kdm( + milestoneTitle="Application for permit to ORES", + completed=True, + date=None, + kdm=update_object["key_development_milestones"], + ) + try: + response = ( + supabase.table("Projects_duplicate") + .update(update_object) + .eq( + "project_name", + project["project_name"], + ) + .execute() + ) + print("UPDATE", response, "\n") + except Exception as exception: + print(exception) + else: + project["key_development_milestones"] = update_kdm( + milestoneTitle="Application for permit to ORES", + completed=True, + date=None, + kdm=project["key_development_milestones"], + ) + try: + response = ( + supabase.table("Projects_duplicate").insert(project).execute() + ) + print("INSERT", response, "\n") + except Exception as exception: + print(exception) + + +def ores_permitted_to_database(): + database = [] + database.extend(query_ores_permitted()) + for project in database: + existing_data = ( + supabase.table("Projects_duplicate") + .select("*") + .eq("project_name", project["project_name"]) + .execute() + ) + if len(existing_data.data) > 0: + existing_project = existing_data.data[0] + update_object = create_update_object(existing_project, project) + # if the existing project has no kdms, add the dict first + if ( + existing_project["key_development_milestones"] is None + or len(existing_project["key_development_milestones"]) < 0 + ): + update_object["key_development_milestones"] = initial_kdm_dict + else: + update_object["key_development_milestones"] = existing_project[ + "key_development_milestones" + ] + + # update kdm for ores projects under review + update_object["key_development_milestones"] = update_kdm( + milestoneTitle="Issuance of permit from ORES", + completed=True, + date=None, + kdm=update_object["key_development_milestones"], + ) + try: + response = ( + supabase.table("Projects_duplicate") + .update(update_object) + .eq( + "project_name", + project["project_name"], + ) + .execute() + ) + print("UPDATE", response, "\n") + except Exception as exception: + print(exception) + else: + project["key_development_milestones"] = update_kdm( + milestoneTitle="Issuance of permit from ORES", + completed=True, + date=None, + kdm=project["key_development_milestones"], + ) + try: + response = ( + supabase.table("Projects_duplicate").insert(project).execute() + ) + print("INSERT", response, "\n") + except Exception as exception: + print(exception) + + """ For testing """ @@ -420,3 +577,6 @@ def nyiso_in_service_to_database(): # nyserda_solar_to_database() # nyiso_to_database() # nyiso_in_service_to_database() +# ores_noi_to_database() +# ores_under_review_to_database() +# ores_permitted_to_database() diff --git a/api/webscraper/database_constants.py b/api/webscraper/database_constants.py index 1c4f9af..1df8137 100644 --- a/api/webscraper/database_constants.py +++ b/api/webscraper/database_constants.py @@ -40,4 +40,14 @@ "date": None, }, {"milestoneTitle": "Start of operations", "completed": False, "date": None}, + { + "milestoneTitle": "Application for permit to ORES", + "completed": False, + "date": None, + }, + { + "milestoneTitle": "Issuance of permit from ORES", + "completed": False, + "date": None, + }, ] diff --git a/api/webscraper/nyserda_scraper.py b/api/webscraper/nyserda_scraper.py index 195f2cc..686a705 100644 --- a/api/webscraper/nyserda_scraper.py +++ b/api/webscraper/nyserda_scraper.py @@ -76,8 +76,6 @@ def write_large_to_json(): file.write("\n") -write_large_to_json() - """ This scrapes data from the NYSERDA Statewide Distributed Solar Projects database. We filter for specific columns from the database's API and save them to a json file. diff --git a/api/webscraper/ores_scraper.py b/api/webscraper/ores_scraper.py new file mode 100644 index 0000000..9c830df --- /dev/null +++ b/api/webscraper/ores_scraper.py @@ -0,0 +1,180 @@ +import requests +from bs4 import BeautifulSoup +import pandas as pd +from io import StringIO +from utils.scraper_utils import geocode_lat_long, update_kdm +from database_constants import initial_kdm_dict + +# url = "https://dps.ny.gov/ores-permit-applications" +# page = requests.get(url) + +# soup = BeautifulSoup(page.content, "html.parser") +# tables = soup.find_all("table") + +# notices_of_intent = pd.read_html(StringIO(tables[0].prettify()))[0] +# noi_dict = notices_of_intent.to_dict(orient="records") + +# # Complete Applications Under Review +# under_review = pd.read_html(StringIO(tables[3].prettify()))[0] +# under_review_dict = under_review.to_dict(orient="records") + +# # Permitted Applications +# permitted = pd.read_html(StringIO(tables[4].prettify()))[0] +# permitted_dict = permitted.to_dict(orient="records") + +""" +All the descriptions of the ORES data describe the location of the project in the following format: +... Located in the Towns of ALTONA, CLINTON, ELLENBURG, and MOOERS, CLINTON COUNTY. +""" + + +def parse_for_location(description): + # finds index in the description where the phrase "Town of..." appears + town_index = description.find("Town") + town_string = description[town_index:] + # splits town_string by the comma + town_split = town_string.split(",") + # town is the second to last word before the comma + town = town_split[-2].split(" ")[-1].strip() + # county is the last word when the location string is split by commas + county = town_split[-1].strip() + + # removes the period from the end of county if it exists + index = county.find(".") + if index != -1: + while county.find(".", index + 1) != -1: + index = county.find(".", index + 1) + county = county[:index] + + # capitalize first letter of each word in town/county name + if town: + town = " ".join([word.capitalize() for word in town.split(" ")]) + if county: + county = " ".join([word.capitalize() for word in county.split(" ")]) + return (town, county) + + +# ORES notice of intent +def filter_noi(data: list) -> list: + """ + params: data - list of dictionaries representing rows in the ORES Notices of Intent table + Parses description to find town, county of project + Reverse Geocodes for latitude and longitude + Returns list of projects with data filtered to include the desired fields + """ + filtered_list = [] + for row in data: + town, county = parse_for_location(row["Description"]) + lat, long = geocode_lat_long(f"{town}, NY") + project_dict = { + "permit_application_number": row.get("Permit Application Number", None), + "project_name": row.get("Project Name", None), + "town": town if town else None, + "county": county if county else None, + "latitude": lat if lat else None, + "longitude": long if long else None, + "key_development_milestones": initial_kdm_dict, + } + filtered_list.append(project_dict) + + return filtered_list + + +def filter_under_review(data: list) -> list: + """ + params: data - list of dictionaries representing rows in the ORES Completed Projects Under Review table + Parses description to find town, county of project + Reverse Geocodes for latitude and longitude + Returns list of projects with data filtered to include the desired fields + """ + filtered_list = [] + for row in data: + town, county = parse_for_location(row["Description"]) + lat, long = geocode_lat_long(f"{town}, NY") + project_dict = { + "permit_application_number": row.get("Permit Application Number", None), + "project_name": row.get("Project Name", None), + "town": town if town else None, + "county": county if county else None, + "latitude": lat if lat else None, + "longitude": long if long else None, + "key_development_milestones": initial_kdm_dict, + } + project_dict["key_development_milestones"] = update_kdm( + "Application for permit to ORES", + date=None, + completed=True, + kdm=project_dict.get("key_development_milestones"), + ) + filtered_list.append(project_dict) + return filtered_list + + +def filter_permitted(data): + """ + params: data - list of dictionaries representing rows in the ORES Permitted Applications table + Parses description to find town, county of project + Reverse Geocodes for latitude and longitude + Returns list of projects with data filtered to include the desired fields + """ + filtered_list = [] + for row in data: + town, county = parse_for_location(row["Description"]) + lat, long = geocode_lat_long(f"{town}, NY") + project_dict = { + "permit_application_number": row.get("Permit Application Number", None), + "project_name": row.get("Project Name", None), + "town": town if town else None, + "county": county if county else None, + "latitude": lat if lat else None, + "longitude": long if long else None, + "key_development_milestones": initial_kdm_dict, + } + project_dict["key_development_milestones"] = update_kdm( + "Issuance of permit from ORES", + date=None, + completed=True, + kdm=project_dict.get("key_development_milestones"), + ) + filtered_list.append(project_dict) + return filtered_list + + +# ORES notice of review +def query_ores_noi(): + url = "https://dps.ny.gov/ores-permit-applications" + page = requests.get(url) + + soup = BeautifulSoup(page.content, "html.parser") + tables = soup.find_all("table") + + notices_of_intent = pd.read_html(StringIO(tables[0].prettify()))[0] + noi_dict = notices_of_intent.to_dict(orient="records") + response = filter_noi(noi_dict) + return response + + +def query_ores_under_review(): + url = "https://dps.ny.gov/ores-permit-applications" + page = requests.get(url) + + soup = BeautifulSoup(page.content, "html.parser") + tables = soup.find_all("table") + + under_review = pd.read_html(StringIO(tables[3].prettify()))[0] + under_review_dict = under_review.to_dict(orient="records") + response = filter_under_review(under_review_dict) + return response + + +def query_ores_permitted(): + url = "https://dps.ny.gov/ores-permit-applications" + page = requests.get(url) + + soup = BeautifulSoup(page.content, "html.parser") + tables = soup.find_all("table") + + permitted = pd.read_html(StringIO(tables[4].prettify()))[0] + permitted_dict = permitted.to_dict(orient="records") + response = filter_under_review(permitted_dict) + return response diff --git a/api/webscraper/utils/__pycache__/__init__.cpython-312.pyc b/api/webscraper/utils/__pycache__/__init__.cpython-312.pyc index 229c244faa7a71c8c03c6e0b32fb28ce44fd7041..492d23651037e727eae6706b0351728b6056a7eb 100644 GIT binary patch delta 19 ZcmZ3@xSEmsG%qg~0}#xcsymT;0RS&;1n&R< delta 19 ZcmZ3@xSEmsG%qg~0}#xW6PU=o001nY1X=(9 diff --git a/package.json b/package.json index efc172d..5b51f3c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ }, "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.3.1", + "@types/google.maps": "^3.58.1", "@types/node": "^20.17.2", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff31001..fc0581b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,6 +45,9 @@ importers: '@ianvs/prettier-plugin-sort-imports': specifier: ^4.3.1 version: 4.3.1(prettier@3.3.3) + '@types/google.maps': + specifier: ^3.58.1 + version: 3.58.1 '@types/node': specifier: ^20.17.2 version: 20.17.2 @@ -2572,7 +2575,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -2585,7 +2588,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -2607,7 +2610,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.2.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3