import argparse import typing as t import random from pathlib import Path import jinja2 as j2 from .config import Task, Category, Config from . import util def constituer_groupes(choristes: list[str]) -> list[list[str]]: """Répartir aléatoirement les gens en groupes""" TAILLE_GROUPE: int = 4 nb_choristes = len(choristes) groupes: list[list[str]] = [] random.shuffle(choristes) pos = 0 for _ in range(nb_choristes // TAILLE_GROUPE): groupes.append(choristes[pos : pos + TAILLE_GROUPE]) pos += TAILLE_GROUPE reste = choristes[pos:] if len(reste) == TAILLE_GROUPE - 1: groupes.append(reste) else: for gid, pers in enumerate(reste): groupes[gid].append(pers) for groupe in groupes: groupe.sort() random.shuffle(groupes) return groupes def assigner_taches(task: Category | Task, group_count: int, cur_group: int = 0) -> int: """Assigne les tâches aux groupes (round-robin)""" if isinstance(task, Task): task.assigned = list( map( lambda x: x % group_count, range(cur_group, cur_group + task.nb_groups), ) ) return (cur_group + task.nb_groups) % group_count for subtask in task.tasks: cur_group = assigner_taches(subtask, group_count, cur_group) return cur_group def export_short_md(config: Config, groupes: list[list[str]]) -> str: """Exporte la liste des tâches au format Markdown court (pour vérification)""" def export_taskcat(grp: Task | Category) -> str: if isinstance(grp, Task): assert grp.assigned is not None return f'* {grp.qualified_name}: {", ".join(map(lambda x: str(x+1), grp.assigned))}' out = "\n" + "#" * (2 + grp.depth) + f" {grp.name}" if grp.time: out += f" ({grp.time})" out += "\n\n" if grp.intro: out += grp.intro + "\n\n" out += "\n".join(map(export_taskcat, grp.tasks)) return out out = "## Groupes\n\n" for g_id, group in enumerate(groupes): out += f"* Groupe {g_id+1} : " + ", ".join(group) + "\n" out += "\n## Tâches\n" out += "\n".join(map(export_taskcat, config.taches.tasks)) return out def export_bare_tasks_md(config: Config) -> str: """Exporte la liste des tâches sans assignation en markdown, pour relecture de la liste, des nombres de groupes assignés et du coefficient de pénibilité""" def export_taskcat(grp: Task | Category) -> str: if isinstance(grp, Task): out = f"* **{grp.name}** : " out += f"{grp.nb_groups} groupe{'s' if grp.nb_groups > 1 else ''}, " out += f"pénible x{grp.tough}" if grp.referent is not None: out += f" (référent {grp.referent})" return out out = "\n" + "#" * (2 + grp.depth) + f" {grp.name}" if grp.time: out += f" ({grp.time})" out += "\n\n" if grp.intro: out += grp.intro + "\n\n" out += "\n".join(map(export_taskcat, grp.tasks)) return out return "\n".join(map(export_taskcat, config.taches.tasks)) def export_latex(config: Config, groupes: list[list[str]]) -> str: """Exporter la liste des tâches en LaTeX (à insérer dans un template)""" j2_env = util.j2_environment() template = j2_env.get_template("repartition.tex.j2") env = { "groupes": {g_id: grp for g_id, grp in enumerate(groupes)}, "taches": config.taches.tasks, "couleur": util.group_colors, } return template.render(**env) def main(): parser = argparse.ArgumentParser("Répartition des tâches") parser.add_argument("taches", help="Fichier yaml contenant les tâches") parser.add_argument("choristes", help="Fichier CSV contenant les choristes") parser.add_argument("--to-tex", help="Exporter vers un fichier LaTeX") parser.add_argument( "--bare-tasks", help=( "Exporter seulement les tâches sans assignation pour revue vers ce fichier" ), ) parser.add_argument( "--to-short-md", help="Exporter vers un fichier Markdown (pour vérification uniquement)", ) args = parser.parse_args() config = Config(args.taches, args.choristes) if args.bare_tasks: util.write_to_file(args.bare_tasks, export_bare_tasks_md(config)) groupes = constituer_groupes(config.choristes) assigner_taches(config.taches, len(groupes)) if args.to_tex: util.write_to_file(args.to_tex, export_latex(config, groupes)) if args.to_short_md: util.write_to_file(args.to_short_md, export_short_md(config, groupes))