# -*- mode: python -*-
# -*- coding: utf-8 -*-

##############################################################################
#
# Gestion scolarite IUT
#
# Copyright (c) 1999 - 2020 Emmanuel Viennet.  All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#   Emmanuel Viennet      emmanuel.viennet@viennet.net
#
##############################################################################

"""Rapports suivi:
  - statistiques decisions
  - suivi cohortes
"""
import mx.DateTime
from mx.DateTime import DateTime as mxDateTime
import tempfile, urllib, re


from notesdb import *
from sco_utils import *
from notes_log import log
from gen_tables import GenTable
import sco_excel, sco_pdf
import sco_codes_parcours
from sco_codes_parcours import code_semestre_validant
import sco_parcours_dut
import sco_formsemestre
import sco_formsemestre_status
from sco_pdf import SU

MAX_ETUD_IN_DESCR = 20


def formsemestre_etuds_stats(context, sem, only_primo=False):
    """Récupère liste d'etudiants avec etat et decision."""
    nt = context._getNotesCache().get_NotesTable(
        context, sem["formsemestre_id"]
    )  # > get_table_moyennes_triees, identdict, get_etud_decision_sem, get_etud_etat,
    T = nt.get_table_moyennes_triees()
    # Construit liste d'étudiants du semestre avec leur decision
    etuds = []
    for t in T:
        etudid = t[-1]
        etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
        decision = nt.get_etud_decision_sem(etudid)
        if decision:
            etud["codedecision"] = decision["code"]
        etud["etat"] = nt.get_etud_etat(etudid)
        if etud["etat"] == "D":
            etud["codedecision"] = "DEM"
        if not etud.has_key("codedecision"):
            etud["codedecision"] = "(nd)"  # pas de decision jury
        # Ajout devenir (autorisations inscriptions), utile pour stats passage
        aut_list = sco_parcours_dut.formsemestre_get_autorisation_inscription(
            context, etudid, sem["formsemestre_id"]
        )
        autorisations = ["S%s" % x["semestre_id"] for x in aut_list]
        autorisations.sort()
        autorisations_str = ", ".join(autorisations)
        etud["devenir"] = autorisations_str
        # Ajout clé 'bac-specialite'
        bs = []
        if etud["bac"]:
            bs.append(etud["bac"])
        if etud["specialite"]:
            bs.append(etud["specialite"])
        etud["bac-specialite"] = " ".join(bs)
        #
        if (not only_primo) or context.isPrimoEtud(etud, sem):
            etuds.append(etud)
    return etuds


def _categories_and_results(etuds, category, result):
    categories = {}
    results = {}
    for etud in etuds:
        categories[etud[category]] = True
        results[etud[result]] = True
    categories = categories.keys()
    categories.sort()
    results = results.keys()
    results.sort()
    return categories, results


def _results_by_category(
    etuds,
    category="",
    result="",
    category_name=None,
    context=None,
    formsemestre_id=None,
):
    """Construit table: categories (eg types de bacs) en ligne, décisions jury en colonnes

    etuds est une liste d'etuds (dicts).
    category et result sont des clés de etud (category définie les lignes, result les colonnes).

    Retourne une table.
    """
    if category_name is None:
        category_name = category
    # types de bacs differents:
    categories, results = _categories_and_results(etuds, category, result)
    #
    Count = {}  # { bac : { decision : nb_avec_ce_bac_et_ce_code } }
    results = {}  # { result_value : True }
    for etud in etuds:
        results[etud[result]] = True
        if Count.has_key(etud[category]):
            Count[etud[category]][etud[result]] += 1
        else:
            Count[etud[category]] = DictDefault(kv_dict={etud[result]: 1})
    # conversion en liste de dict
    C = [Count[cat] for cat in categories]
    # Totaux par lignes et colonnes
    tot = 0
    for l in [Count[cat] for cat in categories]:
        l["sum"] = sum(l.values())
        tot += l["sum"]
    # pourcentages sur chaque total de ligne
    for l in C:
        l["sumpercent"] = "%2.1f%%" % ((100.0 * l["sum"]) / tot)
    #
    codes = results.keys()
    codes.sort()

    bottom_titles = []
    if C:  # ligne du bas avec totaux:
        bottom_titles = {}
        for code in codes:
            bottom_titles[code] = sum([l[code] for l in C])
        bottom_titles["sum"] = tot
        bottom_titles["sumpercent"] = "100%"
        bottom_titles["row_title"] = "Total"

    # ajout titre ligne:
    for (cat, l) in zip(categories, C):
        l["row_title"] = cat or "?"

    #
    codes.append("sum")
    codes.append("sumpercent")

    # on veut { ADM : ADM, ... }, était peu elegant en python 2.3:
    # titles = {}
    # map( lambda x,titles=titles: titles.__setitem__(x[0],x[1]), zip(codes,codes) )
    # Version moderne:
    titles = {x: x for x in codes}
    titles["sum"] = "Total"
    titles["sumpercent"] = "%"
    titles["DEM"] = "Dém."  # démissions
    titles["row_title"] = category_name
    return GenTable(
        titles=titles,
        columns_ids=codes,
        rows=C,
        bottom_titles=bottom_titles,
        html_col_width="4em",
        html_sortable=True,
        preferences=context.get_preferences(formsemestre_id),
    )


# pages
def formsemestre_report(
    context,
    formsemestre_id,
    etuds,
    REQUEST=None,
    category="bac",
    result="codedecision",
    category_name="",
    result_name="",
    title="Statistiques",
    only_primo=None,
):
    """
    Tableau sur résultats (result) par type de category bac
    """
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    if not category_name:
        category_name = category
    if not result_name:
        result_name = result
    if result_name == "codedecision":
        result_name = "résultats"
    #
    tab = _results_by_category(
        etuds,
        category=category,
        category_name=category_name,
        result=result,
        context=context,
        formsemestre_id=formsemestre_id,
    )
    #
    tab.filename = make_filename("stats " + sem["titreannee"])

    tab.origin = "Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + ""
    tab.caption = "Répartition des résultats par %s, semestre %s" % (
        category_name,
        sem["titreannee"],
    )
    tab.html_caption = "Répartition des résultats par %s." % category_name
    tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
    if only_primo:
        tab.base_url += "&amp;only_primo=on"
    return tab


