1
0
mirror of https://github.com/pcvolkmer/onkostar-plugin-dnpm.git synced 2025-07-03 17:52: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,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;