Em4e's picture
Update app.py
3c3cecf verified
import streamlit as st
import math
# Adhering to OOP principles by encapsulating the forecasting logic
class SEOImpactForecaster:
"""
A class to calculate the estimated incremental SEO traffic based on internal linking improvements.
Follows Single Responsibility Principle (SRP) by focusing solely on calculation.
"""
_CTR_CURVE = {
1: 0.28, # Position 1: ~28% CTR
2: 0.15, # Position 2: ~15% CTR
3: 0.10, # Position 3: ~10% CTR
4: 0.07, # Position 4: ~7% CTR
5: 0.05, # Position 5: ~5% CTR
6: 0.03, # Position 6: ~3% CTR
7: 0.02, # Position 7: ~2% CTR
8: 0.015, # Position 8: ~1.5% CTR
9: 0.01, # Position 9: ~1% CTR
10: 0.008 # Position 10: ~0.8% CTR
}
def calculate_incremental_traffic(
self,
current_position: int,
target_position: int,
search_volume_proxy_monthly: float,
manual_ctr_improvement: float = None
) -> dict:
"""
Calculates the estimated incremental SEO traffic.
Args:
current_position (int): The current average search engine ranking position for the keywords.
target_position (int): The target average search engine ranking position after internal linking.
search_volume_proxy_monthly (float): Monthly proxy for search volume (e.g., GSC Impressions / 12).
manual_ctr_improvement (float, optional): Manual CTR improvement as a decimal (e.g., 0.10 for 10%).
If provided, this overrides the position-based CTR estimation.
Returns:
dict: A dictionary containing the estimated monthly and annual incremental traffic.
"""
# Input validation for search_volume_proxy_monthly
if search_volume_proxy_monthly < 0:
raise ValueError("Monthly search volume proxy cannot be negative.")
# Determine CTR improvement.
if manual_ctr_improvement is not None:
if not (0 <= manual_ctr_improvement <= 1):
raise ValueError("Manual CTR improvement must be a decimal between 0 and 1 (e.g., 0.10 for 10%).")
ctr_improvement = manual_ctr_improvement
st.info(f"Using manual CTR improvement: {manual_ctr_improvement:.2%}")
else:
# Validate and clamp input positions for CTR curve
if not (1 <= current_position <= 10 and 1 <= target_position <= 10):
st.warning("Current and target positions should ideally be between 1 and 10 for reasonable CTR estimation.")
current_position = max(1, min(10, current_position))
target_position = max(1, min(10, target_position))
current_ctr = self._CTR_CURVE.get(current_position, 0)
target_ctr = self._CTR_CURVE.get(target_position, 0)
ctr_improvement = target_ctr - current_ctr
if ctr_improvement < 0:
st.warning(f"Target position ({target_position}) has a lower estimated CTR ({target_ctr:.2%}) than current position ({current_position}) ({current_ctr:.2%}). This will result in negative incremental traffic.")
else:
st.info(f"Estimated CTR improvement from Pos {current_position} ({current_ctr:.2%}) to Pos {target_position} ({target_ctr:.2%}): {ctr_improvement:.2%} percentage points.")
incremental_traffic_monthly = ctr_improvement * search_volume_proxy_monthly
incremental_traffic_annual = incremental_traffic_monthly * 12
return {
"incremental_traffic_monthly": incremental_traffic_monthly,
"incremental_traffic_annual": incremental_traffic_annual
}
def run_streamlit_app():
"""
Main function to run the Streamlit application for SEO Impact Forecaster.
Handles user input and displays results using Streamlit widgets.
"""
st.set_page_config(
page_title="Internal Linking SEO Impact Forecaster",
layout="centered",
initial_sidebar_state="auto"
)
st.title("Internal Linking SEO Impact Forecaster")
st.markdown("This tool helps estimate incremental SEO traffic from internal linking improvements.")
st.markdown("---")
# Input for monthly search volume proxy
search_volume_proxy_monthly = st.number_input(
"Enter the approximate *monthly total search impressions* for the keywords you are targeting:",
min_value=0.0,
value=100000.0, # Example default
step=10000.0,
format="%f",
help="This can be obtained from Google Search Console (GSC) Impressions data. "
"If you have annual impressions, divide by 12 to get a monthly figure. "
"For example, if you get 24 million annual impressions, enter 2000000 for monthly."
)
# Choose between manual CTR improvement or position-based calculation
ctr_method = st.radio(
"How do you want to determine CTR improvement?",
("Estimate based on position change", "Enter specific CTR improvement percentage")
)
manual_ctr_decimal = None
current_pos = None
target_pos = None
if ctr_method == "Enter specific CTR improvement percentage":
manual_ctr_percent = st.number_input(
"Enter the CTR improvement percentage (e.g., 10 for 10%):",
min_value=0.0,
max_value=100.0,
value=5.0, # Example default
step=0.1,
format="%f",
help="Enter the expected increase in CTR as a percentage (e.g., 5 for a 5% increase in CTR)."
)
manual_ctr_decimal = manual_ctr_percent / 100
else:
current_pos = st.number_input(
"Enter your current average SEO ranking position (1-10 recommended):",
min_value=1,
max_value=100, # Allow higher but warn
value=5, # Example default
step=1,
help="The current average position of your targeted keywords in search results."
)
target_pos = st.number_input(
"Enter your target average SEO ranking position (1-10 recommended):",
min_value=1,
max_value=100, # Allow higher but warn
value=3, # Example default
step=1,
help="The desired average position after implementing internal linking strategies."
)
st.markdown("---")
# Calculate button
if st.button("Calculate Estimated Traffic"):
forecaster = SEOImpactForecaster()
try:
if manual_ctr_decimal is not None:
results = forecaster.calculate_incremental_traffic(
current_position=1, # Dummy value, not used when manual_ctr_improvement is provided
target_position=1, # Dummy value, not used when manual_ctr_improvement is provided
search_volume_proxy_monthly=search_volume_proxy_monthly,
manual_ctr_improvement=manual_ctr_decimal
)
else:
results = forecaster.calculate_incremental_traffic(
current_position=current_pos,
target_position=target_pos,
search_volume_proxy_monthly=search_volume_proxy_monthly
)
st.success("### Forecast Results")
st.metric(
label="Estimated Monthly Incremental Traffic",
value=f"{math.ceil(results['incremental_traffic_monthly']):,}"
)
st.metric(
label="Estimated Annual Incremental Traffic",
value=f"{math.ceil(results['incremental_traffic_annual']):,}"
)
except ValueError as e:
st.error(f"Error: {e}")
except Exception as e:
st.error(f"An unexpected error occurred: {e}")
st.markdown("---")
st.info("### Important Notes")
st.markdown("""
- The CTR curve used is a **simplified model**. Actual CTRs vary greatly by niche, SERP features, and keyword intent.
- 'Search Volume Proxy' assumes Google Search Console (GSC) Impressions / 12 is a good stand-in for actual search volume.
- This is a **forecast**; actual results may differ based on many other SEO factors (e.g., content quality, competition, technical SEO).
- For more accurate CTR improvements, consider using A/B testing or historical data specific to your site.
""")
# New section for CTR Benchmarks
st.markdown("---")
st.info("### Reference CTR Benchmarks (Simplified Model)")
st.markdown("""
These are the approximate Click-Through Rates (CTRs) used in this model based on general observations.
Please note these are simplified and actual CTRs can vary significantly.
""")
# Display CTR curve in a table or list
ctr_data = SEOImpactForecaster._CTR_CURVE
st.table(
{"Position": list(ctr_data.keys()),
"Approximate CTR": [f"{v:.2%}" for v in ctr_data.values()]}
)
# To run the Streamlit app, save this code as a .py file (e.g., app.py)
# and run 'streamlit run app.py' in your terminal.
if __name__ == "__main__":
run_streamlit_app()