File size: 16,284 Bytes
4a79595
ff7a2f7
438882a
4a79595
438882a
 
 
 
5d07470
438882a
 
 
5d97e4f
6b29192
438882a
2256252
438882a
5f7d6bc
5d97e4f
 
 
4a79595
 
 
2256252
4a79595
2256252
 
 
6b29192
438882a
2256252
 
5f7d6bc
 
4a79595
5f7d6bc
2256252
 
 
 
 
 
 
 
 
438882a
2256252
438882a
2256252
 
 
5d97e4f
2256252
 
5d97e4f
2256252
 
 
5d97e4f
2256252
 
 
 
5d97e4f
 
2256252
 
 
 
 
 
 
 
 
 
 
 
 
b13593c
2256252
b13593c
 
 
 
 
 
 
2256252
b13593c
 
 
 
 
 
2256252
b13593c
 
 
 
 
 
 
 
2256252
b13593c
 
 
 
 
 
 
 
 
2256252
b13593c
 
 
 
2256252
b13593c
 
 
 
 
 
5f7d6bc
2256252
 
 
 
 
 
438882a
2256252
b13593c
438882a
2256252
 
 
 
 
 
 
 
5f7d6bc
b13593c
 
 
 
 
 
 
 
 
 
2256252
 
 
b13593c
2256252
 
 
 
 
 
5f7d6bc
 
2256252
6b29192
5f7d6bc
 
 
 
6b29192
5f7d6bc
2256252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5f7d6bc
2256252
5f7d6bc
 
 
2256252
5f7d6bc
 
 
 
 
 
 
 
 
 
 
 
 
2256252
5f7d6bc
 
5d97e4f
b13593c
 
 
2256252
5f7d6bc
 
4a79595
5f7d6bc
2256252
 
 
 
5f7d6bc
2256252
5f7d6bc
 
2256252
5f7d6bc
 
 
 
 
 
 
 
 
 
 
 
2256252
 
 
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# src/streamlit_app.py
import streamlit as st
import pandas as pd
from serpapi import Client
from prophet import Prophet
import plotly.express as px
import time
import google.generativeai as genai
import os
from matplotlib import pyplot as plt
from statsmodels.tsa.seasonal import seasonal_decompose

# --- Page Configuration ---
st.set_page_config(page_title="Trend-AI Marketing Dashboard", page_icon="๐Ÿง ", layout="wide")

# --- Caching & Data Fetching Functions ---
@st.cache_data(ttl=3600)
def fetch_data_from_serpapi(api_key, keywords, timeframe, geo):
    """
    Fetches Interest Over Time and By Region from SerpApi for a live analysis.
    """
    def _parse_date_string(date_str):
        if 'โ€“' in date_str:
            try:
                parts = date_str.split('โ€“'); start_day_month = parts[0].strip(); year = parts[1].split(',')[-1].strip()
                return pd.to_datetime(f"{start_day_month}, {year}")
            except: return pd.to_datetime(date_str.split('โ€“')[0].strip())
        else: return pd.to_datetime(date_str)
            
    params = {"engine": "google_trends", "q": ", ".join(keywords), "date": timeframe, "geo": geo, "api_key": api_key}
    try:
        client = Client(); results = client.search(params)
        all_data = {"interest_over_time": pd.DataFrame(), "interest_by_region": pd.DataFrame()}
        if 'interest_over_time' in results:
            timeline_data = results['interest_over_time']['timeline_data']
            dates = [_parse_date_string(item['date']) for item in timeline_data]
            data = {}
            for i, keyword in enumerate(keywords): data[keyword] = [item['values'][i].get('value', 0) for item in timeline_data]
            all_data["interest_over_time"] = pd.DataFrame(data, index=dates)
        if 'interest_by_region' in results:
            df_region = pd.DataFrame(results['interest_by_region']).set_index('geoName')
            if keywords and len(keywords) > 0:
                 renamed_col = df_region.columns[0]
                 df_region = df_region.rename(columns={renamed_col: 'Interest'})
            all_data["interest_by_region"] = df_region
        return all_data
    except Exception as e:
        st.error(f"An error occurred with the SerpApi request: {e}"); return None

