Compare commits
56 Commits
894ebc4ec8
...
dev
Author | SHA1 | Date | |
---|---|---|---|
98d9fd4026 | |||
28fc41315f | |||
4b30f39354 | |||
58647a734c | |||
29f67cfa0c | |||
1ffdd8676b | |||
8f2ae96338 | |||
a0e94f52b1 | |||
c84f9845b2 | |||
d9e95f2186 | |||
581b0f4f2f | |||
3dbfc85447 | |||
b5bf1ac137 | |||
74d751a586 | |||
1855d4016d | |||
ff94470fb4 | |||
d322452a6e | |||
e1d3940e9d | |||
7dba11996a | |||
3250a600c9 | |||
589d63ff29 | |||
429fed6a1e | |||
1255bf4b9e | |||
1fe7665753 | |||
e08e4a32a8 | |||
b737612adb | |||
9c19e2ac56 | |||
eb60734c26 | |||
329bcc460c | |||
95fc842c1d | |||
e0ca1a458b | |||
eb1abbe868 | |||
412e624791 | |||
e8bf0b3f0a | |||
c057fa11e7 | |||
e15119605f | |||
494567cdb5 | |||
84fcee625d | |||
f62c898162 | |||
7955b989b4 | |||
4f14e3518c | |||
4bf8f4003e | |||
a14d47b15c | |||
09ac9f01f8 | |||
0a5a931d01 | |||
21397272c9 | |||
7058c79975 | |||
d488807c57 | |||
7e026bedb2 | |||
33117cde71 | |||
7d2cde304d | |||
409b80994a | |||
6fb11cb054 | |||
7a0bb4179d | |||
fe3280b91d | |||
3e85c3829d |
@@ -4,9 +4,9 @@ output: ./
|
||||
templates: templates/
|
||||
|
||||
competences:
|
||||
Calculer:
|
||||
name: Calculer
|
||||
abrv: Cal
|
||||
Chercher:
|
||||
name: Chercher
|
||||
abrv: Cher
|
||||
Représenter:
|
||||
name: Représenter
|
||||
abrv: Rep
|
||||
|
@@ -2,16 +2,16 @@
|
||||
# encoding: utf-8
|
||||
|
||||
NO_ST_COLUMNS = {
|
||||
"term": "Trimestre",
|
||||
"assessment": "Nom",
|
||||
"term": "Trimestre",
|
||||
"date": "Date",
|
||||
"exercise": "Exercice",
|
||||
"question": "Question",
|
||||
"competence": "Competence",
|
||||
"theme": "Domaine",
|
||||
"comment": "Commentaire",
|
||||
"score_rate": "Bareme",
|
||||
"is_leveled": "Est_nivele",
|
||||
"score_rate": "Bareme",
|
||||
}
|
||||
|
||||
COLUMNS = {
|
||||
|
0
recopytex/dashboard/__init__.py
Normal file
0
recopytex/dashboard/__init__.py
Normal file
5
recopytex/dashboard/app.py
Normal file
5
recopytex/dashboard/app.py
Normal file
@@ -0,0 +1,5 @@
|
||||
import dash
|
||||
|
||||
app = dash.Dash(__name__, suppress_callback_exceptions=True)
|
||||
# app = dash.Dash(__name__)
|
||||
server = app.server
|
66
recopytex/dashboard/assets/style.css
Normal file
66
recopytex/dashboard/assets/style.css
Normal file
@@ -0,0 +1,66 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
font-family: 'Source Sans Pro','Roboto','Open Sans','Liberation Sans','DejaVu Sans','Verdana','Helvetica','Arial',sans-serif;
|
||||
}
|
||||
|
||||
header {
|
||||
margin: 0px 0px 20px 0px;
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header > h1 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
main {
|
||||
width: 95vw;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
}
|
||||
|
||||
/* Exam analysis */
|
||||
|
||||
#select {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#select > div {
|
||||
width: 40vw;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#analysis {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
#analysis > * {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 45vw;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* Create new exam */
|
||||
|
||||
#new-exam {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#new-exam label {
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
0
recopytex/dashboard/create_exam/__init__.py
Normal file
0
recopytex/dashboard/create_exam/__init__.py
Normal file
355
recopytex/dashboard/create_exam/app.py
Normal file
355
recopytex/dashboard/create_exam/app.py
Normal file
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import dash
|
||||
import dash_html_components as html
|
||||
import dash_core_components as dcc
|
||||
import dash_table
|
||||
import plotly.graph_objects as go
|
||||
from datetime import date, datetime
|
||||
import uuid
|
||||
import pandas as pd
|
||||
import yaml
|
||||
|
||||
from ...scripts.getconfig import config
|
||||
from ...config import NO_ST_COLUMNS
|
||||
from ..app import app
|
||||
from ...scripts.exam import Exam
|
||||
|
||||
QUESTION_COLUMNS = [
|
||||
{"id": "id", "name": "Question"},
|
||||
{
|
||||
"id": "competence",
|
||||
"name": "Competence",
|
||||
"presentation": "dropdown",
|
||||
},
|
||||
{"id": "theme", "name": "Domaine"},
|
||||
{"id": "comment", "name": "Commentaire"},
|
||||
{"id": "score_rate", "name": "Bareme"},
|
||||
{"id": "is_leveled", "name": "Est_nivele"},
|
||||
]
|
||||
|
||||
|
||||
def get_current_year_limit():
|
||||
today = date.today()
|
||||
if today.month > 8:
|
||||
return {
|
||||
"min_date_allowed": date(today.year, 9, 1),
|
||||
"max_date_allowed": date(today.year + 1, 7, 15),
|
||||
"initial_visible_month": today,
|
||||
}
|
||||
|
||||
return {
|
||||
"min_date_allowed": date(today.year - 1, 9, 1),
|
||||
"max_date_allowed": date(today.year, 7, 15),
|
||||
"initial_visible_month": today,
|
||||
}
|
||||
|
||||
|
||||
layout = html.Div(
|
||||
[
|
||||
html.Header(
|
||||
children=[
|
||||
html.H1("Création d'une évaluation"),
|
||||
html.P("Pas encore de sauvegarde", id="is-saved"),
|
||||
html.Button("Enregistrer dans csv", id="save-csv"),
|
||||
],
|
||||
),
|
||||
html.Main(
|
||||
children=[
|
||||
html.Section(
|
||||
children=[
|
||||
html.Form(
|
||||
id="new-exam",
|
||||
children=[
|
||||
html.Label(
|
||||
children=[
|
||||
"Classe",
|
||||
dcc.Dropdown(
|
||||
id="tribe",
|
||||
options=[
|
||||
{"label": t["name"], "value": t["name"]}
|
||||
for t in config["tribes"]
|
||||
],
|
||||
value=config["tribes"][0]["name"],
|
||||
),
|
||||
]
|
||||
),
|
||||
html.Label(
|
||||
children=[
|
||||
"Nom de l'évaluation",
|
||||
dcc.Input(
|
||||
id="exam_name",
|
||||
type="text",
|
||||
placeholder="Nom de l'évaluation",
|
||||
),
|
||||
]
|
||||
),
|
||||
html.Label(
|
||||
children=[
|
||||
"Date",
|
||||
dcc.DatePickerSingle(
|
||||
id="date",
|
||||
date=date.today(),
|
||||
**get_current_year_limit(),
|
||||
),
|
||||
]
|
||||
),
|
||||
html.Label(
|
||||
children=[
|
||||
"Trimestre",
|
||||
dcc.Dropdown(
|
||||
id="term",
|
||||
options=[
|
||||
{"label": i + 1, "value": i + 1}
|
||||
for i in range(3)
|
||||
],
|
||||
value=1,
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
id="form",
|
||||
),
|
||||
html.Section(
|
||||
children=[
|
||||
html.Div(
|
||||
id="exercises",
|
||||
children=[],
|
||||
),
|
||||
html.Button(
|
||||
"Ajouter un exercice",
|
||||
id="add-exercise",
|
||||
className="add-exercise",
|
||||
),
|
||||
html.Div(
|
||||
id="summary",
|
||||
),
|
||||
],
|
||||
id="exercises",
|
||||
),
|
||||
html.Section(
|
||||
children=[
|
||||
html.Div(
|
||||
id="score_rate",
|
||||
),
|
||||
html.Div(
|
||||
id="exercises-viz",
|
||||
),
|
||||
html.Div(
|
||||
id="competences-viz",
|
||||
),
|
||||
html.Div(
|
||||
id="themes-viz",
|
||||
),
|
||||
],
|
||||
id="visualisation",
|
||||
),
|
||||
]
|
||||
),
|
||||
dcc.Store(id="exam_store"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output("exercises", "children"),
|
||||
dash.dependencies.Input("add-exercise", "n_clicks"),
|
||||
dash.dependencies.State("exercises", "children"),
|
||||
)
|
||||
def add_exercise(n_clicks, children):
|
||||
if n_clicks is None:
|
||||
return children
|
||||
element_table = pd.DataFrame(columns=[c["id"] for c in QUESTION_COLUMNS])
|
||||
element_table = element_table.append(
|
||||
pd.Series(
|
||||
data={
|
||||
"id": 1,
|
||||
"competence": "Rechercher",
|
||||
"theme": "",
|
||||
"comment": "",
|
||||
"score_rate": 1,
|
||||
"is_leveled": 1,
|
||||
},
|
||||
name=0,
|
||||
)
|
||||
)
|
||||
new_exercise = html.Div(
|
||||
children=[
|
||||
html.Div(
|
||||
children=[
|
||||
dcc.Input(
|
||||
id={"type": "exercice", "index": str(n_clicks)},
|
||||
type="text",
|
||||
value=f"Exercice {len(children)+1}",
|
||||
placeholder="Nom de l'exercice",
|
||||
className="exercise-name",
|
||||
),
|
||||
html.Button(
|
||||
"X",
|
||||
id={"type": "rm_exercice", "index": str(n_clicks)},
|
||||
className="delete-exercise",
|
||||
),
|
||||
],
|
||||
className="exercise-head",
|
||||
),
|
||||
dash_table.DataTable(
|
||||
id={"type": "elements", "index": str(n_clicks)},
|
||||
columns=QUESTION_COLUMNS,
|
||||
data=element_table.to_dict("records"),
|
||||
editable=True,
|
||||
row_deletable=True,
|
||||
dropdown={
|
||||
"competence": {
|
||||
"options": [
|
||||
{"label": i, "value": i} for i in config["competences"]
|
||||
]
|
||||
},
|
||||
},
|
||||
style_cell={
|
||||
"whiteSpace": "normal",
|
||||
"height": "auto",
|
||||
},
|
||||
),
|
||||
html.Button(
|
||||
"Ajouter un élément de notation",
|
||||
id={"type": "add-element", "index": str(n_clicks)},
|
||||
className="add-element",
|
||||
),
|
||||
],
|
||||
className="exercise",
|
||||
id=f"exercise-{n_clicks}",
|
||||
)
|
||||
children.append(new_exercise)
|
||||
return children
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output(
|
||||
{"type": "elements", "index": dash.dependencies.MATCH}, "data"
|
||||
),
|
||||
dash.dependencies.Input(
|
||||
{"type": "add-element", "index": dash.dependencies.MATCH}, "n_clicks"
|
||||
),
|
||||
[
|
||||
dash.dependencies.State(
|
||||
{"type": "elements", "index": dash.dependencies.MATCH}, "data"
|
||||
),
|
||||
],
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def add_element(n_clicks, elements):
|
||||
if n_clicks is None or n_clicks < len(elements):
|
||||
return elements
|
||||
|
||||
df = pd.DataFrame.from_records(elements)
|
||||
df = df.append(
|
||||
pd.Series(
|
||||
data={
|
||||
"id": len(df) + 1,
|
||||
"competence": "",
|
||||
"theme": "",
|
||||
"comment": "",
|
||||
"score_rate": 1,
|
||||
"is_leveled": 1,
|
||||
},
|
||||
name=n_clicks,
|
||||
)
|
||||
)
|
||||
return df.to_dict("records")
|
||||
|
||||
|
||||
def exam_generalities(tribe, exam_name, date, term, exercices=[], elements=[]):
|
||||
return [
|
||||
html.H1(f"{exam_name} pour les {tribe}"),
|
||||
html.P(f"Fait le {date} (Trimestre {term})"),
|
||||
]
|
||||
|
||||
|
||||
def exercise_summary(identifier, name, elements=[]):
|
||||
df = pd.DataFrame.from_records(elements)
|
||||
return html.Div(
|
||||
[
|
||||
html.H2(name),
|
||||
dash_table.DataTable(
|
||||
columns=[{"id": c, "name": c} for c in df], data=elements
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output("exam_store", "data"),
|
||||
[
|
||||
dash.dependencies.Input("tribe", "value"),
|
||||
dash.dependencies.Input("exam_name", "value"),
|
||||
dash.dependencies.Input("date", "date"),
|
||||
dash.dependencies.Input("term", "value"),
|
||||
dash.dependencies.Input(
|
||||
{"type": "exercice", "index": dash.dependencies.ALL}, "value"
|
||||
),
|
||||
dash.dependencies.Input(
|
||||
{"type": "elements", "index": dash.dependencies.ALL}, "data"
|
||||
),
|
||||
],
|
||||
dash.dependencies.State({"type": "elements", "index": dash.dependencies.ALL}, "id"),
|
||||
)
|
||||
def store_exam(tribe, exam_name, date, term, exercices, elements, elements_id):
|
||||
exam = Exam(exam_name, tribe, date, term)
|
||||
for (i, name) in enumerate(exercices):
|
||||
ex_elements_id = [el for el in elements_id if el["index"] == str(i + 1)][0]
|
||||
index = elements_id.index(ex_elements_id)
|
||||
ex_elements = elements[index]
|
||||
exam.add_exercise(name, ex_elements)
|
||||
|
||||
return exam.to_dict()
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output("score_rate", "children"),
|
||||
dash.dependencies.Input("exam_store", "data"),
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def score_rate(data):
|
||||
exam = Exam(**data)
|
||||
return [html.P(f"Barème /{exam.score_rate}")]
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output("competences-viz", "figure"),
|
||||
dash.dependencies.Input("exam_store", "data"),
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def competences_viz(data):
|
||||
exam = Exam(**data)
|
||||
return [html.P(str(exam.competences_rate))]
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output("themes-viz", "children"),
|
||||
dash.dependencies.Input("exam_store", "data"),
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def themes_viz(data):
|
||||
exam = Exam(**data)
|
||||
themes_rate = exam.themes_rate
|
||||
fig = go.Figure()
|
||||
if themes_rate:
|
||||
fig.add_trace(go.Pie(labels=list(themes_rate.keys()), values=list(themes_rate.values())))
|
||||
return [dcc.Graph(figure=fig)]
|
||||
return []
|
||||
|
||||
|
||||
@app.callback(
|
||||
dash.dependencies.Output("is-saved", "children"),
|
||||
dash.dependencies.Input("save-csv", "n_clicks"),
|
||||
dash.dependencies.State("exam_store", "data"),
|
||||
prevent_initial_call=True,
|
||||
)
|
||||
def save_to_csv(n_clicks, data):
|
||||
exam = Exam(**data)
|
||||
csv = exam.path(".csv")
|
||||
exam.write_csv()
|
||||
return [f"Dernière sauvegarde {datetime.today()} dans {csv}"]
|
0
recopytex/dashboard/exam_analysis/__init__.py
Normal file
0
recopytex/dashboard/exam_analysis/__init__.py
Normal file
406
recopytex/dashboard/exam_analysis/app.py
Normal file
406
recopytex/dashboard/exam_analysis/app.py
Normal file
@@ -0,0 +1,406 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import dash
|
||||
import dash_html_components as html
|
||||
import dash_core_components as dcc
|
||||
import dash_table
|
||||
from dash.exceptions import PreventUpdate
|
||||
import plotly.graph_objects as go
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
|
||||
from ... import flat_df_students, pp_q_scores
|
||||
from ...config import NO_ST_COLUMNS
|
||||
from ...scripts.getconfig import config
|
||||
from ..app import app
|
||||
|
||||
COLORS = {
|
||||
".": "black",
|
||||
0: "#E7472B",
|
||||
1: "#FF712B",
|
||||
2: "#F2EC4C",
|
||||
3: "#68D42F",
|
||||
}
|
||||
|
||||
layout = html.Div(
|
||||
children=[
|
||||
html.Header(
|
||||
children=[
|
||||
html.H1("Analyse des notes"),
|
||||
html.P("Dernière sauvegarde", id="lastsave"),
|
||||
],
|
||||
),
|
||||
html.Main(
|
||||
[
|
||||
html.Section(
|
||||
[
|
||||
html.Div(
|
||||
[
|
||||
"Classe: ",
|
||||
dcc.Dropdown(
|
||||
id="tribe",
|
||||
options=[
|
||||
{"label": t["name"], "value": t["name"]}
|
||||
for t in config["tribes"]
|
||||
],
|
||||
value=config["tribes"][0]["name"],
|
||||
),
|
||||
],
|
||||
style={
|
||||
"display": "flex",
|
||||
"flex-flow": "column",
|
||||
},
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
"Evaluation: ",
|
||||
dcc.Dropdown(id="csv"),
|
||||
],
|
||||
style={
|
||||
"display": "flex",
|
||||
"flex-flow": "column",
|
||||
},
|
||||
),
|
||||
],
|
||||
id="select",
|
||||
style={
|
||||
"display": "flex",
|
||||
"flex-flow": "row wrap",
|
||||
},
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
html.Div(
|
||||
dash_table.DataTable(
|
||||
id="final_score_table",
|
||||
columns=[
|
||||
{"id": "Eleve", "name": "Élève"},
|
||||
{"id": "Note", "name": "Note"},
|
||||
{"id": "Bareme", "name": "Barème"},
|
||||
],
|
||||
data=[],
|
||||
style_data_conditional=[
|
||||
{
|
||||
"if": {"row_index": "odd"},
|
||||
"backgroundColor": "rgb(248, 248, 248)",
|
||||
}
|
||||
],
|
||||
style_data={
|
||||
"width": "100px",
|
||||
"maxWidth": "100px",
|
||||
"minWidth": "100px",
|
||||
},
|
||||
),
|
||||
id="final_score_table_container",
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
dash_table.DataTable(
|
||||
id="final_score_describe",
|
||||
columns=[
|
||||
{"id": "count", "name": "count"},
|
||||
{"id": "mean", "name": "mean"},
|
||||
{"id": "std", "name": "std"},
|
||||
{"id": "min", "name": "min"},
|
||||
{"id": "25%", "name": "25%"},
|
||||
{"id": "50%", "name": "50%"},
|
||||
{"id": "75%", "name": "75%"},
|
||||
{"id": "max", "name": "max"},
|
||||
],
|
||||
),
|
||||
dcc.Graph(
|
||||
id="fig_assessment_hist",
|
||||
),
|
||||
dcc.Graph(id="fig_competences"),
|
||||
],
|
||||
id="desc_plots",
|
||||
),
|
||||
],
|
||||
id="analysis",
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
dash_table.DataTable(
|
||||
id="scores_table",
|
||||
columns=[
|
||||
{"id": "id", "name": "Question"},
|
||||
{
|
||||
"id": "competence",
|
||||
"name": "Competence",
|
||||
},
|
||||
{"id": "theme", "name": "Domaine"},
|
||||
{"id": "comment", "name": "Commentaire"},
|
||||
{"id": "score_rate", "name": "Bareme"},
|
||||
{"id": "is_leveled", "name": "Est_nivele"},
|
||||
],
|
||||
style_cell={
|
||||
"whiteSpace": "normal",
|
||||
"height": "auto",
|
||||
},
|
||||
fixed_columns={"headers": True, "data": 7},
|
||||
style_table={"minWidth": "100%"},
|
||||
style_data_conditional=[],
|
||||
editable=True,
|
||||
),
|
||||
html.Button("Ajouter un élément", id="btn_add_element"),
|
||||
],
|
||||
id="big_table",
|
||||
),
|
||||
dcc.Store(id="final_score"),
|
||||
],
|
||||
className="content",
|
||||
style={
|
||||
"width": "95vw",
|
||||
"margin": "auto",
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("csv", "options"),
|
||||
dash.dependencies.Output("csv", "value"),
|
||||
],
|
||||
[dash.dependencies.Input("tribe", "value")],
|
||||
)
|
||||
def update_csvs(value):
|
||||
if not value:
|
||||
raise PreventUpdate
|
||||
p = Path(value)
|
||||
csvs = list(p.glob("*.csv"))
|
||||
try:
|
||||
return [{"label": str(c), "value": str(c)} for c in csvs], str(csvs[0])
|
||||
except IndexError:
|
||||
return []
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("final_score", "data"),
|
||||
],
|
||||
[dash.dependencies.Input("scores_table", "data")],
|
||||
)
|
||||
def update_final_scores(data):
|
||||
if not data:
|
||||
raise PreventUpdate
|
||||
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
try:
|
||||
if scores.iloc[0]["Commentaire"] == "commentaire" or scores.iloc[0].str.contains("PPRE").any():
|
||||
scores.drop([0], inplace=True)
|
||||
except KeyError:
|
||||
pass
|
||||
scores = flat_df_students(scores).dropna(subset=["Score"])
|
||||
if scores.empty:
|
||||
return [{}]
|
||||
|
||||
scores = pp_q_scores(scores)
|
||||
assessment_scores = scores.groupby(["Eleve"]).agg({"Note": "sum", "Bareme": "sum"})
|
||||
return [assessment_scores.reset_index().to_dict("records")]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("final_score_table", "data"),
|
||||
],
|
||||
[dash.dependencies.Input("final_score", "data")],
|
||||
)
|
||||
def update_final_scores_table(data):
|
||||
assessment_scores = pd.DataFrame.from_records(data)
|
||||
return [assessment_scores.to_dict("records")]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("final_score_describe", "data"),
|
||||
],
|
||||
[dash.dependencies.Input("final_score", "data")],
|
||||
)
|
||||
def update_final_scores_descr(data):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
if scores.empty:
|
||||
return [[{}]]
|
||||
desc = scores["Note"].describe().T.round(2)
|
||||
return [[desc.to_dict()]]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("fig_assessment_hist", "figure"),
|
||||
],
|
||||
[dash.dependencies.Input("final_score", "data")],
|
||||
)
|
||||
def update_final_scores_hist(data):
|
||||
assessment_scores = pd.DataFrame.from_records(data)
|
||||
|
||||
if assessment_scores.empty:
|
||||
return [go.Figure(data=[go.Scatter(x=[], y=[])])]
|
||||
|
||||
ranges = np.linspace(
|
||||
-0.5,
|
||||
assessment_scores.Bareme.max(),
|
||||
int(assessment_scores.Bareme.max() * 2 + 2),
|
||||
)
|
||||
bins = pd.cut(assessment_scores["Note"], ranges)
|
||||
assessment_scores["Bin"] = bins
|
||||
assessment_grouped = (
|
||||
assessment_scores.reset_index()
|
||||
.groupby("Bin")
|
||||
.agg({"Bareme": "count", "Eleve": lambda x: "\n".join(x)})
|
||||
)
|
||||
assessment_grouped.index = assessment_grouped.index.map(lambda i: i.right)
|
||||
fig = go.Figure()
|
||||
fig.add_bar(
|
||||
x=assessment_grouped.index,
|
||||
y=assessment_grouped.Bareme,
|
||||
text=assessment_grouped.Eleve,
|
||||
textposition="auto",
|
||||
hovertemplate="",
|
||||
marker_color="#4E89DE",
|
||||
)
|
||||
fig.update_layout(
|
||||
height=300,
|
||||
margin=dict(l=5, r=5, b=5, t=5),
|
||||
)
|
||||
return [fig]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("fig_competences", "figure"),
|
||||
],
|
||||
[dash.dependencies.Input("scores_table", "data")],
|
||||
)
|
||||
def update_competence_fig(data):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
try:
|
||||
if scores.iloc[0]["Commentaire"] == "commentaire" or scores.iloc[0].str.contains("PPRE").any():
|
||||
scores.drop([0], inplace=True)
|
||||
except KeyError:
|
||||
pass
|
||||
scores = flat_df_students(scores).dropna(subset=["Score"])
|
||||
|
||||
if scores.empty:
|
||||
return [go.Figure(data=[go.Scatter(x=[], y=[])])]
|
||||
|
||||
scores = pp_q_scores(scores)
|
||||
pt = pd.pivot_table(
|
||||
scores,
|
||||
index=["Exercice", "Question", "Commentaire"],
|
||||
columns="Score",
|
||||
aggfunc="size",
|
||||
fill_value=0,
|
||||
)
|
||||
for i in {i for i in pt.index.get_level_values(0)}:
|
||||
pt.loc[(str(i), "", ""), :] = ""
|
||||
pt.sort_index(inplace=True)
|
||||
index = (
|
||||
pt.index.get_level_values(0).map(str)
|
||||
+ ":"
|
||||
+ pt.index.get_level_values(1).map(str)
|
||||
+ " "
|
||||
+ pt.index.get_level_values(2).map(str)
|
||||
)
|
||||
|
||||
fig = go.Figure()
|
||||
bars = [
|
||||
{"score": -1, "name": "Pas de réponse", "color": COLORS["."]},
|
||||
{"score": 0, "name": "Faux", "color": COLORS[0]},
|
||||
{"score": 1, "name": "Peu juste", "color": COLORS[1]},
|
||||
{"score": 2, "name": "Presque juste", "color": COLORS[2]},
|
||||
{"score": 3, "name": "Juste", "color": COLORS[3]},
|
||||
]
|
||||
for b in bars:
|
||||
try:
|
||||
fig.add_bar(
|
||||
x=index, y=pt[b["score"]], name=b["name"], marker_color=b["color"]
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
fig.update_layout(barmode="relative")
|
||||
fig.update_layout(
|
||||
height=500,
|
||||
margin=dict(l=5, r=5, b=5, t=5),
|
||||
)
|
||||
return [fig]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("lastsave", "children"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("scores_table", "data"),
|
||||
dash.dependencies.State("csv", "value"),
|
||||
],
|
||||
)
|
||||
def save_scores(data, csv):
|
||||
try:
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
scores = scores_table_column_order(scores)
|
||||
scores.to_csv(csv, index=False)
|
||||
except:
|
||||
return [f"Soucis pour sauvegarder à {datetime.today()} dans {csv}"]
|
||||
else:
|
||||
return [f"Dernière sauvegarde {datetime.today()} dans {csv}"]
|
||||
|
||||
|
||||
def highlight_value(df):
|
||||
""" Cells style """
|
||||
hight = []
|
||||
for v, color in COLORS.items():
|
||||
hight += [
|
||||
{
|
||||
"if": {"filter_query": "{{{}}} = {}".format(col, v), "column_id": col},
|
||||
"backgroundColor": color,
|
||||
"color": "white",
|
||||
}
|
||||
for col in df.columns
|
||||
if col not in NO_ST_COLUMNS.values()
|
||||
]
|
||||
return hight
|
||||
|
||||
def scores_table_column_order(df):
|
||||
df_student_columns = [c for c in df.columns if c not in NO_ST_COLUMNS.values()]
|
||||
order = list(NO_ST_COLUMNS.values())+df_student_columns
|
||||
return df.loc[:, order]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("scores_table", "columns"),
|
||||
dash.dependencies.Output("scores_table", "data"),
|
||||
dash.dependencies.Output("scores_table", "style_data_conditional"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("csv", "value"),
|
||||
dash.dependencies.Input("btn_add_element", "n_clicks"),
|
||||
dash.dependencies.State("scores_table", "data"),
|
||||
],
|
||||
)
|
||||
def update_scores_table(csv, add_element, data):
|
||||
ctx = dash.callback_context
|
||||
if ctx.triggered[0]["prop_id"] == "csv.value":
|
||||
stack = pd.read_csv(csv, encoding="UTF8")
|
||||
elif ctx.triggered[0]["prop_id"] == "btn_add_element.n_clicks":
|
||||
stack = pd.DataFrame.from_records(data)
|
||||
infos = pd.DataFrame.from_records(
|
||||
[{k: stack.iloc[-1][k] for k in NO_ST_COLUMNS.values()}]
|
||||
)
|
||||
stack = stack.append(infos)
|
||||
stack = scores_table_column_order(stack)
|
||||
return (
|
||||
[
|
||||
{"id": c, "name": c}
|
||||
for c in stack.columns
|
||||
if c not in ["Trimestre", "Nom", "Date"]
|
||||
],
|
||||
stack.to_dict("records"),
|
||||
highlight_value(stack),
|
||||
)
|
29
recopytex/dashboard/index.py
Normal file
29
recopytex/dashboard/index.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import dash_core_components as dcc
|
||||
import dash_html_components as html
|
||||
from dash.dependencies import Input, Output
|
||||
|
||||
from .app import app
|
||||
from .exam_analysis import app as exam_analysis
|
||||
from .create_exam import app as create_exam
|
||||
from .student_analysis import app as student_analysis
|
||||
|
||||
|
||||
app.layout = html.Div(
|
||||
[dcc.Location(id="url", refresh=False), html.Div(id="page-content")]
|
||||
)
|
||||
|
||||
|
||||
@app.callback(Output("page-content", "children"), Input("url", "pathname"))
|
||||
def display_page(pathname):
|
||||
if pathname == "/":
|
||||
return exam_analysis.layout
|
||||
elif pathname == "/create-exam":
|
||||
return create_exam.layout
|
||||
elif pathname == "/students":
|
||||
return student_analysis.layout
|
||||
else:
|
||||
return "404"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run_server(debug=True)
|
0
recopytex/dashboard/student_analysis/__init__.py
Normal file
0
recopytex/dashboard/student_analysis/__init__.py
Normal file
300
recopytex/dashboard/student_analysis/app.py
Normal file
300
recopytex/dashboard/student_analysis/app.py
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import dash
|
||||
import dash_html_components as html
|
||||
import dash_core_components as dcc
|
||||
import dash_table
|
||||
import plotly.graph_objects as go
|
||||
from datetime import date, datetime
|
||||
import uuid
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
|
||||
from ...scripts.getconfig import config
|
||||
from ... import flat_df_students, pp_q_scores
|
||||
from ...config import NO_ST_COLUMNS
|
||||
from ..app import app
|
||||
from ...scripts.exam import Exam
|
||||
|
||||
|
||||
def get_students(csv):
|
||||
return list(pd.read_csv(csv).T.to_dict().values())
|
||||
|
||||
|
||||
COLORS = {
|
||||
".": "black",
|
||||
0: "#E7472B",
|
||||
1: "#FF712B",
|
||||
2: "#F2EC4C",
|
||||
3: "#68D42F",
|
||||
}
|
||||
|
||||
QUESTION_COLUMNS = [
|
||||
{"id": "id", "name": "Question"},
|
||||
{
|
||||
"id": "competence",
|
||||
"name": "Competence",
|
||||
"presentation": "dropdown",
|
||||
},
|
||||
{"id": "theme", "name": "Domaine"},
|
||||
{"id": "comment", "name": "Commentaire"},
|
||||
{"id": "score_rate", "name": "Bareme"},
|
||||
{"id": "is_leveled", "name": "Est_nivele"},
|
||||
]
|
||||
|
||||
layout = html.Div(
|
||||
[
|
||||
html.Header(
|
||||
children=[
|
||||
html.H1("Bilan des élèves"),
|
||||
],
|
||||
),
|
||||
html.Main(
|
||||
children=[
|
||||
html.Section(
|
||||
children=[
|
||||
html.Form(
|
||||
id="select-student",
|
||||
children=[
|
||||
html.Label(
|
||||
children=[
|
||||
"Classe",
|
||||
dcc.Dropdown(
|
||||
id="tribe",
|
||||
options=[
|
||||
{"label": t["name"], "value": t["name"]}
|
||||
for t in config["tribes"]
|
||||
],
|
||||
value=config["tribes"][0]["name"],
|
||||
),
|
||||
]
|
||||
),
|
||||
html.Label(
|
||||
children=[
|
||||
"Élève",
|
||||
dcc.Dropdown(
|
||||
id="student",
|
||||
options=[
|
||||
{"label": t["Nom"], "value": t["Nom"]}
|
||||
for t in get_students(config["tribes"][0]["students"])
|
||||
],
|
||||
value=get_students(config["tribes"][0]["students"])[0]["Nom"],
|
||||
),
|
||||
]
|
||||
),
|
||||
html.Label(
|
||||
children=[
|
||||
"Trimestre",
|
||||
dcc.Dropdown(
|
||||
id="term",
|
||||
options=[
|
||||
{"label": i + 1, "value": i + 1}
|
||||
for i in range(3)
|
||||
],
|
||||
value=1,
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
id="form",
|
||||
),
|
||||
html.Section(
|
||||
children=[
|
||||
html.H2("Évaluations"),
|
||||
html.Div(
|
||||
dash_table.DataTable(
|
||||
id="exam_scores",
|
||||
columns=[
|
||||
{"id": "Nom", "name": "Évaluations"},
|
||||
{"id": "Note", "name": "Note"},
|
||||
{"id": "Bareme", "name": "Barème"},
|
||||
],
|
||||
data=[],
|
||||
style_data_conditional=[
|
||||
{
|
||||
"if": {"row_index": "odd"},
|
||||
"backgroundColor": "rgb(248, 248, 248)",
|
||||
}
|
||||
],
|
||||
style_data={
|
||||
"width": "100px",
|
||||
"maxWidth": "100px",
|
||||
"minWidth": "100px",
|
||||
},
|
||||
),
|
||||
id="eval-table",
|
||||
),
|
||||
],
|
||||
id="Évaluations",
|
||||
),
|
||||
html.Section(
|
||||
children=[
|
||||
html.Div(
|
||||
id="competences-viz",
|
||||
),
|
||||
html.Div(
|
||||
id="themes-vizz",
|
||||
),
|
||||
],
|
||||
id="visualisation",
|
||||
),
|
||||
]
|
||||
),
|
||||
dcc.Store(id="student-scores"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("student", "options"),
|
||||
dash.dependencies.Output("student", "value"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("tribe", "value")
|
||||
],)
|
||||
def update_students_list(tribe):
|
||||
tribe_config = [t for t in config["tribes"] if t["name"] == tribe][0]
|
||||
students = get_students(tribe_config["students"])
|
||||
options = [
|
||||
{"label": t["Nom"], "value": t["Nom"]}
|
||||
for t in students
|
||||
]
|
||||
value = students[0]["Nom"]
|
||||
return options, value
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("student-scores", "data"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("tribe", "value"),
|
||||
dash.dependencies.Input("student", "value"),
|
||||
dash.dependencies.Input("term", "value"),
|
||||
],
|
||||
)
|
||||
def update_student_scores(tribe, student, term):
|
||||
tribe_config = [t for t in config["tribes"] if t["name"] == tribe][0]
|
||||
|
||||
p = Path(tribe_config["name"])
|
||||
csvs = list(p.glob("*.csv"))
|
||||
|
||||
dfs = []
|
||||
for csv in csvs:
|
||||
try:
|
||||
scores = pd.read_csv(csv)
|
||||
except pd.errors.ParserError:
|
||||
pass
|
||||
else:
|
||||
if scores.iloc[0]["Commentaire"] == "commentaire" or scores.iloc[0].str.contains("PPRE").any():
|
||||
scores.drop([0], inplace=True)
|
||||
scores = flat_df_students(scores).dropna(subset=["Score"])
|
||||
scores = scores[scores["Eleve"] == student]
|
||||
scores = scores[scores["Trimestre"] == term]
|
||||
dfs.append(scores)
|
||||
|
||||
df = pd.concat(dfs)
|
||||
|
||||
return [df.to_dict("records")]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("exam_scores", "data"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("student-scores", "data"),
|
||||
],
|
||||
)
|
||||
def update_exam_scores(data):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
scores = pp_q_scores(scores)
|
||||
assessment_scores = scores.groupby(["Nom"]).agg({"Note": "sum", "Bareme": "sum"})
|
||||
return [assessment_scores.reset_index().to_dict("records")]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("competences-viz", "children"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("student-scores", "data"),
|
||||
],
|
||||
)
|
||||
def update_competences_viz(data):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
scores = pp_q_scores(scores)
|
||||
pt = pd.pivot_table(
|
||||
scores,
|
||||
index=["Competence"],
|
||||
columns="Score",
|
||||
aggfunc="size",
|
||||
fill_value=0,
|
||||
)
|
||||
fig = go.Figure()
|
||||
bars = [
|
||||
{"score": -1, "name": "Pas de réponse", "color": COLORS["."]},
|
||||
{"score": 0, "name": "Faux", "color": COLORS[0]},
|
||||
{"score": 1, "name": "Peu juste", "color": COLORS[1]},
|
||||
{"score": 2, "name": "Presque juste", "color": COLORS[2]},
|
||||
{"score": 3, "name": "Juste", "color": COLORS[3]},
|
||||
]
|
||||
for b in bars:
|
||||
try:
|
||||
fig.add_bar(
|
||||
x=list(config["competences"].keys()), y=pt[b["score"]], name=b["name"], marker_color=b["color"]
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
fig.update_layout(barmode="relative")
|
||||
fig.update_layout(
|
||||
height=500,
|
||||
margin=dict(l=5, r=5, b=5, t=5),
|
||||
)
|
||||
return [dcc.Graph(figure=fig)]
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("themes-vizz", "children"),
|
||||
],
|
||||
[
|
||||
dash.dependencies.Input("student-scores", "data"),
|
||||
],
|
||||
)
|
||||
def update_themes_viz(data):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
scores = pp_q_scores(scores)
|
||||
pt = pd.pivot_table(
|
||||
scores,
|
||||
index=["Domaine"],
|
||||
columns="Score",
|
||||
aggfunc="size",
|
||||
fill_value=0,
|
||||
)
|
||||
fig = go.Figure()
|
||||
bars = [
|
||||
{"score": -1, "name": "Pas de réponse", "color": COLORS["."]},
|
||||
{"score": 0, "name": "Faux", "color": COLORS[0]},
|
||||
{"score": 1, "name": "Peu juste", "color": COLORS[1]},
|
||||
{"score": 2, "name": "Presque juste", "color": COLORS[2]},
|
||||
{"score": 3, "name": "Juste", "color": COLORS[3]},
|
||||
]
|
||||
for b in bars:
|
||||
try:
|
||||
fig.add_bar(
|
||||
x=list(pt.index), y=pt[b["score"]], name=b["name"], marker_color=b["color"]
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
fig.update_layout(barmode="relative")
|
||||
fig.update_layout(
|
||||
height=500,
|
||||
margin=dict(l=5, r=5, b=5, t=5),
|
||||
)
|
||||
return [dcc.Graph(figure=fig)]
|
||||
|
@@ -4,7 +4,7 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from math import ceil, floor
|
||||
from .config import COLUMNS, VALIDSCORE
|
||||
from .config import COLUMNS
|
||||
|
||||
"""
|
||||
Functions for manipulate score dataframes
|
||||
@@ -49,7 +49,7 @@ def score_to_mark(x):
|
||||
raise ValueError(
|
||||
f"The evaluation is out of range: {x[COLUMNS['score']]} at {x}"
|
||||
)
|
||||
return round_half_point(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3)
|
||||
return round(x[COLUMNS["score"]] * x[COLUMNS["score_rate"]] / 3, 2)
|
||||
|
||||
if x[COLUMNS["score"]] > x[COLUMNS["score_rate"]]:
|
||||
raise ValueError(
|
||||
|
10
recopytex/scripts/config.py
Normal file
10
recopytex/scripts/config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import yaml
|
||||
|
||||
CONFIGPATH = "recoconfig.yml"
|
||||
|
||||
with open(CONFIGPATH, "r") as configfile:
|
||||
config = yaml.load(configfile, Loader=yaml.FullLoader)
|
||||
|
@@ -3,23 +3,43 @@
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from prompt_toolkit import HTML
|
||||
|
||||
# from prompt_toolkit import HTML
|
||||
from ..config import NO_ST_COLUMNS
|
||||
import pandas as pd
|
||||
import yaml
|
||||
from .getconfig import config
|
||||
|
||||
|
||||
def try_parsing_date(text, formats=["%Y-%m-%d", "%Y.%m.%d", "%Y/%m/%d"]):
|
||||
for fmt in formats:
|
||||
try:
|
||||
return datetime.strptime(text[:10], fmt)
|
||||
except ValueError:
|
||||
pass
|
||||
raise ValueError("no valid date format found")
|
||||
|
||||
|
||||
def format_question(question):
|
||||
question["score_rate"] = float(question["score_rate"])
|
||||
return question
|
||||
|
||||
|
||||
class Exam:
|
||||
def __init__(self, name, tribename, date, term, **kwrds):
|
||||
self._name = name
|
||||
self._tribename = tribename
|
||||
try:
|
||||
self._date = datetime.strptime(date, "%y%m%d")
|
||||
except:
|
||||
self._date = date
|
||||
|
||||
self._date = try_parsing_date(date)
|
||||
|
||||
self._term = term
|
||||
|
||||
self._exercises = {}
|
||||
try:
|
||||
kwrds["exercices"]
|
||||
except KeyError:
|
||||
self._exercises = {}
|
||||
else:
|
||||
self._exercises = kwrds["exercices"]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@@ -38,11 +58,13 @@ class Exam:
|
||||
return self._term
|
||||
|
||||
def add_exercise(self, name, questions):
|
||||
""" Add key with questions in ._exercises """
|
||||
"""Add key with questions in ._exercises"""
|
||||
try:
|
||||
self._exercises[name]
|
||||
except KeyError:
|
||||
self._exercises[name] = questions
|
||||
self._exercises[name] = [
|
||||
format_question(question) for question in questions
|
||||
]
|
||||
else:
|
||||
raise KeyError("The exercise already exsists. Use modify_exercise")
|
||||
|
||||
@@ -58,9 +80,9 @@ class Exam:
|
||||
raise KeyError("The exercise already exsists. Use modify_exercise")
|
||||
else:
|
||||
if append:
|
||||
self._exercises[name] += questions
|
||||
self._exercises[name] += format_question(questions)
|
||||
else:
|
||||
self._exercises[name] = questions
|
||||
self._exercises[name] = format_question(questions)
|
||||
|
||||
@property
|
||||
def exercices(self):
|
||||
@@ -81,7 +103,7 @@ class Exam:
|
||||
|
||||
@property
|
||||
def long_name(self):
|
||||
""" Get exam name with date inside """
|
||||
"""Get exam name with date inside"""
|
||||
return f"{self.date.strftime('%y%m%d')}_{self.name}"
|
||||
|
||||
def path(self, extention=""):
|
||||
@@ -125,8 +147,65 @@ class Exam:
|
||||
def display(self, name):
|
||||
pass
|
||||
|
||||
def write(self):
|
||||
def write_yaml(self):
|
||||
print(f"Sauvegarde temporaire dans {self.path('.yml')}")
|
||||
self.tribe_path.mkdir(exist_ok=True)
|
||||
with open(self.path(".yml"), "w") as f:
|
||||
f.write(yaml.dump(self.to_dict()))
|
||||
|
||||
def write_csv(self):
|
||||
rows = self.to_row()
|
||||
|
||||
print(rows)
|
||||
base_df = pd.DataFrame.from_dict(rows)[NO_ST_COLUMNS.keys()]
|
||||
base_df.rename(columns=NO_ST_COLUMNS, inplace=True)
|
||||
|
||||
students = pd.read_csv(self.tribe_student_path)["Nom"]
|
||||
for student in students:
|
||||
base_df[student] = ""
|
||||
|
||||
self.tribe_path.mkdir(exist_ok=True)
|
||||
base_df.to_csv(self.path(".csv"), index=False)
|
||||
|
||||
@property
|
||||
def score_rate(self):
|
||||
total = 0
|
||||
for ex, questions in self._exercises.items():
|
||||
total += sum([q["score_rate"] for q in questions])
|
||||
|
||||
return total
|
||||
|
||||
@property
|
||||
def competences_rate(self):
|
||||
"""Dictionnary with competences as key and total rate as value"""
|
||||
rates = {}
|
||||
for ex, questions in self._exercises.items():
|
||||
for q in questions:
|
||||
try:
|
||||
q["competence"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
rates[q["competence"]] += q["score_rate"]
|
||||
except KeyError:
|
||||
rates[q["competence"]] = q["score_rate"]
|
||||
return rates
|
||||
|
||||
@property
|
||||
def themes_rate(self):
|
||||
"""Dictionnary with themes as key and total rate as value"""
|
||||
rates = {}
|
||||
for ex, questions in self._exercises.items():
|
||||
for q in questions:
|
||||
try:
|
||||
q["theme"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if q["theme"]:
|
||||
try:
|
||||
rates[q["theme"]] += q["score_rate"]
|
||||
except KeyError:
|
||||
rates[q["theme"]] = q["score_rate"]
|
||||
return rates
|
||||
|
@@ -1,299 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import dash
|
||||
import dash_html_components as html
|
||||
import dash_core_components as dcc
|
||||
import dash_table
|
||||
from dash.exceptions import PreventUpdate
|
||||
import plotly.graph_objects as go
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
|
||||
from .. import flat_df_students, pp_q_scores
|
||||
from ..config import NO_ST_COLUMNS
|
||||
from .getconfig import config, CONFIGPATH
|
||||
|
||||
COLORS = {
|
||||
".": "black",
|
||||
0: "#E7472B",
|
||||
1: "#FF712B",
|
||||
2: "#F2EC4C",
|
||||
3: "#68D42F",
|
||||
}
|
||||
|
||||
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
|
||||
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
|
||||
# app = dash.Dash(__name__)
|
||||
|
||||
app.layout = html.Div(
|
||||
children=[
|
||||
html.H1("Analyse des notes"),
|
||||
html.Div(
|
||||
[
|
||||
"Classe: ",
|
||||
dcc.Dropdown(
|
||||
id="tribe",
|
||||
options=[
|
||||
{"label": t["name"], "value": t["name"]}
|
||||
for t in config["tribes"]
|
||||
],
|
||||
value=config["tribes"][0]["name"],
|
||||
),
|
||||
"Evaluation: ",
|
||||
dcc.Dropdown(id="csv"),
|
||||
],
|
||||
style={"columnCount": 2},
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
dash_table.DataTable(
|
||||
id="final_score_table",
|
||||
columns=[
|
||||
{"id": "Élève", "name": "Élève"},
|
||||
{"id": "Note", "name": "Note"},
|
||||
{"id": "Barème", "name": "Bareme"},
|
||||
],
|
||||
data=[],
|
||||
style_data_conditional=[
|
||||
{
|
||||
"if": {"row_index": "odd"},
|
||||
"backgroundColor": "rgb(248, 248, 248)",
|
||||
}
|
||||
],
|
||||
style_header={
|
||||
"backgroundColor": "rgb(230, 230, 230)",
|
||||
"fontWeight": "bold",
|
||||
},
|
||||
style_data={
|
||||
"width": "100px",
|
||||
"maxWidth": "100px",
|
||||
"minWidth": "100px",
|
||||
},
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
dash_table.DataTable(
|
||||
id="final_score_describe",
|
||||
),
|
||||
dcc.Graph(id="fig_assessment_hist"),
|
||||
dcc.Graph(id="fig_competences"),
|
||||
]
|
||||
),
|
||||
],
|
||||
style={"columnCount": 2},
|
||||
),
|
||||
html.Br(),
|
||||
html.Div(
|
||||
[
|
||||
dash_table.DataTable(
|
||||
id="scores_table",
|
||||
columns=[{"id": c, "name": c} for c in NO_ST_COLUMNS.values()],
|
||||
style_cell={
|
||||
"whiteSpace": "normal",
|
||||
"height": "auto",
|
||||
},
|
||||
style_data_conditional=[],
|
||||
editable=True,
|
||||
)
|
||||
]
|
||||
),
|
||||
html.P(id="lastsave"),
|
||||
dcc.Store(id="final_score"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("csv", "options"),
|
||||
dash.dependencies.Output("csv", "value"),
|
||||
],
|
||||
[dash.dependencies.Input("tribe", "value")],
|
||||
)
|
||||
def update_csvs(value):
|
||||
if not value:
|
||||
raise PreventUpdate
|
||||
p = Path(value)
|
||||
csvs = list(p.glob("*.csv"))
|
||||
try:
|
||||
return [{"label": str(c), "value": str(c)} for c in csvs], str(csvs[0])
|
||||
except IndexError:
|
||||
return []
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("final_score", "data"),
|
||||
],
|
||||
[dash.dependencies.Input("scores_table", "data")],
|
||||
)
|
||||
def update_final_scores(data):
|
||||
if not data:
|
||||
raise PreventUpdate
|
||||
try:
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
scores = flat_df_students(scores).dropna(subset=["Score"])
|
||||
scores = pp_q_scores(scores)
|
||||
assessment_scores = scores.groupby(["Eleve"]).agg(
|
||||
{"Note": "sum", "Bareme": "sum"}
|
||||
)
|
||||
return [assessment_scores.reset_index().to_dict("records")]
|
||||
except KeyError:
|
||||
raise PreventUpdate
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("final_score_table", "columns"),
|
||||
dash.dependencies.Output("final_score_table", "data"),
|
||||
],
|
||||
[dash.dependencies.Input("final_score", "data")],
|
||||
)
|
||||
def update_final_scores_table(data):
|
||||
assessment_scores = pd.DataFrame.from_records(data)
|
||||
return [
|
||||
{"id": c, "name": c} for c in assessment_scores.columns
|
||||
], assessment_scores.to_dict("records")
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("final_score_describe", "columns"),
|
||||
dash.dependencies.Output("final_score_describe", "data"),
|
||||
],
|
||||
[dash.dependencies.Input("final_score", "data")],
|
||||
)
|
||||
def update_final_scores_descr(data):
|
||||
desc = pd.DataFrame.from_records(data)["Note"].describe()
|
||||
return [{"id": c, "name": c} for c in desc.keys()], [desc.to_dict()]
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("fig_assessment_hist", "figure"),
|
||||
],
|
||||
[dash.dependencies.Input("final_score", "data")],
|
||||
)
|
||||
def update_final_scores_hist(data):
|
||||
assessment_scores = pd.DataFrame.from_records(data)
|
||||
ranges = np.linspace(
|
||||
0, assessment_scores.Bareme.max(), int(assessment_scores.Bareme.max() * 2 + 1)
|
||||
)
|
||||
bins = pd.cut(assessment_scores["Note"], ranges)
|
||||
assessment_scores["Bin"] = bins
|
||||
assessment_grouped = (
|
||||
assessment_scores.reset_index()
|
||||
.groupby("Bin")
|
||||
.agg({"Bareme": "count", "Eleve": lambda x: "\n".join(x)})
|
||||
)
|
||||
assessment_grouped.index = assessment_grouped.index.map(lambda i: i.right)
|
||||
fig = go.Figure()
|
||||
fig.add_bar(
|
||||
x=assessment_grouped.index,
|
||||
y=assessment_grouped.Bareme,
|
||||
text=assessment_grouped.Eleve,
|
||||
textposition="auto",
|
||||
hovertemplate="",
|
||||
marker_color="#4E89DE",
|
||||
)
|
||||
return [fig]
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("fig_competences", "figure"),
|
||||
],
|
||||
[dash.dependencies.Input("scores_table", "data")],
|
||||
)
|
||||
def update_competence_fig(data):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
scores = flat_df_students(scores).dropna(subset=["Score"])
|
||||
scores = pp_q_scores(scores)
|
||||
pt = pd.pivot_table(
|
||||
scores,
|
||||
index=["Exercice", "Question", "Commentaire"],
|
||||
columns="Score",
|
||||
aggfunc="size",
|
||||
fill_value=0,
|
||||
)
|
||||
for i in {i for i in pt.index.get_level_values(0)}:
|
||||
pt.loc[(str(i), "", ""), :] = ""
|
||||
pt.sort_index(inplace=True)
|
||||
index = (
|
||||
pt.index.get_level_values(0)
|
||||
+ ":"
|
||||
+ pt.index.get_level_values(1)
|
||||
+ " "
|
||||
+ pt.index.get_level_values(2)
|
||||
)
|
||||
|
||||
fig = go.Figure()
|
||||
bars = [
|
||||
{"score": -1, "name":"Pas de réponse", "color": COLORS["."]},
|
||||
{"score": 0, "name":"Faut", "color": COLORS[0]},
|
||||
{"score": 1, "name":"Peu juste", "color": COLORS[1]},
|
||||
{"score": 2, "name":"Presque juste", "color": COLORS[2]},
|
||||
{"score": 3, "name":"Juste", "color": COLORS[3]},
|
||||
]
|
||||
for b in bars:
|
||||
try:
|
||||
fig.add_bar(x=index, y=pt[b["score"]], name=b["name"], marker_color=b["color"])
|
||||
except KeyError:
|
||||
pass
|
||||
fig.update_layout(barmode="relative")
|
||||
return [fig]
|
||||
|
||||
@app.callback(
|
||||
[dash.dependencies.Output("lastsave", "children")],
|
||||
[
|
||||
dash.dependencies.Input("scores_table", "data"),
|
||||
dash.dependencies.State("csv", "value"),
|
||||
],
|
||||
)
|
||||
def save_scores(data, csv):
|
||||
scores = pd.DataFrame.from_records(data)
|
||||
print(f"save at {csv} ({datetime.today()})")
|
||||
scores.to_csv(csv, index=False)
|
||||
return [datetime.today()]
|
||||
|
||||
|
||||
def highlight_value(df):
|
||||
""" Cells style """
|
||||
hight = []
|
||||
for v, color in COLORS.items():
|
||||
hight += [
|
||||
{
|
||||
"if": {"filter_query": "{{{}}} = {}".format(col, v), "column_id": col},
|
||||
"backgroundColor": color,
|
||||
"color": "white",
|
||||
}
|
||||
for col in df.columns
|
||||
if col not in NO_ST_COLUMNS.values()
|
||||
]
|
||||
return hight
|
||||
|
||||
|
||||
@app.callback(
|
||||
[
|
||||
dash.dependencies.Output("scores_table", "columns"),
|
||||
dash.dependencies.Output("scores_table", "data"),
|
||||
dash.dependencies.Output("scores_table", "style_data_conditional"),
|
||||
],
|
||||
[dash.dependencies.Input("csv", "value")],
|
||||
)
|
||||
def update_scores_table(value):
|
||||
if not value:
|
||||
raise PreventUpdate
|
||||
stack = pd.read_csv(value, encoding="UTF8")
|
||||
# try:
|
||||
# stack = stack.drop(columns=["Nom", "Trimestre", "Date", "Competence", "Domaine", "Est_nivele", "Bareme"])
|
||||
# except KeyError:
|
||||
# stack = stack
|
||||
return (
|
||||
[{"id": c, "name": c} for c in stack.columns],
|
||||
stack.to_dict("records"),
|
||||
highlight_value(stack),
|
||||
)
|
160
recopytex/scripts/prepare_csv.py
Normal file
160
recopytex/scripts/prepare_csv.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
|
||||
import click
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from PyInquirer import prompt, print_json
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
from .config import config
|
||||
from ..config import NO_ST_COLUMNS
|
||||
|
||||
|
||||
class PromptAbortException(EOFError):
|
||||
def __init__(self, message, errors=None):
|
||||
|
||||
# Call the base class constructor with the parameters it needs
|
||||
super(PromptAbortException, self).__init__("Abort questionnary", errors)
|
||||
|
||||
|
||||
def get_tribes(answers):
|
||||
""" List tribes based on subdirectory of config["source"] which have an "eleves.csv" file inside """
|
||||
return [
|
||||
p.name for p in Path(config["source"]).iterdir() if (p / "eleves.csv").exists()
|
||||
]
|
||||
|
||||
|
||||
def prepare_csv():
|
||||
items = new_eval()
|
||||
|
||||
item = items[0]
|
||||
# item = {"tribe": "308", "date": datetime.today(), "assessment": "plop"}
|
||||
csv_output = (
|
||||
Path(config["source"])
|
||||
/ item["tribe"]
|
||||
/ f"{item['date']:%y%m%d}_{item['assessment']}.csv"
|
||||
)
|
||||
|
||||
students = pd.read_csv(Path(config["source"]) / item["tribe"] / "eleves.csv")["Nom"]
|
||||
|
||||
columns = list(NO_ST_COLUMNS.keys())
|
||||
items = [[it[c] for c in columns] for it in items]
|
||||
columns = list(NO_ST_COLUMNS.values())
|
||||
items_df = pd.DataFrame.from_records(items, columns=columns)
|
||||
for s in students:
|
||||
items_df[s] = np.nan
|
||||
|
||||
items_df.to_csv(csv_output, index=False, date_format="%d/%m/%Y")
|
||||
click.echo(f"Saving csv file to {csv_output}")
|
||||
|
||||
|
||||
def new_eval(answers={}):
|
||||
click.echo(f"Préparation d'un nouveau devoir")
|
||||
|
||||
eval_questions = [
|
||||
{"type": "input", "name": "assessment", "message": "Nom de l'évaluation",},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "tribe",
|
||||
"message": "Classe concernée",
|
||||
"choices": get_tribes,
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"name": "date",
|
||||
"message": "Date du devoir (%y%m%d)",
|
||||
"default": datetime.today().strftime("%y%m%d"),
|
||||
"filter": lambda val: datetime.strptime(val, "%y%m%d"),
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "term",
|
||||
"message": "Trimestre",
|
||||
"choices": ["1", "2", "3"],
|
||||
},
|
||||
]
|
||||
|
||||
eval_ans = prompt(eval_questions)
|
||||
|
||||
items = []
|
||||
add_exo = True
|
||||
while add_exo:
|
||||
ex_items = new_exercice(eval_ans)
|
||||
items += ex_items
|
||||
add_exo = prompt(
|
||||
[
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "add_exo",
|
||||
"message": "Ajouter un autre exercice",
|
||||
"default": True,
|
||||
}
|
||||
]
|
||||
)["add_exo"]
|
||||
return items
|
||||
|
||||
|
||||
def new_exercice(answers={}):
|
||||
exercise_questions = [
|
||||
{"type": "input", "name": "exercise", "message": "Nom de l'exercice"},
|
||||
]
|
||||
|
||||
click.echo(f"Nouvel exercice")
|
||||
exercise_ans = prompt(exercise_questions, answers=answers)
|
||||
|
||||
items = []
|
||||
|
||||
add_item = True
|
||||
while add_item:
|
||||
try:
|
||||
item_ans = new_item(exercise_ans)
|
||||
except PromptAbortException:
|
||||
click.echo("Création de l'item annulée")
|
||||
else:
|
||||
items.append(item_ans)
|
||||
add_item = prompt(
|
||||
[
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "add_item",
|
||||
"message": f"Ajouter un autre item pour l'exercice {exercise_ans['exercise']}",
|
||||
"default": True,
|
||||
}
|
||||
]
|
||||
)["add_item"]
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def new_item(answers={}):
|
||||
item_questions = [
|
||||
{"type": "input", "name": "question", "message": "Nom de l'item",},
|
||||
{"type": "input", "name": "comment", "message": "Commentaire",},
|
||||
{
|
||||
"type": "list",
|
||||
"name": "competence",
|
||||
"message": "Competence",
|
||||
"choices": ["Cher", "Rep", "Mod", "Rai", "Cal", "Com"],
|
||||
},
|
||||
{"type": "input", "name": "theme", "message": "Domaine",},
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "is_leveled",
|
||||
"message": "Évaluation par niveau",
|
||||
"default": True,
|
||||
},
|
||||
{"type": "input", "name": "score_rate", "message": "Bareme"},
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "correct",
|
||||
"message": "Tout est correct?",
|
||||
"default": True,
|
||||
},
|
||||
]
|
||||
click.echo(f"Nouvelle question pour l'exercice {answers['exercise']}")
|
||||
item_ans = prompt(item_questions, answers=answers)
|
||||
if item_ans["correct"]:
|
||||
return item_ans
|
||||
raise PromptAbortException("Abort item creation")
|
@@ -10,10 +10,9 @@ from datetime import datetime
|
||||
import yaml
|
||||
|
||||
from .getconfig import config, CONFIGPATH
|
||||
from .prompts import prompt_exam, prompt_exercise, prompt_validate
|
||||
from ..config import NO_ST_COLUMNS
|
||||
from .exam import Exam
|
||||
from .exam_dash import app as exam_app
|
||||
from ..dashboard.index import app as dash
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -38,56 +37,10 @@ def setup():
|
||||
|
||||
|
||||
@cli.command()
|
||||
def new_exam():
|
||||
""" Create new exam csv file """
|
||||
exam = Exam(**prompt_exam())
|
||||
@click.option("--debug", default=0, help="Debug mode for dash")
|
||||
def dashboard(debug):
|
||||
dash.run_server(debug=bool(debug))
|
||||
|
||||
if exam.path(".yml").exists():
|
||||
print(f"Fichier sauvegarde trouvé à {exam.path('.yml')} -- importation")
|
||||
with open(exam.path(".yml"), "r") as f:
|
||||
for name, questions in yaml.load(f, Loader=yaml.SafeLoader)[
|
||||
"exercices"
|
||||
].items():
|
||||
exam.add_exercise(name, questions)
|
||||
|
||||
print(exam.themes)
|
||||
# print(yaml.dump(exam.to_dict()))
|
||||
|
||||
exam.write()
|
||||
|
||||
for name, questions in exam.exercices.items():
|
||||
exam.modify_exercise(
|
||||
**prompt_exercise(
|
||||
name=name, completer={"theme": exam.themes}, questions=questions
|
||||
)
|
||||
)
|
||||
exam.write()
|
||||
|
||||
new_exercise = prompt_validate("Ajouter un exercice? ")
|
||||
while new_exercise:
|
||||
exam.add_exercise(
|
||||
**prompt_exercise(len(exam.exercices) + 1, completer={"theme": exam.themes})
|
||||
)
|
||||
exam.write()
|
||||
new_exercise = prompt_validate("Ajouter un exercice? ")
|
||||
|
||||
rows = exam.to_row()
|
||||
|
||||
base_df = pd.DataFrame.from_dict(rows)[NO_ST_COLUMNS.keys()]
|
||||
base_df.rename(columns=NO_ST_COLUMNS, inplace=True)
|
||||
|
||||
students = pd.read_csv(exam.tribe_student_path)["Nom"]
|
||||
for student in students:
|
||||
base_df[student] = ""
|
||||
|
||||
exam.tribe_path.mkdir(exist_ok=True)
|
||||
|
||||
base_df.to_csv(exam.path(".csv"), index=False)
|
||||
print(f"Le fichier note a été enregistré à {exam.path('.csv')}")
|
||||
|
||||
@cli.command()
|
||||
def exam_analysis():
|
||||
exam_app.run_server(debug=True)
|
||||
|
||||
@cli.command()
|
||||
@click.argument("csv_file")
|
||||
@@ -109,7 +62,7 @@ def report(csv_file):
|
||||
try:
|
||||
date = datetime.strptime(date, "%y%m%d")
|
||||
except ValueError:
|
||||
date = None
|
||||
date = datetime.today().strptime(date, "%y%m%d")
|
||||
|
||||
tribe = str(tribe_dir).split("/")[-1]
|
||||
|
||||
|
@@ -1,4 +1,77 @@
|
||||
pandas
|
||||
click
|
||||
papermill
|
||||
prompt_toolkit
|
||||
ansiwrap==0.8.4
|
||||
appdirs==1.4.3
|
||||
attrs==19.1.0
|
||||
backcall==0.1.0
|
||||
black==19.10b0
|
||||
bleach==3.1.0
|
||||
certifi==2019.6.16
|
||||
chardet==3.0.4
|
||||
Click==7.0
|
||||
colorama==0.4.1
|
||||
cycler==0.10.0
|
||||
decorator==4.4.0
|
||||
defusedxml==0.6.0
|
||||
entrypoints==0.3
|
||||
future==0.17.1
|
||||
idna==2.8
|
||||
importlib-resources==1.0.2
|
||||
ipykernel==5.1.3
|
||||
ipython==7.11.1
|
||||
ipython-genutils==0.2.0
|
||||
ipywidgets==7.5.1
|
||||
jedi==0.15.2
|
||||
Jinja2==2.10.3
|
||||
jsonschema==3.2.0
|
||||
jupyter==1.0.0
|
||||
jupyter-client==5.3.4
|
||||
jupyter-console==6.1.0
|
||||
jupyter-core==4.6.1
|
||||
jupytex==0.0.3
|
||||
kiwisolver==1.1.0
|
||||
Markdown==3.1.1
|
||||
MarkupSafe==1.1.1
|
||||
matplotlib==3.1.2
|
||||
mistune==0.8.4
|
||||
nbconvert==5.6.1
|
||||
nbformat==5.0.3
|
||||
notebook==6.0.3
|
||||
numpy==1.18.1
|
||||
pandas==0.25.3
|
||||
pandocfilters==1.4.2
|
||||
papermill==1.2.1
|
||||
parso==0.5.2
|
||||
pathspec==0.7.0
|
||||
pexpect==4.8.0
|
||||
pickleshare==0.7.5
|
||||
prometheus-client==0.7.1
|
||||
prompt-toolkit==1.0.14
|
||||
ptyprocess==0.6.0
|
||||
Pygments==2.5.2
|
||||
PyInquirer==1.0.3
|
||||
pyparsing==2.4.6
|
||||
pyrsistent==0.15.7
|
||||
python-dateutil==2.8.0
|
||||
pytz==2019.3
|
||||
PyYAML==5.3
|
||||
pyzmq==18.1.1
|
||||
qtconsole==4.6.0
|
||||
-e git+git_opytex:/lafrite/recopytex.git@7e026bedb24c1ca8bef3b71b3d63f8b0d6916e81#egg=Recopytex
|
||||
regex==2020.1.8
|
||||
requests==2.22.0
|
||||
scipy==1.4.1
|
||||
Send2Trash==1.5.0
|
||||
six==1.12.0
|
||||
tenacity==6.0.0
|
||||
terminado==0.8.3
|
||||
testpath==0.4.4
|
||||
textwrap3==0.9.2
|
||||
toml==0.10.0
|
||||
tornado==6.0.3
|
||||
tqdm==4.41.1
|
||||
traitlets==4.3.2
|
||||
typed-ast==1.4.1
|
||||
urllib3==1.25.8
|
||||
wcwidth==0.1.8
|
||||
webencodings==0.5.1
|
||||
widgetsnbextension==3.5.1
|
||||
|
6
setup.py
6
setup.py
@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='Recopytex',
|
||||
version='0.1',
|
||||
version='1.1.1',
|
||||
description='Assessment analysis',
|
||||
author='Benjamin Bertrand',
|
||||
author_email='',
|
||||
@@ -13,6 +13,10 @@ setup(
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
'Click',
|
||||
'pandas',
|
||||
'numpy',
|
||||
'papermill',
|
||||
'pyyaml',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
|
Reference in New Issue
Block a user