import streamlit as st import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go import textwrap # Custom Stylesheet Application helper (mirrors app.py custom style) def apply_custom_style(): st.markdown(""" """, unsafe_allow_html=True) # Helper function to get severity badge class def get_severity_badge_class(cat): c = str(cat).lower().strip() if "critical" in c: return "badge-critical" elif "high" in c: return "badge-high" elif "medium" in c: return "badge-medium" else: return "badge-low" # Helper function to map risk level colors def get_risk_level_color(risk): r = str(risk).lower().strip() if "high" in r: return "#FF3D00" elif "medium" in r or "moderate" in r: return "#FFD600" else: return "#00B0FF" # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: load_data() # ---------------------------------------------------- @st.cache_data def load_data(): """Loads and processes all anomaly intelligence datasets.""" intel_path = "data/processed/anomaly_intelligence.parquet" summary_path = "data/processed/anomaly_summary.parquet" master_path = "data/processed/investment_intelligence_master.parquet" intel = pd.read_parquet(intel_path) summary = pd.read_parquet(summary_path) master = pd.read_parquet(master_path) # Clean recommendation values globally for df in [intel, summary, master]: for col in ["Recommendation", "Recommendation_Final", "investment_signal", "historical_recommendation"]: if col in df.columns: df[col] = df[col].replace("Avoid", "Sell") # Add Sector from master to summary and intel sector_map = master.set_index("Symbol")["Sector"].to_dict() summary["Sector"] = summary["Symbol"].map(sector_map).fillna("Unknown") intel["Sector"] = intel["Symbol"].map(sector_map).fillna("Unknown") # Extract total_records from master for frequency calculation total_rec_map = master.set_index("Symbol")["total_records"].to_dict() summary["total_records"] = summary["Symbol"].map(total_rec_map).fillna(1000) # ---------------------------------------------------- # ADVANCED ANALYTICS SCORE CALCULATIONS # ---------------------------------------------------- # 1. Anomaly Risk Score (0-100) max_count = summary["anomaly_count"].max() if max_count > 0: summary["norm_count"] = (summary["anomaly_count"] / max_count) * 100 else: summary["norm_count"] = 50.0 summary["Anomaly_Risk_Score"] = ( (summary["norm_count"] * 0.4) + (summary["avg_severity"] * 0.4) + (summary["max_severity"] * 0.2) ).clip(0.0, 100.0) # 2. Sector Risk Score (0-100) # Computed dynamically based on Sector averages sector_grp = summary.groupby("Sector").agg({ "anomaly_count": "mean", "avg_severity": "mean" }).reset_index() max_sec_count = sector_grp["anomaly_count"].max() if max_sec_count > 0: sector_grp["norm_sec_count"] = (sector_grp["anomaly_count"] / max_sec_count) * 100 else: sector_grp["norm_sec_count"] = 50.0 sector_grp["Sector_Risk_Score"] = ( (sector_grp["norm_sec_count"] * 0.5) + (sector_grp["avg_severity"] * 0.5) ).clip(0.0, 100.0) sector_risk_map = sector_grp.set_index("Sector")["Sector_Risk_Score"].to_dict() summary["Sector_Risk_Score"] = summary["Sector"].map(sector_risk_map).fillna(50.0) intel["Sector_Risk_Score"] = intel["Sector"].map(sector_risk_map).fillna(50.0) # 3. Event Frequency Score (0-100) # Frequency = anomalies / total records, normalized summary["raw_freq"] = summary["anomaly_count"] / summary["total_records"] max_freq = summary["raw_freq"].max() if max_freq > 0: summary["Event_Frequency_Score"] = (summary["raw_freq"] / max_freq) * 100 else: summary["Event_Frequency_Score"] = 50.0 summary["Event_Frequency_Score"] = summary["Event_Frequency_Score"].clip(0.0, 100.0) # 4. Alert Priority Score (0-100) (calculated on individual events) min_change = intel["risk_change"].min() max_change = intel["risk_change"].max() if max_change != min_change: intel["norm_risk_change"] = (intel["risk_change"] - min_change) / (max_change - min_change) * 100 else: intel["norm_risk_change"] = 50.0 intel["Alert_Priority_Score"] = ( (intel["severity_score"] * 0.7) + (intel["norm_risk_change"].fillna(0.0) * 0.3) ).fillna(0.0).clip(0.0, 100.0) return intel, summary, master, sector_grp # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: show_anomaly_overview() # ---------------------------------------------------- def show_anomaly_overview(intel, summary): """Renders the dashboard metrics summary header.""" st.markdown('
🚨 Anomaly Overview
', unsafe_allow_html=True) total_anom = len(intel) crit_events = len(intel[intel["severity_category"].str.lower().str.strip() == "critical"]) high_events = len(intel[intel["severity_category"].str.lower().str.strip() == "high"]) affected_stocks = intel["Symbol"].nunique() avg_sev = intel["severity_score"].mean() most_anom_row = summary.sort_values(by="anomaly_count", ascending=False).iloc[0] most_anom_stock = most_anom_row["Symbol"] most_anom_count = most_anom_row["anomaly_count"] col1, col2, col3, col4, col5, col6 = st.columns(6) with col1: st.metric(label="Total Anomalies", value=f"{total_anom:,}") with col2: st.metric(label="Critical Events", value=f"{crit_events}") with col3: st.metric(label="High Severity Events", value=f"{high_events}") with col4: st.metric(label="Affected Stocks", value=f"{affected_stocks} / 49") with col5: st.metric(label="Average Severity", value=f"{avg_sev:.1f}") with col6: st.metric(label="Most Anomalous", value=most_anom_stock, delta=f"{most_anom_count} events") # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: show_event_feed() # ---------------------------------------------------- def show_event_feed(intel): """Renders a real-time visual feed of latest anomalies using custom alert cards.""" st.markdown('
πŸ“° Real-Time Event Feed
', unsafe_allow_html=True) # Sort by Date descending to get latest anomalies latest_anoms = intel.sort_values(by="Date", ascending=False).head(20).reset_index(drop=True) # Render first 6 cards directly to match the height of the anomaly history table (600px) for idx, row in latest_anoms.head(6).iterrows(): b_class = get_severity_badge_class(row["severity_category"]) alert_pri = row['Alert_Priority_Score'] if pd.isna(alert_pri): alert_pri = 0.0 card_html = f"""
{row['Symbol']} ({row['Sector']})
{row['severity_category']}
Event: {row['anomaly_type']}
Severity Score: {row['severity_score']:.1f}
Regime: {row['market_regime']}
Alert Priority: {alert_pri:.1f}
Alert: {row['anomaly_explanation']}
""" st.markdown(card_html, unsafe_allow_html=True) # Render the remaining items inside an expander if len(latest_anoms) > 6: with st.expander("β–Ό Reveal Rest of the Alert Events"): for idx, row in latest_anoms.iloc[6:].iterrows(): b_class = get_severity_badge_class(row["severity_category"]) alert_pri = row['Alert_Priority_Score'] if pd.isna(alert_pri): alert_pri = 0.0 card_html = f"""
{row['Symbol']} ({row['Sector']})
{row['severity_category']}
Event: {row['anomaly_type']}
Severity Score: {row['severity_score']:.1f}
Regime: {row['market_regime']}
Alert Priority: {alert_pri:.1f}
Alert: {row['anomaly_explanation']}
""" st.markdown(card_html, unsafe_allow_html=True) # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: show_stock_anomalies() # ---------------------------------------------------- def show_stock_anomalies(selected_symbol, summary, intel): """Renders customized metrics and complete anomaly timeline history for a selected stock.""" st.markdown(f"### πŸ” Stock Anomaly Explorer: {selected_symbol}") stock_sum = summary[summary["Symbol"] == selected_symbol].iloc[0] stock_intel = intel[intel["Symbol"] == selected_symbol].sort_values(by="Date", ascending=False) col1, col2, col3, col4, col5 = st.columns(5) with col1: st.metric(label="Total Anomalies", value=f"{stock_sum['anomaly_count']}") with col2: st.metric(label="Average Severity", value=f"{stock_sum['avg_severity']:.1f}") with col3: st.metric(label="Maximum Severity", value=f"{stock_sum['max_severity']:.1f}") with col4: st.markdown(f"""
Risk Level
{stock_sum['risk_level']}
""", unsafe_allow_html=True) with col5: st.metric(label="Anomaly Risk Score", value=f"{stock_sum['Anomaly_Risk_Score']:.1f}") if not stock_intel.empty: latest_event = stock_intel.iloc[0] latest_date = latest_event["Date"].strftime("%d-%b-%Y") st.info(f"πŸ“… **Latest Event Detected ({latest_date}):** {latest_event['anomaly_explanation']} (Severity: {latest_event['severity_score']:.1f})") # Chronological layout table st.markdown("**Complete Anomaly History**") disp_cols = ["Date", "anomaly_type", "severity_score", "severity_category", "Alert_Priority_Score", "anomaly_explanation"] col_config = { "Date": st.column_config.DateColumn("Date", format="DD-MMM-YYYY"), "anomaly_type": st.column_config.TextColumn("Anomaly Event Type"), "severity_score": st.column_config.NumberColumn("Severity Score", format="%.1f"), "severity_category": st.column_config.TextColumn("Category"), "Alert_Priority_Score": st.column_config.ProgressColumn("Priority Score", format="%.1f", min_value=0.0, max_value=100.0), "anomaly_explanation": st.column_config.TextColumn("Alert Explanation", width="large") } st.dataframe( stock_intel[disp_cols], column_config=col_config, hide_index=True, use_container_width=True, height=600 ) else: st.info("No anomalies recorded for this stock.") # ---------------------------------------------------- # Severity Intelligence # ---------------------------------------------------- def show_severity_intelligence(intel): st.markdown('
🎯 Severity Intelligence
', unsafe_allow_html=True) # Compute distribution counts sev_counts = intel["severity_category"].value_counts().reset_index() sev_counts.columns = ["Category", "Count"] col_chart1, col_chart2 = st.columns(2) with col_chart1: # Severity Histogram fig1 = px.histogram( intel, x="severity_score", nbins=15, title="Severity Score Distribution (All Anomalies)", color_discrete_sequence=["#FF3D00"] ) fig1.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(showgrid=False, title="Severity Score"), yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"), bargap=0.05, height=280, margin=dict(l=10, r=10, t=50, b=10) ) st.plotly_chart(fig1, use_container_width=True) with col_chart2: # Severity Category Pie Chart fig2 = px.pie( sev_counts, values="Count", names="Category", title="Severity Category breakdown", color="Category", color_discrete_map={ "Critical": "#FF3D00", "High": "#FF9100", "Medium": "#FFD600", "Low": "#00B0FF" } ) fig2.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", height=280, margin=dict(l=10, r=10, t=50, b=10) ) st.plotly_chart(fig2, use_container_width=True) # Severity Trend Over Time # Aggregate average severity daily/weekly intel_time = intel.copy() intel_time["Month"] = intel_time["Date"].dt.to_period("M").astype(str) monthly_avg = intel_time.groupby("Month")["severity_score"].mean().reset_index() fig3 = px.line( monthly_avg, x="Month", y="severity_score", title="Monthly Average Severity Score Trend", color_discrete_sequence=["#FF9100"] ) fig3.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(showgrid=False), yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)", title="Average Severity"), height=280, margin=dict(l=10, r=10, t=50, b=10) ) st.plotly_chart(fig3, use_container_width=True) # ---------------------------------------------------- # Event Analytics Tabs # ---------------------------------------------------- def show_event_analytics_tabs(intel): st.markdown('
πŸ“Š Event Analytics Workspace
', unsafe_allow_html=True) tab1, tab2, tab3, tab4 = st.tabs([ "⚑ Volatility Spikes", "🌊 Drawdown Events", "πŸ“ˆ Return Outliers", "πŸ›‘οΈ Risk Shocks" ]) # Types: 'Volatility Spike' 'Drawdown Shock' 'Return Outlier' 'Risk Shock' types_mapping = { "Volatility Spike": "Volatility Spike", "Drawdown Shock": "Drawdown Shock", "Return Outlier": "Return Outlier", "Risk Shock": "Risk Shock" } def render_tab_content(anom_type): df_type = intel[intel["anomaly_type"] == anom_type] count = len(df_type) avg_sev = df_type["severity_score"].mean() if count > 0 else 0 # Most Affected Stocks affected = df_type["Symbol"].value_counts().reset_index() affected.columns = ["Symbol", "Event Count"] col_c1, col_c2 = st.columns([1, 2]) with col_c1: st.metric(label="Event Count", value=f"{count}") st.metric(label="Average Severity", value=f"{avg_sev:.1f}") with col_c2: st.markdown(f"**Most Affected Assets ({anom_type})**") st.dataframe( affected.head(5), hide_index=True, use_container_width=True ) with tab1: render_tab_content("Volatility Spike") with tab2: render_tab_content("Drawdown Shock") with tab3: render_tab_content("Return Outlier") with tab4: render_tab_content("Risk Shock") # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: show_sector_analysis() # ---------------------------------------------------- def show_sector_analysis(summary, sector_grp): """Renders sector anomaly counts, average severity and charts.""" st.markdown('
πŸ—ΊοΈ Sector Anomaly Analysis
', unsafe_allow_html=True) most_volatile_row = sector_grp.sort_values(by="avg_severity", ascending=False).iloc[0] safest_row = sector_grp.sort_values(by="avg_severity", ascending=True).iloc[0] col1, col2, col3, col4 = st.columns(4) with col1: st.metric(label="Total Anomalies (Avg/Sector)", value=f"{sector_grp['anomaly_count'].mean():.1f}") with col2: st.metric(label="Average Sector Severity", value=f"{sector_grp['avg_severity'].mean():.1f}") with col3: st.metric(label="Most Volatile Sector", value=most_volatile_row["Sector"], delta=f"{most_volatile_row['avg_severity']:.1f} Avg Sev") with col4: st.metric(label="Safest Sector", value=safest_row["Sector"], delta=f"{safest_row['avg_severity']:.1f} Avg Sev", delta_color="inverse") col_chart1, col_chart2 = st.columns(2) with col_chart1: # Anomalies by Sector fig1 = px.bar( sector_grp.sort_values("anomaly_count", ascending=True), x="anomaly_count", y="Sector", orientation="h", title="Total Anomalies by Sector", color="anomaly_count", color_continuous_scale="Reds" ) fig1.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"), yaxis=dict(showgrid=False, title=None), coloraxis_showscale=False, height=280, margin=dict(l=10, r=10, t=50, b=10) ) st.plotly_chart(fig1, use_container_width=True) with col_chart2: # Severity by Sector Heatmap # Pivot table of sector average metrics fig2 = px.density_heatmap( sector_grp, x="Sector", y="avg_severity", z="avg_severity", title="Average Severity Heatmap by Sector", color_continuous_scale="Reds" ) fig2.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", height=280, margin=dict(l=10, r=10, t=50, b=10) ) st.plotly_chart(fig2, use_container_width=True) # ---------------------------------------------------- # Event Timeline # ---------------------------------------------------- def show_event_timeline(intel): st.markdown('
πŸ•°οΈ Event Timeline & Frequency
', unsafe_allow_html=True) # Interactive Timeline Scatter Chart fig = px.scatter( intel, x="Date", y="severity_score", color="anomaly_type", size="severity_score", hover_name="Symbol", title="Anomaly Timeline (Date vs Severity Score)", color_discrete_map={ "Volatility Spike": "#FF3D00", "Drawdown Shock": "#00B0FF", "Return Outlier": "#FF9100", "Risk Shock": "#FFD600" } ) fig.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(showgrid=False), yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)", title="Severity Score"), height=320, margin=dict(l=10, r=10, t=50, b=10) ) st.plotly_chart(fig, use_container_width=True) # Frequency aggregations intel_freq = intel.copy() intel_freq["Month"] = intel_freq["Date"].dt.to_period("M").astype(str) intel_freq["Year"] = intel_freq["Date"].dt.year monthly_cnt = intel_freq.groupby("Month").size().reset_index(name="Count") yearly_cnt = intel_freq.groupby("Year").size().reset_index(name="Count") col1, col2 = st.columns(2) with col1: fig_m = px.bar( monthly_cnt, x="Month", y="Count", title="Monthly Event Frequency Distribution", color_discrete_sequence=["#00B0FF"] ) fig_m.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(showgrid=False), yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"), height=260, margin=dict(l=10, r=10, t=40, b=10) ) st.plotly_chart(fig_m, use_container_width=True) with col2: fig_y = px.bar( yearly_cnt, x="Year", y="Count", title="Yearly Event Frequency Distribution", color_discrete_sequence=["#FFD600"] ) fig_y.update_layout( template="plotly_dark", font_family="Outfit", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", xaxis=dict(showgrid=False), yaxis=dict(showgrid=True, gridcolor="rgba(255,255,255,0.08)"), height=260, margin=dict(l=10, r=10, t=40, b=10) ) st.plotly_chart(fig_y, use_container_width=True) # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: show_critical_alerts() # ---------------------------------------------------- def show_critical_alerts(intel): """Renders the top 20 alerts ranked by severity.""" st.markdown('
⚠️ Critical Alerts Center
', unsafe_allow_html=True) # Sort by severity score desc critical_df = intel.sort_values(by="severity_score", ascending=False).head(20) disp_cols = ["Date", "Symbol", "severity_score", "anomaly_type", "Alert_Priority_Score", "anomaly_explanation"] col_config = { "Date": st.column_config.DateColumn("Date", format="DD-MMM-YYYY"), "Symbol": st.column_config.TextColumn("Stock"), "severity_score": st.column_config.NumberColumn("Severity Score", format="%.1f"), "anomaly_type": st.column_config.TextColumn("Anomaly Event"), "Alert_Priority_Score": st.column_config.ProgressColumn("Priority Score", format="%.1f", min_value=0.0, max_value=100.0), "anomaly_explanation": st.column_config.TextColumn("Advisory Alerts Detail", width="large") } st.dataframe( critical_df[disp_cols], column_config=col_config, hide_index=True, use_container_width=True ) # ---------------------------------------------------- # TECHNICAL REQUIREMENTS: show_ai_event_report() # ---------------------------------------------------- def show_ai_event_report(intel, summary, sector_grp): """Generates and displays AI executive strategizing briefs.""" st.markdown('
🧠 AI Event Strategist
', unsafe_allow_html=True) total_anoms = len(intel) unique_stocks = intel["Symbol"].nunique() crit_cnt = len(intel[intel["severity_category"].str.lower().str.strip() == "critical"]) vol_cnt = len(intel[intel["anomaly_type"] == "Volatility Spike"]) outlier_cnt = len(intel[intel["anomaly_type"] == "Return Outlier"]) most_volatile_sector = sector_grp.sort_values(by="avg_severity", ascending=False).iloc[0]["Sector"] safest_sector = sector_grp.sort_values(by="avg_severity", ascending=True).iloc[0]["Sector"] st.markdown(f"""

