aimodelshare 0.3.7__py3-none-any.whl → 0.3.94__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 (36) hide show
  1. aimodelshare/moral_compass/__init__.py +51 -2
  2. aimodelshare/moral_compass/api_client.py +92 -4
  3. aimodelshare/moral_compass/apps/__init__.py +36 -16
  4. aimodelshare/moral_compass/apps/ai_consequences.py +98 -88
  5. aimodelshare/moral_compass/apps/bias_detective_ca.py +2722 -0
  6. aimodelshare/moral_compass/apps/bias_detective_en.py +2722 -0
  7. aimodelshare/moral_compass/apps/bias_detective_part1.py +2722 -0
  8. aimodelshare/moral_compass/apps/bias_detective_part2.py +2465 -0
  9. aimodelshare/moral_compass/apps/bias_detective_part_es.py +2722 -0
  10. aimodelshare/moral_compass/apps/ethical_revelation.py +237 -147
  11. aimodelshare/moral_compass/apps/fairness_fixer.py +1839 -859
  12. aimodelshare/moral_compass/apps/fairness_fixer_ca.py +1869 -0
  13. aimodelshare/moral_compass/apps/fairness_fixer_en.py +1869 -0
  14. aimodelshare/moral_compass/apps/fairness_fixer_es.py +1869 -0
  15. aimodelshare/moral_compass/apps/judge.py +130 -143
  16. aimodelshare/moral_compass/apps/justice_equity_upgrade.py +793 -831
  17. aimodelshare/moral_compass/apps/justice_equity_upgrade_ca.py +815 -0
  18. aimodelshare/moral_compass/apps/justice_equity_upgrade_en.py +815 -0
  19. aimodelshare/moral_compass/apps/justice_equity_upgrade_es.py +815 -0
  20. aimodelshare/moral_compass/apps/mc_integration_helpers.py +227 -745
  21. aimodelshare/moral_compass/apps/model_building_app_ca.py +4399 -0
  22. aimodelshare/moral_compass/apps/model_building_app_ca_final.py +3899 -0
  23. aimodelshare/moral_compass/apps/model_building_app_en.py +4167 -0
  24. aimodelshare/moral_compass/apps/model_building_app_en_final.py +3869 -0
  25. aimodelshare/moral_compass/apps/model_building_app_es.py +4351 -0
  26. aimodelshare/moral_compass/apps/model_building_app_es_final.py +3899 -0
  27. aimodelshare/moral_compass/apps/model_building_game.py +4211 -935
  28. aimodelshare/moral_compass/apps/moral_compass_challenge.py +195 -95
  29. aimodelshare/moral_compass/apps/what_is_ai.py +126 -117
  30. aimodelshare/moral_compass/challenge.py +98 -17
  31. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/METADATA +1 -1
  32. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/RECORD +35 -19
  33. aimodelshare/moral_compass/apps/bias_detective.py +0 -714
  34. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/WHEEL +0 -0
  35. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/licenses/LICENSE +0 -0
  36. {aimodelshare-0.3.7.dist-info → aimodelshare-0.3.94.dist-info}/top_level.txt +0 -0
@@ -5,6 +5,7 @@ Updated with i18n support and visual fixes (gr.HTML implementation).
5
5
  import contextlib
6
6
  import os
7
7
  import gradio as gr
8
+ from functools import lru_cache
8
9
 
9
10
  os.environ.setdefault("APP_NAME", "judge")
10
11
 
