More improvements to ConceptMap Infrastructure

This commit is contained in:
Grahame Grieve 2024-03-01 16:40:41 +11:00
parent 7fb2b1ea00
commit a3fb1457a0
2 changed files with 55 additions and 34 deletions

View File

@ -39,8 +39,9 @@ public class ConceptMapRenderer extends TerminologyRenderer {
} }
public interface IConceptMapInformationProvider { public interface IMultiMapRendererAdvisor {
public List<Coding> getMembers(String uri); public List<Coding> getMembers(String uri);
public boolean describeMap(ConceptMap map, XhtmlNode x);
public String getLink(String system, String code); public String getLink(String system, String code);
} }
@ -723,11 +724,11 @@ public class ConceptMapRenderer extends TerminologyRenderer {
return null; return null;
} }
public static XhtmlNode renderMultipleMaps(String start, List<ConceptMap> maps, IConceptMapInformationProvider linker, RenderMultiRowSortPolicy sort) { public static XhtmlNode renderMultipleMaps(String start, String startLink, List<ConceptMap> maps, IMultiMapRendererAdvisor advisor, RenderMultiRowSortPolicy sort) {
// 1+1 column for each provided map // 1+1 column for each provided map
List<MultipleMappingRow> rowSets = new ArrayList<>(); List<MultipleMappingRow> rowSets = new ArrayList<>();
for (int i = 0; i < maps.size(); i++) { for (int i = 0; i < maps.size(); i++) {
populateRows(rowSets, maps.get(i), i, linker); populateRows(rowSets, maps.get(i), i, advisor);
} }
collateRows(rowSets); collateRows(rowSets);
if (sort != RenderMultiRowSortPolicy.UNSORTED) { if (sort != RenderMultiRowSortPolicy.UNSORTED) {
@ -736,16 +737,19 @@ public class ConceptMapRenderer extends TerminologyRenderer {
XhtmlNode div = new XhtmlNode(NodeType.Element, "div"); XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
XhtmlNode tbl = div.table("none").style("text-align: left; border-spacing: 0; padding: 5px"); XhtmlNode tbl = div.table("none").style("text-align: left; border-spacing: 0; padding: 5px");
XhtmlNode tr = tbl.tr(); XhtmlNode tr = tbl.tr();
styleCell(tr.td(), false, true, 5).b().tx(start); styleCell(tr.td(), false, true, 5).b().ahOrNot(startLink).tx(start);
for (ConceptMap map : maps) { for (ConceptMap map : maps) {
if (map.hasWebPath()) { XhtmlNode td = styleCell(tr.td(), false, true, 5).colspan(2);
styleCell(tr.td(), false, true, 5).colspan(2).b().ah(map.getWebPath(), map.getVersionedUrl()).tx(map.present()); if (!advisor.describeMap(map, td)) {
} else { if (map.hasWebPath()) {
styleCell(tr.td(), false, true, 5).colspan(2).b().tx(map.present()); td.b().ah(map.getWebPath(), map.getVersionedUrl()).tx(map.present());
} else {
td.b().tx(map.present());
}
} }
} }
for (MultipleMappingRow row : rowSets) { for (MultipleMappingRow row : rowSets) {
renderMultiRow(tbl, row, maps, linker); renderMultiRow(tbl, row, maps, advisor);
} }
return div; return div;
} }
@ -765,7 +769,7 @@ public class ConceptMapRenderer extends TerminologyRenderer {
rowSets.removeAll(toDelete); rowSets.removeAll(toDelete);
} }
private static void renderMultiRow(XhtmlNode tbl, MultipleMappingRow rows, List<ConceptMap> maps, IConceptMapInformationProvider linker) { private static void renderMultiRow(XhtmlNode tbl, MultipleMappingRow rows, List<ConceptMap> maps, IMultiMapRendererAdvisor advisor) {
int rowCounter = 0; int rowCounter = 0;
for (MultipleMappingRowItem row : rows.rowSets) { for (MultipleMappingRowItem row : rows.rowSets) {
XhtmlNode tr = tbl.tr(); XhtmlNode tr = tbl.tr();
@ -787,7 +791,7 @@ public class ConceptMapRenderer extends TerminologyRenderer {
if (cell.code == null) { if (cell.code == null) {
styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee"); styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee");
} else { } else {
String link = linker.getLink(cell.system, cell.code); String link = advisor.getLink(cell.system, cell.code);
XhtmlNode x = null; XhtmlNode x = null;
if (link != null) { if (link != null) {
x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link); x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link);
@ -835,7 +839,7 @@ public class ConceptMapRenderer extends TerminologyRenderer {
if (cell.code == null) { if (cell.code == null) {
styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee"); styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee");
} else { } else {
String link = linker.getLink(cell.system, cell.code); String link = advisor.getLink(cell.system, cell.code);
XhtmlNode x = null; XhtmlNode x = null;
if (link != null) { if (link != null) {
x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link); x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link);
@ -868,10 +872,10 @@ public class ConceptMapRenderer extends TerminologyRenderer {
return td; return td;
} }
private static void populateRows(List<MultipleMappingRow> rowSets, ConceptMap map, int i, IConceptMapInformationProvider linker) { private static void populateRows(List<MultipleMappingRow> rowSets, ConceptMap map, int i, IMultiMapRendererAdvisor advisor) {
// if we can resolve the value set, we create entries for it // if we can resolve the value set, we create entries for it
if (map.hasSourceScope()) { if (map.hasSourceScope()) {
List<Coding> codings = linker.getMembers(map.getSourceScope().primitiveValue()); List<Coding> codings = advisor.getMembers(map.getSourceScope().primitiveValue());
if (codings != null) { if (codings != null) {
for (Coding c : codings) { for (Coding c : codings) {
MultipleMappingRow row = i == 0 ? null : findExistingRowBySource(rowSets, c.getSystem(), c.getCode(), i); MultipleMappingRow row = i == 0 ? null : findExistingRowBySource(rowSets, c.getSystem(), c.getCode(), i);
@ -931,7 +935,7 @@ public class ConceptMapRenderer extends TerminologyRenderer {
} }
} }
if (map.hasTargetScope()) { if (map.hasTargetScope()) {
List<Coding> codings = linker.getMembers(map.getTargetScope().primitiveValue()); List<Coding> codings = advisor.getMembers(map.getTargetScope().primitiveValue());
if (codings != null) { if (codings != null) {
for (Coding c : codings) { for (Coding c : codings) {
MultipleMappingRow row = findExistingRowByTarget(rowSets, c.getSystem(), c.getCode(), i); MultipleMappingRow row = findExistingRowByTarget(rowSets, c.getSystem(), c.getCode(), i);

View File

@ -4,8 +4,10 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CanonicalType;
@ -321,7 +323,6 @@ public class ConceptMapUtilities {
} }
public static boolean checkReciprocal(ConceptMap left, ConceptMap right, List<String> issues) { public static boolean checkReciprocal(ConceptMap left, ConceptMap right, List<String> issues) {
boolean altered = false;
if (!Base.compareDeep(left.getTargetScope(), right.getSourceScope(), true)) { if (!Base.compareDeep(left.getTargetScope(), right.getSourceScope(), true)) {
issues.add("scopes are not reciprocal: "+left.getTargetScope()+" vs "+right.getSourceScope()); issues.add("scopes are not reciprocal: "+left.getTargetScope()+" vs "+right.getSourceScope());
} }
@ -344,8 +345,7 @@ public class ConceptMapUtilities {
switch (tgtL.getRelationship()) { switch (tgtL.getRelationship()) {
case EQUIVALENT: case EQUIVALENT:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.EQUIVALENT); issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) { if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) {
issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -354,18 +354,16 @@ public class ConceptMapUtilities {
break; break;
case RELATEDTO: case RELATEDTO:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.RELATEDTO); issues.add("Left map says that "+srcL.getCode()+" is related to "+tgtL.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) { if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) {
issues.add("Left map says that "+srcL.getCode()+" is equivalent to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Left map says that "+srcL.getCode()+" is related to "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
} }
} }
break; break;
case SOURCEISBROADERTHANTARGET: case SOURCEISBROADERTHANTARGET:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.SOURCEISNARROWERTHANTARGET); issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) { if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Left map says that "+srcL.getCode()+" is broader than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -374,8 +372,7 @@ public class ConceptMapUtilities {
break; break;
case SOURCEISNARROWERTHANTARGET: case SOURCEISNARROWERTHANTARGET:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.SOURCEISBROADERTHANTARGET); issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) { if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) {
issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Left map says that "+srcL.getCode()+" is narrower than "+tgtL.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -410,8 +407,7 @@ public class ConceptMapUtilities {
switch (tgtR.getRelationship()) { switch (tgtR.getRelationship()) {
case EQUIVALENT: case EQUIVALENT:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.EQUIVALENT); issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) { if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT) {
issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -420,8 +416,7 @@ public class ConceptMapUtilities {
break; break;
case RELATEDTO: case RELATEDTO:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.RELATEDTO); issues.add("Right map says that "+srcR.getCode()+" is related to "+tgtR.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) { if (pair.tgt.getRelationship() != ConceptMapRelationship.EQUIVALENT && pair.tgt.getRelationship() != ConceptMapRelationship.RELATEDTO) {
issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Right map says that "+srcR.getCode()+" is equivalent to "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -430,8 +425,7 @@ public class ConceptMapUtilities {
break; break;
case SOURCEISBROADERTHANTARGET: case SOURCEISBROADERTHANTARGET:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.SOURCEISNARROWERTHANTARGET); issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) { if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Right map says that "+srcR.getCode()+" is broader than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -440,8 +434,7 @@ public class ConceptMapUtilities {
break; break;
case SOURCEISNARROWERTHANTARGET: case SOURCEISNARROWERTHANTARGET:
if (pairs.isEmpty()) { if (pairs.isEmpty()) {
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.SOURCEISBROADERTHANTARGET); issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but there's no reverse relationship");
altered = true;
} else for (ElementMappingPair pair : pairs) { } else for (ElementMappingPair pair : pairs) {
if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) { if (pair.tgt.getRelationship() != ConceptMapRelationship.SOURCEISBROADERTHANTARGET) {
issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode()); issues.add("Right map says that "+srcR.getCode()+" is narrower than "+tgtR.getCode()+" but the reverse relationship has type "+pair.tgt.getRelationship().toCode());
@ -470,7 +463,7 @@ public class ConceptMapUtilities {
} }
} }
} }
return altered; return false;
} }
private static List<ElementMappingPair> getMappings(ConceptMapGroupComponent g, String source, String target) { private static List<ElementMappingPair> getMappings(ConceptMapGroupComponent g, String source, String target) {
@ -530,4 +523,28 @@ public class ConceptMapUtilities {
return i; return i;
} }
public static Set<String> listCodesWithNoMappings(Set<String> set, ConceptMap map) {
Set<String> res = new HashSet<>();
for (String s : set) {
if (s != null) {
boolean found = false;
for (ConceptMapGroupComponent grp : map.getGroup()) {
for (SourceElementComponent src : grp.getElement()) {
if (s.equals(src.getCode())) {
for (TargetElementComponent tgt : src.getTarget()) {
if (tgt.getRelationship() == ConceptMapRelationship.RELATEDTO || tgt.getRelationship() == ConceptMapRelationship.EQUIVALENT || tgt.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
found = true;
}
}
}
}
}
if (!found) {
res.add(s);
}
}
}
return res;
}
} }