|
|
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.device.Device; import com.iflytop.a800.model.MdbIdChip; import com.iflytop.a800.model.MdbProject; import com.iflytop.a800.model.MdbTest; import com.iflytop.a800.resource.*; import com.iflytop.a800.utils.ScanResultAnalysisAlgo; 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 TestTube tube; // 测试项目
public MdbProject project;
public TestTubeRack tubeRack; public String status;
// 测试记录
private MdbTest test; // 缓冲液试管
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(); if ("yes".equals(this.project.isLargeBufferRequired)) { 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) ) )));
// 测试记录
this.test = new MdbTest(); this.test.save(); }
@Override public void run() { 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( JsonNode stepNode ) { UfCmdSnippetExecutor.execute("SampleTestShake"); }
// 取盖
private void uncap() { UfCmdSnippetExecutor.execute("SampleTestUnCap"); }
// 穿孔
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 executeStepDropTip( JsonNode stepNode ) { Device device = Device.getInstance(); var pipette = device.pipette; pipette.tipDrop(); }
// 孵育
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 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")); }
this.test.result = UfJsonHelper.objectToJson(results); this.test.save(); }
/** * @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); } } }
|