11from __future__ import annotations
22
33import os
4+ import traceback
45from typing import Any , Dict , List , Optional , Tuple
56from urllib .parse import urlparse , unquote
67
1920 class ImageUploadError (Exception ): # type: ignore
2021 pass
2122
22- PIPELINE_IMPORT_ERROR = None
23+
24+ # Pipeline import guard
25+ PIPELINE_IMPORT_ERROR : Optional [Dict [str , str ]] = None
2326run_yolo_pipeline = None
2427
2528try :
2629 from .yolo_pipeline import run_yolo_pipeline # type: ignore
2730except Exception as e :
28- PIPELINE_IMPORT_ERROR = f"{ type (e ).__name__ } : { e } "
31+ PIPELINE_IMPORT_ERROR = {
32+ "stage" : "IMPORT" ,
33+ "error_code" : "E_PIPELINE_IMPORT" ,
34+ "exc_type" : type (e ).__name__ ,
35+ "message" : str (e ),
36+ "traceback" : traceback .format_exc (),
37+ }
2938 run_yolo_pipeline = None
30- #from .yolo_pipeline import run_yolo_pipeline
31-
3239
3340# URL / path helpers
3441def file_url_to_local_path (url : str ) -> str :
@@ -98,8 +105,9 @@ def _pget(params: Params, key: str, default: Any) -> Any:
98105 except Exception :
99106 return default
100107
101- def _items_to_feedback_html (items ):
102- lines = []
108+
109+ def _items_to_feedback_html (items : List [Tuple [Any , Any ]]) -> str :
110+ lines : List [str ] = []
103111 for k , v in items :
104112 k = str (k ).strip () if k is not None else ""
105113 v = str (v ).strip () if v is not None else ""
@@ -110,33 +118,68 @@ def _items_to_feedback_html(items):
110118 return "<br>" .join (lines )
111119
112120
121+ def _escape_html (s : str ) -> str :
122+ # minimal safe escaping for traceback readability
123+ return s .replace ("&" , "&" ).replace ("<" , "<" ).replace (">" , ">" )
124+
125+
126+ def _error_dict_to_items (err : Dict [str , str ]) -> List [Tuple [str , str ]]:
127+ """
128+ Convert a structured error dict into readable feedback_items.
129+ Includes two traceback renderings:
130+ - <pre> (nice if allowed)
131+ - <br> version (if <pre> is stripped by sanitizer)
132+ """
133+ items : List [Tuple [str , str ]] = []
134+ items .append (("Stage" , err .get ("stage" , "UNKNOWN" )))
135+ items .append (("ErrorCode" , err .get ("error_code" , "E_UNKNOWN" )))
136+ items .append (("ExceptionType" , err .get ("exc_type" , "" )))
137+ items .append (("Message" , err .get ("message" , "" )))
138+
139+ tb = err .get ("traceback" , "" )
140+ if tb :
141+ safe_tb = _escape_html (tb )
142+ items .append (("Traceback" , f"<pre>{ safe_tb } </pre>" ))
143+ items .append (("Traceback(html)" , safe_tb .replace ("\n " , "<br>" )))
144+
145+ return items
146+
113147# Main entry
114148def evaluation_function (response : Any , answer : Any , params : Params ) -> Result :
115149 try :
116150 # 0) Pipeline import guard (MOST IMPORTANT)
117151 if run_yolo_pipeline is None :
118- items = [("Error" , f"Pipeline import failed: { PIPELINE_IMPORT_ERROR } " )]
152+ if isinstance (PIPELINE_IMPORT_ERROR , dict ):
153+ items = _error_dict_to_items (PIPELINE_IMPORT_ERROR )
154+ else :
155+ items = [
156+ ("Stage" , "IMPORT" ),
157+ ("ErrorCode" , "E_PIPELINE_IMPORT" ),
158+ ("Message" , f"Pipeline import failed: { PIPELINE_IMPORT_ERROR } " ),
159+ ]
160+
119161 feedback_html = _items_to_feedback_html (items )
120162 try :
121163 return Result (is_correct = False , feedback = feedback_html , feedback_items = items )
122164 except TypeError :
123165 return Result (is_correct = False , feedback_items = items )
124- # 1) Validate input
125166
167+ # 1) Validate input
126168 if not isinstance (response , list ) or len (response ) == 0 :
127169 items = [("Response" , "Please upload at least one image." )]
128170 feedback_html = _items_to_feedback_html (items )
129171 try :
130172 return Result (is_correct = False , feedback = feedback_html , feedback_items = items )
131173 except TypeError :
132174 return Result (is_correct = False , feedback_items = items )
133- # 2) Optional controls
134175
176+ # 2) Optional controls
135177 return_images : bool = bool (_pget (params , "return_images" , False ))
136178 debug : bool = bool (_pget (params , "debug" , False ))
137179
138180 gear_model_rel = str (_pget (params , "gear_model_rel" , "gear_model.pt" ))
139181 shaft_model_rel = str (_pget (params , "shaft_model_rel" , "shaft_model.pt" ))
182+
140183 # 3) Process images
141184 merged_errors : List [Dict [str , str ]] = []
142185 merged_summaries : List [Dict [str , Any ]] = []
@@ -146,13 +189,13 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
146189 for idx , item in enumerate (response ):
147190 url = item .get ("url" ) if isinstance (item , dict ) else None
148191 if not url :
149- merged_errors .append ({"code" : "NO_URL " , "message" : f"Image [{ idx } ] has no 'url' field." })
192+ merged_errors .append ({"code" : "E_NO_URL " , "message" : f"Image [{ idx } ] has no 'url' field." })
150193 continue
151194
152195 img_bgr , err = _load_bgr_image_from_url (url )
153196 if img_bgr is None :
154197 merged_errors .append ({
155- "code" : "LOAD_FAIL " ,
198+ "code" : "E_LOAD_FAIL " ,
156199 "message" : f"Failed to load image [{ idx } ] from URL. ({ err } )"
157200 })
158201 if debug :
@@ -168,9 +211,12 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
168211 return_images = return_images ,
169212 )
170213 except Exception as e :
214+ msg = f"Pipeline failed on image[{ idx } ]: { type (e ).__name__ } : { e } "
215+ if debug :
216+ msg += "\n " + traceback .format_exc ()
171217 merged_errors .append ({
172- "code" : "PIPELINE_RUNTIME_FAIL " ,
173- "message" : f"Pipeline failed on image[ { idx } ]: { type ( e ). __name__ } : { e } "
218+ "code" : "E_PIPELINE_RUNTIME " ,
219+ "message" : msg
174220 })
175221 if debug :
176222 feedback_items .append ((f"Input URL [{ idx } ]" , str (url )))
@@ -202,12 +248,12 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
202248 )
203249 except ImageUploadError as e :
204250 merged_errors .append ({
205- "code" : "UPLOAD_FAIL " ,
251+ "code" : "E_UPLOAD_FAIL " ,
206252 "message" : f"Failed to upload { key } for image[{ idx } ]: { e } "
207253 })
208254 except Exception as e :
209255 merged_errors .append ({
210- "code" : "UPLOAD_FAIL " ,
256+ "code" : "E_UPLOAD_FAIL " ,
211257 "message" : f"Failed to encode/upload { key } for image[{ idx } ]: { e } "
212258 })
213259 elif upload_image is None and debug :
@@ -216,23 +262,19 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
216262 if debug :
217263 feedback_items .append ((f"Input URL [{ idx } ]" , str (url )))
218264
219- # ---------------------------
220265 # 4) Decide correctness
221- # ---------------------------
222266 has_E = any (str (e .get ("code" , "" )).startswith ("E_" ) for e in merged_errors )
223267 is_correct = (not has_E )
224268
225- # ---------------------------
226269 # 5) Text feedback
227- # ---------------------------
228270 if merged_summaries :
229271 feedback_items .append (("Summary" , str (merged_summaries [- 1 ])))
230272
231273 if merged_ratios :
232274 feedback_items .append (("Ratio" , str (merged_ratios [- 1 ])))
233275
234276 if merged_errors :
235- lines = [f"- { e .get ('code' , 'ERR ' )} : { e .get ('message' , '' )} " for e in merged_errors ]
277+ lines = [f"- { e .get ('code' , 'E_ERR ' )} : { e .get ('message' , '' )} " for e in merged_errors ]
236278 feedback_items .append (("Issues" , "\n " .join (lines )))
237279
238280 if not feedback_items :
@@ -247,9 +289,18 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
247289
248290 except Exception as e :
249291 # Absolute last-resort: never crash the platform UI
250- items = [("Error" , f"{ type (e ).__name__ } : { e } " )]
292+ tb = traceback .format_exc ()
293+ safe_tb = _escape_html (tb )
294+ items = [
295+ ("Stage" , "UNHANDLED" ),
296+ ("ErrorCode" , "E_UNHANDLED" ),
297+ ("ExceptionType" , type (e ).__name__ ),
298+ ("Message" , str (e )),
299+ ("Traceback" , f"<pre>{ safe_tb } </pre>" ),
300+ ("Traceback(html)" , safe_tb .replace ("\n " , "<br>" )),
301+ ]
251302 feedback_html = _items_to_feedback_html (items )
252303 try :
253304 return Result (is_correct = False , feedback = feedback_html , feedback_items = items )
254305 except TypeError :
255- return Result (is_correct = False , feedback_items = items )
306+ return Result (is_correct = False , feedback_items = items )
0 commit comments