aimodelshare 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. aimodelshare/README.md +26 -0
  2. aimodelshare/__init__.py +100 -0
  3. aimodelshare/aimsonnx.py +2381 -0
  4. aimodelshare/api.py +836 -0
  5. aimodelshare/auth.py +163 -0
  6. aimodelshare/aws.py +511 -0
  7. aimodelshare/aws_client.py +173 -0
  8. aimodelshare/base_image.py +154 -0
  9. aimodelshare/bucketpolicy.py +106 -0
  10. aimodelshare/color_mappings/color_mapping_keras.csv +121 -0
  11. aimodelshare/color_mappings/color_mapping_pytorch.csv +117 -0
  12. aimodelshare/containerisation.py +244 -0
  13. aimodelshare/containerization.py +712 -0
  14. aimodelshare/containerization_templates/Dockerfile.txt +8 -0
  15. aimodelshare/containerization_templates/Dockerfile_PySpark.txt +23 -0
  16. aimodelshare/containerization_templates/buildspec.txt +14 -0
  17. aimodelshare/containerization_templates/lambda_function.txt +40 -0
  18. aimodelshare/custom_approach/__init__.py +1 -0
  19. aimodelshare/custom_approach/lambda_function.py +17 -0
  20. aimodelshare/custom_eval_metrics.py +103 -0
  21. aimodelshare/data_sharing/__init__.py +0 -0
  22. aimodelshare/data_sharing/data_sharing_templates/Dockerfile.txt +3 -0
  23. aimodelshare/data_sharing/data_sharing_templates/__init__.py +1 -0
  24. aimodelshare/data_sharing/data_sharing_templates/buildspec.txt +15 -0
  25. aimodelshare/data_sharing/data_sharing_templates/codebuild_policies.txt +129 -0
  26. aimodelshare/data_sharing/data_sharing_templates/codebuild_trust_relationship.txt +12 -0
  27. aimodelshare/data_sharing/download_data.py +620 -0
  28. aimodelshare/data_sharing/share_data.py +373 -0
  29. aimodelshare/data_sharing/utils.py +8 -0
  30. aimodelshare/deploy_custom_lambda.py +246 -0
  31. aimodelshare/documentation/Makefile +20 -0
  32. aimodelshare/documentation/karma_sphinx_theme/__init__.py +28 -0
  33. aimodelshare/documentation/karma_sphinx_theme/_version.py +2 -0
  34. aimodelshare/documentation/karma_sphinx_theme/breadcrumbs.html +70 -0
  35. aimodelshare/documentation/karma_sphinx_theme/layout.html +172 -0
  36. aimodelshare/documentation/karma_sphinx_theme/search.html +50 -0
  37. aimodelshare/documentation/karma_sphinx_theme/searchbox.html +14 -0
  38. aimodelshare/documentation/karma_sphinx_theme/static/css/custom.css +2 -0
  39. aimodelshare/documentation/karma_sphinx_theme/static/css/custom.css.map +1 -0
  40. aimodelshare/documentation/karma_sphinx_theme/static/css/theme.css +2751 -0
  41. aimodelshare/documentation/karma_sphinx_theme/static/css/theme.css.map +1 -0
  42. aimodelshare/documentation/karma_sphinx_theme/static/css/theme.min.css +2 -0
  43. aimodelshare/documentation/karma_sphinx_theme/static/css/theme.min.css.map +1 -0
  44. aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.eot +0 -0
  45. aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.svg +32 -0
  46. aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.ttf +0 -0
  47. aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.woff +0 -0
  48. aimodelshare/documentation/karma_sphinx_theme/static/font/fontello.woff2 +0 -0
  49. aimodelshare/documentation/karma_sphinx_theme/static/js/theme.js +68 -0
  50. aimodelshare/documentation/karma_sphinx_theme/theme.conf +9 -0
  51. aimodelshare/documentation/make.bat +35 -0
  52. aimodelshare/documentation/requirements.txt +2 -0
  53. aimodelshare/documentation/source/about.rst +18 -0
  54. aimodelshare/documentation/source/advanced_features.rst +137 -0
  55. aimodelshare/documentation/source/competition.rst +218 -0
  56. aimodelshare/documentation/source/conf.py +58 -0
  57. aimodelshare/documentation/source/create_credentials.rst +86 -0
  58. aimodelshare/documentation/source/example_notebooks.rst +132 -0
  59. aimodelshare/documentation/source/functions.rst +151 -0
  60. aimodelshare/documentation/source/gettingstarted.rst +390 -0
  61. aimodelshare/documentation/source/images/creds1.png +0 -0
  62. aimodelshare/documentation/source/images/creds2.png +0 -0
  63. aimodelshare/documentation/source/images/creds3.png +0 -0
  64. aimodelshare/documentation/source/images/creds4.png +0 -0
  65. aimodelshare/documentation/source/images/creds5.png +0 -0
  66. aimodelshare/documentation/source/images/creds_file_example.png +0 -0
  67. aimodelshare/documentation/source/images/predict_tab.png +0 -0
  68. aimodelshare/documentation/source/index.rst +110 -0
  69. aimodelshare/documentation/source/modelplayground.rst +132 -0
  70. aimodelshare/exceptions.py +11 -0
  71. aimodelshare/generatemodelapi.py +1270 -0
  72. aimodelshare/iam/codebuild_policy.txt +129 -0
  73. aimodelshare/iam/codebuild_trust_relationship.txt +12 -0
  74. aimodelshare/iam/lambda_policy.txt +15 -0
  75. aimodelshare/iam/lambda_trust_relationship.txt +12 -0
  76. aimodelshare/json_templates/__init__.py +1 -0
  77. aimodelshare/json_templates/api_json.txt +155 -0
  78. aimodelshare/json_templates/auth/policy.txt +1 -0
  79. aimodelshare/json_templates/auth/role.txt +1 -0
  80. aimodelshare/json_templates/eval/policy.txt +1 -0
  81. aimodelshare/json_templates/eval/role.txt +1 -0
  82. aimodelshare/json_templates/function/policy.txt +1 -0
  83. aimodelshare/json_templates/function/role.txt +1 -0
  84. aimodelshare/json_templates/integration_response.txt +5 -0
  85. aimodelshare/json_templates/lambda_policy_1.txt +15 -0
  86. aimodelshare/json_templates/lambda_policy_2.txt +8 -0
  87. aimodelshare/json_templates/lambda_role_1.txt +12 -0
  88. aimodelshare/json_templates/lambda_role_2.txt +16 -0
  89. aimodelshare/leaderboard.py +174 -0
  90. aimodelshare/main/1.txt +132 -0
  91. aimodelshare/main/1B.txt +112 -0
  92. aimodelshare/main/2.txt +153 -0
  93. aimodelshare/main/3.txt +134 -0
  94. aimodelshare/main/4.txt +128 -0
  95. aimodelshare/main/5.txt +109 -0
  96. aimodelshare/main/6.txt +105 -0
  97. aimodelshare/main/7.txt +144 -0
  98. aimodelshare/main/8.txt +142 -0
  99. aimodelshare/main/__init__.py +1 -0
  100. aimodelshare/main/authorization.txt +275 -0
  101. aimodelshare/main/eval_classification.txt +79 -0
  102. aimodelshare/main/eval_lambda.txt +1709 -0
  103. aimodelshare/main/eval_regression.txt +80 -0
  104. aimodelshare/main/lambda_function.txt +8 -0
  105. aimodelshare/main/nst.txt +149 -0
  106. aimodelshare/model.py +1543 -0
  107. aimodelshare/modeluser.py +215 -0
  108. aimodelshare/moral_compass/README.md +408 -0
  109. aimodelshare/moral_compass/__init__.py +65 -0
  110. aimodelshare/moral_compass/_version.py +3 -0
  111. aimodelshare/moral_compass/api_client.py +601 -0
  112. aimodelshare/moral_compass/apps/__init__.py +69 -0
  113. aimodelshare/moral_compass/apps/ai_consequences.py +540 -0
  114. aimodelshare/moral_compass/apps/bias_detective.py +714 -0
  115. aimodelshare/moral_compass/apps/ethical_revelation.py +898 -0
  116. aimodelshare/moral_compass/apps/fairness_fixer.py +889 -0
  117. aimodelshare/moral_compass/apps/judge.py +888 -0
  118. aimodelshare/moral_compass/apps/justice_equity_upgrade.py +853 -0
  119. aimodelshare/moral_compass/apps/mc_integration_helpers.py +820 -0
  120. aimodelshare/moral_compass/apps/model_building_game.py +1104 -0
  121. aimodelshare/moral_compass/apps/model_building_game_beginner.py +687 -0
  122. aimodelshare/moral_compass/apps/moral_compass_challenge.py +858 -0
  123. aimodelshare/moral_compass/apps/session_auth.py +254 -0
  124. aimodelshare/moral_compass/apps/shared_activity_styles.css +349 -0
  125. aimodelshare/moral_compass/apps/tutorial.py +481 -0
  126. aimodelshare/moral_compass/apps/what_is_ai.py +853 -0
  127. aimodelshare/moral_compass/challenge.py +365 -0
  128. aimodelshare/moral_compass/config.py +187 -0
  129. aimodelshare/placeholders/model.onnx +0 -0
  130. aimodelshare/placeholders/preprocessor.zip +0 -0
  131. aimodelshare/playground.py +1968 -0
  132. aimodelshare/postprocessormodules.py +157 -0
  133. aimodelshare/preprocessormodules.py +373 -0
  134. aimodelshare/pyspark/1.txt +195 -0
  135. aimodelshare/pyspark/1B.txt +181 -0
  136. aimodelshare/pyspark/2.txt +220 -0
  137. aimodelshare/pyspark/3.txt +204 -0
  138. aimodelshare/pyspark/4.txt +187 -0
  139. aimodelshare/pyspark/5.txt +178 -0
  140. aimodelshare/pyspark/6.txt +174 -0
  141. aimodelshare/pyspark/7.txt +211 -0
  142. aimodelshare/pyspark/8.txt +206 -0
  143. aimodelshare/pyspark/__init__.py +1 -0
  144. aimodelshare/pyspark/authorization.txt +258 -0
  145. aimodelshare/pyspark/eval_classification.txt +79 -0
  146. aimodelshare/pyspark/eval_lambda.txt +1441 -0
  147. aimodelshare/pyspark/eval_regression.txt +80 -0
  148. aimodelshare/pyspark/lambda_function.txt +8 -0
  149. aimodelshare/pyspark/nst.txt +213 -0
  150. aimodelshare/python/my_preprocessor.py +58 -0
  151. aimodelshare/readme.md +26 -0
  152. aimodelshare/reproducibility.py +181 -0
  153. aimodelshare/sam/Dockerfile.txt +8 -0
  154. aimodelshare/sam/Dockerfile_PySpark.txt +24 -0
  155. aimodelshare/sam/__init__.py +1 -0
  156. aimodelshare/sam/buildspec.txt +11 -0
  157. aimodelshare/sam/codebuild_policies.txt +129 -0
  158. aimodelshare/sam/codebuild_trust_relationship.txt +12 -0
  159. aimodelshare/sam/codepipeline_policies.txt +173 -0
  160. aimodelshare/sam/codepipeline_trust_relationship.txt +12 -0
  161. aimodelshare/sam/spark-class.txt +2 -0
  162. aimodelshare/sam/template.txt +54 -0
  163. aimodelshare/tools.py +103 -0
  164. aimodelshare/utils/__init__.py +78 -0
  165. aimodelshare/utils/optional_deps.py +38 -0
  166. aimodelshare/utils.py +57 -0
  167. aimodelshare-0.3.7.dist-info/METADATA +298 -0
  168. aimodelshare-0.3.7.dist-info/RECORD +171 -0
  169. aimodelshare-0.3.7.dist-info/WHEEL +5 -0
  170. aimodelshare-0.3.7.dist-info/licenses/LICENSE +5 -0
  171. aimodelshare-0.3.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,858 @@
