@@ -112,6 +112,7 @@ public ResponseEntity<?> exportInspection(@PathVariable String id) {
112112 JsonNode currentFaults = parseJsonNode (mapper , inspection .getFaultTypes ());
113113 JsonNode currentAnnotated = parseJsonNode (mapper , inspection .getAnnotatedBy ());
114114 JsonNode currentSeverity = parseJsonNode (mapper , inspection .getSeverity ());
115+ JsonNode currentComments = parseJsonNode (mapper , inspection .getComment ());
115116
116117 ObjectNode currentNode = root .putObject ("current" );
117118 putNullable (currentNode , "timestamp" ,
@@ -120,15 +121,17 @@ public ResponseEntity<?> exportInspection(@PathVariable String id) {
120121 currentNode .set ("faultTypes" , cloneNode (currentFaults ));
121122 currentNode .set ("annotatedBy" , cloneNode (currentAnnotated ));
122123 currentNode .set ("severity" , cloneNode (currentSeverity ));
124+ currentNode .set ("comments" , cloneNode (currentComments ));
123125
124126 ArrayNode boxHistory = asArrayNode (parseJsonNode (mapper , inspection .getBoundingBoxHistory ()));
125127 ArrayNode faultHistory = asArrayNode (parseJsonNode (mapper , inspection .getFaultTypeHistory ()));
126128 ArrayNode annotatedHistory = asArrayNode (parseJsonNode (mapper , inspection .getAnnotatedByHistory ()));
127129 ArrayNode severityHistory = asArrayNode (parseJsonNode (mapper , inspection .getSeverityHistory ()));
130+ ArrayNode commentHistory = asArrayNode (parseJsonNode (mapper , inspection .getCommentHistory ()));
128131 ArrayNode timestampHistory = asArrayNode (parseJsonNode (mapper , inspection .getTimestampHistory ()));
129132
130133 ArrayNode history = mapper .createArrayNode ();
131- int snapshotCount = maxSize (boxHistory , faultHistory , annotatedHistory , severityHistory , timestampHistory );
134+ int snapshotCount = maxSize (boxHistory , faultHistory , annotatedHistory , severityHistory , commentHistory , timestampHistory );
132135 for (int index = 0 ; index < snapshotCount ; index ++) {
133136 ObjectNode entry = mapper .createObjectNode ();
134137 String ts = extractText (timestampHistory , index );
@@ -138,6 +141,7 @@ public ResponseEntity<?> exportInspection(@PathVariable String id) {
138141 entry .set ("faultTypes" , cloneNode (snapshotValue (faultHistory , index )));
139142 entry .set ("annotatedBy" , cloneNode (snapshotValue (annotatedHistory , index )));
140143 entry .set ("severity" , cloneNode (snapshotValue (severityHistory , index )));
144+ entry .set ("comments" , cloneNode (snapshotValue (commentHistory , index )));
141145 history .add (entry );
142146 }
143147
@@ -149,26 +153,29 @@ public ResponseEntity<?> exportInspection(@PathVariable String id) {
149153 currentEntry .set ("faultTypes" , cloneNode (currentFaults ));
150154 currentEntry .set ("annotatedBy" , cloneNode (currentAnnotated ));
151155 currentEntry .set ("severity" , cloneNode (currentSeverity ));
156+ currentEntry .set ("comments" , cloneNode (currentComments ));
152157 history .add (currentEntry );
153158
154159 root .set ("history" , history );
155160
156161 byte [] metadataBytes = mapper .writerWithDefaultPrettyPrinter ().writeValueAsBytes (root );
157162
158163 StringBuilder csvBuilder = new StringBuilder ();
159- csvBuilder .append ("timestamp,isCurrent,boundingBoxes,faultTypes,annotatedBy,severity\n " );
164+ csvBuilder .append ("timestamp,isCurrent,boundingBoxes,faultTypes,annotatedBy,severity,comments \n " );
160165 for (JsonNode entry : history ) {
161166 String timestamp = entry .path ("timestamp" ).isNull () ? "" : entry .path ("timestamp" ).asText ("" );
162167 String boxesJson = mapper .writeValueAsString (entry .path ("boundingBoxes" ));
163168 String faultsJson = mapper .writeValueAsString (entry .path ("faultTypes" ));
164169 String annotatedJson = mapper .writeValueAsString (entry .path ("annotatedBy" ));
165170 String severityJson = mapper .writeValueAsString (entry .path ("severity" ));
171+ String commentsJson = mapper .writeValueAsString (entry .path ("comments" ));
166172 csvBuilder .append ('"' ).append (csvEscape (timestamp )).append ('"' ).append (',' );
167173 csvBuilder .append (entry .path ("isCurrent" ).asBoolean (false ) ? "true" : "false" ).append (',' );
168174 csvBuilder .append ('"' ).append (csvEscape (boxesJson )).append ('"' ).append (',' );
169175 csvBuilder .append ('"' ).append (csvEscape (faultsJson )).append ('"' ).append (',' );
170176 csvBuilder .append ('"' ).append (csvEscape (annotatedJson )).append ('"' ).append (',' );
171- csvBuilder .append ('"' ).append (csvEscape (severityJson )).append ('"' ).append ('\n' );
177+ csvBuilder .append ('"' ).append (csvEscape (severityJson )).append ('"' ).append (',' );
178+ csvBuilder .append ('"' ).append (csvEscape (commentsJson )).append ('"' ).append ('\n' );
172179 }
173180 byte [] csvBytes = csvBuilder .toString ().getBytes (StandardCharsets .UTF_8 );
174181
@@ -435,6 +442,7 @@ private void archivePreviousAnalysis(Inspection i, String annotatedBy) throws Ex
435442 String boxesJson = i .getBoundingBoxes ();
436443 String faultsJson = i .getFaultTypes ();
437444 String severityJson = i .getSeverity ();
445+ String commentJson = i .getComment ();
438446 if ((boxesJson == null || boxesJson .isBlank ()) && (faultsJson == null || faultsJson .isBlank ())) {
439447 return ; // nothing to archive
440448 }
@@ -563,6 +571,29 @@ private void archivePreviousAnalysis(Inspection i, String annotatedBy) throws Ex
563571 severityHist .addNull ();
564572 }
565573
574+ // commentHistory as array of snapshots aligned with boxes/faults
575+ ArrayNode commentHist ;
576+ String commentHistJson = i .getCommentHistory ();
577+ if (commentHistJson != null && !commentHistJson .isBlank ()) {
578+ try {
579+ var node = mapper .readTree (commentHistJson );
580+ commentHist = node instanceof ArrayNode ? (ArrayNode ) node : mapper .createArrayNode ();
581+ } catch (Exception ex ) {
582+ commentHist = mapper .createArrayNode ();
583+ }
584+ } else {
585+ commentHist = mapper .createArrayNode ();
586+ }
587+ if (commentJson != null && !commentJson .isBlank ()) {
588+ try {
589+ commentHist .add (mapper .readTree (commentJson ));
590+ } catch (Exception ex ) {
591+ commentHist .addNull ();
592+ }
593+ } else {
594+ commentHist .addNull ();
595+ }
596+
566597 // timestampHistory records when each snapshot was archived
567598 ArrayNode timestampHist ;
568599 String timestampHistJson = i .getTimestampHistory ();
@@ -583,6 +614,7 @@ private void archivePreviousAnalysis(Inspection i, String annotatedBy) throws Ex
583614 i .setFaultTypeHistory (faultHist .toString ());
584615 i .setAnnotatedByHistory (annotatedHist .toString ());
585616 i .setSeverityHistory (severityHist .toString ());
617+ i .setCommentHistory (commentHist .toString ());
586618 i .setTimestampHistory (timestampHist .toString ());
587619 }
588620
@@ -786,6 +818,8 @@ public ResponseEntity<?> clearAnalysis(@PathVariable String id) {
786818 i .setAnnotatedByHistory (null );
787819 i .setSeverity (null );
788820 i .setSeverityHistory (null );
821+ i .setComment (null );
822+ i .setCommentHistory (null );
789823 i .setTimestampHistory (null );
790824 // analyzed image dimensions removed; nothing to clear
791825 } catch (Exception ignore ) { }
@@ -882,6 +916,21 @@ public ResponseEntity<?> removeBox(@PathVariable String id,
882916 } catch (Exception ignore ) { /* ignore malformed severity */ }
883917 }
884918
919+ // Update comment array to maintain alignment
920+ String commentJson = i .getComment ();
921+ if (commentJson != null && !commentJson .isBlank ()) {
922+ try {
923+ JsonNode commentNode = mapper .readTree (commentJson );
924+ if (commentNode instanceof ArrayNode ) {
925+ ArrayNode commentArr = (ArrayNode ) commentNode ;
926+ if (index >= 0 && index < commentArr .size ()) {
927+ commentArr .remove (index );
928+ i .setComment (commentArr .isEmpty () ? null : commentArr .toString ());
929+ }
930+ }
931+ } catch (Exception ignore ) { /* ignore malformed comment */ }
932+ }
933+
885934 repo .save (i );
886935 return ResponseEntity .ok (Map .of ("ok" , true , "boundingBoxes" , arr , "faultTypes" , i .getFaultTypes ()));
887936 } catch (Exception e ) {
@@ -976,6 +1025,21 @@ public ResponseEntity<?> removeBoxByValue(@PathVariable String id,
9761025 } catch (Exception ignore ) { /* ignore malformed severity */ }
9771026 }
9781027
1028+ // Update comment alignment
1029+ String commentJson = i .getComment ();
1030+ if (commentJson != null && !commentJson .isBlank ()) {
1031+ try {
1032+ JsonNode commentNode = mapper .readTree (commentJson );
1033+ if (commentNode instanceof ArrayNode ) {
1034+ ArrayNode commentArr = (ArrayNode ) commentNode ;
1035+ if (matchIdx >= 0 && matchIdx < commentArr .size ()) {
1036+ commentArr .remove (matchIdx );
1037+ i .setComment (commentArr .isEmpty () ? null : commentArr .toString ());
1038+ }
1039+ }
1040+ } catch (Exception ignore ) { /* ignore malformed comment */ }
1041+ }
1042+
9791043 repo .save (i );
9801044 return ResponseEntity .ok (Map .of ("ok" , true , "boundingBoxes" , arr , "faultTypes" , i .getFaultTypes ()));
9811045 } catch (Exception e ) {
@@ -997,6 +1061,14 @@ public ResponseEntity<?> addBox(@PathVariable String id,
9971061 double w = ((Number )payload .getOrDefault ("w" , 0 )).doubleValue ();
9981062 double h = ((Number )payload .getOrDefault ("h" , 0 )).doubleValue ();
9991063 String faultType = String .valueOf (payload .getOrDefault ("faultType" , "none" ));
1064+ Object rawComment = payload .get ("comment" );
1065+ String commentValue = null ;
1066+ if (rawComment instanceof String ) {
1067+ String trimmed = ((String ) rawComment ).trim ();
1068+ if (!trimmed .isEmpty ()) {
1069+ commentValue = trimmed ;
1070+ }
1071+ }
10001072
10011073 ObjectMapper mapper = new ObjectMapper ();
10021074 ArrayNode boxesArr ;
@@ -1065,8 +1137,28 @@ public ResponseEntity<?> addBox(@PathVariable String id,
10651137 sevArr .addNull (); // User-added boxes have null severity
10661138 i .setSeverity (sevArr .toString ());
10671139
1140+ // Update comment array aligned with boxes
1141+ ArrayNode commentArr ;
1142+ String commentJson = i .getComment ();
1143+ if (commentJson != null && !commentJson .isBlank ()) {
1144+ try {
1145+ var node = mapper .readTree (commentJson );
1146+ commentArr = node instanceof ArrayNode ? (ArrayNode ) node : mapper .createArrayNode ();
1147+ } catch (Exception ex ) {
1148+ commentArr = mapper .createArrayNode ();
1149+ }
1150+ } else {
1151+ commentArr = mapper .createArrayNode ();
1152+ }
1153+ if (commentValue != null ) {
1154+ commentArr .add (commentValue );
1155+ } else {
1156+ commentArr .addNull ();
1157+ }
1158+ i .setComment (commentArr .isEmpty () ? null : commentArr .toString ());
1159+
10681160 repo .save (i );
1069- return ResponseEntity .ok (Map .of ("ok" , true , "boundingBoxes" , boxesArr , "faultTypes" , ftArr ));
1161+ return ResponseEntity .ok (Map .of ("ok" , true , "boundingBoxes" , boxesArr , "faultTypes" , ftArr , "comments" , commentArr ));
10701162 } catch (Exception e ) {
10711163 return ResponseEntity .internalServerError ().body (Map .of ("error" , "Failed to add box" ));
10721164 }
@@ -1091,6 +1183,7 @@ public ResponseEntity<?> bulkUpdateBoxes(@PathVariable String id,
10911183 var boxesPayload = payload .get ("boundingBoxes" );
10921184 var faultsPayload = payload .get ("faultTypes" );
10931185 var annotatedByPayload = payload .get ("annotatedBy" );
1186+ var commentsPayload = payload .get ("comments" );
10941187 var tuneModelPayload = payload .get ("tuneModel" );
10951188 boolean tuneModel = true ;
10961189 if (tuneModelPayload instanceof Boolean ) {
@@ -1137,14 +1230,41 @@ public ResponseEntity<?> bulkUpdateBoxes(@PathVariable String id,
11371230 }
11381231 }
11391232
1140- String finalBoxesJson = finalBoxes .toString ();
1141- String finalFaultsJson = finalFaults .toString ();
1142- String finalAnnotatedJson = finalAnnotated .toString ();
1233+ ArrayNode finalComments = mapper .createArrayNode ();
1234+ if (commentsPayload instanceof List ) {
1235+ for (Object commentObj : (List <?>) commentsPayload ) {
1236+ if (commentObj == null ) {
1237+ finalComments .addNull ();
1238+ } else {
1239+ String text = commentObj .toString ().trim ();
1240+ finalComments .add (text .isEmpty () ? null : text );
1241+ }
1242+ }
1243+ }
1244+ while (finalComments .size () < finalBoxes .size ()) {
1245+ finalComments .addNull ();
1246+ }
1247+ while (finalComments .size () > finalBoxes .size ()) {
1248+ finalComments .remove (finalComments .size () - 1 );
1249+ }
1250+ boolean hasCommentValue = false ;
1251+ for (JsonNode node : finalComments ) {
1252+ if (node != null && !node .isNull () && !node .asText ("" ).isBlank ()) {
1253+ hasCommentValue = true ;
1254+ break ;
1255+ }
1256+ }
1257+
1258+ String finalBoxesJson = finalBoxes .toString ();
1259+ String finalFaultsJson = finalFaults .toString ();
1260+ String finalAnnotatedJson = finalAnnotated .toString ();
1261+ String finalCommentsJson = hasCommentValue ? finalComments .toString () : null ;
11431262
11441263 // Persist final state
11451264 i .setBoundingBoxes (finalBoxesJson );
11461265 i .setFaultTypes (finalFaultsJson );
11471266 i .setAnnotatedBy (finalAnnotatedJson );
1267+ i .setComment (finalCommentsJson );
11481268 repo .save (i );
11491269
11501270 if (tuneModel ) {
@@ -1159,7 +1279,7 @@ public ResponseEntity<?> bulkUpdateBoxes(@PathVariable String id,
11591279 );
11601280 }
11611281
1162- return ResponseEntity .ok (Map .of ("ok" , true , "boundingBoxes" , finalBoxes , "faultTypes" , finalFaults ));
1282+ return ResponseEntity .ok (Map .of ("ok" , true , "boundingBoxes" , finalBoxes , "faultTypes" , finalFaults , "comments" , finalComments ));
11631283 } catch (Exception e ) {
11641284 return ResponseEntity .internalServerError ().body (Map .of ("error" , "Bulk update failed" ));
11651285 }
0 commit comments