Feat: Final mark for students

This commit is contained in:
Bertrand Benjamin 2021-04-20 18:30:12 +02:00
parent 8ec24a24b3
commit 235019102b
4 changed files with 139 additions and 57 deletions

View File

@ -7,62 +7,71 @@ from .models import get_tribes, get_exams
from .callbacks import * from .callbacks import *
layout = html.Div( layout = html.Div(
children=[ children=[
html.Header( html.Header(
children=[ children=[
html.H1("Analyse des notes"), html.H1("Analyse des notes"),
html.P("Dernière sauvegarde", id="lastsave"), html.P("Dernière sauvegarde", id="lastsave"),
],
),
html.Main(
children=[
html.Section(
children=[
html.Div(
children=[
"Classe: ",
dcc.Dropdown(
id="tribe",
options=[
{"label": t["name"], "value": t["name"]}
for t in get_tribes().values()
],
value=next(iter(get_tribes().values()))["name"],
),
],
),
html.Div(
children=[
"Evaluation: ",
dcc.Dropdown(id="exam_select"),
],
),
], ],
id="selects",
), ),
html.Main( html.Section(
children=[ children=[
html.Section( html.Div(
children=[ children=[
html.Div( dash_table.DataTable(
children=[ id="final_score_table",
"Classe: ", columns=[
dcc.Dropdown( {"name": "Étudiant", "id": "student_name"},
id="tribe", {"name": "Note", "id": "mark"},
options=[ {"name": "Barème", "id": "score_rate"},
{"label": t["name"], "value": t["name"]}
for t in get_tribes().values()
],
value=next(iter(get_tribes().values()))["name"],
),
], ],
),
html.Div(
children=[
"Evaluation: ",
dcc.Dropdown(id="exam_select"),
],
),
],
id="selects",
),
html.Section(
children=[
html.Div(
children=[],
id="final_score_table_container",
),
],
id="analysis",
),
html.Section(
children=[
dash_table.DataTable(
id="scores_table",
columns=[],
style_data_conditional=[],
fixed_columns={},
editable=True,
) )
], ],
id="edit", id="final_score_table_container",
), ),
], ],
id="analysis",
),
html.Section(
children=[
dash_table.DataTable(
id="scores_table",
columns=[],
style_data_conditional=[],
fixed_columns={},
editable=True,
)
],
id="edit",
), ),
dcc.Store(id="scores"),
], ],
) ),
dcc.Store(id="scores"),
],
)

View File

@ -17,6 +17,7 @@ from .models import (
get_unstack_scores, get_unstack_scores,
get_students_from_exam, get_students_from_exam,
get_score_colors, get_score_colors,
score_to_final_mark,
) )
@ -77,3 +78,17 @@ def update_scores_store(exam):
highlight_scores(students, score_color), highlight_scores(students, score_color),
{"headers": True, "data": len(fixed_columns)}, {"headers": True, "data": len(fixed_columns)},
] ]
@app.callback(
[
Output("final_score_table", "data"),
],
[
Input("scores_table", "data"),
],
)
def update_scores_store(scores):
scores_df = pd.DataFrame.from_records(scores)
# print(scores_df)
return score_to_final_mark(scores_df)

View File

@ -1,10 +1,44 @@
#!/usr/bin/env python #!/use/bin/env python
# encoding: utf-8 # encoding: utf-8
from recopytex.database.filesystem.loader import CSVLoader from recopytex.database.filesystem.loader import CSVLoader
from recopytex.datalib.dataframe import column_values_to_column from recopytex.datalib.dataframe import column_values_to_column
import recopytex.datalib.on_score_column as on_column
import pandas as pd
LOADER = CSVLoader("./test_config.yml") LOADER = CSVLoader("./test_confia.ml")
SCORES_CONFIG = LOADER.get_config()["scores"]
def unstack_scores(scores):
"""Put student_name values to columns
:param scores: Score dataframe with one line per score
:returns: Scrore dataframe with student_name in columns
"""
kept_columns = [col for col in LOADER.score_columns if col != "score"]
return column_values_to_column("student_name", "score", kept_columns, scores)
def stack_scores(scores):
"""Student columns are melt to rows with student_name column
:param scores: Score dataframe with student_name in columns
:returns: Scrore dataframe with one line per score
"""
kept_columns = [
c for c in LOADER.score_columns if c not in ["score", "student_name"]
]
student_names = [c for c in scores.columns if c not in kept_columns]
return pd.melt(
scores,
id_vars=kept_columns,
value_vars=student_names,
var_name="student_name",
value_name="score",
)
def get_tribes(): def get_tribes():
@ -21,8 +55,7 @@ def get_record_scores(exam):
def get_unstack_scores(exam): def get_unstack_scores(exam):
flat_scores = LOADER.get_exam_scores(exam) flat_scores = LOADER.get_exam_scores(exam)
kept_columns = [col for col in LOADER.score_columns if col != "score"] return unstack_scores(flat_scores)
return column_values_to_column("student_name", "score", kept_columns, flat_scores)
def get_students_from_exam(exam): def get_students_from_exam(exam):
@ -31,8 +64,32 @@ def get_students_from_exam(exam):
def get_score_colors(): def get_score_colors():
scores_config = LOADER.get_config()["valid_scores"]
score_color = {} score_color = {}
for key, score in scores_config.items(): for key, score in SCORES_CONFIG.items():
score_color[score["value"]] = score["color"] score_color[score["value"]] = score["color"]
return score_color return score_color
is_none_score = lambda x: on_column.is_none_score(x, SCORES_CONFIG)
score_to_numeric_score = lambda x: on_column.score_to_numeric_score(x, SCORES_CONFIG)
score_to_mark = lambda x: on_column.score_to_mark(
x, max([v["value"] for v in SCORES_CONFIG.values() if isinstance(v["value"], int)])
)
def score_to_final_mark(scores):
""" Compute marks then reduce to final mark per student """
melted_scores = stack_scores(scores)
filtered_scores = melted_scores[~melted_scores.apply(is_none_score, axis=1)]
filtered_scores = filtered_scores.assign(
score=filtered_scores.apply(score_to_numeric_score, axis=1)
)
filtered_scores = filtered_scores.assign(
mark=filtered_scores.apply(score_to_mark, axis=1)
)
final_score = filtered_scores.groupby(["student_name"])[
["mark", "score_rate"]
].sum()
return [final_score.reset_index().to_dict("records")]

View File

@ -2,6 +2,7 @@
# encoding: utf-8 # encoding: utf-8
from math import ceil from math import ceil
import numpy as np
def is_none_score(x, score_config): def is_none_score(x, score_config):
@ -39,7 +40,7 @@ def is_none_score(x, score_config):
for v in score_config.values() for v in score_config.values()
if str(v["numeric_value"]).lower() == "none" if str(v["numeric_value"]).lower() == "none"
] ]
return x["score"] in none_values return x["score"] in none_values or x["score"] is None or np.isnan(x["score"])
def score_to_numeric_score(x, score_config): def score_to_numeric_score(x, score_config):