1
0
mirror of https://github.com/pcvolkmer/onkostar-plugin-dnpm.git synced 2025-07-01 17:02:53 +00:00

Issue #19: Integration des ATC-Codes-Plugins in dieses Plugin

closes #19
This commit is contained in:
2023-04-05 10:28:55 +02:00
parent a47d6279cf
commit abb54090aa
16 changed files with 30 additions and 566 deletions

View File

@ -1,5 +1,9 @@
# Onkostar-Plugin zur Verwendung mit der DNPM-Formularsammlung
## ATC-Codes
Dieses Plugin integriert das ATC-Codes-Plugin vollständig. Dieses kann daher nicht zusätzlich in Onkostar installiert werden.
## Consent
Das Plugin ist auf die Übernahme des DNPM-Consents ausgelegt. Hierzu muss die Einstellung `consentform` festgelegt werden.
@ -78,5 +82,25 @@ In der Klasse `DefaultSystemtherapieService` wird zur Laufzeit der erforderliche
An dieser Stelle kann auch eine eigene Implementierung - eine neue Klasse, die das Interface `ProzedurToProzedurwerteMapper` implementiert - integriert werden, indem das zu verwendende Formular (Formularname) je `SID` und die zu verwendende Mapping-Klasse für den Formularnamen angegeben wird.
## Bauen des Plugins
Für das Bauen des Plugins ist zwingend JDK in Version 11 erforderlich.
Spätere Versionen des JDK beinhalten einige Methoden nicht mehr, die von Onkostar und dort benutzten Libraries verwendet
werden.
Voraussetzung ist das Kopieren der Datei `onkostar-api-2.11.1.1.jar` (oder neuer) in das Projektverzeichnis `libs`.
Weiterhin verwendet dieses Plugin das [ATC-Codes-Plugin](https://github.com/CCC-MF/onkostar-plugin-atccodes).
Die zugehörige JAR-Datei muss ebenfalls in das Projektverzeichnis `libs` kopiert werden. Aktuell wird Version 0.5.0 verwendet.
**_Hinweis_**: Bei Verwendung einer neueren Version der Onkostar-API oder des ATC-Codes-Plugins
muss die Datei `pom.xml` entsprechend angepasst werden.
Danach Ausführen des Befehls:
```shell
./mvnw package
```

View File

@ -29,9 +29,9 @@
<dependency>
<groupId>de.ukw</groupId>
<artifactId>onkostar-plugin-atccodes</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/onkostar-plugin-atccodes-0.4.0.jar</systemPath>
<systemPath>${project.basedir}/libs/onkostar-plugin-atccodes-0.5.0.jar</systemPath>
</dependency>
<!-- Hibernate -->
@ -159,7 +159,8 @@
${project.build.directory}/classes/
</outputDirectory>
<includes>
de/ukw/ccc/**/*.class
de/ukw/ccc/**/*.class,
de/ukw/ccc/**/*.js
</includes>
<excludeTransitive>true</excludeTransitive>
<includeScope>system</includeScope>

View File

@ -1,15 +0,0 @@
package ATCCodes;
/**
* Common Agent Code definition
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
public interface AgentCode extends Comparable<AgentCode> {
String getCode();
String getName();
CodeSystem getSystem();
}

View File

@ -1,62 +0,0 @@
package ATCCodes;
import java.util.Objects;
/**
* ATC-Code as used in WHO XML file
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
public class AtcCode implements AgentCode {
private final String code;
private final String name;
public AtcCode(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public CodeSystem getSystem() {
return CodeSystem.ATC;
}
@Override
public int compareTo(final AgentCode agentCode) {
return this.name.toLowerCase().compareTo(agentCode.getName().toLowerCase());
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AgentCode otherAgentCode = (AgentCode) o;
return Objects.equals(code.toLowerCase(), otherAgentCode.getCode().toLowerCase())
&& Objects.equals(name.toLowerCase(), otherAgentCode.getName().toLowerCase());
}
@Override
public int hashCode() {
return Objects.hash(code.toLowerCase(), name.toLowerCase());
}
/**
* Prüft auf gültiges ATCCode-Schema ab Ebene 2
* @param code Der zu prüfende Code
* @return Gibt <code>true</code> zurück, wenn der angegebene Code dem ATCCode-Schema entspricht
*/
public static boolean isAtcCode(String code) {
return null != code
&& ! code.isBlank()
&& code.matches("[ABCDGHJLMNPRSV][0-2][1-9]([A-Z]([A-Z]([0-9]{2})?)?)?");
}
}

