5454 "Gear ratio: {R_total}\n "
5555 "Output speed: {out_rpm} RPM"
5656 ),
57+ "no_gears" : (
58+ "No gears were detected clearly. Please make sure the gears are installed correctly "
59+ "and clearly visible in the photo."
60+ ),
61+ "gear_missing" : (
62+ "Some gears may be missing or not clearly visible. Please check the gear setup and "
63+ "retake the photo from a clearer angle."
64+ ),
65+ "gear_overloaded" : (
66+ "The gear setup seems to contain extra gears. Please try reducing the number of gears, "
67+ "and remove any gears that are not being used or are not part of the assembly from the photo."
68+ ),
69+ "no_spacer" : (
70+ "No spacer was detected. Please make sure the spacer is installed correctly and clearly visible in the photo."
71+ ),
72+ "extra_spacer" : (
73+ "Too many spacers were detected. Please remove any spacer that is not needed from the assembly or from the photo."
74+ ),
75+ "no_shaft" : (
76+ "No shaft was detected. Please make sure the shaft is installed correctly and clearly visible in the photo."
77+ ),
78+ "extra_shaft" : (
79+ "Too many shafts were detected. Please remove any shaft that is not needed from the assembly or from the photo."
80+ ),
81+ "mismesh" : (
82+ "The gears do not appear to mesh correctly. Please check the gear engagement and remove any gear "
83+ "that is not properly participating in the transmission."
84+ ),
85+ "stage_fail" : (
86+ "A clear single-stage transmission could not be confirmed. Please simplify the setup and "
87+ "make sure only the intended transmission parts are visible."
88+ ),
89+ "hint_long_spacer" : (
90+ "The setup works, but think about whether a short spacer or a long spacer is more appropriate here."
91+ ),
92+ "hint_short_shaft" : (
93+ "The setup works, but think about whether a long shaft or a short shaft is the better choice here."
94+ ),
5795 "fail" : "Please check the single-stage setup again. A valid one-stage transmission could not be confirmed." ,
5896 },
5997 "shaft" : {
@@ -323,7 +361,7 @@ def _get_gear_counts(out: Dict[str, Any]) -> Tuple[int, int, int]:
323361
324362
325363def _get_shaft_counts (out : Dict [str , Any ]) -> Tuple [int , int , int ]:
326- counts = _get_counts_dict ( out )
364+ counts = out . get ( "shaft_counts" , {}) if isinstance ( out . get ( "shaft_counts" ), dict ) else {}
327365
328366 n_long = _safe_int (counts .get ("shaft_long" , 0 ))
329367 n_short = _safe_int (counts .get ("shaft_short" , 0 ))
@@ -333,7 +371,7 @@ def _get_shaft_counts(out: Dict[str, Any]) -> Tuple[int, int, int]:
333371
334372
335373def _get_spacer_counts (out : Dict [str , Any ]) -> Tuple [int , int , int ]:
336- counts = _get_counts_dict ( out )
374+ counts = out . get ( "spacer_counts" , {}) if isinstance ( out . get ( "spacer_counts" ), dict ) else {}
337375
338376 n_long = _safe_int (counts .get ("spacer_long" , 0 ))
339377 n_short = _safe_int (counts .get ("spacer_short" , 0 ))
@@ -487,6 +525,51 @@ def _build_student_message(
487525 return True , MESSAGE_POLICY ["precheck" ]["pass" ]
488526
489527 if task == "single_stage" :
528+ codes = {
529+ str (e .get ("code" , "" )).upper ()
530+ for e in selected_errors
531+ if isinstance (e , dict )
532+ }
533+ driving_gear , smallgear , biggear = _get_gear_counts (out )
534+ shaft_long , shaft_short , shaft_total = _get_shaft_counts (out )
535+ spacer_long , spacer_short , spacer_total = _get_spacer_counts (out )
536+
537+ if "E_NO_GEARS" in codes :
538+ return False , MESSAGE_POLICY ["single_stage" ]["no_gears" ]
539+
540+ if "E_SINGLE_STAGE_SHAFT" in codes :
541+ if shaft_total <= 0 :
542+ return False , MESSAGE_POLICY ["single_stage" ]["no_shaft" ]
543+ if shaft_total > 1 :
544+ return False , MESSAGE_POLICY ["single_stage" ]["extra_shaft" ]
545+
546+ if "E_SINGLE_STAGE_SPACER_COUNT" in codes :
547+ if spacer_total <= 0 :
548+ return False , MESSAGE_POLICY ["single_stage" ]["no_spacer" ]
549+ if spacer_total > 1 :
550+ return False , MESSAGE_POLICY ["single_stage" ]["extra_spacer" ]
551+
552+ if "E_SINGLE_STAGE_MISMESH" in codes :
553+ return False , MESSAGE_POLICY ["single_stage" ]["mismesh" ]
554+
555+ gear_codes = {
556+ "E_SINGLE_STAGE_DRIVING_GEAR" ,
557+ "E_SINGLE_STAGE_SMALL_GEAR" ,
558+ "E_SINGLE_STAGE_BIG_GEAR" ,
559+ }
560+ if codes & gear_codes :
561+ if (
562+ driving_gear > 1
563+ or smallgear > 1
564+ or biggear > 1
565+ or biggear != smallgear
566+ ):
567+ return False , MESSAGE_POLICY ["single_stage" ]["gear_overloaded" ]
568+ return False , MESSAGE_POLICY ["single_stage" ]["gear_missing" ]
569+
570+ if "E_SINGLE_STAGE_STAGE_COUNT" in codes or "E_SINGLE_STAGE_RATIO" in codes :
571+ return False , MESSAGE_POLICY ["single_stage" ]["stage_fail" ]
572+
490573 is_correct = not _has_pipeline_error (errors )
491574 if not is_correct :
492575 return False , MESSAGE_POLICY ["single_stage" ]["fail" ]
@@ -499,11 +582,17 @@ def _build_student_message(
499582 if num_stages is None or r_total is None or out_rpm is None :
500583 return False , MESSAGE_POLICY ["single_stage" ]["fail" ]
501584
502- return True , MESSAGE_POLICY ["single_stage" ]["pass" ].format (
585+ msg = MESSAGE_POLICY ["single_stage" ]["pass" ].format (
503586 num_stages = _format_stage_value (num_stages ),
504587 R_total = _format_ratio_value (r_total ),
505588 out_rpm = _format_rpm_value (out_rpm ),
506589 )
590+ hints = out .get ("single_stage_hints" , [])
591+ if isinstance (hints , list ):
592+ cleaned = [str (h ).strip () for h in hints if str (h ).strip ()]
593+ if cleaned :
594+ msg = f"{ msg } \n " + "\n " .join (cleaned )
595+ return True , msg
507596
508597 if task == "shaft" :
509598 codes = {
0 commit comments