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,898 @@
1
+ """
2
+ The Ethical Revelation: Real-World Impact - 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 pandas as pd
13
+ import gradio as gr
14
+
15
+ # --- AI Model Share Imports ---
16
+ try:
17
+ from aimodelshare.playground import Competition
18
+ from aimodelshare.aws import get_token_from_session, _get_username_from_token
19
+ except ImportError:
20
+ # For local dev without aimodelshare installed, mock these if needed or raise error
21
+ # raise ImportError("The 'aimodelshare' library is required. Install with: pip install aimodelshare")
22
+ pass
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Configuration & Caching
26
+ # ---------------------------------------------------------------------------
27
+ LEADERBOARD_CACHE_SECONDS = int(os.environ.get("LEADERBOARD_CACHE_SECONDS", "45"))
28
+ MAX_LEADERBOARD_ENTRIES = os.environ.get("MAX_LEADERBOARD_ENTRIES")
29
+ MAX_LEADERBOARD_ENTRIES = int(MAX_LEADERBOARD_ENTRIES) if MAX_LEADERBOARD_ENTRIES else None
30
+ DEBUG_LOG = os.environ.get("DEBUG_LOG", "false").lower() == "true"
31
+
32
+ TEAM_NAMES = [
33
+ "The Justice League", "The Moral Champions", "The Data Detectives",
34
+ "The Ethical Explorers", "The Fairness Finders", "The Accuracy Avengers"
35
+ ]
36
+
37
+ _cache_lock = threading.Lock()
38
+ _leaderboard_cache: Dict[str, Any] = {"data": None, "timestamp": 0.0}
39
+ _user_stats_cache: Dict[str, Dict[str, Any]] = {}
40
+ USER_STATS_TTL = LEADERBOARD_CACHE_SECONDS
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # TRANSLATION CONFIGURATION
44
+ # ---------------------------------------------------------------------------
45
+
46
+ TRANSLATIONS = {
47
+ "en": {
48
+ "title": "🚀 The Ethical Revelation: Real-World Impact",
49
+ "loading_personal": "⏳ Loading your personalized experience...",
50
+ # Stats Screen
51
+ "stats_title": "🏆 Great Work, Engineer! 🏆",
52
+ "stats_subtitle": "Here's your performance summary.",
53
+ "stats_heading": "Your Stats",
54
+ "lbl_accuracy": "Best Accuracy",
55
+ "lbl_rank": "Your Rank",
56
+ "lbl_team": "Team",
57
+ "stats_footer": "Ready to share your model and explore its real-world impact?",
58
+ "btn_deploy": "🌍 Share Your AI Model (Simulation Only)",
59
+ "guest_title": "🚀 You're Signed In!",
60
+ "guest_subtitle": "You haven't submitted a model yet, but you're all set to continue learning.",
61
+ "guest_body": "Once you submit a model in the Model Building Game, your accuracy and ranking will appear here.",
62
+ "guest_footer": "Continue to the next section when you're ready.",
63
+ "loading_session": "🔒 Loading your session...",
64
+ # Step 2 (Warning)
65
+ "s2_title": "⚠️ But Wait...",
66
+ "s2_intro": "Before we share the model, there's something you need to know...",
67
+ "s2_box_title": "A Real-World Story",
68
+ "s2_p1": "A model similar to yours was actually used in the real world. It was used by judges across the United States to help make decisions about defendants' futures.",
69
+ "s2_p2": "Like yours, it had impressive accuracy scores. Like yours, it was built on data about past criminal cases. Like yours, it aimed to predict who would re-offend.",
70
+ "s2_p3": "But something was terribly wrong...",
71
+ "btn_back": "◀️ Back",
72
+ "btn_reveal": "Reveal the Truth ▶️",
73
+ # Step 3 (ProPublica)
74
+ "s3_title": "📰 The ProPublica Investigation",
75
+ "s3_head": "\"Machine Bias\" - A Landmark Investigation",
76
+ "s3_p1": "In 2016, journalists at <strong>ProPublica</strong> investigated a widely-used criminal risk assessment algorithm called <strong>COMPAS</strong>. They analyzed over <strong>7,000 actual cases</strong> to see if the AI's predictions came true.",
77
+ "s3_box_title": "Their Shocking Findings:",
78
+ "s3_alert": "⚠️ Black defendants were labeled \"high-risk\" at nearly <u>TWICE</u> the rate of white defendants.",
79
+ "s3_spec": "<strong>Specifically:</strong>",
80
+ "s3_li1_pre": "defendants who <em>did NOT re-offend</em> were incorrectly labeled as <strong>\"high-risk\"</strong> at a rate of",
81
+ "s3_li2_pre": "<strong>White defendants</strong> who <em>did NOT re-offend</em> were incorrectly labeled as <strong>\"high-risk\"</strong> at a rate of only <strong>24%</strong>",
82
+ "s3_li3": "Meanwhile, <strong>white defendants</strong> who <em>DID re-offend</em> were <strong>more likely to be labeled \"low-risk\"</strong> compared to Black defendants",
83
+ "s3_box2_title": "What Does This Mean?",
84
+ "s3_mean_p1": "The AI system was <strong class='emph-danger'>systematically biased</strong>. It didn't just make random errors—it made <strong>different kinds of errors for different groups of people</strong>.",
85
+ "s3_mean_p2": "Black defendants faced a much higher risk of being <strong class='emph-danger'>unfairly labeled as dangerous</strong>, potentially leading to longer prison sentences or denied parole—even when they would not have re-offended.",
86
+ "btn_eu": "See This in Europe ▶️",
87
+ "lbl_black": "Black", # Used in dynamic construction if needed
88
+ # Step 4 EU
89
+ "s4eu_title": "🇪🇺 This Isn’t Just a US Problem",
90
+ "s4eu_head": "AI for “Risky Offenders” Is Already in Europe",
91
+ "s4eu_intro": "The COMPAS story is not just an American warning. Across Europe, public authorities have experimented with <strong>very similar tools</strong> that aim to predict who will reoffend or which areas are “high risk”.",
92
+ "s4eu_li1_title": "United Kingdom – HART (Harm Assessment Risk Tool)",
93
+ "s4eu_li1_body": "A machine-learning model used by Durham Police to predict who will reoffend within two years. It uses variables like age, gender, <em>postcode</em>, housing and job instability – socio-economic proxies that can reproduce the same kinds of biased patterns exposed in COMPAS.",
94
+ "s4eu_li2_title": "Spain – VioGén",
95
+ "s4eu_li2_body": "A risk tool for gender-violence cases whose inner workings are largely a <em>\"black box\"</em>. Officers rely heavily on its scores to decide protection measures, even though the algorithm cannot easily be audited for bias or errors.",
96
+ "s4eu_li3_title": "Netherlands & Denmark – Predictive profiling",
97
+ "s4eu_li3_body": "Systems like the Dutch <em>Crime Anticipation System (CAS)</em> and Denmark’s algorithmic <em>“ghetto”</em> classifications use demographic and socio-economic data to steer policing and penalties, risking feedback loops that target certain communities again and again.",
98
+ "s4eu_box_title": "Ongoing European Debate",
99
+ "s4eu_box_body": "The Barcelona Prosecuter's office has proposed an \"electronic repeat-offense calculator\". Courts, regulators and researchers are actively examining how these tools affect fundamental rights such as non-discrimination, fair trial and data protection.",
100
+ "s4eu_note": "<strong>Key point:</strong> The risks you saw with COMPAS are not far away in another country. <strong class='emph-key'>They are live questions in both Europe and the U.S. right now.</strong>",
101
+ "btn_back_invest": "◀️ Back to the Investigation",
102
+ "btn_zoom": "Zoom Out to the Lesson ▶️",
103
+ # Step 4 Lesson
104
+ "s4_title": "💡 The Critical Lesson",
105
+ "s4_box_title": "Why This Matters:",
106
+ "s4_li1_title": "Overall accuracy can hide group-specific harm",
107
+ "s4_li1_body": "A model might be 70% accurate overall — but the remaining 30% of errors can fall disproportionately on <span class='emph-harm'>specific groups</span>, resulting in real harm even when the total accuracy appears “good”.",
108
+ "s4_li2_title": "Historical bias in training data gets amplified",
109
+ "s4_li2_body": "If past policing or judicial decisions were biased, the AI system will <span class='emph-harm'>learn and reinforce</span> those inequities — often making them worse at scale.",
110
+ "s4_li3_title": "Real people's lives are affected",
111
+ "s4_li3_body": "Each <strong class='emph-harm'>\"false positive\"</strong> represents a person who may lose years of freedom, employment, housing, or family connection — all due to a single <strong class='emph-harm'>biased prediction</strong>.",
112
+ "btn_back_eu": "◀️ Back",
113
+ "btn_what_do": "What Can We Do? ▶️",
114
+ # Step 5 Path
115
+ "s5_title": "🛤️ The Path Forward",
116
+ "s5_head": "From Accuracy to Ethics",
117
+ "s5_intro": "You've now seen both sides of the AI story:",
118
+ "s5_li1": "✅ You built models that achieved higher accuracy scores",
119
+ "s5_li2": "⚠️ You learned how similar models caused real-world harm",
120
+ "s5_li3": "🤔 You understand that accuracy alone is not enough",
121
+ "s5_box_title": "What You'll Do Next:",
122
+ "s5_p1": "In the next section, you'll be introduced to a <strong class='emph-key'>new way of measuring success</strong>—one that balances performance with fairness and ethics.",
123
+ "s5_p2": "You'll learn techniques to <strong class='emph-key'>detect bias</strong> in your models, <strong class='emph-key'>measure fairness</strong> across different groups, and <strong class='emph-key'>redesign your AI</strong> to minimize harm.",
124
+ "s5_mission": "🎯 Your new mission: Build AI that is not just accurate, but also <strong class='emph-key'>fair, equitable, and ethically sound</strong>.",
125
+ "s5_scroll": "👇 SCROLL DOWN 👇",
126
+ "s5_continue": "Continue to the next section below to begin your ethical AI journey.",
127
+ "btn_review": "◀️ Review the Investigation"
128
+ },
129
+ "es": {
130
+ "title": "🚀 La Revelación Ética: Impacto Real",
131
+ "loading_personal": "⏳ Cargando tu experiencia personalizada...",
132
+ "stats_title": "🏆 ¡Gran Trabajo, Ingeniero! 🏆",
133
+ "stats_subtitle": "Aquí está tu resumen de rendimiento.",
134
+ "stats_heading": "Tus Estadísticas",
135
+ "lbl_accuracy": "Mejor Precisión",
136
+ "lbl_rank": "Tu Rango",
137
+ "lbl_team": "Equipo",
138
+ "stats_footer": "¿Listo para compartir tu modelo y explorar su impacto en el mundo real?",
139
+ "btn_deploy": "🌍 Compartir tu Modelo de IA (Simulación)",
140
+ "guest_title": "🚀 ¡Has Iniciado Sesión!",
141
+ "guest_subtitle": "Aún no has enviado un modelo, pero estás listo para seguir aprendiendo.",
142
+ "guest_body": "Una vez que envíes un modelo en el Juego de Construcción de Modelos, tu precisión y clasificación aparecerán aquí.",
143
+ "guest_footer": "Continúa a la siguiente sección cuando estés listo.",
144
+ "loading_session": "🔒 Cargando tu sesión...",
145
+ "s2_title": "⚠️ Pero Espera...",
146
+ "s2_intro": "Antes de compartir el modelo, hay algo que necesitas saber...",
147
+ "s2_box_title": "Una Historia del Mundo Real",
148
+ "s2_p1": "Un modelo similar al tuyo se utilizó realmente en el mundo real. Fue usado por jueces en todo Estados Unidos para ayudar a tomar decisiones sobre el futuro de los acusados.",
149
+ "s2_p2": "Como el tuyo, tenía puntuaciones de precisión impresionantes. Como el tuyo, se construyó con datos de casos criminales pasados. Como el tuyo, su objetivo era predecir quién reincidiría.",
150
+ "s2_p3": "Pero algo estaba terriblemente mal...",
151
+ "btn_back": "◀️ Atrás",
152
+ "btn_reveal": "Revelar la Verdad ▶️",
153
+ "s3_title": "📰 La Investigación de ProPublica",
154
+ "s3_head": "\"Sesgo de Máquina\" - Una Investigación Histórica",
155
+ "s3_p1": "En 2016, periodistas de <strong>ProPublica</strong> investigaron un algoritmo de evaluación de riesgo criminal ampliamente utilizado llamado <strong>COMPAS</strong>. Analizaron más de <strong>7,000 casos reales</strong> para ver si las predicciones de la IA se cumplían.",
156
+ "s3_box_title": "Sus Hallazgos Impactantes:",
157
+ "s3_alert": "⚠️ Los acusados negros fueron etiquetados como \"alto riesgo\" casi el <u>DOBLE</u> que los acusados blancos.",
158
+ "s3_spec": "<strong>Específicamente:</strong>",
159
+ "s3_li1_pre": "Los <span class='emph-danger'>acusados negros</span> que <em>NO reincidieron</em> fueron etiquetados incorrectamente como <strong>\"alto riesgo\"</strong> a una tasa del",
160
+ "s3_li2_pre": "Los <strong>acusados blancos</strong> que <em>NO reincidieron</em> fueron etiquetados incorrectamente como <strong>\"alto riesgo\"</strong> a una tasa de solo el <strong>24%</strong>",
161
+ "s3_li3": "Mientras tanto, los <strong>acusados blancos</strong> que <em>SÍ reincidieron</em> tenían <strong>más probabilidades de ser etiquetados como \"bajo riesgo\"</strong> en comparación con los acusados negros",
162
+ "s3_box2_title": "¿Qué Significa Esto?",
163
+ "s3_mean_p1": "El sistema de IA estaba <strong class='emph-danger'>sistemáticamente sesgado</strong>. No solo cometía errores aleatorios, cometía <strong>diferentes tipos de errores para diferentes grupos de personas</strong>.",
164
+ "s3_mean_p2": "Los acusados negros enfrentaban un riesgo mucho mayor de ser <strong class='emph-danger'>injustamente etiquetados como peligrosos</strong>, lo que potencialmente conducía a sentencias de prisión más largas o libertad condicional denegada, incluso cuando no habrían reincidido.",
165
+ "btn_eu": "Ver Esto en Europa ▶️",
166
+ "lbl_black": "Negros",
167
+ "s4eu_title": "🇪🇺 Esto No Es Solo un Problema de EE. UU.",
168
+ "s4eu_head": "La IA para \"Delincuentes de Riesgo\" Ya Está en Europa",
169
+ "s4eu_intro": "La historia de COMPAS no es solo una advertencia estadounidense. En toda Europa, las autoridades públicas han experimentado con <strong>herramientas muy similares</strong> que pretenden predecir quién reincidirá o qué áreas son de \"alto riesgo\".",
170
+ "s4eu_li1_title": "Reino Unido – HART (Harm Assessment Risk Tool)",
171
+ "s4eu_li1_body": "Un modelo de aprendizaje automático utilizado por la Policía de Durham para predecir quién reincidirá en dos años. Utiliza variables como edad, género, <em>código postal</em>, vivienda e inestabilidad laboral: indicadores socioeconómicos que pueden reproducir los mismos tipos de patrones sesgados expuestos en COMPAS.",
172
+ "s4eu_li2_title": "España – VioGén",
173
+ "s4eu_li2_body": "Una herramienta de riesgo para casos de violencia de género cuyo funcionamiento interno es en gran medida una <em>\"caja negra\"</em>. Los oficiales dependen en gran medida de sus puntuaciones para decidir medidas de protección, aunque el algoritmo no puede ser auditado fácilmente en busca de sesgos o errores.",
174
+ "s4eu_li3_title": "Países Bajos y Dinamarca – Perfiles predictivos",
175
+ "s4eu_li3_body": "Sistemas como el <em>Sistema de Anticipación del Crimen (CAS)</em> holandés y las clasificaciones algorítmicas de <em>\"guetos\"</em> de Dinamarca utilizan datos demográficos y socioeconómicos para dirigir la vigilancia y las sanciones, arriesgando bucles de retroalimentación que apuntan a ciertas comunidades una y otra vez.",
176
+ "s4eu_box_title": "Debate Europeo en Curso",
177
+ "s4eu_box_body": "La Fiscalía de Barcelona ha propuesto una \"calculadora electrónica de reincidencia\". Tribunales, reguladores e investigadores están examinando activamente cómo estas herramientas afectan los derechos fundamentales como la no discriminación, el juicio justo y la protección de datos.",
178
+ "s4eu_note": "<strong>Punto clave:</strong> Los riesgos que viste con COMPAS no están lejos en otro país. <strong class='emph-key'>Son preguntas vivas tanto en Europa como en los EE. UU. en este momento.</strong>",
179
+ "btn_back_invest": "◀️ Volver a la Investigación",
180
+ "btn_zoom": "Alejarse a la Lección ▶️",
181
+ "s4_title": "💡 La Lección Crítica",
182
+ "s4_box_title": "Por Qué Importa Esto:",
183
+ "s4_li1_title": "La precisión general puede ocultar daños específicos a grupos",
184
+ "s4_li1_body": "Un modelo puede tener un 70% de precisión general, pero el 30% restante de errores puede recaer desproporcionadamente en <span class='emph-harm'>grupos específicos</span>, resultando en un daño real incluso cuando la precisión total parece \"buena\".",
185
+ "s4_li2_title": "El sesgo histórico en los datos de entrenamiento se amplifica",
186
+ "s4_li2_body": "Si las decisiones policiales o judiciales pasadas fueron sesgadas, el sistema de IA <span class='emph-harm'>aprenderá y reforzará</span> esas desigualdades, a menudo empeorándolas a escala.",
187
+ "s4_li3_title": "Las vidas de personas reales se ven afectadas",
188
+ "s4_li3_body": "Cada <strong class='emph-harm'>\"falso positivo\"</strong> representa a una persona que puede perder años de libertad, empleo, vivienda o conexión familiar, todo debido a una sola <strong class='emph-harm'>predicción sesgada</strong>.",
189
+ "btn_back_eu": "◀️ Atrás",
190
+ "btn_what_do": "¿Qué Podemos Hacer? ▶️",
191
+ "s5_title": "🛤️ El Camino a Seguir",
192
+ "s5_head": "De la Precisión a la Ética",
193
+ "s5_intro": "Ahora has visto ambos lados de la historia de la IA:",
194
+ "s5_li1": "✅ Construiste modelos que lograron puntuaciones de precisión más altas",
195
+ "s5_li2": "⚠️ Aprendiste cómo modelos similares causaron daño en el mundo real",
196
+ "s5_li3": "🤔 Entiendes que la precisión por sí sola no es suficiente",
197
+ "s5_box_title": "Lo Que Harás A Continuación:",
198
+ "s5_p1": "En la siguiente sección, se te presentará una <strong class='emph-key'>nueva forma de medir el éxito</strong>, una que equilibra el rendimiento con la equidad y la ética.",
199
+ "s5_p2": "Aprenderás técnicas para <strong class='emph-key'>detectar sesgos</strong> en tus modelos, <strong class='emph-key'>medir la equidad</strong> en diferentes grupos y <strong class='emph-key'>rediseñar tu IA</strong> para minimizar el daño.",
200
+ "s5_mission": "🎯 Tu nueva misión: Construir IA que no solo sea precisa, sino también <strong class='emph-key'>justa, equitativa y éticamente sólida</strong>.",
201
+ "s5_scroll": "👇 DESPLÁZATE HACIA ABAJO 👇",
202
+ "s5_continue": "Continúa en la siguiente sección abajo para comenzar tu viaje de IA ética.",
203
+ "btn_review": "◀️ Revisar la Investigación"
204
+ },
205
+ "ca": {
206
+ "title": "🚀 La Revelació Ètica: Impacte Real",
207
+ "loading_personal": "⏳ Carregant la teva experiència personalitzada...",
208
+ "stats_title": "🏆 Bona Feina, Enginyer! 🏆",
209
+ "stats_subtitle": "Aquí tens el teu resum de rendiment.",
210
+ "stats_heading": "Les Teves Estadístiques",
211
+ "lbl_accuracy": "Millor Precisió",
212
+ "lbl_rank": "El Teu Rang",
213
+ "lbl_team": "Equip",
214
+ "stats_footer": "A punt per compartir el teu model i explorar el seu impacte al món real?",
215
+ "btn_deploy": "🌍 Compartir el Teu Model d'IA (Simulació)",
216
+ "guest_title": "🚀 Has Iniciat Sessió!",
217
+ "guest_subtitle": "Encara no has enviat un model, però estàs a punt per continuar aprenent.",
218
+ "guest_body": "Un cop enviïs un model al Joc de Construcció de Models, la teva precisió i classificació apareixeran aquí.",
219
+ "guest_footer": "Continua a la següent secció quan estiguis a punt.",
220
+ "loading_session": "🔒 Carregant la teva sessió...",
221
+ "s2_title": "⚠️ Però Espera...",
222
+ "s2_intro": "Abans de compartir el model, hi ha una cosa que necessites saber...",
223
+ "s2_box_title": "Una Història del Món Real",
224
+ "s2_p1": "Un model similar al teu es va utilitzar realment al món real. Va ser utilitzat per jutges a tot Estats Units per ajudar a prendre decisions sobre el futur dels acusats.",
225
+ "s2_p2": "Com el teu, tenia puntuacions de precisió impressionants. Com el teu, es va construir amb dades de casos criminals passats. Com el teu, el seu objectiu era predir qui reincidiria.",
226
+ "s2_p3": "Però alguna cosa estava terriblement malament...",
227
+ "btn_back": "◀️ Enrere",
228
+ "btn_reveal": "Revelar la Veritat ▶️",
229
+ "s3_title": "📰 La Investigació de ProPublica",
230
+ "s3_head": "\"Biaix de Màquina\" - Una Investigació Històrica",
231
+ "s3_p1": "El 2016, periodistes de <strong>ProPublica</strong> van investigar un algorisme d'avaluació de risc criminal àmpliament utilitzat anomenat <strong>COMPAS</strong>. Van analitzar més de <strong>7.000 casos reals</strong> per veure si les prediccions de la IA es complien.",
232
+ "s3_box_title": "Les Seves Troballes Impactants:",
233
+ "s3_alert": "⚠️ Els acusats negres van ser etiquetats com a \"alt risc\" gairebé el <u>DOBLE</u> que els acusats blancs.",
234
+ "s3_spec": "<strong>Específicament:</strong>",
235
+ "s3_li1_pre": "Els <span class='emph-danger'>acusats negres</span> que <em>NO van reincidir</em> van ser etiquetats incorrectament com a <strong>\"alt risc\"</strong> a una taxa del",
236
+ "s3_li2_pre": "Els <strong>acusats blancs</strong> que <em>NO van reincidir</em> van ser etiquetats incorrectament com a <strong>\"alt risc\"</strong> a una taxa de només el <strong>24%</strong>",
237
+ "s3_li3": "Mentrestant, els <strong>acusats blancs</strong> que <em>SÍ van reincidir</em> tenien <strong>més probabilitats de ser etiquetats com a \"baix risc\"</strong> en comparació amb els acusats negres",
238
+ "s3_box2_title": "Què Significa Això?",
239
+ "s3_mean_p1": "El sistema d'IA estava <strong class='emph-danger'>sistemàticament esbiaixat</strong>. No només cometia errors aleatoris, cometia <strong>diferents tipus d'errors per a diferents grups de persones</strong>.",
240
+ "s3_mean_p2": "Els acusats negres s'enfrontaven a un risc molt més gran de ser <strong class='emph-danger'>injustament etiquetats com a perillosos</strong>, la qual cosa potencialment conduïa a sentències de presó més llargues o llibertat condicional denegada, fins i tot quan no haurien reincidit.",
241
+ "btn_eu": "Veure Això a Europa ▶️",
242
+ "lbl_black": "Negres",
243
+ "s4eu_title": "🇪🇺 Això No És Només un Problema dels EUA",
244
+ "s4eu_head": "La IA per a \"Delinqüents de Risc\" Ja És a Europa",
245
+ "s4eu_intro": "La història de COMPAS no és només una advertència nord-americana. A tota Europa, les autoritats públiques han experimentat amb <strong>eines molt similars</strong> que pretenen predir qui reincidirà o quines àrees són d'\"alt risc\".",
246
+ "s4eu_li1_title": "Regne Unit – HART (Harm Assessment Risk Tool)",
247
+ "s4eu_li1_body": "Un model d'aprenentatge automàtic utilitzat per la Policia de Durham per predir qui reincidirà en dos anys. Utilitza variables com edat, gènere, <em>codi postal</em>, habitatge i inestabilitat laboral: indicadors socioeconòmics que poden reproduir els mateixos tipus de patrons esbiaixats exposats a COMPAS.",
248
+ "s4eu_li2_title": "Espanya – VioGén",
249
+ "s4eu_li2_body": "Una eina de risc per a casos de violència de gènere el funcionament intern de la qual és en gran part una <em>\"caixa negra\"</em>. Els oficials depenen en gran mesura de les seves puntuacions per decidir mesures de protecció, tot i que l'algorisme no pot ser auditat fàcilment a la recerca de biaixos o errors.",
250
+ "s4eu_li3_title": "Països Baixos i Dinamarca – Perfils predictius",
251
+ "s4eu_li3_body": "Sistemes com el <em>Sistema d'Anticipació del Crim (CAS)</em> holandès i les classificacions algorísmiques de <em>\"guetos\"</em> de Dinamarca utilitzen dades demogràfiques i socioeconòmiques per dirigir la vigilància i les sancions, arriscant bucles de retroalimentació que apunten a certes comunitats una i altra vegada.",
252
+ "s4eu_box_title": "Debat Europeu en Curs",
253
+ "s4eu_box_body": "La Fiscalia de Barcelona ha proposat una \"calculadora electrònica de reincidència\". Tribunals, reguladors i investigadors estan examinant activament com aquestes eines afecten els drets fonamentals com la no discriminació, el judici just i la protecció de dades.",
254
+ "s4eu_note": "<strong>Punt clau:</strong> Els riscos que vas veure amb COMPAS no són lluny en un altre país. <strong class='emph-key'>Són preguntes vives tant a Europa com als EUA en aquest moment.</strong>",
255
+ "btn_back_invest": "◀️ Tornar a la Investigació",
256
+ "btn_zoom": "Allunyar-se a la Lliçó ▶️",
257
+ "s4_title": "💡 La Lliçó Crítica",
258
+ "s4_box_title": "Per Què Importa Això:",
259
+ "s4_li1_title": "La precisió general pot amagar danys específics a grups",
260
+ "s4_li1_body": "Un model pot tenir un 70% de precisió general, però el 30% restant d'errors pot recaure desproporcionadament en <span class='emph-harm'>grups específics</span>, resultant en un dany real fins i tot quan la precisió total sembla \"bona\".",
261
+ "s4_li2_title": "El biaix històric en les dades d'entrenament s'amplifica",
262
+ "s4_li2_body": "Si les decisions policials o judicials passades van ser esbiaixades, el sistema d'IA <span class='emph-harm'>aprendrà i reforçarà</span> aquestes desigualtats, sovint empitjorant-les a escala.",
263
+ "s4_li3_title": "Les vides de persones reals es veuen afectades",
264
+ "s4_li3_body": "Cada <strong class='emph-harm'>\"fals positiu\"</strong> representa una persona que pot perdre anys de llibertat, feina, habitatge o connexió familiar, tot a causa d'una sola <strong class='emph-harm'>predicció esbiaixada</strong>.",
265
+ "btn_back_eu": "◀️ Enrere",
266
+ "btn_what_do": "Què Podem Fer? ▶️",
267
+ "s5_title": "🛤️ El Camí a Seguir",
268
+ "s5_head": "De la Precisió a l'Ètica",
269
+ "s5_intro": "Ara has vist els dos costats de la història de la IA:",
270
+ "s5_li1": "✅ Vas construir models que van aconseguir puntuacions de precisió més altes",
271
+ "s5_li2": "⚠️ Vas aprendre com models similars van causar dany al món real",
272
+ "s5_li3": "🤔 Entens que la precisió per si sola no és suficient",
273
+ "s5_box_title": "El Que Faràs A Continuació:",
274
+ "s5_p1": "En la següent secció, se't presentarà una <strong class='emph-key'>nova manera de mesurar l'èxit</strong>, una que equilibra el rendiment amb l'equitat i l'ètica.",
275
+ "s5_p2": "Aprendràs tècniques per <strong class='emph-key'>detectar biaixos</strong> en els teus models, <strong class='emph-key'>mesurar l'equitat</strong> en diferents grups i <strong class='emph-key'>redissenyar la teva IA</strong> per minimitzar el dany.",
276
+ "s5_mission": "🎯 La teva nova missió: Construir IA que no només sigui precisa, sinó també <strong class='emph-key'>justa, equitativa i èticament sòlida</strong>.",
277
+ "s5_scroll": "👇 DESPLAÇA'T CAP AVALL 👇",
278
+ "s5_continue": "Continua a la següent secció a sota per començar el teu viatge d'IA ètica.",
279
+ "btn_review": "◀️ Revisar la Investigació"
280
+ }
281
+ }
282
+
283
+ # ---------------------------------------------------------------------------
284
+ # Logic / Helpers
285
+ # ---------------------------------------------------------------------------
286
+
287
+ def _log(msg: str):
288
+ if DEBUG_LOG:
289
+ print(f"[MoralCompassApp] {msg}")
290
+
291
+ def _normalize_team_name(name: str) -> str:
292
+ if not name:
293
+ return ""
294
+ return " ".join(str(name).strip().split())
295
+
296
+ def _fetch_leaderboard(token: str) -> Optional[pd.DataFrame]:
297
+ now = time.time()
298
+ with _cache_lock:
299
+ if (
300
+ _leaderboard_cache["data"] is not None
301
+ and now - _leaderboard_cache["timestamp"] < LEADERBOARD_CACHE_SECONDS
302
+ ):
303
+ return _leaderboard_cache["data"]
304
+
305
+ try:
306
+ playground_id = "https://cf3wdpkg0d.execute-api.us-east-1.amazonaws.com/prod/m"
307
+ playground = Competition(playground_id)
308
+ df = playground.get_leaderboard(token=token)
309
+ if df is not None and not df.empty and MAX_LEADERBOARD_ENTRIES:
310
+ df = df.head(MAX_LEADERBOARD_ENTRIES)
311
+ except Exception as e:
312
+ _log(f"Leaderboard fetch failed: {e}")
313
+ df = None
314
+
315
+ with _cache_lock:
316
+ _leaderboard_cache["data"] = df
317
+ _leaderboard_cache["timestamp"] = time.time()
318
+ return df
319
+
320
+ def _get_or_assign_team(username: str, leaderboard_df: Optional[pd.DataFrame]) -> Tuple[str, bool]:
321
+ try:
322
+ if leaderboard_df is not None and not leaderboard_df.empty and "Team" in leaderboard_df.columns:
323
+ user_submissions = leaderboard_df[leaderboard_df["username"] == username]
324
+ if not user_submissions.empty:
325
+ if "timestamp" in user_submissions.columns:
326
+ try:
327
+ user_submissions = user_submissions.copy()
328
+ user_submissions["timestamp"] = pd.to_datetime(
329
+ user_submissions["timestamp"], errors="coerce"
330
+ )
331
+ user_submissions = user_submissions.sort_values("timestamp", ascending=False)
332
+ except Exception as ts_err:
333
+ _log(f"Timestamp sort error: {ts_err}")
334
+ existing_team = user_submissions.iloc[0]["Team"]
335
+ if pd.notna(existing_team) and str(existing_team).strip():
336
+ return _normalize_team_name(existing_team), False
337
+ return _normalize_team_name(random.choice(TEAM_NAMES)), True
338
+ except Exception as e:
339
+ _log(f"Team assignment error: {e}")
340
+ return _normalize_team_name(random.choice(TEAM_NAMES)), True
341
+
342
+ def _try_session_based_auth(request: "gr.Request") -> Tuple[bool, Optional[str], Optional[str]]:
343
+ try:
344
+ session_id = request.query_params.get("sessionid") if request else None
345
+ if not session_id:
346
+ return False, None, None
347
+ token = get_token_from_session(session_id)
348
+ if not token:
349
+ return False, None, None
350
+ username = _get_username_from_token(token)
351
+ if not username:
352
+ return False, None, None
353
+ return True, username, token
354
+ except Exception as e:
355
+ _log(f"Session auth failed: {e}")
356
+ return False, None, None
357
+
358
+ def _compute_user_stats(username: str, token: str) -> Dict[str, Any]:
359
+ now = time.time()
360
+ cached = _user_stats_cache.get(username)
361
+ if cached and (now - cached.get("_ts", 0) < USER_STATS_TTL):
362
+ return cached
363
+
364
+ leaderboard_df = _fetch_leaderboard(token)
365
+ team_name, _ = _get_or_assign_team(username, leaderboard_df)
366
+ best_score = None
367
+ rank = None
368
+ team_rank = None
369
+
370
+ try:
371
+ if leaderboard_df is not None and not leaderboard_df.empty:
372
+ if "accuracy" in leaderboard_df.columns and "username" in leaderboard_df.columns:
373
+ user_submissions = leaderboard_df[leaderboard_df["username"] == username]
374
+ if not user_submissions.empty:
375
+ best_score = user_submissions["accuracy"].max()
376
+
377
+ # Individual rank
378
+ user_bests = leaderboard_df.groupby("username")["accuracy"].max()
379
+ summary_df = user_bests.reset_index()
380
+ summary_df.columns = ["Engineer", "Best_Score"]
381
+ summary_df = summary_df.sort_values("Best_Score", ascending=False).reset_index(drop=True)
382
+ summary_df.index = summary_df.index + 1
383
+ my_row = summary_df[summary_df["Engineer"] == username]
384
+ if not my_row.empty:
385
+ rank = my_row.index[0]
386
+
387
+ # Team rank
388
+ if "Team" in leaderboard_df.columns and team_name:
389
+ team_summary_df = (
390
+ leaderboard_df.groupby("Team")["accuracy"]
391
+ .agg(Best_Score="max")
392
+ .reset_index()
393
+ .sort_values("Best_Score", ascending=False)
394
+ .reset_index(drop=True)
395
+ )
396
+ team_summary_df.index = team_summary_df.index + 1
397
+ my_team_row = team_summary_df[team_summary_df["Team"] == team_name]
398
+ if not my_team_row.empty:
399
+ team_rank = my_team_row.index[0]
400
+ except Exception as e:
401
+ _log(f"User stats error for {username}: {e}")
402
+
403
+ stats = {
404
+ "username": username,
405
+ "best_score": best_score,
406
+ "rank": rank,
407
+ "team_name": team_name,
408
+ "team_rank": team_rank,
409
+ "is_signed_in": True,
410
+ "_ts": now
411
+ }
412
+ _user_stats_cache[username] = stats
413
+ return stats
414
+
415
+ # ---------------------------------------------------------------------------
416
+ # HTML Helpers (I18N)
417
+ # ---------------------------------------------------------------------------
418
+
419
+ def t(lang, key):
420
+ return TRANSLATIONS.get(lang, TRANSLATIONS["en"]).get(key, key)
421
+
422
+ def build_stats_html(user_stats: Dict[str, Any], lang="en") -> str:
423
+ if user_stats.get("best_score") is not None:
424
+ best_score_pct = f"{(user_stats['best_score'] * 100):.1f}%"
425
+ rank_text = f"#{user_stats['rank']}" if user_stats['rank'] else "N/A"
426
+ team_text = user_stats['team_name'] if user_stats['team_name'] else "N/A"
427
+ return f"""
428
+ <div class='slide-shell slide-shell--primary'>
429
+ <div style='text-align:center;'>
430
+ <h2 class='slide-shell__title'>
431
+ {t(lang, 'stats_title')}
432
+ </h2>
433
+ <p class='slide-shell__subtitle'>
434
+ {t(lang, 'stats_subtitle')}
435
+ </p>
436
+
437
+ <div class='content-box'>
438
+ <h3 class='content-box__heading'>{t(lang, 'stats_heading')}</h3>
439
+
440
+ <div class='stat-grid'>
441
+ <div class='stat-card'>
442
+ <p class='stat-card__label'>{t(lang, 'lbl_accuracy')}</p>
443
+ <p class='stat-card__value'>
444
+ {best_score_pct}
445
+ </p>
446
+ </div>
447
+
448
+ <div class='stat-card'>
449
+ <p class='stat-card__label'>{t(lang, 'lbl_rank')}</p>
450
+ <p class='stat-card__value'>
451
+ {rank_text}
452
+ </p>
453
+ </div>
454
+ </div>
455
+
456
+ <div class='team-card'>
457
+ <p class='team-card__label'>{t(lang, 'lbl_team')}</p>
458
+ <p class='team-card__value'>
459
+ 🛡️ {team_text}
460
+ </p>
461
+ </div>
462
+ </div>
463
+
464
+ <p class='slide-shell__subtitle' style='font-weight:500;'>
465
+ {t(lang, 'stats_footer')}
466
+ </p>
467
+ </div>
468
+ </div>
469
+ """
470
+ else:
471
+ # Authenticated but no submission
472
+ return f"""
473
+ <div class='slide-shell slide-shell--primary'>
474
+ <div style='text-align:center;'>
475
+ <h2 class='slide-shell__title'>
476
+ {t(lang, 'guest_title')}
477
+ </h2>
478
+ <p class='slide-shell__subtitle'>
479
+ {t(lang, 'guest_subtitle')}
480
+ </p>
481
+
482
+ <div class='content-box'>
483
+ <p style='margin:0;'>
484
+ {t(lang, 'guest_body')}
485
+ </p>
486
+ </div>
487
+
488
+ <p class='slide-shell__subtitle' style='font-weight:500;'>
489
+ {t(lang, 'guest_footer')}
490
+ </p>
491
+ </div>
492
+ </div>
493
+ """
494
+
495
+ def _get_step2_html(lang):
496
+ return f"""
497
+ <div class='slide-shell slide-shell--warning'>
498
+ <p class='large-text' style='text-align:center; font-weight:600; margin:0;'>
499
+ {t(lang, 's2_intro')}
500
+ </p>
501
+
502
+ <div class='content-box'>
503
+ <h3 class='content-box__heading'>{t(lang, 's2_box_title')}</h3>
504
+ <p class='slide-warning-body'>
505
+ {t(lang, 's2_p1')}
506
+ </p>
507
+ <p class='slide-warning-body' style='margin-top:16px;'>
508
+ {t(lang, 's2_p2')}
509
+ </p>
510
+ <p class='slide-warning-body' style='margin-top:16px; font-weight:600;'>
511
+ {t(lang, 's2_p3')}
512
+ </p>
513
+ </div>
514
+ </div>
515
+ """
516
+
517
+ def _get_step3_html(lang):
518
+ return f"""
519
+ <div class='revelation-box'>
520
+ <h3 style='margin-top:0; font-size:1.8rem;'>
521
+ {t(lang, 's3_head')}
522
+ </h3>
523
+ <p style='font-size:1.1rem; line-height:1.6;'>
524
+ {t(lang, 's3_p1')}
525
+ </p>
526
+ <div class='content-box content-box--emphasis'>
527
+ <h4 class='content-box__heading'>{t(lang, 's3_box_title')}</h4>
528
+ <div class='bg-danger-soft' style='margin:20px 0;'>
529
+ <p class='emph-danger' style='font-size:1.15rem; margin:0;'>
530
+ {t(lang, 's3_alert')}
531
+ </p>
532
+ </div>
533
+ <p style='font-size:1.05rem; margin-top:20px;'>
534
+ {t(lang, 's3_spec')}
535
+ </p>
536
+ <ul style='font-size:1.05rem; line-height:1.8;'>
537
+ <li>
538
+ {t(lang, 's3_li1_pre')}
539
+ <span class='emph-danger'> 45%</span>
540
+ </li>
541
+ <li>
542
+ {t(lang, 's3_li2_pre')}
543
+ </li>
544
+ <li style='margin-top:12px;'>
545
+ {t(lang, 's3_li3')}
546
+ </li>
547
+ </ul>
548
+ </div>
549
+
550
+ <div class='content-box content-box--emphasis'>
551
+ <h4 class='content-box__heading'>{t(lang, 's3_box2_title')}</h4>
552
+ <p style='font-size:1.05rem; margin:0; line-height:1.6;'>
553
+ {t(lang, 's3_mean_p1')}
554
+ </p>
555
+ <p style='font-size:1.05rem; margin-top:12px; line-height:1.6;'>
556
+ {t(lang, 's3_mean_p2')}
557
+ </p>
558
+ </div>
559
+ </div>
560
+ """
561
+
562
+ def _get_step4_eu_html(lang):
563
+ return f"""
564
+ <div class='eu-panel'>
565
+ <h3 class='emph-eu' style='font-size:1.9rem; text-align:center;'>
566
+ {t(lang, 's4eu_head')}
567
+ </h3>
568
+ <p style='line-height:1.8;'>
569
+ {t(lang, 's4eu_intro')}
570
+ </p>
571
+ <ul style='line-height:1.9; font-size:1.05rem; margin:20px 0;'>
572
+ <li>
573
+ <strong class='emph-eu'>{t(lang, 's4eu_li1_title')}</strong><br>
574
+ {t(lang, 's4eu_li1_body')}
575
+ </li>
576
+ <li style='margin-top:14px;'>
577
+ <strong class='emph-eu'>{t(lang, 's4eu_li2_title')}</strong><br>
578
+ {t(lang, 's4eu_li2_body')}
579
+ </li>
580
+ <li style='margin-top:14px;'>
581
+ <strong class='emph-eu'>{t(lang, 's4eu_li3_title')}</strong><br>
582
+ {t(lang, 's4eu_li3_body')}
583
+ </li>
584
+ </ul>
585
+ <div class='bg-eu-soft eu-panel__highlight'>
586
+ <h4 class='emph-eu'>{t(lang, 's4eu_box_title')}</h4>
587
+ <p style='margin:0; line-height:1.7; font-size:1.05rem;'>
588
+ {t(lang, 's4eu_box_body')}
589
+ </p>
590
+ </div>
591
+ <div class='eu-panel__note'>
592
+ <p style='margin:0; line-height:1.8; font-size:1.1rem;'>
593
+ {t(lang, 's4eu_note')}
594
+ </p>
595
+ </div>
596
+ </div>
597
+ """
598
+
599
+ def _get_step4_lesson_html(lang):
600
+ return f"""
601
+ <div class='content-box'>
602
+ <h4 class='content-box__heading emph-key' style='font-size:1.5rem;'>
603
+ {t(lang, 's4_box_title')}
604
+ </h4>
605
+ <div class='lesson-emphasis-box'>
606
+ <span class='lesson-item-title'>
607
+ <span class='lesson-badge'>1</span>
608
+ {t(lang, 's4_li1_title')}
609
+ </span>
610
+ <p class='slide-teaching-body'>
611
+ {t(lang, 's4_li1_body')}
612
+ </p>
613
+ </div>
614
+ <div class='lesson-emphasis-box'>
615
+ <span class='lesson-item-title'>
616
+ <span class='lesson-badge'>2</span>
617
+ {t(lang, 's4_li2_title')}
618
+ </span>
619
+ <p class='slide-teaching-body'>
620
+ {t(lang, 's4_li2_body')}
621
+ </p>
622
+ </div>
623
+ <div class='lesson-emphasis-box'>
624
+ <span class='lesson-item-title'>
625
+ <span class='lesson-badge'>3</span>
626
+ {t(lang, 's4_li3_title')}
627
+ </span>
628
+ <p class='slide-teaching-body'>
629
+ {t(lang, 's4_li3_body')}
630
+ </p>
631
+ </div>
632
+ </div>
633
+ """
634
+
635
+ def _get_step5_html(lang):
636
+ return f"""
637
+ <div style='text-align:center;'>
638
+ <div class='slide-shell slide-shell--info'>
639
+ <h3 class='slide-shell__title'>
640
+ {t(lang, 's5_head')}
641
+ </h3>
642
+ <p style='line-height:1.8; text-align:left;'>
643
+ {t(lang, 's5_intro')}
644
+ </p>
645
+ <ul style='text-align:left; line-height:2; font-size:1.1rem; margin:24px 0;'>
646
+ <li>{t(lang, 's5_li1')}</li>
647
+ <li>{t(lang, 's5_li2')}</li>
648
+ <li>{t(lang, 's5_li3')}</li>
649
+ </ul>
650
+ <div class='content-box'>
651
+ <h4 class='content-box__heading'>{t(lang, 's5_box_title')}</h4>
652
+ <p style='font-size:1.1rem; line-height:1.8;'>
653
+ {t(lang, 's5_p1')}
654
+ </p>
655
+ <p style='font-size:1.1rem; line-height:1.8; margin-top:16px;'>
656
+ {t(lang, 's5_p2')}
657
+ </p>
658
+ </div>
659
+ <div class='content-box content-box--emphasis'>
660
+ <p style='font-size:1.15rem; font-weight:600; margin:0;'>
661
+ {t(lang, 's5_mission')}
662
+ </p>
663
+ </div>
664
+ <h1 style='margin:32px 0 16px 0; font-size: 3rem;'>{t(lang, 's5_scroll')}</h1>
665
+ <p style='font-size:1.2rem;'>{t(lang, 's5_continue')}</p>
666
+ </div>
667
+ </div>
668
+ """
669
+
670
+ # ---------------------------------------------------------------------------
671
+ # CSS
672
+ # ---------------------------------------------------------------------------
673
+ CSS = """
674
+ .large-text { font-size: 20px !important; }
675
+ .slide-shell, .celebration-box {
676
+ padding:24px; border-radius:16px;
677
+ background-color: var(--block-background-fill);
678
+ color: var(--body-text-color);
679
+ border:2px solid var(--border-color-primary);
680
+ max-width:900px; margin:auto;
681
+ }
682
+ .slide-shell--primary, .slide-shell--warning, .slide-shell--info { border-color: var(--color-accent); }
683
+ .slide-shell__title { font-size:2.3rem; margin:0; text-align:center; }
684
+ .slide-shell__subtitle { font-size:1.2rem; margin-top:16px; text-align:center; color: var(--secondary-text-color); }
685
+ .stat-grid { display:grid; grid-template-columns:1fr 1fr; gap:16px; margin-top:16px; }
686
+ .stat-card, .team-card { text-align:center; padding:16px; border-radius:8px; border:1px solid var(--border-color-primary); background-color: var(--block-background-fill); }
687
+ .stat-card__label, .team-card__label { margin:0; font-size:0.9rem; color: var(--secondary-text-color); }
688
+ .stat-card__value { margin:4px 0 0 0; font-size:1.8rem; font-weight:700; }
689
+ .team-card__value { margin:4px 0 0 0; font-size:1.3rem; font-weight:600; }
690
+ .content-box { background-color: var(--block-background-fill); border-radius:12px; border:1px solid var(--border-color-primary); padding:24px; margin:24px 0; }
691
+ .content-box--emphasis { border-left:6px solid var(--color-accent); }
692
+ .revelation-box { background-color: var(--block-background-fill); border-left:6px solid var(--color-accent); border-radius:8px; padding:24px; margin-top:24px; }
693
+ .eu-panel { font-size:20px; padding:32px; border-radius:16px; border:3px solid var(--border-color-primary); background-color: var(--block-background-fill); max-width:900px; margin:auto; }
694
+ .bg-danger-soft { background-color:#fee2e2; border-left:6px solid #dc2626; padding:16px; border-radius:8px; }
695
+ .emph-danger { color:#b91c1c; font-weight:700; }
696
+ .emph-key { color: var(--color-accent); font-weight:700; }
697
+ .lesson-emphasis-box { background-color: var(--block-background-fill); border-left:6px solid var(--color-accent); padding:18px 20px; border-radius:10px; margin-top:1.5rem; }
698
+ .lesson-item-title { font-size:1.35em; font-weight:700; margin-bottom:0.25rem; display:block; }
699
+ .lesson-badge { display:inline-block; background-color: var(--color-accent); color: var(--button-text-color); padding:6px 12px; border-radius:10px; font-weight:700; margin-right:10px; font-size:0.9em; }
700
+ .slide-warning-body, .slide-teaching-body { font-size:1.25em; line-height:1.75; }
701
+ #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 .3s ease; }
702
+ .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; }
703
+ @keyframes nav-spin { 0%{transform:rotate(0deg);} 100%{transform:rotate(360deg);} }
704
+ /* EU Panel Highlighting */
705
+ .bg-eu-soft { background-color: color-mix(in srgb, var(--color-accent) 15%, transparent); border-radius: 8px; padding: 16px; margin: 20px 0; }
706
+ .emph-eu { color: var(--color-accent); font-weight: 700; }
707
+ .emph-harm { color: #b91c1c; font-weight: 700; }
708
+ @media (prefers-color-scheme: dark) {
709
+ .bg-danger-soft { background-color: #450a0a; border-color: #dc2626; }
710
+ .emph-danger { color: #f87171; }
711
+ .emph-harm { color: #f87171; }
712
+ }
713
+ """
714
+
715
+ # ---------------------------------------------------------------------------
716
+ # App
717
+ # ---------------------------------------------------------------------------
718
+ def create_ethical_revelation_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
719
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue=theme_primary_hue), css=CSS) as demo:
720
+ gr.HTML("<div id='app_top_anchor' style='height:0;'></div>")
721
+ gr.HTML("""
722
+ <div id='nav-loading-overlay'>
723
+ <div class='nav-spinner'></div>
724
+ <span id='nav-loading-text'>Loading...</span>
725
+ </div>
726
+ """)
727
+
728
+ # Components needing dynamic updates
729
+ c_title = gr.Markdown("<h1 style='text-align:center;'>🚀 The Ethical Revelation: Real-World Impact</h1>")
730
+
731
+ # Loading placeholder
732
+ with gr.Column(visible=True, elem_id="initial-loading") as initial_loading:
733
+ c_loading_text = gr.Markdown("<div style='text-align:center; padding:80px 0;'><h2>⏳ Loading...</h2></div>")
734
+
735
+ # Steps
736
+ with gr.Column(visible=False, elem_id="step-1") as step_1:
737
+ stats_display = gr.HTML() # Content built dynamically
738
+ deploy_button = gr.Button(t('en', 'btn_deploy'), variant="primary", size="lg", scale=1)
739
+
740
+ with gr.Column(visible=False, elem_id="step-2") as step_2:
741
+ c_s2_title = gr.Markdown(f"<h2 style='text-align:center;'>{t('en', 's2_title')}</h2>")
742
+ c_s2_html = gr.HTML(_get_step2_html("en"))
743
+ with gr.Row():
744
+ step_2_back = gr.Button(t('en', 'btn_back'), size="lg")
745
+ step_2_next = gr.Button(t('en', 'btn_reveal'), variant="primary", size="lg")
746
+
747
+ with gr.Column(visible=False, elem_id="step-3") as step_3:
748
+ c_s3_title = gr.Markdown(f"<h2 style='text-align:center;'>{t('en', 's3_title')}</h2>")
749
+ c_s3_html = gr.HTML(_get_step3_html("en"))
750
+ with gr.Row():
751
+ step_3_back = gr.Button(t('en', 'btn_back'), size="lg")
752
+ step_3_next = gr.Button(t('en', 'btn_eu'), variant="primary", size="lg")
753
+
754
+ with gr.Column(visible=False, elem_id="step-4-eu") as step_4_eu:
755
+ c_s4eu_title = gr.Markdown(f"<h2 style='text-align:center;'>{t('en', 's4eu_title')}</h2>")
756
+ c_s4eu_html = gr.HTML(_get_step4_eu_html("en"))
757
+ with gr.Row():
758
+ step_4_eu_back = gr.Button(t('en', 'btn_back_invest'), size="lg")
759
+ step_4_eu_next = gr.Button(t('en', 'btn_zoom'), variant="primary", size="lg")
760
+
761
+ with gr.Column(visible=False, elem_id="step-4") as step_4:
762
+ c_s4_title = gr.Markdown(f"<h2 style='text-align:center;'>{t('en', 's4_title')}</h2>")
763
+ c_s4_html = gr.HTML(_get_step4_lesson_html("en"))
764
+ with gr.Row():
765
+ step_4_back = gr.Button(t('en', 'btn_back_eu'), size="lg")
766
+ step_4_next = gr.Button(t('en', 'btn_what_do'), variant="primary", size="lg")
767
+
768
+ with gr.Column(visible=False, elem_id="step-5") as step_5:
769
+ c_s5_title = gr.Markdown(f"<h2 style='text-align:center;'>{t('en', 's5_title')}</h2>")
770
+ c_s5_html = gr.HTML(_get_step5_html("en"))
771
+ back_to_lesson_btn = gr.Button(t('en', 'btn_review'), size="lg")
772
+
773
+ loading_screen = gr.Column(visible=False)
774
+ all_steps = [step_1, step_2, step_3, step_4_eu, step_4, step_5, loading_screen, initial_loading]
775
+
776
+ # --- Initial Load with Language & Auth ---
777
+ def initial_load(request: gr.Request):
778
+ params = request.query_params
779
+ lang = params.get("lang", "en")
780
+ if lang not in TRANSLATIONS: lang = "en"
781
+
782
+ # Try auth
783
+ success, username, token = _try_session_based_auth(request)
784
+
785
+ # Build Stats HTML (Translating inside)
786
+ stats_html = ""
787
+ if success and username:
788
+ stats = _compute_user_stats(username, token)
789
+ stats_html = build_stats_html(stats, lang)
790
+ else:
791
+ stats_html = f"""
792
+ <div class='slide-shell slide-shell--primary' style='text-align:center;'>
793
+ <h2 class='slide-shell__title'>{t(lang, 'loading_session')}</h2>
794
+ </div>
795
+ """
796
+
797
+ # Return all UI updates
798
+ return [
799
+ gr.update(visible=False), # initial_loading
800
+ gr.update(visible=True), # step_1
801
+ gr.update(value=stats_html), # stats_display
802
+ # Title
803
+ f"<h1 style='text-align:center;'>{t(lang, 'title')}</h1>",
804
+ # Loading Text
805
+ f"<div style='text-align:center; padding:80px 0;'><h2>{t(lang, 'loading_personal')}</h2></div>",
806
+ # Step 1 Button
807
+ gr.Button(value=t(lang, 'btn_deploy')),
808
+ # Step 2
809
+ f"<h2 style='text-align:center;'>{t(lang, 's2_title')}</h2>",
810
+ _get_step2_html(lang),
811
+ gr.Button(value=t(lang, 'btn_back')),
812
+ gr.Button(value=t(lang, 'btn_reveal')),
813
+ # Step 3
814
+ f"<h2 style='text-align:center;'>{t(lang, 's3_title')}</h2>",
815
+ _get_step3_html(lang),
816
+ gr.Button(value=t(lang, 'btn_back')),
817
+ gr.Button(value=t(lang, 'btn_eu')),
818
+ # Step 4 EU
819
+ f"<h2 style='text-align:center;'>{t(lang, 's4eu_title')}</h2>",
820
+ _get_step4_eu_html(lang),
821
+ gr.Button(value=t(lang, 'btn_back_invest')),
822
+ gr.Button(value=t(lang, 'btn_zoom')),
823
+ # Step 4 Lesson
824
+ f"<h2 style='text-align:center;'>{t(lang, 's4_title')}</h2>",
825
+ _get_step4_lesson_html(lang),
826
+ gr.Button(value=t(lang, 'btn_back_eu')),
827
+ gr.Button(value=t(lang, 'btn_what_do')),
828
+ # Step 5
829
+ f"<h2 style='text-align:center;'>{t(lang, 's5_title')}</h2>",
830
+ _get_step5_html(lang),
831
+ gr.Button(value=t(lang, 'btn_review'))
832
+ ]
833
+
834
+ # Load trigger
835
+ update_targets = [
836
+ initial_loading, step_1, stats_display, c_title, c_loading_text,
837
+ deploy_button,
838
+ c_s2_title, c_s2_html, step_2_back, step_2_next,
839
+ c_s3_title, c_s3_html, step_3_back, step_3_next,
840
+ c_s4eu_title, c_s4eu_html, step_4_eu_back, step_4_eu_next,
841
+ c_s4_title, c_s4_html, step_4_back, step_4_next,
842
+ c_s5_title, c_s5_html, back_to_lesson_btn
843
+ ]
844
+ demo.load(fn=initial_load, inputs=None, outputs=update_targets)
845
+
846
+ # --- Navigation Logic ---
847
+ def create_nav_generator(current_step, next_step):
848
+ def navigate():
849
+ updates = {loading_screen: gr.update(visible=True)}
850
+ for s in all_steps:
851
+ if s != loading_screen: updates[s] = gr.update(visible=False)
852
+ yield updates
853
+ updates = {next_step: gr.update(visible=True)}
854
+ for s in all_steps:
855
+ if s != next_step: updates[s] = gr.update(visible=False)
856
+ yield updates
857
+ return navigate
858
+
859
+ def nav_js(target_id: str, message: str, min_show_ms: int = 900) -> str:
860
+ return f"""
861
+ ()=>{{
862
+ try {{
863
+ const overlay=document.getElementById('nav-loading-overlay');
864
+ const msg=document.getElementById('nav-loading-text');
865
+ if(overlay && msg){{ msg.textContent='{message}'; overlay.style.display='flex'; setTimeout(()=>overlay.style.opacity='1',10); }}
866
+ const start=Date.now();
867
+ setTimeout(()=>{{ window.scrollTo({{top:0, behavior:'smooth'}}); }},40);
868
+ const poll=setInterval(()=>{{
869
+ const elapsed=Date.now()-start;
870
+ const target=document.getElementById('{target_id}');
871
+ const visible=target && target.offsetParent!==null;
872
+ if((visible && elapsed>={min_show_ms}) || elapsed>6000){{
873
+ clearInterval(poll);
874
+ if(overlay){{ overlay.style.opacity='0'; setTimeout(()=>overlay.style.display='none',320); }}
875
+ }}
876
+ }},100);
877
+ }} catch(e){{}}
878
+ }}
879
+ """
880
+
881
+ deploy_button.click(fn=create_nav_generator(step_1, step_2), inputs=None, outputs=all_steps, js=nav_js("step-2", "Sharing model..."))
882
+ step_2_back.click(fn=create_nav_generator(step_2, step_1), inputs=None, outputs=all_steps, js=nav_js("step-1", "Returning..."))
883
+ step_2_next.click(fn=create_nav_generator(step_2, step_3), inputs=None, outputs=all_steps, js=nav_js("step-3", "Loading investigation..."))
884
+ step_3_back.click(fn=create_nav_generator(step_3, step_2), inputs=None, outputs=all_steps, js=nav_js("step-2", "Going back..."))
885
+ step_3_next.click(fn=create_nav_generator(step_3, step_4_eu), inputs=None, outputs=all_steps, js=nav_js("step-4-eu", "Exploring European context..."))
886
+ step_4_eu_back.click(fn=create_nav_generator(step_4_eu, step_3), inputs=None, outputs=all_steps, js=nav_js("step-3", "Reviewing findings..."))
887
+ step_4_eu_next.click(fn=create_nav_generator(step_4_eu, step_4), inputs=None, outputs=all_steps, js=nav_js("step-4", "Zooming out..."))
888
+ step_4_back.click(fn=create_nav_generator(step_4, step_4_eu), inputs=None, outputs=all_steps, js=nav_js("step-4-eu", "European context..."))
889
+ step_4_next.click(fn=create_nav_generator(step_4, step_5), inputs=None, outputs=all_steps, js=nav_js("step-5", "Exploring solutions..."))
890
+ back_to_lesson_btn.click(fn=create_nav_generator(step_5, step_4), inputs=None, outputs=all_steps, js=nav_js("step-4", "Reviewing lesson..."))
891
+
892
+ return demo
893
+
894
+ def launch_ethical_revelation_app(height: int = 1000, share: bool = False, debug: bool = False) -> None:
895
+ demo = create_ethical_revelation_app()
896
+ port = int(os.environ.get("PORT", 8080))
897
+ demo.launch(share=share, inline=True, debug=debug, height=height, server_port=port)
898
+