Skip to content

Commit 0a0806a

Browse files
committed
refactor performance statistics handling to improve key resolution and alignment with task directories
1 parent b35e4b7 commit 0a0806a

File tree

1 file changed

+84
-7
lines changed

1 file changed

+84
-7
lines changed

scoreboard/main.py

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -700,13 +700,13 @@ def _compute_display_deadlines_processes(n_items: int) -> list[date]:
700700
(p for p in candidates_processes if p.exists()), candidates_processes[0]
701701
)
702702

703-
# Read and merge performance statistics CSVs
703+
# Read and merge performance statistics CSVs (keys = CSV Task column)
704704
perf_stats_threads = load_performance_data_threads(threads_csv)
705705
perf_stats_processes = load_performance_data_processes(processes_csv)
706-
perf_stats: dict[str, dict] = {}
707-
perf_stats.update(perf_stats_threads)
706+
perf_stats_raw: dict[str, dict] = {}
707+
perf_stats_raw.update(perf_stats_threads)
708708
for k, v in perf_stats_processes.items():
709-
perf_stats[k] = {**perf_stats.get(k, {}), **v}
709+
perf_stats_raw[k] = {**perf_stats_raw.get(k, {}), **v}
710710

711711
# Partition tasks by tasks_type from settings.json
712712
threads_task_dirs = [
@@ -724,6 +724,73 @@ def _compute_display_deadlines_processes(n_items: int) -> list[date]:
724724
elif "processes" in name:
725725
processes_task_dirs.append(name)
726726

727+
# Resolve performance stats keys (from CSV Task names) to actual task directories
728+
import re as _re
729+
730+
def _family_from_name(name: str) -> tuple[str, int]:
731+
# Infer family from CSV Task value, using only structural markers
732+
# threads -> ("threads", 0); processes[_N] -> ("processes", N|1)
733+
if "threads" in name:
734+
return "threads", 0
735+
if "processes" in name:
736+
m = _re.search(r"processes(?:_(\d+))?", name)
737+
if m:
738+
try:
739+
idx = int(m.group(1)) if m.group(1) else 1
740+
except Exception:
741+
idx = 1
742+
else:
743+
idx = 1
744+
return "processes", idx
745+
# Fallback: treat as threads family
746+
return "threads", 0
747+
748+
def _family_from_dir(dir_name: str) -> tuple[str, int]:
749+
# Prefer explicit tasks_type from settings.json and task_number from info.json
750+
kind_guess = tasks_type_map.get(dir_name) or (
751+
"threads" if "threads" in dir_name else "processes"
752+
)
753+
idx = 0
754+
if kind_guess == "processes":
755+
# Lightweight reader to avoid dependency on later-scoped helpers
756+
try:
757+
import json as _json
758+
759+
info_path = tasks_dir / dir_name / "info.json"
760+
if info_path.exists():
761+
with open(info_path, "r") as _f:
762+
data = _json.load(_f)
763+
s = data.get("student", {}) if isinstance(data, dict) else {}
764+
try:
765+
idx = int(str(s.get("task_number", "0")))
766+
except Exception:
767+
idx = 0
768+
except Exception:
769+
idx = 0
770+
return kind_guess, idx
771+
772+
# Build map family -> list of dir names in this repo
773+
family_to_dirs: dict[tuple[str, int], list[str]] = {}
774+
for d in sorted(directories.keys()):
775+
fam = _family_from_dir(d)
776+
family_to_dirs.setdefault(fam, []).append(d)
777+
778+
# Aggregate perf by family (CSV keys may not match dir names)
779+
perf_by_family: dict[tuple[str, int], dict] = {}
780+
for key, vals in perf_stats_raw.items():
781+
fam = _family_from_name(key)
782+
perf_by_family[fam] = {**perf_by_family.get(fam, {}), **vals}
783+
784+
# Project family perf onto actual directories (prefer exact one per family)
785+
perf_stats: dict[str, dict] = {}
786+
for fam, vals in perf_by_family.items():
787+
dirs_for_family = family_to_dirs.get(fam, [])
788+
if not dirs_for_family:
789+
continue
790+
# Assign same perf to all dirs in the family (usually one)
791+
for d in dirs_for_family:
792+
perf_stats[d] = vals.copy()
793+
727794
# Build rows for each page
728795
threads_rows = _build_rows_for_task_types(
729796
task_types_threads,
@@ -758,15 +825,15 @@ def _identity_key(student: dict) -> str:
758825
]
759826
)
760827

761-
def _build_cell(dir_name: str, ttype: str):
828+
def _build_cell(dir_name: str, ttype: str, perf_map: dict[str, dict]):
762829
status = directories[dir_name].get(ttype)
763830
sol_points, solution_style = get_solution_points_and_style(ttype, status, cfg)
764831
task_points = sol_points
765832
is_cheated, plagiarism_points = check_plagiarism_and_calculate_penalty(
766833
dir_name, ttype, sol_points, plagiarism_cfg, cfg, semester="processes"
767834
)
768835
task_points += plagiarism_points
769-
perf_val = perf_stats.get(dir_name, {}).get(ttype, "?")
836+
perf_val = perf_map.get(dir_name, {}).get(ttype, "?")
770837
acceleration, efficiency = calculate_performance_metrics(
771838
perf_val, eff_num_proc, ttype
772839
)
@@ -832,7 +899,7 @@ def _build_cell(dir_name: str, ttype: str):
832899
proc_group_headers.append({"type": "seq"})
833900
group_cells = []
834901
for ttype in ["mpi", "seq"]:
835-
cell, _ = _build_cell(d, ttype)
902+
cell, _ = _build_cell(d, ttype, perf_stats)
836903
group_cells.append(cell)
837904
# Override displayed points for processes: S under MPI/SEQ from points-info; A points under MPI only
838905
s_mpi, s_seq, a_mpi, r_max = _find_process_points(cfg, n)
@@ -948,6 +1015,16 @@ def _build_cell(dir_name: str, ttype: str):
9481015
}
9491016
]
9501017

1018+
# Rebuild threads rows with resolved perf stats
1019+
threads_rows = _build_rows_for_task_types(
1020+
task_types_threads,
1021+
threads_task_dirs,
1022+
perf_stats,
1023+
cfg,
1024+
eff_num_proc,
1025+
deadlines_cfg,
1026+
)
1027+
9511028
parser = argparse.ArgumentParser(description="Generate HTML scoreboard.")
9521029
parser.add_argument(
9531030
"-o", "--output", type=str, required=True, help="Output directory path"

0 commit comments

Comments
 (0)