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}