Em4e commited on
Commit
d767c8e
·
verified ·
1 Parent(s): 2550ee1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +21 -22
app.py CHANGED
@@ -18,14 +18,12 @@ class DataLoader:
18
  self.sample_file_url = sample_file_url
19
 
20
  @st.cache_data
21
- # _self is correct for the instance itself
22
- def load_csv(_self, uploaded_file_obj: st.runtime.uploaded_file_manager.UploadedFile | None) -> pd.DataFrame | None:
23
  """
24
  Loads the GSC data from an uploaded CSV or a sample URL,
25
  normalizes column names, and ensures a 'cpc' column exists.
26
-
27
  Args:
28
- _self: The instance of the DataLoader class (ignored by Streamlit caching).
29
  uploaded_file_obj (streamlit.runtime.uploaded_file_manager.UploadedFile): The file object
30
  uploaded by the user, or None.
31
  Returns:
@@ -35,8 +33,8 @@ class DataLoader:
35
  if uploaded_file_obj:
36
  df = pd.read_csv(uploaded_file_obj)
37
  else:
38
- # Use _self.sample_file_url since _self is the instance
39
- df = pd.read_csv(_self.sample_file_url)
40
  except Exception as e:
41
  st.error(f"Error loading file: {e}")
42
  return None
@@ -83,9 +81,8 @@ class SeoCalculator:
83
  return df.rename(columns={found_columns[k]: k for k in found_columns})
84
 
85
  @st.cache_data
86
- # _self is correct for the instance itself
87
  def calculate_metrics(
88
- _self, # Changed to _self
89
  df: pd.DataFrame,
90
  target_position: float,
91
  conversion_rate: float,
@@ -96,18 +93,16 @@ class SeoCalculator:
96
  ) -> tuple[dict, pd.DataFrame] | tuple[None, pd.DataFrame]:
97
  """
98
  Performs core calculations for SEO forecasting based on GSC data and user inputs.
99
-
100
  Returns:
101
  tuple: A dictionary of calculated metrics and a DataFrame with detailed results.
102
  Returns (None, pd.DataFrame()) if required columns are missing.
103
  """
104
- # Use _self.ctr_benchmarks and _self.required_columns_map, etc.
105
- df_processed = _self._validate_and_rename_columns(df.copy())
106
  if df_processed is None:
107
  return None, pd.DataFrame()
108
 
109
- df_processed["current_ctr"] = df_processed["position"].apply(_self._get_ctr)
110
- target_ctr_value = _self._get_ctr(target_position)
111
  df_processed["target_ctr"] = target_ctr_value
112
 
113
  df_processed["current_clicks"] = df_processed["impressions"] * df_processed["current_ctr"]
@@ -199,6 +194,7 @@ class SeoAppUI:
199
  def _get_sidebar_inputs(self) -> tuple:
200
  with st.sidebar:
201
  st.header("🔧 Assumptions & Inputs")
 
202
  uploaded_file = st.file_uploader("Upload queries CSV data", type="csv")
203
  target_position = st.slider(
204
  "Target SERP Position",
@@ -214,13 +210,6 @@ class SeoAppUI:
214
  seo_cost = st.slider("Total SEO Investment ($)", 1_000, 100_000, 10_000, 1_000)
215
  add_spend = st.slider("Additional Ad Spend ($)", 0, 50_000, 0, 1_000, help="A **hypothetical budget** for extra paid ad spend, not from your GSC data. Use it to directly compare SEO's projected incremental MRR with a potential ad investment.")
216
 
217
- sample_bytes = requests.get(SAMPLE_FILE_URL).content
218
- st.download_button(
219
- label="📥 Download sample CSV",
220
- data=sample_bytes,
221
- file_name="sample_gsc_data.csv",
222
- mime="text/csv",
223
- )
224
  return uploaded_file, target_position, conversion_rate, close_rate, mrr_per_customer, seo_cost, add_spend
225
 
226
  def _display_summary_metrics(self, metrics: dict):
@@ -293,13 +282,23 @@ class SeoAppUI:
293
 
294
  def run(self):
295
  self._display_info_expander()
 
 
 
 
 
 
 
 
 
 
 
 
296
  uploaded_file, target_position, conversion_rate, close_rate, mrr_per_customer, seo_cost, add_spend = self._get_sidebar_inputs()
297
 
298
- # FIX: Call load_csv normally, Python handles the _self
299
  df = self.data_loader.load_csv(uploaded_file)
300
 
301
  if df is not None:
302
- # FIX: Call calculate_metrics normally, Python handles the _self
303
  metrics, df_results = self.seo_calculator.calculate_metrics(
304
  df,
305
  target_position,
 
18
  self.sample_file_url = sample_file_url
19
 
20
  @st.cache_data
21
+ def load_csv(self, uploaded_file_obj: st.runtime.uploaded_file_manager.UploadedFile | None) -> pd.DataFrame | None:
 
22
  """