1
+ """
2
+ The Moral Compass Challenge - Gradio application for the Justice & Equity Challenge.
3
+ Updated with i18n support for English (en), Spanish (es), and Catalan (ca).
4
+ """
5
+
6
+ import os
7
+ import random
8
+ import time
9
+ import threading
10
+ from typing import Optional, Dict, Any, Tuple
11
+
12
+ import gradio as gr
13
+ import pandas as pd
14
+
15
+ try:
16
+ from aimodelshare.playground import Competition
17
+ from aimodelshare.aws import get_token_from_session, _get_username_from_token
18
+ except ImportError:
19
+ # Mock/Pass if not available locally
20
+ pass
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Configuration
24
+ # ---------------------------------------------------------------------------
25
+ LEADERBOARD_CACHE_SECONDS = int(os.environ.get("LEADERBOARD_CACHE_SECONDS", "45"))
26
+ MAX_LEADERBOARD_ENTRIES = os.environ.get("MAX_LEADERBOARD_ENTRIES")
27
+ MAX_LEADERBOARD_ENTRIES = int(MAX_LEADERBOARD_ENTRIES) if MAX_LEADERBOARD_ENTRIES else None
28
+ DEBUG_LOG = os.environ.get("DEBUG_LOG", "false").lower() == "true"
29
+
30
+ TEAM_NAMES = [
31
+ "The Justice League", "The Moral Champions", "The Data Detectives",
32
+ "The Ethical Explorers", "The Fairness Finders", "The Accuracy Avengers"
33
+ ]
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # In-memory caches
37
+ # ---------------------------------------------------------------------------
38
+ _cache_lock = threading.Lock()
39
+ _leaderboard_cache: Dict[str, Any] = {"data": None, "timestamp": 0.0}
40
+ _user_stats_cache: Dict[str, Dict[str, Any]] = {}
41
+ USER_STATS_TTL = LEADERBOARD_CACHE_SECONDS
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # TRANSLATION CONFIGURATION
45
+ # ---------------------------------------------------------------------------
46
+
47
+ TRANSLATIONS = {
48
+ "en": {
49
+ "title": "⚖️ The Moral Compass Challenge",
50
+ "loading": "⏳ Loading...",
51
+ "loading_session": "🔒 Loading your session...",
52
+ # Step 1 (Standing)
53
+ "s1_title_auth": "You've Built an Accurate Model",
54
+ "s1_sub_auth": "Through experimentation and iteration, you've achieved impressive results:",
55
+ "lbl_best_acc": "Your Best Accuracy",
56
+ "lbl_ind_rank": "Your Individual Rank",
57
+ "lbl_team": "Your Team",
58
+ "lbl_team_rank": "Team Rank:",
59
+ "s1_li1": "✅ Mastered the model-building process",
60
+ "s1_li2": "✅ Climbed the accuracy leaderboard",
61
+ "s1_li3": "✅ Competed with fellow engineers",
62
+ "s1_li4": "✅ Earned promotions and unlocked tools",
63
+ "s1_congrats": "🏆 Congratulations on your technical achievement!",
64
+ "s1_box_title": "But now you know the full story...",
65
+ "s1_box_text": "High accuracy isn't enough. Real-world AI systems must also be <strong>fair, equitable, and <span class='emph-harm'>minimize harm</span></strong> across all groups of people.",
66
+ "s1_title_guest": "Ready to Begin Your Journey",
67
+ "s1_sub_guest": "You've learned about the model-building process and are ready to take on the challenge:",
68
+ "s1_li1_guest": "✅ Understood the AI model-building process",
69
+ "s1_li2_guest": "✅ Learned about accuracy and performance",
70
+ "s1_li3_guest": "✅ Discovered real-world bias in AI systems",
71
+ "s1_ready": "🎯 Ready to learn about ethical AI!",
72
+ "btn_new_std": "Introduce the New Standard ▶️",
73
+ # Step 2 (Gauge)
74
+ "s2_title": "We Need a Higher Standard",
75
+ "s2_sub": "While your model is accurate, a higher standard is needed to prevent <span class='emph-harm'>real-world harm</span>. To incentivize this new focus, we're introducing a new score.",
76
+ "s2_box_head": "Watch Your Score",
77
+ "lbl_acc_score": "Accuracy Score",
78
+ "s2_box_emph_head": "This score measures only <strong>one dimension</strong> of success.",
79
+ "s2_box_emph_text": "It's time to add a second, equally important dimension: <strong class='emph-fairness'>Ethics</strong>.",
80
+ "btn_back": "◀️ Back",
81
+ "btn_reset": "Reset and Transform ▶️",
82
+ # Step 3 (Reset)
83
+ "s3_title": "Your Accuracy Score Is Being Reset",
84
+ "lbl_score_reset": "Score Reset",
85
+ "s3_why_head": "⚠️ Why This Reset?",
86
+ "s3_why_text": "We reset your score to emphasize a critical truth: your previous success was measured by only <strong>one dimension</strong> — prediction accuracy. So far, you <strong>have not demonstrated</strong> that you know how to make your AI system <span class='emph-fairness'>safe for society</span>. You don’t yet know whether the model you built is <strong class='emph-harm'>just as biased</strong> as the harmful examples we studied in the previous activity. Moving forward, you’ll need to excel on <strong>two fronts</strong>: technical performance <em>and</em> ethical responsibility.",
87
+ "s3_worry_head": "✅ Don't Worry!",
88
+ "s3_worry_text": "As you make your AI more ethical through the upcoming lessons and challenges, <strong>your score will be restored</strong>—and could climb even higher than before.",
89
+ "btn_intro_mc": "Introduce Moral Compass ▶️",
90
+ # Step 4 (Formula)
91
+ "s4_title": "A New Way to Win",
92
+ "s4_sub": "Your new goal is to climb the leaderboard by increasing your <strong>Moral Compass Score</strong>.",
93
+ "s4_formula_head": "📐 The Scoring Formula",
94
+ "s4_formula_text": "<strong>Moral Compass Score</strong> =<br><br>[ Current Model Accuracy ] × [ Ethical Progress % ]",
95
+ "s4_where": "Where:",
96
+ "s4_li1": "<strong>Current Model Accuracy:</strong> Your technical performance",
97
+ "s4_li2": "<strong>Ethical Progress %:</strong> Percentage of:",
98
+ "s4_li2_sub1": "✅ Ethical learning tasks completed",
99
+ "s4_li2_sub2": "✅ Check-in questions answered correctly",
100
+ "s4_mean_head": "💡 What This Means:",
101
+ "s4_mean_text": "You <strong>cannot</strong> win by accuracy alone—you must also demonstrate <strong class='emph-fairness'>ethical understanding</strong>. And you <strong>cannot</strong> win by just completing lessons—you need a working model too. <strong class='emph-risk'>Both dimensions matter</strong>.",
102
+ "btn_see_chal": "See Challenge Ahead ▶️",
103
+ # Step 6 (Path)
104
+ "s6_title": "📍 Your New Starting Point",
105
+ "s6_pos_auth": "You were previously ranked #{rank} on the accuracy leaderboard.",
106
+ "s6_pos_guest": "You will establish your position as you build ethically aware models.",
107
+ "s6_mc_rank": "Current Moral Compass Rank: <span style='color:#b91c1c;'>Not Yet Established</span>",
108
+ "s6_mc_score": "(Moral Compass Score = 0 initially)",
109
+ "s6_path_head": "🛤️ Path Forward",
110
+ "s6_li1": "🔍 Detect and measure bias",
111
+ "s6_li2": "⚖️ Apply fairness metrics",
112
+ "s6_li3": "🔧 Redesign models to minimize harm",
113
+ "s6_li4": "📊 Balance performance & ethics",
114
+ "s6_ach_head": "🏆 Achievement",
115
+ "s6_ach_text": "Improve your Moral Compass Score to earn certification.",
116
+ "s6_scroll": "👇 CONTINUE 👇",
117
+ "s6_proceed": "Proceed to ethical tooling & evaluation modules."
118
+ },
119
+ "es": {
120
+ "title": "⚖️ El Desafío de la Brújula Moral",
121
+ "loading": "⏳ Cargando...",
122
+ "loading_session": "🔒 Cargando tu sesión...",
123
+ "s1_title_auth": "Has Construido un Modelo Preciso",
124
+ "s1_sub_auth": "A través de la experimentación e iteración, has logrado resultados impresionantes:",
125
+ "lbl_best_acc": "Tu Mejor Precisión",
126
+ "lbl_ind_rank": "Tu Rango Individual",
127
+ "lbl_team": "Tu Equipo",
128
+ "lbl_team_rank": "Rango de Equipo:",
129
+ "s1_li1": "✅ Dominaste el proceso de construcción de modelos",
130
+ "s1_li2": "✅ Escalaste en la tabla de clasificación de precisión",
131
+ "s1_li3": "✅ Competiste con otros ingenieros",
132
+ "s1_li4": "✅ Ganaste promociones y desbloqueaste herramientas",
133
+ "s1_congrats": "🏆 ¡Felicidades por tu logro técnico!",
134
+ "s1_box_title": "Pero ahora conoces la historia completa...",
135
+ "s1_box_text": "La alta precisión no es suficiente. Los sistemas de IA del mundo real también deben ser <strong>justos, equitativos y <span class='emph-harm'>minimizar el daño</span></strong> para todos los grupos de personas.",
136
+ "s1_title_guest": "Listo para Comenzar tu Viaje",
137
+ "s1_sub_guest": "Has aprendido sobre el proceso de construcción de modelos y estás listo para aceptar el desafío:",
138
+ "s1_li1_guest": "✅ Entendiste el proceso de construcción de modelos de IA",
139
+ "s1_li2_guest": "✅ Aprendiste sobre precisión y rendimiento",
140
+ "s1_li3_guest": "✅ Descubriste el sesgo del mundo real en los sistemas de IA",
141
+ "s1_ready": "🎯 ¡Listo para aprender sobre IA ética!",
142
+ "btn_new_std": "Introducir el Nuevo Estándar ▶️",
143
+ "s2_title": "Necesitamos un Estándar Más Alto",
144
+ "s2_sub": "Aunque tu modelo es preciso, se necesita un estándar más alto para prevenir <span class='emph-harm'>daños en el mundo real</span>. Para incentivar este nuevo enfoque, introducimos una nueva puntuación.",
145
+ "s2_box_head": "Observa tu Puntuación",
146
+ "lbl_acc_score": "Puntuación de Precisión",
147
+ "s2_box_emph_head": "Esta puntuación mide solo <strong>una dimensión</strong> del éxito.",
148
+ "s2_box_emph_text": "Es hora de agregar una segunda dimensión igualmente importante: <strong class='emph-fairness'>Ética</strong>.",
149
+ "btn_back": "◀️ Atrás",
150
+ "btn_reset": "Restablecer y Transformar ▶️",
151
+ "s3_title": "Tu Puntuación de Precisión Se Está Restableciendo",
152
+ "lbl_score_reset": "Puntuación Restablecida",
153
+ "s3_why_head": "⚠️ ¿Por Qué Este Restablecimiento?",
154
+ "s3_why_text": "Restablecemos tu puntuación para enfatizar una verdad crítica: tu éxito anterior se midió por solo <strong>una dimensión</strong> — precisión de predicción. Hasta ahora, <strong>no has demostrado</strong> que sabes cómo hacer que tu sistema de IA sea <span class='emph-fairness'>seguro para la sociedad</span>. Aún no sabes si el modelo que construiste está <strong class='emph-harm'>tan sesgado</strong> como los ejemplos dañinos que estudiamos en la actividad anterior. De ahora en adelante, necesitarás sobresalir en <strong>dos frentes</strong>: rendimiento técnico <em>y</em> responsabilidad ética.",
155
+ "s3_worry_head": "✅ ¡No Te Preocupes!",
156
+ "s3_worry_text": "A medida que hagas que tu IA sea más ética a través de las próximas lecciones y desafíos, <strong>tu puntuación será restaurada</strong>—y podría subir aún más que antes.",
157
+ "btn_intro_mc": "Introducir Brújula Moral ▶️",
158
+ "s4_title": "Una Nueva Forma de Ganar",
159
+ "s4_sub": "Tu nuevo objetivo es escalar en la clasificación aumentando tu <strong>Puntuación de Brújula Moral</strong>.",
160
+ "s4_formula_head": "📐 La Fórmula de Puntuación",
161
+ "s4_formula_text": "<strong>Puntuación de Brújula Moral</strong> =<br><br>[ Precisión del Modelo Actual ] × [ Progreso Ético % ]",
162
+ "s4_where": "Donde:",
163
+ "s4_li1": "<strong>Precisión del Modelo Actual:</strong> Tu rendimiento técnico",
164
+ "s4_li2": "<strong>Progreso Ético %:</strong> Porcentaje de:",
165
+ "s4_li2_sub1": "✅ Tareas de aprendizaje ético completadas",
166
+ "s4_li2_sub2": "✅ Preguntas de control respondidas correctamente",
167
+ "s4_mean_head": "💡 Qué Significa Esto:",
168
+ "s4_mean_text": "<strong>No puedes</strong> ganar solo con precisión—también debes demostrar <strong class='emph-fairness'>comprensión ética</strong>. Y <strong>no puedes</strong> ganar solo completando lecciones—necesitas un modelo funcional también. <strong class='emph-risk'>Ambas dimensiones importan</strong>.",
169
+ "btn_see_chal": "Ver el Desafío por Delante ▶️",
170
+ "s6_title": "📍 Tu Nuevo Punto de Partida",
171
+ "s6_pos_auth": "Anteriormente estabas clasificado #{rank} en la tabla de precisión.",
172
+ "s6_pos_guest": "Establecerás tu posición a medida que construyas modelos éticamente conscientes.",
173
+ "s6_mc_rank": "Rango Actual de Brújula Moral: <span style='color:#b91c1c;'>Aún No Establecido</span>",
174
+ "s6_mc_score": "(Puntuación de Brújula Moral = 0 inicialmente)",
175
+ "s6_path_head": "🛤️ Camino a Seguir",
176
+ "s6_li1": "🔍 Detectar y medir sesgos",
177
+ "s6_li2": "⚖️ Aplicar métricas de equidad",
178
+ "s6_li3": "🔧 Rediseñar modelos para minimizar daños",
179
+ "s6_li4": "📊 Equilibrar rendimiento y ética",
180
+ "s6_ach_head": "🏆 Logro",
181
+ "s6_ach_text": "Mejora tu Puntuación de Brújula Moral para obtener la certificación.",
182
+ "s6_scroll": "👇 CONTINUAR 👇",
183
+ "s6_proceed": "Proceder a herramientas y evaluación ética."
184
+ },
185
+ "ca": {
186
+ "title": "⚖️ El Repte de la Brúixola Moral",
187
+ "loading": "⏳ Carregant...",
188
+ "loading_session": "🔒 Carregant la teva sessió...",
189
+ "s1_title_auth": "Has Construït un Model Precís",
190
+ "s1_sub_auth": "A través de l'experimentació i iteració, has aconseguit resultats impressionants:",
191
+ "lbl_best_acc": "La Teva Millor Precisió",
192
+ "lbl_ind_rank": "El Teu Rang Individual",
193
+ "lbl_team": "El Teu Equip",
194
+ "lbl_team_rank": "Rang d'Equip:",
195
+ "s1_li1": "✅ Has dominat el procés de construcció de models",
196
+ "s1_li2": "✅ Has escalat a la taula de classificació de precisió",
197
+ "s1_li3": "✅ Has competit amb altres enginyers",
198
+ "s1_li4": "✅ Has guanyat promocions i desbloquejat eines",
199
+ "s1_congrats": "🏆 Felicitats pel teu èxit tècnic!",
200
+ "s1_box_title": "Però ara coneixes la història completa...",
201
+ "s1_box_text": "L'alta precisió no és suficient. Els sistemes d'IA del món real també han de ser <strong>justos, equitatius i <span class='emph-harm'>minimitzar el dany</span></strong> per a tots els grups de persones.",
202
+ "s1_title_guest": "A Punt per Començar el Teu Viatge",
203
+ "s1_sub_guest": "Has après sobre el procés de construcció de models i estàs a punt per acceptar el repte:",
204
+ "s1_li1_guest": "✅ Has entès el procés de construcció de models d'IA",
205
+ "s1_li2_guest": "✅ Has après sobre precisió i rendiment",
206
+ "s1_li3_guest": "✅ Has descobert el biaix del món real en els sistemes d'IA",
207
+ "s1_ready": "🎯 A punt per aprendre sobre IA ètica!",
208
+ "btn_new_std": "Introduir el Nou Estàndard ▶️",
209
+ "s2_title": "Necessitem un Estàndard Més Alt",
210
+ "s2_sub": "Tot i que el teu model és precís, es necessita un estàndard més alt per prevenir <span class='emph-harm'>danys al món real</span>. Per incentivar aquest nou enfocament, introduïm una nova puntuació.",
211
+ "s2_box_head": "Observa la teva Puntuació",
212
+ "lbl_acc_score": "Puntuació de Precisió",
213
+ "s2_box_emph_head": "Aquesta puntuació mesura només <strong>una dimensió</strong> de l'èxit.",
214
+ "s2_box_emph_text": "És hora d'afegir una segona dimensió igualment important: <strong class='emph-fairness'>Ètica</strong>.",
215
+ "btn_back": "◀️ Enrere",
216
+ "btn_reset": "Restablir i Transformar ▶️",
217
+ "s3_title": "La Teva Puntuació de Precisió S'està Restablint",
218
+ "lbl_score_reset": "Puntuació Restablerta",
219
+ "s3_why_head": "⚠️ Per Què Aquest Restabliment?",
220
+ "s3_why_text": "Restablim la teva puntuació per emfatitzar una veritat crítica: el teu èxit anterior es va mesurar per només <strong>una dimensió</strong> — precisió de predicció. Fins ara, <strong>no has demostrat</strong> que saps com fer que el teu sistema d'IA sigui <span class='emph-fairness'>segur per a la societat</span>. Encara no saps si el model que has construït està <strong class='emph-harm'>tan esbiaixat</strong> com els exemples nocius que vam estudiar en l'activitat anterior. D'ara endavant, necessitaràs sobresortir en <strong>dos fronts</strong>: rendiment tècnic <em>i</em> responsabilitat ètica.",
221
+ "s3_worry_head": "✅ No Et Preocupis!",
222
+ "s3_worry_text": "A mesura que facis que la teva IA sigui més ètica a través de les properes lliçons i reptes, <strong>la teva puntuació serà restaurada</strong>—i podria pujar encara més que abans.",
223
+ "btn_intro_mc": "Introduir Brúixola Moral ▶️",
224
+ "s4_title": "Una Nova Manera de Guanyar",
225
+ "s4_sub": "El teu nou objectiu és escalar a la classificació augmentant la teva <strong>Puntuació de Brúixola Moral</strong>.",
226
+ "s4_formula_head": "📐 La Fórmula de Puntuació",
227
+ "s4_formula_text": "<strong>Puntuació de Brúixola Moral</strong> =<br><br>[ Precisió del Model Actual ] × [ Progrés Ètic % ]",
228
+ "s4_where": "On:",
229
+ "s4_li1": "<strong>Precisió del Model Actual:</strong> El teu rendiment tècnic",
230
+ "s4_li2": "<strong>Progrés Ètic %:</strong> Percentatge de:",
231
+ "s4_li2_sub1": "✅ Tasques d'aprenentatge ètic completades",
232
+ "s4_li2_sub2": "✅ Preguntes de control respostes correctament",
233
+ "s4_mean_head": "💡 Què Significa Això:",
234
+ "s4_mean_text": "<strong>No pots</strong> guanyar només amb precisió—també has de demostrar <strong class='emph-fairness'>comprensió ètica</strong>. I <strong>no pots</strong> guanyar només completant lliçons—necessites un model funcional també. <strong class='emph-risk'>Les dues dimensions importen</strong>.",
235
+ "btn_see_chal": "Veure el Repte per Endavant ▶️",
236
+ "s6_title": "📍 El Teu Nou Punt de Partida",
237
+ "s6_pos_auth": "Anteriorment estaves classificat #{rank} a la taula de precisió.",
238
+ "s6_pos_guest": "Establiràs la teva posició a mesura que construeixis models èticament conscients.",
239
+ "s6_mc_rank": "Rang Actual de Brúixola Moral: <span style='color:#b91c1c;'>Encara No Establert</span>",
240
+ "s6_mc_score": "(Puntuació de Brúixola Moral = 0 inicialment)",
241
+ "s6_path_head": "🛤️ Camí a Seguir",
242
+ "s6_li1": "🔍 Detectar i mesurar biaixos",
243
+ "s6_li2": "⚖️ Aplicar mètriques d'equitat",
244
+ "s6_li3": "🔧 Redissenyar models per minimitzar danys",
245
+ "s6_li4": "📊 Equilibrar rendiment i ètica",
246
+ "s6_ach_head": "🏆 Assoliment",
247
+ "s6_ach_text": "Millora la teva Puntuació de Brúixola Moral per obtenir la certificació.",
248
+ "s6_scroll": "👇 CONTINUAR 👇",
249
+ "s6_proceed": "Procedir a eines i avaluació ètica."
250
+ }
251
+ }
252
+
253
+ # ---------------------------------------------------------------------------
254
+ # Logic / Helpers
255
+ # ---------------------------------------------------------------------------
256
+
257
+ def _log(msg: str):
258
+ if DEBUG_LOG:
259
+ print(f"[MoralCompassApp] {msg}")
260
+
261
+ def _normalize_team_name(name: str) -> str:
262
+ if not name:
263
+ return ""
264
+ return " ".join(str(name).strip().split())
265
+
266
+ def _fetch_leaderboard(token: str) -> Optional[pd.DataFrame]:
267
+ now = time.time()
268
+ with _cache_lock:
269
+ if (
270
+ _leaderboard_cache["data"] is not None
271
+ and now - _leaderboard_cache["timestamp"] < LEADERBOARD_CACHE_SECONDS
272
+ ):
273
+ return _leaderboard_cache["data"]
274
+
275
+ try:
276
+ playground_id = "https://cf3wdpkg0d.execute-api.us-east-1.amazonaws.com/prod/m"
277
+ playground = Competition(playground_id)
278
+ df = playground.get_leaderboard(token=token)
279
+ if df is not None and not df.empty and MAX_LEADERBOARD_ENTRIES:
280
+ df = df.head(MAX_LEADERBOARD_ENTRIES)
281
+ except Exception as e:
282
+ _log(f"Leaderboard fetch failed: {e}")
283
+ df = None
284
+
285
+ with _cache_lock:
286
+ _leaderboard_cache["data"] = df
287
+ _leaderboard_cache["timestamp"] = time.time()
288
+ return df
289
+
290
+ def _get_or_assign_team(username: str, leaderboard_df: Optional[pd.DataFrame]) -> Tuple[str, bool]:
291
+ try:
292
+ if leaderboard_df is not None and not leaderboard_df.empty and "Team" in leaderboard_df.columns:
293
+ user_submissions = leaderboard_df[leaderboard_df["username"] == username]
294
+ if not user_submissions.empty:
295
+ if "timestamp" in user_submissions.columns:
296
+ try:
297
+ user_submissions = user_submissions.copy()
298
+ user_submissions["timestamp"] = pd.to_datetime(
299
+ user_submissions["timestamp"], errors="coerce"
300
+ )
301
+ user_submissions = user_submissions.sort_values("timestamp", ascending=False)
302
+ except Exception as ts_err:
303
+ _log(f"Timestamp sort error: {ts_err}")
304
+ existing_team = user_submissions.iloc[0]["Team"]
305
+ if pd.notna(existing_team) and str(existing_team).strip():
306
+ return _normalize_team_name(existing_team), False
307
+ return _normalize_team_name(random.choice(TEAM_NAMES)), True
308
+ except Exception as e:
309
+ _log(f"Team assignment error: {e}")
310
+ return _normalize_team_name(random.choice(TEAM_NAMES)), True
311
+
312
+ def _try_session_based_auth(request: "gr.Request") -> Tuple[bool, Optional[str], Optional[str]]:
313
+ try:
314
+ session_id = request.query_params.get("sessionid") if request else None
315
+ if not session_id:
316
+ return False, None, None
317
+ token = get_token_from_session(session_id)
318
+ if not token:
319
+ return False, None, None
320
+ username = _get_username_from_token(token)
321
+ if not username:
322
+ return False, None, None
323
+ return True, username, token
324
+ except Exception as e:
325
+ _log(f"Session auth failed: {e}")
326
+ return False, None, None
327
+
328
+ def _compute_user_stats(username: str, token: str) -> Dict[str, Any]:
329
+ now = time.time()
330
+ cached = _user_stats_cache.get(username)
331
+ if cached and (now - cached.get("_ts", 0) < USER_STATS_TTL):
332
+ return cached
333
+
334
+ leaderboard_df = _fetch_leaderboard(token)
335
+ team_name, _ = _get_or_assign_team(username, leaderboard_df)
336
+ best_score = None
337
+ rank = None
338
+ team_rank = None
339
+
340
+ try:
341
+ if leaderboard_df is not None and not leaderboard_df.empty:
342
+ if "accuracy" in leaderboard_df.columns and "username" in leaderboard_df.columns:
343
+ user_submissions = leaderboard_df[leaderboard_df["username"] == username]
344
+ if not user_submissions.empty:
345
+ best_score = user_submissions["accuracy"].max()
346
+
347
+ # Individual rank
348
+ user_bests = leaderboard_df.groupby("username")["accuracy"].max()
349
+ summary_df = user_bests.reset_index()
350
+ summary_df.columns = ["Engineer", "Best_Score"]
351
+ summary_df = summary_df.sort_values("Best_Score", ascending=False).reset_index(drop=True)
352
+ summary_df.index = summary_df.index + 1
353
+ my_row = summary_df[summary_df["Engineer"] == username]
354
+ if not my_row.empty:
355
+ rank = my_row.index[0]
356
+
357
+ # Team rank
358
+ if "Team" in leaderboard_df.columns and team_name:
359
+ team_summary_df = (
360
+ leaderboard_df.groupby("Team")["accuracy"]
361
+ .agg(Best_Score="max")
362
+ .reset_index()
363
+ .sort_values("Best_Score", ascending=False)
364
+ .reset_index(drop=True)
365
+ )
366
+ team_summary_df.index = team_summary_df.index + 1
367
+ my_team_row = team_summary_df[team_summary_df["Team"] == team_name]
368
+ if not my_team_row.empty:
369
+ team_rank = my_team_row.index[0]
370
+ except Exception as e:
371
+ _log(f"User stats error for {username}: {e}")
372
+
373
+ stats = {
374
+ "username": username,
375
+ "best_score": best_score,
376
+ "rank": rank,
377
+ "team_name": team_name,
378
+ "team_rank": team_rank,
379
+ "is_signed_in": True,
380
+ "_ts": now
381
+ }
382
+ _user_stats_cache[username] = stats
383
+ return stats
384
+
385
+ # ---------------------------------------------------------------------------
386
+ # HTML Helpers (I18N)
387
+ # ---------------------------------------------------------------------------
388
+
389
+ def t(lang, key):
390
+ return TRANSLATIONS.get(lang, TRANSLATIONS["en"]).get(key, key)
391
+
392
+ def build_standing_html(user_stats, lang="en"):
393
+ if user_stats["is_signed_in"] and user_stats["best_score"] is not None:
394
+ best_score_pct = f"{(user_stats['best_score'] * 100):.1f}%"
395
+ rank_text = f"#{user_stats['rank']}" if user_stats["rank"] else "N/A"
396
+ team_text = user_stats["team_name"] if user_stats["team_name"] else "N/A"
397
+ team_rank_text = f"#{user_stats['team_rank']}" if user_stats["team_rank"] else "N/A"
398
+ return f"""
399
+ <div class='slide-shell slide-shell--info'>
400
+ <h3 class='slide-shell__title'>
401
+ {t(lang, 's1_title_auth')}
402
+ </h3>
403
+ <div class='content-box'>
404
+ <p class='slide-shell__subtitle'>
405
+ {t(lang, 's1_sub_auth')}
406
+ </p>
407
+ <div class='stat-grid'>
408
+ <div class='stat-card stat-card--success'>
409
+ <p class='stat-card__label'>{t(lang, 'lbl_best_acc')}</p>
410
+ <p class='stat-card__value'>{best_score_pct}</p>
411
+ </div>
412
+ <div class='stat-card stat-card--accent'>
413
+ <p class='stat-card__label'>{t(lang, 'lbl_ind_rank')}</p>
414
+ <p class='stat-card__value'>{rank_text}</p>
415
+ </div>
416
+ </div>
417
+ <div class='team-card'>
418
+ <p class='team-card__label'>{t(lang, 'lbl_team')}</p>
419
+ <p class='team-card__value'>🛡️ {team_text}</p>
420
+ <p class='team-card__rank'>{t(lang, 'lbl_team_rank')} {team_rank_text}</p>
421
+ </div>
422
+ <ul class='bullet-list'>
423
+ <li>{t(lang, 's1_li1')}</li>
424
+ <li>{t(lang, 's1_li2')}</li>
425
+ <li>{t(lang, 's1_li3')}</li>
426
+ <li>{t(lang, 's1_li4')}</li>
427
+ </ul>
428
+ <p class='slide-shell__subtitle' style='font-weight:600;'>
429
+ {t(lang, 's1_congrats')}
430
+ </p>
431
+ </div>
432
+ <div class='content-box content-box--emphasis'>
433
+ <p class='content-box__heading'>
434
+ {t(lang, 's1_box_title')}
435
+ </p>
436
+ <p>
437
+ {t(lang, 's1_box_text')}
438
+ </p>
439
+ </div>
440
+ </div>
441
+ """
442
+ elif user_stats["is_signed_in"]:
443
+ return f"""
444
+ <div class='slide-shell slide-shell--info'>
445
+ <h3 class='slide-shell__title'>
446
+ {t(lang, 's1_title_guest')}
447
+ </h3>
448
+ <div class='content-box'>
449
+ <p class='slide-shell__subtitle'>
450
+ {t(lang, 's1_sub_guest')}
451
+ </p>
452
+ <ul class='bullet-list'>
453
+ <li>{t(lang, 's1_li1_guest')}</li>
454
+ <li>{t(lang, 's1_li2_guest')}</li>
455
+ <li>{t(lang, 's1_li3_guest')}</li>
456
+ </ul>
457
+ <p class='slide-shell__subtitle' style='font-weight:600;'>
458
+ {t(lang, 's1_ready')}
459
+ </p>
460
+ </div>
461
+ <div class='content-box content-box--emphasis'>
462
+ <p class='content-box__heading'>
463
+ {t(lang, 's1_box_title')}
464
+ </p>
465
+ <p>
466
+ {t(lang, 's1_box_text')}
467
+ </p>
468
+ </div>
469
+ </div>
470
+ """
471
+ else:
472
+ return f"""
473
+ <div class='slide-shell slide-shell--warning' style='text-align:center;'>
474
+ <h2 class='slide-shell__title'>
475
+ {t(lang, 'loading_session')}
476
+ </h2>
477
+ </div>
478
+ """
479
+
480
+ def build_step2_html(user_stats, lang="en"):
481
+ if user_stats.get("is_signed_in") and user_stats.get("best_score") is not None:
482
+ gauge_value = int(user_stats["best_score"] * 100)
483
+ else:
484
+ gauge_value = 75
485
+ gauge_percent = f"{gauge_value}%"
486
+ return f"""
487
+ <div class='slide-shell slide-shell--warning'>
488
+ <h3 class='slide-shell__title'>{t(lang, 's2_title')}</h3>
489
+ <p class='slide-shell__subtitle'>
490
+ {t(lang, 's2_sub')}
491
+ </p>
492
+ <div class='content-box'>
493
+ <h4 class='content-box__heading'>{t(lang, 's2_box_head')}</h4>
494
+ <div class='score-gauge-container'>
495
+ <div class='score-gauge' style='--fill-percent:{gauge_percent};'>
496
+ <div class='score-gauge-inner'>
497
+ <div class='score-gauge-value'>{gauge_value}</div>
498
+ <div class='score-gauge-label'>{t(lang, 'lbl_acc_score')}</div>
499
+ </div>
500
+ </div>
501
+ </div>
502
+ </div>
503
+ <div class='content-box content-box--emphasis'>
504
+ <p class='content-box__heading'>
505
+ {t(lang, 's2_box_emph_head')}
506
+ </p>
507
+ <p>
508
+ {t(lang, 's2_box_emph_text')}
509
+ </p>
510
+ </div>
511
+ </div>
512
+ """
513
+
514
+ def _get_step3_html(lang):
515
+ return f"""
516
+ <div class='slide-shell slide-shell--warning'>
517
+ <div style='text-align:center;'>
518
+ <h3 class='slide-shell__title'>
519
+ {t(lang, 's3_title')}
520
+ </h3>
521
+ <div class='score-gauge-container'>
522
+ <div class='score-gauge gauge-dropped' style='--fill-percent: 0%;'>
523
+ <div class='score-gauge-inner'>
524
+ <div class='score-gauge-value' style='color:#dc2626;'>0</div>
525
+ <div class='score-gauge-label'>{t(lang, 'lbl_score_reset')}</div>
526
+ </div>
527
+ </div>
528
+ </div>
529
+ <div class='content-box content-box--danger'>
530
+ <h4 class='content-box__heading'>
531
+ {t(lang, 's3_why_head')}
532
+ </h4>
533
+ <p class='slide-teaching-body' style='text-align:left;'>
534
+ {t(lang, 's3_why_text')}
535
+ </p>
536
+ </div>
537
+ <div class='content-box content-box--success'>
538
+ <h4 class='content-box__heading'>
539
+ {t(lang, 's3_worry_head')}
540
+ </h4>
541
+ <p class='slide-teaching-body'>
542
+ {t(lang, 's3_worry_text')}
543
+ </p>
544
+ </div>
545
+ </div>
546
+ </div>
547
+ """
548
+
549
+ def _get_step4_html(lang):
550
+ return f"""
551
+ <div class='slide-shell slide-shell--info'>
552
+ <h3 class='slide-shell__title'>
553
+ {t(lang, 's4_title')}
554
+ </h3>
555
+ <p class='slide-shell__subtitle'>
556
+ {t(lang, 's4_sub')}
557
+ </p>
558
+ <div class='content-box formula-box'>
559
+ <h4 class='content-box__heading' style='text-align:center;'>{t(lang, 's4_formula_head')}</h4>
560
+ <div class='formula-math'>
561
+ {t(lang, 's4_formula_text')}
562
+ </div>
563
+ <div class='content-box' style='margin-top:20px;'>
564
+ <p class='content-box__heading'>{t(lang, 's4_where')}</p>
565
+ <ul class='bullet-list'>
566
+ <li>{t(lang, 's4_li1')}</li>
567
+ <li>
568
+ {t(lang, 's4_li2')}
569
+ <ul class='bullet-list' style='margin-top:8px;'>
570
+ <li>{t(lang, 's4_li2_sub1')}</li>
571
+ <li>{t(lang, 's4_li2_sub2')}</li>
572
+ </ul>
573
+ </li>
574
+ </ul>
575
+ </div>
576
+ </div>
577
+ <div class='content-box content-box--success'>
578
+ <h4 class='content-box__heading'>{t(lang, 's4_mean_head')}</h4>
579
+ <p class='slide-teaching-body'>
580
+ {t(lang, 's4_mean_text')}
581
+ </p>
582
+ </div>
583
+ </div>
584
+ """
585
+
586
+ def build_step6_html(user_stats, lang="en"):
587
+ if user_stats.get("is_signed_in") and user_stats.get("rank"):
588
+ position_msg = t(lang, 's6_pos_auth').replace("{rank}", str(user_stats['rank']))
589
+ else:
590
+ position_msg = t(lang, 's6_pos_guest')
591
+
592
+ return f"""
593
+ <div class='slide-shell slide-shell--info'>
594
+ <h3 class='slide-shell__title'>{t(lang, 's6_title')}</h3>
595
+ <div class='content-box'>
596
+ <p>{position_msg}</p>
597
+ <div class='content-box content-box--danger'>
598
+ <p class='content-box__heading'>
599
+ {t(lang, 's6_mc_rank')}
600
+ </p>
601
+ <p>{t(lang, 's6_mc_score')}</p>
602
+ </div>
603
+ </div>
604
+ <div class='content-box content-box--success'>
605
+ <h4 class='content-box__heading'>{t(lang, 's6_path_head')}</h4>
606
+ <ul class='bullet-list'>
607
+ <li>{t(lang, 's6_li1')}</li>
608
+ <li>{t(lang, 's6_li2')}</li>
609
+ <li>{t(lang, 's6_li3')}</li>
610
+ <li>{t(lang, 's6_li4')}</li>
611
+ </ul>
612
+ </div>
613
+ <div class='content-box content-box--emphasis'>
614
+ <p class='content-box__heading'>
615
+ {t(lang, 's6_ach_head')}
616
+ </p>
617
+ <p>{t(lang, 's6_ach_text')}</p>
618
+ </div>
619
+ <h1 style='margin:32px 0 16px 0; font-size:2.5rem; text-align:center;'>{t(lang, 's6_scroll')}</h1>
620
+ <p style='text-align:center;'>{t(lang, 's6_proceed')}</p>
621
+ </div>
622
+ """
623
+
624
+ # ---------------------------------------------------------------------------
625
+ # CSS
626
+ # ---------------------------------------------------------------------------
627
+ CSS = """
628
+ .large-text { font-size: 20px !important; }
629
+ /* Slide + containers */
630
+ .slide-shell {
631
+ padding: 28px;
632
+ border-radius: 16px;
633
+ background-color: var(--block-background-fill);
634
+ color: var(--body-text-color);
635
+ border: 2px solid var(--border-color-primary);
636
+ box-shadow: 0 8px 20px rgba(0,0,0,0.05);
637
+ max-width: 900px;
638
+ margin: 0 auto 24px auto;
639
+ font-size: 20px;
640
+ }
641
+ .slide-shell--info { border-color: var(--color-accent); }
642
+ .slide-shell--warning { border-color: var(--color-accent); }
643
+ .slide-shell__title {
644
+ font-size: 2rem; margin: 0 0 16px 0; text-align: center;
645
+ }
646
+ .slide-shell__subtitle {
647
+ font-size: 1.1rem; margin-top: 8px; text-align: center; color: var(--secondary-text-color); line-height: 1.7;
648
+ }
649
+ .content-box {
650
+ background-color: var(--block-background-fill); border-radius: 12px; border: 1px solid var(--border-color-primary); padding: 24px; margin: 24px 0;
651
+ }
652
+ .content-box__heading {
653
+ margin-top: 0; font-weight: 600; font-size: 1.2rem;
654
+ }
655
+ .content-box--emphasis { border-left: 6px solid var(--color-accent); }
656
+ .content-box--danger { border-left: 6px solid #dc2626; }
657
+ .content-box--success { border-left: 6px solid #16a34a; }
658
+ .bullet-list {
659
+ list-style: none; padding-left: 0; margin: 16px auto 0 auto; max-width: 600px; font-size: 1.05rem;
660
+ }
661
+ .bullet-list li { padding: 6px 0; }
662
+ /* Stats cards */
663
+ .stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 24px auto; max-width: 600px; }
664
+ .stat-card, .team-card {
665
+ text-align: center; padding: 16px; border-radius: 10px; border: 1px solid var(--border-color-primary); background-color: var(--block-background-fill);
666
+ }
667
+ .stat-card__label, .team-card__label { margin: 0; font-size: 0.9rem; color: var(--secondary-text-color); }
668
+ .stat-card__value { margin: 8px 0 0 0; font-size: 2.2rem; font-weight: 800; }
669
+ .stat-card--success .stat-card__value { color: #16a34a; }
670
+ .stat-card--accent .stat-card__value { color: var(--color-accent); }
671
+ .team-card__value { margin: 8px 0 4px 0; font-size: 1.5rem; font-weight: 700; }
672
+ .team-card__rank { margin: 0; font-size: 1rem; color: var(--secondary-text-color); }
673
+ /* Teaching body */
674
+ .slide-teaching-body { font-size: 1.1rem; line-height: 1.8; margin-top: 1rem; }
675
+ /* Emphasis */
676
+ .emph-harm { color: #b91c1c; font-weight: 700; }
677
+ .emph-risk { color: #b45309; font-weight: 600; }
678
+ .emph-fairness { color: var(--color-accent); font-weight: 600; }
679
+ @media (prefers-color-scheme: dark) {
680
+ .emph-harm { color: #fca5a5; }
681
+ .emph-risk { color: #fed7aa; }
682
+ }
683
+ /* Gauge */
684
+ .score-gauge-container { position: relative; width: 260px; height: 260px; margin: 24px auto; }
685
+ .score-gauge {
686
+ width: 100%; height: 100%; border-radius: 50%;
687
+ background: conic-gradient(from 180deg, #16a34a 0%, #16a34a var(--fill-percent, 0%), var(--border-color-primary) var(--fill-percent, 0%), var(--border-color-primary) 100%);
688
+ display: flex; align-items: center; justify-content: center; position: relative; box-shadow: 0 10px 30px rgba(0,0,0,0.12);
689
+ }
690
+ .score-gauge-inner {
691
+ width: 70%; height: 70%; border-radius: 50%; background-color: var(--block-background-fill);
692
+ display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 2; border: 1px solid var(--border-color-primary);
693
+ }
694
+ .score-gauge-value { font-size: 3.2rem; font-weight: 800; color: var(--body-text-color); line-height: 1; }
695
+ .score-gauge-label { font-size: 0.95rem; color: var(--secondary-text-color); margin-top: 8px; }
696
+ /* Gauge reset animation */
697
+ @keyframes gauge-drop {
698
+ 0% { background: conic-gradient(from 180deg,#16a34a 0%,#16a34a 75%,var(--border-color-primary) 75%,var(--border-color-primary) 100%); }
699
+ 100% { background: conic-gradient(from 180deg,#dc2626 0%,#dc2626 0%,var(--border-color-primary) 0%,var(--border-color-primary) 100%); }
700
+ }
701
+ .gauge-dropped { animation: gauge-drop 2s ease-out forwards; }
702
+ /* Navigation overlay */
703
+ #nav-loading-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background-color: var(--body-background-fill); z-index:9999; display:none; flex-direction:column; align-items:center; justify-content:center; opacity:0; transition:opacity .25s ease; }
704
+ .nav-spinner { width:50px; height:50px; border:5px solid var(--block-background-fill); border-top:5px solid var(--color-accent); border-radius:50%; animation: nav-spin 1s linear infinite; margin-bottom:20px; }
705
+ @keyframes nav-spin { to { transform: rotate(360deg); } }
706
+ #nav-loading-text { font-size:1.3rem; font-weight:600; color: var(--body-text-color); }
707
+ @media (prefers-color-scheme: dark) { .slide-shell, .content-box, .alert { box-shadow:none; } .score-gauge { box-shadow:none; } }
708
+ """
709
+
710
+ # ---------------------------------------------------------------------------
711
+ # App Factory
712
+ # ---------------------------------------------------------------------------
713
+ def create_moral_compass_challenge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
714
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue=theme_primary_hue), css=CSS) as demo:
715
+ gr.HTML("<div id='app_top_anchor' style='height:0;'></div>")
716
+ gr.HTML("""
717
+ <div id='nav-loading-overlay'>
718
+ <div class='nav-spinner'></div>
719
+ <span id='nav-loading-text'>Loading...</span>
720
+ </div>
721
+ """)
722
+
723
+ # --- Components ---
724
+ c_title = gr.Markdown("<h1 style='text-align:center;'>⚖️ The Moral Compass Challenge</h1>")
725
+
726
+ # Initial loading (visible first)
727
+ with gr.Column(visible=True, elem_id="initial-loading") as initial_loading:
728
+ c_loading = gr.Markdown("<div style='text-align:center; padding:80px 0;'><h2>⏳ Loading...</h2></div>")
729
+
730
+ # Step 1
731
+ with gr.Column(visible=False, elem_id="step-1") as step_1:
732
+ stats_display = gr.HTML() # Built dynamically
733
+ step_1_next = gr.Button(t('en', 'btn_new_std'), variant="primary", size="lg")
734
+
735
+ # Step 2
736
+ with gr.Column(visible=False, elem_id="step-2") as step_2:
737
+ step_2_html_comp = gr.HTML() # Built dynamically
738
+ with gr.Row():
739
+ step_2_back = gr.Button(t('en', 'btn_back'), size="lg")
740
+ step_2_next = gr.Button(t('en', 'btn_reset'), variant="primary", size="lg")
741
+
742
+ # Step 3
743
+ with gr.Column(visible=False, elem_id="step-3") as step_3:
744
+ step_3_html_comp = gr.HTML(_get_step3_html('en'))
745
+ with gr.Row():
746
+ step_3_back = gr.Button(t('en', 'btn_back'), size="lg")
747
+ step_3_next = gr.Button(t('en', 'btn_intro_mc'), variant="primary", size="lg")
748
+
749
+ # Step 4
750
+ with gr.Column(visible=False, elem_id="step-4") as step_4:
751
+ step_4_html_comp = gr.HTML(_get_step4_html('en'))
752
+ with gr.Row():
753
+ step_4_back = gr.Button(t('en', 'btn_back'), size="lg")
754
+ step_4_next = gr.Button(t('en', 'btn_see_chal'), variant="primary", size="lg")
755
+
756
+ # Step 6
757
+ with gr.Column(visible=False, elem_id="step-6") as step_6:
758
+ step_6_html_comp = gr.HTML() # Built dynamically
759
+ with gr.Row():
760
+ step_6_back = gr.Button(t('en', 'btn_back'), size="lg")
761
+
762
+ loading_screen = gr.Column(visible=False)
763
+ all_steps = [step_1, step_2, step_3, step_4, step_6, loading_screen, initial_loading]
764
+
765
+ # --- Initial Load Handler ---
766
+ def initial_load(request: gr.Request):
767
+ # 1. Language
768
+ params = request.query_params
769
+ lang = params.get("lang", "en")
770
+ if lang not in TRANSLATIONS: lang = "en"
771
+
772
+ # 2. Auth
773
+ success, username, token = _try_session_based_auth(request)
774
+
775
+ # 3. Stats
776
+ stats = {"is_signed_in": False, "best_score": None}
777
+ if success and username:
778
+ stats = _compute_user_stats(username, token)
779
+
780
+ return [
781
+ gr.update(visible=False), # initial_loading
782
+ gr.update(visible=True), # step_1
783
+
784
+ # Text Updates
785
+ f"<h1 style='text-align:center;'>{t(lang, 'title')}</h1>",
786
+ f"<div style='text-align:center; padding:80px 0;'><h2>{t(lang, 'loading')}</h2></div>",
787
+
788
+ # HTML Components
789
+ build_standing_html(stats, lang),
790
+ build_step2_html(stats, lang),
791
+ _get_step3_html(lang),
792
+ _get_step4_html(lang),
793
+ build_step6_html(stats, lang),
794
+
795
+ # Buttons
796
+ gr.Button(value=t(lang, 'btn_new_std')), # s1 next
797
+ gr.Button(value=t(lang, 'btn_back')), # s2 back
798
+ gr.Button(value=t(lang, 'btn_reset')), # s2 next
799
+ gr.Button(value=t(lang, 'btn_back')), # s3 back
800
+ gr.Button(value=t(lang, 'btn_intro_mc')), # s3 next
801
+ gr.Button(value=t(lang, 'btn_back')), # s4 back
802
+ gr.Button(value=t(lang, 'btn_see_chal')), # s4 next
803
+ gr.Button(value=t(lang, 'btn_back')), # s6 back
804
+ ]
805
+
806
+ update_targets = [
807
+ initial_loading, step_1,
808
+ c_title, c_loading,
809
+ stats_display, step_2_html_comp, step_3_html_comp, step_4_html_comp, step_6_html_comp,
810
+ step_1_next, step_2_back, step_2_next, step_3_back, step_3_next, step_4_back, step_4_next, step_6_back
811
+ ]
812
+
813
+ demo.load(fn=initial_load, inputs=None, outputs=update_targets)
814
+
815
+ # --- Navigation ---
816
+ def _nav_generator(target):
817
+ def go():
818
+ yield {**{s: gr.update(visible=False) for s in all_steps}, loading_screen: gr.update(visible=True)}
819
+ yield {**{s: gr.update(visible=False) for s in all_steps}, target: gr.update(visible=True)}
820
+ return go
821
+
822
+ def _nav_js(target_id: str, message: str) -> str:
823
+ return f"""
824
+ ()=>{{
825
+ try {{
826
+ const overlay=document.getElementById('nav-loading-overlay');
827
+ const msg=document.getElementById('nav-loading-text');
828
+ if(overlay && msg){{ msg.textContent='{message}'; overlay.style.display='flex'; setTimeout(()=>overlay.style.opacity='1',10); }}
829
+ const start=Date.now();
830
+ setTimeout(()=>{{ window.scrollTo({{top:0, behavior:'smooth'}}); }},40);
831
+ const poll=setInterval(()=>{{
832
+ const elapsed=Date.now()-start;
833
+ const target=document.getElementById('{target_id}');
834
+ const visible=target && target.offsetParent!==null;
835
+ if((visible && elapsed>=600) || elapsed>5000){{
836
+ clearInterval(poll);
837
+ if(overlay){{ overlay.style.opacity='0'; setTimeout(()=>overlay.style.display='none',300); }}
838
+ }}
839
+ }},90);
840
+ }} catch(e){{}}
841
+ }}
842
+ """
843
+
844
+ step_1_next.click(fn=_nav_generator(step_2), outputs=all_steps, js=_nav_js("step-2", "Loading..."))
845
+ step_2_back.click(fn=_nav_generator(step_1), outputs=all_steps, js=_nav_js("step-1", "Loading..."))
846
+ step_2_next.click(fn=_nav_generator(step_3), outputs=all_steps, js=_nav_js("step-3", "Loading..."))
847
+ step_3_back.click(fn=_nav_generator(step_2), outputs=all_steps, js=_nav_js("step-2", "Loading..."))
848
+ step_3_next.click(fn=_nav_generator(step_4), outputs=all_steps, js=_nav_js("step-4", "Loading..."))
849
+ step_4_back.click(fn=_nav_generator(step_3), outputs=all_steps, js=_nav_js("step-3", "Loading..."))
850
+ step_4_next.click(fn=_nav_generator(step_6), outputs=all_steps, js=_nav_js("step-6", "Loading..."))
851
+ step_6_back.click(fn=_nav_generator(step_4), outputs=all_steps, js=_nav_js("step-4", "Loading..."))
852
+
853
+ return demo
854
+
855
+ def launch_moral_compass_challenge_app(height: int = 1000, share: bool = False, debug: bool = False) -> None:
856
+ demo = create_moral_compass_challenge_app()
857
+ port = int(os.environ.get("PORT", 8080))
858
+ demo.launch(share=share, inline=True, debug=debug, height=height, server_port=port)