improve Patient rendering

This commit is contained in:
Grahame Grieve 2022-09-08 22:02:33 +10:00
parent 2e35e85dec
commit a6d3d56223
3 changed files with 623 additions and 1 deletions

View File

@ -679,6 +679,13 @@ public class DataRenderer extends Renderer {
renderSampledData(x, (SampledData) type);
} else if (type instanceof Reference) {
renderReference(x, (Reference) type);
} else if (type instanceof CodeableReference) {
CodeableReference cr = (CodeableReference) type;
if (cr.hasConcept()) {
renderCodeableConcept(x, cr.getConcept());
} else {
renderReference(x, cr.getReference());
}
} else if (type instanceof MarkdownType) {
addMarkdown(x, ((MarkdownType) type).asStringValue());
} else if (type.isPrimitive()) {

View File

@ -1,28 +1,76 @@
package org.hl7.fhir.r5.renderers;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.poi.hssf.record.chart.DatRecord;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.model.Address;
import org.hl7.fhir.r5.model.Attachment;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.DateType;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.HumanName;
import org.hl7.fhir.r5.model.HumanName.NameUse;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Identifier.IdentifierUse;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Period;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.renderers.PatientRenderer.NamedReferance;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class PatientRenderer extends ResourceRenderer {
public class NamedReferance {
private String name;
private Reference reference;
private BaseWrapper wrapper;
public NamedReferance(String name, Reference ref, BaseWrapper refw) {
this.name = name;
this.reference = ref;
this.wrapper = refw;
}
public String getName() {
return name;
}
public Reference getReference() {
return reference;
}
public BaseWrapper getWrapper() {
return wrapper;
}
}
private static final int MAX_IMAGE_LENGTH = 2*1024*1024;
public PatientRenderer(RenderingContext context) {
super(context);
}
@ -224,7 +272,542 @@ public class PatientRenderer extends ResourceRenderer {
@Override
public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
describe(x.para(), r);
// banner
describe(makeBanner(x.para()), r);
x.hr();
XhtmlNode tbl;
if (hasRenderablePhoto(r)) {
tbl = x.table("none");
XhtmlNode tr = tbl.tr();
tbl = tr.td().table("grid");
renderPhoto(tr.td(), r);
} else {
tbl = x.table("grid");
}
// the table has 4 columns
addStatus(tbl, r);
addIdentifiers(tbl, r);
addNames(tbl, r);
addComms(tbl, r);
addLangs(tbl, r);
addNOKs(tbl, r);
addLinks(tbl, r);
addExtensions(tbl, r);
if (tbl.isEmpty()) {
x.remove(tbl);
}
return false;
}
private void addExtensions(XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
Map<String, List<Extension>> extensions = new HashMap<>();
PropertyWrapper pw = getProperty(r, "extension");
for (BaseWrapper t : pw.getValues()) {
Extension ext = (Extension) t.getBase();
if (!extensions.containsKey(ext.getUrl())) {
extensions.put(ext.getUrl(), new ArrayList<>());
}
extensions.get(ext.getUrl()).add(ext);
}
for (String url : extensions.keySet()) {
StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, url);
if (sd != null) {
List<Extension> list = extensions.get(url);
boolean anyComplex = false;
for (Extension ext : list) {
anyComplex = anyComplex || ext.hasExtension();
}
if (!anyComplex) {
XhtmlNode tr = tbl.tr();
nameCell(tr, sd.getTitle()+":", sd.getDescription(), sd.getUserString("path"));
XhtmlNode td = tr.td();
td.colspan("3");
if (list.size() == 1) {
XhtmlNode ul = td.ul();
for (Extension s : list) {
XhtmlNode li = ul.li();
render(r, li, s.getValue());
}
} else {
render(r, td, list.get(0).getValue());
}
} else {
for (Extension ext : list) {
XhtmlNode tr = tbl.tr();
nameCell(tr, sd.getTitle()+":", sd.getDescription());
XhtmlNode td = tr.td();
td.colspan("3");
if (ext.hasExtension()) {
XhtmlNode ul = td.ul();
for (Extension s : ext.getExtension()) {
XhtmlNode li = ul.li();
li.tx(s.getUrl()+": ");
if (s.hasExtension()) {
boolean first = true;
for (Extension t : s.getExtension()) {
if (first) first = false; else li.tx("; ");
li.tx(t.getUrl()+"=");
render(r, li, t.getValue());
}
} else {
render(r, li, s.getValue());
}
}
} else {
render(r, td, ext.getValue());
}
}
}
}
}
}
private void addIdentifiers(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
List<Identifier> ids = new ArrayList<>();
PropertyWrapper pw = getProperty(r, "identifier");
for (BaseWrapper t : pw.getValues()) {
ids.add((Identifier) t.getBase());
}
Identifier id = null;
for (Identifier i : ids) {
id = chooseId(id, i);
}
if (id != null) {
ids.remove(id);
};
if (ids.size() == 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Other Id:", "Other Ids (see the one above)");
XhtmlNode td = tr.td();
td.colspan("3");
render(r, td, ids.get(0));
} else if (ids.size() > 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Other Ids:", "Other Ids (see the one above)");
XhtmlNode td = tr.td();
td.colspan("3");
XhtmlNode ul = td.ul();
for (Identifier i : ids) {
render(r, ul.li(), i);
}
}
}
private void addLangs(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
List<CodeableConcept> langs = new ArrayList<>();
PropertyWrapper pw = getProperty(r, "communication");
CodeableConcept prefLang = null;
for (BaseWrapper t : pw.getValues()) {
PropertyWrapper l = getProperty(t, "language");
if (l != null && l.hasValues()) {
CodeableConcept lang = (CodeableConcept) l.getValues().get(0).getBase();
langs.add(lang);
l = getProperty(t, "preferred");
if (l != null && l.hasValues() && "true".equals(l.getValues().get(0).getBase().primitiveValue())) {
prefLang = lang;
}
}
}
if (langs.size() == 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Language:", "Languages spoken");
XhtmlNode td = tr.td();
td.colspan("3");
render(r, td, langs.get(0));
if (prefLang != null) {
td.tx(" (preferred)");
}
} else if (langs.size() > 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Languages:", "Languages spoken");
XhtmlNode td = tr.td();
td.colspan("3");
XhtmlNode ul = td.ul();
for (CodeableConcept i : langs) {
XhtmlNode li = ul.li();
render(r, li, i);
if (i == prefLang) {
li.tx(" (preferred)");
}
}
}
}
private void addLinks(XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
List<NamedReferance> refs = new ArrayList<>();
PropertyWrapper pw = getProperty(r, "generalPractitioner");
if (pw != null) {
for (BaseWrapper t : pw.getValues()) {
refs.add(new NamedReferance("General Practitioner", (Reference) t.getBase(), t));
}
}
pw = getProperty(r, "managingOrganization");
if (pw != null) {
for (BaseWrapper t : pw.getValues()) {
refs.add(new NamedReferance("Managing Organization", (Reference) t.getBase(), t));
}
}
pw = getProperty(r, "link");
for (BaseWrapper t : pw.getValues()) {
PropertyWrapper l = getProperty(t, "other");
Reference ref = null;
BaseWrapper refw = null;
for (BaseWrapper v : l.getValues()) {
ref = (Reference) v.getBase();
refw = v;
}
String type = null;
l = getProperty(t, "type");
for (BaseWrapper v : l.getValues()) {
type = v.getBase().primitiveValue();
}
if (type != null && ref != null) {
refs.add(new NamedReferance(describeLinkedRecord(type), ref, refw));
}
}
if (refs.size() > 0) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Links:", "Patient Links");
XhtmlNode td = tr.td();
td.colspan("3");
XhtmlNode ul = td.ul();
for (NamedReferance ref : refs) {
XhtmlNode li = ul.li();
li.tx(ref.getName());
li.tx(": ");
renderReference(r, li, ref.getReference());
}
}
}
private String describeLinkedRecord(String type) {
switch (type) {
case "replaced-by" : return "This record replaced by";
case "replaces": return "This record replaces";
case "refer": return "Please refer to";
case "seealso": return "Also see";
}
return "Unknown";
}
private void addNOKs(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
PropertyWrapper pw = getProperty(r, "contact");
for (BaseWrapper t : pw.getValues()) {
addNOK(tbl,r, t);
}
}
private void addNOK(XhtmlNode tbl, ResourceWrapper r, BaseWrapper bw) throws FHIRFormatError, DefinitionException, IOException {
List<CodeableConcept> rels = new ArrayList<>();
HumanName name = null;
Address add = null;
String gender = null;
Period period = null;
Reference organization = null;
List<ContactPoint> tels = new ArrayList<>();
PropertyWrapper pw = getProperty(bw, "relationship");
for (BaseWrapper t : pw.getValues()) {
CodeableConcept v = (CodeableConcept) t.getBase();
rels.add(v);
}
pw = getProperty(bw, "name");
if (pw.hasValues()) {
name = (HumanName) pw.getValues().get(0).getBase();
}
pw = getProperty(bw, "telecom");
for (BaseWrapper t : pw.getValues()) {
ContactPoint v = (ContactPoint) t.getBase();
tels.add(v);
}
pw = getProperty(bw, "address");
if (pw.hasValues()) {
add = (Address) pw.getValues().get(0).getBase();
}
pw = getProperty(bw, "gender");
if (pw.hasValues()) {
gender = pw.getValues().get(0).getBase().primitiveValue();
}
pw = getProperty(bw, "organization");
if (pw.hasValues()) {
organization = (Reference) pw.getValues().get(0).getBase();
}
pw = getProperty(bw, "period");
if (pw.hasValues()) {
period = (Period) pw.getValues().get(0).getBase();
}
if (rels.size() < 2 && name == null && add == null && gender == null && period == null && organization == null && tels.size() == 0) {
return; // nothing to render
}
XhtmlNode tr = tbl.tr();
if (rels.size() == 1) {
nameCell(tr, (rels.get(0).getCodingFirstRep().hasDisplay() ? rels.get(0).getCodingFirstRep().getDisplay() : display(rels.get(0)))+":", "Nominated Contact: "+display(rels.get(0)));
} else {
nameCell(tr, "Contact", "Patient contact");
}
XhtmlNode td = tr.td();
td.colspan("3");
XhtmlNode ul = td.ul();
XhtmlNode li;
if (name != null) {
li = ul.li();
render(r, li, name);
if (gender != null) {
li.tx(" ("+gender+")");
}
} else if (gender != null) {
li = ul.li();
li.tx("Gender: "+gender);
}
if (rels.size() > 1) {
li = ul.li();
li.tx("Relationships: ");
boolean first = true;
for (CodeableConcept rel : rels) {
if (first) first = false; else li.tx(", ");
render(r, li, rel);
}
}
if (add != null) {
render(r, ul.li(), add);
}
for (ContactPoint cp : tels) {
render(r, ul.li(), cp);
}
if (organization != null) {
li = ul.li();
li.tx("Organization: ");
render(r, li, organization);
}
if (period != null) {
li = ul.li();
li.tx("Valid Period: ");
render(r, li, period);
}
}
private void addNames(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
List<HumanName> names = new ArrayList<>();
PropertyWrapper pw = getProperty(r, "name");
for (BaseWrapper t : pw.getValues()) {
names.add((HumanName) t.getBase());
}
HumanName name = null;
for (HumanName n : names) {
name = chooseName(name, n);
}
if (name != null) {
names.remove(name);
};
if (names.size() == 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Alt. Name:", "Alternate names (see the one above)");
XhtmlNode td = tr.td();
td.colspan("3");
render(r, td, names.get(0));
} else if (names.size() > 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Alt Names:", "Alternate names (see the one above)");
XhtmlNode td = tr.td();
td.colspan("3");
XhtmlNode ul = td.ul();
for (HumanName n : names) {
render(r, ul.li(), n);
}
}
}
private void addComms(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
List<ContactPoint> tels = new ArrayList<>();
PropertyWrapper pw = getProperty(r, "telecom");
for (BaseWrapper t : pw.getValues()) {
tels.add((ContactPoint) t.getBase());
}
List<Address> adds = new ArrayList<>();
pw = getProperty(r, "address");
for (BaseWrapper t : pw.getValues()) {
adds.add((Address) t.getBase());
}
if (tels.size() + adds.size() == 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Contact Detail:", "Ways to contact the Patient");
XhtmlNode td = tr.td();
td.colspan("3");
if (adds.isEmpty()) {
render(r, td, tels.get(0));
} else {
render(r, td, adds.get(0));
}
} else if (tels.size() + adds.size() > 1) {
XhtmlNode tr = tbl.tr();
nameCell(tr, "Contact Details:", "Ways to contact the Patient");
XhtmlNode td = tr.td();
td.colspan("3");
XhtmlNode ul = td.ul();
for (ContactPoint n : tels) {
render(r, ul.li(), n);
}
for (Address n : adds) {
render(r, ul.li(), n);
}
}
}
private void addStatus(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, UnsupportedEncodingException, FHIRException, IOException {
// TODO Auto-generated method stub
int count = 0;
if (r.has("active")) {
count++;
}
if (r.has("deceased")) {
count++;
}
if (r.has("maritalStatus")) {
count++;
}
if (r.has("multipleBirth")) {
count++;
}
if (count > 0) {
XhtmlNode tr = tbl.tr();
int pos = 0;
if (r.has("active")) {
PropertyWrapper a = r.getChildByName("active");
if (a.hasValues()) {
pos++;
nameCell(tr, "Active:", "Record is active");
XhtmlNode td = tr.td();
if (pos == count) {
td.colspan("3");
}
render(r, td, (DataType) a.value().getBase());
}
}
if (r.has("deceased[x]")) {
PropertyWrapper a = r.getChildByName("deceased[x]");
if (a.hasValues()) {
pos++;
nameCell(tr, "Deceased:", "Known status of Patient");
XhtmlNode td = tr.td();
if (pos == count) {
td.colspan("3");
}
render(r, td, (DataType) a.value().getBase());
}
}
if (r.has("maritalStatus")) {
PropertyWrapper a = r.getChildByName("maritalStatus");
if (a.hasValues()) {
pos++;
if (pos == 3) {
tr = tbl.tr();
}
nameCell(tr, "Marital Status:", "Known Marital status of Patient");
XhtmlNode td = tr.td();
if (pos == count) {
td.colspan("3");
}
render(r, td, (DataType) a.value().getBase());
}
}
if (r.has("multipleBirth[x]")) {
PropertyWrapper a = r.getChildByName("multipleBirth[x]");
if (a.hasValues()) {
pos++;
if (pos == 3) {
tr = tbl.tr();
}
nameCell(tr, "Multiple Birth:", "Known multipleBirth status of Patient");
XhtmlNode td = tr.td();
if (pos == count) {
td.colspan("3");
}
render(r, td, (DataType) a.value().getBase());
}
}
}
}
private void nameCell(XhtmlNode tr, String text, String title) {
XhtmlNode td = tr.td();
td.setAttribute("title", title);
td.tx(text);
td.style("background-color: #f3f5da");
}
private void nameCell(XhtmlNode tr, String text, String title, String link) {
XhtmlNode td = tr.td();
td.setAttribute("title", title);
if (link != null) {
td.ah(link).tx(text);
} else {
td.tx(text);
}
td.style("background-color: #f3f5da");
}
private void renderPhoto(XhtmlNode td, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
if (r.has("photo")) {
PropertyWrapper a = r.getChildByName("photo");
for (BaseWrapper v : a.getValues()) {
Attachment att = (Attachment) v.getBase();
if (att.getContentType().startsWith("image/") &&
att.getData() != null && (!context.isInlineGraphics() || (att.getData().length > 0 && att.getData().length < MAX_IMAGE_LENGTH))) {
String ext = extensionForType(att.getContentType());
if (context.isInlineGraphics() || Utilities.noString(context.getDestDir()) || ext == null) {
td.img("data:"+att.getContentType()+";base64,"+att.getDataElement().asStringValue());
} else {
String n = UUID.randomUUID().toString().toLowerCase()+ext;
TextFile.bytesToFile(att.getData(), new File(Utilities.path(context.getDestDir(), n)));
td.img(n);
}
return;
}
}
}
return;
}
private String extensionForType(String contentType) {
if (contentType.equals("image/gif")) {
return ".gif";
}
if (contentType.equals("image/png")) {
return ".png";
}
if (contentType.equals("image/jpeg")) {
return ".jpg";
}
return null;
}
private boolean hasRenderablePhoto(ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
if (r.has("photo")) {
PropertyWrapper a = r.getChildByName("photo");
for (BaseWrapper v : a.getValues()) {
Attachment att = (Attachment) v.getBase();
if (att.getContentType().startsWith("image/") &&
att.getData() != null && (!context.isInlineGraphics() || (att.getData().length > 0 && att.getData().length < MAX_IMAGE_LENGTH))) {
return true;
}
}
}
return false;
}
private XhtmlNode makeBanner(XhtmlNode para) {
para.style("border: 1px #661aff solid; background-color: #e6e6ff; padding: 10px;");
return para;
}
}

View File

@ -11,7 +11,9 @@ import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeableReference;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
@ -172,6 +174,36 @@ public abstract class ResourceRenderer extends DataRenderer {
}
}
public void render(Resource res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException {
if (type instanceof Reference) {
renderReference(res, x, (Reference) type);
} else if (type instanceof CodeableReference) {
CodeableReference cr = (CodeableReference) type;
if (cr.hasReference()) {
renderReference(res, x, cr.getReference());
} else {
render(x, type);
}
} else {
render(x, type);
}
}
public void render(ResourceWrapper res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException {
if (type instanceof Reference) {
renderReference(res, x, (Reference) type);
} else if (type instanceof CodeableReference) {
CodeableReference cr = (CodeableReference) type;
if (cr.hasReference()) {
renderReference(res, x, cr.getReference());
} else {
render(x, type);
}
} else {
render(x, type);
}
}
public void renderReference(Resource res, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException {
ResourceWrapper rw = new ResourceWrapperDirect(this.context, res);
renderReference(rw, x, r);