# def formsemestre_report_bacs(context, formsemestre_id, format='html', REQUEST=None):
#     """
#     Tableau sur résultats par type de bac
#     """
#     sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
#     title = 'Statistiques bacs ' + sem['titreannee']
#     etuds = formsemestre_etuds_stats(context, sem)
#     tab = formsemestre_report(context, formsemestre_id, etuds, REQUEST=REQUEST,
#                               category='bac', result='codedecision',
#                               category_name='Bac',
#                               title=title)
#     return tab.make_page(
#         context,
#         title =  """<h2>Résultats de <a href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a></h2>""" % sem,
#         format=format, page_title = title, REQUEST=REQUEST )


def formsemestre_report_counts(
    context,
    formsemestre_id,
    format="html",
    REQUEST=None,
    category="bac",
    result="codedecision",
    allkeys=False,
    only_primo=False,
):
    """
    Tableau comptage avec choix des categories
    """
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    category_name = strcapitalize(category)
    title = "Comptages " + category_name
    etuds = formsemestre_etuds_stats(context, sem, only_primo=only_primo)
    tab = formsemestre_report(
        context,
        formsemestre_id,
        etuds,
        REQUEST=REQUEST,
        category=category,
        result=result,
        category_name=category_name,
        title=title,
        only_primo=only_primo,
    )
    if not etuds:
        F = ["""<p><em>Aucun étudiant</em></p>"""]
    else:
        if allkeys:
            keys = etuds[0].keys()
        else:
            # clés présentées à l'utilisateur:
            keys = [
                "annee_bac",
                "annee_naissance",
                "bac",
                "specialite",
                "bac-specialite",
                "codedecision",
                "devenir",
                "etat",
                "sexe",
                "qualite",
                "villelycee",
                "statut",
                "type_admission",
                "boursier_prec",
            ]
        keys.sort()
        F = [
            """<form name="f" method="get" action="%s"><p>
              Colonnes: <select name="result" onchange="document.f.submit()">"""
            % REQUEST.URL0
        ]
        for k in keys:
            if k == result:
                selected = "selected"
            else:
                selected = ""
            F.append('<option value="%s" %s>%s</option>' % (k, selected, k))
        F.append("</select>")
        F.append(' Lignes: <select name="category" onchange="document.f.submit()">')
        for k in keys:
            if k == category:
                selected = "selected"
            else:
                selected = ""
            F.append('<option value="%s" %s>%s</option>' % (k, selected, k))
        F.append("</select>")
        if only_primo:
            checked = 'checked="1"'
        else:
            checked = ""
        F.append(
            '<br/><input type="checkbox" name="only_primo" onchange="document.f.submit()" %s>Restreindre aux primo-entrants</input>'
            % checked
        )
        F.append(
            '<input type="hidden" name="formsemestre_id" value="%s"/>' % formsemestre_id
        )
        F.append("</p></form>")

    t = tab.make_page(
        context,
        title="""<h2 class="formsemestre">Comptes croisés</h2>""",
        format=format,
        REQUEST=REQUEST,
        with_html_headers=False,
    )
    if format != "html":
        return t
    H = [
        context.sco_header(REQUEST, page_title=title),
        t,
        "\n".join(F),
        """<p class="help">Le tableau affiche le nombre d'étudiants de ce semestre dans chacun
          des cas choisis: à l'aide des deux menus, vous pouvez choisir les catégories utilisées
          pour les lignes et les colonnes. Le <tt>codedecision</tt> est le code de la décision 
          du jury.
          </p>""",
        context.sco_footer(REQUEST),
    ]
    return "\n".join(H)