View File

@ -1,110 +0,0 @@
package ATCCodes;
import de.itc.onkostar.api.Disease;
import de.itc.onkostar.api.Procedure;
import de.itc.onkostar.api.analysis.AnalyzerRequirement;
import de.itc.onkostar.api.analysis.IProcedureAnalyzer;
import de.itc.onkostar.api.analysis.OnkostarPluginType;
import ATCCodes.services.AgentCodeService;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Plugin implementation
* Provides methods exposed to Onkostar
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
@Component
public class AtcCodesPlugin implements IProcedureAnalyzer {
private final List<AgentCodeService> agentCodeServices;
public AtcCodesPlugin(List<AgentCodeService> agentCodeServices) {
this.agentCodeServices = agentCodeServices;
}
@Override
public OnkostarPluginType getType() {
return OnkostarPluginType.BACKEND_SERVICE;
}
@Override
public String getVersion() {
return "0.4.0";
}
@Override
public String getName() {
return "ATC-Codes und Substanzen";
}
@Override
public String getDescription() {
return "ATC-Codes und Substanzen";
}
@Override
public boolean isSynchronous() {
return false;
}
@Override
public AnalyzerRequirement getRequirement() {
return AnalyzerRequirement.PROCEDURE;
}
@Override
public boolean isRelevantForDeletedProcedure() {
return false;
}
@Override
public boolean isRelevantForAnalyzer(final Procedure procedure, final Disease disease) {
return false;
}
@Override
public void analyze(final Procedure procedure, final Disease disease) {
// Nothing to do - should never be called
}
/**
* Return list with ATC codes and agents.
* Usage in script:
*
* <pre>
* executePluginMethod(
* 'AtcCodesPlugin',
* 'query',
* { q: '', size: 10 },
* function (result) {console.log(result);},
* false
* );
* </pre>
*
* @param input The data Map
* @return The result list filtered by input
*/
public List<AgentCode> query(final Map<String, Object> input) {
String query = "";
if (null != input.get("q")) {
query = input.get("q").toString();
}
int size = Integer.parseInt(input.get("size").toString());
if (size == 0) {
size = 10;
}
var result = new ArrayList<AgentCode>();
for (var agentCodeService : this.agentCodeServices) {
result.addAll(agentCodeService.findAgentCodes(query, size));
}
return result.stream().distinct().sorted().collect(Collectors.toList());
}
}

View File

@ -1,12 +0,0 @@
package ATCCodes;
/**
* This enum provides usable coding systems
*
* @author Paul-Christion Volkmer
* @since 0.1.0
*/
public enum CodeSystem {
ATC,
UNREGISTERED
}

View File

@ -1,14 +0,0 @@
package ATCCodes;
/**
* Exception to be thrown if any file parsing error occurs
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
public class FileParsingException extends RuntimeException {
public FileParsingException(final String msg) {
super(msg);
}
}

View File

@ -1,51 +0,0 @@
package ATCCodes;
import java.util.Objects;
/**
* Unregistered code as used in Onkostar database
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
public class UnregisteredCode implements AgentCode {
private final String code;
private final String name;
public UnregisteredCode(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public CodeSystem getSystem() {
return CodeSystem.UNREGISTERED;
}
@Override
public int compareTo(final AgentCode agentCode) {
return this.name.toLowerCase().compareTo(agentCode.getName().toLowerCase());
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AgentCode otherAgentCode = (AgentCode) o;
return Objects.equals(code.toLowerCase(), otherAgentCode.getCode().toLowerCase())
&& Objects.equals(name.toLowerCase(), otherAgentCode.getName().toLowerCase());
}
@Override
public int hashCode() {
return Objects.hash(code.toLowerCase(), name.toLowerCase());
}
}

View File

@ -1,23 +0,0 @@
package ATCCodes.services;
import ATCCodes.AgentCode;
import java.util.List;
/**
* Common interface for agent code services
*
* @author Paul-Christian Volkmer
*/
public interface AgentCodeService {
/**
* Queries source for agents with name and code starting with query string.
* If size is zero, all available results will be returned.
*
* @param query The query string
* @param size Maximal amount of responses
* @return A list with agent codes
*/
List<AgentCode> findAgentCodes(String query, int size);
}

