@@ -720,6 +720,20 @@ def _process_assessment_task(
720720 "confidence_reason" : f"Unable to parse assessment response for { attr_name } - default score assigned" ,
721721 }
722722
723+ # Process bounding boxes automatically if bbox data is present
724+ try :
725+ logger .debug (
726+ f"Checking for bounding box data in granular assessment task { task .task_id } "
727+ )
728+ assessment_data = self ._extract_geometry_from_assessment (
729+ assessment_data
730+ )
731+ except Exception as e :
732+ logger .warning (
733+ f"Failed to extract geometry data for task { task .task_id } : { str (e )} "
734+ )
735+ # Continue with assessment even if geometry extraction fails
736+
723737 # Check for confidence threshold alerts
724738 confidence_alerts = []
725739 self ._check_confidence_alerts_for_task (
@@ -987,6 +1001,149 @@ def _get_text_confidence_data(self, page) -> str:
9871001
9881002 return ""
9891003
1004+ def _convert_bbox_to_geometry (
1005+ self , bbox_coords : List [float ], page_num : int
1006+ ) -> Dict [str , Any ]:
1007+ """
1008+ Convert [x1,y1,x2,y2] coordinates to geometry format.
1009+
1010+ Args:
1011+ bbox_coords: List of 4 coordinates [x1, y1, x2, y2] in 0-1000 scale
1012+ page_num: Page number where the bounding box appears
1013+
1014+ Returns:
1015+ Dictionary in geometry format compatible with pattern-1 UI
1016+ """
1017+ if len (bbox_coords ) != 4 :
1018+ raise ValueError (f"Expected 4 coordinates, got { len (bbox_coords )} " )
1019+
1020+ x1 , y1 , x2 , y2 = bbox_coords
1021+
1022+ # Ensure coordinates are in correct order
1023+ x1 , x2 = min (x1 , x2 ), max (x1 , x2 )
1024+ y1 , y2 = min (y1 , y2 ), max (y1 , y2 )
1025+
1026+ # Convert from normalized 0-1000 scale to 0-1
1027+ left = x1 / 1000.0
1028+ top = y1 / 1000.0
1029+ width = (x2 - x1 ) / 1000.0
1030+ height = (y2 - y1 ) / 1000.0
1031+
1032+ return {
1033+ "boundingBox" : {"top" : top , "left" : left , "width" : width , "height" : height },
1034+ "page" : page_num ,
1035+ }
1036+
1037+ def _process_single_assessment_geometry (
1038+ self , attr_assessment : Dict [str , Any ], attr_name : str = ""
1039+ ) -> Dict [str , Any ]:
1040+ """
1041+ Process geometry data for a single assessment (with confidence key).
1042+
1043+ Args:
1044+ attr_assessment: Single assessment dictionary with confidence data
1045+ attr_name: Name of attribute for logging
1046+
1047+ Returns:
1048+ Enhanced assessment with geometry converted to proper format
1049+ """
1050+ enhanced_attr = attr_assessment .copy ()
1051+
1052+ # Check if this assessment includes bbox data
1053+ if "bbox" in attr_assessment or "page" in attr_assessment :
1054+ # Both bbox and page are required for valid geometry
1055+ if "bbox" in attr_assessment and "page" in attr_assessment :
1056+ try :
1057+ bbox_coords = attr_assessment ["bbox" ]
1058+ page_num = attr_assessment ["page" ]
1059+
1060+ # Validate bbox coordinates
1061+ if isinstance (bbox_coords , list ) and len (bbox_coords ) == 4 :
1062+ # Convert to geometry format
1063+ geometry = self ._convert_bbox_to_geometry (bbox_coords , page_num )
1064+ enhanced_attr ["geometry" ] = [geometry ]
1065+
1066+ logger .debug (
1067+ f"Converted bounding box for { attr_name } : { bbox_coords } -> geometry format"
1068+ )
1069+ else :
1070+ logger .warning (
1071+ f"Invalid bounding box format for { attr_name } : { bbox_coords } "
1072+ )
1073+
1074+ except Exception as e :
1075+ logger .warning (
1076+ f"Failed to process bounding box for { attr_name } : { str (e )} "
1077+ )
1078+ else :
1079+ # If only one of bbox/page exists, log a warning about incomplete data
1080+ if "bbox" in attr_assessment and "page" not in attr_assessment :
1081+ logger .warning (
1082+ f"Found bbox without page for { attr_name } - removing incomplete bbox data"
1083+ )
1084+ elif "page" in attr_assessment and "bbox" not in attr_assessment :
1085+ logger .warning (
1086+ f"Found page without bbox for { attr_name } - removing incomplete page data"
1087+ )
1088+
1089+ # Always remove raw bbox/page data from output (whether processed or incomplete)
1090+ enhanced_attr .pop ("bbox" , None )
1091+ enhanced_attr .pop ("page" , None )
1092+
1093+ return enhanced_attr
1094+
1095+ def _extract_geometry_from_assessment (
1096+ self , assessment_data : Dict [str , Any ]
1097+ ) -> Dict [str , Any ]:
1098+ """
1099+ Extract geometry data from assessment response and convert to proper format.
1100+ Now supports recursive processing of nested group attributes.
1101+
1102+ Args:
1103+ assessment_data: Dictionary containing assessment results from LLM
1104+
1105+ Returns:
1106+ Enhanced assessment data with geometry information converted to proper format
1107+ """
1108+ enhanced_assessment = {}
1109+
1110+ for attr_name , attr_assessment in assessment_data .items ():
1111+ if isinstance (attr_assessment , dict ):
1112+ # Check if this is a direct confidence assessment
1113+ if "confidence" in attr_assessment :
1114+ # This is a direct assessment - process its geometry
1115+ enhanced_assessment [attr_name ] = (
1116+ self ._process_single_assessment_geometry (
1117+ attr_assessment , attr_name
1118+ )
1119+ )
1120+ else :
1121+ # This is a group attribute (no direct confidence) - recursively process nested attributes
1122+ logger .debug (f"Processing group attribute: { attr_name } " )
1123+ enhanced_assessment [attr_name ] = (
1124+ self ._extract_geometry_from_assessment (attr_assessment )
1125+ )
1126+
1127+ elif isinstance (attr_assessment , list ):
1128+ # Handle list attributes - process each item recursively
1129+ enhanced_list = []
1130+ for i , item_assessment in enumerate (attr_assessment ):
1131+ if isinstance (item_assessment , dict ):
1132+ # Recursively process each list item
1133+ enhanced_item = self ._extract_geometry_from_assessment (
1134+ item_assessment
1135+ )
1136+ enhanced_list .append (enhanced_item )
1137+ else :
1138+ # Non-dict items pass through unchanged
1139+ enhanced_list .append (item_assessment )
1140+ enhanced_assessment [attr_name ] = enhanced_list
1141+ else :
1142+ # Other types pass through unchanged
1143+ enhanced_assessment [attr_name ] = attr_assessment
1144+
1145+ return enhanced_assessment
1146+
9901147 def process_document_section (self , document : Document , section_id : str ) -> Document :
9911148 """
9921149 Process a single section from a Document object to assess extraction confidence using granular approach.
0 commit comments