""" AI Economic Pulse — Interactive Explorer for the Anthropic Economic Index Built by nicovlr | Data: Anthropic (CC-BY) Explore how AI is reshaping the global economy using real-world data from millions of Claude conversations. """ import gradio as gr import pandas as pd import plotly.graph_objects as go import plotly.express as px import numpy as np from huggingface_hub import hf_hub_download from rapidfuzz import process, fuzz # ============================================================================ # DESIGN SYSTEM # ============================================================================ COLORS = { "primary": "#D97757", "primary_light": "#f0c4b0", "secondary": "#1a1a2e", "accent": "#e8ddd3", "bg": "#fafaf8", "card": "#ffffff", "text": "#2d2d2d", "muted": "#8b8b8b", "success": "#2ecc71", "warning": "#f39c12", "danger": "#e74c3c", "info": "#3498db", } PLOTLY_DEFAULTS = dict( font=dict(family="Inter, system-ui, -apple-system, sans-serif", color=COLORS["text"]), paper_bgcolor="white", plot_bgcolor="#fafafa", colorway=[ COLORS["primary"], COLORS["info"], COLORS["success"], "#9b59b6", COLORS["warning"], COLORS["danger"], "#1abc9c", "#e67e22", "#2c3e50", "#f1c40f", ], ) DEFAULT_MARGIN = dict(l=60, r=30, t=50, b=50) def apply_layout(fig, **overrides): """Apply consistent layout with optional overrides (margin, title, etc.).""" layout = {**PLOTLY_DEFAULTS, "margin": DEFAULT_MARGIN, **overrides} fig.update_layout(**layout) return fig def styled_card(title, value, subtitle="", color=COLORS["primary"]): """Generate HTML for a styled metric card.""" return f"""
{title}
{value}
{subtitle}
""" def risk_badge(score): """Return HTML badge for exposure risk level.""" if score >= 0.5: level, color, emoji = "Very High", COLORS["danger"], "!!" elif score >= 0.3: level, color, emoji = "High", COLORS["warning"], "!" elif score >= 0.15: level, color, emoji = "Moderate", COLORS["info"], "~" else: level, color, emoji = "Low", COLORS["success"], "" return f""" {emoji} {level} Exposure """ # ============================================================================ # DATA LOADING # ============================================================================ print("=" * 60) print(" AI Economic Pulse — Loading data...") print("=" * 60) DATASET_ID = "Anthropic/EconomicIndex" def load_dataset_file(path): """Download and load a CSV from the Anthropic Economic Index.""" local = hf_hub_download(repo_id=DATASET_ID, filename=path, repo_type="dataset") return pd.read_csv(local) print("[1/4] Loading job exposure data...") df_jobs = load_dataset_file("labor_market_impacts/job_exposure.csv") df_jobs["exposure_pct"] = (df_jobs["observed_exposure"] * 100).round(1) df_jobs["rank"] = df_jobs["observed_exposure"].rank(ascending=False, method="min").astype(int) total_jobs = len(df_jobs) job_titles = df_jobs["title"].tolist() print(f" {total_jobs} occupations loaded.") print("[2/4] Loading task penetration data...") df_tasks = load_dataset_file("labor_market_impacts/task_penetration.csv") print(f" {len(df_tasks)} task records loaded.") print("[3/4] Loading Claude.ai usage data (this may take a moment)...") df_claude = load_dataset_file( "release_2026_03_24/data/aei_raw_claude_ai_2026-02-05_to_2026-02-12.csv" ) print(f" {len(df_claude):,} rows loaded.") print("[4/4] Loading API usage data...") df_api = load_dataset_file( "release_2026_03_24/data/aei_raw_1p_api_2026-02-05_to_2026-02-12.csv" ) print(f" {len(df_api):,} rows loaded.") # ============================================================================ # DATA PRE-PROCESSING # ============================================================================ print("Pre-processing data...") # --- Global summaries from Claude.ai data --- def get_global_facet(df, facet_name, variable_suffix="_pct"): """Extract global-level data for a given facet.""" mask = ( (df["facet"] == facet_name) & (df["geography"] == "global") & (df["variable"].str.endswith(variable_suffix)) ) subset = df[mask][["cluster_name", "value"]].copy() subset = subset[subset["cluster_name"] != "not_classified"] subset = subset.sort_values("value", ascending=False) return subset def get_global_numeric(df, facet_name): """Extract global numeric stats (mean, median) for a facet.""" mask = ( (df["facet"] == facet_name) & (df["geography"] == "global") & (df["variable"].str.contains("_mean$|_median$", regex=True)) ) return df[mask][["variable", "cluster_name", "value"]].copy() # Collaboration patterns collab_data = get_global_facet(df_claude, "collaboration") # Use case breakdown usecase_data = get_global_facet(df_claude, "use_case") # Task success success_data = get_global_facet(df_claude, "task_success") # Request complexity request_data = get_global_facet(df_claude, "request") # AI autonomy stats autonomy_stats = get_global_numeric(df_claude, "ai_autonomy") # Time data time_human = get_global_numeric(df_claude, "human_only_time") time_ai = get_global_numeric(df_claude, "human_with_ai_time") # --- Country-level data --- country_usage = df_claude[ (df_claude["facet"] == "country") & (df_claude["geography"] == "global") & (df_claude["variable"] == "usage_pct") ].copy() country_usage = country_usage[country_usage["cluster_name"] != "not_classified"] # O*NET task data from Claude usage onet_usage = get_global_facet(df_claude, "onet_task") # --- Task penetration processing --- task_cols = df_tasks.columns.tolist() print("Data ready!") print("=" * 60) # ============================================================================ # CHART GENERATORS # ============================================================================ def make_gauge(score, title="AI Exposure"): """Create a gauge chart for exposure score.""" if score >= 0.5: bar_color = COLORS["danger"] elif score >= 0.3: bar_color = COLORS["warning"] elif score >= 0.15: bar_color = COLORS["info"] else: bar_color = COLORS["success"] fig = go.Figure(go.Indicator( mode="gauge+number", value=score * 100, number={"suffix": "%", "font": {"size": 48, "color": COLORS["text"]}}, gauge={ "axis": {"range": [0, 100], "tickwidth": 1, "tickcolor": COLORS["muted"]}, "bar": {"color": bar_color, "thickness": 0.7}, "bgcolor": "#f5f0eb", "borderwidth": 0, "steps": [ {"range": [0, 15], "color": "rgba(46,204,113,0.08)"}, {"range": [15, 30], "color": "rgba(52,152,219,0.08)"}, {"range": [30, 50], "color": "rgba(243,156,18,0.08)"}, {"range": [50, 100], "color": "rgba(231,76,60,0.08)"}, ], }, )) apply_layout(fig, height=280, margin=dict(l=30, r=30, t=30, b=10)) return fig def make_top_bottom_chart(df, n=20, show_top=True): """Horizontal bar chart of top or bottom N jobs by exposure.""" if show_top: subset = df.nlargest(n, "observed_exposure") title = f"Top {n} Most Exposed Occupations" else: subset = df[df["observed_exposure"] > 0].nsmallest(n, "observed_exposure") title = f"Top {n} Least Exposed Occupations" subset = subset.sort_values("observed_exposure", ascending=True) colors = [ COLORS["danger"] if v >= 0.5 else COLORS["warning"] if v >= 0.3 else COLORS["info"] if v >= 0.15 else COLORS["success"] for v in subset["observed_exposure"] ] fig = go.Figure(go.Bar( x=subset["observed_exposure"] * 100, y=subset["title"], orientation="h", marker_color=colors, text=[f"{v:.1f}%" for v in subset["observed_exposure"] * 100], textposition="outside", )) apply_layout(fig, title=dict(text=title, font=dict(size=16)), xaxis_title="Exposure Score (%)", yaxis_title="", height=max(400, n * 28), margin=dict(l=300, r=60, t=50, b=50), ) return fig def make_comparison_chart(jobs_data): """Bar chart comparing multiple jobs.""" fig = go.Figure() colors = [COLORS["primary"], COLORS["info"], COLORS["success"], "#9b59b6", COLORS["warning"]] for i, (_, row) in enumerate(jobs_data.iterrows()): color = colors[i % len(colors)] fig.add_trace(go.Bar( x=[row["title"]], y=[row["observed_exposure"] * 100], name=row["title"], marker_color=color, text=[f"{row['observed_exposure']*100:.1f}%"], textposition="outside", )) apply_layout(fig, title=dict(text="Job Exposure Comparison", font=dict(size=16)), yaxis_title="Exposure Score (%)", yaxis_range=[0, max(jobs_data["observed_exposure"] * 100) * 1.3 + 5], showlegend=False, height=400, ) return fig def make_world_map(): """Choropleth map of AI usage by country.""" map_data = country_usage.copy() map_data = map_data.rename(columns={"cluster_name": "country_code", "value": "usage_pct"}) map_data["usage_pct"] = map_data["usage_pct"] * 100 fig = px.choropleth( map_data, locations="country_code", color="usage_pct", color_continuous_scale=[ [0, "#fef3ee"], [0.25, COLORS["primary_light"]], [0.5, COLORS["primary"]], [0.75, "#c0553a"], [1, COLORS["secondary"]], ], labels={"usage_pct": "AI Usage Share (%)"}, ) apply_layout(fig, title=dict(text="Global AI Adoption (Claude.ai Usage by Country)", font=dict(size=16)), height=500, geo=dict( showframe=False, showcoastlines=True, coastlinecolor="#ddd", projection_type="natural earth", bgcolor="#fafaf8", ), margin=dict(l=0, r=0, t=50, b=0), ) return fig def make_donut(data, title, hole=0.55): """Donut chart from a cluster_name/value dataframe.""" data = data.head(10) # limit to top 10 categories fig = go.Figure(go.Pie( labels=data["cluster_name"], values=data["value"], hole=hole, textinfo="label+percent", textposition="outside", marker=dict(colors=PLOTLY_DEFAULTS["colorway"][:len(data)]), )) apply_layout(fig, title=dict(text=title, font=dict(size=16)), height=420, showlegend=True, legend=dict(orientation="h", yanchor="bottom", y=-0.3, xanchor="center", x=0.5), ) return fig def make_bar_facet(data, title, x_label="Category", y_label="Share (%)"): """Vertical bar chart from facet data.""" data = data.head(15) fig = go.Figure(go.Bar( x=data["cluster_name"], y=data["value"] * 100, marker_color=COLORS["primary"], text=[f"{v:.1f}%" for v in data["value"] * 100], textposition="outside", )) apply_layout(fig, title=dict(text=title, font=dict(size=16)), xaxis_title=x_label, yaxis_title=y_label, height=400, xaxis_tickangle=-35, ) return fig def make_distribution_chart(): """Show the distribution of exposure scores across all jobs.""" fig = go.Figure() bins = [0, 0.05, 0.10, 0.15, 0.20, 0.30, 0.50, 1.0] labels = ["0-5%", "5-10%", "10-15%", "15-20%", "20-30%", "30-50%", "50%+"] counts = pd.cut(df_jobs["observed_exposure"], bins=bins, labels=labels).value_counts() counts = counts.reindex(labels) colors_dist = [ COLORS["success"], COLORS["success"], COLORS["info"], COLORS["info"], COLORS["warning"], COLORS["warning"], COLORS["danger"], ] fig.add_trace(go.Bar( x=labels, y=counts.values, marker_color=colors_dist, text=counts.values, textposition="outside", )) apply_layout(fig, title=dict(text="Distribution of AI Exposure Across All Occupations", font=dict(size=16)), xaxis_title="Exposure Score Range", yaxis_title="Number of Occupations", height=380, ) return fig # ============================================================================ # SEARCH & INTERACTION LOGIC # ============================================================================ def search_job(query): """Fuzzy search for a job title.""" if not query or len(query.strip()) < 2: return gr.update(choices=[], value=None) matches = process.extract(query, job_titles, scorer=fuzz.WRatio, limit=8) results = [m[0] for m in matches if m[1] > 40] return gr.update(choices=results, value=results[0] if results else None) def analyze_job(job_title): """Generate full analysis for a selected job.""" if not job_title: empty = go.Figure() apply_layout(empty, height=100) return ( "

Search for a job above

", empty, "", "" ) try: row = df_jobs[df_jobs["title"] == job_title] if row.empty: empty = go.Figure() apply_layout(empty, height=100) return ( "

Job not found

", empty, "", "" ) row = row.iloc[0] score = float(row["observed_exposure"]) rank = int(row["rank"]) occ_code = str(row["occ_code"]) # Percentile percentile = (total_jobs - rank) / total_jobs * 100 # Find similar jobs (same 2-digit SOC code) soc_prefix = occ_code[:2] similar = df_jobs[ (df_jobs["occ_code"].str.startswith(soc_prefix)) & (df_jobs["title"] != job_title) ].head(5) # Risk color if score >= 0.5: score_color = COLORS["danger"] elif score >= 0.3: score_color = COLORS["warning"] elif score >= 0.15: score_color = COLORS["info"] else: score_color = COLORS["success"] # Build cards HTML badge = risk_badge(score) card1 = styled_card("AI Exposure Score", f"{score*100:.1f}%", f"Rank #{rank} of {total_jobs}", score_color) card2 = styled_card("Percentile", f"{percentile:.0f}th", "Higher = more exposed than other jobs", COLORS["primary"]) card3 = styled_card("Risk Level", badge, "", COLORS["text"]) cards_html = f"""
{card1} {card2} {card3}

