From af8c36aedf57354f21ac127676a8e3d2594a3b2c Mon Sep 17 00:00:00 2001 From: Jason Loux Date: Thu, 2 Apr 2026 15:39:23 -0400 Subject: [PATCH] [BI-2813] Set levelNameDbIds in OUs in experiment creation use case --- .../BrAPIObservationLevelService.java | 27 +------- .../steps/CommitPendingImportObjectsStep.java | 69 ++++++++++++------- .../experiment/service/DatasetService.java | 43 +++++++++++- 3 files changed, 88 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java index 8d48979cb..e1f3d9b98 100644 --- a/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java +++ b/src/main/java/org/breedinginsight/brapi/v2/services/BrAPIObservationLevelService.java @@ -1,49 +1,28 @@ package org.breedinginsight.brapi.v2.services; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; -import org.brapi.client.v2.ApiResponse; import org.brapi.client.v2.model.exceptions.ApiException; -import org.brapi.client.v2.modules.phenotype.ObservationUnitsApi; import org.brapi.v2.model.pheno.BrAPIObservationUnitHierarchyLevel; -import org.brapi.v2.model.pheno.BrAPIObservationUnitLevelRelationship; -import org.brapi.v2.model.pheno.response.BrAPIObservationLevelListResponse; import org.breedinginsight.brapi.v2.dao.BrAPIObservationLevelDAO; import org.breedinginsight.model.DatasetLevel; import org.breedinginsight.model.Program; -import org.breedinginsight.services.ProgramService; -import org.breedinginsight.services.exceptions.DoesNotExistException; import javax.inject.Inject; import javax.inject.Singleton; -import java.util.ArrayList; import java.util.List; -import java.util.Map; @Slf4j @Singleton public class BrAPIObservationLevelService { private final BrAPIObservationLevelDAO brAPIObservationLevelDAO; - private final ProgramService programService; - @Inject - public BrAPIObservationLevelService(BrAPIObservationLevelDAO brAPIObservationLevelDAO, - ProgramService programService) { + public BrAPIObservationLevelService(BrAPIObservationLevelDAO brAPIObservationLevelDAO) { this.brAPIObservationLevelDAO = brAPIObservationLevelDAO; - this.programService = programService; } - /** - * @return Pair[GlobalLevelNames, ProgrammaticLevelNames] - */ - public Pair, List> getGlobalAndProgrammaticLevelNames(Program program, String brapiProgramDbId) { - List globalLevels = brAPIObservationLevelDAO.getGlobalObservationLevelNames(program); - List programmaticLevels = brAPIObservationLevelDAO.getObservationLevelNamesByProgramId(program, brapiProgramDbId); - - - return new ImmutablePair<>(globalLevels, programmaticLevels); + public List getGlobalLevelNames(Program program) { + return brAPIObservationLevelDAO.getGlobalObservationLevelNames(program); } public List getProgrammaticLevelNames(Program program, String brapiProgramDbId) { diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/create/workflow/steps/CommitPendingImportObjectsStep.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/create/workflow/steps/CommitPendingImportObjectsStep.java index ef9dc9ef5..bdcc8956f 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/create/workflow/steps/CommitPendingImportObjectsStep.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/create/workflow/steps/CommitPendingImportObjectsStep.java @@ -23,6 +23,7 @@ import org.apache.commons.lang3.StringUtils; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.core.BrAPIListSummary; +import org.brapi.v2.model.core.BrAPIProgram; import org.brapi.v2.model.core.BrAPIStudy; import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.core.request.BrAPIListNewRequest; @@ -43,6 +44,7 @@ import org.breedinginsight.brapps.importer.services.processors.experiment.ExperimentUtilities; import org.breedinginsight.brapps.importer.services.processors.experiment.create.model.PendingData; import org.breedinginsight.brapps.importer.services.processors.experiment.create.model.ProcessContext; +import org.breedinginsight.brapps.importer.services.processors.experiment.service.DatasetService; import org.breedinginsight.model.DatasetLevel; import org.breedinginsight.model.Program; import org.breedinginsight.model.ProgramLocation; @@ -55,10 +57,7 @@ import javax.inject.Inject; import javax.inject.Singleton; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import static org.breedinginsight.brapps.importer.services.processors.experiment.ExperimentUtilities.PREEXISTING_EXPERIMENT_TITLE; @@ -74,7 +73,7 @@ public class CommitPendingImportObjectsStep { private final BrAPIObservationUnitDAO brAPIObservationUnitDAO; private final ProgramLocationService locationService; private final OntologyService ontologyService; - private final BrAPIObservationLevelDAO brAPIObservationLevelDAO; + private final DatasetService datasetService; @Inject public CommitPendingImportObjectsStep(BrAPIListDAO brAPIListDAO, @@ -84,7 +83,7 @@ public CommitPendingImportObjectsStep(BrAPIListDAO brAPIListDAO, BrAPIObservationUnitDAO brAPIObservationUnitDAO, ProgramLocationService locationService, OntologyService ontologyService, - BrAPIObservationLevelDAO brAPIObservationLevelDAO) { + DatasetService datasetService) { this.brAPIListDAO = brAPIListDAO; this.brapiTrialDAO = brapiTrialDAO; this.brAPIStudyDAO = brAPIStudyDAO; @@ -92,11 +91,13 @@ public CommitPendingImportObjectsStep(BrAPIListDAO brAPIListDAO, this.brAPIObservationUnitDAO = brAPIObservationUnitDAO; this.locationService = locationService; this.ontologyService = ontologyService; - this.brAPIObservationLevelDAO = brAPIObservationLevelDAO; + this.datasetService = datasetService; } // TODO: some common code between workflows here that could be broken out, removed append/update specific code - public void process(ProcessContext processContext, ProcessedData processedData) throws UnprocessableEntityException { + // TODO: For instance, multiple trials don't really exist in the use case which this code is used for: creating an experiment. + // TODO: This means having a Map of trials doesn't really make sense anymore, and should be replaced with a singular PendingImportObject + public void process(ProcessContext processContext, ProcessedData processedData) throws UnprocessableEntityException, ApiException { PendingData pendingData = processContext.getPendingData(); ImportContext importContext = processContext.getImportContext(); @@ -113,6 +114,32 @@ public void process(ProcessContext processContext, ProcessedData processedData) Map> observationByHash = pendingData.getObservationByHash(); Map expUnitbyTrialName = pendingData.getExpUnitByTrialName(); + // Assume that there's only one expUnit per trial in this workflow. + // This should be a safe assumption to make considering this workflow is for the creation of a single experiment. + // If that changes in the future, this code will break and need to be dealt with. + + if (expUnitbyTrialName.size() > 1) { + throw new InternalServerException("Multiple Experiment names exist during commit stage"); + } + + String expUnitName = expUnitbyTrialName.values() + .stream() + .findFirst() + .orElse(null); + + if (expUnitName == null) { + throw new InternalServerException("Experiment unit name not found during commit stage"); + } + + String brapiProgramId = Optional.of(program) + .map(Program::getBrapiProgram) + .map(BrAPIProgram::getProgramDbId) + .orElse(null); + + if (brapiProgramId == null) { + throw new InternalServerException("BrAPI program not found during commit stage"); + } + List newTrials = ProcessorData.getNewObjects(pendingData.getTrialByNameNoScope()); List newLocations = ProcessorData.getNewObjects(pendingData.getLocationByName()) @@ -139,6 +166,14 @@ public void process(ProcessContext processContext, ProcessedData processedData) List newObservationUnits = ProcessorData.getNewObjects(pendingData.getObservationUnitByNameNoScope()); + // Inject level names here + datasetService.updateObservationUnitsWithLevelNameDbIds(newObservationUnits, + program, + brapiProgramId, + expUnitName, + DatasetLevel.EXP_UNIT + ); + // filter out observations with no 'value' so they will not be saved List newObservations = ProcessorData.getNewObjects(observationByHash) .stream() @@ -176,8 +211,6 @@ public void process(ProcessContext processContext, ProcessedData processedData) trialByNameNoScope.get(createdTrialName) .getBrAPIObject() .setTrialDbId(createdTrial.getTrialDbId()); - - createObservationLevel(createdTrialName, expUnitbyTrialName, program); } List createdLocations = new ArrayList<>(locationService.create(actingUser, program.getId(), newLocations)); @@ -251,22 +284,6 @@ public void process(ProcessContext processContext, ProcessedData processedData) } - //Check if the experimental unit associated with the trial does not exist in the system and if so create a new observation level - private void createObservationLevel(String trialName, Map expUnitByTrialName, Program program) throws ApiException, InternalServerException { - String expUnit = expUnitByTrialName.get(trialName).toLowerCase(); - String programDbId = program.getBrapiProgram() != null ? program.getBrapiProgram().getProgramDbId() : null; - HttpResponse levelResponse = brAPIObservationLevelDAO.createObservationLevelName(program, expUnit, DatasetLevel.EXP_UNIT, programDbId); - - if (levelResponse.getStatus().getCode() == 409) { - log.info(String.format("Level with name=%s, order=%s, programDbId=%s already exists in database", expUnit, DatasetLevel.EXP_UNIT, programDbId)); - } else if (levelResponse.getStatus().getCode() == 200) { - log.info(String.format("Level with name=%s, order=%s, programDbId=%s created in database", expUnit, DatasetLevel.EXP_UNIT, programDbId)); - } else { - log.error("Error saving experiment import: " + levelResponse.getStatus().getReason()); - throw new InternalServerException("Unable to create observation level: " + levelResponse.getStatus().getReason()); - } - } - private void updateStudyDependencyValues(PendingData pendingData, Map mappedBrAPIImport, String programKey) { // update location DbIds in studies for all distinct locations Map> trialByNameNoScope = pendingData.getTrialByNameNoScope(); diff --git a/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java b/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java index 18157fd11..0bba7f48a 100644 --- a/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java +++ b/src/main/java/org/breedinginsight/brapps/importer/services/processors/experiment/service/DatasetService.java @@ -18,6 +18,7 @@ package org.breedinginsight.brapps.importer.services.processors.experiment.service; import io.micronaut.context.annotation.Property; +import io.micronaut.http.server.exceptions.InternalServerException; import org.apache.commons.lang3.StringUtils; import org.brapi.client.v2.model.exceptions.ApiException; import org.brapi.v2.model.BrAPIExternalReference; @@ -26,14 +27,16 @@ import org.brapi.v2.model.core.BrAPITrial; import org.brapi.v2.model.core.request.BrAPIListNewRequest; import org.brapi.v2.model.core.response.BrAPIListDetails; +import org.brapi.v2.model.pheno.BrAPIObservationUnit; import org.brapi.v2.model.pheno.BrAPIObservationUnitHierarchyLevel; +import org.brapi.v2.model.pheno.BrAPIObservationUnitLevelRelationship; import org.breedinginsight.brapi.v2.constants.BrAPIAdditionalInfoFields; import org.breedinginsight.brapi.v2.dao.BrAPIListDAO; -import org.breedinginsight.brapi.v2.dao.BrAPIObservationLevelDAO; import org.breedinginsight.brapi.v2.services.BrAPIObservationLevelService; import org.breedinginsight.brapps.importer.model.response.ImportObjectState; import org.breedinginsight.brapps.importer.model.response.PendingImportObject; import org.breedinginsight.brapps.importer.services.ExternalReferenceSource; +import org.breedinginsight.model.BrAPIConstants; import org.breedinginsight.model.DatasetLevel; import org.breedinginsight.model.DatasetMetadata; import org.breedinginsight.model.Program; @@ -216,4 +219,42 @@ private String findLevelNameByNameAndOrder(Program program, return levelNameStreamResult.get(0).getLevelNameDbId(); } + + public void updateObservationUnitsWithLevelNameDbIds(List observationUnits, + Program program, + String brapiProgramDbId, + String expUnitName, + DatasetLevel levelOrder) throws ApiException { + Map levelNameDbIdByName = new HashMap<>(); + + String expLevelName = expUnitName.toLowerCase(); + + String existingLevelNameDbId = getOrCreateLevelNameForDataset(program, brapiProgramDbId, expLevelName, levelOrder); + levelNameDbIdByName.put(expLevelName, existingLevelNameDbId); + + List globalLevelNames = observationLevelService.getGlobalLevelNames(program); + + globalLevelNames.forEach(ouln -> levelNameDbIdByName.put(ouln.getLevelName(), ouln.getLevelNameDbId())); + + for (BrAPIObservationUnit observationUnit : observationUnits) { + + String positionLevelName = observationUnit.getObservationUnitPosition().getObservationLevel().getLevelName().toLowerCase(); + + observationUnit.getObservationUnitPosition().getObservationLevel().setLevelNameDbId(levelNameDbIdByName.get(positionLevelName)); + + for (BrAPIObservationUnitLevelRelationship lvlRelationship : observationUnit.getObservationUnitPosition().getObservationLevelRelationships()) { + if (lvlRelationship.getLevelName().equals(BrAPIConstants.BLOCK.getValue())) { + lvlRelationship.setLevelNameDbId(levelNameDbIdByName.get(lvlRelationship.getLevelName())); + } else if (lvlRelationship.getLevelName().equals(BrAPIConstants.REPLICATE.getValue())) { + lvlRelationship.setLevelNameDbId(levelNameDbIdByName.get(lvlRelationship.getLevelName())); + } else { + throw new InternalServerException(String.format("Level name [%s] detected in OU Level Relationship " + + "for experiment with Exp Unit name [%s]. This is unexpected and the new level " + + "name must be retrieved properly from BrAPI to insert its DbId into BrAPI request for proper creation and assignment.", + lvlRelationship.getLevelName(), expUnitName)); + } + } + } + + } }