|
@ -1,25 +1,89 @@ |
|
|
package com.iflytop.a800.task; |
|
|
package com.iflytop.a800.task; |
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException; |
|
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode; |
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
import com.iflytop.a800.TaskBase; |
|
|
import com.iflytop.a800.TaskBase; |
|
|
import com.iflytop.a800.resource.TestTube; |
|
|
|
|
|
import com.iflytop.a800.resource.TestTubeRack; |
|
|
|
|
|
|
|
|
import com.iflytop.a800.device.Device; |
|
|
|
|
|
import com.iflytop.a800.model.MdbIdChip; |
|
|
|
|
|
import com.iflytop.a800.model.MdbProject; |
|
|
|
|
|
import com.iflytop.a800.resource.*; |
|
|
|
|
|
import com.iflytop.a800.utils.ScanResultAnalysisAlgo; |
|
|
import com.iflytop.uf.UfCmdSnippetExecutor; |
|
|
import com.iflytop.uf.UfCmdSnippetExecutor; |
|
|
|
|
|
import com.iflytop.uf.util.UfCommon; |
|
|
|
|
|
import com.iflytop.uf.util.UfJsonHelper; |
|
|
|
|
|
|
|
|
|
|
|
import java.util.*; |
|
|
|
|
|
|
|
|
public class TubeTestTask extends TaskBase { |
|
|
public class TubeTestTask extends TaskBase { |
|
|
|
|
|
// 测试项目 |
|
|
|
|
|
public MdbProject project; |
|
|
public TestTubeRack tubeRack; |
|
|
public TestTubeRack tubeRack; |
|
|
public TestTube tube; |
|
|
public TestTube tube; |
|
|
public String status; |
|
|
public String status; |
|
|
|
|
|
|
|
|
|
|
|
// 缓冲液试管 |
|
|
|
|
|
private BufferTube bufferTube; |
|
|
|
|
|
// 大容量缓冲液试管 |
|
|
|
|
|
private LargeBufferTube largeBufferTube; |
|
|
|
|
|
// 测试卡 |
|
|
|
|
|
private TestCard testCard; |
|
|
|
|
|
// id chip |
|
|
|
|
|
private MdbIdChip idChip; |
|
|
|
|
|
|
|
|
|
|
|
// 任务准备 |
|
|
|
|
|
public void prepare() { |
|
|
|
|
|
var device = Device.getInstance(); |
|
|
|
|
|
this.bufferTube = device.bufferTube.alloc(); |
|
|
|
|
|
this.largeBufferTube = device.largeBufferTube.getTube(); |
|
|
|
|
|
this.testCard = device.testCard.alloc(); |
|
|
|
|
|
this.idChip = new MdbIdChip(); |
|
|
|
|
|
this.idChip.peakCount = 3; |
|
|
|
|
|
this.idChip.itemCount = 1; |
|
|
|
|
|
this.idChip.scanPeakCount = 3; |
|
|
|
|
|
this.idChip.items = UfJsonHelper.objectToJson(List.of(Map.of( |
|
|
|
|
|
"item", "hsCRP", |
|
|
|
|
|
"isPiecewiseFunction", false, |
|
|
|
|
|
"nonPiecewiseFunction", Map.of( |
|
|
|
|
|
"xValueSource", 1, |
|
|
|
|
|
"serum", Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0), |
|
|
|
|
|
"wb", Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0) |
|
|
|
|
|
) |
|
|
|
|
|
))); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public void run() { |
|
|
public void run() { |
|
|
this.shake(); |
|
|
|
|
|
this.uncap(); |
|
|
|
|
|
this.sampling(); |
|
|
|
|
|
this.cap(); |
|
|
|
|
|
this.incubate(); |
|
|
|
|
|
this.scan(); |
|
|
|
|
|
|
|
|
this.prepare(); |
|
|
|
|
|
|
|
|
|
|
|
ObjectMapper jsonMapper = new ObjectMapper(); |
|
|
|
|
|
JsonNode stepsJsonTree = null; |
|
|
|
|
|
try { |
|
|
|
|
|
stepsJsonTree = jsonMapper.readTree(this.project.steps); |
|
|
|
|
|
} catch (JsonProcessingException e) { |
|
|
|
|
|
throw new RuntimeException(e); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for ( JsonNode stepNode : stepsJsonTree ) { |
|
|
|
|
|
var stepEnable = stepNode.get("enable").asBoolean(); |
|
|
|
|
|
if ( !stepEnable ) { |
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var stepAction = stepNode.get("action").asText(); |
|
|
|
|
|
switch ( stepAction ) { |
|
|
|
|
|
// case "shaking" : this.shake(stepNode); break; |
|
|
|
|
|
case "puncture" : this.executeStepPuncture(stepNode); break; |
|
|
|
|
|
case "aspirate" : this.executeStepAspirate(stepNode); break; |
|
|
|
|
|
case "mixing" : this.executeStepMixing(stepNode); break; |
|
|
|
|
|
case "drop-tip" : this.executeStepDropTip(stepNode); break; |
|
|
|
|
|
case "incubate" : this.executeStepIncubate(stepNode); break; |
|
|
|
|
|
case "analysis" : this.executeStepAnalysis(stepNode); break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 摇匀 |
|
|
// 摇匀 |
|
|
private void shake() { |
|
|
|
|
|
|
|
|
private void shake( JsonNode stepNode ) { |
|
|
UfCmdSnippetExecutor.execute("SampleTestShake"); |
|
|
UfCmdSnippetExecutor.execute("SampleTestShake"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -28,25 +92,206 @@ public class TubeTestTask extends TaskBase { |
|
|
UfCmdSnippetExecutor.execute("SampleTestUnCap"); |
|
|
UfCmdSnippetExecutor.execute("SampleTestUnCap"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 取样 |
|
|
|
|
|
private void sampling() { |
|
|
|
|
|
UfCmdSnippetExecutor.execute("SampleTestSamplingFromWb5ml"); |
|
|
|
|
|
UfCmdSnippetExecutor.execute("SampleTestSamplingFromBufferTube"); |
|
|
|
|
|
UfCmdSnippetExecutor.execute("SampleTestSamplingFromBufferTube"); |
|
|
|
|
|
|
|
|
// 穿孔 |
|
|
|
|
|
private void executeStepPuncture( JsonNode stepNode ) { |
|
|
|
|
|
Device device = Device.getInstance(); |
|
|
|
|
|
var pipette = device.pipette; |
|
|
|
|
|
pipette.tipPickUp(); |
|
|
|
|
|
pipette.puncture(this.bufferTube); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 吸液 |
|
|
|
|
|
private void executeStepAspirate( JsonNode stepNode ) { |
|
|
|
|
|
Device device = Device.getInstance(); |
|
|
|
|
|
var pipette = device.pipette; |
|
|
|
|
|
pipette.tipPickUp(); |
|
|
|
|
|
|
|
|
|
|
|
String source = stepNode.get("source").asText(); |
|
|
|
|
|
Integer volume = stepNode.get("volume").asInt(); |
|
|
|
|
|
if ( "LargeBufferTube".equals(source) ) { |
|
|
|
|
|
pipette.aspirateFromLargeBufferTube(this.largeBufferTube, volume); |
|
|
|
|
|
} else if ( "Sample".equals(source) && "EmergencyTube".equals(this.tube.type) ) { |
|
|
|
|
|
pipette.aspirateFromEmergencyTube(this.tube.index, volume); |
|
|
|
|
|
} else if ( "Sample".equals(source) && "SampleEpp0_5Tube".equals(this.tube.type) ) { |
|
|
|
|
|
pipette.aspirateFromSampleEpp0_5(this.tube.index, volume); |
|
|
|
|
|
} else if ( "Sample".equals(source) && "SampleEpp1_5Tube".equals(this.tube.type) ) { |
|
|
|
|
|
pipette.aspirateFromSampleEpp1_5(this.tube.index, volume); |
|
|
|
|
|
} else if ( "Sample".equals(source) && "SampleWholeBlood5mlTube".equals(this.tube.type) ) { |
|
|
|
|
|
pipette.aspirateFromWholeBlood5ml(this.tube.index, volume); |
|
|
|
|
|
} else if ( "Sample".equals(source) && "SampleWholeBlood3mlTube".equals(this.tube.type) ) { |
|
|
|
|
|
pipette.aspirateFromWholeBlood3ml(this.tube.index, volume); |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new RuntimeException(String.format("无效的取样源 : %s/%s",source,this.tube.type)); |
|
|
|
|
|
} |
|
|
|
|
|
pipette.dispense(this.bufferTube); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 混匀 |
|
|
|
|
|
private void executeStepMixing( JsonNode stepNode ) { |
|
|
|
|
|
Device device = Device.getInstance(); |
|
|
|
|
|
var pipette = device.pipette; |
|
|
|
|
|
var mixCount = stepNode.get("count").asInt(); |
|
|
|
|
|
var mixVolume = stepNode.get("volume").asInt(); |
|
|
|
|
|
pipette.mix(this.bufferTube, mixCount, mixVolume); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 盖回 |
|
|
|
|
|
private void cap() { |
|
|
|
|
|
UfCmdSnippetExecutor.execute("SampleTestCap"); |
|
|
|
|
|
|
|
|
// 丢弃吸头 |
|
|
|
|
|
private void executeStepDropTip( JsonNode stepNode ) { |
|
|
|
|
|
Device device = Device.getInstance(); |
|
|
|
|
|
var pipette = device.pipette; |
|
|
|
|
|
pipette.tipDrop(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 孵育 |
|
|
// 孵育 |
|
|
private void incubate() { |
|
|
|
|
|
UfCmdSnippetExecutor.execute("SampleTestIncubate"); |
|
|
|
|
|
|
|
|
private void executeStepIncubate( JsonNode stepNode ) { |
|
|
|
|
|
Device device = Device.getInstance(); |
|
|
|
|
|
device.incubator.pushNewCard(this.testCard.boxIndex); |
|
|
|
|
|
|
|
|
|
|
|
var pipette = device.pipette; |
|
|
|
|
|
pipette.tipPickUp(); |
|
|
|
|
|
|
|
|
|
|
|
Integer volume = stepNode.get("volume").asInt(); |
|
|
|
|
|
pipette.aspirateFromBufferTubeAndDispenseToTestCard(this.bufferTube, volume); |
|
|
|
|
|
|
|
|
|
|
|
pipette.tipDrop(); |
|
|
|
|
|
|
|
|
|
|
|
Integer duration = stepNode.get("duration").asInt(); |
|
|
|
|
|
UfCommon.delay(duration); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 扫描 |
|
|
// 扫描 |
|
|
private void scan() { |
|
|
|
|
|
UfCmdSnippetExecutor.execute("SampleTestScanResult"); |
|
|
|
|
|
|
|
|
private void executeStepAnalysis( JsonNode stepNode ) { |
|
|
|
|
|
// 扫描 |
|
|
|
|
|
Device device = Device.getInstance(); |
|
|
|
|
|
// device.incubator.exitToScanner(); |
|
|
|
|
|
var scanner = device.scanner; |
|
|
|
|
|
scanner.scanTypeF(); |
|
|
|
|
|
var scanResult = scanner.readResult(); |
|
|
|
|
|
scanner.dropCard(); |
|
|
|
|
|
|
|
|
|
|
|
// 计算 |
|
|
|
|
|
var algo = new ScanResultAnalysisAlgo(); |
|
|
|
|
|
var algoResult = algo.calculate(scanResult, this.idChip.peakCount); |
|
|
|
|
|
|
|
|
|
|
|
// 处理 |
|
|
|
|
|
String sampleType = "WB"; // @TODO : 这里要从样本中取 ~~~~ |
|
|
|
|
|
List<Map<String, Object>> results = new ArrayList<>(); |
|
|
|
|
|
var projects = UfJsonHelper.jsonToNode(idChip.items); |
|
|
|
|
|
for ( var i=0; i< idChip.itemCount; i++ ) { |
|
|
|
|
|
double valueX = 0; |
|
|
|
|
|
JsonNode funcInfo = null; |
|
|
|
|
|
|
|
|
|
|
|
var project = projects.get(i); |
|
|
|
|
|
var itemName = project.get("item").asText(); |
|
|
|
|
|
var isPiecewiseFunction = project.get("isPiecewiseFunction").asBoolean(); |
|
|
|
|
|
if ( !isPiecewiseFunction ) { // 非分段函数 |
|
|
|
|
|
var func = project.get("nonPiecewiseFunction"); |
|
|
|
|
|
var xValueSource = func.get("xValueSource").asInt(); |
|
|
|
|
|
valueX = this.calculateRatio(algoResult, xValueSource, itemName); |
|
|
|
|
|
funcInfo = func.get("serum"); |
|
|
|
|
|
if ("WB".equals(sampleType)) { |
|
|
|
|
|
funcInfo = func.get("wb"); |
|
|
|
|
|
} |
|
|
|
|
|
} else { // 分段函数 |
|
|
|
|
|
var func = project.get("piecewiseFunction"); |
|
|
|
|
|
var piecewiseValueSrc = func.get("piecewiseValueSrc").asInt(); |
|
|
|
|
|
var piecewiseValueSrcValue = this.calculateRatio(algoResult, piecewiseValueSrc, itemName); |
|
|
|
|
|
var piecewiseValue = func.get("piecewiseValue").asDouble(); |
|
|
|
|
|
if ( piecewiseValueSrcValue > piecewiseValue ) { // 高浓度 |
|
|
|
|
|
var highXValueSource = func.get("highXValueSource").asInt(); |
|
|
|
|
|
valueX = this.calculateRatio(algoResult, highXValueSource, itemName); |
|
|
|
|
|
funcInfo = func.get("serumHigh"); |
|
|
|
|
|
if ("WB".equals(sampleType)) { |
|
|
|
|
|
funcInfo = func.get("wbHigh"); |
|
|
|
|
|
} |
|
|
|
|
|
} else { // 低浓度 |
|
|
|
|
|
var lowXValueSource = func.get("lowXValueSource").asInt(); |
|
|
|
|
|
valueX = this.calculateRatio(algoResult, lowXValueSource, itemName); |
|
|
|
|
|
funcInfo = func.get("serumLow"); |
|
|
|
|
|
if ("WB".equals(sampleType)) { |
|
|
|
|
|
funcInfo = func.get("wbLow"); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
double valueD = funcInfo.get("d").asDouble(); |
|
|
|
|
|
double valueB = funcInfo.get("b").asDouble(); |
|
|
|
|
|
double valueC = funcInfo.get("c").asDouble(); |
|
|
|
|
|
double valueA = funcInfo.get("a").asDouble(); |
|
|
|
|
|
double value = valueA * Math.pow(valueX,3) + valueB * Math.pow(valueX,2) + valueC * valueX + valueD; |
|
|
|
|
|
|
|
|
|
|
|
// @TODO : 单位也要从配置中读取 |
|
|
|
|
|
results.add(Map.of("name",itemName,"value",value,"unit","xx/xx")); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String testResult = UfJsonHelper.objectToJson(results); |
|
|
|
|
|
System.out.println(testResult); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @link https://iflytop1.feishu.cn/docx/UnRtdSG4qouMTaxzRb2cjiTTnZe |
|
|
|
|
|
* @link https://iflytop1.feishu.cn/docx/A0CHdQL6OoTTCSx8MB7cQKEjnSc |
|
|
|
|
|
* @param result - algo result |
|
|
|
|
|
* @param xSource - x source |
|
|
|
|
|
* @return double - value of ratio |
|
|
|
|
|
*/ |
|
|
|
|
|
private double calculateRatio(ScanResultAnalysisAlgo.AlgoResult result, int xSource, String itenName) { |
|
|
|
|
|
// 计算 ratio |
|
|
|
|
|
Map<String, Float> ratios = new HashMap<>(); |
|
|
|
|
|
if ( 5 == idChip.scanPeakCount ) { |
|
|
|
|
|
var peaks = result.peakInfos; |
|
|
|
|
|
ratios.put("ratio", peaks[3].area / peaks[4].area); // T/C |
|
|
|
|
|
ratios.put("antiRatio", peaks[3].area / peaks[4].area); // H/C |
|
|
|
|
|
ratios.put("antiTestRatio", peaks[3].area / peaks[2].area); // T/H |
|
|
|
|
|
ratios.put("rfRatio", peaks[1].area / peaks[4].area); // R/C |
|
|
|
|
|
ratios.put("rtRatio", peaks[3].area / peaks[1].area); // T/R |
|
|
|
|
|
ratios.put("t4Ratio", peaks[0].area / peaks[4].area); // T4/C |
|
|
|
|
|
ratios.put("t4t3Ratio", peaks[1].area / peaks[0].area); // R/T4 |
|
|
|
|
|
} else if ( 4 == idChip.scanPeakCount && Objects.equals(idChip.scanType, 1) ) { // F 光学 |
|
|
|
|
|
var peaks = result.peakInfos; // H T C |
|
|
|
|
|
ratios.put("ratio", peaks[1].area / peaks[2].area); // T/C |
|
|
|
|
|
ratios.put("antiRatio", peaks[0].area / peaks[2].area); // H/C |
|
|
|
|
|
ratios.put("antiTestRatio", peaks[1].area / peaks[0].area); // T/H |
|
|
|
|
|
ratios.put("rfRatio", peaks[0].area / peaks[2].area); // R/C @TODO : 待定, 三联卡F光学没有R |
|
|
|
|
|
ratios.put("rtRatio", peaks[1].area / peaks[0].area); // T/R @TODO : 待定, 三联卡F光学没有R |
|
|
|
|
|
} else if ( 4 == idChip.scanPeakCount && Objects.equals(idChip.scanType, 2) ) { // T 光学 |
|
|
|
|
|
var peaks = result.peakInfos; // R H T C |
|
|
|
|
|
ratios.put("ratio", peaks[2].area / peaks[3].area); // T/C |
|
|
|
|
|
ratios.put("antiRatio", peaks[1].area / peaks[3].area); // H/C |
|
|
|
|
|
ratios.put("antiTestRatio", peaks[2].area / peaks[1].area); // T/H |
|
|
|
|
|
ratios.put("rfRatio", peaks[0].area / peaks[3].area); // R/C |
|
|
|
|
|
ratios.put("rtRatio", peaks[2].area / peaks[0].area); // T/R |
|
|
|
|
|
} else if ( 3 == idChip.scanPeakCount ) { |
|
|
|
|
|
var peaks = result.peakInfos; // H T C |
|
|
|
|
|
ratios.put("ratio", peaks[1].area / peaks[2].area); // T/C |
|
|
|
|
|
ratios.put("antiRatio", peaks[0].area / peaks[2].area); // H/C |
|
|
|
|
|
ratios.put("antiTestRatio", peaks[1].area / peaks[0].area); // T/H |
|
|
|
|
|
} else if ("PCT".equals(itenName)) { |
|
|
|
|
|
var peaks = result.peakInfos; // C T |
|
|
|
|
|
ratios.put("ratio", peaks[1].area / peaks[0].area); // T/C |
|
|
|
|
|
} else if ( 2 == idChip.scanPeakCount ) { |
|
|
|
|
|
var peaks = result.peakInfos; // T C |
|
|
|
|
|
ratios.put("ratio", peaks[0].area / peaks[1].area); // T/C |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new RuntimeException("unknown scan peak count" + idChip.scanPeakCount); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// calculate ratio by x source |
|
|
|
|
|
if ( 1 == xSource ) { |
|
|
|
|
|
return ratios.get("ratio"); |
|
|
|
|
|
} else if ( 2 == xSource ) { |
|
|
|
|
|
return ratios.get("antiTestRatio"); |
|
|
|
|
|
} else if ( 3 == xSource ) { |
|
|
|
|
|
return ratios.get("antiRatio"); |
|
|
|
|
|
} else if ( 4 == xSource ) { |
|
|
|
|
|
return ratios.get("ratio") + ratios.get("antiTestRatio"); |
|
|
|
|
|
} else if ( 5 == xSource ) { |
|
|
|
|
|
// @TODO : 这里 R-ratio 不知道是啥 |
|
|
|
|
|
throw new RuntimeException("不知道 R-Ratio 是啥"); |
|
|
|
|
|
} else if ( 6 == xSource ) { |
|
|
|
|
|
return ratios.get("t4Ratio"); |
|
|
|
|
|
} else { |
|
|
|
|
|
throw new RuntimeException("unknown x source" + xSource); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |