Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement op mapping #155

Merged
merged 14 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static class FhirExtensions {
private String sysTheraProto;
private String dataAbsentReason;
private String genderAmtlich;
private String miiExOnkoOPIntention;
private String miiExOnkoStrahlentherapieIntention;
private String miiExOnkoStrahlentherapieBestrahlung;
private String miiExOnkoHistologyMorphologyBehaviorIcdo3;
Expand All @@ -37,6 +38,7 @@ public static class FhirSystems {
private String conditionId;
private String observationId;
private String procedureId;
private String operationProcedureId;
private String medicationStatementId;
private String observationCategorySystem;
private String allgemeinerLeistungszustandEcogId;
Expand Down Expand Up @@ -76,6 +78,7 @@ public static class FhirSystems {
private String miiCsOnkoStrahlentherapieApplikationsart;
private String miiCsOnkoStrahlentherapieStrahlenart;
private String miiCsOnkoStrahlentherapieZielgebiet;
private String miiCsOnkoOperationResidualstatus;
private String strahlentherapieProcedureId;
private String systemischeTherapieProcedureId;
private String systemischeTherapieMedicationStatementId;
Expand Down Expand Up @@ -106,6 +109,7 @@ public static class FhirProfiles {
private String miiPatientPseudonymisiert;
private String deathObservation;
private String miiPrOnkoDiagnosePrimaertumor;
private String miiPrOnkoOperation;
private String miiPrOnkoStrahlentherapie;
private String miiPrOnkoSystemischeTherapie;
private String miiPrMedicationStatement;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package org.miracum.streams.ume.obdstofhir.mapper.mii;

import de.basisdatensatz.obds.v3.OPTyp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.r4.model.*;
import org.miracum.streams.ume.obdstofhir.FhirProperties;
import org.miracum.streams.ume.obdstofhir.mapper.ObdsToFhirMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OperationMapper extends ObdsToFhirMapper {

private static final Logger LOG = LoggerFactory.getLogger(OperationMapper.class);

@Autowired
public OperationMapper(FhirProperties fhirProperties) {
super(fhirProperties);
}

public List<Procedure> map(OPTyp op, Reference subject, Reference condition) {

Objects.requireNonNull(op, "OP must not be null");
Objects.requireNonNull(subject, "Reference must not be null");
Validate.notBlank(op.getOPID(), "Required OP_ID is unset");

// create a list to hold all single Procedure resources
var procedureList = new ArrayList<Procedure>();

for (var opsCode : op.getMengeOPS().getOPS()) {
var procedure = new Procedure();

// identifier, meta
// to do: brauch ich hier die patid? dann muss ich meine Parameter nochmal überdenken weil im
var identifier =
new Identifier()
.setSystem(fhirProperties.getSystems().getOperationProcedureId())
.setValue(op.getOPID() + opsCode.getCode());
procedure.addIdentifier(identifier);
procedure.setId(
computeResourceIdFromIdentifier(
identifier)); // das hier macht den Hash vom identifier.value

procedure.getMeta().addProfile(fhirProperties.getProfiles().getMiiPrOnkoOperation());

// status
procedure.setStatus(Procedure.ProcedureStatus.COMPLETED);

// code
CodeableConcept opsCodeableConcept =
new CodeableConcept()
.addCoding(
new Coding()
.setSystem(fhirProperties.getSystems().getOps())
.setCode(opsCode.getCode())
.setVersion(opsCode.getVersion()));

procedure.setCode(opsCodeableConcept);

// category: if OPS starts with 5, set 387713003; if first digit 1, set 165197003; else
// 394841004
// https://simplifier.net/packages/de.medizininformatikinitiative.kerndatensatz.prozedur/2024.0.0-ballot/files/2253000
String firstDigitOPS = opsCode.getCode().substring(0, 1);
String categoryCode;
String categoryDisplay;

switch (firstDigitOPS) {
case "5":
categoryCode = "387713003";
categoryDisplay = "Surgical procedure";
break;
case "1":
categoryCode = "165197003";
categoryDisplay = "Diagnostic assessment";
break;
default:
categoryCode = "394841004";
categoryDisplay = "Other category";
break;
}

procedure.setCategory(
new CodeableConcept()
.addCoding(
new Coding()
.setSystem(fhirProperties.getSystems().getSnomed())
.setCode(categoryCode)
.setDisplay(categoryDisplay)));

// subject reference
procedure.setSubject(subject);

// performed
var dataAbsentExtension =
new Extension(
fhirProperties.getExtensions().getDataAbsentReason(), new CodeType("unknown"));
var dataAbsentCode = new CodeType();
dataAbsentCode.addExtension(dataAbsentExtension);

var dateTime = convertObdsDatumToDateTimeType(op.getDatum());
if (dateTime.isPresent()) {
procedure.setPerformed(dateTime.get());
} else {
var performed = new DateTimeType();
performed.addExtension(dataAbsentExtension);
procedure.setPerformed(performed);
}

// ReasonReference
procedure.addReasonReference(condition);

// intention
var intention = new CodeableConcept();
intention
.addCoding()
.setSystem(fhirProperties.getSystems().getMiiCsOnkoIntention())
.setCode(op.getIntention());

var intentionExtens =
new Extension()
.setUrl(fhirProperties.getExtensions().getMiiExOnkoOPIntention())
.setValue(intention);

procedure.addExtension(intentionExtens);

// outcome
var outcome = new CodeableConcept();
outcome
.addCoding()
.setSystem(fhirProperties.getSystems().getMiiCsOnkoOperationResidualstatus())
.setCode(op.getResidualstatus().getLokaleBeurteilungResidualstatus().value());

procedure.setOutcome(outcome);

// add procedure to procedure list here
jasminziegler marked this conversation as resolved.
Show resolved Hide resolved
procedureList.add(procedure);
}

return procedureList;
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ fhir:
mii-ex-onko-strahlentherapie-bestrahlung: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-ex-onko-strahlentherapie-bestrahlung"
mii-ex-onko-histology-morphology-behavior-icdo3: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-ex-onko-histology-morphology-behavior-icdo3"
mii-ex-onko-systemische-therapie-intention: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-ex-onko-systemische-therapie-intention"
mii-ex-onko-op-intention: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-ex-onko-operation-intention"
condition-asserted-date: "http://hl7.org/fhir/StructureDefinition/condition-assertedDate"
systems:
patientId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/patient-id"
identifier-type: "http://terminology.hl7.org/CodeSystem/v2-0203"
conditionId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/primaerdiagnose-id"
observationId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/observation-id"
procedureId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/procedure-id"
operationProcedureId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/operation-procedure-id"
medicationStatementId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/medication-statement-id"
observationCategorySystem: "http://hl7.org/fhir/observation-category"
gleasonScoreObservationId: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/gleason-score-observation-id"
Expand Down Expand Up @@ -71,6 +73,7 @@ fhir:
mii-cs-onko-strahlentherapie-zielgebiet: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-strahlentherapie-zielgebiet"
mii-cs-onko-seitenlokalisation: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-seitenlokalisation"
mii-cs-onko-systemische-therapie-art: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-therapie-typ"
mii-cs-onko-operation-residualstatus: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-residualstatus"
mii-cs-therapie-grund-ende: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-therapie-grund-ende"
mii-cs-onko-tod-interpretation: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-tod"
mii-cs-onko-tod-observation-id: "${fhir.default.baseSystemUrl}/obds-to-fhir/identifiers/tod-observation-id"
Expand All @@ -91,6 +94,7 @@ fhir:
deathObservation: "http://dktk.dkfz.de/fhir/StructureDefinition/onco-core-Observation-TodUrsache"
# MII
mii-patient-pseudonymisiert: "https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/PatientPseudonymisiert"
mii-pr-onko-operation: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-pr-onko-operation"
mii-pr-onko-diagnose-primaertumor: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-pr-onko-diagnose-primaertumor"
mii-pr-onko-strahlentherapie: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-pr-onko-strahlentherapie"
mii-pr-onko-systemische-therapie: "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-pr-onko-systemische-therapie"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.miracum.streams.ume.obdstofhir.mapper.mii;

import static org.assertj.core.api.Assertions.assertThat;

import ca.uhn.fhir.context.FhirContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationModule;
import de.basisdatensatz.obds.v3.OBDS;
import java.io.IOException;
import java.util.TimeZone;
import org.approvaltests.Approvals;
import org.hl7.fhir.r4.model.Reference;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.miracum.streams.ume.obdstofhir.FhirProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(classes = {FhirProperties.class})
@EnableConfigurationProperties
public class OperationMapperTest {
private static OperationMapper sut;

private static final Logger LOG = LoggerFactory.getLogger(OperationMapper.class);

@BeforeAll
static void beforeEach(@Autowired FhirProperties fhirProps) {
TimeZone.setDefault(TimeZone.getTimeZone("Europe/Berlin"));
sut = new OperationMapper(fhirProps);
}

@ParameterizedTest
@CsvSource({"Testpatient_1.xml", "Testpatient_2.xml"})
void map_withGivenObds_shouldCreateValidProcedure(String sourceFile) throws IOException {
final var resource = this.getClass().getClassLoader().getResource("obds3/" + sourceFile);
assertThat(resource).isNotNull();

System.out.println("Loaded resource: " + resource.getPath());

final var xmlMapper =
XmlMapper.builder()
.defaultUseWrapper(false)
.addModule(new JakartaXmlBindAnnotationModule())
.addModule(new Jdk8Module())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.build();

final var obds = xmlMapper.readValue(resource.openStream(), OBDS.class);

var obdsPatient = obds.getMengePatient().getPatient().getFirst();

var subject = new Reference("Patient/any");
var condition = new Reference("Condition/any");

var opMeldung =
obdsPatient.getMengeMeldung().getMeldung().stream()
.filter(m -> m.getOP() != null)
.findFirst()
.get();

// Map and get the list of procedures
final var resultResources = sut.map(opMeldung.getOP(), subject, condition);

assertThat(resultResources).isNotEmpty();
LOG.info("Length of resultResources {}", resultResources.size());

var fhirParser = FhirContext.forR4().newJsonParser().setPrettyPrint(true);

LOG.info("Number of OPS codes: {}", opMeldung);

Check notice

Code scanning / CodeQL

Use of default toString() Note test

Default toString(): Meldung inherits toString() from Object, and so is not suitable for printing.

for (int i = 0; i < resultResources.size(); i++) {
assertThat(resultResources.get(i)).isNotNull();
var fhirJson = fhirParser.encodeResourceToString(resultResources.get(i));
System.out.println("Verifying resource: index_" + i);
Approvals.verify(
fhirJson,
Approvals.NAMES
.withParameters(sourceFile, "index_" + i)
.forFile()
.withExtension(".fhir.json"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"resourceType": "Procedure",
"id": "cd1f9a1a39fedb333d953a94169407e9327425dbfa86215925f3e4190665bd52",
"meta": {
"profile": [ "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-pr-onko-operation" ]
},
"extension": [ {
"url": "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/StructureDefinition/mii-ex-onko-operation-intention",
"valueCodeableConcept": {
"coding": [ {
"system": "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-intention",
"code": "K"
} ]
}
} ],
"identifier": [ {
"system": "https://bzkf.github.io/obds-to-fhir/identifiers/operation-procedure-id",
"value": "3015-870"
} ],
"status": "completed",
"category": {
"coding": [ {
"system": "http://snomed.info/sct",
"code": "387713003",
"display": "Surgical procedure"
} ]
},
"code": {
"coding": [ {
"system": "http://fhir.de/CodeSystem/bfarm/ops",
"version": "2020",
"code": "5-870"
} ]
},
"subject": {
"reference": "Patient/any"
},
"performedDateTime": "2020-03-05",
"reasonReference": [ {
"reference": "Condition/any"
} ],
"outcome": {
"coding": [ {
"system": "https://www.medizininformatik-initiative.de/fhir/ext/modul-onko/CodeSystem/mii-cs-onko-residualstatus",
"code": "R0"
} ]
}
}
Loading
Loading