diff --git a/pom.xml b/pom.xml
index a4d04f5..a482f68 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,12 @@
org.springframework.boot
spring-boot-starter-aop
+
+ org.jetbrains
+ annotations
+ RELEASE
+ compile
+
diff --git a/src/main/java/com/dreamworks/boditech/MyApplication.java b/src/main/java/com/dreamworks/boditech/MyApplication.java
new file mode 100644
index 0000000..76c96b8
--- /dev/null
+++ b/src/main/java/com/dreamworks/boditech/MyApplication.java
@@ -0,0 +1,228 @@
+package com.dreamworks.boditech;
+import com.dreamworks.boditech.driver.Device;
+import com.dreamworks.boditech.driver.actuator.ActAnalysisScanner;
+import com.dreamworks.boditech.driver.actuator.ActMotor;
+import com.dreamworks.boditech.driver.actuator.ActuatorModule;
+import com.dreamworks.boditech.model.MdbIdChip;
+import com.dreamworks.boditech.model.MyActiveRecord;
+import com.dreamworks.boditech.utils.MyCommon;
+import com.dreamworks.boditech.utils.MyOptAlgo;
+import com.fasterxml.jackson.databind.JsonNode;
+import jakarta.annotation.Resource;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.nio.ByteOrder;
+import java.util.*;
+
+@Order(1)
+@Component
+public class MyApplication implements ApplicationRunner, ApplicationContextAware {
+ // application context
+ private static ApplicationContext context;
+
+ // get application context
+ public static ApplicationContext getContext() {
+ return MyApplication.context;
+ }
+
+ // get bean
+ public static T getBean(Class beanClass) {
+ return context.getBean(beanClass);
+ }
+
+ @Override
+ public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
+ MyApplication.context = applicationContext;
+ }
+
+
+ @Resource
+ public Device device;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ var analysisScanMotor = (ActMotor)device.getActuator(ActuatorModule.ANALYSIS_SCAN_MOTOR);
+
+// analysisScanMotor.reset();
+
+ this.analysis();
+ }
+
+ private void analysis() {
+ var scanner = (ActAnalysisScanner)device.getActuator(ActuatorModule.ANALYSIS_SCAN_SCANNER);
+
+ var sampleType = "wb";
+ var idChip = MyActiveRecord.findById(MdbIdChip.class, 2);
+ assert idChip != null;
+
+ // 配置相机
+ if (Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_F)) { // F 光学扫描
+ scanner.setRegister(4200, 0); // 设置扫描类型
+ scanner.setRegister(4201, 2860); // 设置扫描开始坐标
+ scanner.setRegister(4202, -1); // 设置扫描方向
+ scanner.setRegister(4203, 1); // 设置扫描间隔
+ scanner.setRegister(4204, 1200); // 设置扫描点数
+ scanner.setRegister(4205, 1); // 选通通道
+ scanner.setRegister(4206, 0); // 激光增益
+ scanner.setRegister(4207, 0); // 扫描器增益
+ scanner.setRegister(40, 7); // 刷新激光器增益到mcp41xxx
+ scanner.setRegister(40, 8); // 刷新设置扫描器增益到mcp41xxx
+ scanner.setRegister(40, 1); // 选通通道
+ } else if (Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_T)) { // T 光学扫描
+ scanner.setRegister(4200, 1); // 设置扫描类型
+ scanner.setRegister(4201, 4005); // 设置扫描开始坐标
+ scanner.setRegister(4202, -1); // 设置扫描方向
+ scanner.setRegister(4203, 1); // 设置扫描间隔
+ scanner.setRegister(4204, 1200); // 设置扫描点数
+ scanner.setRegister(4205, 1); // 选通通道
+ scanner.setRegister(4206, 0); // 激光增益
+ scanner.setRegister(4207, 0); // 扫描器增益
+ scanner.setRegister(40, 7); // 刷新激光器增益到mcp41xxx
+ scanner.setRegister(40, 8); // 刷新设置扫描器增益到mcp41xxx
+ scanner.setRegister(40, 1); // 选通通道
+ } else {
+ throw new RuntimeException("unknown scan type" + idChip.scanType);
+ }
+
+ // 开始扫描
+ scanner.start();
+ MyCommon.easySleep(20000); // 延时10s @TODO : 这里要根据不同的扫描类型进行延时
+
+ // 读取扫描数据
+ var buffer = scanner.readRaw();
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ float[] data = new float[buffer.capacity() / 2];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = buffer.getShort();
+ }
+
+ // 分析数据
+ var algo = new MyOptAlgo();
+ var result = algo.calculate(data, idChip.scanPeakCount);
+
+ // 结算结果
+ List values = new ArrayList<>();
+ var projects = MyCommon.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(result, xValueSource, idChip, itemName);
+ funcInfo = func.get("wb");
+ if ("serum".equals(sampleType)) {
+ funcInfo = project.get("serum");
+ }
+ } else { // 分段函数
+ var func = project.get("piecewiseFunction");
+ var piecewiseValueSrc = func.get("piecewiseValueSrc").asInt();
+ var piecewiseValueSrcValue = this.calculateRatio(result, piecewiseValueSrc, idChip, itemName);
+ var piecewiseValue = func.get("piecewiseValue").asDouble();
+ if ( piecewiseValueSrcValue > piecewiseValue ) { // 高浓度
+ var highXValueSource = func.get("highXValueSource").asInt();
+ valueX = this.calculateRatio(result, highXValueSource, idChip, itemName);
+ funcInfo = func.get("wbHigh");
+ if ("serum".equals(sampleType)) {
+ funcInfo = project.get("serumHigh");
+ }
+ } else { // 低浓度
+ var lowXValueSource = func.get("lowXValueSource").asInt();
+ valueX = this.calculateRatio(result, lowXValueSource, idChip, itemName);
+ funcInfo = func.get("wbLow");
+ if ("serum".equals(sampleType)) {
+ funcInfo = project.get("serumLow");
+ }
+ }
+ }
+
+ 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;
+ values.add(value);
+ System.out.println("扫描完成 : " + value);
+ }
+
+ System.out.println("扫描完成 : " + values.size());
+ }
+
+ /**
+ * @link https://iflytop1.feishu.cn/docx/UnRtdSG4qouMTaxzRb2cjiTTnZe
+ * @link https://iflytop1.feishu.cn/docx/A0CHdQL6OoTTCSx8MB7cQKEjnSc
+ * @param result
+ * @param xSource
+ * @return
+ */
+ private double calculateRatio(MyOptAlgo.AlgoResult result, int xSource, MdbIdChip idChip, String itenName) {
+ // 计算 ratio
+ Map 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, MdbIdChip.SCAN_TYPE_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, MdbIdChip.SCAN_TYPE_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);
+ }
+ }
+}
diff --git a/src/main/java/com/dreamworks/boditech/driver/DeviceCommand.java b/src/main/java/com/dreamworks/boditech/driver/DeviceCommand.java
index 104feb9..b4cb455 100644
--- a/src/main/java/com/dreamworks/boditech/driver/DeviceCommand.java
+++ b/src/main/java/com/dreamworks/boditech/driver/DeviceCommand.java
@@ -3,6 +3,7 @@ public class DeviceCommand {
// command constants
public static final Integer CMD_MODULE_STOP = 0x0101;
public static final Integer CMD_MODULE_GET_STATUS = 0x0104;
+ public static final Integer CMD_MODULE_SET_REG = 0x0105;
public static final Integer CMD_MODULE_GET_REG = 0x0106;
public static final Integer CMD_MODULE_READ_IO = 0x0107;
public static final Integer CMD_MODULE_READ_RAW = 0x0113;
diff --git a/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorBase.java b/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorBase.java
index d9e9d8d..1223929 100644
--- a/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorBase.java
+++ b/src/main/java/com/dreamworks/boditech/driver/actuator/ActuatorBase.java
@@ -6,6 +6,8 @@ import com.dreamworks.boditech.utils.MyCommon;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
public class ActuatorBase implements Actuator {
// device
@@ -40,7 +42,7 @@ public class ActuatorBase implements Actuator {
}
// wait for finish
- protected void waitForFinish() {
+ public void waitForFinish() {
Integer status = ActuatorModule.MODULE_STATUS_BUSY;
do {
try {
@@ -87,6 +89,29 @@ public class ActuatorBase implements Actuator {
return rawText.trim();
}
+ // read raw
+ // @TODO : 数据过大时,需要分批读取, 但是仍然会出现响应分批到达导致解析失败的问题 ~~~
+ public ByteBuffer readRaw() {
+ Integer size = this.getRegister(10);
+ Integer count = this.getRegister(11);
+ List data = new ArrayList<>();
+
+ for ( int i = 0; i < count; i++ ) {
+ ByteBuffer response = this.call(DeviceCommand.CMD_MODULE_READ_RAW, i);
+ var bytes = response.array();
+ for ( int j = 8; j < bytes.length; j++ ) {
+ data.add(bytes[j]);
+ }
+ MyCommon.easySleep(100);
+ }
+
+ byte[] result = new byte[data.size()];
+ for ( int i = 0; i < data.size(); i++ ) {
+ result[i] = data.get(i);
+ }
+ return ByteBuffer.wrap(result);
+ }
+
// get register
public Integer getRegister( Integer index ) {
ByteBuffer response = this.call(DeviceCommand.CMD_MODULE_GET_REG, index);
@@ -94,6 +119,11 @@ public class ActuatorBase implements Actuator {
return value;
}
+ // set register
+ public void setRegister( Integer index, Integer value ) {
+ this.call(DeviceCommand.CMD_MODULE_SET_REG, index, value);
+ }
+
// get integer from response
protected Integer getIntegerFromResponse(ByteBuffer response, Integer index ) {
response.order(ByteOrder.LITTLE_ENDIAN);
diff --git a/src/main/java/com/dreamworks/boditech/driver/task/step/StepAnalysis.java b/src/main/java/com/dreamworks/boditech/driver/task/step/StepAnalysis.java
index 89611aa..6c69290 100644
--- a/src/main/java/com/dreamworks/boditech/driver/task/step/StepAnalysis.java
+++ b/src/main/java/com/dreamworks/boditech/driver/task/step/StepAnalysis.java
@@ -8,12 +8,13 @@ import com.dreamworks.boditech.driver.actuator.ActMotor;
import com.dreamworks.boditech.driver.actuator.ActuatorModule;
import com.dreamworks.boditech.driver.entity.IncubatorSlot;
import com.dreamworks.boditech.driver.task.TaskTest;
+import com.dreamworks.boditech.model.MdbIdChip;
+import com.dreamworks.boditech.model.MyActiveRecord;
import com.dreamworks.boditech.utils.MyCommon;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
+import com.dreamworks.boditech.utils.MyOptAlgo;
+import com.fasterxml.jackson.databind.JsonNode;
+import java.nio.ByteOrder;
+import java.util.*;
public class StepAnalysis extends StepBase {
// task test
private TaskTest taskTest;
@@ -29,23 +30,15 @@ public class StepAnalysis extends StepBase {
ActAnalysisScanner scanner = (ActAnalysisScanner)device.getActuator(ActuatorModule.ANALYSIS_SCAN_SCANNER);
// 01. 获取卡位
- IncubatorSlot incubatorSlot = this.taskTest.getIncubatorSlot();
- incubator.moveTo(incubatorSlot.getExitLocation());
+// IncubatorSlot incubatorSlot = this.taskTest.getIncubatorSlot();
+// incubator.moveTo(incubatorSlot.getExitLocation());
// 02. 推出测试卡
pushMotor.moveTo("analysisPushMotorScanStart");
pushMotor.moveTo("analysisPushMotorStandby");
- // 03. 扫描测试卡
- scanner.start();
- try { // @TODO : wait for scan finish
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- scanner.stop();
- this.calculateScanResult();
- // @TODO : read raw from scanner and parse it later
+ // 03. 分析
+ this.analysis(executor);
// 04. 丢弃测试卡
scanMotor.moveTo("analysisScanMotorDropCard");
@@ -54,8 +47,8 @@ public class StepAnalysis extends StepBase {
// @TODO : process raw data from scanner
// unlock incubator slot
- incubator.unlockSlot(incubatorSlot.getIndex());
- executor.incubatorSlotAvailable();
+// incubator.unlockSlot(incubatorSlot.getIndex());
+// executor.incubatorSlotAvailable();
}
// calculate result
@@ -65,4 +58,176 @@ public class StepAnalysis extends StepBase {
results.add(Map.of("name", "HbA1c", "value", 5.6, "unit","g/ml"));
this.taskTest.setTestResult(MyCommon.objectToJson(results));
}
+
+ private void analysis(Executor executor) {
+ Device device = executor.getDevice();
+ var scanner = (ActAnalysisScanner)device.getActuator(ActuatorModule.ANALYSIS_SCAN_SCANNER);
+
+ var sampleType = "wb";
+ var idChip = MyActiveRecord.findById(MdbIdChip.class, 2);
+ assert idChip != null;
+
+ // 配置相机
+ if (Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_F)) { // F 光学扫描
+ scanner.setRegister(4200, 0); // 设置扫描类型
+ scanner.setRegister(4201, 2860); // 设置扫描开始坐标
+ scanner.setRegister(4202, -1); // 设置扫描方向
+ scanner.setRegister(4203, 1); // 设置扫描间隔
+ scanner.setRegister(4204, 1200); // 设置扫描点数
+ scanner.setRegister(4205, 1); // 选通通道
+ scanner.setRegister(4206, 0); // 激光增益
+ scanner.setRegister(4207, 0); // 扫描器增益
+ scanner.setRegister(40, 7); // 刷新激光器增益到mcp41xxx
+ scanner.setRegister(40, 8); // 刷新设置扫描器增益到mcp41xxx
+ scanner.setRegister(40, 1); // 选通通道
+ } else if (Objects.equals(idChip.scanType, MdbIdChip.SCAN_TYPE_T)) { // T 光学扫描
+ scanner.setRegister(4200, 1); // 设置扫描类型
+ scanner.setRegister(4201, 4005); // 设置扫描开始坐标
+ scanner.setRegister(4202, -1); // 设置扫描方向
+ scanner.setRegister(4203, 1); // 设置扫描间隔
+ scanner.setRegister(4204, 1200); // 设置扫描点数
+ scanner.setRegister(4205, 1); // 选通通道
+ scanner.setRegister(4206, 0); // 激光增益
+ scanner.setRegister(4207, 0); // 扫描器增益
+ scanner.setRegister(40, 7); // 刷新激光器增益到mcp41xxx
+ scanner.setRegister(40, 8); // 刷新设置扫描器增益到mcp41xxx
+ scanner.setRegister(40, 1); // 选通通道
+ } else {
+ throw new RuntimeException("unknown scan type" + idChip.scanType);
+ }
+
+ // 开始扫描
+ scanner.start();
+ MyCommon.easySleep(20000); // 延时10s @TODO : 这里要根据不同的扫描类型进行延时
+
+ // 读取扫描数据
+ var buffer = scanner.readRaw();
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ float[] data = new float[buffer.capacity() / 2];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = buffer.getShort();
+ }
+
+ // 分析数据
+ var algo = new MyOptAlgo();
+ var result = algo.calculate(data, idChip.scanPeakCount);
+
+ // 结算结果
+ List values = new ArrayList<>();
+ var projects = MyCommon.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(result, xValueSource, idChip, itemName);
+ funcInfo = func.get("wb");
+ if ("serum".equals(sampleType)) {
+ funcInfo = project.get("serum");
+ }
+ } else { // 分段函数
+ var func = project.get("piecewiseFunction");
+ var piecewiseValueSrc = func.get("piecewiseValueSrc").asInt();
+ var piecewiseValueSrcValue = this.calculateRatio(result, piecewiseValueSrc, idChip, itemName);
+ var piecewiseValue = func.get("piecewiseValue").asDouble();
+ if ( piecewiseValueSrcValue > piecewiseValue ) { // 高浓度
+ var highXValueSource = func.get("highXValueSource").asInt();
+ valueX = this.calculateRatio(result, highXValueSource, idChip, itemName);
+ funcInfo = func.get("wbHigh");
+ if ("serum".equals(sampleType)) {
+ funcInfo = project.get("serumHigh");
+ }
+ } else { // 低浓度
+ var lowXValueSource = func.get("lowXValueSource").asInt();
+ valueX = this.calculateRatio(result, lowXValueSource, idChip, itemName);
+ funcInfo = func.get("wbLow");
+ if ("serum".equals(sampleType)) {
+ funcInfo = project.get("serumLow");
+ }
+ }
+ }
+
+ 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;
+ values.add(value);
+ System.out.println("扫描完成 : " + value);
+ }
+
+ System.out.println("扫描完成 : " + values.size());
+ }
+
+ /**
+ * @link https://iflytop1.feishu.cn/docx/UnRtdSG4qouMTaxzRb2cjiTTnZe
+ * @link https://iflytop1.feishu.cn/docx/A0CHdQL6OoTTCSx8MB7cQKEjnSc
+ * @param result
+ * @param xSource
+ * @return
+ */
+ private double calculateRatio(MyOptAlgo.AlgoResult result, int xSource, MdbIdChip idChip, String itenName) {
+ // 计算 ratio
+ Map 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, MdbIdChip.SCAN_TYPE_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, MdbIdChip.SCAN_TYPE_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);
+ }
+ }
}
diff --git a/src/main/java/com/dreamworks/boditech/model/MdbIdChip.java b/src/main/java/com/dreamworks/boditech/model/MdbIdChip.java
new file mode 100644
index 0000000..7e8809d
--- /dev/null
+++ b/src/main/java/com/dreamworks/boditech/model/MdbIdChip.java
@@ -0,0 +1,39 @@
+package com.dreamworks.boditech.model;
+public class MdbIdChip extends MyActiveRecord {
+ public static final Integer SCAN_TYPE_AUTO = 0;
+ public static final Integer SCAN_TYPE_F = 1;
+ public static final Integer SCAN_TYPE_T = 2;
+ public static final Integer SCAN_TYPE_F_T = 3;
+
+ @MyActiveRecordField
+ public Long id;
+
+ @MyActiveRecordField
+ public String item;
+
+ @MyActiveRecordField
+ public Integer itemCount;
+
+ @MyActiveRecordField
+ public String lotCode;
+
+ @MyActiveRecordField
+ public Integer expiredDate;
+
+ @MyActiveRecordField
+ public Integer version;
+
+ @MyActiveRecordField
+ public Integer scanType;
+
+ @MyActiveRecordField
+ public Integer scanPeakCount;
+
+ @MyActiveRecordField
+ public String items;
+
+ // get table name
+ public static String getTableName() {
+ return "bdt_id_chips";
+ }
+}
diff --git a/src/main/java/com/dreamworks/boditech/model/MyActiveRecord.java b/src/main/java/com/dreamworks/boditech/model/MyActiveRecord.java
new file mode 100644
index 0000000..2ee0576
--- /dev/null
+++ b/src/main/java/com/dreamworks/boditech/model/MyActiveRecord.java
@@ -0,0 +1,81 @@
+package com.dreamworks.boditech.model;
+import com.dreamworks.boditech.MyApplication;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+public class MyActiveRecord {
+ // find model by given id
+ public static T findById(Class clazz , int id ) {
+ var mapper = MyApplication.getBean(MyActiveRecordMapper.class);
+ var criteria = new MyActiveRecordCriteria();
+ criteria.tableName = MyActiveRecord.getTableNameFromModelClass(clazz);
+ criteria.limit = 1;
+ criteria.whereIs("id", id);
+ var rows = mapper.find(criteria);
+ if (rows.isEmpty()) {
+ return null;
+ }
+ return MyActiveRecord.fillModel(clazz, rows.get(0));
+ }
+
+ // get table name from model class
+ private static String getTableNameFromModelClass(Class modelClass) {
+ Method tableNameGetter = null;
+ try {
+ tableNameGetter = modelClass.getMethod("getTableName");
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ String tableName = null;
+ try {
+ tableName = (String)tableNameGetter.invoke(null);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ return tableName;
+ }
+
+ // fill model
+ private static T fillModel(Class modelClass, Map data) {
+ Constructor> modelConstructor = null;
+ try {
+ modelConstructor = modelClass.getDeclaredConstructor();
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+
+ T model = null;
+ try {
+ model = modelClass.cast(modelConstructor.newInstance());
+ } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+
+ for ( Map.Entry entry : data.entrySet() ) {
+ String key = entry.getKey();
+ Field field = null;
+ try {
+ field = model.getClass().getDeclaredField(key);
+ } catch (NoSuchFieldException e) {
+ continue ;
+ }
+
+ Class> fieldType = field.getType();
+ if ( !fieldType.isAssignableFrom(entry.getValue().getClass()) ) {
+ throw new RuntimeException("Attribute type mismatch: " + key);
+ }
+
+ field.setAccessible(true);
+ try {
+ field.set(model, entry.getValue());
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ return model;
+ }
+}
diff --git a/src/main/java/com/dreamworks/boditech/model/MyActiveRecordCriteria.java b/src/main/java/com/dreamworks/boditech/model/MyActiveRecordCriteria.java
new file mode 100644
index 0000000..086b73f
--- /dev/null
+++ b/src/main/java/com/dreamworks/boditech/model/MyActiveRecordCriteria.java
@@ -0,0 +1,46 @@
+package com.dreamworks.boditech.model;
+import java.util.ArrayList;
+import java.util.List;
+public class MyActiveRecordCriteria {
+ // condition
+ public static class Condition {
+ public String name;
+ public String operator;
+ public Object value;
+ }
+
+ // table name
+ public String tableName;
+ // conditions
+ public List conditions;
+ // limit
+ public Integer limit;
+ // offset
+ public Integer offset;
+
+ // where is
+ public void whereIs(String key, Object value) {
+ var condition = new Condition();
+ condition.name = key;
+ condition.operator = "=";
+ condition.value = value;
+ this.appendCondition(condition);
+ }
+
+ // where is not
+ public void whereIsNot(String key, Object value) {
+ var condition = new Condition();
+ condition.name = key;
+ condition.operator = "!=";
+ condition.value = value;
+ this.appendCondition(condition);
+ }
+
+ // append condition
+ private void appendCondition( Condition condition ) {
+ if ( this.conditions == null ) {
+ this.conditions = new ArrayList<>();
+ }
+ this.conditions.add(condition);
+ }
+}
diff --git a/src/main/java/com/dreamworks/boditech/model/MyActiveRecordField.java b/src/main/java/com/dreamworks/boditech/model/MyActiveRecordField.java
new file mode 100644
index 0000000..eff86bc
--- /dev/null
+++ b/src/main/java/com/dreamworks/boditech/model/MyActiveRecordField.java
@@ -0,0 +1,10 @@
+package com.dreamworks.boditech.model;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD})
+public @interface MyActiveRecordField {
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/dreamworks/boditech/model/MyActiveRecordMapper.java b/src/main/java/com/dreamworks/boditech/model/MyActiveRecordMapper.java
new file mode 100644
index 0000000..2920472
--- /dev/null
+++ b/src/main/java/com/dreamworks/boditech/model/MyActiveRecordMapper.java
@@ -0,0 +1,47 @@
+package com.dreamworks.boditech.model;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import java.util.List;
+import java.util.Map;
+@Mapper
+public interface MyActiveRecordMapper {
+ @Select(
+ ""
+ )
+ List