File size: 6,067 Bytes
1bc7a0c
 
330f0b8
 
 
1bc7a0c
0fab24c
1bc7a0c
 
 
 
 
 
 
 
 
 
330f0b8
 
1bc7a0c
330f0b8
 
 
 
1bc7a0c
330f0b8
1bc7a0c
 
330f0b8
1bc7a0c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330f0b8
1bc7a0c
330f0b8
1bc7a0c
330f0b8
1bc7a0c
 
 
330f0b8
1bc7a0c
330f0b8
1bc7a0c
 
 
330f0b8
 
 
1bc7a0c
 
 
 
 
 
 
 
 
 
 
330f0b8
1bc7a0c
 
 
 
 
330f0b8
1bc7a0c
 
 
 
 
330f0b8
 
 
 
 
1bc7a0c
 
 
 
 
330f0b8
1bc7a0c
 
 
 
 
 
 
 
 
 
 
 
 
 
330f0b8
 
1bc7a0c
 
330f0b8
1bc7a0c
 
 
 
330f0b8
 
1bc7a0c
330f0b8
1bc7a0c
330f0b8
 
 
 
 
 
 
1bc7a0c
 
 
 
 
330f0b8
1bc7a0c
 
330f0b8
1bc7a0c
330f0b8
 
 
 
 
 
 
 
 
 
 
1bc7a0c
 
 
330f0b8
1bc7a0c
330f0b8
 
 
1bc7a0c
330f0b8
 
1bc7a0c
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import trueskill as ts
import pandas as pd
import random
import datetime
from typing import Dict, List, Tuple, Union
import db
import pandas as pd
from typing import TypedDict

MU_init = ts.Rating().mu
SIGMA_init = ts.Rating().sigma


class Prompt(TypedDict):
    id: int
    name: str
    text: str


class Arena:
    """
    Une arène pour comparer et classer des prompts en utilisant l'algorithme TrueSkill.
    """

    def init_estimates(self, reboot=True) -> None:
        """
        Initialise les estimations des prompts avec des ratings TrueSkill par défaut.
        reboot : si le fichier estimates.csv existe déjà, on le laisse tel quel.
        """
        estimates = db.load("estimates")
        if not estimates.empty and reboot:
            return None
        if estimates.empty:
            for i in db.load("prompts")["id"].to_list():
                db.insert(
                    "estimates",
                    {
                        "prompt_id": i,
                        "mu": MU_init,
                        "sigma": SIGMA_init,
                    },
                )

    def load(self, table_name: str) -> pd.DataFrame:
        """
        fonction back pour l'UI.
        Charge les données d'une table depuis le fichier CSV.
        """
        return db.load(table_name)

    def replace(self, table_name: str, df: pd.DataFrame) -> pd.DataFrame:
        """
        fonction back pour l'UI.
        Remplace le contenu d'une table par les données du fichier CSV.
        Pour l'admin uniquement
        """
        return db.replace(table_name, df)

    def select_match(self) -> Tuple[Prompt, Prompt]:
        """
        Sélectionne deux prompts pour un match en privilégiant ceux avec une grande incertitude.
        Returns:
            Un tuple contenant les IDs des deux prompts à comparer (prompt_a, prompt_b)
        """
        # le prompt le plus incertain (sigma le plus élevé)
        estimates = db.load("estimates")
        estimate_a = estimates.sort_values(by="sigma", ascending=False).iloc[0]

        # le prompt le plus proche en niveau (mu) du prompt_a
        estimate_b = (
            estimates.loc[estimates["prompt_id"] != estimate_a["prompt_id"]]
            .assign(delta_mu=lambda df_: abs(df_["mu"] - estimate_a["mu"]))
            .sort_values(by="delta_mu", ascending=True)
            .iloc[0]
        )

        prompts = db.load("prompts")
        prompt_a = prompts.query(f"id == {estimate_a['prompt_id']}").iloc[0].to_dict()
        prompt_b = prompts.query(f"id == {estimate_b['prompt_id']}").iloc[0].to_dict()
        # We need to update the selection strategy to prefer prompts with high uncertainty
        # but also consider prompts that are close in ranking (within 5 positions)

        # Create pairs of prompts that are at most 5 positions apart in the ranking
        # close_pairs = []
        # for i in range(len(prompt_ids)):
        #     for j in range(i + 1, min(i + 6, len(prompt_ids))):
        #         close_pairs.append((prompt_ids[i], prompt_ids[j]))

        return prompt_a, prompt_b

    def record_result(self, winner_id: str, loser_id: str) -> None:
        # Obtenir les ratings actuels
        estimates = db.load("estimates")
        winner_estimate = (
            estimates[estimates["prompt_id"] == winner_id].iloc[0].to_dict()
        )
        loser_estimate = estimates[estimates["prompt_id"] == loser_id].iloc[0].to_dict()

        winner_rating = ts.Rating(winner_estimate["mu"], winner_estimate["sigma"])
        loser_rating = ts.Rating(loser_estimate["mu"], loser_estimate["sigma"])

        winner_new_rating, loser_new_rating = ts.rate_1vs1(winner_rating, loser_rating)

        db.update(
            "estimates",
            winner_estimate["id"],
            {"mu": winner_new_rating.mu, "sigma": winner_new_rating.sigma},
        )
        db.update(
            "estimates",
            loser_estimate["id"],
            {"mu": loser_new_rating.mu, "sigma": loser_new_rating.sigma},
        )

        db.insert(
            "votes",
            {
                "winner_id": winner_id,
                "loser_id": loser_id,
                #                "timestamp": datetime.datetime.now().isoformat(),
            },
        )

        return None

    def get_rankings(self) -> pd.DataFrame:
        """
        Obtient le classement actuel des prompts.

        Returns:
            Liste de dictionnaires contenant le classement de chaque prompt avec
            ses informations (rang, id, texte, mu, sigma, score)
        """

        prompts = db.load("prompts")
        estimates = db.load("estimates").drop(columns=["id"])
        rankings = prompts.merge(estimates, left_on="id", right_on="prompt_id").drop(
            columns=["id", "prompt_id"]
        )
        return rankings.sort_values(by="mu", ascending=False)
        # eventuellement afficher plutôt mu - 3 sigma pour être conservateur

    def get_progress(self) -> str:
        """
        Renvoie des statistiques sur la progression du tournoi.

        Returns:
            Dictionnaire contenant des informations sur la progression:
            - total_prompts: nombre total de prompts
            - total_matches: nombre total de matchs joués
            - avg_sigma: incertitude moyenne des ratings
            - progress: pourcentage estimé de progression du tournoi
            - estimated_remaining_matches: estimation du nombre de matchs restants
        """
        prompts = db.load("prompts")
        estimates = db.load("estimates")
        votes = db.load("votes")

        avg_sigma = estimates["sigma"].mean()

        # Estimer quel pourcentage du tournoi est complété
        # En se basant sur la réduction moyenne de sigma par rapport à la valeur initiale
        initial_sigma = ts.Rating().sigma
        progress = min(100, max(0, (1 - avg_sigma / initial_sigma) * 100))

        msg = f"""{len(prompts)} propositions à départager
{len(votes)} matchs joués
{avg_sigma:.2f} d'incertitude moyenne"""

        return msg