work on ConceptMap infrastructure for cross-version analysis
This commit is contained in:
parent
3a27e012be
commit
c2756a24ef
|
@ -107,7 +107,7 @@ public abstract class FormatUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidId(String tail) {
|
public static boolean isValidId(String tail) {
|
||||||
return tail.matches(ID_REGEX);
|
return tail == null ? false : tail.matches(ID_REGEX);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String makeId(String candidate) {
|
public static String makeId(String candidate) {
|
||||||
|
|
|
@ -596,6 +596,9 @@ public abstract class CanonicalResource extends DomainResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String present() {
|
public String present() {
|
||||||
|
if (hasUserData("presentation")) {
|
||||||
|
return getUserString("presentation");
|
||||||
|
}
|
||||||
if (hasTitle())
|
if (hasTitle())
|
||||||
return getTitle();
|
return getTitle();
|
||||||
if (hasName())
|
if (hasName())
|
||||||
|
|
|
@ -1777,10 +1777,23 @@ public class ConceptMap extends MetadataResource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SourceElementComponent getOrAddElement(String code) {
|
||||||
|
for (SourceElementComponent e : getElement()) {
|
||||||
|
if (code.equals(e.getCode())) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addElement().setCode(code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Block()
|
@Block()
|
||||||
public static class SourceElementComponent extends BackboneElement implements IBaseBackboneElement {
|
public static class SourceElementComponent extends BackboneElement implements IBaseBackboneElement {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SourceElementComponent [code=" + code + ", display=" + display + ", noMap=" + noMap + "]";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identity (code or path) or the element/item being mapped.
|
* Identity (code or path) or the element/item being mapped.
|
||||||
*/
|
*/
|
||||||
|
@ -2264,6 +2277,11 @@ public class ConceptMap extends MetadataResource {
|
||||||
|
|
||||||
@Block()
|
@Block()
|
||||||
public static class TargetElementComponent extends BackboneElement implements IBaseBackboneElement {
|
public static class TargetElementComponent extends BackboneElement implements IBaseBackboneElement {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TargetElementComponent [code=" + code + ", relationship=" + relationship + "]";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identity (code or path) or the element/item that the map refers to.
|
* Identity (code or path) or the element/item that the map refers to.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3829,6 +3829,17 @@ public class Enumerations {
|
||||||
default: return "?";
|
default: return "?";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public String getSymbol() {
|
||||||
|
switch (this) {
|
||||||
|
case RELATEDTO: return "-";
|
||||||
|
case EQUIVALENT: return "=";
|
||||||
|
case SOURCEISNARROWERTHANTARGET: return "<";
|
||||||
|
case SOURCEISBROADERTHANTARGET: return ">";
|
||||||
|
case NOTRELATEDTO: return "!=";
|
||||||
|
case NULL: return null;
|
||||||
|
default: return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConceptMapRelationshipEnumFactory implements EnumFactory<ConceptMapRelationship> {
|
public static class ConceptMapRelationshipEnumFactory implements EnumFactory<ConceptMapRelationship> {
|
||||||
|
|
|
@ -1150,6 +1150,15 @@ public class ValueSet extends MetadataResource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasConcept(String code) {
|
||||||
|
for (ConceptReferenceComponent c : getConcept()) {
|
||||||
|
if (code.equals(c.getCode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Block()
|
@Block()
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.hl7.fhir.r5.renderers;
|
package org.hl7.fhir.r5.renderers;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -9,8 +12,10 @@ import java.util.Map;
|
||||||
import org.hl7.fhir.exceptions.DefinitionException;
|
import org.hl7.fhir.exceptions.DefinitionException;
|
||||||
import org.hl7.fhir.exceptions.FHIRFormatError;
|
import org.hl7.fhir.exceptions.FHIRFormatError;
|
||||||
import org.hl7.fhir.r5.model.CodeSystem;
|
import org.hl7.fhir.r5.model.CodeSystem;
|
||||||
|
import org.hl7.fhir.r5.model.Coding;
|
||||||
import org.hl7.fhir.r5.model.ConceptMap;
|
import org.hl7.fhir.r5.model.ConceptMap;
|
||||||
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
|
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
|
||||||
|
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode;
|
||||||
import org.hl7.fhir.r5.model.ConceptMap.MappingPropertyComponent;
|
import org.hl7.fhir.r5.model.ConceptMap.MappingPropertyComponent;
|
||||||
import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent;
|
import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent;
|
||||||
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
||||||
|
@ -19,14 +24,263 @@ import org.hl7.fhir.r5.model.ContactDetail;
|
||||||
import org.hl7.fhir.r5.model.ContactPoint;
|
import org.hl7.fhir.r5.model.ContactPoint;
|
||||||
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
||||||
import org.hl7.fhir.r5.model.Resource;
|
import org.hl7.fhir.r5.model.Resource;
|
||||||
|
import org.hl7.fhir.r5.renderers.ConceptMapRenderer.MultipleMappingRow;
|
||||||
|
import org.hl7.fhir.r5.renderers.ConceptMapRenderer.MultipleMappingRowSorter;
|
||||||
|
import org.hl7.fhir.r5.renderers.ConceptMapRenderer.RenderMultiRowSortPolicy;
|
||||||
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
|
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
|
||||||
import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
|
import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
|
||||||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||||
|
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||||
|
import org.hl7.fhir.utilities.DebugUtilities;
|
||||||
import org.hl7.fhir.utilities.Utilities;
|
import org.hl7.fhir.utilities.Utilities;
|
||||||
|
import org.hl7.fhir.utilities.VersionUtilities;
|
||||||
|
import org.hl7.fhir.utilities.xhtml.NodeType;
|
||||||
|
import org.hl7.fhir.utilities.xhtml.XhtmlFluent;
|
||||||
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
|
||||||
|
|
||||||
public class ConceptMapRenderer extends TerminologyRenderer {
|
public class ConceptMapRenderer extends TerminologyRenderer {
|
||||||
|
|
||||||
|
public enum RenderMultiRowSortPolicy {
|
||||||
|
UNSORTED, FIRST_COL, LAST_COL
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IConceptMapInformationProvider {
|
||||||
|
public List<Coding> getMembers(String uri);
|
||||||
|
public String getLink(String system, String code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MultipleMappingRowSorter implements Comparator<MultipleMappingRow> {
|
||||||
|
|
||||||
|
private boolean first;
|
||||||
|
|
||||||
|
protected MultipleMappingRowSorter(boolean first) {
|
||||||
|
super();
|
||||||
|
this.first = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(MultipleMappingRow o1, MultipleMappingRow o2) {
|
||||||
|
String s1 = first ? o1.firstCode() : o1.lastCode();
|
||||||
|
String s2 = first ? o2.firstCode() : o2.lastCode();
|
||||||
|
return s1.compareTo(s2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cell {
|
||||||
|
|
||||||
|
private String system;
|
||||||
|
private String code;
|
||||||
|
private String display;
|
||||||
|
private String relationship;
|
||||||
|
private String relComment;
|
||||||
|
public boolean renderedRel;
|
||||||
|
public boolean renderedCode;
|
||||||
|
private Cell clone;
|
||||||
|
|
||||||
|
protected Cell() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell(String system, String code, String display) {
|
||||||
|
this.system = system;
|
||||||
|
this.code = code;
|
||||||
|
this.display = display;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell(String system, String code, String relationship, String comment) {
|
||||||
|
this.system = system;
|
||||||
|
this.code = code;
|
||||||
|
this.relationship = relationship;
|
||||||
|
this.relComment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(String system, String code) {
|
||||||
|
return (system != null && system.equals(this.system)) && (code != null && code.equals(this.code));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String present() {
|
||||||
|
if (system == null) {
|
||||||
|
return code;
|
||||||
|
} else {
|
||||||
|
return code; //+(clone == null ? "" : " (@"+clone.code+")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell copy(boolean clone) {
|
||||||
|
Cell res = new Cell();
|
||||||
|
res.system = system;
|
||||||
|
res.code = code;
|
||||||
|
res.display = display;
|
||||||
|
res.relationship = relationship;
|
||||||
|
res.relComment = relComment;
|
||||||
|
res.renderedRel = renderedRel;
|
||||||
|
res.renderedCode = renderedCode;
|
||||||
|
if (clone) {
|
||||||
|
res.clone = this;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return relationship+" "+system + "#" + code + " \"" + display + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MultipleMappingRowItem {
|
||||||
|
List<Cell> cells = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||||
|
for (Cell cell : cells) {
|
||||||
|
if (cell.relationship != null) {
|
||||||
|
b.append(cell.relationship+cell.code);
|
||||||
|
} else {
|
||||||
|
b.append(cell.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MultipleMappingRow {
|
||||||
|
private List<MultipleMappingRowItem> rowSets = new ArrayList<>();
|
||||||
|
private MultipleMappingRow stickySource;
|
||||||
|
|
||||||
|
public MultipleMappingRow(int i, String system, String code, String display) {
|
||||||
|
MultipleMappingRowItem row = new MultipleMappingRowItem();
|
||||||
|
rowSets.add(row);
|
||||||
|
for (int c = 0; c < i; c++) {
|
||||||
|
row.cells.add(new Cell()); // blank cell spaces
|
||||||
|
}
|
||||||
|
row.cells.add(new Cell(system, code, display));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public MultipleMappingRow(MultipleMappingRow stickySource) {
|
||||||
|
this.stickySource = stickySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||||
|
for (MultipleMappingRowItem rowSet : rowSets) {
|
||||||
|
b.append(""+rowSet.cells.size());
|
||||||
|
}
|
||||||
|
CommaSeparatedStringBuilder b2 = new CommaSeparatedStringBuilder(";");
|
||||||
|
for (MultipleMappingRowItem rowSet : rowSets) {
|
||||||
|
b2.append(rowSet.toString());
|
||||||
|
}
|
||||||
|
return ""+rowSets.size()+" ["+b.toString()+"] ("+b2.toString()+")";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String lastCode() {
|
||||||
|
MultipleMappingRowItem first = rowSets.get(0);
|
||||||
|
for (int i = first.cells.size()-1; i >= 0; i--) {
|
||||||
|
if (first.cells.get(i).code != null) {
|
||||||
|
return first.cells.get(i).code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String firstCode() {
|
||||||
|
MultipleMappingRowItem first = rowSets.get(0);
|
||||||
|
for (int i = 0; i < first.cells.size(); i++) {
|
||||||
|
if (first.cells.get(i).code != null) {
|
||||||
|
return first.cells.get(i).code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSource(MultipleMappingRow sourceRow, List<MultipleMappingRow> rowList, ConceptMapRelationship relationship, String comment) {
|
||||||
|
// we already have a row, and we're going to collapse the rows on sourceRow into here, and add a matching terminus
|
||||||
|
assert sourceRow.rowSets.get(0).cells.size() == rowSets.get(0).cells.size()-1;
|
||||||
|
rowList.remove(sourceRow);
|
||||||
|
Cell template = rowSets.get(0).cells.get(rowSets.get(0).cells.size()-1);
|
||||||
|
for (MultipleMappingRowItem row : sourceRow.rowSets) {
|
||||||
|
row.cells.add(new Cell(template.system, template.code, relationship.getSymbol(), comment));
|
||||||
|
}
|
||||||
|
rowSets.addAll(sourceRow.rowSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTerminus() {
|
||||||
|
for (MultipleMappingRowItem row : rowSets) {
|
||||||
|
row.cells.add(new Cell(null, null, "X", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTarget(String system, String code, ConceptMapRelationship relationship, String comment, List<MultipleMappingRow> sets, int colCount) {
|
||||||
|
if (rowSets.get(0).cells.size() == colCount+1) { // if it's already has a target for this col then we have to clone (and split) the rows
|
||||||
|
for (MultipleMappingRowItem row : rowSets) {
|
||||||
|
row.cells.add(new Cell(system, code, relationship.getSymbol(), comment));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MultipleMappingRow nrow = new MultipleMappingRow(this);
|
||||||
|
for (MultipleMappingRowItem row : rowSets) {
|
||||||
|
MultipleMappingRowItem n = new MultipleMappingRowItem();
|
||||||
|
for (int i = 0; i < row.cells.size()-1; i++) { // note to skip the last
|
||||||
|
n.cells.add(row.cells.get(i).copy(true));
|
||||||
|
}
|
||||||
|
n.cells.add(new Cell(system, code, relationship.getSymbol(), comment));
|
||||||
|
nrow.rowSets.add(n);
|
||||||
|
}
|
||||||
|
sets.add(sets.indexOf(this), nrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String lastSystem() {
|
||||||
|
MultipleMappingRowItem first = rowSets.get(0);
|
||||||
|
for (int i = first.cells.size()-1; i >= 0; i--) {
|
||||||
|
if (first.cells.get(i).system != null) {
|
||||||
|
return first.cells.get(i).system;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addCopy(String system) {
|
||||||
|
for (MultipleMappingRowItem row : rowSets) {
|
||||||
|
row.cells.add(new Cell(system, lastCode(), "=", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean alreadyHasMappings(int i) {
|
||||||
|
for (MultipleMappingRowItem row : rowSets) {
|
||||||
|
if (row.cells.size() > i+1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Cell getLastSource(int i) {
|
||||||
|
for (MultipleMappingRowItem row : rowSets) {
|
||||||
|
return row.cells.get(i+1);
|
||||||
|
}
|
||||||
|
throw new Error("Should not get here"); // return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void cloneSource(int i, Cell cell) {
|
||||||
|
MultipleMappingRowItem row = new MultipleMappingRowItem();
|
||||||
|
rowSets.add(row);
|
||||||
|
for (int c = 0; c < i-1; c++) {
|
||||||
|
row.cells.add(new Cell()); // blank cell spaces
|
||||||
|
}
|
||||||
|
row.cells.add(cell.copy(true));
|
||||||
|
row.cells.add(rowSets.get(0).cells.get(rowSets.get(0).cells.size()-1).copy(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ConceptMapRenderer(RenderingContext context) {
|
public ConceptMapRenderer(RenderingContext context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
@ -475,4 +729,254 @@ public class ConceptMapRenderer extends TerminologyRenderer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static XhtmlNode renderMultipleMaps(String start, List<ConceptMap> maps, IConceptMapInformationProvider linker, RenderMultiRowSortPolicy sort) {
|
||||||
|
// 1+1 column for each provided map
|
||||||
|
List<MultipleMappingRow> rowSets = new ArrayList<>();
|
||||||
|
for (int i = 0; i < maps.size(); i++) {
|
||||||
|
populateRows(rowSets, maps.get(i), i, linker);
|
||||||
|
}
|
||||||
|
collateRows(rowSets);
|
||||||
|
if (sort != RenderMultiRowSortPolicy.UNSORTED) {
|
||||||
|
Collections.sort(rowSets, new MultipleMappingRowSorter(sort == RenderMultiRowSortPolicy.FIRST_COL));
|
||||||
|
}
|
||||||
|
XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
|
||||||
|
XhtmlNode tbl = div.table("none").style("text-align: left; border-spacing: 0; padding: 5px");
|
||||||
|
XhtmlNode tr = tbl.tr();
|
||||||
|
styleCell(tr.td(), false, true, 5).b().tx(start);
|
||||||
|
for (ConceptMap map : maps) {
|
||||||
|
if (map.hasWebPath()) {
|
||||||
|
styleCell(tr.td(), false, true, 5).colspan(2).b().ah(map.getWebPath(), map.getVersionedUrl()).tx(map.present());
|
||||||
|
} else {
|
||||||
|
styleCell(tr.td(), false, true, 5).colspan(2).b().tx(map.present());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MultipleMappingRow row : rowSets) {
|
||||||
|
renderMultiRow(tbl, row, maps, linker);
|
||||||
|
}
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void collateRows(List<MultipleMappingRow> rowSets) {
|
||||||
|
List<MultipleMappingRow> toDelete = new ArrayList<ConceptMapRenderer.MultipleMappingRow>();
|
||||||
|
for (MultipleMappingRow rowSet : rowSets) {
|
||||||
|
MultipleMappingRow tgt = rowSet.stickySource;
|
||||||
|
while (toDelete.contains(tgt)) {
|
||||||
|
tgt = tgt.stickySource;
|
||||||
|
}
|
||||||
|
if (tgt != null && rowSets.contains(tgt)) {
|
||||||
|
tgt.rowSets.addAll(rowSet.rowSets);
|
||||||
|
toDelete.add(rowSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rowSets.removeAll(toDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderMultiRow(XhtmlNode tbl, MultipleMappingRow rows, List<ConceptMap> maps, IConceptMapInformationProvider linker) {
|
||||||
|
int rowCounter = 0;
|
||||||
|
for (MultipleMappingRowItem row : rows.rowSets) {
|
||||||
|
XhtmlNode tr = tbl.tr();
|
||||||
|
boolean first = true;
|
||||||
|
int cellCounter = 0;
|
||||||
|
Cell last = null;
|
||||||
|
for (Cell cell : row.cells) {
|
||||||
|
if (first) {
|
||||||
|
if (!cell.renderedCode) {
|
||||||
|
int c = 1;
|
||||||
|
for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) {
|
||||||
|
if (cell.code != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) {
|
||||||
|
rows.rowSets.get(i).cells.get(cellCounter).renderedCode = true;
|
||||||
|
c++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cell.code == null) {
|
||||||
|
styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee");
|
||||||
|
} else {
|
||||||
|
String link = linker.getLink(cell.system, cell.code);
|
||||||
|
XhtmlNode x = null;
|
||||||
|
if (link != null) {
|
||||||
|
x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link);
|
||||||
|
} else {
|
||||||
|
x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c);
|
||||||
|
}
|
||||||
|
// if (cell.clone != null) {
|
||||||
|
// x.style("color: grey");
|
||||||
|
// }
|
||||||
|
x.tx(cell.present());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
if (!cell.renderedRel) {
|
||||||
|
int c = 1;
|
||||||
|
for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) {
|
||||||
|
if ((cell.relationship != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.relationship.equals(rows.rowSets.get(i).cells.get(cellCounter).relationship)) &&
|
||||||
|
(cell.code != null && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) &&
|
||||||
|
(last.code != null && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter-1).code))) {
|
||||||
|
rows.rowSets.get(i).cells.get(cellCounter).renderedRel = true;
|
||||||
|
c++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (last.code == null || cell.code == null) {
|
||||||
|
styleCell(tr.td(), rowCounter == 0, true, 5).style("background-color: #eeeeee");
|
||||||
|
} else if (cell.relationship != null) {
|
||||||
|
styleCell(tr.tdW(16), rowCounter == 0, true, 0).attributeNN("title", cell.relComment).rowspan(c).style("background-color: LightGrey; text-align: center; vertical-align: middle; color: white").tx(cell.relationship);
|
||||||
|
} else {
|
||||||
|
styleCell(tr.tdW(16), rowCounter == 0, false, 0).rowspan(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cell.renderedCode) {
|
||||||
|
int c = 1;
|
||||||
|
for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) {
|
||||||
|
if (cell.code != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) {
|
||||||
|
rows.rowSets.get(i).cells.get(cellCounter).renderedCode = true;
|
||||||
|
c++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cell.code == null) {
|
||||||
|
styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee");
|
||||||
|
} else {
|
||||||
|
String link = linker.getLink(cell.system, cell.code);
|
||||||
|
XhtmlNode x = null;
|
||||||
|
if (link != null) {
|
||||||
|
x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link);
|
||||||
|
} else {
|
||||||
|
x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c);
|
||||||
|
}
|
||||||
|
// if (cell.clone != null) {
|
||||||
|
// x.style("color: grey");
|
||||||
|
// }
|
||||||
|
x.tx(cell.present());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = cell;
|
||||||
|
cellCounter++;
|
||||||
|
}
|
||||||
|
rowCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XhtmlNode styleCell(XhtmlNode td, boolean firstrow, boolean sides, int padding) {
|
||||||
|
if (firstrow) {
|
||||||
|
td.style("vertical-align: middle; border-top: 1px solid black; padding: "+padding+"px");
|
||||||
|
} else {
|
||||||
|
td.style("vertical-align: middle; border-top: 1px solid LightGrey; padding: "+padding+"px");
|
||||||
|
}
|
||||||
|
if (sides) {
|
||||||
|
td.style("border-left: 1px solid LightGrey; border-right: 2px solid LightGrey");
|
||||||
|
}
|
||||||
|
return td;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void populateRows(List<MultipleMappingRow> rowSets, ConceptMap map, int i, IConceptMapInformationProvider linker) {
|
||||||
|
// if we can resolve the value set, we create entries for it
|
||||||
|
if (map.hasSourceScope()) {
|
||||||
|
List<Coding> codings = linker.getMembers(map.getSourceScope().primitiveValue());
|
||||||
|
if (codings != null) {
|
||||||
|
for (Coding c : codings) {
|
||||||
|
MultipleMappingRow row = i == 0 ? null : findExistingRowBySource(rowSets, c.getSystem(), c.getCode(), i);
|
||||||
|
if (row == null) {
|
||||||
|
row = new MultipleMappingRow(i, c.getSystem(), c.getCode(), c.getDisplay());
|
||||||
|
rowSets.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ConceptMapGroupComponent grp : map.getGroup()) {
|
||||||
|
for (SourceElementComponent src : grp.getElement()) {
|
||||||
|
MultipleMappingRow row = findExistingRowBySource(rowSets, grp.getSource(), src.getCode(), i);
|
||||||
|
if (row == null) {
|
||||||
|
row = new MultipleMappingRow(i, grp.getSource(), src.getCode(), src.getDisplay());
|
||||||
|
rowSets.add(row);
|
||||||
|
}
|
||||||
|
if (src.getNoMap()) {
|
||||||
|
row.addTerminus();
|
||||||
|
} else {
|
||||||
|
List<TargetElementComponent> todo = new ArrayList<>();
|
||||||
|
for (TargetElementComponent tgt : src.getTarget()) {
|
||||||
|
MultipleMappingRow trow = findExistingRowByTarget(rowSets, grp.getTarget(), tgt.getCode(), i);
|
||||||
|
if (trow == null) {
|
||||||
|
row.addTarget(grp.getTarget(), tgt.getCode(), tgt.getRelationship(), tgt.getComment(), rowSets, i);
|
||||||
|
} else {
|
||||||
|
todo.add(tgt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we've already got a mapping to these targets. So we gather them under the one mapping - but do this after the others are done
|
||||||
|
for (TargetElementComponent t : todo) {
|
||||||
|
MultipleMappingRow trow = findExistingRowByTarget(rowSets, grp.getTarget(), t.getCode(), i);
|
||||||
|
if (row.alreadyHasMappings(i)) {
|
||||||
|
// src is already mapped, and so is target, and now we need to map src to target too
|
||||||
|
// we have to clone src, but we only clone the last
|
||||||
|
trow.cloneSource(i, row.getLastSource(i));
|
||||||
|
} else {
|
||||||
|
trow.addSource(row, rowSets, t.getRelationship(), t.getComment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean copy = grp.hasUnmapped() && grp.getUnmapped().getMode() == ConceptMapGroupUnmappedMode.USESOURCECODE;
|
||||||
|
if (copy) {
|
||||||
|
for (MultipleMappingRow row : rowSets) {
|
||||||
|
if (row.rowSets.get(0).cells.size() == i && row.lastSystem().equals(grp.getSource())) {
|
||||||
|
row.addCopy(grp.getTarget());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (MultipleMappingRow row : rowSets) {
|
||||||
|
if (row.rowSets.get(0).cells.size() == i) {
|
||||||
|
row.addTerminus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (map.hasTargetScope()) {
|
||||||
|
List<Coding> codings = linker.getMembers(map.getTargetScope().primitiveValue());
|
||||||
|
if (codings != null) {
|
||||||
|
for (Coding c : codings) {
|
||||||
|
MultipleMappingRow row = findExistingRowByTarget(rowSets, c.getSystem(), c.getCode(), i);
|
||||||
|
if (row == null) {
|
||||||
|
row = new MultipleMappingRow(i+1, c.getSystem(), c.getCode(), c.getDisplay());
|
||||||
|
rowSets.add(row);
|
||||||
|
} else {
|
||||||
|
for (MultipleMappingRowItem cells : row.rowSets) {
|
||||||
|
Cell last = cells.cells.get(cells.cells.size() -1);
|
||||||
|
if (last.system != null && last.system.equals(c.getSystem()) && last.code.equals(c.getCode()) && last.display == null) {
|
||||||
|
last.display = c.getDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultipleMappingRow findExistingRowByTarget(List<MultipleMappingRow> rows, String system, String code, int i) {
|
||||||
|
for (MultipleMappingRow row : rows) {
|
||||||
|
for (MultipleMappingRowItem cells : row.rowSets) {
|
||||||
|
if (cells.cells.size() > i + 1 && cells.cells.get(i+1).matches(system, code)) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultipleMappingRow findExistingRowBySource(List<MultipleMappingRow> rows, String system, String code, int i) {
|
||||||
|
for (MultipleMappingRow row : rows) {
|
||||||
|
for (MultipleMappingRowItem cells : row.rowSets) {
|
||||||
|
if (cells.cells.size() > i && cells.cells.get(i).matches(system, code)) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ package org.hl7.fhir.r5.terminologies;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -206,6 +207,11 @@ public class CodeSystemUtilities extends TerminologyUtilities {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isNotSelectable(CodeSystem cs, String code) {
|
||||||
|
ConceptDefinitionComponent cd = findCode(cs.getConcept(), code);
|
||||||
|
return cd == null ? false : isNotSelectable(cs, cd);
|
||||||
|
}
|
||||||
|
|
||||||
public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
|
public static void setNotSelectable(CodeSystem cs, ConceptDefinitionComponent concept) throws FHIRFormatError {
|
||||||
defineNotSelectableProperty(cs);
|
defineNotSelectableProperty(cs);
|
||||||
ConceptPropertyComponent p = getProperty(concept, "notSelectable");
|
ConceptPropertyComponent p = getProperty(concept, "notSelectable");
|
||||||
|
@ -476,6 +482,28 @@ public class CodeSystemUtilities extends TerminologyUtilities {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static List<ConceptDefinitionComponent> findCodeWithParents(List<ConceptDefinitionComponent> parents, List<ConceptDefinitionComponent> list, String code) {
|
||||||
|
for (ConceptDefinitionComponent c : list) {
|
||||||
|
if (c.hasCode() && c.getCode().equals(code)) {
|
||||||
|
return addToList(parents, c);
|
||||||
|
}
|
||||||
|
List<ConceptDefinitionComponent> s = findCodeWithParents(addToList(parents, c), c.getConcept(), code);
|
||||||
|
if (s != null)
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ConceptDefinitionComponent> addToList(List<ConceptDefinitionComponent> parents, ConceptDefinitionComponent c) {
|
||||||
|
List<ConceptDefinitionComponent> res = new ArrayList<CodeSystem.ConceptDefinitionComponent>();
|
||||||
|
if (parents != null) {
|
||||||
|
res.addAll(parents);
|
||||||
|
}
|
||||||
|
res.add(c);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
public static ConceptDefinitionComponent findCodeOrAltCode(List<ConceptDefinitionComponent> list, String code, String use) {
|
public static ConceptDefinitionComponent findCodeOrAltCode(List<ConceptDefinitionComponent> list, String code, String use) {
|
||||||
for (ConceptDefinitionComponent c : list) {
|
for (ConceptDefinitionComponent c : list) {
|
||||||
if (c.hasCode() && c.getCode().equals(code))
|
if (c.hasCode() && c.getCode().equals(code))
|
||||||
|
@ -928,5 +956,35 @@ public class CodeSystemUtilities extends TerminologyUtilities {
|
||||||
return v.primitiveValue();
|
return v.primitiveValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Boolean subsumes(CodeSystem cs, String pc, String cc) {
|
||||||
|
if (pc.equals(cc)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
List<ConceptDefinitionComponent> child = findCodeWithParents(null, cs.getConcept(), cc);
|
||||||
|
for (ConceptDefinitionComponent item : child) {
|
||||||
|
if (pc.equals(item.getCode())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> codes(CodeSystem cs) {
|
||||||
|
Set<String> res = new HashSet<>();
|
||||||
|
addCodes(res, cs.getConcept());
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCodes(Set<String> res, List<ConceptDefinitionComponent> list) {
|
||||||
|
for (ConceptDefinitionComponent cd : list) {
|
||||||
|
if (cd.hasCode()) {
|
||||||
|
res.add(cd.getCode());
|
||||||
|
}
|
||||||
|
if (cd.hasConcept()) {
|
||||||
|
addCodes(res, cd.getConcept());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.hl7.fhir.r5.model.Base;
|
||||||
import org.hl7.fhir.r5.model.CanonicalType;
|
import org.hl7.fhir.r5.model.CanonicalType;
|
||||||
import org.hl7.fhir.r5.model.CodeSystem;
|
import org.hl7.fhir.r5.model.CodeSystem;
|
||||||
import org.hl7.fhir.r5.model.Coding;
|
import org.hl7.fhir.r5.model.Coding;
|
||||||
|
@ -16,6 +17,7 @@ import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
|
||||||
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
|
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
|
||||||
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
|
||||||
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ConceptMapElementSorter;
|
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ConceptMapElementSorter;
|
||||||
|
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ElementMappingPair;
|
||||||
import org.hl7.fhir.r5.model.Identifier;
|
import org.hl7.fhir.r5.model.Identifier;
|
||||||
import org.hl7.fhir.r5.model.Meta;
|
import org.hl7.fhir.r5.model.Meta;
|
||||||
import org.hl7.fhir.r5.model.UriType;
|
import org.hl7.fhir.r5.model.UriType;
|
||||||
|
@ -23,6 +25,36 @@ import org.hl7.fhir.r5.model.ValueSet;
|
||||||
|
|
||||||
public class ConceptMapUtilities {
|
public class ConceptMapUtilities {
|
||||||
|
|
||||||
|
public static class TargetSorter implements Comparator<TargetElementComponent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(TargetElementComponent o1, TargetElementComponent o2) {
|
||||||
|
return o1.getCode().compareTo(o2.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ElementSorter implements Comparator<SourceElementComponent> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(SourceElementComponent o1, SourceElementComponent o2) {
|
||||||
|
return o1.getCode().compareTo(o2.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ElementMappingPair {
|
||||||
|
|
||||||
|
private SourceElementComponent src;
|
||||||
|
private TargetElementComponent tgt;
|
||||||
|
|
||||||
|
public ElementMappingPair(SourceElementComponent src, TargetElementComponent tgt) {
|
||||||
|
this.src = src;
|
||||||
|
this.tgt = tgt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class TranslatedCode {
|
public static class TranslatedCode {
|
||||||
private String code;
|
private String code;
|
||||||
private ConceptMapRelationship relationship;
|
private ConceptMapRelationship relationship;
|
||||||
|
@ -37,7 +69,7 @@ public class ConceptMapUtilities {
|
||||||
public ConceptMapRelationship getRelationship() {
|
public ConceptMapRelationship getRelationship() {
|
||||||
return relationship;
|
return relationship;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ConceptMapElementSorter implements Comparator<SourceElementComponent> {
|
public static class ConceptMapElementSorter implements Comparator<SourceElementComponent> {
|
||||||
|
@ -287,5 +319,215 @@ public class ConceptMapUtilities {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean checkReciprocal(ConceptMap left, ConceptMap right, List<String> issues) {
|
||||||
|
boolean altered = false;
|
||||||
|
if (!Base.compareDeep(left.getTargetScope(), right.getSourceScope(), true)) {
|
||||||
|
issues.add("scopes are not reciprocal: "+left.getTargetScope()+" vs "+right.getSourceScope());
|
||||||
|
}
|
||||||
|
if (!Base.compareDeep(left.getSourceScope(), right.getTargetScope(), true)) {
|
||||||
|
issues.add("scopes are not reciprocal: "+left.getSourceScope()+" vs "+right.getTargetScope());
|
||||||
|
}
|
||||||
|
if (left.getGroup().size() != right.getGroup().size()) {
|
||||||
|
issues.add("group count mismatch: "+left.getGroup().size()+" vs "+right.getGroup().size());
|
||||||
|
}
|
||||||
|
for (ConceptMapGroupComponent gl : left.getGroup()) {
|
||||||
|
ConceptMapGroupComponent gr = findMatchingGroup(right.getGroup(), gl.getTarget(), gl.getSource());
|
||||||
|
if (gr == null) {
|
||||||
|
issues.add("left maps from "+gl.getSource()+" to "+gl.getTarget()+" but right has no matching reverse map");
|
||||||
|
} else {
|
||||||
|
for (SourceElementComponent srcL : gl.getElement()) {
|
||||||
|
if (!"CHECK!".equals(srcL.getCode())) {
|
||||||
|
if (!srcL.getNoMap()) {
|
||||||
|
for (TargetElementComponent tgtL : srcL.getTarget()) {
|
||||||
|
List<ElementMappingPair> pairs = getMappings(gr, tgtL.getCode(), srcL.getCode());
|
||||||
|
switch (tgtL.getRelationship()) {
|
||||||
|
case EQUIVALENT:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.EQUIVALENT);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RELATEDTO:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.RELATEDTO);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SOURCEISBROADERTHANTARGET:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.SOURCEISNARROWERTHANTARGET);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SOURCEISNARROWERTHANTARGET:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gr.getOrAddElement(tgtL.getCode()).addTarget().setCode(srcL.getCode()).setRelationship(ConceptMapRelationship.SOURCEISBROADERTHANTARGET);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NOTRELATEDTO:
|
||||||
|
for (ElementMappingPair pair : pairs) {
|
||||||
|
if (pair.tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
|
||||||
|
issues.add("Left map says that "+srcL.getCode()+" is not related to "+tgtL.getCode()+" but a reverse relationship exists with type "+pair.tgt.getRelationship().toCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (SourceElementComponent srcR : gr.getElement()) {
|
||||||
|
for (TargetElementComponent tgtR : srcR.getTarget()) {
|
||||||
|
if (srcL.getCode().equals(tgtR.getCode())) {
|
||||||
|
issues.add("Left map says that there is no relationship for "+srcL.getCode()+" but right map has a "+tgtR.getRelationship().toCode()+" mapping to it from "+srcR.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (SourceElementComponent srcR : gr.getElement()) {
|
||||||
|
if (!"CHECK!".equals(srcR.getCode())) {
|
||||||
|
if (!srcR.getNoMap()) {
|
||||||
|
for (TargetElementComponent tgtR : srcR.getTarget()) {
|
||||||
|
List<ElementMappingPair> pairs = getMappings(gl, tgtR.getCode(), srcR.getCode());
|
||||||
|
switch (tgtR.getRelationship()) {
|
||||||
|
case EQUIVALENT:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.EQUIVALENT);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RELATEDTO:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.RELATEDTO);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SOURCEISBROADERTHANTARGET:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.SOURCEISNARROWERTHANTARGET);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SOURCEISNARROWERTHANTARGET:
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
gl.getOrAddElement(tgtR.getCode()).addTarget().setCode(srcR.getCode()).setRelationship(ConceptMapRelationship.SOURCEISBROADERTHANTARGET);
|
||||||
|
altered = true;
|
||||||
|
} else for (ElementMappingPair pair : pairs) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NOTRELATEDTO:
|
||||||
|
for (ElementMappingPair pair : pairs) {
|
||||||
|
if (pair.tgt.getRelationship() != ConceptMapRelationship.NOTRELATEDTO) {
|
||||||
|
issues.add("Right map says that "+srcR.getCode()+" is not related to "+tgtR.getCode()+" but a reverse relationship exists with type "+pair.tgt.getRelationship().toCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (SourceElementComponent srcL : gr.getElement()) {
|
||||||
|
for (TargetElementComponent tgtL : srcL.getTarget()) {
|
||||||
|
if (srcR.getCode().equals(tgtL.getCode())) {
|
||||||
|
issues.add("Right map says that there is no relationship for "+srcR.getCode()+" but right map has a "+tgtL.getRelationship().toCode()+" mapping to it from "+srcL.getCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return altered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ElementMappingPair> getMappings(ConceptMapGroupComponent g, String source, String target) {
|
||||||
|
List<ElementMappingPair> res = new ArrayList<ConceptMapUtilities.ElementMappingPair>();
|
||||||
|
|
||||||
|
for (SourceElementComponent src : g.getElement()) {
|
||||||
|
for (TargetElementComponent tgt : src.getTarget()) {
|
||||||
|
if (source.equals(src.getCode()) && target.equals(tgt.getCode())) {
|
||||||
|
res.add(new ElementMappingPair(src, tgt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ConceptMapGroupComponent findMatchingGroup(List<ConceptMapGroupComponent> groups, String source, String target) {
|
||||||
|
for (ConceptMapGroupComponent g : groups) {
|
||||||
|
if (source.equals(g.getSource()) && target.equals(g.getTarget())) {
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param cmF
|
||||||
|
* @return true if all the maps simply map to the same code
|
||||||
|
*/
|
||||||
|
public static boolean isUnityMap(ConceptMap cm) {
|
||||||
|
for (ConceptMapGroupComponent grp : cm.getGroup()) {
|
||||||
|
for (SourceElementComponent src : grp.getElement()) {
|
||||||
|
if (src.hasNoMap()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (src.getTarget().size() != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (src.getTargetFirstRep().getRelationship() != ConceptMapRelationship.EQUIVALENT && src.getTargetFirstRep().getRelationship() != ConceptMapRelationship.RELATEDTO) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!src.getCode().equals(src.getTargetFirstRep().getCode())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int mapCount(ConceptMap cm) {
|
||||||
|
int i = 0;
|
||||||
|
for (ConceptMapGroupComponent grp : cm.getGroup()) {
|
||||||
|
for (SourceElementComponent src : grp.getElement()) {
|
||||||
|
i = i + src.getTarget().size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -479,4 +479,25 @@ public class ValueSetUtilities extends TerminologyUtilities {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Set<String> codes(ValueSet vs, CodeSystem cs) {
|
||||||
|
Set<String> res = new HashSet<>();
|
||||||
|
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
|
||||||
|
if (inc.getSystem().equals(cs.getUrl())) {
|
||||||
|
addCodes(res, inc, cs.getConcept());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCodes(Set<String> res, ConceptSetComponent inc, List<ConceptDefinitionComponent> list) {
|
||||||
|
for (ConceptDefinitionComponent cd : list) {
|
||||||
|
if (cd.hasCode() && (!inc.hasConcept() || inc.hasConcept(cd.getCode()))) {
|
||||||
|
res.add(cd.getCode());
|
||||||
|
}
|
||||||
|
if (cd.hasConcept()) {
|
||||||
|
addCodes(res, inc, cd.getConcept());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -838,7 +838,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ValidationResult validateCode(String path, Coding code, CodeSystem cs, CodeableConcept vcc, ValidationProcessInfo info) {
|
private ValidationResult validateCode(String path, Coding code, CodeSystem cs, CodeableConcept vcc, ValidationProcessInfo info) {
|
||||||
ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode(), allAltCodes);
|
ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode(), cs.getCaseSensitive(), allAltCodes);
|
||||||
if (cc == null) {
|
if (cc == null) {
|
||||||
cc = findSpecialConcept(code, cs);
|
cc = findSpecialConcept(code, cs);
|
||||||
}
|
}
|
||||||
|
@ -850,6 +850,11 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
String msg = context.formatMessage(I18nConstants.UNKNOWN_CODE_IN_VERSION, code.getCode(), cs.getUrl(), cs.getVersion());
|
String msg = context.formatMessage(I18nConstants.UNKNOWN_CODE_IN_VERSION, code.getCode(), cs.getUrl(), cs.getVersion());
|
||||||
return new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path+".code", msg, OpIssueCode.InvalidCode, null));
|
return new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.CODEINVALID, path+".code", msg, OpIssueCode.InvalidCode, null));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!cc.getCode().equals(code.getCode())) {
|
||||||
|
String msg = context.formatMessage(I18nConstants.CODE_CASE_DIFFERENCE, code.getCode(), cc.getCode(), cs.getVersionedUrl());
|
||||||
|
info.addIssue(makeIssue(IssueSeverity.INFORMATION, IssueType.BUSINESSRULE, path+".code", msg, OpIssueCode.CodeRule, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Coding vc = new Coding().setCode(cc.getCode()).setSystem(cs.getUrl()).setVersion(cs.getVersion()).setDisplay(getPreferredDisplay(cc, cs));
|
Coding vc = new Coding().setCode(cc.getCode()).setSystem(cs.getUrl()).setVersion(cs.getVersion()).setDisplay(getPreferredDisplay(cc, cs));
|
||||||
if (vcc != null) {
|
if (vcc != null) {
|
||||||
|
@ -1001,19 +1006,19 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, AlternateCodesProcessingRules altCodeRules) {
|
private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, boolean caseSensitive, AlternateCodesProcessingRules altCodeRules) {
|
||||||
opContext.deadCheck();
|
opContext.deadCheck();
|
||||||
if (code.equals(concept.getCode())) {
|
if (code.equals(concept.getCode())) {
|
||||||
return concept;
|
return concept;
|
||||||
}
|
}
|
||||||
ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code, altCodeRules);
|
ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code, caseSensitive, altCodeRules);
|
||||||
if (cc != null) {
|
if (cc != null) {
|
||||||
return cc;
|
return cc;
|
||||||
}
|
}
|
||||||
if (concept.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
|
if (concept.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
|
||||||
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) concept.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
|
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) concept.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
|
||||||
for (ConceptDefinitionComponent c : children) {
|
for (ConceptDefinitionComponent c : children) {
|
||||||
cc = findCodeInConcept(c, code, altCodeRules);
|
cc = findCodeInConcept(c, code, caseSensitive, altCodeRules);
|
||||||
if (cc != null) {
|
if (cc != null) {
|
||||||
return cc;
|
return cc;
|
||||||
}
|
}
|
||||||
|
@ -1022,15 +1027,15 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code, AlternateCodesProcessingRules altCodeRules) {
|
private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code, boolean caseSensitive, AlternateCodesProcessingRules altCodeRules) {
|
||||||
for (ConceptDefinitionComponent cc : concept) {
|
for (ConceptDefinitionComponent cc : concept) {
|
||||||
if (code.equals(cc.getCode())) {
|
if (code.equals(cc.getCode()) || (!caseSensitive && (code.equalsIgnoreCase(cc.getCode())))) {
|
||||||
return cc;
|
return cc;
|
||||||
}
|
}
|
||||||
if (Utilities.existsInList(code, alternateCodes(cc, altCodeRules))) {
|
if (Utilities.existsInList(code, alternateCodes(cc, altCodeRules))) {
|
||||||
return cc;
|
return cc;
|
||||||
}
|
}
|
||||||
ConceptDefinitionComponent c = findCodeInConcept(cc, code, altCodeRules);
|
ConceptDefinitionComponent c = findCodeInConcept(cc, code, caseSensitive, altCodeRules);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
@ -1122,7 +1127,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code, allAltCodes);
|
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code, cs.getCaseSensitive(), allAltCodes);
|
||||||
if (cc != null) {
|
if (cc != null) {
|
||||||
sys.add(vsi.getSystem());
|
sys.add(vsi.getSystem());
|
||||||
}
|
}
|
||||||
|
@ -1409,11 +1414,11 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
||||||
if (!excludeRoot && code.equals(f.getValue())) {
|
if (!excludeRoot && code.equals(f.getValue())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue(), altCodeParams);
|
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue(), cs.getCaseSensitive(), altCodeParams);
|
||||||
if (cc == null) {
|
if (cc == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ConceptDefinitionComponent cc2 = findCodeInConcept(cc, code, altCodeParams);
|
ConceptDefinitionComponent cc2 = findCodeInConcept(cc, code, cs.getCaseSensitive(), altCodeParams);
|
||||||
return cc2 != null && cc2 != cc;
|
return cc2 != null && cc2 != cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue