Feat: Final mark for students
This commit is contained in:
parent
8ec24a24b3
commit
235019102b
@ -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"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@ -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)
|
||||||
|
@ -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")]
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user