/* * Licensed under the Apache License, Version 2.0 (the "License"); * * You may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. * * Contributions from 2013-2017 where performed either by US government * employees, or under US Veterans Health Administration contracts. * * US Veterans Health Administration contributions by government employees * are work of the U.S. Government and are not subject to copyright * protection in the United States. Portions contributed by government * employees are USGovWork (17USC ยง105). Not subject to copyright. * * Contribution by contractors to the US Veterans Health Administration * during this period are contractually contributed under the * Apache License, Version 2.0. * * See: https://www.usa.gov/government-works * * Contributions prior to 2013: * * Copyright (C) International Health Terminology Standards Development Organisation. * Licensed under the Apache License, Version 2.0. * */ package sh.isaac.convert.rxnorm.solor; //~--- JDK imports ------------------------------------------------------------ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; //~--- non-JDK imports -------------------------------------------------------- import javafx.util.Pair; import org.apache.maven.plugin.MojoExecutionException; import org.jvnet.hk2.annotations.Service; import sh.isaac.api.DataTarget; import sh.isaac.api.Get; import sh.isaac.api.LookupService; import sh.isaac.api.Util; import sh.isaac.api.chronicle.LatestVersion; import sh.isaac.api.commit.ChangeCheckerMode; import sh.isaac.api.component.concept.ConceptChronology; import sh.isaac.api.component.sememe.SememeChronology; import sh.isaac.api.component.sememe.version.DynamicSememe; import sh.isaac.api.component.sememe.version.LogicGraphSememe; import sh.isaac.api.component.sememe.version.MutableLogicGraphSememe; import sh.isaac.api.coordinate.EditCoordinate; import sh.isaac.api.index.IndexServiceBI; import sh.isaac.api.index.SearchResult; import sh.isaac.api.logic.LogicalExpression; import sh.isaac.api.logic.LogicalExpressionBuilder; import sh.isaac.api.logic.LogicalExpressionBuilderService; import sh.isaac.api.logic.NodeSemantic; import sh.isaac.api.logic.assertions.Assertion; import sh.isaac.api.task.TimedTask; import sh.isaac.converters.sharedUtils.ConsoleUtil; import sh.isaac.converters.sharedUtils.ConverterBaseMojo; import sh.isaac.converters.sharedUtils.stats.ConverterUUID; import sh.isaac.converters.sharedUtils.umlsUtils.rrf.REL; import sh.isaac.model.logic.LogicalExpressionOchreImpl; import sh.isaac.model.logic.node.AndNode; import sh.isaac.model.logic.node.LiteralNodeFloat; import sh.isaac.model.logic.node.internal.ConceptNodeWithSequences; import sh.isaac.model.logic.node.internal.FeatureNodeWithSequences; import sh.isaac.model.logic.node.internal.RoleNodeSomeWithSequences; import sh.isaac.rxnorm.rrf.RXNCONSO; import static sh.isaac.api.logic.LogicalExpressionBuilder.And; import static sh.isaac.api.logic.LogicalExpressionBuilder.ConceptAssertion; import static sh.isaac.api.logic.LogicalExpressionBuilder.Feature; import static sh.isaac.api.logic.LogicalExpressionBuilder.FloatLiteral; import static sh.isaac.api.logic.LogicalExpressionBuilder.NecessarySet; import static sh.isaac.api.logic.LogicalExpressionBuilder.SomeRole; //~--- classes ---------------------------------------------------------------- /** * The Class RxNormLogicGraphsMojo. * * @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a> */ @Service(name = "convert-rxnorm-to-solor-ibdf") public class RxNormLogicGraphsMojo extends ConverterBaseMojo { // int availStrengthCount = 0; // int newLogicGraphs = 0; // int modifiedLogicGraphs = 0; // int errors = 0; // // private PreparedStatement ingredSubstanceMergeCheck, scdProductMergeCheck, scdgToSCTIngredient; // // // private AtomicInteger ingredSubstanceMerge_ = new AtomicInteger(); // private AtomicInteger scdProductMerge_ = new AtomicInteger(); // private AtomicInteger ingredSubstanceMergeDupeFail_ = new AtomicInteger(); // private AtomicInteger scdProductMergeDupeFail_ = new AtomicInteger(); // private AtomicInteger convertedTradenameCount_ = new AtomicInteger(); // private AtomicInteger scdgToSCTIngredientCount_ = new AtomicInteger(); // private AtomicInteger doseFormMappedItemsCount_ = new AtomicInteger(); // // private HashMap<String, DoseForm> doseFormMappings_ = new HashMap<>(); //rxCUI to DoseForm // // // private HashMap<String, Optional<UUID>> mergeCache_ = new HashMap<>(); //rxcui to existing SCT concept // // // @Override // public void execute() throws MojoExecutionException // { // super.execute(); // getLog().info("RxNorm Logic Graph Processing Begins " + new Date().toString()); // // Optional<UUID> mergeOntoSCTConceptUUID = sctMergeCheck(rxCui); // // if (mergeOntoSCTConceptUUID.isPresent()) // { // //Add the UUID we would have generated // //TODO need to debug, this doesn't seem to be working. // importUtil_.addUUID(cuiConcept, cuiBasedUUID, ptSABs_.getProperty("RXNORM").getUUID()); // } // // ConsoleUtil.println("Reading dose form mapping file"); // DoseFormMapping.readDoseFormMapping().forEach(df -> // { // doseFormMappings_.put(df.rxcui, df); // }); // ConsoleUtil.println("Read " + doseFormMappings_.size() + " dose form mappings"); // // // //Make special units concepts // for (UNIT u : UNIT.values()) // { // if (!u.hasRealSCTConcept()) // { // importUtil_.createMetaDataConcept(u.getConceptUUID(), u.getFullName(), u.getFullName(), null, null, annotations.getPropertyTypeUUID(), null, null, dos_); // } // } // // ingredSubstanceMergeCheck = db_.getConnection().prepareStatement("SELECT DISTINCT r2.code FROM RXNCONSO r1, RXNCONSO r2" // + " WHERE r1.RXCUI = ?" // + " AND r1.TTY='IN' AND r2.rxcui = r1.rxcui AND r2.sab='" + sctSab_ + "'" // + " AND r2.STR like '% (substance)'"); // // scdProductMergeCheck = db_.getConnection().prepareStatement("SELECT DISTINCT r2.code FROM RXNCONSO r1, RXNCONSO r2" // + " WHERE r1.RXCUI = ?" // + " AND r1.TTY='SCD' AND r2.rxcui = r1.rxcui AND r2.sab='" + sctSab_ + "'" // + " AND r2.STR like '% (product)'"); // // //See doc in addCustomRelationships // scdgToSCTIngredient_ = db_.getConnection().prepareStatement("SELECT conso_2.code from RXNREL, RXNCONSO as conso_1, RXNCONSO as conso_2" // + " where RXCUI2=? and RELA='has_ingredient'" // + " and RXNREL.RXCUI1 = conso_1.RXCUI and conso_1.SAB = 'RXNORM' and conso_1.TTY='IN'" // + " and conso_2.RXCUI = RXNREL.RXCUI1 and conso_2.SAB='SNOMEDCT_US' and conso_2.STR like '%(product)' and conso_2.TTY = 'FN'"); // // // TimedTask<Void> task = new Worker(); // LookupService.getService(WorkExecutors.class).getExecutor().submit(task); // try // { // Util.addToTaskSetAndWaitTillDone(task); // } // catch (InterruptedException | ExecutionException e) // { // throw new MojoExecutionException("Failure", e); // } // // getLog().info("RxNorm Logic Graph Processing Ends " + new Date().toString()); // // getLog().info("Processed " + availStrengthCount + " strength annotations"); // getLog().info("Created " + newLogicGraphs + " new logic graphs"); // getLog().info("Modified " + modifiedLogicGraphs + " existing logic graphs"); // getLog().info("Had errors processing " + errors + " annotations"); // } // // private class Worker extends TimedTask<Void> // { // @SuppressWarnings("deprecation") // @Override // protected Void call() throws Exception // { // getLog().info("Processing RxNorm Concrete Domains"); // updateTitle("Processing RxNorm Concrete Domains"); // updateMessage("Building Logic Graphs"); // updateProgress(1, 3); // // EditCoordinate ec = Get.configurationService().getDefaultEditCoordinate(); // //TODO find constant // ConceptChronology<?> unitConcept = Get.conceptService().getConcept(UUID.fromString("17055d89-84e3-3e12-9fb1-1bc4c75a122d")); //Units (attribute) // // LogicalExpressionBuilderService expressionBuilderService = LookupService.getService(LogicalExpressionBuilderService.class); // // //Need to gather per concept, as some concepts have multiple instances of this assemblage // // HashMap<Integer, ArrayList<String>> entries = new HashMap<Integer, ArrayList<String>>(); //con nid to values // Get.sememeService().getSememesFromAssemblage(findAssemblageNid("RXN_AVAILABLE_STRENGTH")).forEach(sememe -> // { // availStrengthCount++; // try // { // @SuppressWarnings({ "rawtypes", "unchecked" }) // Optional<LatestVersion<DynamicSememe>> ds = ((SememeChronology)sememe).getLatestVersion(DynamicSememe.class, Get.configurationService().getDefaultStampCoordinate()); // if (ds.isPresent()) // { // @SuppressWarnings("rawtypes") // DynamicSememe dsv = ds.get().value(); // int descriptionSememe = dsv.getReferencedComponentNid(); // int conceptNid = Get.sememeService().getSememe(descriptionSememe).getReferencedComponentNid(); // String value = dsv.getData()[0].getDataObject().toString(); // String[] multipart = value.split(" / "); // // ArrayList<String> itemEntries = entries.get(conceptNid); // if (itemEntries == null) // { // itemEntries = new ArrayList<>(); // entries.put(conceptNid, itemEntries); // } // for (String s : multipart) // { // itemEntries.add(s); // } // } // } // catch (Exception e) // { // errors++; // getLog().error("Failed reading " + sememe, e); // } // // }); // // for (Entry<Integer, ArrayList<String>> item : entries.entrySet()) // { // try // { // Optional<LatestVersion<? extends LogicalExpression>> existingLogicExpr = Get.logicService().getLogicalExpression(item.getKey(), // LogicCoordinates.getStandardElProfile().getStatedAssemblageSequence(), // Get.configurationService().getDefaultStampCoordinate()); // // LogicalExpression existing = null; // if (existingLogicExpr.isPresent()) // { // existing = existingLogicExpr.get().value(); // } // // LogicalExpressionBuilder leb = expressionBuilderService.getLogicalExpressionBuilder(); // ArrayList<Assertion> assertions = new ArrayList<>(); // // for (String part : item.getValue()) // { // if (part.length() > 0) // { // Pair<Float, UNIT> parsed = parseSpecifics(part); // // if (existing == null) // { // assertions.add(SomeRole(IsaacMetadataAuxiliaryBinding.ROLE_GROUP, And( // Feature(IsaacMetadataAuxiliaryBinding.HAS_STRENGTH, FloatLiteral(parsed.getKey(), leb)), // SomeRole(unitConcept, // ConceptAssertion(Get.conceptService().getConcept(parsed.getValue().getConceptUUID()), leb))))); // } // else // { // //We can't use the builder, because there is currently no way to combine the logic graph from this builder, with // //the existing logic graph. So, instead, manually build these nodes into the preexisting logic graph, below. // // boolean found = false; // for (Node n : existing.getRoot().getChildren()) // { // if (n.getNodeSemantic() == NodeSemantic.NECESSARY_SET) // { // if (n.getChildren().length == 1 && n.getChildren()[0].getNodeSemantic() == NodeSemantic.AND) // { // FeatureNodeWithSequences feature = new FeatureNodeWithSequences( // (LogicalExpressionOchreImpl)existing, // IsaacMetadataAuxiliaryBinding.HAS_STRENGTH.getConceptSequence(), // new LiteralNodeFloat((LogicalExpressionOchreImpl)existing, parsed.getKey().floatValue())); // // RoleNodeSomeWithSequences unitRole = new RoleNodeSomeWithSequences((LogicalExpressionOchreImpl)existing, // unitConcept.getConceptSequence(), // new ConceptNodeWithSequences((LogicalExpressionOchreImpl)existing, // Get.identifierService().getConceptSequenceForUuids(parsed.getValue().getConceptUUID()))); // // AndNode andNode = new AndNode((LogicalExpressionOchreImpl)existing, feature, unitRole); // // RoleNodeSomeWithSequences groupingRole = new RoleNodeSomeWithSequences((LogicalExpressionOchreImpl)existing, // IsaacMetadataAuxiliaryBinding.ROLE_GROUP.getConceptSequence(), andNode); // // n.getChildren()[0].addChildren(groupingRole); // found = true; // break; // } // } // } // // if (!found) // { // throw new RuntimeException("oops! - couldn't merge on necessary"); // } // } // } // } // // if (existing != null) // { // //I should find one and only 1, as we read it above, from the logic expression service, and it validates. // SememeChronology<?> sc = Get.sememeService().getSememesForComponentFromAssemblage(item.getKey(), // LogicCoordinates.getStandardElProfile().getStatedAssemblageSequence()).findFirst().get(); // // @SuppressWarnings("unchecked") // MutableLogicGraphSememe<?> mls = ((SememeChronology<LogicGraphSememe<?>>)sc).createMutableVersion(MutableLogicGraphSememe.class, // sh.isaac.api.State.ACTIVE, // ec); // // mls.setGraphData(existing.getData(DataTarget.INTERNAL)); // // Get.commitService().addUncommitted(sc); // modifiedLogicGraphs++; // } // else // { // NecessarySet(And(assertions.toArray(new Assertion[0]))); // LogicalExpression le = leb.build(); // Get.sememeBuilderService().getLogicalExpressionSememeBuilder(le, item.getKey(), // LogicCoordinates.getStandardElProfile().getStatedAssemblageSequence()).build(ec, ChangeCheckerMode.ACTIVE); // newLogicGraphs++; // } // // } // catch (Exception e) // { // errors++; // getLog().error("Failed creating logic graph for concept id " + item.getKey(), e); // } // } // // getLog().info("Committing"); // updateMessage("Committing"); // updateProgress(2, 3); // // Get.commitService().commit("Adding RxNorm Concrete Domains").get(); // // getLog().info("Done"); // updateMessage("Done"); // updateProgress(3, 3); // // return null; // // ingredSubstanceMergeCheck.close(); // scdProductMergeCheck.close(); // scdgToSCTIngredient_.close(); // // // ConsoleUtil.println("Ingredient / Substance merge concepts: " + ingredSubstanceMerge_.get()); // ConsoleUtil.println("Ingredient / Substance merge fail due to duplicates: " + ingredSubstanceMergeDupeFail_.get()); // ConsoleUtil.println("SCD / Product merge concepts: " + scdProductMerge_.get()); // ConsoleUtil.println("SCD / Product merge fail due to duplicates: " + scdProductMergeDupeFail_.get()); // ConsoleUtil.println("Dose Form merge concepts: " + doseFormMappedItemsCount_.get()); // ConsoleUtil.println("Converted tradename of relationships: " + convertedTradenameCount_.get()); // ConsoleUtil.println("Added is a relationships for scdg to ingredient: " + scdgToSCTIngredientCount_.get()); // } // // } // // private void addCustomRelationships(String rxCui, TtkConceptChronicle cuiConcept, ArrayList<RXNCONSO> conceptData) throws SQLException // { // /** // * Rule 2 is about: // * Create is_a relationship from RxNorm SCDG to SNOMED [ingredient] product concept, // * WHERE no SNOMED equivalent exists per RxNCONSO file for the SCDG. // * // * (a) from RxNCONSO identify the SCDG that do NOT have an SCT equivalent concept in the product hierarchy. // * (b) from RxNREL identify the IN targets of SCDG (found in (a)) where "has_ingredient" relationship exists. // * (c) in RxNCONSO find the SCT product equivalent of the RxCUI TTY=IN found in (a); OR in RxNCONSO find the SCT substance // * equivalent of the RxCUI TTY=IN found in (a), then find the SCT product using the SCT "active ingredient_of" (which is the inverse of // * "has_active_ingredient" relationship) between substance and product. // * (d) Create a SOLOR is_a relationship between the SCDG (with no SCT equivalent in RxNCONSO) and the SCT [ingredient type] product concept. // * // */ // // HashSet<String> uniqueTTYs = new HashSet<String>(); // HashSet<String> uniqueSABS = new HashSet<String>(); // for (RXNCONSO x : conceptData) // { // uniqueTTYs.add(x.tty); // uniqueSABS.add(x.sab); // } // // //covers (a) // if (uniqueTTYs.contains("SCDG") && !uniqueSABS.contains(sctSab_)) // { // scdgToSCTIngredient_.setString(1, rxCui); // ResultSet rs = scdgToSCTIngredient_.executeQuery(); // while (rs.next()) // { // Long sctid = Long.parseLong(rs.getString("code")); // // UUID target = sctIDToUUID_.get(sctid); // // if (target == null) // { // throw new RuntimeException("Unexpected - missing target for sctid " + sctid + " on cui " + rxCui); // } // // importUtil_.addRelationship(cuiConcept, target); // scdgToSCTIngredientCount_.incrementAndGet(); // } // } // } // // // /** // * from RxNCONSO find all RxCUI with TTY = IN and SAB = SNOMED CT_US with STR = "*(substance)" // * - that is, all RxCUI with TTY = IN and there is an equivalent SNOMEDCT_US concept in the Substance hierarchy // * @return the UUID from the snomed concept that is our merge target (if any) // */ // private Optional<UUID> sctMergeCheck(String rxCui) throws SQLException // { // if (sctIDToUUID_ == null) // { // return Optional.empty(); // } // // if (mergeCache_.get(rxCui) != null) // { // return mergeCache_.get(rxCui); // } // // UUID snoConUUID = null; // // if (doseFormMappings_ != null) // { // DoseForm df = doseFormMappings_.get(rxCui); // if (df != null) // { // Long id = Long.parseLong(df.sctid); // snoConUUID = sctIDToUUID_.get(id); // if (snoConUUID != null) // { // doseFormMappedItemsCount_.incrementAndGet(); // mergeCache_.put(rxCui, Optional.of(snoConUUID)); // return Optional.of(snoConUUID); // } // } // } // // ingredSubstanceMergeCheck.setString(1, rxCui); // // ResultSet rs = ingredSubstanceMergeCheck.executeQuery(); // while (rs.next()) // { // long code = Long.parseLong(rs.getString(1)); // UUID found = sctIDToUUID_.get(code); // if (found != null) // { // if (snoConUUID == null || found.equals(snoConUUID)) // { // if (snoConUUID == null) // { // ingredSubstanceMerge_.incrementAndGet(); // } // snoConUUID = found; // } // else // { // ingredSubstanceMergeDupeFail_.incrementAndGet(); // ConsoleUtil.printErrorln("Can't merge ingredient / substance to multiple Snomed concepts: " + rxCui); // } // } // else // { // ConsoleUtil.printErrorln("Can't find UUID for SCTID " + code); // } // } // // rs.close(); // // scdProductMergeCheck.setString(1, rxCui); // rs = scdProductMergeCheck.executeQuery(); // boolean passOne = true; // while (rs.next()) // { // if (passOne && snoConUUID != null) // { // ConsoleUtil.printErrorln("Cant merge to substance and to product at the same time! RXCUI " + rxCui); // break; // } // passOne = false; // long code = Long.parseLong(rs.getString(1)); // UUID found = sctIDToUUID_.get(code); // if (found != null) // { // if (snoConUUID == null || found.equals(snoConUUID)) // { // if (snoConUUID == null) // { // scdProductMerge_.incrementAndGet(); // } // snoConUUID = found; // } // else // { // scdProductMergeDupeFail_.incrementAndGet(); // ConsoleUtil.printErrorln("Can't merge SCD / product to multiple Snomed concepts: " + rxCui); // } // } // else // { // ConsoleUtil.printErrorln("Can't find UUID for SCTID " + code); // } // } // // mergeCache_.put(rxCui, Optional.ofNullable(snoConUUID)); // return Optional.ofNullable(snoConUUID); // } // // // private boolean handleAsRel(REL relationship) throws SQLException // { // /** // * Rule 1 is about: // * Convert RxNorm Tradename_of to is_a between RxNorm SBD and RxNorm SCD // * tradename_of is created as both an association and a relationship - in the rel form, it maps to is_a. // */ // if (hasTTYType(relationship.getSourceCUI(), "SBD") && hasTTYType(relationship.getTargetCUI(), "SCD")) // { // convertedTradenameCount_.incrementAndGet(); // return true; // } // // return false; // } // // // private Pair<Float, UNIT> parseSpecifics(String value) // { // value = removeParenStuff(value).trim(); // String[] parts = value.split(" "); // if (parts.length == 1) // { // return new Pair<>(1.0f, UNIT.parse(parts[0])); // } // else if (parts.length == 2) // { // return new Pair<>(Float.parseFloat(parts[0]), UNIT.parse(parts[1])); // } // throw new RuntimeException("Wrong number of parts in '" + value + "'"); // } // // private String removeParenStuff(String input) // { // if (input.contains("(") && input.contains(")")) // { // int i = input.indexOf("("); // int z = input.lastIndexOf(")"); // if (z < i) // { // throw new RuntimeException("oops"); // } // return input.substring(0, i) + input.substring(z + 1, input.length()); // } // return input; // } // // private boolean hasTTYType(String cui, String tty) throws SQLException // { // hasTTYType_.setString(1, cui); // hasTTYType_.setString(2, tty); // ResultSet rs = hasTTYType_.executeQuery(); // if (rs.next()) // { // return rs.getInt("count") > 0; // } // throw new RuntimeException("Unexpected"); // } // // private int findAssemblageNid(String uniqueName) // { // IndexServiceBI si = LookupService.get().getService(IndexServiceBI.class, "description indexer"); // if (si != null) // { // //force the prefix algorithm, and add a trailing space - quickest way to do an exact-match type of search // List<SearchResult> result = si.query(uniqueName + " ", true, // null, 5, Long.MIN_VALUE); // if (result.size() > 0) // { // return Get.sememeService().getSememe(result.get(0).getNid()).getReferencedComponentNid(); // } // } // throw new RuntimeException("Can't find assemblage nid with the name " + uniqueName); // } @Override protected ConverterUUID.NAMESPACE getNamespace() { return ConverterUUID.NAMESPACE.RXNORM; } }