# --------------------------------------------------------------------------
def table_suivi_cohorte(
    context,
    formsemestre_id,
    percent=False,
    bac="",  # selection sur type de bac
    bacspecialite="",
    annee_bac="",
    sexe="",
    statut="",
    only_primo=False,
):
    """
    Tableau indiquant le nombre d'etudiants de la cohorte dans chaque état:
    Etat     date_debut_Sn   date1  date2 ...
    S_n       #inscrits en Sn
    S_n+1
    ...
    S_last
    Diplome
    Sorties

    Determination des dates: on regroupe les semestres commençant à des dates proches

    """
    sem = sco_formsemestre.get_formsemestre(
        context, formsemestre_id
    )  # sem est le semestre origine
    t0 = time.time()

    def logt(op):
        if 0:  # debug, set to 0 in production
            log("%s: %s" % (op, time.time() - t0))

    logt("table_suivi_cohorte: start")
    # 1-- Liste des semestres posterieurs dans lesquels ont été les etudiants de sem
    nt = context._getNotesCache().get_NotesTable(
        context, formsemestre_id
    )  # > get_etudids, get_etud_decision_sem
    etudids = nt.get_etudids()

    logt("A: orig etuds set")
    S = {formsemestre_id: sem}  # ensemble de formsemestre_id
    orig_set = set()  # ensemble d'etudid du semestre d'origine
    bacs = set()
    bacspecialites = set()
    annee_bacs = set()
    sexes = set()
    statuts = set()
    for etudid in etudids:
        etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
        bacspe = etud["bac"] + " / " + etud["specialite"]
        # sélection sur bac:
        if (
            (not bac or (bac == etud["bac"]))
            and (not bacspecialite or (bacspecialite == bacspe))
            and (not annee_bac or (annee_bac == str(etud["annee_bac"])))
            and (not sexe or (sexe == etud["sexe"]))
            and (not statut or (statut == etud["statut"]))
            and (not only_primo or context.isPrimoEtud(etud, sem))
        ):
            orig_set.add(etudid)
            # semestres suivants:
            for s in etud["sems"]:
                if DateDMYtoISO(s["date_debut"]) > DateDMYtoISO(sem["date_debut"]):
                    S[s["formsemestre_id"]] = s
        bacs.add(etud["bac"])
        bacspecialites.add(bacspe)
        annee_bacs.add(etud["annee_bac"])
        sexes.add(etud["sexe"])
        if etud["statut"]:  # ne montre pas les statuts non renseignés
            statuts.add(etud["statut"])
    sems = S.values()
    # tri les semestres par date de debut
    for s in sems:
        d, m, y = [int(x) for x in s["date_debut"].split("/")]
        s["date_debut_mx"] = mxDateTime(y, m, d)  # pylint: disable=not-callable
    sems.sort(lambda x, y: cmp(x["date_debut_mx"], y["date_debut_mx"]))

    # 2-- Pour chaque semestre, trouve l'ensemble des etudiants venant de sem
    logt("B: etuds sets")
    sem["members"] = orig_set
    for s in sems:
        ins = context.do_formsemestre_inscription_list(
            args={"formsemestre_id": s["formsemestre_id"]}
        )  # sans dems
        inset = set([i["etudid"] for i in ins])
        s["members"] = orig_set.intersection(inset)
        nb_dipl = 0  # combien de diplomes dans ce semestre ?
        if s["semestre_id"] == nt.parcours.NB_SEM:
            nt = context._getNotesCache().get_NotesTable(
                context, s["formsemestre_id"]
            )  # > get_etud_decision_sem
            for etudid in s["members"]:
                dec = nt.get_etud_decision_sem(etudid)
                if dec and code_semestre_validant(dec["code"]):
                    nb_dipl += 1
        s["nb_dipl"] = nb_dipl

    # 3-- Regroupe les semestres par date de debut
    P = []  #  liste de periodsem

    class periodsem:
        pass

    # semestre de depart:
    porigin = periodsem()
    d, m, y = [int(x) for x in sem["date_debut"].split("/")]
    porigin.datedebut = mxDateTime(y, m, d)  # pylint: disable=not-callable
    porigin.sems = [sem]

    #
    tolerance = mx.DateTime.DateTimeDelta(45)  # 45 days
    for s in sems:
        merged = False
        for p in P:
            if abs(s["date_debut_mx"] - p.datedebut) < tolerance:
                p.sems.append(s)
                merged = True
                break
        if not merged:
            p = periodsem()
            p.datedebut = s["date_debut_mx"]
            p.sems = [s]
            P.append(p)

    # 4-- regroupe par indice de semestre S_i
    indices_sems = list(set([s["semestre_id"] for s in sems]))
    indices_sems.sort()
    for p in P:
        p.nb_etuds = 0  # nombre total d'etudiants dans la periode
        p.sems_by_id = DictDefault(defaultvalue=[])
        for s in p.sems:
            p.sems_by_id[s["semestre_id"]].append(s)
            p.nb_etuds += len(s["members"])

    # 5-- Contruit table
    logt("C: build table")
    nb_initial = len(sem["members"])

    def fmtval(x):
        if not x:
            return ""  # ne montre pas les 0
        if percent:
            return "%2.1f%%" % (100.0 * x / nb_initial)
        else:
            return x

    L = [
        {
            "row_title": "Origine: S%s" % sem["semestre_id"],
            porigin.datedebut: nb_initial,
            "_css_row_class": "sorttop",
        }
    ]
    if nb_initial <= MAX_ETUD_IN_DESCR:
        etud_descr = _descr_etud_set(context, sem["members"])
        L[0]["_%s_help" % porigin.datedebut] = etud_descr
    for idx_sem in indices_sems:
        if idx_sem >= 0:
            d = {"row_title": "S%s" % idx_sem}
        else:
            d = {"row_title": "Autre semestre"}

        for p in P:
            etuds_period = set()
            for s in p.sems:
                if s["semestre_id"] == idx_sem:
                    etuds_period = etuds_period.union(s["members"])
            nbetuds = len(etuds_period)
            if nbetuds:
                d[p.datedebut] = fmtval(nbetuds)
                if nbetuds <= MAX_ETUD_IN_DESCR:  # si peu d'etudiants, indique la liste
                    etud_descr = _descr_etud_set(context, etuds_period)
                    d["_%s_help" % p.datedebut] = etud_descr
        L.append(d)
    # Compte nb de démissions et de ré-orientation par période
    logt("D: cout dems reos")
    sem["dems"], sem["reos"] = _count_dem_reo(context, formsemestre_id, sem["members"])
    for p in P:
        p.dems = set()
        p.reos = set()
        for s in p.sems:
            d, r = _count_dem_reo(context, s["formsemestre_id"], s["members"])
            p.dems.update(d)
            p.reos.update(r)
    # Nombre total d'etudiants par periode
    l = {
        "row_title": "Inscrits",
        "row_title_help": "Nombre d'étudiants inscrits",
        "_table_part": "foot",
        porigin.datedebut: fmtval(nb_initial),
    }
    for p in P:
        l[p.datedebut] = fmtval(p.nb_etuds)
    L.append(l)
    # Nombre de démissions par période
    l = {
        "row_title": "Démissions",
        "row_title_help": "Nombre de démissions pendant la période",
        "_table_part": "foot",
        porigin.datedebut: fmtval(len(sem["dems"])),
    }
    if len(sem["dems"]) <= MAX_ETUD_IN_DESCR:
        etud_descr = _descr_etud_set(context, sem["dems"])
        l["_%s_help" % porigin.datedebut] = etud_descr
    for p in P:
        l[p.datedebut] = fmtval(len(p.dems))
        if len(p.dems) <= MAX_ETUD_IN_DESCR:
            etud_descr = _descr_etud_set(context, p.dems)
            l["_%s_help" % p.datedebut] = etud_descr
    L.append(l)
    # Nombre de réorientations par période
    l = {
        "row_title": "Echecs",
        "row_title_help": "Ré-orientations (décisions NAR)",
        "_table_part": "foot",
        porigin.datedebut: fmtval(len(sem["reos"])),
    }
    if len(sem["reos"]) < 10:
        etud_descr = _descr_etud_set(context, sem["reos"])
        l["_%s_help" % porigin.datedebut] = etud_descr
    for p in P:
        l[p.datedebut] = fmtval(len(p.reos))
        if len(p.reos) <= MAX_ETUD_IN_DESCR:
            etud_descr = _descr_etud_set(context, p.reos)
            l["_%s_help" % p.datedebut] = etud_descr
    L.append(l)
    # derniere ligne: nombre et pourcentage de diplomes
    l = {
        "row_title": "Diplômes",
        "row_title_help": "Nombre de diplômés à la fin de la période",
        "_table_part": "foot",
    }
    for p in P:
        nb_dipl = 0
        for s in p.sems:
            nb_dipl += s["nb_dipl"]
        l[p.datedebut] = fmtval(nb_dipl)
    L.append(l)

    columns_ids = [p.datedebut for p in P]
    titles = dict([(p.datedebut, p.datedebut.strftime("%d/%m/%y")) for p in P])
    titles[porigin.datedebut] = porigin.datedebut.strftime("%d/%m/%y")
    if percent:
        pp = "(en % de la population initiale) "
        titles["row_title"] = "%"
    else:
        pp = ""
        titles["row_title"] = ""
    if only_primo:
        pp += "(restreint aux primo-entrants) "
    if bac:
        dbac = " (bacs %s)" % bac
    else:
        dbac = ""
    if bacspecialite:
        dbac += " (spécialité %s)" % bacspecialite
    if annee_bac:
        dbac += " (année bac %s)" % annee_bac
    if sexe:
        dbac += " genre: %s" % sexe
    if statut:
        dbac += " statut: %s" % statut
    tab = GenTable(
        titles=titles,
        columns_ids=columns_ids,
        rows=L,
        html_col_width="4em",
        html_sortable=True,
        filename=make_filename("cohorte " + sem["titreannee"]),
        origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
        caption="Suivi cohorte " + pp + sem["titreannee"] + dbac,
        page_title="Suivi cohorte " + sem["titreannee"],
        html_class="table_cohorte",
        preferences=context.get_preferences(formsemestre_id),
    )
    # Explication: liste des semestres associés à chaque date
    if not P:
        expl = [
            '<p class="help">(aucun étudiant trouvé dans un semestre ultérieur)</p>'
        ]
    else:
        expl = ["<h3>Semestres associés à chaque date:</h3><ul>"]
        for p in P:
            expl.append("<li><b>%s</b>:" % p.datedebut.strftime("%d/%m/%y"))
            ls = []
            for s in p.sems:
                ls.append(
                    '<a href="formsemestre_status?formsemestre_id=%(formsemestre_id)s">%(titreannee)s</a>'
                    % s
                )
            expl.append(", ".join(ls) + "</li>")
        expl.append("</ul>")
    logt("Z: table_suivi_cohorte done")
    return tab, "\n".join(expl), bacs, bacspecialites, annee_bacs, sexes, statuts


