|
@@ -0,0 +1,488 @@
|
|
|
|
|
+package com.jeesite.modules.scheduling;
|
|
|
|
|
+
|
|
|
|
|
+import com.jeesite.modules.mes.entity.MesGpy;
|
|
|
|
|
+import com.jeesite.modules.mes.entity.MesGpyMeas;
|
|
|
|
|
+import com.jeesite.modules.mes.service.MesGpyMeasService;
|
|
|
|
|
+import com.jeesite.modules.mes.service.MesGpyService;
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.context.annotation.Configuration;
|
|
|
|
|
+import org.springframework.scheduling.annotation.EnableScheduling;
|
|
|
|
|
+import org.springframework.scheduling.annotation.Scheduled;
|
|
|
|
|
+import org.w3c.dom.Document;
|
|
|
|
|
+import org.w3c.dom.Element;
|
|
|
|
|
+import org.w3c.dom.Node;
|
|
|
|
|
+import org.w3c.dom.NodeList;
|
|
|
|
|
+
|
|
|
|
|
+import javax.xml.parsers.DocumentBuilder;
|
|
|
|
|
+import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 光谱仪XML文件定时解析任务
|
|
|
|
|
+ * 定时扫描 F:\guangpuyi 目录下的 xml 文件,解析后写入 mes_gpy 表和 mes_gpy_meas 表,
|
|
|
|
|
+ * 成功后将文件移到 backup 子目录,避免重复导入。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author system
|
|
|
|
|
+ * @version 2026-05-21
|
|
|
|
|
+ */
|
|
|
|
|
+@Configuration
|
|
|
|
|
+@EnableScheduling
|
|
|
|
|
+public class GpyXmlScheduling {
|
|
|
|
|
+
|
|
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(GpyXmlScheduling.class);
|
|
|
|
|
+
|
|
|
|
|
+ /** 光谱仪 文件存放目录 */
|
|
|
|
|
+ private static final String XML_DIR = "F:\\guangpuyi";
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ private static final String BACKUP_DIR = "F:\\guangpuyi\\backup";
|
|
|
|
|
+
|
|
|
|
|
+ private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
|
|
|
|
+
|
|
|
|
|
+ // 自增ID计数器
|
|
|
|
|
+ private static final AtomicInteger ID_SEQ = new AtomicInteger((int)(System.currentTimeMillis() / 1000));
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private MesGpyService mesGpyService;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private MesGpyMeasService mesGpyMeasService;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 每 30 秒执行一次扫描
|
|
|
|
|
+ */
|
|
|
|
|
+ @Scheduled(fixedRate = 30000)
|
|
|
|
|
+ private void scanAndImport() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ File dir = new File(XML_DIR);
|
|
|
|
|
+ if (!dir.exists() || !dir.isDirectory()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ File[] xmlFiles = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".xml"));
|
|
|
|
|
+ if (xmlFiles == null || xmlFiles.length == 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保备份目录存在
|
|
|
|
|
+ File backupDir = new File(BACKUP_DIR);
|
|
|
|
|
+ if (!backupDir.exists()) {
|
|
|
|
|
+ backupDir.mkdirs();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (File xmlFile : xmlFiles) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ parseAndSave(xmlFile);
|
|
|
|
|
+ // 处理成功,移动到备份目录
|
|
|
|
|
+ File dest = new File(backupDir, xmlFile.getName());
|
|
|
|
|
+ if (dest.exists()) {
|
|
|
|
|
+ dest.delete();
|
|
|
|
|
+ }
|
|
|
|
|
+ boolean moved = xmlFile.renameTo(dest);
|
|
|
|
|
+ if (!moved) {
|
|
|
|
|
+ logger.warn("文件移动失败: {}", xmlFile.getName());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logger.info("光谱仪文件处理完成: {}", xmlFile.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("解析光谱仪文件失败: " + xmlFile.getName(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("光谱仪定时任务异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析单个 XML 文件并入库
|
|
|
|
|
+ */
|
|
|
|
|
+ private void parseAndSave(File xmlFile) throws Exception {
|
|
|
|
|
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
|
|
|
|
+ DocumentBuilder builder = factory.newDocumentBuilder();
|
|
|
|
|
+ Document doc = builder.parse(xmlFile);
|
|
|
|
|
+ doc.getDocumentElement().normalize();
|
|
|
|
|
+
|
|
|
|
|
+ // 获取 SampleResult 节点
|
|
|
|
|
+ NodeList sampleResultList = doc.getElementsByTagName("SampleResult");
|
|
|
|
|
+ if (sampleResultList.getLength() == 0) {
|
|
|
|
|
+ logger.warn("文件中没有 SampleResult 节点: {}", xmlFile.getName());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Element sampleResult = (Element) sampleResultList.item(0);
|
|
|
|
|
+
|
|
|
|
|
+ // --- 从 SampleResult 属性中提取字段 ---
|
|
|
|
|
+ String sampleResultName = sampleResult.getAttribute("Name");
|
|
|
|
|
+ String recalculationDateTimeStr = sampleResult.getAttribute("RecalculationDateTime");
|
|
|
|
|
+ String methodName = sampleResult.getAttribute("MethodName");
|
|
|
|
|
+ String origin = sampleResult.getAttribute("Origin");
|
|
|
|
|
+ String corrType = sampleResult.getAttribute("CorrType");
|
|
|
|
|
+ String reproTestType = sampleResult.getAttribute("ReproTestType");
|
|
|
|
|
+
|
|
|
|
|
+ // --- 从 SampleIDs 中提取字段 ---
|
|
|
|
|
+ String submitInspection = "";
|
|
|
|
|
+ String batchNumber = "";
|
|
|
|
|
+ String submissionTime = "";
|
|
|
|
|
+ String bottleModelSpec = "";
|
|
|
|
|
+ String bottleModelId = "";
|
|
|
|
|
+ String furnaceNumber = "";
|
|
|
|
|
+
|
|
|
|
|
+ NodeList sampleIds = sampleResult.getElementsByTagName("SampleID");
|
|
|
|
|
+ for (int i = 0; i < sampleIds.getLength(); i++) {
|
|
|
|
|
+ Element sampleId = (Element) sampleIds.item(i);
|
|
|
|
|
+ String type = sampleId.getAttribute("Type");
|
|
|
|
|
+ String isSampleName = sampleId.getAttribute("IsSampleName");
|
|
|
|
|
+ String idName = getTextContent(sampleId, "IDName");
|
|
|
|
|
+ String idValue = getTextContent(sampleId, "IDValue");
|
|
|
|
|
+
|
|
|
|
|
+ if ("GradeNorm".equals(type)) {
|
|
|
|
|
+ bottleModelSpec = idValue;
|
|
|
|
|
+ } else if ("GradeName".equals(type)) {
|
|
|
|
|
+ bottleModelId = idValue;
|
|
|
|
|
+ } else if ("Text".equals(type) && "False".equals(isSampleName)) {
|
|
|
|
|
+ // 按 IDName 精确匹配
|
|
|
|
|
+ if ("送检部门".equals(idName)) {
|
|
|
|
|
+ submitInspection = idValue;
|
|
|
|
|
+ } else if ("批次号".equals(idName)) {
|
|
|
|
|
+ batchNumber = idValue;
|
|
|
|
|
+ } else if ("送检时间".equals(idName)) {
|
|
|
|
|
+ submissionTime = idValue;
|
|
|
|
|
+ } else if (idName != null && idName.contains("炉号/包数")) {
|
|
|
|
|
+ furnaceNumber = idValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- 从 MeasurementStatistics > Measurement 中提取检测结果 ---
|
|
|
|
|
+ String checkType = "";
|
|
|
|
|
+ String checkStatus = "";
|
|
|
|
|
+ String gradeVerificationName = "";
|
|
|
|
|
+ String gradeVerificationSimilarity = "";
|
|
|
|
|
+
|
|
|
|
|
+ NodeList statisticsList = sampleResult.getElementsByTagName("MeasurementStatistics");
|
|
|
|
|
+ if (statisticsList.getLength() > 0) {
|
|
|
|
|
+ Element statistics = (Element) statisticsList.item(0);
|
|
|
|
|
+ NodeList measurementList = statistics.getElementsByTagName("Measurement");
|
|
|
|
|
+ if (measurementList.getLength() > 0) {
|
|
|
|
|
+ Element measurement = (Element) measurementList.item(0);
|
|
|
|
|
+ checkType = measurement.getAttribute("CheckType");
|
|
|
|
|
+ checkStatus = measurement.getAttribute("CheckStatus");
|
|
|
|
|
+ gradeVerificationName = measurement.getAttribute("GradeName");
|
|
|
|
|
+ gradeVerificationSimilarity = measurement.getAttribute("GradeSimilarityPercentage");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- 从 MeasurementReplicate 中提取最早的测量时间 ---
|
|
|
|
|
+ Date measureDateTime = null;
|
|
|
|
|
+ NodeList replicates = sampleResult.getElementsByTagName("MeasurementReplicate");
|
|
|
|
|
+ if (replicates.getLength() > 0) {
|
|
|
|
|
+ Element firstReplicate = (Element) replicates.item(0);
|
|
|
|
|
+ String mdt = firstReplicate.getAttribute("MeasureDateTime");
|
|
|
|
|
+ if (mdt != null && !mdt.isEmpty()) {
|
|
|
|
|
+ measureDateTime = SDF.parse(mdt);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- 解析重新计算时间 ---
|
|
|
|
|
+ Date recalculationDateTime = null;
|
|
|
|
|
+ if (recalculationDateTimeStr != null && !recalculationDateTimeStr.isEmpty()) {
|
|
|
|
|
+ recalculationDateTime = SDF.parse(recalculationDateTimeStr);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // --- 构建 mes_gpy 实体并保存 ---
|
|
|
|
|
+ String gpyId = String.valueOf(ID_SEQ.incrementAndGet());
|
|
|
|
|
+
|
|
|
|
|
+ // 从文件名提取编码 1#集中炉_20260521152155.xml → 20260521152155
|
|
|
|
|
+ String code = extractCodeFromFileName(xmlFile.getName());
|
|
|
|
|
+
|
|
|
|
|
+ MesGpy mesGpy = new MesGpy();
|
|
|
|
|
+ mesGpy.setId(gpyId);
|
|
|
|
|
+ mesGpy.setIsNewRecord(true);
|
|
|
|
|
+ mesGpy.setSampleResultName(sampleResultName);
|
|
|
|
|
+ mesGpy.setMeasureDateTime(measureDateTime);
|
|
|
|
|
+ mesGpy.setRecalculationDateTime(recalculationDateTime);
|
|
|
|
|
+ mesGpy.setMethodName(methodName);
|
|
|
|
|
+ mesGpy.setOrigin(origin);
|
|
|
|
|
+ mesGpy.setCheckType(checkType);
|
|
|
|
|
+ mesGpy.setCheckStatus(checkStatus);
|
|
|
|
|
+ mesGpy.setGradeVerificationName(gradeVerificationName);
|
|
|
|
|
+ mesGpy.setGradeVerificationSimilarity(gradeVerificationSimilarity);
|
|
|
|
|
+ mesGpy.setCorrectionType(corrType);
|
|
|
|
|
+ mesGpy.setOutlierTestType(reproTestType);
|
|
|
|
|
+ mesGpy.setSubmitInspection(submitInspection);
|
|
|
|
|
+ mesGpy.setBatchNumber(batchNumber);
|
|
|
|
|
+ mesGpy.setSubmissionTime(submissionTime);
|
|
|
|
|
+ mesGpy.setBottleModelSpecification(bottleModelSpec);
|
|
|
|
|
+ mesGpy.setBottleModelId(bottleModelId);
|
|
|
|
|
+ mesGpy.setFurnaceNumber(furnaceNumber);
|
|
|
|
|
+ mesGpy.setCode(code);
|
|
|
|
|
+
|
|
|
|
|
+ mesGpyService.save(mesGpy);
|
|
|
|
|
+ logger.info("光谱仪数据入库成功: name={}, method={}, checkStatus={}",
|
|
|
|
|
+ sampleResultName, methodName, checkStatus);
|
|
|
|
|
+
|
|
|
|
|
+ // 解析元素数据
|
|
|
|
|
+ parseAndSaveElements(sampleResult, gpyId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析元素测量数据
|
|
|
|
|
+ * 从 MeasurementReplicates 中取每次激发的 Reported 值,
|
|
|
|
|
+ * 从 MeasurementStatistics 中取 Mean 值和 AcceptanceLimit
|
|
|
|
|
+ */
|
|
|
|
|
+ private void parseAndSaveElements(Element sampleResult, String gpyId) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 收集各次激发的元素 Reported 值
|
|
|
|
|
+ // key=元素名称, value=按 replicate 序号存的值列表
|
|
|
|
|
+ Map<String, List<String>> replicateValues = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ NodeList replicateNodes = sampleResult.getElementsByTagName("MeasurementReplicate");
|
|
|
|
|
+ for (int r = 0; r < replicateNodes.getLength(); r++) {
|
|
|
|
|
+ Element replicate = (Element) replicateNodes.item(r);
|
|
|
|
|
+ // 取 Measurement 下的 Elements
|
|
|
|
|
+ NodeList measurementNodes = replicate.getElementsByTagName("Measurement");
|
|
|
|
|
+ if (measurementNodes.getLength() == 0) continue;
|
|
|
|
|
+ Element measurement = (Element) measurementNodes.item(0);
|
|
|
|
|
+ NodeList elements = measurement.getElementsByTagName("Element");
|
|
|
|
|
+
|
|
|
|
|
+ for (int e = 0; e < elements.getLength(); e++) {
|
|
|
|
|
+ Element elem = (Element) elements.item(e);
|
|
|
|
|
+ String elemName = elem.getAttribute("ElementName");
|
|
|
|
|
+ String elemType = elem.getAttribute("Type");
|
|
|
|
|
+ // 跳过伪元素(如 OR)
|
|
|
|
|
+ if ("Pseudo".equals(elemType)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 取 StatType="Reported" 且 Type="Conc" 的 ResultValue
|
|
|
|
|
+ String reportedValue = getReportedValue(elem);
|
|
|
|
|
+
|
|
|
|
|
+ replicateValues.computeIfAbsent(elemName, k -> new ArrayList<>());
|
|
|
|
|
+ List<String> vals = replicateValues.get(elemName);
|
|
|
|
|
+ // 确保按序号填充
|
|
|
|
|
+ while (vals.size() <= r) {
|
|
|
|
|
+ vals.add("");
|
|
|
|
|
+ }
|
|
|
|
|
+ vals.set(r, reportedValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 从 MeasurementStatistics 中取 Mean 和 AcceptanceLimit
|
|
|
|
|
+ Map<String, String> meanMap = new LinkedHashMap<>();
|
|
|
|
|
+ Map<String, String> lowerLimitMap = new LinkedHashMap<>();
|
|
|
|
|
+ Map<String, String> upperLimitMap = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ NodeList statisticsList = sampleResult.getElementsByTagName("MeasurementStatistics");
|
|
|
|
|
+ if (statisticsList.getLength() > 0) {
|
|
|
|
|
+ Element statistics = (Element) statisticsList.item(0);
|
|
|
|
|
+ NodeList measurementNodes = statistics.getElementsByTagName("Measurement");
|
|
|
|
|
+ if (measurementNodes.getLength() > 0) {
|
|
|
|
|
+ Element measurement = (Element) measurementNodes.item(0);
|
|
|
|
|
+ NodeList elements = measurement.getElementsByTagName("Element");
|
|
|
|
|
+
|
|
|
|
|
+ for (int e = 0; e < elements.getLength(); e++) {
|
|
|
|
|
+ Element elem = (Element) elements.item(e);
|
|
|
|
|
+ String elemName = elem.getAttribute("ElementName");
|
|
|
|
|
+ String elemType = elem.getAttribute("Type");
|
|
|
|
|
+ if ("Pseudo".equals(elemType)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 取 Mean 值(StatType="Mean", Type="Conc")
|
|
|
|
|
+ String meanValue = getMeanValue(elem);
|
|
|
|
|
+ meanMap.put(elemName, meanValue);
|
|
|
|
|
+
|
|
|
|
|
+ // 取 AcceptanceLimit(从 Reported 的 ResultValueLimits 中)
|
|
|
|
|
+ String[] limits = getAcceptanceLimits(elem);
|
|
|
|
|
+ lowerLimitMap.put(elemName, limits[0]);
|
|
|
|
|
+ upperLimitMap.put(elemName, limits[1]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 组装并保存 mes_gpy_meas
|
|
|
|
|
+ for (Map.Entry<String, List<String>> entry : replicateValues.entrySet()) {
|
|
|
|
|
+ String elemName = entry.getKey();
|
|
|
|
|
+ List<String> reps = entry.getValue();
|
|
|
|
|
+
|
|
|
|
|
+ MesGpyMeas meas = new MesGpyMeas();
|
|
|
|
|
+ meas.setId(String.valueOf(ID_SEQ.incrementAndGet()));
|
|
|
|
|
+ meas.setIsNewRecord(true);
|
|
|
|
|
+ meas.setMesGpyId(gpyId);
|
|
|
|
|
+ meas.setElementName(elemName);
|
|
|
|
|
+ meas.setReplicate0(reps.size() > 0 ? reps.get(0) : "");
|
|
|
|
|
+ meas.setReplicate1(reps.size() > 1 ? reps.get(1) : "");
|
|
|
|
|
+ meas.setMean(meanMap.getOrDefault(elemName, ""));
|
|
|
|
|
+ meas.setLowerAcceptanceLimit(lowerLimitMap.getOrDefault(elemName, ""));
|
|
|
|
|
+ meas.setUpperAcceptanceLimit(upperLimitMap.getOrDefault(elemName, ""));
|
|
|
|
|
+
|
|
|
|
|
+ mesGpyMeasService.save(meas);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logger.info("光谱仪元素数据入库成功: gpyId={}, 元素数量={}", gpyId, replicateValues.size());
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ logger.error("解析元素数据失败: gpyId=" + gpyId, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 Element 节点中获取 Type="Conc" + StatType="Reported" 的 ResultValue
|
|
|
|
|
+ * 根据 CalibrationStatus 添加 < 或 > 前缀:
|
|
|
|
|
+ * UnderRange → "<值"
|
|
|
|
|
+ * OverRange → ">值"
|
|
|
|
|
+ * InRange → "值"
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getReportedValue(Element elem) {
|
|
|
|
|
+ NodeList results = elem.getElementsByTagName("ElementResult");
|
|
|
|
|
+ for (int i = 0; i < results.getLength(); i++) {
|
|
|
|
|
+ Element result = (Element) results.item(i);
|
|
|
|
|
+ if ("Conc".equals(result.getAttribute("Type"))
|
|
|
|
|
+ && "Reported".equals(result.getAttribute("StatType"))) {
|
|
|
|
|
+ String value = getTextContent(result, "ResultValue");
|
|
|
|
|
+ String calibStatus = result.getAttribute("CalibrationStatus");
|
|
|
|
|
+ return addRangePrefix(value, calibStatus);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果没有 Reported,取 StatType="None" 且 Type="Conc"
|
|
|
|
|
+ for (int i = 0; i < results.getLength(); i++) {
|
|
|
|
|
+ Element result = (Element) results.item(i);
|
|
|
|
|
+ if ("Conc".equals(result.getAttribute("Type"))
|
|
|
|
|
+ && "None".equals(result.getAttribute("StatType"))) {
|
|
|
|
|
+ String value = getTextContent(result, "ResultValue");
|
|
|
|
|
+ String calibStatus = result.getAttribute("CalibrationStatus");
|
|
|
|
|
+ return addRangePrefix(value, calibStatus);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 根据 CalibrationStatus 添加范围前缀,并将科学计数法转为普通小数
|
|
|
|
|
+ */
|
|
|
|
|
+ private String addRangePrefix(String value, String calibrationStatus) {
|
|
|
|
|
+ if (value == null || value.isEmpty()) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ // 将科学计数法转为普通小数格式(如 5E-05 → 0.00005)
|
|
|
|
|
+ value = convertScientificToDecimal(value);
|
|
|
|
|
+ if ("UnderRange".equals(calibrationStatus)) {
|
|
|
|
|
+ return "<" + value;
|
|
|
|
|
+ } else if ("OverRange".equals(calibrationStatus)) {
|
|
|
|
|
+ return ">" + value;
|
|
|
|
|
+ }
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将科学计数法字符串转为普通小数字符串
|
|
|
|
|
+ * 例如: "5E-05" → "0.00005", "1.2E-03" → "0.0012"
|
|
|
|
|
+ * 非科学计数法的直接原样返回
|
|
|
|
|
+ */
|
|
|
|
|
+ private String convertScientificToDecimal(String value) {
|
|
|
|
|
+ if (value == null || value.isEmpty()) {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 判断是否包含 E 或 e(科学计数法标志)
|
|
|
|
|
+ if (value.toUpperCase().contains("E")) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ java.math.BigDecimal bd = new java.math.BigDecimal(value);
|
|
|
|
|
+ return bd.stripTrailingZeros().toPlainString();
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ // 解析失败则原样返回
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 MeasurementStatistics 的 Element 节点中获取平均值
|
|
|
|
|
+ * 优先取 StatType="Reported"(修剪后的报告值),与 replicate 取值逻辑一致
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getMeanValue(Element elem) {
|
|
|
|
|
+ NodeList results = elem.getElementsByTagName("ElementResult");
|
|
|
|
|
+ // 优先取 Reported
|
|
|
|
|
+ for (int i = 0; i < results.getLength(); i++) {
|
|
|
|
|
+ Element result = (Element) results.item(i);
|
|
|
|
|
+ if ("Conc".equals(result.getAttribute("Type"))
|
|
|
|
|
+ && "Reported".equals(result.getAttribute("StatType"))) {
|
|
|
|
|
+ String value = getTextContent(result, "ResultValue");
|
|
|
|
|
+ String calibStatus = result.getAttribute("CalibrationStatus");
|
|
|
|
|
+ return addRangePrefix(value, calibStatus);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 没有 Reported 则取 Mean
|
|
|
|
|
+ for (int i = 0; i < results.getLength(); i++) {
|
|
|
|
|
+ Element result = (Element) results.item(i);
|
|
|
|
|
+ if ("Conc".equals(result.getAttribute("Type"))
|
|
|
|
|
+ && "Mean".equals(result.getAttribute("StatType"))) {
|
|
|
|
|
+ String value = getTextContent(result, "ResultValue");
|
|
|
|
|
+ String calibStatus = result.getAttribute("CalibrationStatus");
|
|
|
|
|
+ return addRangePrefix(value, calibStatus);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 Element 节点中获取 AcceptanceLimit
|
|
|
|
|
+ * 优先从 StatType="Reported" 的 ResultValueLimits 中取
|
|
|
|
|
+ * @return [lowerAcceptanceLimit, upperAcceptanceLimit]
|
|
|
|
|
+ */
|
|
|
|
|
+ private String[] getAcceptanceLimits(Element elem) {
|
|
|
|
|
+ String lower = "";
|
|
|
|
|
+ String upper = "";
|
|
|
|
|
+
|
|
|
|
|
+ NodeList results = elem.getElementsByTagName("ElementResult");
|
|
|
|
|
+ for (int i = 0; i < results.getLength(); i++) {
|
|
|
|
|
+ Element result = (Element) results.item(i);
|
|
|
|
|
+ if ("Conc".equals(result.getAttribute("Type"))
|
|
|
|
|
+ && "Reported".equals(result.getAttribute("StatType"))) {
|
|
|
|
|
+ NodeList limits = result.getElementsByTagName("ResultValueLimit");
|
|
|
|
|
+ for (int j = 0; j < limits.getLength(); j++) {
|
|
|
|
|
+ Element limit = (Element) limits.item(j);
|
|
|
|
|
+ String limitType = limit.getAttribute("Type");
|
|
|
|
|
+ String limitValue = convertScientificToDecimal(limit.getTextContent().trim());
|
|
|
|
|
+ if ("LowerAcceptanceLimit".equals(limitType)) {
|
|
|
|
|
+ lower = limitValue;
|
|
|
|
|
+ } else if ("UpperAcceptanceLimit".equals(limitType)) {
|
|
|
|
|
+ upper = limitValue;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 找到 Reported 就不再继续
|
|
|
|
|
+ if (!lower.isEmpty() || !upper.isEmpty()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return new String[]{lower, upper};
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取子元素文本内容
|
|
|
|
|
+ */
|
|
|
|
|
+ private String getTextContent(Element parent, String tagName) {
|
|
|
|
|
+ NodeList list = parent.getElementsByTagName(tagName);
|
|
|
|
|
+ if (list.getLength() > 0) {
|
|
|
|
|
+ String text = list.item(0).getTextContent();
|
|
|
|
|
+ return text != null ? text.trim() : "";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String extractCodeFromFileName(String fileName) {
|
|
|
|
|
+ // 去掉扩展名
|
|
|
|
|
+ String name = fileName;
|
|
|
|
|
+ int dotIdx = name.lastIndexOf('.');
|
|
|
|
|
+ if (dotIdx > 0) {
|
|
|
|
|
+ name = name.substring(0, dotIdx);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 取最后一个下划线后面的部分
|
|
|
|
|
+ int underscoreIdx = name.lastIndexOf('_');
|
|
|
|
|
+ if (underscoreIdx >= 0 && underscoreIdx < name.length() - 1) {
|
|
|
|
|
+ return name.substring(underscoreIdx + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ return name;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|