{job_title}

O*NET Code: {occ_code}

""" # Gauge chart gauge = make_gauge(score) # Similar jobs comparison if not similar.empty: similar_html = "

Related Occupations

" similar_html += "" for _, s in similar.iterrows(): bar_width = float(s["observed_exposure"]) * 100 similar_html += f""" """ similar_html += "
OccupationExposure
{s['title']}
{bar_width:.1f}%
" else: similar_html = "" # Interpretation if score >= 0.5: interp = "This occupation has very high AI exposure. A significant portion of its tasks are already being performed with AI assistance. Workers in this field should actively develop AI collaboration skills." elif score >= 0.3: interp = "This occupation has high AI exposure. Many of its tasks intersect with AI capabilities. Embracing AI tools can significantly boost productivity." elif score >= 0.15: interp = "This occupation has moderate AI exposure. Some tasks are being augmented by AI, but core functions still require substantial human expertise." else: interp = "This occupation has low AI exposure. Most of its tasks are not significantly impacted by current AI capabilities, though this may change over time." interp_html = f"""
Interpretation: {interp}
""" return cards_html, gauge, similar_html, interp_html except Exception as e: import traceback err = traceback.format_exc() print(f"Error in analyze_job: {err}") empty = go.Figure() apply_layout(empty, height=100) return ( f"

Error analyzing job: {str(e)}

", empty, "", "" ) # ============================================================================ # KEY METRICS # ============================================================================ avg_exposure = df_jobs["observed_exposure"].mean() median_exposure = df_jobs["observed_exposure"].median() high_exposure_jobs = len(df_jobs[df_jobs["observed_exposure"] >= 0.3]) zero_exposure_jobs = len(df_jobs[df_jobs["observed_exposure"] == 0]) # ============================================================================ # GRADIO UI # ============================================================================ CSS = """ .gradio-container { max-width: 1200px !important; margin: auto; background: #fafaf8 !important; } .header-banner { background: white; color: #1a1a2e; border: 1px solid #f0ece8; box-shadow: 0 2px 12px rgba(0,0,0,0.06); padding: 48px 40px; border-radius: 20px; margin-bottom: 24px; text-align: center; } .header-banner h1 { font-size: 2.6em !important; font-weight: 800; margin: 0 0 8px 0; letter-spacing: -0.02em; } .header-banner p { font-size: 1.15em; color: #666; margin: 0; max-width: 700px; margin: 0 auto; } .metric-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin: 20px 0; } .tab-content { padding: 8px 0; } .source-badge { display: inline-block; background: #f0ece8; color: #666; padding: 4px 12px; border-radius: 12px; font-size: 0.8em; margin-top: 8px; } """ with gr.Blocks( theme=gr.themes.Soft( primary_hue="orange", secondary_hue="gray", neutral_hue="gray", font=("Inter", "system-ui", "-apple-system", "sans-serif"), ), title="AI Economic Pulse", css=CSS, analytics_enabled=True, ) as demo: # --- Header --- gr.HTML("""

AI Economic Pulse

How is AI reshaping the global economy? Explore real data from millions of Claude conversations. Powered by the Anthropic Economic Index.

""") # --- Key Metrics Bar --- gr.HTML(f"""
{styled_card("Occupations Tracked", f"{total_jobs:,}", "O*NET classifications")} {styled_card("Avg Exposure", f"{avg_exposure*100:.1f}%", "Across all occupations", COLORS['info'])} {styled_card("High Exposure Jobs", f"{high_exposure_jobs}", f"Score >= 30%", COLORS['warning'])} {styled_card("Data Period", "Feb 2026", "Latest release (5th)", COLORS['muted'])}
""") # === TABS === with gr.Tabs(): # ──────────────────────────────────────────────────────── # TAB 1: SEARCH YOUR JOB # ──────────────────────────────────────────────────────── with gr.Tab("Search Your Job", id="search"): gr.Markdown( "### Find your occupation and see how AI is impacting it\n" "Search among 800+ O*NET occupations to get a personalized exposure report." ) with gr.Row(): search_input = gr.Textbox( label="Search for your job", placeholder="e.g. Software Developer, Nurse, Marketing Manager...", scale=3, ) search_btn = gr.Button("Search", variant="primary", scale=1) job_dropdown = gr.Dropdown( label="Select your occupation", choices=[], interactive=True, ) cards_output = gr.HTML() with gr.Row(): gauge_output = gr.Plot(label="Exposure Gauge") similar_output = gr.HTML() interp_output = gr.HTML() search_btn.click( fn=search_job, inputs=[search_input], outputs=[job_dropdown], ) search_input.submit( fn=search_job, inputs=[search_input], outputs=[job_dropdown], ) job_dropdown.change( fn=analyze_job, inputs=[job_dropdown], outputs=[cards_output, gauge_output, similar_output, interp_output], ) # ──────────────────────────────────────────────────────── # TAB 2: RANKINGS # ──────────────────────────────────────────────────────── with gr.Tab("Rankings", id="rankings"): gr.Markdown( "### Which jobs are most and least exposed to AI?\n" "Rankings based on observed AI usage for each occupation's tasks." ) distribution_plot = gr.Plot(value=make_distribution_chart(), label="Distribution") with gr.Row(): n_jobs = gr.Slider( minimum=10, maximum=50, value=20, step=5, label="Number of jobs to show", ) show_mode = gr.Radio( choices=["Most Exposed", "Least Exposed"], value="Most Exposed", label="Show", ) ranking_plot = gr.Plot( value=make_top_bottom_chart(df_jobs, 20, True), label="Rankings", ) def update_ranking(n, mode): return make_top_bottom_chart(df_jobs, int(n), mode == "Most Exposed") n_jobs.change(fn=update_ranking, inputs=[n_jobs, show_mode], outputs=[ranking_plot]) show_mode.change(fn=update_ranking, inputs=[n_jobs, show_mode], outputs=[ranking_plot]) # Full table with gr.Accordion("Full data table (all occupations)", open=False): table_df = df_jobs[["occ_code", "title", "observed_exposure", "rank"]].copy() table_df.columns = ["Code", "Occupation", "Exposure Score", "Rank"] table_df = table_df.sort_values("Rank") gr.Dataframe( value=table_df, label="All Occupations", interactive=False, wrap=True, ) # ──────────────────────────────────────────────────────── # TAB 3: WORLD MAP # ──────────────────────────────────────────────────────── with gr.Tab("Global Map", id="map"): gr.Markdown( "### Where in the world is AI being adopted?\n" "Geographic distribution of Claude.ai usage across countries." ) world_map = gr.Plot(value=make_world_map(), label="World Map") gr.Markdown( f'Source: Claude.ai usage data, Feb 5-12 2026' ) # Top countries table if not country_usage.empty: top_countries = country_usage.nlargest(20, "value").copy() top_countries["value"] = (top_countries["value"] * 100).round(2) top_countries = top_countries[["cluster_name", "value"]] top_countries.columns = ["Country Code", "Usage Share (%)"] with gr.Accordion("Top 20 countries by AI usage share", open=False): gr.Dataframe(value=top_countries, interactive=False) # ──────────────────────────────────────────────────────── # TAB 4: AI USAGE PATTERNS # ──────────────────────────────────────────────────────── with gr.Tab("AI Usage Patterns", id="patterns"): gr.Markdown( "### How are people using AI?\n" "Breakdown of human-AI collaboration patterns, use cases, and task characteristics." ) with gr.Row(): with gr.Column(): if not collab_data.empty: gr.Plot( value=make_donut(collab_data, "Human-AI Collaboration Patterns"), label="Collaboration", ) with gr.Column(): if not usecase_data.empty: gr.Plot( value=make_donut(usecase_data, "Use Cases"), label="Use Cases", ) with gr.Row(): with gr.Column(): if not success_data.empty: gr.Plot( value=make_donut(success_data, "Task Completion Success"), label="Task Success", ) with gr.Column(): if not request_data.empty: gr.Plot( value=make_bar_facet( request_data, "Request Complexity Distribution", "Complexity Level", ), label="Request Complexity", ) # O*NET task usage if not onet_usage.empty: gr.Markdown("### Most Common AI-Assisted Tasks (O*NET)") gr.Plot( value=make_bar_facet( onet_usage.head(20), "Top 20 O*NET Tasks in AI Conversations", "Task", "Share (%)", ), label="O*NET Tasks", ) # ──────────────────────────────────────────────────────── # TAB 5: API INSIGHTS # ──────────────────────────────────────────────────────── with gr.Tab("API & Enterprise", id="api"): gr.Markdown( "### How enterprises use AI via the API\n" "Insights from first-party API usage — cost patterns, token usage, " "and task distribution in production deployments." ) # API collaboration api_collab = get_global_facet(df_api, "collaboration") api_usecase = get_global_facet(df_api, "use_case") api_tasks = get_global_facet(df_api, "onet_task") with gr.Row(): with gr.Column(): if not api_collab.empty: gr.Plot( value=make_donut(api_collab, "API: Collaboration Patterns"), ) with gr.Column(): if not api_usecase.empty: gr.Plot( value=make_donut(api_usecase, "API: Use Cases"), ) if not api_tasks.empty: gr.Markdown("### Top API Tasks (O*NET)") gr.Plot( value=make_bar_facet( api_tasks.head(20), "Top 20 O*NET Tasks in API Usage", "Task", "Share (%)", ), ) # API vs Claude.ai comparison if not api_collab.empty and not collab_data.empty: gr.Markdown("### Claude.ai vs API Usage Comparison") gr.Markdown( "The API is primarily used for production/enterprise workloads, " "while Claude.ai serves direct consumer and professional use cases. " "Compare the collaboration patterns above to see the difference." ) # ──────────────────────────────────────────────────────── # TAB 6: ABOUT # ──────────────────────────────────────────────────────── with gr.Tab("About", id="about"): gr.Markdown(""" ### About this Dashboard **AI Economic Pulse** is an interactive explorer for the [Anthropic Economic Index](https://www.anthropic.com/economic-index) — the most comprehensive dataset on real-world AI usage in the economy. #### What makes this data unique Unlike theoretical predictions about AI's economic impact, the Anthropic Economic Index is based on **observed usage** from millions of real Claude conversations. It measures what people actually use AI for, not what researchers think AI could do. #### Data sources | Dataset | Description | Size | |---------|-------------|------| | Job Exposure | 800+ occupations with AI exposure scores | 37 KB | | Task Penetration | O*NET task-level penetration rates | 1.9 MB | | Claude.ai Usage | Consumer usage by geography, task, collaboration pattern | 96 MB | | API Usage | Enterprise/developer usage with cost & token metrics | 44 MB | #### Methodology - **Exposure scores** are derived from mapping O*NET occupational tasks to observed AI conversation topics - **Geographic data** uses ISO 3166-1 (country) and ISO 3166-2 (region) codes - **Privacy**: minimum 200 conversations per country, 100 per region - **Time period**: February 5-12, 2026 (latest release) #### Citation ```bibtex @misc{anthropic2025economicindex, title={The Anthropic Economic Index}, author={Anthropic}, year={2025}, url={https://www.anthropic.com/economic-index} } ``` #### Credits - **Data**: [Anthropic](https://anthropic.com) (CC-BY license) - **Dashboard**: Built by [nicovlr](https://huggingface.co/nicovlr) - **Tech**: Gradio, Plotly, Pandas ---
View the raw dataset on Hugging Face  |  Anthropic Economic Index
""") # --- Footer --- gr.HTML(f"""
AI Economic Pulse by nicovlr  •  Data: Anthropic Economic Index (CC-BY)  •  Not affiliated with Anthropic
""") if __name__ == "__main__": demo.launch()