View File

@ -1,51 +0,0 @@
package ATCCodes.services;
import ATCCodes.AgentCode;
import ATCCodes.AtcCode;
import ATCCodes.FileParsingException;
import org.apache.commons.csv.CSVFormat;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
/**
* Service to query for agent codes based on WHO xml file
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
@Service
public class CsvAtcCodeService extends FileBasedAgentCodeService {
public CsvAtcCodeService(final ResourceLoader resourceLoader) {
super(resourceLoader);
}
protected List<AgentCode> parseFile(final ResourceLoader resourceLoader) {
var result = new ArrayList<AgentCode>();
var filename = getFilePath("atc.csv");
try {
var inputStream = resourceLoader.getResource(filename).getInputStream();
var parser = CSVFormat.RFC4180
.withHeader()
.withSkipHeaderRecord()
.parse(new InputStreamReader(inputStream));
for (var row : parser) {
if (!row.isMapped("CODE") || !row.isMapped("NAME")) {
throw new FileParsingException("No CSV column 'CODE' or 'NAME' found");
}
result.add(new AtcCode(row.get("CODE"), row.get("NAME")));
}
logger.info("Found CSV file for ATC-Codes.");
return result;
} catch (IOException | FileParsingException e) {
logger.warn("Error reading CSV file '{}' for ATC-Codes. Proceeding without data", filename);
}
return result;
}
}

View File

@ -1,63 +0,0 @@
package ATCCodes.services;
import ATCCodes.AgentCode;
import org.apache.commons.lang.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ResourceLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* Abstract {@link AgentCodeService} for use with files that will load information into memory
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
public abstract class FileBasedAgentCodeService implements AgentCodeService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final List<AgentCode> codeList = new ArrayList<>();
FileBasedAgentCodeService(final ResourceLoader resourceLoader) {
this.codeList.addAll(parseFile(resourceLoader));
}
static String getFilePath(final String filename) {
String pluginPathPart = "onkostar/files/onkostar/plugins/onkostar-plugin-atccodes";
if (SystemUtils.IS_OS_WINDOWS) {
return String.format("file:///c:/%s/%s", pluginPathPart, filename);
} else if (SystemUtils.IS_OS_LINUX) {
return String.format("file:///opt/%s/%s", pluginPathPart, filename);
}
return filename;
}
protected abstract List<AgentCode> parseFile(final ResourceLoader resourceLoader);
/**
* Queries source for agents code starting with or name containing query string.
* If size is zero, all available results will be returned.
*
* @param query The query string
* @param size Maximal amount of responses
* @return A list with agent codes
*/
@Override
public List<AgentCode> findAgentCodes(final String query, final int size) {
var resultStream = this.codeList.stream().filter(agentCode ->
agentCode.getCode().toLowerCase().startsWith(query.toLowerCase())
|| agentCode.getName().toLowerCase().contains(query.toLowerCase())
);
if (size > 0) {
return resultStream.limit(size).collect(Collectors.toList());
}
return resultStream.collect(Collectors.toList());
}
}

View File

@ -1,61 +0,0 @@
package ATCCodes.services;
import ATCCodes.AgentCode;
import ATCCodes.AtcCode;
import ATCCodes.UnregisteredCode;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.util.List;
/**
* Implementation of {@link AgentCodeService} that uses database to query for unregistered agents
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
@Service
public class OnkostarAgentCodeService implements AgentCodeService {
private final JdbcTemplate jdbcTemplate;
public OnkostarAgentCodeService(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* Queries source for agents code starting with or name containing query string.
* If size is zero, all available results will be returned.
*
* @param query The query string
* @param size Maximal amount of responses
* @return A list with agent codes
*/
@Override
public List<AgentCode> findAgentCodes(final String query, final int size) {
var sql = "SELECT code, shortdesc\n" +
" FROM property_catalogue\n" +
" JOIN property_catalogue_version ON (property_catalogue_version.datacatalog_id = property_catalogue.id)\n" +
" JOIN property_catalogue_version_entry p ON (p.property_version_id = property_catalogue_version.id)\n" +
" WHERE name = 'OS.Substanzen'\n" +
" AND (LOWER(code) LIKE ? OR LOWER(shortdesc) LIKE ? OR LOWER(synonyms) LIKE ?)";
if (size > 0) {
sql = sql + " LIMIT " + size;
}
return jdbcTemplate.query(
sql,
new Object[]{query + "%", "%" + query + "%", "%" + query + "%"},
(resultSet, i) -> {
var code = resultSet.getString("code");
var shortdesc = resultSet.getString("shortdesc");
if (AtcCode.isAtcCode(code)) {
return new AtcCode(code, shortdesc);
}
return new UnregisteredCode(code, shortdesc);
}
);
}
}