23
  Loads the GSC data from an uploaded CSV or a sample URL,
24
  normalizes column names, and ensures a 'cpc' column exists.
 
25
  Args:
26
+ self: The instance of the DataLoader class.
27
  uploaded_file_obj (streamlit.runtime.uploaded_file_manager.UploadedFile): The file object
28
  uploaded by the user, or None.
29
  Returns:
 
33
  if uploaded_file_obj:
34
  df = pd.read_csv(uploaded_file_obj)
35
  else:
36
+ # Use self.sample_file_url since self is the instance
37
+ df = pd.read_csv(self.sample_file_url)
38
  except Exception as e:
39
  st.error(f"Error loading file: {e}")
40
  return None
 
81
  return df.rename(columns={found_columns[k]: k for k in found_columns})
82
 
83
  @st.cache_data
 
84
  def calculate_metrics(
85
+ self,
86
  df: pd.DataFrame,
87
  target_position: float,
88
  conversion_rate: float,
 
93
  ) -> tuple[dict, pd.DataFrame] | tuple[None, pd.DataFrame]:
94
  """
95
  Performs core calculations for SEO forecasting based on GSC data and user inputs.
 
96
  Returns:
97
  tuple: A dictionary of calculated metrics and a DataFrame with detailed results.
98
  Returns (None, pd.DataFrame()) if required columns are missing.
99
  """
100
+ df_processed = self._validate_and_rename_columns(df.copy())
 
101
  if df_processed is None:
102
  return None, pd.DataFrame()
103
 
104
+ df_processed["current_ctr"] = df_processed["position"].apply(self._get_ctr)
105
+ target_ctr_value = self._get_ctr(target_position)
106
  df_processed["target_ctr"] = target_ctr_value
107
 
108
  df_processed["current_clicks"] = df_processed["impressions"] * df_processed["current_ctr"]
 
194
  def _get_sidebar_inputs(self) -> tuple:
195
  with st.sidebar:
196
  st.header("🔧 Assumptions & Inputs")
197
+ # The upload file will remain in the sidebar as it's an input
198
  uploaded_file = st.file_uploader("Upload queries CSV data", type="csv")
199
  target_position = st.slider(
200
  "Target SERP Position",
 
210
  seo_cost = st.slider("Total SEO Investment ($)", 1_000, 100_000, 10_000, 1_000)
211
  add_spend = st.slider("Additional Ad Spend ($)", 0, 50_000, 0, 1_000, help="A **hypothetical budget** for extra paid ad spend, not from your GSC data. Use it to directly compare SEO's projected incremental MRR with a potential ad investment.")
212
 
 
 
 
 
 
 
 
213
  return uploaded_file, target_position, conversion_rate, close_rate, mrr_per_customer, seo_cost, add_spend
214
 
215
  def _display_summary_metrics(self, metrics: dict):
 
282
 
283
  def run(self):
284
  self._display_info_expander()
285
+
286
+ # Moved the download button out of the sidebar to the main area
287
+ sample_bytes = requests.get(SAMPLE_FILE_URL).content
288
+ st.download_button(
289
+ label="📥 Download sample CSV",
290
+ data=sample_bytes,
291
+ file_name="sample_gsc_data.csv",
292
+ mime="text/csv",
293
+ key="download_sample_main" # Added a key to avoid potential duplicate widget issues
294
+ )
295
+ st.markdown("---") # Add a separator for better visual organization
296
+
297
  uploaded_file, target_position, conversion_rate, close_rate, mrr_per_customer, seo_cost, add_spend = self._get_sidebar_inputs()
298
 
 
299
  df = self.data_loader.load_csv(uploaded_file)
300
 
301
  if df is not None:
 
302
  metrics, df_results = self.seo_calculator.calculate_metrics(
303
  df,
304
  target_position,