🧠 Premium Market Surveillance & Event Assessment

The surveillance engines have processed {total_anoms:,} anomalies across {unique_stocks} active assets. A total of {crit_cnt} critical alerts are currently logged, indicating localized market volatility regimes.

""", unsafe_allow_html=True) c1, c2 = st.columns(2) with c1: st.markdown(f"""

πŸ“Š Market Event Summary & Risk Commentary

The bulk of identified anomalies originated from Volatility Spikes ({vol_cnt}) and Return Outliers ({outlier_cnt}). This points toward dynamic shifts in volatility levels rather than structural breakdowns in downside support.

πŸ’‘ Opportunity Commentary

Several high-severity return outliers may represent temporary price dislocations. Growth managers should track high-severity events on blue chips as entry windows rather than risk flags.

""", unsafe_allow_html=True) with c2: st.markdown(f"""

🌐 Sector Vulnerability Commentary

Vulnerabilities are currently concentrated within the {most_volatile_sector} sector, yielding the highest average severity score. Defensive reallocations should favor the {safest_sector} cohort which remains extremely resilient.

πŸ›‘οΈ Defensive Signals

Risk profiles show sideways volatility regime shifts inside consumer defense clusters, indicating robust protection flags.

""", unsafe_allow_html=True) # ---------------------------------------------------- # Export Features # ---------------------------------------------------- def show_export_features(selected_symbol, intel): st.markdown('
πŸ“₯ Export Station
', unsafe_allow_html=True) col_e1, col_e2, col_e3 = st.columns(3) with col_e1: feed_csv = intel.sort_values(by="Date", ascending=False).head(100)[["Date", "Symbol", "anomaly_type", "severity_score", "severity_category", "anomaly_explanation"]].to_csv(index=False).encode("utf-8") st.download_button( label="Download Anomaly Feed", data=feed_csv, file_name="investiq_anomaly_feed.csv", mime="text/csv", key="dl-anom-feed", use_container_width=True ) with col_e2: crit_csv = intel.sort_values(by="severity_score", ascending=False).head(50)[["Date", "Symbol", "severity_score", "anomaly_type", "Alert_Priority_Score", "anomaly_explanation"]].to_csv(index=False).encode("utf-8") st.download_button( label="Download Critical Alerts", data=crit_csv, file_name="investiq_critical_alerts.csv", mime="text/csv", key="dl-crit-alerts", use_container_width=True ) with col_e3: stock_csv = intel[intel["Symbol"] == selected_symbol].sort_values(by="Date", ascending=False)[["Date", "anomaly_type", "severity_score", "severity_category", "anomaly_explanation"]].to_csv(index=False).encode("utf-8") st.download_button( label="Download Stock Events", data=stock_csv, file_name=f"investiq_{selected_symbol}_anomaly_events.csv", mime="text/csv", key="dl-stock-events", use_container_width=True ) # ---------------------------------------------------- # MAIN CALLABLE: show_anomaly_intelligence_workstation() # ---------------------------------------------------- def show_anomaly_intelligence_workstation(): """Main Streamlit execution point for Section 8 Page.""" apply_custom_style() st.title("🚨 Anomaly Intelligence Workstation") st.markdown("

Monitor unusual market events, risk shocks, and abnormal price/volatility behaviors before they propagate.

", unsafe_allow_html=True) # Load cached datasets try: intel, summary, master, sector_grp = load_data() except Exception as e: st.error(f"Error loading anomaly datasets: {e}") st.stop() # 1. Anomaly Overview show_anomaly_overview(intel, summary) st.markdown("---") # 2. Event Feed & Explorer Split (Layout split: 40% Explorer, 60% Feed) col_explorer, col_feed = st.columns([4, 6]) with col_explorer: st.markdown('
πŸ” Anomaly Explorer
', unsafe_allow_html=True) selected_symbol = st.selectbox("Select Asset to Audit Anomalies", summary["Symbol"].unique()) show_stock_anomalies(selected_symbol, summary, intel) with col_feed: show_event_feed(intel) st.markdown("---") # 4. Severity Intelligence show_severity_intelligence(intel) st.markdown("---") # 5. Event Analytics Workspace show_event_analytics_tabs(intel) st.markdown("---") # 6. Sector Anomaly Analysis show_sector_analysis(summary, sector_grp) st.markdown("---") # 7. Event Timeline & Frequency show_event_timeline(intel) st.markdown("---") # 8. Critical Alerts Center show_critical_alerts(intel) st.markdown("---") # 9. AI Event Strategist show_ai_event_report(intel, summary, sector_grp) st.markdown("---") # 10. Export Features show_export_features(selected_symbol, intel)