FredOru commited on
Commit
9565067
·
1 Parent(s): bf3c3b7
.DS_Store ADDED
Binary file (6.15 kB). View file
 
README copy.md DELETED
@@ -1 +0,0 @@
1
- # prompt-arena
 
 
README.md CHANGED
@@ -1,12 +1 @@
1
- ---
2
- title: Arena
3
- emoji: 🐨
4
- colorFrom: indigo
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 5.29.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # prompt-arena
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,176 +1,39 @@
1
  import gradio as gr
2
- import pandas as pd
3
  from arena import Arena
4
- import plotly.graph_objs as go
5
- import numpy as np
6
 
7
  LABEL_A = "Proposition A"
8
  LABEL_B = "Proposition B"
9
 
10
  ARENA = Arena()
11
- ARENA.init_estimates()
12
-
13
-
14
- def select_and_display_match():
15
- try:
16
- prompt_a, prompt_b = ARENA.select_match()
17
- state = {"prompt_a_id": prompt_a["id"], "prompt_b_id": prompt_b["id"]}
18
- vote_a_btn_update = gr.update(interactive=True)
19
- vote_b_btn_update = gr.update(interactive=True)
20
- new_match_btn_update = gr.update(interactive=False)
21
- return (
22
- prompt_a["text"],
23
- prompt_b["text"],
24
- state,
25
- vote_a_btn_update,
26
- vote_b_btn_update,
27
- new_match_btn_update,
28
- )
29
- except Exception as e:
30
- return f"Erreur lors de la sélection d'un match: {str(e)}", "", "", {}
31
-
32
-
33
- def record_winner_a(state):
34
- try:
35
- prompt_a_id = state["prompt_a_id"]
36
- prompt_b_id = state["prompt_b_id"]
37
- ARENA.record_result(prompt_a_id, prompt_b_id)
38
- progress_info = ARENA.get_progress()
39
- rankings_table = ARENA.get_rankings()
40
- vote_a_btn_update = gr.update(interactive=False)
41
- vote_b_btn_update = gr.update(interactive=False)
42
- new_match_btn_update = gr.update(interactive=True)
43
- return (
44
- f"Vous avez choisi : {LABEL_A}",
45
- progress_info,
46
- rankings_table,
47
- vote_a_btn_update,
48
- vote_b_btn_update,
49
- new_match_btn_update,
50
- )
51
- except Exception as e:
52
- return (
53
- f"Erreur lors de l'enregistrement du résultat: {str(e)}",
54
- "",
55
- pd.DataFrame(),
56
- )
57
-
58
-
59
- def record_winner_b(state):
60
- try:
61
- prompt_a_id = state["prompt_a_id"]
62
- prompt_b_id = state["prompt_b_id"]
63
- ARENA.record_result(prompt_b_id, prompt_a_id)
64
- progress_info = ARENA.get_progress()
65
- rankings_table = ARENA.get_rankings()
66
- vote_a_btn_update = gr.update(interactive=False)
67
- vote_b_btn_update = gr.update(interactive=False)
68
- new_match_btn_update = gr.update(interactive=True)
69
- return (
70
- f"Vous avez choisi : {LABEL_B}",
71
- progress_info,
72
- rankings_table,
73
- vote_a_btn_update,
74
- vote_b_btn_update,
75
- new_match_btn_update,
76
- )
77
- except Exception as e:
78
- return (
79
- f"Erreur lors de l'enregistrement du résultat: {str(e)}",
80
- "",
81
- pd.DataFrame(),
82
- )
83
-
84
 
85
- def update_table(table_name, df):
86
- """Met à jour le fichier CSV de la table spécifiée à partir du DataFrame édité."""
87
- ARENA.replace(table_name, df)
88
- return None
 
89
 
 
 
 
 
 
90
 
91
- def admin_visible(request: gr.Request):
92
- is_admin = request.username == "admin"
93
- return gr.update(visible=is_admin)
94
-
95
-
96
- def welcome_user(request: gr.Request):
97
- return request.username
98
-
99
-
100
- def plot_estimates_distribution():
101
- """Affiche une gaussienne par prompt (Plotly) + lignes verticales pointillées sur les moyennes."""
102
- estimates = ARENA.load("estimates")
103
- prompts = ARENA.load("prompts")
104
- if estimates.empty or prompts.empty:
105
- fig = go.Figure()
106
- fig.add_annotation(
107
- text="Aucune estimation disponible", x=0.5, y=0.5, showarrow=False
108
- )
109
- return fig
110
- x = np.linspace(
111
- estimates["mu"].min() - 3 * estimates["sigma"].max(),
112
- estimates["mu"].max() + 3 * estimates["sigma"].max(),
113
- 500,
114
- )
115
- fig = go.Figure()
116
- shapes = []
117
- # Une gaussienne par prompt
118
- for _, row in estimates.iterrows():
119
- mu = row["mu"]
120
- sigma = row["sigma"]
121
- prompt_id = row["prompt_id"] if "prompt_id" in row else row["id"]
122
- # Chercher le nom du prompt
123
- name = str(prompt_id)
124
- if "name" in prompts.columns:
125
- match = prompts[prompts["id"] == prompt_id]
126
- if not match.empty:
127
- name = match.iloc[0]["name"]
128
- y = 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-0.5 * ((x - mu) / sigma) ** 2)
129
- fig.add_trace(
130
- go.Scatter(
131
- x=x,
132
- y=y,
133
- mode="lines",
134
- name=f"{name}",
135
- hovertemplate=f"<b>{name}</b><br>Score (mu): {mu:.2f}<br>Sigma: {sigma:.2f}<extra></extra>",
136
- )
137
- )
138
- # Ajout de la ligne verticale pointillée à mu (en gris)
139
- shapes.append(
140
- dict(
141
- type="line",
142
- x0=mu,
143
- x1=mu,
144
- y0=0,
145
- y1=max(y),
146
- line=dict(
147
- color="gray",
148
- width=2,
149
- dash="dot",
150
- ),
151
- xref="x",
152
- yref="y",
153
- )
154
- )
155
- fig.update_layout(
156
- title="Distribution gaussienne de chaque prompt",
157
- xaxis_title="Score (mu)",
158
- yaxis_title="Densité",
159
- template="plotly_white",
160
- shapes=shapes,
161
- )
162
- return fig
163
-
164
 
165
  with gr.Blocks(
166
  title="Prompt Arena",
 
167
  # theme=gr.themes.Default.load("theme_schema_miku.json"),
168
  ) as demo:
169
- state = gr.State()
170
-
171
- with gr.Row():
172
- username = gr.Markdown("")
173
- gr.Button("Logout", link="/logout", scale=0, min_width=50)
174
 
