You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
13 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. package com.iflytop.a800.task;
  2. import com.fasterxml.jackson.core.JsonProcessingException;
  3. import com.fasterxml.jackson.databind.JsonNode;
  4. import com.fasterxml.jackson.databind.ObjectMapper;
  5. import com.iflytop.a800.TaskBase;
  6. import com.iflytop.a800.device.Device;
  7. import com.iflytop.a800.model.MdbIdChip;
  8. import com.iflytop.a800.model.MdbProject;
  9. import com.iflytop.a800.model.MdbTest;
  10. import com.iflytop.a800.resource.*;
  11. import com.iflytop.a800.utils.ScanResultAnalysisAlgo;
  12. import com.iflytop.uf.UfCmdSnippetExecutor;
  13. import com.iflytop.uf.util.UfCommon;
  14. import com.iflytop.uf.util.UfJsonHelper;
  15. import java.util.*;
  16. public class TubeTestTask extends TaskBase {
  17. // 测试试管
  18. public TestTube tube;
  19. // 测试项目
  20. public MdbProject project;
  21. public TestTubeRack tubeRack;
  22. public String status;
  23. // 测试记录
  24. private MdbTest test;
  25. // 缓冲液试管
  26. private BufferTube bufferTube;
  27. // 大容量缓冲液试管
  28. private LargeBufferTube largeBufferTube;
  29. // 测试卡
  30. private TestCard testCard;
  31. // id chip
  32. private MdbIdChip idChip;
  33. // 任务准备
  34. public void prepare() {
  35. var device = Device.getInstance();
  36. this.bufferTube = device.bufferTube.alloc();
  37. if ("yes".equals(this.project.isLargeBufferRequired)) {
  38. this.largeBufferTube = device.largeBufferTube.getTube();
  39. }
  40. this.testCard = device.testCard.alloc();
  41. this.idChip = new MdbIdChip();
  42. this.idChip.peakCount = 3;
  43. this.idChip.itemCount = 1;
  44. this.idChip.scanPeakCount = 3;
  45. this.idChip.items = UfJsonHelper.objectToJson(List.of(Map.of(
  46. "item", "hsCRP",
  47. "isPiecewiseFunction", false,
  48. "nonPiecewiseFunction", Map.of(
  49. "xValueSource", 1,
  50. "serum", Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0),
  51. "wb", Map.of("a", 1.0, "b", 2.0, "c", 3.0, "d", 4.0)
  52. )
  53. )));
  54. // 测试记录
  55. this.test = new MdbTest();
  56. this.test.save();
  57. }
  58. @Override
  59. public void run() {
  60. this.prepare();
  61. ObjectMapper jsonMapper = new ObjectMapper();
  62. JsonNode stepsJsonTree = null;
  63. try {
  64. stepsJsonTree = jsonMapper.readTree(this.project.steps);
  65. } catch (JsonProcessingException e) {
  66. throw new RuntimeException(e);
  67. }
  68. for ( JsonNode stepNode : stepsJsonTree ) {
  69. var stepEnable = stepNode.get("enable").asBoolean();
  70. if ( !stepEnable ) {
  71. continue;
  72. }
  73. var stepAction = stepNode.get("action").asText();
  74. switch ( stepAction ) {
  75. // case "shaking" : this.shake(stepNode); break;
  76. case "puncture" : this.executeStepPuncture(stepNode); break;
  77. case "aspirate" : this.executeStepAspirate(stepNode); break;
  78. case "mixing" : this.executeStepMixing(stepNode); break;
  79. case "drop-tip" : this.executeStepDropTip(stepNode); break;
  80. case "incubate" : this.executeStepIncubate(stepNode); break;
  81. case "analysis" : this.executeStepAnalysis(stepNode); break;
  82. }
  83. }
  84. }
  85. // 摇匀
  86. private void shake( JsonNode stepNode ) {
  87. UfCmdSnippetExecutor.execute("SampleTestShake");
  88. }
  89. // 取盖
  90. private void uncap() {
  91. UfCmdSnippetExecutor.execute("SampleTestUnCap");
  92. }
  93. // 穿孔
  94. private void executeStepPuncture( JsonNode stepNode ) {
  95. Device device = Device.getInstance();
  96. var pipette = device.pipette;
  97. pipette.tipPickUp();
  98. pipette.puncture(this.bufferTube);
  99. }
  100. // 吸液
  101. private void executeStepAspirate( JsonNode stepNode ) {
  102. Device device = Device.getInstance();
  103. var pipette = device.pipette;
  104. pipette.tipPickUp();
  105. String source = stepNode.get("source").asText();
  106. Integer volume = stepNode.get("volume").asInt();
  107. if ( "LargeBufferTube".equals(source) ) {
  108. pipette.aspirateFromLargeBufferTube(this.largeBufferTube, volume);
  109. } else if ( "Sample".equals(source) && "EmergencyTube".equals(this.tube.type) ) {
  110. pipette.aspirateFromEmergencyTube(this.tube.index, volume);
  111. } else if ( "Sample".equals(source) && "SampleEpp0_5Tube".equals(this.tube.type) ) {
  112. pipette.aspirateFromSampleEpp0_5(this.tube.index, volume);
  113. } else if ( "Sample".equals(source) && "SampleEpp1_5Tube".equals(this.tube.type) ) {
  114. pipette.aspirateFromSampleEpp1_5(this.tube.index, volume);
  115. } else if ( "Sample".equals(source) && "SampleWholeBlood5mlTube".equals(this.tube.type) ) {
  116. pipette.aspirateFromWholeBlood5ml(this.tube.index, volume);
  117. } else if ( "Sample".equals(source) && "SampleWholeBlood3mlTube".equals(this.tube.type) ) {
  118. pipette.aspirateFromWholeBlood3ml(this.tube.index, volume);
  119. } else {
  120. throw new RuntimeException(String.format("无效的取样源 : %s/%s",source,this.tube.type));
  121. }
  122. pipette.dispense(this.bufferTube);
  123. }
  124. // 混匀
  125. private void executeStepMixing( JsonNode stepNode ) {
  126. Device device = Device.getInstance();
  127. var pipette = device.pipette;
  128. var mixCount = stepNode.get("count").asInt();
  129. var mixVolume = stepNode.get("volume").asInt();
  130. pipette.mix(this.bufferTube, mixCount, mixVolume);
  131. }
  132. // 丢弃吸头
  133. private void executeStepDropTip( JsonNode stepNode ) {
  134. Device device = Device.getInstance();
  135. var pipette = device.pipette;
  136. pipette.tipDrop();
  137. }
  138. // 孵育
  139. private void executeStepIncubate( JsonNode stepNode ) {
  140. Device device = Device.getInstance();
  141. device.incubator.pushNewCard(this.testCard.boxIndex);
  142. var pipette = device.pipette;
  143. pipette.tipPickUp();
  144. Integer volume = stepNode.get("volume").asInt();
  145. pipette.aspirateFromBufferTubeAndDispenseToTestCard(this.bufferTube, volume);
  146. pipette.tipDrop();
  147. Integer duration = stepNode.get("duration").asInt();
  148. UfCommon.delay(duration);
  149. }
  150. // 扫描
  151. private void executeStepAnalysis( JsonNode stepNode ) {
  152. // 扫描
  153. Device device = Device.getInstance();
  154. // device.incubator.exitToScanner();
  155. var scanner = device.scanner;
  156. scanner.scanTypeF();
  157. var scanResult = scanner.readResult();
  158. scanner.dropCard();
  159. // 计算
  160. var algo = new ScanResultAnalysisAlgo();
  161. var algoResult = algo.calculate(scanResult, this.idChip.peakCount);
  162. // 处理
  163. String sampleType = "WB"; // @TODO : 这里要从样本中取 ~~~~
  164. List<Map<String, Object>> results = new ArrayList<>();
  165. var projects = UfJsonHelper.jsonToNode(idChip.items);
  166. for ( var i=0; i< idChip.itemCount; i++ ) {
  167. double valueX = 0;
  168. JsonNode funcInfo = null;
  169. var project = projects.get(i);
  170. var itemName = project.get("item").asText();
  171. var isPiecewiseFunction = project.get("isPiecewiseFunction").asBoolean();
  172. if ( !isPiecewiseFunction ) { // 非分段函数
  173. var func = project.get("nonPiecewiseFunction");
  174. var xValueSource = func.get("xValueSource").asInt();
  175. valueX = this.calculateRatio(algoResult, xValueSource, itemName);
  176. funcInfo = func.get("serum");
  177. if ("WB".equals(sampleType)) {
  178. funcInfo = func.get("wb");
  179. }
  180. } else { // 分段函数
  181. var func = project.get("piecewiseFunction");
  182. var piecewiseValueSrc = func.get("piecewiseValueSrc").asInt();
  183. var piecewiseValueSrcValue = this.calculateRatio(algoResult, piecewiseValueSrc, itemName);
  184. var piecewiseValue = func.get("piecewiseValue").asDouble();
  185. if ( piecewiseValueSrcValue > piecewiseValue ) { // 高浓度
  186. var highXValueSource = func.get("highXValueSource").asInt();
  187. valueX = this.calculateRatio(algoResult, highXValueSource, itemName);
  188. funcInfo = func.get("serumHigh");
  189. if ("WB".equals(sampleType)) {
  190. funcInfo = func.get("wbHigh");
  191. }
  192. } else { // 低浓度
  193. var lowXValueSource = func.get("lowXValueSource").asInt();
  194. valueX = this.calculateRatio(algoResult, lowXValueSource, itemName);
  195. funcInfo = func.get("serumLow");
  196. if ("WB".equals(sampleType)) {
  197. funcInfo = func.get("wbLow");
  198. }
  199. }
  200. }
  201. double valueD = funcInfo.get("d").asDouble();
  202. double valueB = funcInfo.get("b").asDouble();
  203. double valueC = funcInfo.get("c").asDouble();
  204. double valueA = funcInfo.get("a").asDouble();
  205. double value = valueA * Math.pow(valueX,3) + valueB * Math.pow(valueX,2) + valueC * valueX + valueD;
  206. // @TODO : 单位也要从配置中读取
  207. results.add(Map.of("name",itemName,"value",value,"unit","xx/xx"));
  208. }
  209. this.test.result = UfJsonHelper.objectToJson(results);
  210. this.test.save();
  211. }
  212. /**
  213. * @link https://iflytop1.feishu.cn/docx/UnRtdSG4qouMTaxzRb2cjiTTnZe
  214. * @link https://iflytop1.feishu.cn/docx/A0CHdQL6OoTTCSx8MB7cQKEjnSc
  215. * @param result - algo result
  216. * @param xSource - x source
  217. * @return double - value of ratio
  218. */
  219. private double calculateRatio(ScanResultAnalysisAlgo.AlgoResult result, int xSource, String itenName) {
  220. // 计算 ratio
  221. Map<String, Float> ratios = new HashMap<>();
  222. if ( 5 == idChip.scanPeakCount ) {
  223. var peaks = result.peakInfos;
  224. ratios.put("ratio", peaks[3].area / peaks[4].area); // T/C
  225. ratios.put("antiRatio", peaks[3].area / peaks[4].area); // H/C
  226. ratios.put("antiTestRatio", peaks[3].area / peaks[2].area); // T/H
  227. ratios.put("rfRatio", peaks[1].area / peaks[4].area); // R/C
  228. ratios.put("rtRatio", peaks[3].area / peaks[1].area); // T/R
  229. ratios.put("t4Ratio", peaks[0].area / peaks[4].area); // T4/C
  230. ratios.put("t4t3Ratio", peaks[1].area / peaks[0].area); // R/T4
  231. } else if ( 4 == idChip.scanPeakCount && Objects.equals(idChip.scanType, 1) ) { // F 光学
  232. var peaks = result.peakInfos; // H T C
  233. ratios.put("ratio", peaks[1].area / peaks[2].area); // T/C
  234. ratios.put("antiRatio", peaks[0].area / peaks[2].area); // H/C
  235. ratios.put("antiTestRatio", peaks[1].area / peaks[0].area); // T/H
  236. ratios.put("rfRatio", peaks[0].area / peaks[2].area); // R/C @TODO : 待定, 三联卡F光学没有R
  237. ratios.put("rtRatio", peaks[1].area / peaks[0].area); // T/R @TODO : 待定, 三联卡F光学没有R
  238. } else if ( 4 == idChip.scanPeakCount && Objects.equals(idChip.scanType, 2) ) { // T 光学
  239. var peaks = result.peakInfos; // R H T C
  240. ratios.put("ratio", peaks[2].area / peaks[3].area); // T/C
  241. ratios.put("antiRatio", peaks[1].area / peaks[3].area); // H/C
  242. ratios.put("antiTestRatio", peaks[2].area / peaks[1].area); // T/H
  243. ratios.put("rfRatio", peaks[0].area / peaks[3].area); // R/C
  244. ratios.put("rtRatio", peaks[2].area / peaks[0].area); // T/R
  245. } else if ( 3 == idChip.scanPeakCount ) {
  246. var peaks = result.peakInfos; // H T C
  247. ratios.put("ratio", peaks[1].area / peaks[2].area); // T/C
  248. ratios.put("antiRatio", peaks[0].area / peaks[2].area); // H/C
  249. ratios.put("antiTestRatio", peaks[1].area / peaks[0].area); // T/H
  250. } else if ("PCT".equals(itenName)) {
  251. var peaks = result.peakInfos; // C T
  252. ratios.put("ratio", peaks[1].area / peaks[0].area); // T/C
  253. } else if ( 2 == idChip.scanPeakCount ) {
  254. var peaks = result.peakInfos; // T C
  255. ratios.put("ratio", peaks[0].area / peaks[1].area); // T/C
  256. } else {
  257. throw new RuntimeException("unknown scan peak count" + idChip.scanPeakCount);
  258. }
  259. // calculate ratio by x source
  260. if ( 1 == xSource ) {
  261. return ratios.get("ratio");
  262. } else if ( 2 == xSource ) {
  263. return ratios.get("antiTestRatio");
  264. } else if ( 3 == xSource ) {
  265. return ratios.get("antiRatio");
  266. } else if ( 4 == xSource ) {
  267. return ratios.get("ratio") + ratios.get("antiTestRatio");
  268. } else if ( 5 == xSource ) {
  269. // @TODO : 这里 R-ratio 不知道是啥
  270. throw new RuntimeException("不知道 R-Ratio 是啥");
  271. } else if ( 6 == xSource ) {
  272. return ratios.get("t4Ratio");
  273. } else {
  274. throw new RuntimeException("unknown x source" + xSource);
  275. }
  276. }
  277. }