
001package org.hl7.fhir.convertors.misc; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.List; 006 007import javax.xml.parsers.ParserConfigurationException; 008 009import org.hl7.fhir.convertors.analytics.PackageVisitor; 010import org.hl7.fhir.convertors.analytics.PackageVisitor.IPackageVisitorProcessor; 011import org.hl7.fhir.convertors.analytics.PackageVisitor.PackageContext; 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.r5.utils.EOperationOutcome; 014import org.hl7.fhir.utilities.FileUtilities; 015import org.hl7.fhir.utilities.Utilities; 016import org.hl7.fhir.utilities.json.model.JsonObject; 017import org.hl7.fhir.utilities.json.parser.JsonParser; 018import org.hl7.fhir.utilities.npm.NpmPackage; 019import org.hl7.fhir.utilities.npm.PackageServer; 020import org.xml.sax.SAXException; 021 022@SuppressWarnings("checkstyle:systemout") 023public class USGenderFinder implements IPackageVisitorProcessor { 024 025 public class Issue { 026 private String pvid; 027 private String rid; 028 private String rType; 029 private String path; 030 private String type; 031 private String message; 032 public Issue(String pvid, String rid, String rType, String path, String type, String message) { 033 super(); 034 this.pvid = pvid; 035 this.rid = rid; 036 this.rType = rType; 037 this.path = path; 038 this.type = type; 039 this.message = message; 040 } 041 public String summary() { 042 return /*"Package "+pvid+" resource "+*/rid+"#"+path+": "+message; 043 } 044 public String csv() { 045 return pvid+","+rid+","+rType+","+path+","+type+","+message; 046 } 047 048 } 049 050 public static void main(String[] args) throws FHIRException, IOException, ParserConfigurationException, SAXException, EOperationOutcome { 051 new USGenderFinder().execute(); 052 } 053 054 List<Issue> issues = new ArrayList<>(); 055 056 private void execute() throws FHIRException, IOException, ParserConfigurationException, SAXException, EOperationOutcome { 057 PackageVisitor pv = new PackageVisitor(); 058 pv.setCorePackages(false); 059 pv.setClientPackageServer(PackageServer.primaryServer()); 060 pv.setResourceTypes("StructureDefinition", "Patient", "RelatedPerson", "Practitioner", "Person"); 061 pv.setProcessor(this); 062 pv.visitPackages(); 063 for (Issue s : issues) { 064 System.out.println(s.summary()); 065 } 066 067 StringBuilder csv = new StringBuilder(); 068 csv.append("Package,Resource,Type,Path,Code,Message\r\n"); 069 System.out.println("====================="); 070 for (Issue s : issues) { 071 System.out.println(s.csv()); 072 csv.append(s.csv()); 073 csv.append("\r\n"); 074 } 075 FileUtilities.stringToFile(csv.toString(), Utilities.path("[tmp]", "gender-elements.csv")); 076 } 077 078 @Override 079 public Object startPackage(PackageContext context) throws FHIRException, IOException, EOperationOutcome { 080 if (isUS(context.getNpm())) { 081 System.out.println(context.getPid()+" is a US Realm package"); 082 return this; 083 } else { 084 return null; 085 } 086 } 087 088 private boolean isUS(NpmPackage npm) { 089 if (npm != null) { 090 return npm.name().contains("hl7.fhir.us"); 091 } 092 return false; 093 } 094 095 @Override 096 public void processResource(PackageContext context, Object clientContext, String type, String id, byte[] content) 097 throws FHIRException, IOException, EOperationOutcome { 098 if (clientContext != null) { 099 JsonObject r = JsonParser.parseObject(content); 100 if ("StructureDefinition".equals(type)) { 101 String rt = r.asString("type"); 102 if (Utilities.existsInList(rt, "Patient", "RelatedPerson", "Practitioner", "Person")) { 103 checkForElement(context, r, rt, rt+".gender", null); 104 checkForExtension(context, r, rt, "birth-sex", "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex"); 105 checkForExtension(context, r, rt, "core-sex", "http://hl7.org/fhir/us/core/StructureDefinition/us-core-sex"); 106 } 107 if (Utilities.existsInList(rt, "Patient")) { 108 checkForElement(context, r, rt, rt+".contact.gender", rt+".contact"); 109 } 110 } else if (r.has("gender")) { 111 String gender = r.asString("gender"); 112 if (!Utilities.existsInList(gender, "male", "female")) { 113 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("resourceType")+"/"+r.asString("id"), r.asString("resourceType"), "gender", "fixed", "resource has a gender that is not male / female ("+gender+")")); 114 } 115 } 116 } 117 } 118 119 private void checkForExtension(PackageContext context, JsonObject r, String rt, String name, String url) { 120 boolean mentions = false; 121 JsonObject diff = r.getJsonObject("differential"); 122 if (diff != null) { 123 for (JsonObject ed : diff.getJsonObjects("element")) { 124 if ((rt+".extension").equals(ed.asString("path") )) { 125 for (JsonObject tr : ed.getJsonObjects("type")) { 126 for (String p : tr.getStrings("profile")) { 127 if (url.equals(p)) { 128 mentions = true; 129 } 130 } 131 } 132 } 133 } 134 } 135 136 if (mentions) { 137 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, rt+".extension:"+name, "uses", "uses the "+name+" extension")); 138 } else { 139 String base = r.asString("baseDefinition"); 140 if (("http://hl7.org/fhir/StructureDefinition/"+rt).equals(base)) { 141 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, rt+".extension:"+name, "nouse/code", "inherits from core and does not use the "+name+" extension")); 142 } else if (!base.contains("us/core")){ 143 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, rt+".extension:"+name, "nouse/other", "does not inherit from US Core and does not use the "+name+" extension ("+base+")")); 144 } else { 145 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, rt+".extension:"+name, "nouse/us-core", "inherits from US Core and does not use the "+name+" extension")); 146 } 147 } 148 } 149 150 private void checkForElement(PackageContext context, JsonObject r, String rt, String path, String filterPath) { 151 boolean constrained = false; 152 JsonObject diff = r.getJsonObject("differential"); 153 if (diff != null) { 154 for (JsonObject ed : diff.getJsonObjects("element")) { 155 if (filterPath != null && filterPath.equals(ed.asString("path") )) { 156 if ("0".equals(ed.asString("max"))) { 157 return; // no, we don't care 158 } 159 } 160 if (path.equals(ed.asString("path") )) { 161 if (ed.has("slicing")) { 162 throw new Error("Not handled"); 163 } 164 boolean required = !"0".equals(ed.asString("min")); 165 if ("0".equals(ed.asString("max"))) { 166 constrained = true; 167 } else if (ed.has("fixedCode")) { 168 String gender = r.asString("fixedCode"); 169 if (!Utilities.existsInList(gender, "male", "female")) { 170 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "fixed", "gender is fixed to a code that is not male / female ("+gender+")")); 171 } 172 constrained = true; 173 } else if (ed.has("patternCode") ) { 174 String gender = r.asString("patternCode"); 175 if (!Utilities.existsInList(gender, "male", "female")) { 176 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "fixed", "gender is fixed to a code that is not male / female ("+gender+")")); 177 } 178 constrained = true; 179 } else if (ed.has("binding")) { 180 String vs = ed.getJsonObject("binding").asString("valueSet"); 181 if (!Utilities.existsInList(vs, "http://cts.nlm.nih.gov/fhir/ValueSet/1.2.91.13925.17760.26878039")) { 182 if (required) { 183 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "binding", "gender is REQUIRED and bound to the value set '"+vs+"' and needs to be changed")); 184 } else { 185 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "binding", "gender is bound to the value set '"+vs+"' and needs to be changed")); 186 } 187 } 188 constrained = true; 189 } 190 } 191 } 192 } 193 if (!constrained) { 194 String base = r.asString("baseDefinition"); 195 if (("http://hl7.org/fhir/StructureDefinition/"+rt).equals(base)) { 196 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "missing/code", "the default UV binding is not overruled")); 197 } else if (!base.contains("us/core")){ 198 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "missing/other", "does not inherit from US Core and does not fix the valueset ("+base+")")); 199 } else { 200 issues.add(new Issue(context.getPid()+"#"+context.getVersion(), r.asString("url")+"|"+r.asString("version"), rt, path, "missing/us-core", "inherits from US Core and does nothing with gender")); 201 } 202 } 203 } 204 205 @Override 206 public void finishPackage(PackageContext context) throws FHIRException, IOException, EOperationOutcome { 207 } 208 209 @Override 210 public void alreadyVisited(String pid) throws FHIRException, IOException, EOperationOutcome { 211 } 212 213}