@st.cache_data
def load_all_offline_data(scenario_config):
    """
    Loads Interest Over Time and By Region CSVs for a given offline scenario.
    """
    prefix = scenario_config["prefix"]
    keywords = scenario_config["keywords"]
    all_data = {"interest_over_time": pd.DataFrame(), "interest_by_region": pd.DataFrame()}
    try:
        ot_path = f"data/{prefix}_over_time.csv"
        df_ot = pd.read_csv(ot_path, skiprows=2); df_ot.rename(columns={df_ot.columns[0]: 'Date'}, inplace=True)
        original_columns = df_ot.columns[1:] 
        column_map = dict(zip(original_columns, keywords))
        df_ot.rename(columns=column_map, inplace=True)
        df_ot['Date'] = pd.to_datetime(df_ot['Date']); df_ot.set_index('Date', inplace=True)
        for col in keywords:
            if col in df_ot.columns: df_ot[col] = pd.to_numeric(df_ot[col], errors='coerce')
        all_data['interest_over_time'] = df_ot
        
        r_path = f"data/{prefix}_by_region.csv"
        df_r = pd.read_csv(r_path, skiprows=1); df_r.rename(columns={df_r.columns[0]: 'Region', df_r.columns[1]: 'Interest'}, inplace=True); df_r['Interest'] = pd.to_numeric(df_r['Interest'], errors='coerce')
        all_data['interest_by_region'] = df_r.set_index('Region')
        return all_data
    except FileNotFoundError as e:
        st.error(f"Offline Data Error: Missing a primary CSV file: {e.filename}"); return None
    except Exception as e:
        st.error(f"Error loading offline data: {e}"); return None

# --- Main Application Logic ---
st.title("๐Ÿ“ˆ Trend-AI: Marketing Intelligence Dashboard")
st.sidebar.header("Dashboard Controls")

# --- Final Sidebar UI using Session State and a Callback ---
scenarios = {
    "Nike, Adidas, Puma, Asics, Under Armour": {"prefix": "athletic_brands", "keywords": ["Nike", "Adidas", "Puma", "Asics", "Under Armour"], "geo_code": "", "timeframe_key": "Past 5 Years"},
    "Apple iPhone, Samsung Galaxy, Google Pixel": {"prefix": "smartphones", "keywords": ["Apple iPhone", "Samsung Galaxy", "Google Pixel"], "geo_code": "US", "timeframe_key": "Past 5 Years"},
    "Mahindra Scorpio, Maruti Suzuki Brezza, Hyundai Creta": {"prefix": "indian_suvs", "keywords": ["Mahindra Scorpio", "Maruti Suzuki Brezza", "Hyundai Creta"], "geo_code": "IN", "timeframe_key": "Past 5 Years"}
}
scenario_options = [""] + list(scenarios.keys())

# Initialize session state for widgets. Default to the "Athletic Brands" scenario.
if 'keywords_input' not in st.session_state:
    st.session_state.keywords_input = "Nike, Adidas, Puma, Asics, Under Armour"
    st.session_state.geo_selection = "" 
    st.session_state.timeframe_selection = "Past 5 Years"
    st.session_state.scenario_selector = "Nike, Adidas, Puma, Asics, Under Armour"

# Callback function to update other widgets when a scenario is chosen from the dropdown
def update_state_from_scenario():
    scenario_key = st.session_state.scenario_selector
    if scenario_key and scenario_key in scenarios:
        config = scenarios[scenario_key]
        st.session_state.keywords_input = scenario_key
        st.session_state.geo_selection = config["geo_code"]
        st.session_state.timeframe_selection = config["timeframe_key"]

# The UI widgets are linked via session state and the callback
keywords_input = st.sidebar.text_input("Enter Keywords", key="keywords_input")
st.sidebar.selectbox(
    "Or, select a popular comparison", 
    options=scenario_options, 
    key='scenario_selector', 
    on_change=update_state_from_scenario,
    help="Selecting an option will pre-fill the controls and use reliable offline data."
)

country_list = ['', 'US', 'GB', 'CA', 'AU', 'DE', 'FR', 'IN', 'JP', 'BR', 'ZA']; country_names = ['Worldwide', 'United States', 'United Kingdom', 'Canada', 'Australia', 'Germany', 'France', 'India', 'Japan', 'Brazil', 'South Africa']; country_dict = dict(zip(country_list, country_names)); geo_keys = list(country_dict.keys())
try: geo_default_index = geo_keys.index(st.session_state.geo_selection)
except ValueError: geo_default_index = 0
geo = st.sidebar.selectbox("Select Region", options=country_list, format_func=lambda x: country_dict[x], index=geo_default_index)

