Commit 2d256f9d authored by R.W.Majeed's avatar R.W.Majeed

experimental inference API and implementation

parent 19f596ef
package de.sekmi.histream.inference;
import java.util.List;
import java.util.function.Consumer;
import de.sekmi.histream.Observation;
public interface EncounterInferenceEngine {
String[] getRequiredConcepts();
boolean useWildcardRequirements();
void run(List<Observation> facts, Consumer<Observation> inferred);
default void run(List<Observation> facts){
run(facts, facts::add);
}
}
package de.sekmi.histream.inference;
import de.sekmi.histream.ObservationFactory;
public interface EncounterInferenceFactory {
void setObservationFactory(ObservationFactory factory);
/**
* Determine whether this inference factory can infer
* facts with the given concept id.
*
* @param inferredConceptId inferred concept
* @return true if the concept can be inferred, false otherwise.
*/
boolean canInfer(String inferredConceptId);
/**
* Create an inference engine which infers the given concept id.
* @param inferredConceptId concept to infer
* @return inference engine or {@code null} if the concept is not
* supported by this factory.
*/
public EncounterInferenceEngine createEngine(String inferredConceptId);
}
package de.sekmi.histream.inference;
import java.util.Iterator;
public interface InferredConcept {
public String getConceptId();
public Iterator<String> getDependencyIDs();
}
/**
* API interfaces and classes for fact inference.
* E.g. deduce a fact from existing observations.
* <p>
* Inference can be used in time context or in
* encounter context.
* </p>
* <p>
* Encounter context inference will use all data that
* is available for a (complete) encounter therefore
* is mainly useful after the patient was discharged.
* Typical use cases are the ETL process, where additional
* information is generated from a full patient case; or
* the data table export where concepts need to be grouped
* in a common column.
* </p>
* <p>
* Time context inference can occur on continuous
* observation in real time.
* </p>
* @author R.W.Majeed
*
*/
package de.sekmi.histream.inference;
\ No newline at end of file
package de.sekmi.histream.scripting;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import de.sekmi.histream.ObservationFactory;
import de.sekmi.histream.inference.EncounterInferenceEngine;
import de.sekmi.histream.inference.EncounterInferenceFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
public class JSEncounterInferenceFactory implements EncounterInferenceFactory{
private ScriptEngineManager sem;
private ObservationFactory os;
private List<Script> scripts;
public JSEncounterInferenceFactory() {
sem = new ScriptEngineManager();
this.scripts = new ArrayList<>();
}
private static class Script{
URL location;
String charset;
String[] produces;
String[] requires;
public Script(URL location, String charset){
this.location = location;
this.charset = charset;
}
}
public void addScript(URL scriptLocation, String charsetName) throws IOException, ScriptException{
ScriptEngine meta = sem.getEngineByName("nashorn");
// TODO use script context for control of error writer and bindings
Bindings bindings = meta.createBindings();
try(
InputStream in = scriptLocation.openStream();
Reader reader = new InputStreamReader(in, charsetName)
){
meta.eval(reader, bindings);
}
Script script = new Script(scriptLocation, charsetName);
// retrieve metadata
Object p = bindings.get("produces");
if( p == null ){
throw new ScriptException("this.produces not defined", scriptLocation.toString(), -1);
}else if( p instanceof ScriptObjectMirror ){
ScriptObjectMirror som = (ScriptObjectMirror)p;
script.produces = som.values().stream()
.map(o -> o.toString())
.toArray(i -> new String[i]);
}else{
throw new ScriptException("Unable to read this.produces of type "+p.getClass(), scriptLocation.toString(), -1);
}
Object r = bindings.get("requires");
if( r == null ){
throw new ScriptException("this.requires not defined", scriptLocation.toString(), -1);
}else if( r instanceof ScriptObjectMirror ){
ScriptObjectMirror som = (ScriptObjectMirror)r;
script.requires = som.values().stream()
.map(o -> o.toString())
.toArray(i -> new String[i]);
}else{
throw new ScriptException("Unable to read this.requires of type "+p.getClass(), scriptLocation.toString(), -1);
}
scripts.add(script);
}
@Override
public void setObservationFactory(ObservationFactory factory) {
this.os = factory;
}
@Override
public boolean canInfer(String inferredConceptId) {
// TODO use hashmap for more efficient lookups
for( Script s : scripts ){
for( String prod : s.produces ){
if( prod.equals(inferredConceptId) ){
return true;
}
}
}
return false;
}
@Override
public EncounterInferenceEngine createEngine(String inferredConceptId) {
// TODO Auto-generated method stub
return null;
}
}
package de.sekmi.histream.scripting;
import org.junit.Assert;
import org.junit.Test;
public class TestEncounterInferenceFactory {
@Test
public void testMetaInfo() throws Exception{
JSEncounterInferenceFactory f = new JSEncounterInferenceFactory();
f.addScript(getClass().getResource("/encounter-inference-1.js"), "UTF-8");
Assert.assertTrue( f.canInfer("ASDF") );
Assert.assertFalse( f.canInfer("XYZ") );
}
}
// concept ids of the facts which can be
// concept ids of the facts which can be
// produced if the conditions are right
this.produces = ["ASDF"];
......
// post-process a whole encounter
// post-process a whole encounter
// a facts object will be available for modifications
facts.add('test1').value(1);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment