|
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) |
|
""" |
|
|
|
estimates = db.load("estimates") |
|
estimate_a = estimates.sort_values(by="sigma", ascending=False).iloc[0] |
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return prompt_a, prompt_b |
|
|
|
def record_result(self, winner_id: str, loser_id: str) -> None: |
|
|
|
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, |
|
|
|
}, |
|
) |
|
|
|
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) |
|
|
|
|
|
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() |
|
|
|
|
|
|
|
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 |
|
|