Add first take on IPS builder

This commit is contained in:
Grahame Grieve 2023-11-22 07:51:26 +11:00
parent f2f10bf27b
commit 2f105d85ab
1 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,430 @@
package org.hl7.fhir.r4.ips;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.r4.ips.IPSBuilder.TypeAndId;
import org.hl7.fhir.r4.model.Age;
import org.hl7.fhir.r4.model.Annotation;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Period;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Range;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.Composition.CompositionStatus;
import org.hl7.fhir.r4.model.Composition.SectionComponent;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.Device;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Dosage;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Medication;
import org.hl7.fhir.r4.model.MedicationStatement;
import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r4.utils.client.FHIRToolingClient;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtil;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class IPSBuilder {
public static class TypeAndId {
private String type;
private String id;
protected TypeAndId(String type, String id) {
super();
this.type = type;
this.id = id;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
}
public static Bundle generateIPS(FHIRToolingClient server, String patientId) {
Patient pat = server.fetchResource(Patient.class, patientId);
Bundle bnd = initBundle();
Composition cmp = initComposition(bnd, server.getAddress(), pat);
pat = processPatient(bnd, server.getAddress(), pat);
// addMedications(bnd, cmp, server, patientId);
addConditions(bnd, cmp, server, patientId);
return bnd;
}
private static Bundle initBundle() {
Bundle bnd = new Bundle();
bnd.getIdentifier().setSystem("urn:ietf:rfc:3986");
bnd.getIdentifier().setValue(Utilities.makeUuidUrn());
bnd.setType(BundleType.DOCUMENT);
bnd.setTimestamp(new Date());
return bnd;
}
private static Composition initComposition(Bundle bnd, String url, Patient pat) {
Composition cmp = new Composition();
cmp.setIdBase(Utilities.makeUuidLC());
cmp.setStatus(CompositionStatus.FINAL);
cmp.getType().addCoding().setSystem("http://loinc.org").setCode("60591-5");
cmp.setDate(new Date());
cmp.setTitle("International Patient Summary");
cmp.getSubject().setReference("Patient/"+pat.getIdBase());
cmp.addAuthor().setReference("Device/java");
bnd.addEntry().setResource(cmp).setFullUrl(Utilities.pathURL(url, "Composition", cmp.getIdBase()));
Device dev = new Device();
dev.setId("java");
dev.addDeviceName().setName("Java Core Library");
dev.addVersion().setValue(VersionUtil.getVersion());
return cmp;
}
private static Patient processPatient(Bundle bnd, String url, Patient pat) {
bnd.addEntry().setResource(pat).setFullUrl(Utilities.pathURL(url, "Patient", pat.getIdBase()));
return pat;
}
private static void addMedications(Bundle bnd, Composition cmp, FHIRToolingClient server, String patientId) {
Bundle sb = server.search("MedicationStatement", "?patient="+patientId+"&_include=MedicationStatement:medication&_include=MedicationStatement:source");
SectionComponent sct = cmp.addSection();
sct.setTitle("Medications");
sct.getCode().addCoding().setSystem("http://loinc.org").setCode("10160-0");
sct.getText().setStatus(NarrativeStatus.GENERATED);
var x = sct.getText().getDiv();
var tbl = x.table("grid");
var tr = tbl.tr();
tr.th().tx("Medication");
tr.th().tx("Category");
tr.th().tx("Status");
tr.th().tx("When");
tr.th().tx("Dosage");
tr.th().tx("Reason");
tr.th().tx("Source");
tr.th().tx("Notes");
boolean ok = false;
for (BundleEntryComponent be : sb.getEntry()) {
if (be.hasResource() && be.getResource() instanceof MedicationStatement) {
MedicationStatement mdstmt = (MedicationStatement) be.getResource();
ok = true;
bnd.addEntry().setResource(mdstmt).setFullUrl(Utilities.pathURL(server.getAddress(), "MedicationStatement", mdstmt.getIdBase()));
sct.addEntry().setReference("MedicationStatement/"+mdstmt.getIdBase());
tr = tbl.tr();
if (mdstmt.hasMedicationReference()) {
Medication med = findMedication(sb, server, mdstmt, mdstmt.getMedicationReference());
if (med == null) {
tr.td().b().tx("Unknown?");
} else {
tr.td().tx(summarise(med));
bnd.addEntry().setResource(med).setFullUrl(Utilities.pathURL(server.getAddress(), "Medication", med.getIdBase()));
}
} else {
tr.td().tx(genCC(mdstmt.getMedicationCodeableConcept()));
}
tr.td().tx(genCC(mdstmt.getCategory()));
var td = tr.td();
td.tx(mdstmt.getStatus().getDisplay());
if (mdstmt.hasStatusReason()) {
td.tx(" (");
td.tx(genCC(mdstmt.getStatusReason()));
td.tx(")");
}
tr.td().tx(genDT(mdstmt.getEffective()));
genDosages(tr.td(), mdstmt.getDosage());
tr.td().tx(genReference(mdstmt, mdstmt.getInformationSource(), bnd, sb, server));
genNotes(tr.td(), mdstmt.getNote());
}
}
if (!ok) {
Condition cnd = new Condition();
cnd.setId(Utilities.makeUuidLC());
cnd.getText().setStatus(NarrativeStatus.GENERATED);
var rx = cnd.getText().getDiv();
rx.tx("No information is provided about the patient's medical problems");
tr = tbl.tr();
tr.td().colspan(7).tx("No information is provided about the patient's medical problems");
cnd.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical").setCode("active").setDisplay("Active");
cnd.getCode().addCoding().setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips").setCode("no-problem-info").setDisplay("No information about current problems");
cnd.getSubject().setReference("Patient/"+patientId);
}
}
private static void genDosages(XhtmlNode x, List<Dosage> dosages) {
if (dosages == null || dosages.size() == 0) {
} else if (dosages.size() == 1) {
genDosage(x, dosages.get(0));
} else {
var ul = x.ul();
for (Dosage d : dosages) {
genDosage(ul.li(), d);
}
}
}
private static void genDosage(XhtmlNode x, Dosage dosage) {
x.tx(dosage.getText());
if (dosage.hasAsNeeded()) {
x.nbsp();
if (dosage.hasAsNeededBooleanType()) {
if (dosage.getAsNeededBooleanType().booleanValue()) {
x.tx(" (as needed)");
}
} else {
x.tx(genDT(dosage.getAsNeeded()));
}
} else if (dosage.hasTiming()) {
x.nbsp();
x.tx(genDT(dosage.getTiming()));
}
if (dosage.hasSite()) {
x.tx(". ");
x.tx(genDT(dosage.getSite()));
}
if (dosage.hasRoute()) {
x.tx(". ");
x.tx(genDT(dosage.getRoute()));
}
}
private static Medication findMedication(Bundle sb, FHIRToolingClient server, MedicationStatement mdstmt, Reference ref) {
if (ref == null || !ref.hasReference()) {
return null;
}
if (ref.getReference().startsWith("#")) {
} else {
}
return null;
}
private static void addConditions(Bundle bnd, Composition cmp, FHIRToolingClient server, String patientId) {
Bundle sb = server.search("Condition", "?patient="+patientId); //+"&_include=MedicationStatement:asserter");
SectionComponent sct = cmp.addSection();
sct.setTitle("Problems");
sct.getCode().addCoding().setSystem("http://loinc.org").setCode("11450-4");
sct.getText().setStatus(NarrativeStatus.GENERATED);
var x = sct.getText().getDiv();
var tbl = x.table("grid");
var tr = tbl.tr();
tr.th().tx("Code");
tr.th().tx("Category");
tr.th().tx("Severity");
tr.th().tx("Status");
tr.th().tx("Onset");
tr.th().tx("Abatement");
tr.th().tx("Source");
tr.th().tx("Notes");
boolean ok = false;
for (BundleEntryComponent be : sb.getEntry()) {
if (be.hasResource() && be.getResource() instanceof Condition) {
Condition cnd = (Condition) be.getResource();
ok = true;
bnd.addEntry().setResource(cnd).setFullUrl(Utilities.pathURL(server.getAddress(), "Condition", cnd.getIdBase()));
sct.addEntry().setReference("Condition/"+cnd.getIdBase());
tr = tbl.tr();
tr.td().tx(genCC(cnd.getCode()));
tr.td().tx(genCC(cnd.getCategory()));
tr.td().tx(genCC(cnd.getSeverity()));
tr.td().tx(genStatus(cnd));
tr.td().tx(genDT(cnd.getOnset()));
tr.td().tx(genDT(cnd.getAbatement()));
tr.td().tx(genSource(cnd, bnd, sb, server));
genNotes(tr.td(), cnd.getNote());
}
}
if (!ok) {
Condition cnd = new Condition();
cnd.setId(Utilities.makeUuidLC());
cnd.getText().setStatus(NarrativeStatus.GENERATED);
var rx = cnd.getText().getDiv();
rx.tx("No information is provided about the patient's medical problems");
tr = tbl.tr();
tr.td().colspan(7).tx("No information is provided about the patient's medical problems");
cnd.getClinicalStatus().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical").setCode("active").setDisplay("Active");
cnd.getCode().addCoding().setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips").setCode("no-problem-info").setDisplay("No information about current problems");
cnd.getSubject().setReference("Patient/"+patientId);
}
}
private static String genReference(DomainResource src, Reference ref, Bundle bnd, Bundle search, FHIRToolingClient server) {
if (ref == null || ref.isEmpty()) {
return null;
}
boolean contained = false;
DomainResource tgt = null;
if (ref.hasReference()) {
if (ref.getReference().startsWith("#")) {
tgt = (DomainResource) src.getContained(ref.getReference());
contained = true;
} else {
TypeAndId tid = getTypeAndId(server.getAddress(), ref.getReference());
if (tid != null) {
tgt = findInBundle(bnd, Utilities.pathURL(server.getAddress(), tid.getType(), tid.getId()));
if (tgt == null) {
tgt = findInBundle(search, Utilities.pathURL(server.getAddress(), tid.getType(), tid.getId()));
if (tgt == null) {
tgt = (DomainResource) server.read(tid.getType(), tid.getId());
}
} else {
contained = true;
}
}
}
}
if (tgt != null) {
if (!contained) {
bnd.addEntry().setResource(tgt).setFullUrl(Utilities.pathURL(server.getAddress(), tgt.fhirType(), tgt.getIdBase()));
}
return summarise(tgt);
} else if (ref.hasDisplay()) {
return ref.getDisplay();
} else if (ref.hasReference()) {
return ref.getReference();
} else if (ref.hasIdentifier()) {
return genIdentifier(ref.getIdentifier());
} else {
return "unknown";
}
}
private static TypeAndId getTypeAndId(String baseUrl, String url) {
if (Utilities.noString(url)) {
return null;
}
if (url.startsWith(baseUrl+"/")) {
url = url.substring(baseUrl.length()+1);
}
String[] p = url.split("\\/");
if (p.length > 1) {
if ("_history".equals(p[p.length-2]) && p.length > 3) {
return new TypeAndId(p[p.length-4], p[p.length-3]);
} else {
return new TypeAndId(p[p.length-2], p[p.length-1]);
}
}
return null;
}
private static DomainResource findInBundle(Bundle bnd, String url) {
for (BundleEntryComponent be : bnd.getEntry()) {
if (url.equals(be.getFullUrl()) && be.hasResource() && be.getResource() instanceof DomainResource) {
return (DomainResource) be.getResource();
}
}
return null;
}
private static String summarise(DomainResource tgt) {
// TODO Auto-generated method stub
return null;
}
private static String genIdentifier(Identifier id) {
return id.getValue();
}
private static void genNotes(XhtmlNode td, List<Annotation> notes) {
if (notes.size() > 0) {
if (notes.size() == 1) {
genNote(td, notes.get(0));
} else {
var ul = td.ul();
for (Annotation a : notes) {
genNote(ul.li(), a);
}
}
}
}
private static void genNote(XhtmlNode td, Annotation annotation) {
td.tx(annotation.getText());
}
private static String genSource(Condition cnd, Bundle bnd, Bundle sb, FHIRToolingClient server) {
if (cnd.hasAsserter()) {
return genReference(cnd, cnd.getAsserter(), bnd, sb, server);
} else if (cnd.hasRecorder()) {
return genReference(cnd, cnd.getRecorder(), bnd, sb, server);
} else {
return null;
}
}
private static String genDT(Type v) {
if (v == null) {
return null;
}
if (v.isPrimitive()) {
return v.primitiveValue();
}
if (v instanceof Age) {
return genQty((Age) v);
}
if (v instanceof Period) {
Period p = (Period) v;
return genDT(p.getStartElement())+" - "+genDT(p.getStartElement());
}
if (v instanceof Range) {
Range p = (Range) v;
return genDT(p.getLow())+" - "+genDT(p.getHigh());
}
return "not done: "+v.fhirType();
}
private static String genQty(Quantity v) {
return v.getValue().toPlainString()+v.getUnit();
}
private static String genStatus(Condition cnd) {
if (cnd.hasClinicalStatus() && cnd.hasVerificationStatus()) {
return genCC(cnd.getClinicalStatus()) +"/"+genCC(cnd.getVerificationStatus());
} else if (cnd.hasClinicalStatus()) {
return genCC(cnd.getClinicalStatus());
} else if (cnd.hasVerificationStatus()) {
return genCC(cnd.getVerificationStatus());
} else {
return null;
}
}
private static String genCC(List<CodeableConcept> list) {
if (list != null && list.size() == 1) {
return genCC(list.get(0));
} else {
return null;
}
}
private static String genCC(CodeableConcept code) {
if (code.hasText()) {
return code.getText();
} else if (code.hasCoding()) {
Coding c = code.getCodingFirstRep();
if (c.hasDisplay()) {
return c.getDisplay();
} else {
return c.getCode();
}
} else {
return null;
}
}
}