refactor(ocr): move person-name enrichment from OcrController into OcrTrainingService
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,6 @@ import org.raddatz.familienarchiv.service.OcrBatchService;
|
|||||||
import org.raddatz.familienarchiv.service.OcrProgressService;
|
import org.raddatz.familienarchiv.service.OcrProgressService;
|
||||||
import org.raddatz.familienarchiv.service.OcrService;
|
import org.raddatz.familienarchiv.service.OcrService;
|
||||||
import org.raddatz.familienarchiv.service.OcrTrainingService;
|
import org.raddatz.familienarchiv.service.OcrTrainingService;
|
||||||
import org.raddatz.familienarchiv.service.PersonService;
|
|
||||||
import org.raddatz.familienarchiv.service.SegmentationTrainingExportService;
|
import org.raddatz.familienarchiv.service.SegmentationTrainingExportService;
|
||||||
import org.raddatz.familienarchiv.service.TrainingDataExportService;
|
import org.raddatz.familienarchiv.service.TrainingDataExportService;
|
||||||
import org.raddatz.familienarchiv.service.UserService;
|
import org.raddatz.familienarchiv.service.UserService;
|
||||||
@@ -41,7 +40,6 @@ public class OcrController {
|
|||||||
private final OcrBatchService ocrBatchService;
|
private final OcrBatchService ocrBatchService;
|
||||||
private final OcrProgressService ocrProgressService;
|
private final OcrProgressService ocrProgressService;
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final PersonService personService;
|
|
||||||
private final TrainingDataExportService trainingDataExportService;
|
private final TrainingDataExportService trainingDataExportService;
|
||||||
private final SegmentationTrainingExportService segmentationTrainingExportService;
|
private final SegmentationTrainingExportService segmentationTrainingExportService;
|
||||||
private final OcrTrainingService ocrTrainingService;
|
private final OcrTrainingService ocrTrainingService;
|
||||||
@@ -136,18 +134,6 @@ public class OcrController {
|
|||||||
public Map<String, Object> getTrainingInfo() {
|
public Map<String, Object> getTrainingInfo() {
|
||||||
OcrTrainingService.TrainingInfoResponse info = ocrTrainingService.getTrainingInfo();
|
OcrTrainingService.TrainingInfoResponse info = ocrTrainingService.getTrainingInfo();
|
||||||
|
|
||||||
Map<String, String> personNames = new HashMap<>();
|
|
||||||
for (OcrTrainingRun run : info.runs()) {
|
|
||||||
if (run.getPersonId() != null && !personNames.containsKey(run.getPersonId().toString())) {
|
|
||||||
try {
|
|
||||||
personNames.put(run.getPersonId().toString(),
|
|
||||||
personService.getById(run.getPersonId()).getDisplayName());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("Could not resolve display name for person {}: {}", run.getPersonId(), e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("availableBlocks", info.availableBlocks());
|
result.put("availableBlocks", info.availableBlocks());
|
||||||
result.put("totalOcrBlocks", info.totalOcrBlocks());
|
result.put("totalOcrBlocks", info.totalOcrBlocks());
|
||||||
@@ -156,7 +142,7 @@ public class OcrController {
|
|||||||
result.put("ocrServiceAvailable", info.ocrServiceAvailable());
|
result.put("ocrServiceAvailable", info.ocrServiceAvailable());
|
||||||
result.put("lastRun", info.lastRun() != null ? info.lastRun() : Map.of());
|
result.put("lastRun", info.lastRun() != null ? info.lastRun() : Map.of());
|
||||||
result.put("runs", info.runs());
|
result.put("runs", info.runs());
|
||||||
result.put("personNames", personNames);
|
result.put("personNames", info.personNames());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ import org.springframework.transaction.support.TransactionTemplate;
|
|||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -34,6 +36,7 @@ public class OcrTrainingService {
|
|||||||
private final OcrHealthClient ocrHealthClient;
|
private final OcrHealthClient ocrHealthClient;
|
||||||
private final TranscriptionBlockRepository blockRepository;
|
private final TranscriptionBlockRepository blockRepository;
|
||||||
private final TransactionTemplate txTemplate;
|
private final TransactionTemplate txTemplate;
|
||||||
|
private final PersonService personService;
|
||||||
|
|
||||||
public record TrainingInfoResponse(
|
public record TrainingInfoResponse(
|
||||||
int availableBlocks,
|
int availableBlocks,
|
||||||
@@ -42,7 +45,8 @@ public class OcrTrainingService {
|
|||||||
int availableSegBlocks,
|
int availableSegBlocks,
|
||||||
boolean ocrServiceAvailable,
|
boolean ocrServiceAvailable,
|
||||||
OcrTrainingRun lastRun,
|
OcrTrainingRun lastRun,
|
||||||
List<OcrTrainingRun> runs
|
List<OcrTrainingRun> runs,
|
||||||
|
Map<String, String> personNames
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private void assertNoRunningTraining() {
|
private void assertNoRunningTraining() {
|
||||||
@@ -198,6 +202,18 @@ public class OcrTrainingService {
|
|||||||
List<OcrTrainingRun> recentRuns = trainingRunRepository.findTop20ByOrderByCreatedAtDesc();
|
List<OcrTrainingRun> recentRuns = trainingRunRepository.findTop20ByOrderByCreatedAtDesc();
|
||||||
OcrTrainingRun lastRun = recentRuns.isEmpty() ? null : recentRuns.get(0);
|
OcrTrainingRun lastRun = recentRuns.isEmpty() ? null : recentRuns.get(0);
|
||||||
|
|
||||||
|
Map<String, String> personNames = new HashMap<>();
|
||||||
|
for (OcrTrainingRun run : recentRuns) {
|
||||||
|
if (run.getPersonId() != null && !personNames.containsKey(run.getPersonId().toString())) {
|
||||||
|
try {
|
||||||
|
personNames.put(run.getPersonId().toString(),
|
||||||
|
personService.getById(run.getPersonId()).getDisplayName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Could not resolve display name for person {}: {}", run.getPersonId(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new TrainingInfoResponse(
|
return new TrainingInfoResponse(
|
||||||
eligibleBlocks.size(),
|
eligibleBlocks.size(),
|
||||||
totalOcrBlocks,
|
totalOcrBlocks,
|
||||||
@@ -205,7 +221,8 @@ public class OcrTrainingService {
|
|||||||
availableSegBlocks,
|
availableSegBlocks,
|
||||||
ocrHealthClient.isHealthy(),
|
ocrHealthClient.isHealthy(),
|
||||||
lastRun,
|
lastRun,
|
||||||
recentRuns
|
recentRuns,
|
||||||
|
personNames
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import org.springframework.test.web.servlet.MockMvc;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
@@ -43,7 +45,6 @@ class OcrControllerTest {
|
|||||||
@MockitoBean OcrBatchService ocrBatchService;
|
@MockitoBean OcrBatchService ocrBatchService;
|
||||||
@MockitoBean OcrProgressService ocrProgressService;
|
@MockitoBean OcrProgressService ocrProgressService;
|
||||||
@MockitoBean UserService userService;
|
@MockitoBean UserService userService;
|
||||||
@MockitoBean PersonService personService;
|
|
||||||
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
@MockitoBean CustomUserDetailsService customUserDetailsService;
|
||||||
@MockitoBean TrainingDataExportService trainingDataExportService;
|
@MockitoBean TrainingDataExportService trainingDataExportService;
|
||||||
@MockitoBean SegmentationTrainingExportService segmentationTrainingExportService;
|
@MockitoBean SegmentationTrainingExportService segmentationTrainingExportService;
|
||||||
@@ -220,7 +221,7 @@ class OcrControllerTest {
|
|||||||
@WithMockUser(authorities = "ADMIN")
|
@WithMockUser(authorities = "ADMIN")
|
||||||
void getTrainingInfo_returns200_withInfo() throws Exception {
|
void getTrainingInfo_returns200_withInfo() throws Exception {
|
||||||
OcrTrainingService.TrainingInfoResponse info =
|
OcrTrainingService.TrainingInfoResponse info =
|
||||||
new OcrTrainingService.TrainingInfoResponse(5, 20, 2, 3, true, null, List.of());
|
new OcrTrainingService.TrainingInfoResponse(5, 20, 2, 3, true, null, List.of(), Map.of());
|
||||||
when(ocrTrainingService.getTrainingInfo()).thenReturn(info);
|
when(ocrTrainingService.getTrainingInfo()).thenReturn(info);
|
||||||
|
|
||||||
mockMvc.perform(get("/api/ocr/training-info"))
|
mockMvc.perform(get("/api/ocr/training-info"))
|
||||||
@@ -231,21 +232,38 @@ class OcrControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(authorities = "ADMIN")
|
@WithMockUser(authorities = "ADMIN")
|
||||||
void getTrainingInfo_returns200_and_omits_personName_when_resolution_throws() throws Exception {
|
void getTrainingInfo_returns200_and_omits_personName_when_service_provides_empty_map() throws Exception {
|
||||||
UUID personId = UUID.randomUUID();
|
UUID personId = UUID.randomUUID();
|
||||||
OcrTrainingRun runWithPerson = OcrTrainingRun.builder()
|
OcrTrainingRun runWithPerson = OcrTrainingRun.builder()
|
||||||
.id(UUID.randomUUID()).status(TrainingStatus.DONE)
|
.id(UUID.randomUUID()).status(TrainingStatus.DONE)
|
||||||
.personId(personId).blockCount(5).documentCount(1).modelName("sender_x").build();
|
.personId(personId).blockCount(5).documentCount(1).modelName("sender_x").build();
|
||||||
OcrTrainingService.TrainingInfoResponse info =
|
OcrTrainingService.TrainingInfoResponse info =
|
||||||
new OcrTrainingService.TrainingInfoResponse(5, 20, 2, 3, true, null, List.of(runWithPerson));
|
new OcrTrainingService.TrainingInfoResponse(5, 20, 2, 3, true, null,
|
||||||
|
List.of(runWithPerson), Map.of());
|
||||||
when(ocrTrainingService.getTrainingInfo()).thenReturn(info);
|
when(ocrTrainingService.getTrainingInfo()).thenReturn(info);
|
||||||
when(personService.getById(personId)).thenThrow(new RuntimeException("DB error"));
|
|
||||||
|
|
||||||
mockMvc.perform(get("/api/ocr/training-info"))
|
mockMvc.perform(get("/api/ocr/training-info"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(jsonPath("$.personNames").isEmpty());
|
.andExpect(jsonPath("$.personNames").isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(authorities = "ADMIN")
|
||||||
|
void getTrainingInfo_includesPersonName_whenPersonIdResolves() throws Exception {
|
||||||
|
UUID personId = UUID.randomUUID();
|
||||||
|
OcrTrainingRun runWithPerson = OcrTrainingRun.builder()
|
||||||
|
.id(UUID.randomUUID()).status(TrainingStatus.DONE)
|
||||||
|
.personId(personId).blockCount(5).documentCount(1).modelName("sender_x").build();
|
||||||
|
OcrTrainingService.TrainingInfoResponse info =
|
||||||
|
new OcrTrainingService.TrainingInfoResponse(5, 20, 2, 3, true, null,
|
||||||
|
List.of(runWithPerson), Map.of(personId.toString(), "Max Mustermann"));
|
||||||
|
when(ocrTrainingService.getTrainingInfo()).thenReturn(info);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/ocr/training-info"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.personNames." + personId).value("Max Mustermann"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(authorities = "READ_ALL")
|
@WithMockUser(authorities = "READ_ALL")
|
||||||
void getDocumentOcrStatus_returnsNone_whenNoOcrJobExists() throws Exception {
|
void getDocumentOcrStatus_returnsNone_whenNoOcrJobExists() throws Exception {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.raddatz.familienarchiv.model.TrainingStatus;
|
|||||||
import org.raddatz.familienarchiv.model.TranscriptionBlock;
|
import org.raddatz.familienarchiv.model.TranscriptionBlock;
|
||||||
import org.raddatz.familienarchiv.repository.OcrTrainingRunRepository;
|
import org.raddatz.familienarchiv.repository.OcrTrainingRunRepository;
|
||||||
import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository;
|
import org.raddatz.familienarchiv.repository.TranscriptionBlockRepository;
|
||||||
|
import org.raddatz.familienarchiv.service.PersonService;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
import org.springframework.transaction.support.TransactionCallback;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
import org.springframework.transaction.support.TransactionTemplate;
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ class OcrTrainingServiceTest {
|
|||||||
OcrHealthClient healthClient;
|
OcrHealthClient healthClient;
|
||||||
TranscriptionBlockRepository blockRepository;
|
TranscriptionBlockRepository blockRepository;
|
||||||
TransactionTemplate txTemplate;
|
TransactionTemplate txTemplate;
|
||||||
|
PersonService personService;
|
||||||
OcrTrainingService service;
|
OcrTrainingService service;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -42,6 +44,7 @@ class OcrTrainingServiceTest {
|
|||||||
healthClient = mock(OcrHealthClient.class);
|
healthClient = mock(OcrHealthClient.class);
|
||||||
blockRepository = mock(TranscriptionBlockRepository.class);
|
blockRepository = mock(TranscriptionBlockRepository.class);
|
||||||
txTemplate = mock(TransactionTemplate.class);
|
txTemplate = mock(TransactionTemplate.class);
|
||||||
|
personService = mock(PersonService.class);
|
||||||
|
|
||||||
// Execute transaction callbacks inline so unit tests run without a real DataSource
|
// Execute transaction callbacks inline so unit tests run without a real DataSource
|
||||||
when(txTemplate.execute(any())).thenAnswer(inv -> {
|
when(txTemplate.execute(any())).thenAnswer(inv -> {
|
||||||
@@ -49,7 +52,7 @@ class OcrTrainingServiceTest {
|
|||||||
return callback.doInTransaction(null);
|
return callback.doInTransaction(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
service = new OcrTrainingService(runRepository, exportService, segExportService, ocrClient, healthClient, blockRepository, txTemplate);
|
service = new OcrTrainingService(runRepository, exportService, segExportService, ocrClient, healthClient, blockRepository, txTemplate, personService);
|
||||||
|
|
||||||
when(blockRepository.count()).thenReturn(0L);
|
when(blockRepository.count()).thenReturn(0L);
|
||||||
when(runRepository.findTop20ByOrderByCreatedAtDesc()).thenReturn(List.of());
|
when(runRepository.findTop20ByOrderByCreatedAtDesc()).thenReturn(List.of());
|
||||||
|
|||||||
Reference in New Issue
Block a user