Download app.py from dotoking/CEAR: direct link, hf CLI and curl.
- Browser
- Download file 19.5 kB
-
https://huggingface.co/spaces/dotoking/CEAR/resolve/main/app.py
- Command line
-
hf download hf://spaces/dotoking/CEAR/app.py
-
curl -L -o app.py https://huggingface.co/spaces/dotoking/CEAR/resolve/main/app.py
19.5 kB
| 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() | |