View File

@ -1,72 +0,0 @@
package ATCCodes.services;
import ATCCodes.AgentCode;
import ATCCodes.AtcCode;
import ATCCodes.FileParsingException;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Service to query for agent codes based on WHO xml file
*
* @author Paul-Christian Volkmer
* @since 0.1.0
*/
@Service
public class WhoAtcCodeService extends FileBasedAgentCodeService {
public WhoAtcCodeService(final ResourceLoader resourceLoader) {
super(resourceLoader);
}
protected List<AgentCode> parseFile(final ResourceLoader resourceLoader) {
var result = new ArrayList<AgentCode>();
var filename = getFilePath("atc.xml");
try {
var inputStream = resourceLoader.getResource(filename).getInputStream();
var context = JAXBContext.newInstance(XmlResource.class);
var xmlResource = (XmlResource) context.createUnmarshaller().unmarshal(inputStream);
for (var row : xmlResource.data.rows) {
if (null == row.code || null == row.name) {
throw new FileParsingException("No XML attribute 'ATCCode' or 'Name' found");
}
result.add(new AtcCode(row.code, row.name));
}
logger.info("Found WHO XML file for ATC-Codes.");
return result;
} catch (IOException e) {
logger.warn("Error reading WHO XML file '{}' for ATC-Codes. Proceeding without inserting data", filename);
} catch (JAXBException | FileParsingException e) {
logger.warn("Error parsing WHO XML file '{}' for ATC-Codes. Proceeding without inserting data", filename);
}
return result;
}
@XmlRootElement(name = "xml")
private static class XmlResource {
@XmlElement(name = "data", namespace = "urn:schemas-microsoft-com:rowset")
public XmlData data;
}
private static class XmlData {
@XmlElement(name = "row", namespace = "#RowsetSchema")
public List<XmlRow> rows;
}
private static class XmlRow {
@XmlAttribute(name = "ATCCode")
public String code;
@XmlAttribute(name = "Name")
public String name;
}
}

View File

@ -1,10 +1,10 @@
package DNPM.services.systemtherapie;
import ATCCodes.AtcCode;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.itc.onkostar.api.Item;
import de.itc.onkostar.api.Procedure;
import de.ukw.ccc.onkostar.atccodes.AtcCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -26,4 +26,5 @@
<context:component-scan base-package="DNPM.config" />
<mvc:resources mapping="/app/lib/umr/**" location="classpath:/app/lib/umr/" />
<mvc:resources mapping="/app/plugins/atccodes/**" location="classpath:/de/ukw/ccc/onkostar/atccodes/" />
</beans>

View File

@ -1,28 +0,0 @@
package ATCCodes;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AtcCodeTest {
@Test
void testShouldDetectAtcCodeScheme() {
assertTrue(AtcCode.isAtcCode("L01"));
assertTrue(AtcCode.isAtcCode("L01A"));
assertTrue(AtcCode.isAtcCode("L01AA"));
assertTrue(AtcCode.isAtcCode("L01AA01"));
}
@Test
void testShouldDetectInvalidAtcCodeScheme() {
assertFalse(AtcCode.isAtcCode(null));
assertFalse(AtcCode.isAtcCode(" "));
assertFalse(AtcCode.isAtcCode("irgendwas"));
assertFalse(AtcCode.isAtcCode("L00AA"));
assertFalse(AtcCode.isAtcCode("Z01AA"));
assertFalse(AtcCode.isAtcCode("L01AA0"));
}
}