修正工步中值电压的保存查询与修订
原导入字段使用峰值和谷值,无法保存文件中的充放电中值。使用独立中值扩展字段贯通保存、查询和编辑,保留历史峰谷事实与重复导入冲突规则。
全量测试共287项,286项通过,1项依赖隔离MySQL的测试跳过;用户样例逐行保存核对及后端打包通过。
| | |
| | | return values; |
| | | } |
| | | |
| | | private static final Set<String> STEP_EXTENSION_FIELDS = Set.of("stepDuration", "startVoltage", "chargePeakVoltage", "dischargeValleyVoltage"); |
| | | private static final Set<String> STEP_EXTENSION_FIELDS = Set.of("stepDuration", "startVoltage", "chargeMedianVoltage", "dischargeMedianVoltage"); |
| | | |
| | | private void mergeStepExtension(String type, Object previous, Map<String, Object> fields, Map<String, Object> values) |
| | | { |
| | |
| | | case "voltage" -> "电压(V)"; case "current" -> "电流(A)"; case "capacity" -> "容量(Ah)"; case "energy" -> "能量(Wh)"; |
| | | case "temperature" -> "温度(℃)"; case "positivePotential" -> "正电位(V)"; case "negativePotential" -> "负电位(V)"; |
| | | case "endVoltage" -> "终止电压(V)"; case "startVoltage" -> "起始电压(V)"; case "stepDuration" -> "工步时间"; |
| | | case "chargePeakVoltage" -> "充电峰值电压(V)"; case "dischargeValleyVoltage" -> "放电谷值电压(V)"; |
| | | case "chargeMedianVoltage" -> "充电中值电压(V)"; case "dischargeMedianVoltage" -> "放电中值电压(V)"; |
| | | case "chargeCapacity" -> "充电容量(Ah)"; case "dischargeCapacity" -> "放电容量(Ah)"; |
| | | case "chargeEnergy" -> "充电能量(Wh)"; case "dischargeEnergy" -> "放电能量(Wh)"; default -> "充放电效率(%)"; |
| | | }; |
| | |
| | | Object extension = row.get("extensionMeasurementsJson"); |
| | | if (extension == null) continue; |
| | | Map<String, Object> values = com.alibaba.fastjson2.JSON.parseObject(extension.toString()); |
| | | for (String field : List.of("stepDuration", "startVoltage", "chargePeakVoltage", "dischargeValleyVoltage")) |
| | | // 中值与历史峰谷值是不同测量,不将旧扩展字段回退映射为中值。 |
| | | for (String field : List.of("stepDuration", "startVoltage", "chargeMedianVoltage", "dischargeMedianVoltage")) |
| | | if (values.containsKey(field)) row.put(field, values.get(field)); |
| | | } |
| | | return rows; |
| | |
| | | verify(mapper, never()).insertDataRevisionAction(any()); |
| | | } |
| | | |
| | | @Test void medianEditAuditsPreviousValuesAndPreservesLegacyFacts() |
| | | { |
| | | var action = action("EDIT", "step:7"); action.setDataType("STEP"); |
| | | String previous = "{\"chargeMedianVoltage\":0,\"dischargeMedianVoltage\":1.1709,\"chargePeakVoltage\":1.8,\"dischargeValleyVoltage\":0.8}"; |
| | | when(mapper.selectDataRecord(1L, "STEP", 7L)).thenReturn(Map.of("extension_measurements_json", previous)); |
| | | var item = new DataRevisionItem(); item.setStableIdentity("step:7"); |
| | | item.setFieldValues("{\"chargeMedianVoltage\":1.6431,\"dischargeMedianVoltage\":null}"); |
| | | service.submitAction(action, List.of(item)); |
| | | assertEquals(previous, JSON.parseArray(action.getBeforeContentJson()).getJSONObject(0).getString("extension_measurements_json")); |
| | | var before = JSON.parseArray(action.getChangesSummary()).getJSONObject(0).getJSONObject("before"); |
| | | assertEquals(new BigDecimal("1.1709"), before.getBigDecimal("dischargeMedianVoltage")); |
| | | var values = org.mockito.ArgumentCaptor.forClass(Map.class); |
| | | verify(mapper).updateDataRecord(eq(1L), eq("STEP"), eq(7L), values.capture()); |
| | | var extension = JSON.parseObject(values.getValue().get("extension_measurements_json").toString()); |
| | | assertEquals(new BigDecimal("1.6431"), extension.getBigDecimal("chargeMedianVoltage")); |
| | | assertNull(extension.get("dischargeMedianVoltage")); |
| | | assertEquals(new BigDecimal("1.8"), extension.getBigDecimal("chargePeakVoltage")); |
| | | assertEquals(new BigDecimal("0.8"), extension.getBigDecimal("dischargeValleyVoltage")); |
| | | } |
| | | |
| | | @Test void rejectsIdentityEditsAndNonNumericMeasurements() |
| | | { |
| | | when(mapper.selectDataRecord(1L, "REALTIME", 7L)).thenReturn(Map.of("voltage_v", 3.7)); |
| | |
| | | assertTrue(service.selectActions(new TTestDataRevisionAction()).isEmpty()); |
| | | } |
| | | |
| | | @Test void stepMedianVoltagesPersistAndRemainIndependentOfLegacyPeaks() |
| | | { |
| | | var imported = row(); |
| | | imported.put("chargeMedianVoltage", 0); |
| | | imported.put("dischargeMedianVoltage", 1.1709); |
| | | submit("STEP", List.of(imported)); |
| | | var stored = mapper.selectImportDataRecords(1L, "STEP").get(0); |
| | | var extension = JSON.parseObject(stored.get("extension_measurements_json").toString()); |
| | | assertNotNull(extension.getBigDecimal("chargeMedianVoltage")); |
| | | assertEquals(0, extension.getBigDecimal("chargeMedianVoltage").signum()); |
| | | assertEquals(new java.math.BigDecimal("1.1709"), extension.getBigDecimal("dischargeMedianVoltage")); |
| | | assertEquals(1, submit("STEP", List.of(imported)).get("skippedCount")); |
| | | |
| | | // 旧峰谷值仍是独立事实;不能在查询时作为缺失中值的回退来源。 |
| | | var queryMapper = org.mockito.Mockito.mock(TTestMapper.class); |
| | | var query = new com.battery.business.service.impl.TestDataQueryServiceImpl(queryMapper, |
| | | org.mockito.Mockito.mock(com.battery.business.service.ITTestService.class)); |
| | | var current = new LinkedHashMap<String, Object>(Map.of("extensionMeasurementsJson", extension.toJSONString())); |
| | | var legacy = new LinkedHashMap<String, Object>(Map.of("extensionMeasurementsJson", "{\"chargePeakVoltage\":1.8,\"dischargeValleyVoltage\":0.8}")); |
| | | org.mockito.Mockito.when(queryMapper.selectPersistedFinalStepRecords(1L)).thenReturn(List.of(current, legacy)); |
| | | var result = query.steps(1L); |
| | | assertEquals(new java.math.BigDecimal("1.1709"), result.get(0).get("dischargeMedianVoltage")); |
| | | assertEquals(0.0, ((Number) result.get(0).get("chargeMedianVoltage")).doubleValue()); |
| | | assertNull(result.get(1).get("chargeMedianVoltage")); |
| | | assertNull(result.get(1).get("dischargeMedianVoltage")); |
| | | |
| | | imported.put("dischargeMedianVoltage", 1.2); |
| | | assertTrue(assertThrows(RuntimeException.class, () -> submit("STEP", List.of(imported))).getMessage().contains("冲突拒绝")); |
| | | assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |
| | | |
| | | @Test void workbookImportsThreeSheetsWithOneRevisionAndSkipsRetry() |
| | | { |
| | | var sheets = Map.of("REALTIME", List.of(row(), row()), "STEP", List.of(row()), "CYCLE", List.of(row())); |
| | |
| | | assertEquals(count, submit(type, rows).get("importedCount")); |
| | | assertEquals(count, mapper.selectImportDataRecords(1L, type).size()); |
| | | assertEquals(count, submit(type, rows).get("skippedCount")); |
| | | if ("STEP".equals(type)) |
| | | { |
| | | var persisted = mapper.selectImportDataRecords(1L, type).stream().collect(java.util.stream.Collectors.toMap( |
| | | record -> record.get("source_record_id").toString(), record -> record)); |
| | | for (var input : rows) |
| | | { |
| | | var stored = JSON.parseObject(persisted.get(input.get("recordNo").toString()).get("extension_measurements_json").toString()); |
| | | for (String field : List.of("chargeMedianVoltage", "dischargeMedianVoltage")) |
| | | if (input.get(field) == null) assertNull(stored.get(field)); |
| | | else assertEquals(0, new java.math.BigDecimal(input.get(field).toString()).compareTo(stored.getBigDecimal(field)), field); |
| | | } |
| | | } |
| | | } |
| | | assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size()); |
| | | } |