def formsemestre_suivi_cohorte(
    context,
    formsemestre_id,
    format="html",
    percent=1,
    bac="",
    bacspecialite="",
    annee_bac="",
    sexe="",
    statut="",
    only_primo=False,
    REQUEST=None,
):
    """Affiche suivi cohortes par numero de semestre"""
    percent = int(percent)
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    tab, expl, bacs, bacspecialites, annee_bacs, sexes, statuts = table_suivi_cohorte(
        context,
        formsemestre_id,
        percent=percent,
        bac=bac,
        bacspecialite=bacspecialite,
        annee_bac=annee_bac,
        sexe=sexe,
        statut=statut,
        only_primo=only_primo,
    )
    tab.base_url = (
        "%s?formsemestre_id=%s&amp;percent=%s&amp;bac=%s&amp;bacspecialite=%s&amp;sexe=%s"
        % (REQUEST.URL0, formsemestre_id, percent, bac, bacspecialite, sexe)
    )
    if only_primo:
        tab.base_url += "&amp;only_primo=on"
    t = tab.make_page(context, format=format, with_html_headers=False, REQUEST=REQUEST)
    if format != "html":
        return t

    base_url = REQUEST.URL0
    burl = (
        "%s?formsemestre_id=%s&amp;bac=%s&amp;bacspecialite=%s&amp;sexe=%s&amp;statut=%s"
        % (base_url, formsemestre_id, bac, bacspecialite, sexe, statut)
    )
    if percent:
        pplink = (
            '<p><a href="%s&amp;percent=0">Afficher les résultats bruts</a></p>' % burl
        )
    else:
        pplink = (
            '<p><a href="%s&amp;percent=1">Afficher les résultats en pourcentages</a></p>'
            % burl
        )
    help = (
        pplink
        + """    
    <p class="help">Nombre d'étudiants dans chaque semestre. Les dates indiquées sont les dates approximatives de <b>début</b> des semestres (les semestres commençant à des dates proches sont groupés). Le nombre de diplômés est celui à la <b>fin</b> du semestre correspondant. Lorsqu'il y a moins de %s étudiants dans une case, vous pouvez afficher leurs noms en passant le curseur sur le chiffre.</p>
<p class="help">Les menus permettent de n'étudier que certaines catégories d'étudiants (titulaires d'un type de bac, garçons ou filles). La case "restreindre aux primo-entrants" permet de ne considérer que les étudiants qui n'ont jamais été inscrits dans ScoDoc avant le semestre considéré.</p>
    """
        % (MAX_ETUD_IN_DESCR,)
    )

    H = [
        context.sco_header(REQUEST, page_title=tab.page_title),
        """<h2 class="formsemestre">Suivi cohorte: devenir des étudiants de ce semestre</h2>""",
        _gen_form_selectetuds(
            formsemestre_id,
            REQUEST=REQUEST,
            only_primo=only_primo,
            bac=bac,
            bacspecialite=bacspecialite,
            annee_bac=annee_bac,
            sexe=sexe,
            statut=statut,
            bacs=bacs,
            bacspecialites=bacspecialites,
            annee_bacs=annee_bacs,
            sexes=sexes,
            statuts=statuts,
            percent=percent,
        ),
        t,
        help,
        expl,
        context.sco_footer(REQUEST),
    ]
    return "\n".join(H)


def _gen_form_selectetuds(
    formsemestre_id,
    REQUEST=None,
    percent=None,
    only_primo=None,
    bac=None,
    bacspecialite=None,
    annee_bac=None,
    sexe=None,
    statut=None,
    bacs=None,
    bacspecialites=None,
    annee_bacs=None,
    sexes=None,
    statuts=None,
):
    """HTML form pour choix criteres selection etudiants"""
    bacs = list(bacs)
    bacs.sort()
    bacspecialites = list(bacspecialites)
    bacspecialites.sort()
    annee_bacs = list(annee_bacs)
    annee_bacs.sort()
    sexes = list(sexes)
    sexes.sort()
    statuts = list(statuts)
    statuts.sort()
    #
    if bac:
        selected = ""
    else:
        selected = 'selected="selected"'
    F = [
        """<form id="f" method="get" action="%s">
    <p>Bac: <select name="bac" onchange="javascript: submit(this);">
    <option value="" %s>tous</option>
    """
        % (REQUEST.URL0, selected)
    ]
    for b in bacs:
        if bac == b:
            selected = 'selected="selected"'
        else:
            selected = ""
        F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
    F.append("</select>")
    if bacspecialite:
        selected = ""
    else:
        selected = 'selected="selected"'
    F.append(
        """&nbsp; Bac/Specialité: <select name="bacspecialite" onchange="javascript: submit(this);">
    <option value="" %s>tous</option>
    """
        % selected
    )
    for b in bacspecialites:
        if bacspecialite == b:
            selected = 'selected="selected"'
        else:
            selected = ""
        F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
    F.append("</select>")
    #
    if annee_bac:
        selected = ""
    else:
        selected = 'selected="selected"'
    F.append(
        """&nbsp; Année bac: <select name="annee_bac" onchange="javascript: submit(this);">
    <option value="" %s>tous</option>
    """
        % selected
    )
    for b in annee_bacs:
        if str(annee_bac) == str(b):
            selected = 'selected="selected"'
        else:
            selected = ""
        F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
    F.append("</select>")
    #
    F.append(
        """&nbsp; Genre: <select name="sexe" onchange="javascript: submit(this);">
    <option value="" %s>tous</option>
    """
        % selected
    )
    for b in sexes:
        if sexe == b:
            selected = 'selected="selected"'
        else:
            selected = ""
        F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
    F.append("</select>")

    F.append(
        """&nbsp; Statut: <select name="statut" onchange="javascript: submit(this);">
    <option value="" %s>tous</option>
    """
        % selected
    )
    for b in statuts:
        if statut == b:
            selected = 'selected="selected"'
        else:
            selected = ""
        F.append('<option value="%s" %s>%s</option>' % (b, selected, b))
    F.append("</select>")

    if only_primo:
        checked = 'checked="1"'
    else:
        checked = ""
    F.append(
        '<br/><input type="checkbox" name="only_primo" onchange="javascript: submit(this);" %s/>Restreindre aux primo-entrants'
        % checked
    )
    F.append(
        '<input type="hidden" name="formsemestre_id" value="%s"/>' % formsemestre_id
    )
    F.append('<input type="hidden" name="percent" value="%s"/>' % percent)
    F.append("</p></form>")
    return "\n".join(F)


