CEAR / app.py
dotoking's picture
Update app.py
2ec45a0 verified
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()