|
|
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: |
|
|
|
|
|
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, |
|
|
): |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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", []) |
|
|
|
|
|
|
|
|
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." |
|
|
) |
|
|
|
|
|
|
|
|
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_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_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}**" |
|
|
) |
|
|
|
|
|
|
|
|
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." |
|
|
) |
|
|
|
|
|
|
|
|
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 = ( |
|
|
"### βΉοΈ 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() |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(): |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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_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" |
|
|
) |
|
|
|
|
|
|
|
|
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_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_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() |
|
|
|