def _descr_etud_set(context, etudids):
    "textual html description of a set of etudids"
    etuds = []
    for etudid in etudids:
        etuds.append(context.getEtudInfo(etudid=etudid, filled=True)[0])
    # sort by name
    etuds.sort(lambda x, y: cmp(x["nom"], y["nom"]))
    return ", ".join([e["nomprenom"] for e in etuds])


def _count_dem_reo(context, formsemestre_id, etudids):
    "count nb of demissions and reorientation in this etud set"
    nt = context._getNotesCache().get_NotesTable(
        context, formsemestre_id
    )  # > get_etud_etat, get_etud_decision_sem
    dems = set()
    reos = set()
    for etudid in etudids:
        if nt.get_etud_etat(etudid) == "D":
            dems.add(etudid)
        dec = nt.get_etud_decision_sem(etudid)
        if dec and dec["code"] in sco_codes_parcours.CODES_SEM_REO:
            reos.add(etudid)
    return dems, reos


"""OLDGEA:
27s pour  S1 F.I. classique Semestre 1 2006-2007
B 2.3s
C 5.6s
D 5.9s
Z 27s  => cache des semestres pour nt

à chaud: 3s
B: etuds sets: 2.4s => lent: N x getEtudInfo (non caché)
"""

EXP_LIC = re.compile(r"licence", re.I)
EXP_LPRO = re.compile(r"professionnelle", re.I)


def _codesem(sem, short=True, prefix=""):
    "code semestre: S1 ou S1d"
    idx = sem["semestre_id"]
    # semestre décalé ?
    # les semestres pairs normaux commencent entre janvier et mars
    # les impairs normaux entre aout et decembre
    d = ""
    if idx and idx > 0 and sem["date_debut"]:
        mois_debut = int(sem["date_debut"].split("/")[1])
        if (idx % 2 and mois_debut < 3) or (idx % 2 == 0 and mois_debut >= 8):
            d = "d"
    if idx == -1:
        if short:
            idx = "Autre "
        else:
            idx = sem["titre"] + " "
            idx = EXP_LPRO.sub("pro.", idx)
            idx = EXP_LIC.sub("Lic.", idx)
            prefix = ""  # indique titre au lieu de Sn
    return "%s%s%s" % (prefix, idx, d)


def get_codeparcoursetud(context, etud, prefix="", separator=""):
    """calcule un code de parcours pour un etudiant
    exemples:
       1234A pour un etudiant ayant effectué S1, S2, S3, S4 puis diplome
       12D   pour un étudiant en S1, S2 puis démission en S2
       12R   pour un etudiant en S1, S2 réorienté en fin de S2
    Construit aussi un dict: { semestre_id : decision_jury | None }
    """
    p = []
    decisions_jury = {}
    # élimine les semestres spéciaux sans parcours (LP...)
    sems = [s for s in etud["sems"] if s["semestre_id"] >= 0]
    i = len(sems) - 1
    while i >= 0:
        s = sems[i]  # 'sems' est a l'envers, du plus recent au plus ancien
        nt = context._getNotesCache().get_NotesTable(
            context, s["formsemestre_id"]
        )  # > get_etud_etat, get_etud_decision_sem
        p.append(_codesem(s, prefix=prefix))
        # code decisions jury de chaque semestre:
        if nt.get_etud_etat(etud["etudid"]) == "D":
            decisions_jury[s["semestre_id"]] = "DEM"
        else:
            dec = nt.get_etud_decision_sem(etud["etudid"])
            if not dec:
                decisions_jury[s["semestre_id"]] = ""
            else:
                decisions_jury[s["semestre_id"]] = dec["code"]
        # code etat dans le codeparcours sur dernier semestre seulement
        if i == 0:
            # Démission
            if nt.get_etud_etat(etud["etudid"]) == "D":
                p.append(":D")
            else:
                dec = nt.get_etud_decision_sem(etud["etudid"])
                if dec and dec["code"] in sco_codes_parcours.CODES_SEM_REO:
                    p.append(":R")
                if (
                    dec
                    and s["semestre_id"] == nt.parcours.NB_SEM
                    and code_semestre_validant(dec["code"])
                ):
                    p.append(":A")
        i -= 1
    return separator.join(p), decisions_jury


def tsp_etud_list(
    context,
    formsemestre_id,
    only_primo=False,
    bac="",  # selection sur type de bac
    bacspecialite="",
    annee_bac="",
    sexe="",
    statut="",
):
    """Liste des etuds a considerer dans table suivi parcours
    ramene aussi ensembles des bacs, genres, statuts de (tous) les etudiants
    """
    # log('tsp_etud_list(%s, bac="%s")' % (formsemestre_id,bac))
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    nt = context._getNotesCache().get_NotesTable(
        context, formsemestre_id
    )  # > get_etudids,
    etudids = nt.get_etudids()
    etuds = []
    bacs = set()
    bacspecialites = set()
    annee_bacs = set()
    sexes = set()
    statuts = set()
    for etudid in etudids:
        etud = context.getEtudInfo(etudid=etudid, filled=True)[0]
        bacspe = etud["bac"] + " / " + etud["specialite"]
        # sélection sur bac, primo, ...:
        if (
            (not bac or (bac == etud["bac"]))
            and (not bacspecialite or (bacspecialite == bacspe))
            and (not annee_bac or (annee_bac == str(etud["annee_bac"])))
            and (not sexe or (sexe == etud["sexe"]))
            and (not statut or (statut == etud["statut"]))
            and (not only_primo or context.isPrimoEtud(etud, sem))
        ):
            etuds.append(etud)

        bacs.add(etud["bac"])
        bacspecialites.add(bacspe)
        annee_bacs.add(etud["annee_bac"])
        sexes.add(etud["sexe"])
        if etud["statut"]:  # ne montre pas les statuts non renseignés
            statuts.add(etud["statut"])
    # log('tsp_etud_list: %s etuds' % len(etuds))
    return etuds, bacs, bacspecialites, annee_bacs, sexes, statuts


