import gradio as gr import pandas as pd import numpy as np from cear_model import CEARModel cear_analyzer = CEARModel() def build_dataframe_from_inputs(values): """ Build a DataFrame from a list of (platform_name, minutes, variety) tuples. - If minutes <= 0, the row is excluded entirely. - Variety is only used when minutes > 0. """ rows = [] for name, minutes, variety in values: minutes = 0.0 if minutes is None else float(minutes) if minutes <= 0: # Ignore variety when there is no time invested continue variety = None if variety is None else float(variety) rows.append( { "platform_name": name, "minutes_per_week": minutes, "variety_score": variety, } ) if not rows: return pd.DataFrame( columns=["platform_name", "minutes_per_week", "variety_score"] ) return pd.DataFrame(rows) def analyze_user_data( tiktok_minutes, tiktok_variety, insta_minutes, insta_variety, youtube_minutes, youtube_variety, twitter_minutes, twitter_variety, reddit_minutes, reddit_variety, facebook_minutes, facebook_variety, other_minutes, other_variety, feed_satisfaction, fomo_level, ): # Track impossible input patterns for warnings (variety > 0, minutes <= 0) impossible_platforms = [] def check_impossible(name, minutes, variety): try: m = 0.0 if minutes is None else float(minutes) v = 0.0 if variety is None else float(variety) except ValueError: return if m <= 0 and v > 0: impossible_platforms.append(name) check_impossible("TikTok", tiktok_minutes, tiktok_variety) check_impossible("Instagram", insta_minutes, insta_variety) check_impossible("YouTube", youtube_minutes, youtube_variety) check_impossible("Twitter/X", twitter_minutes, twitter_variety) check_impossible("Reddit", reddit_minutes, reddit_variety) check_impossible("Facebook", facebook_minutes, facebook_variety) check_impossible("Other", other_minutes, other_variety) # Build the input DataFrame for the core model (only minutes > 0) df = build_dataframe_from_inputs( [ ("tiktok", tiktok_minutes, tiktok_variety), ("instagram", insta_minutes, insta_variety), ("youtube", youtube_minutes, youtube_variety), ("twitter", twitter_minutes, twitter_variety), ("reddit", reddit_minutes, reddit_variety), ("facebook", facebook_minutes, facebook_variety), ("other", other_minutes, other_variety), ] ) if df.empty: summary_empty = ( "Please enter at least one platform with some weekly minutes." ) eff_md_empty = ( "### 📈 Platform efficiency ranking\n\n" "No meaningful screen time was entered, so per-platform efficiency " "could not be calculated." ) empty_df = pd.DataFrame(columns=["platform", "efficiency_score"]) return summary_empty, eff_md_empty, empty_df # Call core CEAR model scores = cear_analyzer.calculate_scores( df, satisfaction=feed_satisfaction, fomo=fomo_level ) c = float(scores.get("C_Score", 0.0)) a = float(scores.get("A_Risk", 0.0)) d = float(scores.get("D_Index", 0.0)) avg_variety = scores.get("Avg_Variety", None) satisfaction = scores.get("Satisfaction", None) fomo = scores.get("FOMO", None) per_eff = scores.get("Per_Platform_Efficiency", []) # ---------------- Profile based on C & A ---------------- # if c >= 70 and a >= 70: profile = ( "You are highly plugged into online culture, but that comes with high " "algorithmic risk and a heavy concentration of attention on a small set of feeds." ) elif c >= 70 and a < 70: profile = ( "You are well-connected to online culture without extreme algorithmic concentration. " "Your usage is relatively efficient for staying up to date." ) elif c < 40 and a >= 70: profile = ( "You give a lot of attention to a narrow set of feeds without gaining much cultural exposure. " "This is a high-risk, low-benefit pattern." ) else: profile = ( "You currently have relatively low exposure to viral trends and also keep algorithmic risk low. " "You are either deliberately detached from viral culture or simply under-invested in trend-dense platforms." ) # ---------------- Variety interpretation ---------------- # if avg_variety is None: variety_text = ( "You did not provide variety ratings (for platforms with minutes > 0), " "so this analysis focuses only on time and platform mix." ) elif avg_variety < 4: variety_text = ( f"Your average variety rating is **{avg_variety:.1f} / 10**, which suggests that your feeds " "feel quite repetitive and reinforce a narrow slice of content." ) elif avg_variety > 7: variety_text = ( f"Your average variety rating is **{avg_variety:.1f} / 10**, which suggests that you see a wide " "range of topics and styles. This broadens your exposure and slightly offsets some algorithmic risk." ) else: variety_text = ( f"Your average variety rating is **{avg_variety:.1f} / 10**, indicating a moderate mix of content types." ) # ---------------- Satisfaction & FOMO interpretation ---------------- # satisfaction_text = "" if satisfaction is not None: if satisfaction <= 3: satisfaction_text = ( "You report low satisfaction with your feed, which suggests your current pattern might not " "match what you actually want from social media." ) elif satisfaction >= 8: satisfaction_text = ( "You report high satisfaction with your feed, indicating your current usage largely aligns " "with what you want out of these platforms." ) else: satisfaction_text = ( "Your satisfaction is in the mid range, which suggests your feed is \"fine\" but not fully " "optimized for how you would like to spend your attention." ) fomo_text = "" if fomo is not None: if fomo >= 7 and c < 50: fomo_text = ( "You feel out of the loop and your relatively low C-Score supports that feeling. " "If staying current matters to you, a bit more time on trend-dense platforms could help." ) elif fomo <= 3 and c < 40: fomo_text = ( "You have limited exposure to trends but do not feel much FOMO, which suggests a comfortable " "distance from viral culture." ) # ---------------- Summary header ---------------- # summary_lines = [ "## 📊 CEAR Analysis Summary", "", f"- **Cultural Connectedness Score (C-Score):** **{c:.2f}**", f"- **Algorithmic Risk Score (A-Risk):** **{a:.2f}**", f"- **Platform Diversity Index (D-Index):** **{d:.2f}**", ] if avg_variety is not None: summary_lines.append( f"- **Average Variety Rating (0–10):** **{avg_variety:.2f}**" ) if satisfaction is not None: summary_lines.append( f"- **Feed Satisfaction (0–10):** **{satisfaction:.1f}**" ) if fomo is not None: summary_lines.append( f"- **FOMO / Out-of-the-loop (0–10):** **{fomo:.1f}**" ) # Impossible input warnings if impossible_platforms: unique_list = sorted(set(impossible_platforms)) joined = ", ".join(unique_list) summary_lines.append( f"- âš ī¸ You set a variety score > 0 but 0 minutes for: **{joined}**. " "These variety inputs were ignored in the calculations." ) # ---------------- Interpretation section ---------------- # summary_lines.extend( [ "", "### 📝 Interpretation", "", profile, "", variety_text, ] ) if satisfaction_text: summary_lines.append("") summary_lines.append(satisfaction_text) if fomo_text: summary_lines.append("") summary_lines.append(fomo_text) # ---------------- Survey explainer ---------------- # survey_explainer = ( "### â„šī¸ How your answers are used\n\n" "- **Minutes per week** drive the core scores. More time on high-weight platforms increases both " "C-Score and A-Risk, with diminishing returns for C-Score.\n" "- **Per-platform variety (0–10)** is combined into a minutes-weighted average. Low variety means you " "mainly see one type of content; high variety means you see a wider mix of topics and styles.\n" "- **Feed satisfaction (0–10)** does not change the scores; it is used to interpret whether your current " "pattern feels good or frustrating to you.\n" "- **FOMO (0–10)** is compared with your C-Score: high FOMO with low C-Score means you feel out of the loop, " "while low FOMO with low C-Score means you are detached by choice." ) summary_lines.append("") summary_lines.append(survey_explainer) summary_lines.append( "\nThe C-Score uses a logarithmic transform of your weekly minutes, encoding diminishing returns as time " "increases. A-Risk reflects your raw time investment and how concentrated it is on a small set of high-weight " "platforms. D-Index captures how many platforms you use in a meaningful way (higher values mean your time is " "spread across more platforms)." ) summary = "\n".join(summary_lines).strip() # ---------------- Per-platform efficiency table and explanation ---------------- # if isinstance(per_eff, list) and per_eff: eff_df = pd.DataFrame(per_eff) if "platform_name" in eff_df.columns: eff_df = eff_df.rename( columns={ "platform_name": "platform", "Cultural_Efficiency": "efficiency_score", } ) eff_df["efficiency_score"] = eff_df["efficiency_score"].round(1) eff_df = eff_df.sort_values("efficiency_score", ascending=False) lines = ["### 📈 Platform efficiency ranking (0–100)\n"] lines.append( "Higher scores mean more cultural exposure per minute. " "The top platform in your current mix is set to 100 and others are scaled relative to it.\n" ) for _, row in eff_df.iterrows(): platform = str(row["platform"]) score = float(row["efficiency_score"]) lines.append(f"- **{platform.capitalize()}**: {score:.1f}") lines.append( "\nPlatforms near 100 are the ones that give you the most cultural exposure per minute in this configuration. " "Platforms with low scores cost more attention for less cultural gain." ) eff_md = "\n".join(lines) else: eff_df = pd.DataFrame(columns=["platform", "efficiency_score"]) eff_md = ( "### 📈 Platform efficiency ranking\n\n" "No meaningful screen time was entered, so per-platform efficiency could not be calculated." ) return summary, eff_md, eff_df # ---------------- Helper functions for reset buttons ---------------- # def reset_pair(): """Return a pair of zeros for minutes and variety.""" return 0, 0 def reset_all(): """Return zeros for all minutes and variety sliders (7 platforms * 2 values).""" return (0, 0) * 7 # ---------------- Gradio UI ---------------- # with gr.Blocks() as demo: gr.Markdown( "# CEAR – Cultural Exposure & Algorithmic Risk Analyzer\n" "Enter your weekly screen time per platform, rate the variety of each feed, and optionally report how satisfied " "you are with your feed and how much FOMO you feel." ) with gr.Accordion("1. Platform screen time & variety (per platform)", open=True): with gr.Row(): with gr.Column(): # TikTok row with gr.Row(): tiktok_minutes = gr.Number( label="TikTok minutes/week", value=240, precision=0 ) tiktok_variety = gr.Slider( label="TikTok variety (0–10)", minimum=0, maximum=10, step=1, value=4, ) tiktok_reset_btn = gr.Button("Reset TikTok") # Instagram row with gr.Row(): insta_minutes = gr.Number( label="Instagram minutes/week", value=180, precision=0 ) insta_variety = gr.Slider( label="Instagram variety (0–10)", minimum=0, maximum=10, step=1, value=5, ) insta_reset_btn = gr.Button("Reset Instagram") # YouTube row with gr.Row(): youtube_minutes = gr.Number( label="YouTube minutes/week", value=120, precision=0 ) youtube_variety = gr.Slider( label="YouTube variety (0–10)", minimum=0, maximum=10, step=1, value=7, ) youtube_reset_btn = gr.Button("Reset YouTube") # Twitter/X row with gr.Row(): twitter_minutes = gr.Number( label="Twitter/X minutes/week", value=60, precision=0 ) twitter_variety = gr.Slider( label="Twitter/X variety (0–10)", minimum=0, maximum=10, step=1, value=6, ) twitter_reset_btn = gr.Button("Reset Twitter/X") # Reddit row with gr.Row(): reddit_minutes = gr.Number( label="Reddit minutes/week", value=90, precision=0 ) reddit_variety = gr.Slider( label="Reddit variety (0–10)", minimum=0, maximum=10, step=1, value=8, ) reddit_reset_btn = gr.Button("Reset Reddit") # Facebook row with gr.Row(): facebook_minutes = gr.Number( label="Facebook minutes/week", value=45, precision=0 ) facebook_variety = gr.Slider( label="Facebook variety (0–10)", minimum=0, maximum=10, step=1, value=3, ) facebook_reset_btn = gr.Button("Reset Facebook") # Other row with gr.Row(): other_minutes = gr.Number( label="Other platforms minutes/week", value=30, precision=0 ) other_variety = gr.Slider( label="Other platforms variety (0–10)", minimum=0, maximum=10, step=1, value=5, ) other_reset_btn = gr.Button("Reset Other") # Reset all button reset_all_btn = gr.Button("Reset ALL platforms") with gr.Accordion("2. Self-report sliders & results", open=True): with gr.Row(): with gr.Column(): gr.Markdown("### Self-report (global)") feed_satisfaction = gr.Slider( label="Feed satisfaction (0 = miserable, 10 = very happy)", minimum=0, maximum=10, step=1, value=6, ) fomo_level = gr.Slider( label="FOMO / out-of-the-loop feeling (0 = none, 10 = extreme)", minimum=0, maximum=10, step=1, value=4, ) run_btn = gr.Button("Analyze", variant="primary") with gr.Column(): summary_out = gr.Markdown(label="Score Results") eff_md_out = gr.Markdown(label="Per-platform Efficiency Summary") eff_table_out = gr.Dataframe( label="Per-platform Cultural Efficiency" ) # Wire up reset buttons (per platform) tiktok_reset_btn.click( reset_pair, inputs=[], outputs=[tiktok_minutes, tiktok_variety] ) insta_reset_btn.click( reset_pair, inputs=[], outputs=[insta_minutes, insta_variety] ) youtube_reset_btn.click( reset_pair, inputs=[], outputs=[youtube_minutes, youtube_variety] ) twitter_reset_btn.click( reset_pair, inputs=[], outputs=[twitter_minutes, twitter_variety] ) reddit_reset_btn.click( reset_pair, inputs=[], outputs=[reddit_minutes, reddit_variety] ) facebook_reset_btn.click( reset_pair, inputs=[], outputs=[facebook_minutes, facebook_variety] ) other_reset_btn.click( reset_pair, inputs=[], outputs=[other_minutes, other_variety] ) # Reset all platforms at once reset_all_btn.click( reset_all, inputs=[], outputs=[ tiktok_minutes, tiktok_variety, insta_minutes, insta_variety, youtube_minutes, youtube_variety, twitter_minutes, twitter_variety, reddit_minutes, reddit_variety, facebook_minutes, facebook_variety, other_minutes, other_variety, ], ) # Run analysis run_btn.click( fn=analyze_user_data, inputs=[ tiktok_minutes, tiktok_variety, insta_minutes, insta_variety, youtube_minutes, youtube_variety, twitter_minutes, twitter_variety, reddit_minutes, reddit_variety, facebook_minutes, facebook_variety, other_minutes, other_variety, feed_satisfaction, fomo_level, ], outputs=[summary_out, eff_md_out, eff_table_out], ) if __name__ == "__main__": demo.launch()