@@ -23,7 +24,7 @@ TRANSLATIONS = {
23
24
  which defendants can be safely released.<br><br>
24
25
 
25
26
  To help you, the court has implemented an AI system that predicts the risk of each
26
- defendant committing new crimes if released. The AI categorizes defendants as:<br><br>
27
+ defendant committing new crimes if released on parole. The AI categorizes defendants as:<br><br>
27
28
 
28
29
  <ul style='font-size:18px;'>
29
30
  <li><span class='ai-risk-label risk-high'>High Risk</span> - Likely to re-offend</li>
@@ -36,7 +37,7 @@ TRANSLATIONS = {
36
37
  "btn_start": "Begin Making Decisions ▶️",
37
38
  "profiles_title": "👥 Defendant Profiles",
38
39
  "hint_box": "Review each defendant's information and the AI's risk assessment, then make your decision.",
39
- "btn_release": "✓ Release Prisoner",
40
+ "btn_release": "✓ Release",
40
41
  "btn_keep": "✗ Keep in Prison",
41
42
  "btn_show_summary": "📊 Show My Decisions Summary",
42
43
  "btn_complete": "Complete This Section ▶️",
@@ -58,8 +59,8 @@ TRANSLATIONS = {
58
59
  "decision_keep": "Keep in Prison",
59
60
  "decision_recorded": "✓ Decision recorded:",
60
61
  "summary_title": "📊 Your Decisions Summary",
61
- "summary_released": "Prisoners Released:",
62
- "summary_kept": "Prisoners Kept in Prison:",
62
+ "summary_released": "Incarcerated Individuals Released:",
63
+ "summary_kept": "Incarcerated Individuals Kept in Prison:",
63
64
  "summary_empty": "No decisions made yet.",
64
65
  "nav_loading_profiles": "Loading defendant profiles...",
65
66
  "nav_reviewing": "Reviewing your decisions...",
@@ -68,7 +69,7 @@ TRANSLATIONS = {
68
69
  "label_defendant": "Defendant",
69
70
  "label_age": "Age",
70
71
  "label_gender": "Gender",
71
- "label_race": "Race",
72
+ "label_race": "Race/Etnicity",
72
73
  "label_prior": "Prior Offenses",
73
74
  "label_charge": "Current Charge",
74
75
  "label_ai_risk": "🤖 AI Risk Assessment:",
@@ -90,35 +91,35 @@ TRANSLATIONS = {
90
91
  "Burglary": "Burglary"
91
92
  },
92
93
  "es": {
93
- "title": "⚖️ Eres el Juez",
94
- "intro_role": "<b>Tu Rol:</b> Eres un juez que debe decidir si liberar a los acusados de la prisión.<br>Un sistema de IA ha analizado cada caso y ha proporcionado una evaluación de riesgos.<br><br><b>Tu Tarea:</b> Revisa el perfil de cada acusado y la predicción de la IA, luego toma tu decisión.",
94
+ "title": "⚖️ ¡Ponte en el rol de juez!",
95
+ "intro_role": "<b>Tu rol:</b> Eres un juez o una jueza que debe decidir si se concede la libertad condicional a una persona presa o si debe continuar en prisión.<br>Un sistema de IA ha analizado cada caso y ha proporcionado una evaluación de riesgos.<br><br><b>Tu tarea:</b> Revisa el perfil de cada acusado y la predicción de la IA, luego toma tu decisión.",
95
96
  "loading": "⏳ Cargando...",
96
- "scenario_title": "📋 El Escenario",
97
+ "scenario_title": "📋 El escenario",
97
98
  "scenario_box": """
98
- Eres juez en un tribunal penal muy concurrido. Debido al hacinamiento en las cárceles, debes decidir
99
- qué acusados pueden ser liberados de manera segura.<br><br>
99
+ Eres miembro de un tribunal penal con mucho trabajo. Debido al hacinamiento en las prisiones, debes decidir
100
+ qué personas presas pueden obtener la libertad condicional de manera segura.<br><br>
100
101
 
101
102
  Para ayudarte, el tribunal ha implementado un sistema de IA que predice el riesgo de que cada
102
- acusado cometa nuevos delitos si es liberado. La IA clasifica a los acusados como:<br><br>
103
+ persona presa cometa nuevos delitos si obtiene la libertad condicional. La IA clasifica a las personas presas como:<br><br>
103
104
 
104
105
  <ul style='font-size:18px;'>
105
- <li><span class='ai-risk-label risk-high'>Alto Riesgo</span> - Probable reincidencia</li>
106
+ <li><span class='ai-risk-label risk-high'>Alto Riesgo</span> - Probable reincidencia (de cometer nuevos delitos)</li>
106
107
  <li><span class='ai-risk-label risk-medium'>Riesgo Medio</span> - Probabilidad moderada de reincidencia</li>
107
108
  <li><span class='ai-risk-label risk-low'>Bajo Riesgo</span> - Improbable reincidencia</li>
108
109
  </ul>
109
110
 
110
111
  <b>Recuerda:</b> Tus decisiones afectan la vida de personas reales y la seguridad pública.
111
112
  """,
112
- "btn_start": "Comenzar a Tomar Decisiones ▶️",
113
- "profiles_title": "👥 Perfiles de Acusados",
114
- "hint_box": "Revisa la información de cada acusado y la evaluación de riesgos de la IA, luego toma tu decisión.",
115
- "btn_release": "✓ Liberar Prisionero",
116
- "btn_keep": "✗ Mantener en Prisión",
117
- "btn_show_summary": "📊 Mostrar Resumen de Decisiones",
118
- "btn_complete": "Completar esta Sección ▶️",
119
- "completion_title": "✅ ¡Decisiones Completadas!",
120
- "completion_box_pre": "Has tomado tus decisiones basándote en las recomendaciones de la IA.<br><br>Pero aquí está la pregunta crítica:<br><br>",
121
- "completion_question": "¿Y si la IA estuviera equivocada?",
113
+ "btn_start": "Comenzar a tomar decisiones ▶️",
114
+ "profiles_title": "👥 Perfiles de las personas presas",
115
+ "hint_box": "Revisa la información de cada persona presa y la evaluación de riesgos de la IA, luego toma tu decisión.",
116
+ "btn_release": "✓ Liberar la persona presa",
117
+ "btn_keep": "✗ Mantener en prisión",
118
+ "btn_show_summary": "📊 Mostrar resumen de decisiones",
119
+ "btn_complete": "Completar esta sección ▶️",
120
+ "completion_title": "✅ ¡Decisiones completadas!",
121
+ "completion_box_pre": "Ya has tomado tus decisiones basándote en las recomendaciones de la IA.<br><br>Ahora bien, surge una pregunta clave:<br><br>",
122
+ "completion_question": "¿Y si la IA se equivocó?",
122
123
  "completion_box_post": """
123
124
  <p style='font-size:1.1rem;'>
124
125
  Continúa en la siguiente sección para explorar las consecuencias de
@@ -126,27 +127,27 @@ TRANSLATIONS = {
126
127
  </p>
127
128
  <h1 style='margin:20px 0; font-size: 3rem;'>👇 DESPLÁZATE HACIA ABAJO 👇</h1>
128
129
  <p style='font-size:1.1rem;'>
129
- Encuentra la siguiente sección abajo para continuar tu viaje.
130
+ Encuentra la siguiente sección para continuar tu viaje.
130
131
  </p>
131
132
  """,
132
- "btn_back": "◀️ Volver a Revisar Decisiones",
133
+ "btn_back": "◀️ Volver a revisar decisiones",
133
134
  "decision_release": "Liberar",
134
- "decision_keep": "Mantener en Prisión",
135
+ "decision_keep": "Mantener en prisión",
135
136
  "decision_recorded": "✓ Decisión registrada:",
136
- "summary_title": "📊 Resumen de tus Decisiones",
137
- "summary_released": "Prisioneros Liberados:",
138
- "summary_kept": "Prisioneros Mantenidos en Prisión:",
137
+ "summary_title": "📊 Resumen de tus decisiones",
138
+ "summary_released": "Personas presas puestas en libertad:",
139
+ "summary_kept": "Personas presas que continúan en prisión:",
139
140
  "summary_empty": "Aún no se han tomado decisiones.",
140
141
  "nav_loading_profiles": "Cargando perfiles...",
141
142
  "nav_reviewing": "Revisando tus decisiones...",
142
- "nav_returning": "Volviendo a perfiles...",
143
- "label_defendant": "Acusado",
143
+ "nav_returning": "Volver a perfiles...",
144
+ "label_defendant": "Persona presa",
144
145
  "label_age": "Edad",
145
146
  "label_gender": "Género",
146
- "label_race": "Raza",
147
- "label_prior": "Delitos Previos",
148
- "label_charge": "Cargo Actual",
149
- "label_ai_risk": "🤖 Evaluación de Riesgo IA:",
147
+ "label_race": "Raza/Etnicidad",
148
+ "label_prior": "Delitos previos",
149
+ "label_charge": "Cargo actual",
150
+ "label_ai_risk": "🤖 Evaluación de riesgo IA:",
150
151
  "label_risk": "Riesgo",
151
152
  "label_confidence": "Confianza:",
152
153
  "Male": "Masculino",
@@ -161,37 +162,37 @@ TRANSLATIONS = {
161
162
  "Theft": "Robo",
162
163
  "Assault": "Asalto",
163
164
  "Fraud": "Fraude",
164
- "Burglary": "Robo con allanamiento"
165
+ "Burglary": "Robo con allanamiento de morada"
165
166
  },
166
167
  "ca": {
167
- "title": "⚖️ Tu Ets el Jutge",
168
- "intro_role": "<b>El teu Rol:</b> Ets un jutge que ha de decidir si alliberar els acusats de la presó.<br>Un sistema d'IA ha analitzat cada cas i ha proporcionat una avaluació de riscos.<br><br><b>La teva Tasca:</b> Revisa el perfil de cada acusat i la predicció de la IA, després pren la teva decisió.",
168
+ "title": "⚖️ Posa't en el rol de jutge!",
169
+ "intro_role": "<b>El teu rol:</b> Ets un jutge o una jutgessa que ha de decidir si es concedeix la llibertat condicional a una persona presa o ha de continuar a la presó.<br>Un sistema d'IA ha analitzat cada cas i ha proporcionat una avaluació de riscos.<br><br><b>La teva tasca:</b> Revisa el perfil de cada persona presa i la predicció de la IA, després pren la teva decisió.",
169
170
  "loading": "⏳ Carregant...",
170
- "scenario_title": "📋 L'Escenari",
171
+ "scenario_title": "📋 L'escenari",
171
172
  "scenario_box": """
172
- Ets jutge en un tribunal penal molt concorregut. A causa de la massificació a les presons, has de decidir
173
- quins acusats poden ser alliberats de manera segura.<br><br>
173
+ Ets membre d'un tribunal penal amb molta feina. A causa de la massificació a les presons, has de decidir
174
+ quines persones preses poden ser posades en llibertat de manera segura.<br><br>
174
175
 
175
176
  Per ajudar-te, el tribunal ha implementat un sistema d'IA que prediu el risc que cada
176
- acusat cometi nous delictes si és alliberat. La IA classifica els acusats com:<br><br>
177
+ persona presa cometi nous delictes si obté la llibertat condicional. La IA classifica les persones preses com:<br><br>
177
178
 
178
179
  <ul style='font-size:18px;'>
179
- <li><span class='ai-risk-label risk-high'>Alt Risc</span> - Probable reincidència</li>
180
+ <li><span class='ai-risk-label risk-high'>Alt Risc</span> - Probable reincidència (de cometre nous delictes)</li>
180
181
  <li><span class='ai-risk-label risk-medium'>Risc Mitjà</span> - Probabilitat moderada de reincidència</li>
181
182
  <li><span class='ai-risk-label risk-low'>Baix Risc</span> - Improbable reincidència</li>
182
183
  </ul>
183
184
 
184
185
  <b>Recorda:</b> Les teves decisions afecten la vida de persones reals i la seguretat pública.
185
186
  """,
186
- "btn_start": "Començar a Prendre Decisions ▶️",
187
- "profiles_title": "👥 Perfils d'Acusats",
188
- "hint_box": "Revisa la informació de cada acusat i l'avaluació de riscos de la IA, després pren la teva decisió.",
189
- "btn_release": "✓ Alliberar Presoner",
190
- "btn_keep": "✗ Mantenir a la Presó",
191
- "btn_show_summary": "📊 Mostrar Resum de Decisions",
192
- "btn_complete": "Completar aquesta Secció ▶️",
193
- "completion_title": "✅ Decisions Completades!",
194
- "completion_box_pre": "Has pres les teves decisions basant-te en les recomanacions de la IA.<br><br>Però aquí està la pregunta crítica:<br><br>",
187
+ "btn_start": "Començar a prendre decisions ▶️",
188
+ "profiles_title": "👥 Perfils de les persones preses",
189
+ "hint_box": "Revisa la informació de cada persona presa i l'avaluació de riscos de la IA, després pren la teva decisió.",
190
+ "btn_release": "✓ Alliberar",
191
+ "btn_keep": "✗ Mantenir a la presó",
192
+ "btn_show_summary": "📊 Mostrar resum de decisions",
193
+ "btn_complete": "Completar aquesta secció ▶️",
194
+ "completion_title": "✅ Decisions completades!",
195
+ "completion_box_pre": "Ya has pres les teves decisions basant-te en les recomanacions de la IA.<br><br>Ara bé, sorgeix una pregunta clau:<br><br>",
195
196
  "completion_question": "I si la IA s'hagués equivocat?",
196
197
  "completion_box_post": """
197
198
  <p style='font-size:1.1rem;'>
@@ -203,24 +204,24 @@ TRANSLATIONS = {
203
204
  Troba la següent secció a sota per continuar el teu viatge.
204
205
  </p>
205
206
  """,
206
- "btn_back": "◀️ Tornar a Revisar Decisions",
207
+ "btn_back": "◀️ Tornar a revisar decisions",
207
208
  "decision_release": "Alliberar",
208
- "decision_keep": "Mantenir a la Presó",
209
+ "decision_keep": "Mantenir a la presó",
209
210
  "decision_recorded": "✓ Decisió registrada:",
210
- "summary_title": "📊 Resum de les teves Decisions",
211
- "summary_released": "Presoners Alliberats:",
212
- "summary_kept": "Presoners Mantinguts a la Presó:",
211
+ "summary_title": "📊 Resum de les teves decisions",
212
+ "summary_released": "Persones preses posades en llibertat:",
213
+ "summary_kept": "Persones preses que continuen a la presó:",
213
214
  "summary_empty": "Encara no s'han pres decisions.",
214
215
  "nav_loading_profiles": "Carregant perfils...",
215
216
  "nav_reviewing": "Revisant les teves decisions...",
216
- "nav_returning": "Tornant a perfils...",
217
- "label_defendant": "Acusat",
217
+ "nav_returning": "Tornar a perfils...",
218
+ "label_defendant": "Persona presa",
218
219
  "label_age": "Edat",
219
220
  "label_gender": "Gènere",
220
- "label_race": "Raça",
221
- "label_prior": "Delictes Previs",
222
- "label_charge": "Càrrec Actual",
223
- "label_ai_risk": "🤖 Avaluació de Risc IA:",
221
+ "label_race": "Raça/Etnicitat",
222
+ "label_prior": "Delictes previs",
223
+ "label_charge": "Càrrec actual",
224
+ "label_ai_risk": "🤖 Avaluació de risc de la IA:",
224
225
  "label_risk": "Risc",
225
226
  "label_confidence": "Confiança:",
226
227
  "Male": "Masculí",
@@ -235,7 +236,7 @@ TRANSLATIONS = {
235
236
  "Theft": "Robatori",
236
237
  "Assault": "Assalt",
237
238
  "Fraud": "Frau",
238
- "Burglary": "Robatori amb violència"
239
+ "Burglary": "Robatori amb violació de domicili"
239
240
  }
240
241
  }
241
242
 
@@ -312,8 +313,6 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
312
313
 
313
314
  profiles = _generate_defendant_profiles()
314
315
 
315
- # Track decisions globally for simplicity in this factory context
316
- decisions = {}
317
316
 
318
317
  # Helpers
319
318
  def t(lang, key):
@@ -356,27 +355,30 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
356
355
  </div>
357
356
  """
358
357
 
359
- def make_decision(defendant_id, decision_type, lang):
360
- """Record a decision for a defendant."""
361
- # Map generic decision type to display text
362
- dec_text = t(lang, "decision_release" if decision_type == "Release" else "decision_keep")
363
- decisions[defendant_id] = decision_type # Store raw type
364
- return f"{t(lang, 'decision_recorded')} {dec_text}"
358
+ def make_decision(defendant_id, decision_type, lang, current_decisions):
359
+ """Record a decision for a defendant safely per user."""
360
+ # Create a copy so we don't affect other users
361
+ new_decisions = current_decisions.copy()
362
+ new_decisions[defendant_id] = decision_type
363
+
364
+ dec_text = t(lang, "decision_release" if decision_type == "Release" else "decision_keep")
365
+ # Return the notification text AND the updated dictionary
366
+ return f"{t(lang, 'decision_recorded')} {dec_text}", new_decisions
365
367
 
366
- def get_summary(lang):
367
- """Get summary of all decisions made."""
368
- if not decisions:
368
+ def get_summary(lang, current_decisions):
369
+ """Get summary based on the specific user's decisions."""
370
+ if not current_decisions:
369
371
  return t(lang, "summary_empty")
370
372
 
371
- released = sum(1 for d in decisions.values() if d == "Release")
372
- kept = sum(1 for d in decisions.values() if d == "Keep in Prison")
373
+ released = sum(1 for d in current_decisions.values() if d == "Release")
374
+ kept = sum(1 for d in current_decisions.values() if d == "Keep in Prison")
373
375
 
374
376
  summary = f"""
375
377
  <div class="summary-box">
376
378
  <h3 class="summary-title">{t(lang, 'summary_title')}</h3>
377
379
  <div class="summary-body">
378
- <p><b>{t(lang, 'summary_released')}</b> {released} of {len(decisions)}</p>
379
- <p><b>{t(lang, 'summary_kept')}</b> {kept} of {len(decisions)}</p>
380
+ <p><b>{t(lang, 'summary_released')}</b> {released} of {len(current_decisions)}</p>
381
+ <p><b>{t(lang, 'summary_kept')}</b> {kept} of {len(current_decisions)}</p>
380
382
  </div>
381
383
  </div>
382
384
  """
@@ -586,6 +588,7 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
586
588
  with gr.Blocks(theme=gr.themes.Soft(primary_hue=theme_primary_hue), css=css) as demo:
587
589
  # State to hold current language (defaults to 'en')
588
590
  lang_state = gr.State(value="en")
591
+ decisions_state = gr.State(value={})
589
592
 
590
593
  # --- UI COMPONENTS (Stored in variables for update) ---
591
594
 
@@ -634,24 +637,31 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
634
637
  # Create UI for each defendant
635
638
  for profile in profiles:
636
639
  with gr.Column():
637
- # Profile Card HTML
640
+ # 1. Profile Card HTML
638
641
  p_html = gr.HTML(format_profile(profile, "en"))
639
642
 
643
+ # 2. Define the status text component FIRST (so buttons can reference it)
644
+ decision_status = gr.Markdown("")
645
+
646
+ # 3. Create Row with Buttons
640
647
  with gr.Row():
641
- p_rel_btn = gr.Button(
642
- t('en', 'btn_release'),
643
- variant="primary",
644
- elem_classes=["decision-button"],
648
+ # Define the buttons
649
+ p_rel_btn = gr.Button(t("en", "btn_release"), variant="secondary")
650
+ p_keep_btn = gr.Button(t("en", "btn_keep"), variant="stop")
651
+
652
+ # Wire up buttons
653
+ p_rel_btn.click(
654
+ fn=make_decision,
655
+ inputs=[gr.Number(value=profile["id"], visible=False), gr.State(value="Release"), lang_state, decisions_state],
656
+ outputs=[decision_status, decisions_state],
645
657
  )
646
- p_keep_btn = gr.Button(
647
- t('en', 'btn_keep'),
648
- variant="secondary",
649
- elem_classes=["decision-button"],
658
+ p_keep_btn.click(
659
+ fn=make_decision,
660
+ inputs=[gr.Number(value=profile["id"], visible=False), gr.State(value="Keep in Prison"), lang_state, decisions_state],
661
+ outputs=[decision_status, decisions_state],
650
662
  )
651
663
 
652
- decision_status = gr.Markdown("")
653
-
654
- # Store for language updates
664
+ # 4. Store elements for language updates
655
665
  profile_ui_elements.append({
656
666
  "id": profile["id"],
657
667
  "profile_data": profile,
@@ -660,25 +670,17 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
660
670
  "btn_keep": p_keep_btn
661
671
  })
662
672
 
663
- # Wire up buttons (passing lang_state)
664
- p_rel_btn.click(
665
- lambda l, p_id=profile["id"]: make_decision(p_id, "Release", l),
666
- inputs=[lang_state],
667
- outputs=decision_status,
668
- )
669
- p_keep_btn.click(
670
- lambda l, p_id=profile["id"]: make_decision(p_id, "Keep in Prison", l),
671
- inputs=[lang_state],
672
- outputs=decision_status,
673
- )
674
-
675
673
  gr.HTML("<hr style='margin:24px 0;'>")
676
674
 
677
675
  # Summary section
678
676
  summary_display = gr.HTML("")
679
677
  show_summary_btn = gr.Button(t('en', 'btn_show_summary'), variant="primary", size="lg")
680
678
 
681
- show_summary_btn.click(get_summary, inputs=[lang_state], outputs=summary_display)
679
+ show_summary_btn.click(
680
+ get_summary,
681
+ inputs=[lang_state, decisions_state], # Pass the state
682
+ outputs=summary_display
683
+ )
682
684
 
683
685
  gr.HTML("<br>")
684
686
  complete_btn = gr.Button(t('en', 'btn_complete'), variant="primary", size="lg")
@@ -701,11 +703,10 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
701
703
  back_to_profiles_btn = gr.Button(t('en', 'btn_back'))
702
704
 
703
705
  # -------------------------------------------------------------------------
704
- # I18N UPDATE LOGIC
706
+ # I18N UPDATE LOGIC (CACHED)
705
707
  # -------------------------------------------------------------------------
706
708
 
707
- # List of all components that need text updates
708
- # Order matters! Must match the return of update_language
709
+ # 1. Define targets (This remains the same)
709
710
  update_targets = [
710
711
  lang_state, # 0
711
712
  c_main_title, # 1
@@ -722,56 +723,34 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
722
723
  back_to_profiles_btn # 12
723
724
  ]
724
725
 
725
- # Add dynamic profile components
726
+ # Add dynamic profile components to targets
726
727
  for p_ui in profile_ui_elements:
727
728
  update_targets.append(p_ui["html"])
728
729
  update_targets.append(p_ui["btn_rel"])
729
730
  update_targets.append(p_ui["btn_keep"])
730
731
 
731
- def update_language(request: gr.Request):
732
- """Parse URL params and return updated content for all components."""
733
- params = request.query_params
734
- lang = params.get("lang", "en")
735
- if lang not in TRANSLATIONS:
736
- lang = "en"
737
-
738
- # Prepare updates
732
+ # 2. Define the Cached Generator
733
+ @lru_cache(maxsize=16)
734
+ def get_cached_ui_updates(lang):
735
+ """
736
+ Calculates the massive list of HTML strings and Labels ONCE per language.
737
+ """
739
738
  updates = []
740
739
 
741
740
  # 0. State
742
741
  updates.append(lang)
743
742
 
744
- # 1. Main Title
743
+ # Static Elements
745
744
  updates.append(f"<h1 style='text-align:center;'>{t(lang, 'title')}</h1>")
746
-
747
- # 2. Intro
748
745
  updates.append(f"""<div class="judge-intro-box">{t(lang, 'intro_role')}</div>""")
749
-
750
- # 3. Loading
751
746
  updates.append(f"""<div style='text-align:center; padding: 100px 0;'><h2 class='loading-title'>{t(lang, 'loading')}</h2></div>""")
752
-
753
- # 4. Scenario Title
754
747
  updates.append(f"<h2 style='text-align:center;'>{t(lang, 'scenario_title')}</h2>")
755
-
756
- # 5. Scenario Box
757
748
  updates.append(f"""<div class="scenario-box">{t(lang, 'scenario_box')}</div>""")
758
-
759
- # 6. Start Button
760
749
  updates.append(gr.Button(value=t(lang, 'btn_start')))
761
-
762
- # 7. Profiles Title
763
750
  updates.append(f"<h2 style='text-align:center;'>{t(lang, 'profiles_title')}</h2>")
764
-
765
- # 8. Hint Box
766
751
  updates.append(f"""<div class="hint-box">{t(lang, 'hint_box')}</div>""")
767
-
768
- # 9. Summary Button
769
752
  updates.append(gr.Button(value=t(lang, 'btn_show_summary')))
770
-
771
- # 10. Complete Button
772
753
  updates.append(gr.Button(value=t(lang, 'btn_complete')))
773
-
774
- # 11. Completion HTML
775
754
  updates.append(f"""
776
755
  <div style='text-align:center;'>
777
756
  <h2 style='font-size: 2.5rem;'>{t(lang, 'completion_title')}</h2>
@@ -782,20 +761,27 @@ def create_judge_app(theme_primary_hue: str = "indigo") -> "gr.Blocks":
782
761
  </div>
783
762
  </div>
784
763
  """)
785
-
786
- # 12. Back Button
787
764
  updates.append(gr.Button(value=t(lang, 'btn_back')))
788
765
 
789
- # Dynamic Profiles
790
- for p_ui in profile_ui_elements:
791
- # Update HTML card
792
- updates.append(format_profile(p_ui["profile_data"], lang))
793
- # Update Buttons
766
+ # Dynamic Profiles (Loop through the data, not the UI elements, to be safe)
767
+ # Note: We rely on 'profiles' being available in this scope.
768
+ for profile in profiles:
769
+ updates.append(format_profile(profile, lang))
794
770
  updates.append(gr.Button(value=t(lang, 'btn_release')))
795
771
  updates.append(gr.Button(value=t(lang, 'btn_keep')))
796
772
 
797
773
  return updates
798
774
 
775
+ # 3. Define the Request Handler
776
+ def update_language(request: gr.Request):
777
+ params = request.query_params
778
+ lang = params.get("lang", "en")
779
+ if lang not in TRANSLATIONS:
780
+ lang = "en"
781
+
782
+ # Instant return from cache
783
+ return get_cached_ui_updates(lang)
784
+
799
785
  # Trigger update on page load
800
786
  demo.load(update_language, inputs=None, outputs=update_targets)
801
787
 
@@ -886,3 +872,4 @@ def launch_judge_app(height: int = 1200, share: bool = False, debug: bool = Fals
886
872
  if __name__ == "__main__":
887
873
  launch_judge_app()
888
874
 
875
+