File size: 19,532 Bytes
7d786c2
 
 
 
 
 
 
 
 
 
2ec45a0
 
7d786c2
2ec45a0
6692b0e
7d786c2
 
 
 
6692b0e
 
 
2ec45a0
7d786c2
2ec45a0
6692b0e
 
 
 
 
 
 
 
7d786c2
 
 
 
2ec45a0
7d786c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec45a0
6692b0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d786c2
 
 
 
 
 
 
 
 
 
 
 
 
2ec45a0
 
7d786c2
2ec45a0
 
 
 
 
 
 
7d786c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec45a0
 
7d786c2
 
 
2ec45a0
 
7d786c2
 
 
2ec45a0
 
7d786c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec45a0
 
7d786c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec45a0
7d786c2
2ec45a0
 
 
7d786c2
2ec45a0
 
 
7d786c2
2ec45a0
 
 
7d786c2
6692b0e
 
 
 
 
 
 
 
 
7d786c2
2ec45a0
 
 
 
 
 
 
 
 
 
 
7d786c2
 
 
2ec45a0
7d786c2
 
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
 
 
7d786c2
2ec45a0
7d786c2
 
2ec45a0
 
 
 
7d786c2
 
2ec45a0
7d786c2
 
363ce76
 
 
 
2ec45a0
 
 
 
363ce76
adb0e60
363ce76
7d786c2
2ec45a0
7d786c2
 
2ec45a0
7d786c2
 
 
 
 
 
 
 
2ec45a0
7d786c2
 
 
2ec45a0
af57a2e
 
7d786c2
2ec45a0
7d786c2
 
 
 
 
 
6692b0e
 
2ec45a0
6692b0e
 
 
 
 
 
 
 
 
 
7d786c2
 
 
 
2ec45a0
7d786c2
 
 
 
6692b0e
 
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
2ec45a0
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec45a0
 
 
6692b0e
 
2ec45a0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6692b0e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d786c2
6692b0e
7d786c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
af57a2e
7d786c2
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
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()