timeframe_options = {"Past Hour": "now 1-H", "Past 4 Hours": "now 4-H", "Past Day": "now 1-d", "Past 7 Days": "now 7-d", "Past 30 Days": "today 1-m", "Past 90 Days": "today 3-m", "Past 12 Months": "today 12-m", "Past 5 Years": "today 5-y", "All Time (Since 2004)": "all"}
timeframe_keys = list(timeframe_options.keys())
try: timeframe_default_index = timeframe_keys.index(st.session_state.timeframe_selection)
except ValueError: timeframe_default_index = 7
timeframe_key = st.sidebar.selectbox("Select Timeframe", options=timeframe_keys, index=timeframe_default_index)
timeframe = timeframe_options[timeframe_key]

# --- Main Dashboard ---
keywords_str = keywords_input
if keywords_str in scenarios:
    keywords = scenarios[keywords_str]["keywords"]
else:
    keywords = [k.strip() for k in keywords_str.split(',') if k.strip()][:5]

if not keywords:
    st.info("โฌ…๏ธ Please enter keywords or select a popular comparison from the sidebar.")
else:
    st.header(f"Analysis for: {', '.join(keywords)}")
    tab_names = ["๐Ÿ“ˆ Trend Analysis", "๐Ÿ”ฎ Future Forecast", "๐Ÿค– AI Co-pilot"]
    tab1, tab2, tab3 = st.tabs(tab_names)
    
    trends_data = None
    if keywords_str in scenarios:
        with st.spinner(f"Loading pre-configured analysis for '{keywords_str}'..."):
            trends_data = load_all_offline_data(scenarios[keywords_str])
    else:
        serpapi_key = os.environ.get("SERPAPI_API_KEY") # Use os.environ.get for deployment
        if not serpapi_key: 
             # Fallback for local dev using secrets.toml
            try:
                serpapi_key = st.secrets.get("SERPAPI_API_KEY")
            except:
                serpapi_key = None
        
        if not serpapi_key:
            st.error("โŒ SerpApi API Key not found in secrets.")
        else:
            with st.spinner(f"Fetching live data for '{keywords_str}'..."):
                trends_data = fetch_data_from_serpapi(serpapi_key, keywords, timeframe, geo)
    
    if trends_data:
        interest_df = trends_data.get("interest_over_time", pd.DataFrame())
        if not interest_df.empty:
            for col in interest_df.columns: 
                if col != 'Date': interest_df[col] = pd.to_numeric(interest_df[col], errors='coerce')
        
        with tab1:
            st.subheader("Search Interest Over Time")
            if not interest_df.empty:
                fig = px.line(interest_df, x=interest_df.index, y=interest_df.columns)
                st.plotly_chart(fig, use_container_width=True)
                st.subheader("๐Ÿ“Š Overall Interest Share")
                interest_sum = interest_df.sum().reset_index()
                interest_sum.columns = ['keyword', 'total_interest']
                fig_pie = px.pie(interest_sum, names='keyword', values='total_interest')
                st.plotly_chart(fig_pie, use_container_width=True)
                st.markdown("---")
                st.subheader("๐ŸŒ Interest by Region")
                region_df = trends_data.get("interest_by_region")
                if region_df is not None and not region_df.empty:
                    fig_map = px.choropleth(region_df, locations=region_df.index, locationmode='country names', color='Interest', hover_name=region_df.index, color_continuous_scale=px.colors.sequential.Plasma)
                    st.plotly_chart(fig_map, use_container_width=True)
                    st.dataframe(region_df.sort_values(by="Interest", ascending=False), use_container_width=True)
                else: st.warning("No regional data available.")
                st.markdown("---")
                st.subheader("๐Ÿ“… Keyword Seasonality Analysis")
                monthly_df = interest_df.resample('M').mean()
                monthly_df['month'] = monthly_df.index.month
                seasonal_df = monthly_df.groupby('month')[interest_df.columns].mean().reset_index()
                for col in interest_df.columns:
                    if (seasonal_df[col].max() - seasonal_df[col].min()) != 0:
                        seasonal_df[col] = (seasonal_df[col] - seasonal_df[col].min()) / (seasonal_df[col].max() - seasonal_df[col].min()) * 100
                    else: seasonal_df[col] = 0
                seasonal_df['month'] = seasonal_df['month'].apply(lambda x: pd.to_datetime(str(x), format='%m').strftime('%B'))
                seasonal_df.set_index('month', inplace=True)
                fig_heatmap = px.imshow(seasonal_df.T, labels=dict(x="Month", y="Keyword", color="Normalized Interest"), aspect="auto", color_continuous_scale="Viridis")
                st.plotly_chart(fig_heatmap, use_container_width=True)
                st.markdown("---")
                st.subheader("๐Ÿ”ฌ Year-over-Year Growth Analysis")
                keyword_for_yoy = st.selectbox("Select keyword for YoY analysis", options=keywords, key='yoy_select')
                if keyword_for_yoy:
                    monthly_yoy_df = interest_df[[keyword_for_yoy]].resample('M').mean()
                    monthly_yoy_df['YoY Growth (%)'] = monthly_yoy_df[keyword_for_yoy].pct_change(12) * 100
                    st.dataframe(monthly_yoy_df.style.format({'YoY Growth (%)': "{:+.2f}%"}).applymap(lambda v: 'color: green;' if v > 0 else ('color: red;' if v < 0 else ''), subset=['YoY Growth (%)']), use_container_width=True)
                st.markdown("---")
                st.subheader("๐Ÿ” Trend Decomposition")
                keyword_to_decompose = st.selectbox("Select keyword to decompose", options=keywords, key='decomp_select')
                if keyword_to_decompose:
                    monthly_decomp_df = interest_df[keyword_to_decompose].resample('M').mean()
                    if len(monthly_decomp_df.dropna()) >= 24:
                        decomposition = seasonal_decompose(monthly_decomp_df.dropna(), model='additive', period=12)
                        fig_decomp = decomposition.plot()
                        fig_decomp.set_size_inches(10, 8)
                        st.pyplot(fig_decomp)
                    else: st.error("โŒ Analysis Error: Decomposition requires at least 24 months of data.")
            else:
                st.warning("Could not fetch or load time-series data.")
        with tab2:
            st.subheader("Future Forecast with Prophet")
            keyword_to_forecast = st.selectbox("Select a keyword to forecast", options=keywords)
            if not interest_df.empty and keyword_to_forecast in interest_df.columns:
                if st.button(f"Generate 12-Month Forecast for '{keyword_to_forecast}'"):
                    with st.spinner("Calculating future trends..."):
                        prophet_df = interest_df[[keyword_to_forecast]].reset_index()
                        prophet_df.columns = ['ds', 'y']
                        model = Prophet()
                        model.fit(prophet_df)
                        future = model.make_future_dataframe(periods=365)
                        forecast = model.predict(future)
                        st.success("Forecast generated!")
                        fig_forecast = model.plot(forecast)
                        st.pyplot(fig_forecast)
                        st.subheader("Forecast Data")
                        st.dataframe(forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail(12))
            else: st.warning("Trend data must be loaded first.")
        with tab3:
            st.subheader("AI Marketing Co-pilot")
            google_api_key = os.environ.get("GOOGLE_API_KEY")
            if not google_api_key:
                try: google_api_key = st.secrets.get("GOOGLE_API_KEY")
                except: google_api_key = None
            if not google_api_key: st.info("Please add your Google AI API key to your secrets.")
            else:
                genai.configure(api_key=google_api_key)
                model = genai.GenerativeModel('gemini-1.5-flash-latest')
                data_summary = ""
                if trends_data:
                    if not trends_data.get("interest_over_time", pd.DataFrame()).empty: data_summary += "Time-series summary:\n" + trends_data["interest_over_time"].describe().to_string() + "\n\n"
                    if not trends_data.get("interest_by_region", pd.DataFrame()).empty: data_summary += "Top 5 regions:\n" + trends_data["interest_by_region"].head().to_string() + "\n\n"
                if "messages" not in st.session_state: st.session_state.messages = []
                for message in st.session_state.messages:
                    with st.chat_message(message["role"]): st.markdown(message["content"])
                if prompt := st.chat_input("Ask about trends or request a marketing campaign..."):
                    st.session_state.messages.append({"role": "user", "content": prompt})
                    with st.chat_message("user"): st.markdown(prompt)
                    full_prompt = f"You are a marketing analyst AI. Based on this data summary:\n{data_summary}\n\nUser's Question: '{prompt}'"
                    with st.chat_message("assistant"):
                        message_placeholder = st.empty()
                        try:
                            response = model.generate_content(full_prompt, stream=True)
                            full_response_text = ""
                            for chunk in response:
                                full_response_text += chunk.text
                                message_placeholder.markdown(full_response_text + "โ–Œ")
                            message_placeholder.markdown(full_response_text)
                            st.session_state.messages.append({"role": "assistant", "content": full_response_text})
                        except Exception as e:
                            st.error(f"An error occurred with the AI model: {e}")
    else:
        st.error("Could not load or fetch data.")