File size: 8,248 Bytes
9072331
 
 
 
 
 
 
 
 
 
3751938
9072331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

from uszipcode import SearchEngine
import pandas as pd


gasBuddyurl = 'https://www.gasbuddy.com/home'

# To make this work as of 08/16/2024
# Make sure to use sqlalchemy_mate = 2.0.0.0

def fetchZipCodes():
    engine = SearchEngine()
    allzips = {}
    population_threshold = 10000
    density_threshold = 1000

    for i in range(100000): #Get zipcode info for every possible 5-digit combination
        zipcode = str(i).zfill(5)
        try: 
            allzips[zipcode] = engine.by_zipcode(zipcode).to_dict()
        except: 
            pass
        
        
    #Convert dictionary to DataFrame
    allzips = pd.DataFrame(allzips).T.reset_index(drop = True)
    allzips = allzips.convert_dtypes()
    allzips.dropna(inplace=True)

    columnsNeeded = ['zipcode', 'zipcode_type', 'major_city','county', 'state', 'population', 'population_density']
    zipsRequired = allzips[columnsNeeded]
    zipsRequired = zipsRequired.query('zipcode_type == "STANDARD"')

        # Example thresholds (you can adjust these based on your criteria)

    # Assuming your DataFrame is named `df` and has columns 'population' and 'population_density'
    filtered_df = zipsRequired[(zipsRequired['population'] > population_threshold) & (zipsRequired['population_density'] > density_threshold)]

    # Excluding Territories
    exclude_states = ['PR', 'VI', 'GU']

    # Filter the DataFrame
    zipsRequired = filtered_df[~filtered_df['state'].isin(exclude_states)]
    return filtered_df





def GasBuddy(url, zipCodes, headless = True):


    chrome_options = Options()
    if headless:
        chrome_options.add_argument("--headless")  # Run in headless mode
        chrome_options.add_argument("--no-sandbox")  # Sandbox mode disabled (Linux-specific)

    driverLocal = webdriver.Chrome(service=Service(), options=chrome_options)
    driverLocal.get(url)

    price_Heads = {}
    price_Heads.fromkeys(zipCodes)
    


    for z in zipCodes:
        price_Heads[z] = {}
        price_Heads[z].setdefault('price', [])
        price_Heads[z].setdefault('station', [])
        # Enter the Zip Code and Hit Enter
        try:
            enterZipCode = WebDriverWait(driverLocal, 5).until(
                expected_conditions.presence_of_element_located((By.XPATH, \
                                                                 ".//div[@class='input__inputGroup___3ogvw'] \
                                                                /input[@id='search'] \
                                                                ")))
            enterZipCode.clear()
            enterZipCode.send_keys(z)
            enterZipCode.send_keys(Keys.RETURN)

            # Check for the presence of the error message
            error_message = None
            try:
                error_message = WebDriverWait(driverLocal, 2).until(
                    expected_conditions.presence_of_element_located((By.XPATH, ".//div/em[contains(text(), 'No stations found')]")))
            except:
                pass

            if error_message:
                price_Heads[z] = f"No stations found for ZIP code: {z}"
                continue

            blocks = WebDriverWait(driverLocal, 100).until(
                expected_conditions.presence_of_all_elements_located((By.XPATH, \
                                                                "\
                                                                //div[@class='GenericStationListItem-module__stationListItem___3Jmn4'] \
                                                                ")))
            for b in blocks:
                t = b.find_element(By.XPATH, ".//h3[contains(@class, 'StationDisplay-module__stationNameHeader___1A2q8')]").text
                p = b.find_element(By.XPATH, ".//span[@class='text__xl___2MXGo text__left___1iOw3 StationDisplayPrice-module__price___3rARL']").text
                price_Heads[z].get('station').append(t)
                price_Heads[z].get('price').append(p)

        except:
            pass
    driverLocal.close()  
    return price_Heads




def convertDF(data):
    # Flatten the dictionary to create a list of records
    records = []
    for zipcode, details in data.items():
        if isinstance(details, dict):
            for price, heading in zip(details['price'], details['station']):
                # Handle missing price by replacing '- - -' with 0, and remove the '$' sign
                if price == '- - -':
                    continue
                else:
                    price_value = float(price.replace('$', ''))

                records.append({'zipcode': zipcode, 'station': heading.strip(), 'price': price_value})
    return pd.DataFrame(records)





def loadLottieFile(filePath: str):
    with open(file=filePath, mode = "r") as f:
        return json.load(f)








import streamlit as st
import json
from streamlit_lottie import st_lottie_spinner

st.set_page_config(layout="wide",
                    page_title="Portfolio Optimization For Commodities", 
                    page_icon="Portfolio_Icon.jpg")

if 'loadOnce' not in st.session_state:
    anm = loadLottieFile("Anims/location.json")
    with st_lottie_spinner(anm , height=400):
        st.session_state.loadOnce = fetchZipCodes()
if 'scrappedData' not in st.session_state:
    st.session_state.scrappedData = None
    
# Group by major_city and state, and aggregate zip codes into lists
grouped = st.session_state.loadOnce.groupby(['major_city', 'state'])['zipcode'].apply(list).reset_index()

# Create a dictionary with formatted keys and list of zip codes as values
city_state_zip_dict = {
    f"{row['major_city']}, {row['state']}": row['zipcode']
    for _, row in grouped.iterrows()
}


_,center,_ = st.columns((2,4,2))


with center.form(key = "parameters"):
    st.subheader("Please select locations and configuration parameters as per your scraping needs")

    #-------------------------------------------- Zip Codes ----------------------------------------------------------------
    st.write("")
    selected_locations = st.multiselect('Select Locations and Data will be scrapped as per their zip codes', list(city_state_zip_dict.keys()), default=list())
    st.write("")

    head = st.checkbox(label="Please check if you want headless scrapping")
    submitted = st.form_submit_button("Download")



_, _leftDf, cenButtons, _rightDf, _ = st.columns((2,4,2,4,2))
if submitted:
    # Fetch values for the specified keys and flatten the list
    zipcodes = [zipcode for key in selected_locations if key in city_state_zip_dict for zipcode in city_state_zip_dict[key]]
    anm = loadLottieFile("Anims/citations.json")
    with st_lottie_spinner(anm , height=400):
        prices = GasBuddy(url=gasBuddyurl, zipCodes=zipcodes, headless=head)
    finalDf = convertDF(prices)
    finalDf2 = pd.merge(finalDf, st.session_state.loadOnce[['zipcode', 'major_city', 'state']], on='zipcode', how='left')
    
    st.session_state.scrappedData = finalDf2
    _leftDf.dataframe(finalDf2, use_container_width=True)


cols = st.session_state.scrappedData.columns if st.session_state.scrappedData is not None else []
with cenButtons.form(key='parametersForm2'):
    # if st.session_state.scrappedData is not None:
    ind = st.multiselect(label='Column Names for index (new row identifiers) of Pivot Table', options= cols)
    val = st.multiselect(label='Column Names for values of Pivot Table', options= cols)
    col = st.multiselect(label='Column Names for columns of Pivot Table', options= cols)
    submitted2 = st.form_submit_button("Pivot Data")


if submitted2:
    try:
        pivoted = st.session_state.scrappedData.pivot_table(index=ind, values=val, columns=col)
        _rightDf.dataframe(pivoted, use_container_width = True)

            
    except:
        cenButtons.error("Your Configurations for Pivot Table are not accepable. Do you mind trying a different combination?")