def tsp_grouped_list(context, codes_etuds):
    """Liste pour table regroupant le nombre d'étudiants (+ bulle avec les noms) de chaque parcours"""
    L = []
    parcours = codes_etuds.keys()
    parcours.sort()
    for p in parcours:
        nb = len(codes_etuds[p])
        l = {"parcours": p, "nb": nb}
        if nb <= MAX_ETUD_IN_DESCR:
            l["_nb_help"] = _descr_etud_set(
                context, [e["etudid"] for e in codes_etuds[p]]
            )
        L.append(l)
    # tri par effectifs décroissants
    L.sort(lambda x, y: cmp(y["nb"], x["nb"]))
    return L


def table_suivi_parcours(
    context, formsemestre_id, only_primo=False, grouped_parcours=True
):
    """Tableau recapitulant tous les parcours"""
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list(
        context, formsemestre_id, only_primo=only_primo
    )
    codes_etuds = DictDefault(defaultvalue=[])
    for etud in etuds:
        etud["codeparcours"], etud["decisions_jury"] = get_codeparcoursetud(
            context, etud
        )
        codes_etuds[etud["codeparcours"]].append(etud)
        etud["_nom_target"] = "ficheEtud?etudid=" + etud["etudid"]
        etud["_prenom_target"] = "ficheEtud?etudid=" + etud["etudid"]
        etud["_nom_td_attrs"] = 'id="%s" class="etudinfo"' % (etud["etudid"])

    titles = {
        "parcours": "Code parcours",
        "nb": "Nombre d'étudiants",
        "sexe": "",
        "nom": "Nom",
        "prenom": "Prénom",
        "etudid": "etudid",
        "codeparcours": "Code parcours",
        "bac": "Bac",
        "specialite": "Spe.",
    }

    if grouped_parcours:
        L = tsp_grouped_list(context, codes_etuds)
        columns_ids = ("parcours", "nb")
    else:
        # Table avec le parcours de chaque étudiant:
        L = etuds
        columns_ids = (
            "etudid",
            "sexe",
            "nom",
            "prenom",
            "bac",
            "specialite",
            "codeparcours",
        )
        # Calcule intitulés de colonnes
        S = set()
        sems_ids = list(apply(S.union, [e["decisions_jury"].keys() for e in etuds]))
        sems_ids.sort()
        sem_tits = ["S%s" % s for s in sems_ids]
        titles.update([(s, s) for s in sem_tits])
        columns_ids += tuple(sem_tits)
        for etud in etuds:
            for s in etud["decisions_jury"]:
                etud["S%s" % s] = etud["decisions_jury"][s]

    if only_primo:
        primostr = "primo-entrants du"
    else:
        primostr = "passés dans le"
    tab = GenTable(
        columns_ids=columns_ids,
        rows=L,
        titles=titles,
        origin="Généré par %s le " % VERSION.SCONAME + timedate_human_repr() + "",
        caption="Parcours suivis, étudiants %s semestre " % primostr
        + sem["titreannee"],
        page_title="Parcours " + sem["titreannee"],
        html_sortable=True,
        html_class="table_leftalign table_listegroupe",
        html_next_section="""<table class="help">
                    <tr><td><tt>1, 2, ...</tt></td><td> numéros de semestres</td></tr>
                    <tr><td><tt>1d, 2d, ...</tt></td><td>semestres "décalés"</td></tr>
                    <tr><td><tt>:A</tt></td><td> étudiants diplômés</td></tr>
                    <tr><td><tt>:R</tt></td><td> étudiants réorientés</td></tr>
                    <tr><td><tt>:D</tt></td><td> étudiants démissionnaires</td></tr>
                    </table>""",
        bottom_titles={
            "parcours": "Total",
            "nb": len(etuds),
            "codeparcours": len(etuds),
        },
        preferences=context.get_preferences(formsemestre_id),
    )
    return tab


def tsp_form_primo_group(REQUEST, only_primo, no_grouping, formsemestre_id, format):
    """Element de formulaire pour choisir si restriction aux primos entrants et groupement par lycees"""
    F = ["""<form name="f" method="get" action="%s">""" % REQUEST.URL0]
    if only_primo:
        checked = 'checked="1"'
    else:
        checked = ""
    F.append(
        '<input type="checkbox" name="only_primo" onchange="document.f.submit()" %s>Restreindre aux primo-entrants</input>'
        % checked
    )
    if no_grouping:
        checked = 'checked="1"'
    else:
        checked = ""
    F.append(
        '<input type="checkbox" name="no_grouping" onchange="document.f.submit()" %s>Lister chaque étudiant</input>'
        % checked
    )
    F.append(
        '<input type="hidden" name="formsemestre_id" value="%s"/>' % formsemestre_id
    )
    F.append('<input type="hidden" name="format" value="%s"/>' % format)
    F.append("""</form>""")
    return "\n".join(F)


def formsemestre_suivi_parcours(
    context,
    formsemestre_id,
    format="html",
    only_primo=False,
    no_grouping=False,
    REQUEST=None,
):
    """Effectifs dans les differents parcours possibles."""
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    tab = table_suivi_parcours(
        context,
        formsemestre_id,
        only_primo=only_primo,
        grouped_parcours=not no_grouping,
    )
    tab.base_url = "%s?formsemestre_id=%s" % (REQUEST.URL0, formsemestre_id)
    if only_primo:
        tab.base_url += "&amp;only_primo=1"
    if no_grouping:
        tab.base_url += "&amp;no_grouping=1"
    t = tab.make_page(context, format=format, with_html_headers=False, REQUEST=REQUEST)
    if format != "html":
        return t
    F = [
        tsp_form_primo_group(REQUEST, only_primo, no_grouping, formsemestre_id, format)
    ]

    H = [
        context.sco_header(
            REQUEST,
            page_title=tab.page_title,
            init_qtip=True,
            javascripts=["js/etud_info.js"],
        ),
        """<h2 class="formsemestre">Parcours suivis par les étudiants de ce semestre</h2>""",
        "\n".join(F),
        t,
        context.sco_footer(REQUEST),
    ]
    return "\n".join(H)


