feat(annotations): add createOcrAnnotation that skips overlap check
OCR creates many adjacent text line annotations that would fail the existing overlap check. createOcrAnnotation() accepts an optional polygon and bypasses overlap detection entirely. Refs #227 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,26 @@ public class AnnotationService {
|
||||
return annotationRepository.save(annotation);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public DocumentAnnotation createOcrAnnotation(UUID documentId, CreateAnnotationDTO dto,
|
||||
UUID userId, String fileHash,
|
||||
List<List<Double>> polygon) {
|
||||
DocumentAnnotation annotation = DocumentAnnotation.builder()
|
||||
.documentId(documentId)
|
||||
.pageNumber(dto.getPageNumber())
|
||||
.x(dto.getX())
|
||||
.y(dto.getY())
|
||||
.width(dto.getWidth())
|
||||
.height(dto.getHeight())
|
||||
.color(dto.getColor())
|
||||
.fileHash(fileHash)
|
||||
.createdBy(userId)
|
||||
.polygon(polygon)
|
||||
.build();
|
||||
|
||||
return annotationRepository.save(annotation);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteAnnotation(UUID documentId, UUID annotationId, UUID userId) {
|
||||
DocumentAnnotation annotation = annotationRepository
|
||||
|
||||
@@ -260,6 +260,55 @@ class AnnotationServiceTest {
|
||||
verify(annotationRepository).save(any());
|
||||
}
|
||||
|
||||
// ─── createOcrAnnotation ──────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void createOcrAnnotation_skipsOverlapCheck_andSavesWithPolygon() {
|
||||
UUID docId = UUID.randomUUID();
|
||||
UUID userId = UUID.randomUUID();
|
||||
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.8, 0.04, "#00C7B1");
|
||||
List<List<Double>> polygon = List.of(
|
||||
List.of(0.1, 0.1), List.of(0.9, 0.11),
|
||||
List.of(0.89, 0.14), List.of(0.11, 0.13));
|
||||
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
DocumentAnnotation result = annotationService.createOcrAnnotation(
|
||||
docId, dto, userId, "filehash", polygon);
|
||||
|
||||
assertThat(result.getPolygon()).isEqualTo(polygon);
|
||||
assertThat(result.getDocumentId()).isEqualTo(docId);
|
||||
verify(annotationRepository).save(any());
|
||||
verify(annotationRepository, never()).findByDocumentIdAndPageNumber(any(), any(int.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOcrAnnotation_savesWithNullPolygon_whenPolygonNotProvided() {
|
||||
UUID docId = UUID.randomUUID();
|
||||
UUID userId = UUID.randomUUID();
|
||||
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.8, 0.04, "#00C7B1");
|
||||
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
DocumentAnnotation result = annotationService.createOcrAnnotation(
|
||||
docId, dto, userId, "filehash", null);
|
||||
|
||||
assertThat(result.getPolygon()).isNull();
|
||||
verify(annotationRepository).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createOcrAnnotation_doesNotCheckOverlap_evenWhenOverlappingAnnotationExists() {
|
||||
UUID docId = UUID.randomUUID();
|
||||
UUID userId = UUID.randomUUID();
|
||||
CreateAnnotationDTO dto = new CreateAnnotationDTO(1, 0.1, 0.1, 0.3, 0.3, "#00C7B1");
|
||||
when(annotationRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));
|
||||
|
||||
annotationService.createOcrAnnotation(docId, dto, userId, "hash", null);
|
||||
|
||||
verify(annotationRepository, never()).findByDocumentIdAndPageNumber(any(), any(int.class));
|
||||
}
|
||||
|
||||
// ─── overlaps — partial overlap cases ────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void createAnnotation_noConflict_whenAnnotationIsAbove() {
|
||||
// x ranges overlap, y ranges don't — existing is ABOVE the new annotation
|
||||
|
||||
Reference in New Issue
Block a user