@@ -322,43 +322,88 @@ def _find_performance_max(points_info, task_type: str) -> int:
322322 return 0
323323
324324
325- def _calc_perf_points_from_efficiency (efficiency_str : str , max_points : int ) -> float :
325+ def _get_perf_category (points_info , task_number : int , variant : int ) -> str :
326+ """Determine performance category (A, B, or C) for a given task and variant.
327+
328+ Categories determine which efficiency scale to use:
329+ A: easily scalable tasks (>=50% = 100%)
330+ B: moderately scalable tasks (>=40% = 100%)
331+ C: communication-bound/sequential tasks (>=30% = 100%)
332+ """
333+ proc_tasks = (points_info .get ("processes" , {}) or {}).get ("tasks" , [])
334+ key = f"mpi_task_{ task_number } "
335+ for t in proc_tasks :
336+ if str (t .get ("name" )) == key :
337+ # Check if task has a single category for all variants
338+ if "perf_category" in t :
339+ return str (t .get ("perf_category" , "A" ))
340+ # Check if task has per-variant categories
341+ by_variant = t .get ("perf_category_by_variant" , {})
342+ if by_variant :
343+ # Check each category list
344+ for cat in ["A" , "B" , "C" ]:
345+ variants_in_cat = by_variant .get (cat , [])
346+ if variant in variants_in_cat :
347+ return cat
348+ # Return default if not found in any list
349+ return str (by_variant .get ("default" , "A" ))
350+ return "A" # Default to category A
351+
352+
353+ def _calc_perf_points_from_efficiency (
354+ efficiency_str : str , max_points : int , category : str = "A" , scales : dict = None
355+ ) -> float :
326356 """Calculate Performance points as a real number (x.yy).
327357
328- Mapping (eff -> percent of max):
329- >=50 -> 100; [45,50) -> 90; [42,45) -> 80; [40,42) -> 70; [37,40) -> 60;
330- [35,37) -> 50; [32,35) -> 40; [30,32) -> 30; [27,30) -> 20; [25,27) -> 10; <25 -> 0
331- Returns a float rounded to 2 decimals (no ceil).
358+ Uses category-specific scales from points-info.yml:
359+ A: easily scalable (>=50% = 100%)
360+ B: moderately scalable (>=40% = 100%)
361+ C: communication-bound (>=30% = 100%)
362+
363+ Falls back to default scale A if scales not provided.
364+ Returns a float rounded to 2 decimals.
332365 """
333366 if not isinstance (efficiency_str , str ) or not efficiency_str .endswith ("%" ):
334367 return 0.0
335368 try :
336369 val = float (efficiency_str .rstrip ("%" ))
337370 except Exception :
338371 return 0.0
339- perc = 0.0
340- if val >= 50 :
341- perc = 1.0
342- elif 45 <= val < 50 :
343- perc = 0.9
344- elif 42 <= val < 45 :
345- perc = 0.8
346- elif 40 <= val < 42 :
347- perc = 0.7
348- elif 37 <= val < 40 :
349- perc = 0.6
350- elif 35 <= val < 37 :
351- perc = 0.5
352- elif 32 <= val < 35 :
353- perc = 0.4
354- elif 30 <= val < 32 :
355- perc = 0.3
356- elif 27 <= val < 30 :
357- perc = 0.2
358- elif 25 <= val < 27 :
359- perc = 0.1
372+
373+ # Get scale for category (list of [threshold, percentage] pairs, sorted descending)
374+ if scales and category in scales :
375+ scale = scales [category ]
376+ perc = 0.0
377+ for threshold , points_pct in scale :
378+ if val >= threshold :
379+ perc = points_pct / 100.0
380+ break
360381 else :
382+ # Fallback to default category A scale
361383 perc = 0.0
384+ if val >= 50 :
385+ perc = 1.0
386+ elif 45 <= val < 50 :
387+ perc = 0.9
388+ elif 42 <= val < 45 :
389+ perc = 0.8
390+ elif 40 <= val < 42 :
391+ perc = 0.7
392+ elif 37 <= val < 40 :
393+ perc = 0.6
394+ elif 35 <= val < 37 :
395+ perc = 0.5
396+ elif 32 <= val < 35 :
397+ perc = 0.4
398+ elif 30 <= val < 32 :
399+ perc = 0.3
400+ elif 27 <= val < 30 :
401+ perc = 0.2
402+ elif 25 <= val < 27 :
403+ perc = 0.1
404+ else :
405+ perc = 0.0
406+
362407 pts = max_points * perc if max_points > 0 else 0.0
363408 # round to 2 decimals (banker's rounding acceptable here)
364409 return round (pts , 2 )
@@ -950,14 +995,15 @@ def _match_dir(csv_key: str) -> str | None:
950995 target = _match_dir (key )
951996 if target :
952997 targets .add (target )
953- # 2) If key encodes processes_N, spread to all dirs with that task_number
954- m_num = _re .search (r"processes_(\d+)" , key )
955- if m_num :
956- try :
957- num = int (m_num .group (1 ))
958- targets .update (process_tasknum_map .get (num , []))
959- except Exception :
960- pass
998+ # 2) If key is for example_processes_N, map to that specific example directory only
999+ # (Do NOT spread to all dirs with that task_number - that was causing data duplication)
1000+ if "test_task_processes" in key or "example_processes" in key :
1001+ m_num = _re .search (r"processes_(\d+)" , key )
1002+ if m_num :
1003+ suffix = m_num .group (1 )
1004+ example_dir = f"example_processes_{ suffix } "
1005+ if example_dir in directories :
1006+ targets .add (example_dir )
9611007 # 3) Fallback: if nothing matched and "threads" in key, apply to example_threads only
9621008 if not targets and "threads" in key :
9631009 if "example_threads" in directories :
@@ -1109,9 +1155,30 @@ def _build_process_rows(processes_dirs: list[str]):
11091155 has_seq = status_seq in ("done" , "disabled" )
11101156 report_present = (tasks_dir / d / "report.md" ).exists ()
11111157
1158+ # Get student's variant for this task to determine perf category
1159+ vmax = _find_process_variants_max (cfg , n )
1160+ try :
1161+ variant_idx = assign_variant (
1162+ surname = str (student .get ("last_name" , "" )),
1163+ name = str (student .get ("first_name" , "" )),
1164+ patronymic = str (student .get ("middle_name" , "" )),
1165+ group = str (student .get ("group_number" , "" )),
1166+ repo = f"{ REPO_SALT } /processes/task-{ n } " ,
1167+ num_variants = vmax ,
1168+ )
1169+ variant = variant_idx + 1 # 1-based
1170+ except Exception :
1171+ variant = 1
1172+
1173+ # Get performance category and scales
1174+ perf_category = _get_perf_category (cfg , n , variant )
1175+ perf_scales = (cfg .get ("efficiency" , {}) or {}).get ("scales" , {})
1176+
11121177 mpi_eff = group_cells [0 ].get ("efficiency" , "N/A" )
11131178 perf_points_mpi = (
1114- _calc_perf_points_from_efficiency (mpi_eff , a_mpi )
1179+ _calc_perf_points_from_efficiency (
1180+ mpi_eff , a_mpi , category = perf_category , scales = perf_scales
1181+ )
11151182 if (status_mpi == "done" and status_seq == "done" )
11161183 else 0
11171184 )
0 commit comments