175
  gr.Markdown(
176
  '<h1 style="text-align:center;"> Concours du meilleur Prompt Engineer </h1>'
@@ -181,120 +44,477 @@ with gr.Blocks(
181
  value=ARENA.get_progress(),
182
  interactive=False,
183
  lines=2,
 
184
  )
185
 
186
  with gr.Tabs() as tabs:
187
- # Onglet des Combats
188
- with gr.TabItem("Combats"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  with gr.Row():
190
  new_match_btn = gr.Button("Lancer un nouveau match", variant="primary")
191
 
192
  with gr.Row():
193
  with gr.Column():
194
- proposition_a = gr.Textbox(label=LABEL_A, interactive=False)
195
- vote_a_btn = gr.Button("Choisir " + LABEL_A, interactive=False)
 
196
  with gr.Column():
197
- proposition_b = gr.Textbox(label=LABEL_B, interactive=False)
198
- vote_b_btn = gr.Button("Choisir " + LABEL_B, interactive=False)
199
- result = gr.Textbox("Résultat", interactive=False)
 
200
 
201
  # with gr.TabItem("Classement"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  rankings_table = gr.DataFrame(
203
- label="Classement des prompts",
204
  value=ARENA.get_rankings(),
205
  interactive=True,
206
  )
207
 
208
- # Onglet des Résultats
209
- with gr.TabItem("Admin") as admin_tab:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  with gr.Accordion("Prompts", open=False):
211
  prompts_table = gr.DataFrame(
212
- value=ARENA.load("prompts"),
213
  interactive=True,
214
  )
215
  with gr.Accordion("Estimates", open=False):
216
  estimates_table = gr.DataFrame(
217
  label="Estimations",
218
- value=ARENA.load("estimates"),
219
  interactive=True,
220
  )
221
  with gr.Accordion("Votes", open=False):
222
  votes_table = gr.DataFrame(
223
  label="Votes",
224
- value=ARENA.load("votes"),
 
 
 
 
 
 
 
 
 
 
 
 
225
  interactive=True,
226
  )
227
- gr.Plot(plot_estimates_distribution, label="Distribution des estimations")
 
 
 
 
 
 
 
 
 
 
228
  prompts_table.change(
229
- update_table,
230
  inputs=[gr.Markdown("prompts", visible=False), prompts_table],
231
  outputs=None,
232
  )
233
  estimates_table.change(
234
- update_table,
235
  inputs=[gr.Markdown("estimates", visible=False), estimates_table],
236
  outputs=None,
237
  )
238
  votes_table.change(
239
- update_table,
240
  inputs=[gr.Markdown("votes", visible=False), votes_table],
241
  outputs=None,
242
  )
 
 
 
 
 
243
 
244
- new_match_btn.click(
245
- select_and_display_match,
246
- inputs=[],
247
- outputs=[
248
- proposition_a,
249
- proposition_b,
250
- state,
251
- vote_a_btn,
252
- vote_b_btn,
253
- new_match_btn,
254
- ],
255
- )
256
 
257
- # Callbacks pour les deux onglets
258
- vote_a_btn.click(
259
- record_winner_a,
260
- inputs=[state],
 
 
 
 
261
  outputs=[
262
- result,
263
- progress_info,
264
- rankings_table,
265
- vote_a_btn,
266
- vote_b_btn,
267
- new_match_btn,
268
  ],
269
  )
270
- vote_b_btn.click(
271
- record_winner_b,
272
- inputs=[state],
273
- outputs=[
274
- result,
275
- progress_info,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  rankings_table,
277
- vote_a_btn,
278
- vote_b_btn,
279
- new_match_btn,
280
  ],
281
  )
282
 
283
- demo.load(admin_visible, None, admin_tab)
284
- demo.load(welcome_user, None, username)
285
-
286
-
287
- def arena_auth(username, password):
288
- if username == "admin":
289
- return (
290
- password == "fred"
291
- ) # todo : mettre le mot de passe en variable d'environnement
292
- else:
293
- return username == password
294
-
295
 
296
  # Exemple d'utilisation
297
  if __name__ == "__main__":
298
- demo.launch(
299
- auth_message="Connexion à l'arène des prompts"
300
- ) # ajouter share=True pour partager l'interface
 
1
  import gradio as gr
 
2
  from arena import Arena
3
+ import db
4
+ from plot import plot_estimates_distribution
5
 
6
  LABEL_A = "Proposition A"
7
  LABEL_B = "Proposition B"
8
 
9
  ARENA = Arena()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ css_code = """
12
+ /* Cible le premier bloc Markdown par son ID et lui donne un fond vert */
13
+ #green_background input {
14
+ background-color: #41f707; /* Vert très clair */
15
+ }
16
 
17
+ /* Cible le second bloc Markdown par son ID et lui donne un fond rouge */
18
+ #red_background input {
19
+ background-color: #FFCCCC
20
+ }
21
+ """
22
 
23
+ """
24
+ ###############
25
+ Gradio UI setup
26
+ ###############
27
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  with gr.Blocks(
30
  title="Prompt Arena",
31
+ css=css_code,
32
  # theme=gr.themes.Default.load("theme_schema_miku.json"),
33
  ) as demo:
34
+ match_state = gr.State()
35
+ user_state = gr.State()
36
+ timer = gr.Timer(1)
 
 
37
 
38
  gr.Markdown(
39
  '<h1 style="text-align:center;"> Concours du meilleur Prompt Engineer </h1>'
 
44
  value=ARENA.get_progress(),
45
  interactive=False,
46
  lines=2,
47
+ visible=False,
48
  )
49
 
50
  with gr.Tabs() as tabs:
51
+ """
52
+ ##################
53
+ Identification Tab
54
+ ##################
55
+ """
56
+ # Onglet Identification
57
+ with gr.TabItem("Identification", id=0) as identification_tab:
58
+ with gr.Row():
59
+ user_code_box = gr.Textbox(
60
+ label="Code d'identification",
61
+ placeholder="Saisissez votre code d'identification à 4 lettres",
62
+ interactive=True,
63
+ scale=0,
64
+ min_width=250,
65
+ max_lines=1,
66
+ )
67
+ validate_btn = gr.Button(
68
+ "Valider", variant="primary", scale=0, min_width=250
69
+ )
70
+ message = gr.Markdown("")
71
+
72
+ """
73
+ ######################
74
+ Identification Actions
75
+ ######################
76
+ """
77
+
78
+ @validate_btn.click(inputs=[user_code_box], outputs=[message, user_state])
79
+ def on_validate(code):
80
+ if not code or not code.strip():
81
+ return {
82
+ message: gr.update(
83
+ value="Veuillez entrer votre code d'identification.",
84
+ visible=True,
85
+ )
86
+ }
87
+ user = db.get_user(code)
88
+ if not user:
89
+ return {
90
+ message: gr.update(
91
+ value="Code invalide. Veuillez vérifier votre code d'identification.",
92
+ visible=True,
93
+ )
94
+ }
95
+ return {
96
+ message: gr.update(
97
+ value=f"Code reconnu ! Bienvenue joueur {user['username']}",
98
+ visible=True,
99
+ ),
100
+ user_state: user,
101
+ }
102
+
103
+ """
104
+ ##################
105
+ Candidate Tab
106
+ ##################
107
+ """
108
+
109
+ with gr.TabItem("Candidat", id=1, visible=False) as candidat_tab:
110
+ with gr.Row():
111
+ with gr.Column():
112
+ label_input = gr.Markdown(value="Sujet du concours")
113
+ competition_input = gr.Markdown(
114
+ value=db.load("inputs")
115
+ .query("name == 'Competition'")["text"]
116
+ .values[0],
117
+ container=True,
118
+ max_height=100,
119
+ )
120
+ with gr.Row():
121
+ with gr.Column():
122
+ label_candidat = gr.Markdown(
123
+ value="Votre candidat au concours",
124
+ )
125
+ candidat_prompt = gr.Textbox(
126
+ show_label=False,
127
+ placeholder="Saisissez votre texte candidat",
128
+ interactive=True,
129
+ scale=0,
130
+ min_width=600,
131
+ lines=10,
132
+ max_lines=10,
133
+ )
134
+ submit_prompt_btn = gr.Button(
135
+ "Valider", variant="primary", scale=0, min_width=250
136
+ )
137
+
138
+ message_prompt = gr.Markdown("", visible=False)
139
+
140
+ with gr.Column():
141
+ label_preview = gr.Markdown(
142
+ value="Aperçu du rendu (pour les contenus markdown ou html)",
143
+ visible=False,
144
+ )
145
+ candidat_preview = gr.Markdown(
146
+ value="", max_height=300, visible=False, container=True
147
+ )
148
+
149
+ candidat_prompt.change(
150
+ lambda text: (
151
+ gr.update(value=text if text else "", visible=True if text else False),
152
+ gr.update(visible=True if text else False),
153
+ ),
154
+ inputs=candidat_prompt,
155
+ outputs=[candidat_preview, label_preview],
156
+ )
157
+
158
+ """
159
+ ##################
160
+ Compétition Tab
161
+ ##################
162
+ """
163
+
164
+ with gr.TabItem("Compétition", id=2, visible=False) as competition_tab:
165
  with gr.Row():
166
  new_match_btn = gr.Button("Lancer un nouveau match", variant="primary")
167
 
168
  with gr.Row():
169
  with gr.Column():
170
+ label_prop_a = gr.Markdown(LABEL_A, visible=False)
171
+ proposition_a = gr.Markdown(container=True, visible=False)
172
+ vote_a_btn = gr.Button("Choisir " + LABEL_A, visible=False)
173
  with gr.Column():
174
+ label_prop_b = gr.Markdown(LABEL_B, visible=False)
175
+ proposition_b = gr.Markdown(container=True, visible=False)
176
+ vote_b_btn = gr.Button("Choisir " + LABEL_B, visible=False)
177
+ result = gr.Markdown("")
178
 
179
  # with gr.TabItem("Classement"):
180
+
181
+ @new_match_btn.click(
182
+ inputs=[user_state],
183
+ outputs=[
184
+ label_prop_a,
185
+ label_prop_b,
186
+ proposition_a,
187
+ proposition_b,
188
+ match_state,
189
+ vote_a_btn,
190
+ vote_b_btn,
191
+ new_match_btn,
192
+ result,
193
+ ],
194
+ )
195
+ def select_and_display_match(user_state):
196
+ selected_match = ARENA.select_match(user_state)
197
+ if not selected_match:
198
+ return {
199
+ label_prop_a: gr.update(visible=False),
200
+ label_prop_b: gr.update(visible=False),
201
+ proposition_a: gr.update(value="", visible=False),
202
+ proposition_b: gr.update(value="", visible=False),
203
+ match_state: {},
204
+ vote_a_btn: gr.update(visible=False),
205
+ vote_b_btn: gr.update(visible=False),
206
+ new_match_btn: gr.update(visible=False),
207
+ result: gr.update(
208
+ value="Vous avez déjà voté pour tous les matchs disponibles. Veuillez attendre la fin de la compétition"
209
+ ),
210
+ }
211
+ else:
212
+ prompt_a, prompt_b = selected_match
213
+ return {
214
+ label_prop_a: gr.update(visible=True),
215
+ label_prop_b: gr.update(visible=True),
216
+ proposition_a: gr.update(value=prompt_a["text"], visible=True),
217
+ proposition_b: gr.update(value=prompt_b["text"], visible=True),
218
+ match_state: {
219
+ "prompt_a_id": prompt_a["id"],
220
+ "prompt_b_id": prompt_b["id"],
221
+ },
222
+ vote_a_btn: gr.update(visible=True),
223
+ vote_b_btn: gr.update(visible=True),
224
+ new_match_btn: gr.update(visible=False),
225
+ result: gr.update(value=""), # Reset result
226
+ }
227
+
228
+ @vote_a_btn.click(
229
+ inputs=[match_state, user_state],
230
+ outputs=[
231
+ result,
232
+ vote_a_btn,
233
+ vote_b_btn,
234
+ new_match_btn,
235
+ ],
236
+ )
237
+ def record_winner_a(match_state, user_state):
238
+ prompt_a_id = match_state["prompt_a_id"]
239
+ prompt_b_id = match_state["prompt_b_id"]
240
+ ARENA.record_result(prompt_a_id, prompt_b_id, user_state["id"])
241
+ return {
242
+ result: f"Vous avez choisi : {LABEL_A}",
243
+ vote_a_btn: gr.update(visible=False),
244
+ vote_b_btn: gr.update(visible=False),
245
+ new_match_btn: gr.update(visible=True),
246
+ }
247
+
248
+ @vote_b_btn.click(
249
+ inputs=[match_state, user_state],
250
+ outputs=[
251
+ result,
252
+ vote_a_btn,
253
+ vote_b_btn,
254
+ new_match_btn,
255
+ ],
256
+ )
257
+ def record_winner_b(match_state, user_state):
258
+ prompt_a_id = match_state["prompt_a_id"]
259
+ prompt_b_id = match_state["prompt_b_id"]
260
+ ARENA.record_result(prompt_b_id, prompt_a_id, user_state["id"])
261
+ return {
262
+ result: f"Vous avez choisi : {LABEL_B}",
263
+ vote_a_btn: gr.update(visible=False),
264
+ vote_b_btn: gr.update(visible=False),
265
+ new_match_btn: gr.update(visible=True),
266
+ }
267
+
268
+ """
269
+ ##################
270
+ Result Tab
271
+ ##################
272
+ """
273
+ with gr.TabItem("Classement") as result_tab:
274
  rankings_table = gr.DataFrame(
275
+ label="Classement des équipes",
276
  value=ARENA.get_rankings(),
277
  interactive=True,
278
  )
279
 
280
+ """
281
+ ##################
282
+ Follow up Tab
283
+ ##################
284
+ """
285
+
286
+ with gr.TabItem("Followup") as admin_followup_tab:
287
+ with gr.Accordion("Matrice de compétition", open=False):
288
+ competition_matrix = gr.DataFrame(
289
+ value=ARENA.get_competition_matrix(),
290
+ interactive=True,
291
+ )
292
+ with gr.Accordion("Distribution des estimations", open=False):
293
+ plot_estimates = gr.Plot(
294
+ plot_estimates_distribution,
295
+ label="Distribution des estimations",
296
+ )
297
+
298
+ """
299
+ ##################
300
+ Database Tab
301
+ ##################
302
+ """
303
+ with gr.TabItem("Database") as admin_db_tab:
304
+ with gr.Accordion("Switches", open=False):
305
+ switches_table = gr.DataFrame(
306
+ value=db.load("switches"),
307
+ interactive=True,
308
+ )
309
+ with gr.Accordion("Inputs", open=False):
310
+ inputs_table = gr.DataFrame(
311
+ value=db.load("inputs"),
312
+ interactive=True,
313
+ )
314
  with gr.Accordion("Prompts", open=False):
315
  prompts_table = gr.DataFrame(
316
+ value=db.load("prompts"),
317
  interactive=True,
318
  )
319
  with gr.Accordion("Estimates", open=False):
320
  estimates_table = gr.DataFrame(
321
  label="Estimations",
322
+ value=db.load("estimates"),
323
  interactive=True,
324
  )
325
  with gr.Accordion("Votes", open=False):
326
  votes_table = gr.DataFrame(
327
  label="Votes",
328
+ value=db.load("votes"),
329
+ interactive=True,
330
+ )
331
+ with gr.Accordion("Teams", open=False):
332
+ teams_table = gr.DataFrame(
333
+ label="Teams",
334
+ value=db.load("teams"),
335
+ interactive=True,
336
+ )
337
+ with gr.Accordion("Users", open=False):
338
+ users_table = gr.DataFrame(
339
+ label="Users",
340
+ value=db.load("users"),
341
  interactive=True,
342
  )
343
+
344
+ switches_table.change(
345
+ db.replace,
346
+ inputs=[gr.Markdown("switches", visible=False), switches_table],
347
+ outputs=None,
348
+ )
349
+ inputs_table.change(
350
+ db.replace,
351
+ inputs=[gr.Markdown("inputs", visible=False), inputs_table],
352
+ outputs=None,
353
+ )
354
  prompts_table.change(
355
+ db.replace,
356
  inputs=[gr.Markdown("prompts", visible=False), prompts_table],
357
  outputs=None,
358
  )
359
  estimates_table.change(
360
+ db.replace,
361
  inputs=[gr.Markdown("estimates", visible=False), estimates_table],
362
  outputs=None,
363
  )
364
  votes_table.change(
365
+ db.replace,
366
  inputs=[gr.Markdown("votes", visible=False), votes_table],
367
  outputs=None,
368
  )
369
+ teams_table.change(
370
+ db.replace,
371
+ inputs=[gr.Markdown("teams", visible=False), teams_table],
372
+ outputs=None,
373
+ )
374
 
375
+ users_table.change(
376
+ db.replace,
377
+ inputs=[gr.Markdown("users", visible=False), users_table],
378
+ outputs=None,
379
+ )
 
 
 
 
 
 
 
380
 
381
+ """
382
+ ######################
383
+ Candidat Actions
384
+ ######################
385
+ """
386
+
387
+ @submit_prompt_btn.click(
388
+ inputs=[candidat_prompt, user_state],
389
  outputs=[
390
+ submit_prompt_btn,
391
+ candidat_prompt,
392
+ message_prompt,
393
+ competition_tab,
394
+ tabs,
 
395
  ],
396
  )
397
+ def submit_prompt(prompt_text, user_state):
398
+ if not prompt_text or not prompt_text.strip():
399
+ return {
400
+ message_prompt: gr.update(
401
+ value="Veuillez saisir un prompt",
402
+ visible=True,
403
+ ),
404
+ }
405
+ db_team_prompt = db.get_prompt(user_state["team"])
406
+ if db_team_prompt:
407
+ return {
408
+ submit_prompt_btn: gr.update(interactive=False),
409
+ candidat_prompt: gr.update(value=db_team_prompt, interactive=False),
410
+ message_prompt: gr.update(
411
+ value="Votre équipe a déjà enregistré un candidat.",
412
+ visible=True,
413
+ ),
414
+ }
415
+ db.insert("prompts", {"name": f"{user_state['team']}", "text": prompt_text})
416
+ # Initialize estimates for the newly submitted prompt
417
+ all_prompts = db.load("prompts")
418
+ prompt_id = all_prompts[all_prompts["name"] == user_state["team"]].iloc[0]["id"]
419
+ ARENA.init_estimates(prompt_id)
420
+ return {
421
+ submit_prompt_btn: gr.update(interactive=False),
422
+ candidat_prompt: gr.update(interactive=False),
423
+ message_prompt: gr.update(
424
+ value="Prompt soumis avec succès !", visible=True
425
+ ),
426
+ competition_tab: gr.update(visible=True),
427
+ tabs: gr.Tabs(selected=2),
428
+ }
429
+
430
+ """
431
+ ######################
432
+ Periodic refresh
433
+ ######################
434
+ """
435
+
436
+ def refresh(user_state):
437
+ if user_state is None:
438
+ return {
439
+ candidat_tab: gr.update(visible=False),
440
+ competition_tab: gr.update(visible=False),
441
+ result_tab: gr.update(visible=False),
442
+ admin_db_tab: gr.update(visible=False),
443
+ admin_followup_tab: gr.update(visible=False),
444
+ }
445
+
446
+ team = user_state.get("team", "")
447
+
448
+ update_prompt = {}
449
+ prompt_team = db.get_prompt(team)
450
+ if prompt_team:
451
+ update_prompt = {
452
+ candidat_prompt: gr.update(value=prompt_team, interactive=False),
453
+ submit_prompt_btn: gr.update(interactive=False, visible=False),
454
+ message_prompt: gr.update(
455
+ value="Votre équipe a déjà enregistré un candidat.", visible=True
456
+ ),
457
+ }
458
+
459
+ update_tabs = {}
460
+ if user_state["team"] == "Admin":
461
+ update_tabs = {
462
+ candidat_tab: gr.update(visible=True),
463
+ competition_tab: gr.update(visible=True),
464
+ result_tab: gr.update(visible=True),
465
+ admin_db_tab: gr.update(visible=True),
466
+ admin_followup_tab: gr.update(visible=True),
467
+ }
468
+ else:
469
+ update_tabs = {
470
+ candidat_tab: gr.update(visible=db.get_status("Candidate")),
471
+ competition_tab: gr.update(visible=db.get_status("Competition")),
472
+ result_tab: gr.update(visible=db.get_status("Result")),
473
+ admin_db_tab: gr.update(visible=False),
474
+ admin_followup_tab: gr.update(visible=False),
475
+ }
476
+
477
+ update_tables = {
478
+ prompts_table: gr.update(value=db.load("prompts")),
479
+ estimates_table: gr.update(value=db.load("estimates")),
480
+ votes_table: gr.update(value=db.load("votes")),
481
+ teams_table: gr.update(value=db.load("teams")),
482
+ users_table: gr.update(value=db.load("users")),
483
+ competition_input: gr.update(
484
+ value=db.load("inputs").query("name == 'Competition'")["text"].values[0]
485
+ ),
486
+ plot_estimates: gr.update(value=plot_estimates_distribution()),
487
+ rankings_table: gr.update(value=ARENA.get_rankings()),
488
+ competition_matrix: gr.update(value=ARENA.get_competition_matrix()),
489
+ }
490
+
491
+ return {**update_prompt, **update_tabs, **update_tables}
492
+
493
+ timer.tick(
494
+ refresh,
495
+ [user_state],
496
+ [
497
+ prompts_table,
498
+ estimates_table,
499
+ votes_table,
500
+ teams_table,
501
+ users_table,
502
+ candidat_prompt,
503
+ submit_prompt_btn,
504
+ message_prompt,
505
+ candidat_tab,
506
+ competition_tab,
507
+ result_tab,
508
+ admin_db_tab,
509
+ admin_followup_tab,
510
+ plot_estimates,
511
  rankings_table,
512
+ competition_matrix,
513
+ competition_input,
 
514
  ],
515
  )
516
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  # Exemple d'utilisation
519
  if __name__ == "__main__":
520
+ demo.launch() # ajouter share=True pour partager l'interface
 
 
arena.py CHANGED
@@ -1,7 +1,5 @@
1
  import trueskill as ts
2
  import pandas as pd
3
- import random
4
- import datetime
5
  from typing import Dict, List, Tuple, Union
6
  import db
7
  import pandas as pd
@@ -22,41 +20,28 @@ class Arena:
22
  Une arène pour comparer et classer des prompts en utilisant l'algorithme TrueSkill.
23
  """
24
 
25
- def init_estimates(self, reboot=True) -> None:
26
  """
27
- Initialise les estimations des prompts avec des ratings TrueSkill par défaut.
28
- reboot : si le fichier estimates.csv existe déjà, on le laisse tel quel.
29
  """
30
  estimates = db.load("estimates")
31
- if not estimates.empty and reboot:
32
- return None
33
- if estimates.empty:
34
- for i in db.load("prompts")["id"].to_list():
35
- db.insert(
36
- "estimates",
37
- {
38
- "prompt_id": i,
39
- "mu": MU_init,
40
- "sigma": SIGMA_init,
41
- },
42
- )
43
-
44
- def load(self, table_name: str) -> pd.DataFrame:
45
- """
46
- fonction back pour l'UI.
47
- Charge les données d'une table depuis le fichier CSV.
48
- """
49
- return db.load(table_name)
50
 
51
- def replace(self, table_name: str, df: pd.DataFrame) -> pd.DataFrame:
52
- """
53
- fonction back pour l'UI.
54
- Remplace le contenu d'une table par les données du fichier CSV.
55
- Pour l'admin uniquement
56
- """
57
- return db.replace(table_name, df)
 
58
 
59
- def select_match(self) -> Tuple[Prompt, Prompt]:
60
  """
61
  Sélectionne deux prompts pour un match en privilégiant ceux avec une grande incertitude.
62
  Returns:
@@ -64,31 +49,62 @@ class Arena:
64
  """
65
  # le prompt le plus incertain (sigma le plus élevé)
66
  estimates = db.load("estimates")
67
- estimate_a = estimates.sort_values(by="sigma", ascending=False).iloc[0]
68
-
69
- # le prompt le plus proche en niveau (mu) du prompt_a
70
- estimate_b = (
71
- estimates.loc[estimates["prompt_id"] != estimate_a["prompt_id"]]
72
- .assign(delta_mu=lambda df_: abs(df_["mu"] - estimate_a["mu"]))
73
- .sort_values(by="delta_mu", ascending=True)
74
- .iloc[0]
 
 
 
 
 
 
 
 
 
 
 
 
75
  )
76
 
77
- prompts = db.load("prompts")
78
- prompt_a = prompts.query(f"id == {estimate_a['prompt_id']}").iloc[0].to_dict()
79
- prompt_b = prompts.query(f"id == {estimate_b['prompt_id']}").iloc[0].to_dict()
80
- # We need to update the selection strategy to prefer prompts with high uncertainty
81
- # but also consider prompts that are close in ranking (within 5 positions)
 
 
 
 
 
 
 
 
 
 
82
 
83
- # Create pairs of prompts that are at most 5 positions apart in the ranking
84
- # close_pairs = []
85
- # for i in range(len(prompt_ids)):
86
- # for j in range(i + 1, min(i + 6, len(prompt_ids))):
87
- # close_pairs.append((prompt_ids[i], prompt_ids[j]))
88
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  return prompt_a, prompt_b
90
 
91
- def record_result(self, winner_id: str, loser_id: str) -> None:
92
  # Obtenir les ratings actuels
93
  estimates = db.load("estimates")
94
  winner_estimate = (
@@ -117,6 +133,7 @@ class Arena:
117
  {
118
  "winner_id": winner_id,
119
  "loser_id": loser_id,
 
120
  # "timestamp": datetime.datetime.now().isoformat(),
121
  },
122
  )
@@ -137,8 +154,56 @@ class Arena:
137
  rankings = prompts.merge(estimates, left_on="id", right_on="prompt_id").drop(
138
  columns=["id", "prompt_id"]
139
  )
140
- return rankings.sort_values(by="mu", ascending=False)
 
 
 
141
  # eventuellement afficher plutôt mu - 3 sigma pour être conservateur
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  def get_progress(self) -> str:
144
  """
 
1
  import trueskill as ts
2
  import pandas as pd
 
 
3
  from typing import Dict, List, Tuple, Union
4
  import db
5
  import pandas as pd
 
20
  Une arène pour comparer et classer des prompts en utilisant l'algorithme TrueSkill.
21
  """
22
 
23
+ def init_estimates(self, prompt_id) -> None:
24
  """
25
+ Initialise les estimations d'un prompt avec des ratings TrueSkill par défaut.
 
26
  """
27
  estimates = db.load("estimates")
28
+ if prompt_id in estimates["prompt_id"].values:
29
+ # supprimer la ligne existante
30
+ db.delete(
31
+ "estimates",
32
+ int(estimates[estimates["prompt_id"] == prompt_id].iloc[0].id),
33
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ db.insert(
36
+ "estimates",
37
+ {
38
+ "prompt_id": prompt_id,
39
+ "mu": MU_init,
40
+ "sigma": SIGMA_init,
41
+ },
42
+ )
43
 
44
+ def select_match(self, user_state) -> Tuple[Prompt, Prompt] | None:
45
  """
46
  Sélectionne deux prompts pour un match en privilégiant ceux avec une grande incertitude.
47
  Returns:
 
49
  """
50
  # le prompt le plus incertain (sigma le plus élevé)
51
  estimates = db.load("estimates")
52
+ # retirer le prompt de l'utilisateur pour qu'il ne puisse pas voter pour son propre prompt
53
+ estimates = estimates[
54
+ estimates["prompt_id"] != db.get_prompt_id(user_state["team"])
55
+ ]
56
+
57
+ def order_match(id_a, id_b):
58
+ """Return a tuple of ids ordered by the id."""
59
+ return (id_a, id_b) if id_a < id_b else (id_b, id_a)
60
+
61
+ # les matchs possibles entre les prompts par ordre d'incertitude décroissant
62
+ matches = (
63
+ estimates.merge(estimates, how="cross", suffixes=["_a", "_b"])
64
+ .query("id_a != id_b")
65
+ .assign(delta_mu=lambda df_: abs(df_["mu_a"] - df_["mu_b"]))
66
+ .sort_values(by=["sigma_a", "delta_mu"], ascending=[False, True])
67
+ .assign(
68
+ match=lambda df_: df_.apply(
69
+ lambda row: order_match(int(row["id_a"]), int(row["id_b"])), axis=1
70
+ )
71
+ )
72
  )
73
 
74
+ user_votes = db.load("votes").loc[
75
+ lambda df_: df_["user_id"] == user_state["id"]
76
+ ]
77
+
78
+ if user_votes.empty:
79
+ user_votes = user_votes.assign(match=[])
80
+ else: # les votes de l'utilisateur
81
+ user_votes = user_votes.assign(
82
+ match=lambda df_: df_.apply(
83
+ lambda row: order_match(
84
+ int(row["winner_id"]), int(row["loser_id"])
85
+ ),
86
+ axis=1,
87
+ )
88
+ )
89
 
90
+ # on ne garde que les matchs qui n'ont pas encore été votés par l'utilisateur
91
+ user_matches = matches.loc[~matches["match"].isin(user_votes["match"])]
 
 
 
92
 
93
+ if user_matches.empty:
94
+ # Si l'utilisateur a déjà voté sur tous les matchs, on ne peut pas en sélectionner de nouveaux
95
+ return None
96
+
97
+ selected_match = user_matches.iloc[0]
98
+ prompts = db.load("prompts")
99
+ prompt_a = (
100
+ prompts.query(f"id == {selected_match['prompt_id_a']}").iloc[0].to_dict()
101
+ )
102
+ prompt_b = (
103
+ prompts.query(f"id == {selected_match['prompt_id_b']}").iloc[0].to_dict()
104
+ )
105
  return prompt_a, prompt_b
106
 
107
+ def record_result(self, winner_id: str, loser_id: str, user_id: str) -> None:
108
  # Obtenir les ratings actuels
109
  estimates = db.load("estimates")
110
  winner_estimate = (
 
133
  {
134
  "winner_id": winner_id,
135
  "loser_id": loser_id,
136
+ "user_id": user_id,
137
  # "timestamp": datetime.datetime.now().isoformat(),
138
  },
139
  )
 
154
  rankings = prompts.merge(estimates, left_on="id", right_on="prompt_id").drop(
155
  columns=["id", "prompt_id"]
156
  )
157
+ rankings = rankings.sort_values(by="mu", ascending=False)
158
+ # ajouter un colonne position
159
+ rankings["position"] = range(1, len(rankings) + 1)
160
+
161
  # eventuellement afficher plutôt mu - 3 sigma pour être conservateur
162
+ # rankings["score"] = rankings["mu"] - 3 * rankings["sigma"]
163
+ return rankings[["position", "team"]]
164
+
165
+ def get_competition_matrix(self) -> pd.DataFrame:
166
+ """
167
+ Obtient la matrice de combats des prompts.
168
+
169
+ Returns:
170
+ DataFrame contenant en ligne et en colonne les noms d'équipes,
171
+ et dans la cellule le pourcentage de victoires de l'équipe de la ligne contre l'équipe de la colonne.
172
+ """
173
+
174
+ prompts = db.load("prompts")
175
+ votes = db.load("votes")
176
+
177
+ competition_matrix = pd.DataFrame(
178
+ index=prompts["team"], columns=prompts["team"], data=0
179
+ )
180
+ competition_matrix.index.name = None
181
+ competition_matrix.columns.name = None
182
+
183
+ wins = competition_matrix.copy()
184
+ matches = competition_matrix.copy()
185
+
186
+ for _, row in votes.iterrows():
187
+ winner_name = prompts.loc[prompts["id"] == row["winner_id"], "team"].values[
188
+ 0
189
+ ]
190
+ loser_name = prompts.loc[prompts["id"] == row["loser_id"], "team"].values[0]
191
+ wins.at[winner_name, loser_name] += 1
192
+ matches.at[winner_name, loser_name] += 1
193
+ matches.at[loser_name, winner_name] += 1
194
+
195
+ competition_matrix = wins.div(matches)
196
+
197
+ competition_matrix = competition_matrix.map(
198
+ lambda x: "" if pd.isna(x) or x == 0 else f"{x:.0%}"
199
+ )
200
+
201
+ for i in range(len(competition_matrix)):
202
+ competition_matrix.iloc[i, i] = "X"
203
+
204
+ competition_matrix = competition_matrix.replace("", "?").reset_index(names="")
205
+
206
+ return competition_matrix
207
 
208
  def get_progress(self) -> str:
209
  """
database/estimates.csv CHANGED
@@ -1,6 +1 @@
1
  id,prompt_id,mu,sigma
2
- 1,1,40.23772508094016,2
3
- 2,2,26.46775376631428,4
4
- 3,3,22.084687989982868,3.547977413152066
5
- 4,4,24.390885439458472,3.2275080660341553
6
- 5,5,9.29883828658184,3.5631194000412036
 
1
  id,prompt_id,mu,sigma
 
 
 
 
 
database/inputs.csv ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ id,name,text
2
+ 1,Competition,"Insérer le sujet de la compétition"
database/prompts.csv CHANGED
@@ -1,6 +1 @@
1
- id,name,text
2
- 1,A,Prompt A 100
3
- 2,B,Prompt B 90
4
- 3,C,Prompt C 60
5
- 4,D,Prompt D 55
6
- 5,E,Prompt E 30
 
1
+ id,team,text
 
 
 
 
 
database/switches.csv ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ id,name,status
2
+ 1,Candidate,0
3
+ 2,Competition,0
4
+ 3,Result,0
5
+ 4,Admin,0
database/teams.csv ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,name
2
+ 1,Rock
3
+ 2,Pop
4
+ 3,Jazz
5
+ 4,Blues
6
+ 5,Salsa
7
+ 6,Reggae
8
+ 7,Disco
9
+ 8,Tango
10
+ 9,Soul
11
+ 10,Rap
12
+ 11,Techno
13
+ 12,Folk
14
+ 13,Bossa
15
+ 14,Country
16
+ 15,Gospel
database/users.csv ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,team,username,code
2
+ 0,Admin,Admin,MSTR
3
+ 1,Rock,Rock 1,BGSN
4
+ 2,Rock,Rock 2,SGEF
5
+ 3,Rock,Rock 3,RSET
6
+ 4,Pop,Pop 1,BHWQ
7
+ 5,Pop,Pop 2,GEGX
8
+ 6,Pop,Pop 3,WTRR
9
+ 7,Jazz,Jazz 1,FZBH
10
+ 8,Jazz,Jazz 2,SZUO
11
+ 9,Jazz,Jazz 3,JCCO
12
+ 10,Blues,Blues 1,WKTP
13
+ 11,Blues,Blues 2,DAOA
14
+ 12,Blues,Blues 3,EEPL
15
+ 13,Salsa,Salsa 1,OZPN
16
+ 14,Salsa,Salsa 2,REBM
17
+ 15,Salsa,Salsa 3,DKAH
18
+ 16,Reggae,Reggae 1,VUCY
19
+ 17,Reggae,Reggae 2,VISH
20
+ 18,Reggae,Reggae 3,RWAG
21
+ 19,Disco,Disco 1,PXRJ
22
+ 20,Disco,Disco 2,JJQL
23
+ 21,Disco,Disco 3,HNUT
24
+ 22,Tango,Tango 1,MHBJ
25
+ 23,Tango,Tango 2,BSLF
26
+ 24,Tango,Tango 3,XNKK
27
+ 25,Soul,Soul 1,IZGQ
28
+ 26,Soul,Soul 2,OUGL
29
+ 27,Soul,Soul 3,XEAO
30
+ 28,Rap,Rap 1,CYMX
31
+ 29,Rap,Rap 2,WAFZ
32
+ 30,Rap,Rap 3,BPIR
33
+ 31,Techno,Techno 1,TMCY
34
+ 32,Techno,Techno 2,TXMK
35
+ 33,Techno,Techno 3,NVUO
36
+ 34,Folk,Folk 1,ZATS
37
+ 35,Folk,Folk 2,LNWA
38
+ 36,Folk,Folk 3,HOKK
39
+ 37,Bossa,Bossa 1,FWQM
40
+ 38,Bossa,Bossa 2,UKWF
41
+ 39,Bossa,Bossa 3,EHDR
42
+ 40,Country,Country 1,MKHF
43
+ 41,Country,Country 2,UGCW
44
+ 42,Country,Country 3,KBSO
45
+ 43,Gospel,Gospel 1,IZJB
46
+ 44,Gospel,Gospel 2,HIJK
47
+ 45,Gospel,Gospel 3,JSNN
database/votes.csv CHANGED
@@ -1,11 +1 @@
1
- id,winner_id,loser_id
2
- 1,4,5
3
- 2,1,2
4
- 3,2,3
5
- 4,4,5
6
- 5,1,2
7
- 6,4,5
8
- 7,1,2
9
- 8,4,3
10
- 9,3,5
11
- 10,3,5
 
1
+ id,winner_id,loser_id,user_id
 
 
 
 
 
 
 
 
 
 
db.py CHANGED
@@ -12,7 +12,7 @@ Pour être à jour des mises à jours des autres utilisateurs, il faut recharger
12
  DATABASE = {
13
  "prompts": {
14
  "filename": "database/prompts.csv",
15
- "columns": ["id", "name", "text"],
16
  },
17
  "estimates": {
18
  "filename": "database/estimates.csv",
@@ -22,6 +22,22 @@ DATABASE = {
22
  "filename": "database/votes.csv",
23
  "columns": ["id", "winner_id", "loser_id"],
24
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
 
@@ -53,10 +69,22 @@ def update(table_name: str, row_id: int, data: dict) -> None:
53
  """
54
  df = load(table_name)
55
  idx = df.index[df["id"] == row_id]
56
- if not idx.empty:
57
- for key, value in data.items():
58
- df.loc[idx, key] = value
59
- df.to_csv(DATABASE[table_name]["filename"], index=False)
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
 
62
  def replace(table_name: str, df: pd.DataFrame) -> None:
@@ -66,3 +94,59 @@ def replace(table_name: str, df: pd.DataFrame) -> None:
66
  db.replace("prompts", df)
67
  """
68
  df.to_csv(DATABASE[table_name]["filename"], index=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  DATABASE = {
13
  "prompts": {
14
  "filename": "database/prompts.csv",
15
+ "columns": ["id", "team", "text"],
16
  },
17
  "estimates": {
18
  "filename": "database/estimates.csv",
 
22
  "filename": "database/votes.csv",
23
  "columns": ["id", "winner_id", "loser_id"],
24
  },
25
+ "teams": {
26
+ "filename": "database/teams.csv",
27
+ "columns": ["id", "name"],
28
+ },
29
+ "users": {
30
+ "filename": "database/users.csv",
31
+ "columns": ["id", "team", "username", "code"],
32
+ },
33
+ "switches": {
34
+ "filename": "database/switches.csv",
35
+ "columns": ["id", "name", "status"],
36
+ },
37
+ "inputs": {
38
+ "filename": "database/inputs.csv",
39
+ "columns": ["id", "name", "text"],
40
+ },
41
  }
42
 
43
 
 
69
  """
70
  df = load(table_name)
71
  idx = df.index[df["id"] == row_id]
72
+ if idx.empty:
73
+ idx = len(df)
74
+ for key, value in data.items():
75
+ df.loc[idx, key] = value
76
+ df.to_csv(DATABASE[table_name]["filename"], index=False)
77
+
78
+
79
+ def delete(table_name: str, id: int) -> None:
80
+ """
81
+ Supprime une ligne de la table spécifiée par son id.
82
+ Exemple :
83
+ db.delete("prompts", 5)
84
+ """
85
+ df = load(table_name)
86
+ df = df[df["id"] != id]
87
+ df.to_csv(DATABASE[table_name]["filename"], index=False)
88
 
89
 
90
  def replace(table_name: str, df: pd.DataFrame) -> None:
 
94
  db.replace("prompts", df)
95
  """
96
  df.to_csv(DATABASE[table_name]["filename"], index=False)
97
+
98
+
99
+ def get_prompt(team: str) -> str | None:
100
+ """
101
+ Retourne le prompt de l'équipe spécifiée.
102
+ Si l'équipe n'a pas de prompt, retourne une chaîne vide.
103
+ """
104
+ prompts_df = load("prompts")
105
+ row = prompts_df[prompts_df["team"] == team]
106
+ if not row.empty:
107
+ return row.iloc[0]["text"]
108
+ return None
109
+
110
+
111
+ def get_prompt_id(team: str) -> str | None:
112
+ """
113
+ Retourne le prompt de l'équipe spécifiée.
114
+ Si l'équipe n'a pas de prompt, retourne une chaîne vide.
115
+ """
116
+ prompts_df = load("prompts")
117
+ row = prompts_df[prompts_df["team"] == team]
118
+ if not row.empty:
119
+ return row.iloc[0]["id"]
120
+ return None
121
+
122
+
123
+ def get_status(name: str) -> bool:
124
+ """
125
+ Retourne le statut du switch spécifié.
126
+ Si le nom du switch n'existe pas en base, retourne 0.
127
+ """
128
+ switches_df = load("switches")
129
+ row = switches_df[switches_df["name"] == name]
130
+ if not row.empty:
131
+ return bool(int(row.fillna(0).iloc[0]["status"]))
132
+ return False
133
+
134
+
135
+ def set_status(name: str, status: int) -> None:
136
+ """
137
+ Met à jour le statut du switch spécifié.
138
+ Si le nom du switch n'existe pas, l'ajoute avec le statut donné.
139
+ """
140
+ switches_df = load("switches")
141
+ switches_df.loc[switches_df["name"] == name, "status"] = status
142
+ replace("switches", switches_df)
143
+
144
+
145
+ def get_user(code: str) -> dict | None:
146
+ """
147
+ Retourne les informations de l'utilisateur si le code est valide.
148
+ Si le code est invalide, retourne None.
149
+ """
150
+ users_df = load("users")
151
+ row = users_df.loc[users_df["code"] == code, ["id", "team", "username"]]
152
+ return row.to_dict(orient="records")[0] if not row.empty else None
plot.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import db
2
+ import numpy as np
3
+ import plotly.graph_objects as go
4
+
5
+
6
+ def plot_estimates_distribution():
7
+ """Affiche une gaussienne par prompt (Plotly) + lignes verticales pointillées sur les moyennes."""
8
+ estimates = db.load("estimates")
9
+ prompts = db.load("prompts")
10
+ if estimates.empty or prompts.empty:
11
+ fig = go.Figure()
12
+ fig.add_annotation(
13
+ text="Aucune estimation disponible", x=0.5, y=0.5, showarrow=False
14
+ )
15
+ return fig
16
+ x = np.linspace(
17
+ estimates["mu"].min() - 3 * estimates["sigma"].max(),
18
+ estimates["mu"].max() + 3 * estimates["sigma"].max(),
19
+ 500,
20
+ )
21
+ fig = go.Figure()
22
+ shapes = []
23
+ # Une gaussienne par prompt
24
+ for _, row in estimates.iterrows():
25
+ mu = row["mu"]
26
+ sigma = row["sigma"]
27
+ prompt_id = row["prompt_id"] if "prompt_id" in row else row["id"]
28
+ # Chercher le nom du prompt
29
+ name = str(prompt_id)
30
+ if "name" in prompts.columns:
31
+ match = prompts[prompts["id"] == prompt_id]
32
+ if not match.empty:
33
+ name = match.iloc[0]["name"]
34
+ y = 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-0.5 * ((x - mu) / sigma) ** 2)
35
+ fig.add_trace(
36
+ go.Scatter(
37
+ x=x,
38
+ y=y,
39
+ mode="lines",
40
+ name=f"{name}",
41
+ hovertemplate=f"<b>{name}</b><br>Score (mu): {mu:.2f}<br>Sigma: {sigma:.2f}<extra></extra>",
42
+ )
43
+ )
44
+ # Ajout de la ligne verticale pointillée à mu (en gris)
45
+ shapes.append(
46
+ dict(
47
+ type="line",
48
+ x0=mu,
49
+ x1=mu,
50
+ y0=0,
51
+ y1=max(y),
52
+ line=dict(
53
+ color="gray",
54
+ width=2,
55
+ dash="dot",
56
+ ),
57
+ xref="x",
58
+ yref="y",
59
+ )
60
+ )
61
+ fig.update_layout(
62
+ title="Distribution gaussienne de chaque prompt",
63
+ xaxis_title="Score (mu)",
64
+ yaxis_title="Densité",
65
+ template="plotly_white",
66
+ shapes=shapes,
67
+ )
68
+ return fig
poetry.lock CHANGED
@@ -2019,6 +2019,70 @@ httpx = "*"
2019
  [package.extras]
2020
  dev = ["pytest"]
2021
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2022
  [[package]]
2023
  name = "semantic-version"
2024
  version = "2.10.0"
@@ -2110,6 +2174,18 @@ anyio = ">=3.6.2,<5"
2110
  [package.extras]
2111
  full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
2112
 
 
 
 
 
 
 
 
 
 
 
 
 
2113
  [[package]]
2114
  name = "tomlkit"
2115
  version = "0.13.3"
@@ -2386,4 +2462,4 @@ files = [
2386
  [metadata]
2387
  lock-version = "2.1"
2388
  python-versions = ">=3.12,<4.0"
2389
- content-hash = "f76406c4e9dc01fbaacdfcb31eb2cb78221ae89bc68a1d961b2841e6670f9ec4"
 
2019
  [package.extras]
2020
  dev = ["pytest"]
2021
 
2022
+ [[package]]
2023
+ name = "scipy"
2024
+ version = "1.15.3"
2025
+ description = "Fundamental algorithms for scientific computing in Python"
2026
+ optional = false
2027
+ python-versions = ">=3.10"
2028
+ groups = ["main"]
2029
+ files = [
2030
+ {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"},
2031
+ {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"},
2032
+ {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f"},
2033
+ {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92"},
2034
+ {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82"},
2035
+ {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40"},
2036
+ {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e"},
2037
+ {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c"},
2038
+ {file = "scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13"},
2039
+ {file = "scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b"},
2040
+ {file = "scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba"},
2041
+ {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65"},
2042
+ {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1"},
2043
+ {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889"},
2044
+ {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982"},
2045
+ {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9"},
2046
+ {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594"},
2047
+ {file = "scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb"},
2048
+ {file = "scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019"},
2049
+ {file = "scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6"},
2050
+ {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477"},
2051
+ {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c"},
2052
+ {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45"},
2053
+ {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49"},
2054
+ {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e"},
2055
+ {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539"},
2056
+ {file = "scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed"},
2057
+ {file = "scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759"},
2058
+ {file = "scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62"},
2059
+ {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb"},
2060
+ {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730"},
2061
+ {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825"},
2062
+ {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7"},
2063
+ {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11"},
2064
+ {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126"},
2065
+ {file = "scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163"},
2066
+ {file = "scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8"},
2067
+ {file = "scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5"},
2068
+ {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e"},
2069
+ {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb"},
2070
+ {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723"},
2071
+ {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb"},
2072
+ {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4"},
2073
+ {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5"},
2074
+ {file = "scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca"},
2075
+ {file = "scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf"},
2076
+ ]
2077
+
2078
+ [package.dependencies]
2079
+ numpy = ">=1.23.5,<2.5"
2080
+
2081
+ [package.extras]
2082
+ dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
2083
+ doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"]
2084
+ test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
2085
+
2086
  [[package]]
2087
  name = "semantic-version"
2088
  version = "2.10.0"
 
2174
  [package.extras]
2175
  full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
2176
 
2177
+ [[package]]
2178
+ name = "tinydb"
2179
+ version = "4.8.2"
2180
+ description = "TinyDB is a tiny, document oriented database optimized for your happiness :)"
2181
+ optional = false
2182
+ python-versions = "<4.0,>=3.8"
2183
+ groups = ["main"]
2184
+ files = [
2185
+ {file = "tinydb-4.8.2-py3-none-any.whl", hash = "sha256:f97030ee5cbc91eeadd1d7af07ab0e48ceb04aa63d4a983adbaca4cba16e86c3"},
2186
+ {file = "tinydb-4.8.2.tar.gz", hash = "sha256:f7dfc39b8d7fda7a1ca62a8dbb449ffd340a117c1206b68c50b1a481fb95181d"},
2187
+ ]
2188
+
2189
  [[package]]
2190
  name = "tomlkit"
2191
  version = "0.13.3"
 
2462
  [metadata]
2463
  lock-version = "2.1"
2464
  python-versions = ">=3.12,<4.0"
2465
+ content-hash = "e5d2bd5217695205ea0868e860217189bd487dd86a49d59a14ee98051186cf32"
prompts.csv DELETED
@@ -1,5 +0,0 @@
1
- Prompt A 100
2
- Prompt B 90
3
- Prompt C 60
4
- Prompt D 55
5
- Prompt E 30
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -11,7 +11,7 @@ dependencies = [
11
  "trueskill (>=0.4.5,<0.5.0)",
12
  "gradio (>=4.0.0)",
13
  "pandas (>=2.0.0)",
14
- "plotly (>=6.1.2,<7.0.0)",
15
  ]
16
 
17
 
 
11
  "trueskill (>=0.4.5,<0.5.0)",
12
  "gradio (>=4.0.0)",
13
  "pandas (>=2.0.0)",
14
+ "plotly (>=6.1.2,<7.0.0)"
15
  ]
16
 
17
 
routes.py DELETED
@@ -1,7 +0,0 @@
1
- @app.get("/logout")
2
- def logout():
3
- response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
4
- response.delete_cookie(key=f"access-token")
5
- response.delete_cookie(key=f"access-token-unsecure")
6
- print("Logout user!")
7
- return response