# -------------
def graph_parcours(
    context,
    formsemestre_id,
    format="svg",
    only_primo=False,
    bac="",  # selection sur type de bac
    bacspecialite="",
    annee_bac="",
    sexe="",
    statut="",
):
    """"""
    if not WITH_PYDOT:
        raise ScoValueError("pydot module is not installed")
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = tsp_etud_list(
        context,
        formsemestre_id,
        only_primo=only_primo,
        bac=bac,
        bacspecialite=bacspecialite,
        annee_bac=annee_bac,
        sexe=sexe,
        statut=statut,
    )
    # log('graph_parcours: %s etuds (only_primo=%s)' % (len(etuds), only_primo))
    if not etuds:
        return "", etuds, bacs, bacspecialites, annee_bacs, sexes, statuts
    edges = DictDefault(
        defaultvalue=set()
    )  # {(formsemestre_id_origin, formsemestre_id_dest) : etud_set}
    sems = {}
    effectifs = DictDefault(defaultvalue=set())  # formsemestre_id : etud_set
    decisions = DictDefault(defaultvalue={})  # formsemestre_id : { code : nb_etud }
    isolated_nodes = []
    connected_nodes = set()
    diploma_nodes = []
    dem_nodes = {}  # formsemestre_id : noeud pour demissionnaires
    nar_nodes = {}  # formsemestre_id : noeud pour NAR
    for etud in etuds:
        nxt = None
        etudid = etud["etudid"]
        for s in etud["sems"]:  # du plus recent au plus ancien
            nt = context._getNotesCache().get_NotesTable(
                context, s["formsemestre_id"]
            )  # > get_etud_decision_sem, get_etud_etat
            dec = nt.get_etud_decision_sem(etudid)
            if nxt:
                if (
                    s["semestre_id"] == nt.parcours.NB_SEM
                    and dec
                    and code_semestre_validant(dec["code"])
                    and nt.get_etud_etat(etudid) == "I"
                ):
                    # cas particulier du diplome puis poursuite etude
                    edges[
                        ("_dipl_" + s["formsemestre_id"], nxt["formsemestre_id"])
                    ].add(etudid)
                else:
                    edges[(s["formsemestre_id"], nxt["formsemestre_id"])].add(etudid)
                connected_nodes.add(s["formsemestre_id"])
                connected_nodes.add(nxt["formsemestre_id"])
            else:
                isolated_nodes.append(s["formsemestre_id"])
            sems[s["formsemestre_id"]] = s
            effectifs[s["formsemestre_id"]].add(etudid)
            nxt = s
            # Compte decisions jury de chaque semestres:
            dc = decisions[s["formsemestre_id"]]
            if dec:
                if dec["code"] in dc:
                    dc[dec["code"]] += 1
                else:
                    dc[dec["code"]] = 1
            # ajout noeud pour demissionnaires
            if nt.get_etud_etat(etudid) == "D":
                nid = "_dem_" + s["formsemestre_id"]
                dem_nodes[s["formsemestre_id"]] = nid
                edges[(s["formsemestre_id"], nid)].add(etudid)
            # ajout noeud pour NAR (seulement pour noeud de depart)
            if s["formsemestre_id"] == formsemestre_id and dec and dec["code"] == NAR:
                nid = "_nar_" + s["formsemestre_id"]
                nar_nodes[s["formsemestre_id"]] = nid
                edges[(s["formsemestre_id"], nid)].add(etudid)

            # si "terminal", ajoute noeud pour diplomes
            if s["semestre_id"] == nt.parcours.NB_SEM:
                if (
                    dec
                    and code_semestre_validant(dec["code"])
                    and nt.get_etud_etat(etudid) == "I"
                ):
                    nid = "_dipl_" + s["formsemestre_id"]
                    edges[(s["formsemestre_id"], nid)].add(etudid)
                    diploma_nodes.append(nid)
    #
    g = pydot.graph_from_edges(edges.keys())
    for fid in isolated_nodes:
        if not fid in connected_nodes:
            n = pydot.Node(name=fid)
            g.add_node(n)
    g.set("rankdir", "LR")  # left to right
    g.set_fontname("Helvetica")
    if format == "svg":
        g.set_bgcolor("#fffff0")  # ou 'transparent'
    # titres des semestres:
    for s in sems.values():
        n = pydot_get_node(g, s["formsemestre_id"])
        log("s['formsemestre_id'] = %s" % s["formsemestre_id"])
        log("n=%s" % n)
        log("get=%s" % g.get_node(s["formsemestre_id"]))
        log("nodes names = %s" % [x.get_name() for x in g.get_node_list()])
        if s["modalite"] and s["modalite"] != "FI":
            modalite = " " + s["modalite"]
        else:
            modalite = ""
        label = "%s%s\\n%d/%s - %d/%s\\n%d" % (
            _codesem(s, short=False, prefix="S"),
            modalite,
            s["mois_debut_ord"],
            s["annee_debut"][2:],
            s["mois_fin_ord"],
            s["annee_fin"][2:],
            len(effectifs[s["formsemestre_id"]]),
        )
        n.set("label", suppress_accents(label))
        n.set_fontname("Helvetica")
        n.set_fontsize(8.0)
        n.set_width(1.2)
        n.set_shape("box")
        n.set_URL("formsemestre_status?formsemestre_id=" + s["formsemestre_id"])
    # semestre de depart en vert
    n = pydot_get_node(g, formsemestre_id)
    n.set_color("green")
    # demissions en rouge, octagonal
    for nid in dem_nodes.values():
        n = pydot_get_node(g, nid)
        n.set_color("red")
        n.set_shape("octagon")
        n.set("label", "Dem.")

    # NAR en rouge, Mcircle
    for nid in nar_nodes.values():
        n = pydot_get_node(g, nid)
        n.set_color("red")
        n.set_shape("Mcircle")
        n.set("label", NAR)
    # diplomes:
    for nid in diploma_nodes:
        n = pydot_get_node(g, nid)
        n.set_color("red")
        n.set_shape("ellipse")
        n.set("label", "Diplome")  # bug si accent (pas compris pourquoi)
    # Arètes:
    bubbles = {}  # substitue titres pour bulle aides: src_id:dst_id : etud_descr
    for (src_id, dst_id) in edges.keys():
        e = g.get_edge(src_id, dst_id)
        e.set("arrowhead", "normal")
        e.set("arrowsize", 1)
        e.set_label(len(edges[(src_id, dst_id)]))
        e.set_fontname("Helvetica")
        e.set_fontsize(8.0)
        # bulle avec liste etudiants
        if len(edges[(src_id, dst_id)]) <= MAX_ETUD_IN_DESCR:
            etud_descr = _descr_etud_set(context, edges[(src_id, dst_id)])
            bubbles[src_id + ":" + dst_id] = etud_descr
            e.set_URL("__xxxetudlist__?" + src_id + ":" + dst_id)
    # Genere graphe
    f, path = tempfile.mkstemp(".gr")
    g.write(path=path, format=format)
    data = open(path, "r").read()
    log("dot generated %d bytes in %s format" % (len(data), format))
    if not data:
        log("graph.to_string=%s" % g.to_string())
        raise ValueError(
            "Erreur lors de la génération du document au format %s" % format
        )
    os.unlink(path)
    if format == "svg":
        # dot génère un document XML complet, il faut enlever l'en-tête
        data = "<svg" + "<svg".join(data.split("<svg")[1:])
        # Substitution des titres des URL des aretes pour bulles aide
        def repl(m):
            return '<a title="%s"' % bubbles[m.group("sd")]

        exp = re.compile(
            r'<a.*?href="__xxxetudlist__\?(?P<sd>\w*:\w*).*?".*?xlink:title=".*?"', re.M
        )
        data = exp.sub(repl, data)
        # Substitution des titres des boites (semestres)
        exp1 = re.compile(
            r'<a xlink:href="formsemestre_status\?formsemestre_id=(?P<fid>\w*).*?".*?xlink:title="(?P<title>.*?)"',
            re.M | re.DOTALL,
        )

        def repl_title(m):
            fid = m.group("fid")
            title = sems[fid]["titreannee"]
            if decisions[fid]:
                title += (
                    (" (" + str(decisions[fid]) + ")").replace("{", "").replace("'", "")
                )
            return (
                '<a xlink:href="formsemestre_status?formsemestre_id=%s" xlink:title="%s"'
                % (fid, suppress_accents(title))
            )  # evite accents car svg utf-8 vs page en latin1...

        data = exp1.sub(repl_title, data)
        # Substitution de Arial par Helvetica (new prblem in Debian 5) ???
        # bug turnaround: il doit bien y avoir un endroit ou regler cela ?
        # cf http://groups.google.com/group/pydot/browse_thread/thread/b3704c53e331e2ec
        data = data.replace("font-family:Arial", "font-family:Helvetica")

    return data, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts


def formsemestre_graph_parcours(
    context,
    formsemestre_id,
    format="html",
    only_primo=False,
    bac="",  # selection sur type de bac
    bacspecialite="",
    annee_bac="",
    sexe="",
    statut="",
    allkeys=False,  # unused
    REQUEST=None,
):
    """Graphe suivi cohortes"""
    # log("formsemestre_graph_parcours")
    sem = sco_formsemestre.get_formsemestre(context, formsemestre_id)
    if format == "pdf":
        doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
            context,
            formsemestre_id,
            format="pdf",
            only_primo=only_primo,
            bac=bac,
            bacspecialite=bacspecialite,
            annee_bac=annee_bac,
            sexe=sexe,
            statut=statut,
        )
        filename = make_filename("flux " + sem["titreannee"])
        return sco_pdf.sendPDFFile(REQUEST, doc, filename + ".pdf")
    elif format == "png":
        #
        doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
            context,
            formsemestre_id,
            format="png",
            only_primo=only_primo,
            bac=bac,
            bacspecialite=bacspecialite,
            annee_bac=annee_bac,
            sexe=sexe,
            statut=statut,
        )
        filename = make_filename("flux " + sem["titreannee"])
        REQUEST.RESPONSE.setHeader(
            "content-disposition", 'attachment; filename="%s"' % filename
        )
        REQUEST.RESPONSE.setHeader("content-type", "image/png")
        return doc
    elif format == "html":
        if only_primo:
            op = "only_primo=on&amp;"
        else:
            op = ""
        url = urllib.quote(
            "formsemestre_graph_parcours?formsemestre_id=%s&amp;%sbac=%s&amp;bacspecialite=%s&amp;sexe=%s&amp;statut=%s&amp;format="
            % (formsemestre_id, op, bac, bacspecialite, sexe, statut)
        )
        doc, etuds, bacs, bacspecialites, annee_bacs, sexes, statuts = graph_parcours(
            context,
            formsemestre_id,
            only_primo=only_primo,
            bac=bac,
            bacspecialite=bacspecialite,
            annee_bac=annee_bac,
            sexe=sexe,
            statut=statut,
        )

        H = [
            context.sco_header(
                REQUEST,
                page_title="Parcours étudiants de %(titreannee)s" % sem,
                no_side_bar=True,
            ),
            """<h2 class="formsemestre">Parcours des étudiants de ce semestre</h2>""",
            doc,
            "<p>%d étudiants sélectionnés</p>" % len(etuds),
            _gen_form_selectetuds(
                formsemestre_id,
                REQUEST=REQUEST,
                only_primo=only_primo,
                bac=bac,
                bacspecialite=bacspecialite,
                annee_bac=annee_bac,
                sexe=sexe,
                statut=statut,
                bacs=bacs,
                bacspecialites=bacspecialites,
                annee_bacs=annee_bacs,
                sexes=sexes,
                statuts=statuts,
                percent=0,
            ),
            """<p>Origine et devenir des étudiants inscrits dans %(titreannee)s"""
            % sem,
            # En Debian 4, dot ne genere pas du pdf, et epstopdf ne marche pas sur le .ps ou ps2 générés par dot
            # mais c'est OK en Debian 5
            """(<a href="%spdf">version pdf</a>""" % url,
            """, <a href="%spng">image PNG</a>)""" % url,
            """</p>""",
            """<p class="help">Cette page ne s'affiche correctement que sur les navigateurs récents.</p>""",
            """<p class="help">Le graphe permet de suivre les étudiants inscrits dans le semestre
              sélectionné (dessiné en vert). Chaque rectangle représente un semestre (cliquez dedans
              pour afficher son tableau de bord). Les flèches indiquent le nombre d'étudiants passant
              d'un semestre à l'autre (s'il y en a moins de %s, vous pouvez visualiser leurs noms en
              passant la souris sur le chiffre).
              </p>"""
            % MAX_ETUD_IN_DESCR,
            context.sco_footer(REQUEST),
        ]
        REQUEST.RESPONSE.setHeader("content-type", "application/xhtml+xml")
        return "\n".join(H)
    else:
        raise ValueError("invalid format: %s" % format)
