修复化成报表漏识别导入充放电工步
导入记录没有协议类型时按明确工步名称识别方向,协议类型仍优先。静置独立分类,避免误报未知;验证主动更新来源保留人工值且不改写历史快照。
| | |
| | | Map<Integer, ChargeStep> found = new java.util.TreeMap<>(); |
| | | // 实时记录只补充实际范围身份,不代替缺失的工步终止电压。 |
| | | for (var row : realtime) if (cycleNo(row) == cycle && number(row.get("stepNo")) > 0) { |
| | | String type = String.valueOf(row.getOrDefault("stepType", "")); |
| | | found.putIfAbsent(number(row.get("stepNo")), new ChargeStep(number(row.get("stepNo")), String.valueOf(row.getOrDefault("stepName", "")), type, null, directions.resolve(type), null)); |
| | | String type = Objects.toString(row.get("stepType"), ""); |
| | | String name = Objects.toString(row.get("stepName"), ""); |
| | | found.putIfAbsent(number(row.get("stepNo")), new ChargeStep(number(row.get("stepNo")), name, type, null, stepDirection(type, name), null)); |
| | | } |
| | | if (rows != null) for (var row : rows) if (Objects.equals(battery.getBatteryNo(), row.get("batteryNo")) && cycleNo(row) == cycle && number(row.get("stepNo")) > 0) { |
| | | String type = String.valueOf(row.getOrDefault("stepType", "")); |
| | | found.put(number(row.get("stepNo")), new ChargeStep(number(row.get("stepNo")), String.valueOf(row.getOrDefault("stepName", "")), type, decimal(row.get("endVoltage")), directions.resolve(type), decimal(row.get("capacity")))); |
| | | String type = Objects.toString(row.get("stepType"), ""); |
| | | String name = Objects.toString(row.get("stepName"), ""); |
| | | found.put(number(row.get("stepNo")), new ChargeStep(number(row.get("stepNo")), name, type, decimal(row.get("endVoltage")), stepDirection(type, name), decimal(row.get("capacity")))); |
| | | } |
| | | result.put(cycle, new ArrayList<>(found.values())); |
| | | } |
| | | return result; |
| | | } |
| | | private int number(Object value) { return value instanceof Number n ? n.intValue() : 0; } |
| | | |
| | | private Direction stepDirection(String type, String name) |
| | | { |
| | | // 甲方 Excel 只提供工步名称;有协议类型时禁止用名称覆盖未知/混合类型。 |
| | | if (!type.isBlank()) return directions.resolve(type); |
| | | return switch (name.trim()) { |
| | | case "恒流充电" -> Direction.CHARGE; |
| | | case "恒流放电" -> Direction.DISCHARGE; |
| | | case "搁置", "静置" -> Direction.REST; |
| | | default -> Direction.UNKNOWN; |
| | | }; |
| | | } |
| | | private BigDecimal decimal(Object value) { return value instanceof Number n ? new BigDecimal(n.toString()) : null; } |
| | | |
| | | private Set<String> valueKeys(List<FormationField> fields, List<Item> items) |
| | |
| | | package com.battery.device.api; |
| | | |
| | | /** 将已保存的实际工步类型解释为充电、放电或未知,不依据测量值猜测。 */ |
| | | /** 将已保存的实际工步类型解释为充电、放电、静置或未知,不依据测量值猜测。 */ |
| | | public interface StepDirectionResolver |
| | | { |
| | | enum Direction { CHARGE, DISCHARGE, UNKNOWN } |
| | | enum Direction { CHARGE, DISCHARGE, REST, UNKNOWN } |
| | | |
| | | /** 返回有明确协议依据的方向;未定义或混合类型保持未知。 */ |
| | | Direction resolve(String stepType); |
| | |
| | | public Direction resolve(String stepType) |
| | | { |
| | | String type = stepType == null ? "" : stepType.trim().toLowerCase(Locale.ROOT); |
| | | if ("rest".equals(type)) return Direction.REST; |
| | | if (Set.of("cc", "cv", "cccv", "cp", "cr", "charge", "charging").contains(type)) return Direction.CHARGE; |
| | | if (Set.of("dc", "dp", "dr", "dv", "cccd", "discharge", "discharging").contains(type)) return Direction.DISCHARGE; |
| | | return Direction.UNKNOWN; |
| | |
| | | assertEquals(Direction.CHARGE, resolver.resolve(type)); |
| | | for (String type : List.of("dc", "dp", "dr", "dv", "cccd", "DISCHARGE")) |
| | | assertEquals(Direction.DISCHARGE, resolver.resolve(type)); |
| | | for (String type : List.of("pcccv", "pulse", "sim", "rest", "恒流充电", "0.1C", "")) |
| | | assertEquals(Direction.REST, resolver.resolve(" rest ")); |
| | | for (String type : List.of("pcccv", "pulse", "sim", "恒流充电", "0.1C", "")) |
| | | assertEquals(Direction.UNKNOWN, resolver.resolve(type)); |
| | | assertEquals(Direction.UNKNOWN, resolver.resolve(null)); |
| | | } |
| | |
| | | } |
| | | |
| | | @Test |
| | | void importedStepNamesSupplyDirectionWithoutInventingProtocolTypes() throws Exception |
| | | { |
| | | var discharge = step(1, null, "1", "0.25"); discharge.put("stepName", "恒流放电"); |
| | | var rest = step(2, null, "1", null); rest.put("stepName", "搁置"); |
| | | var charge = step(3, null, "4.2", "0.2"); charge.put("stepName", "恒流充电"); |
| | | var ambiguous = step(4, "pulse", "99", "99"); ambiguous.put("stepName", "恒流充电"); |
| | | when(data.steps(1L)).thenReturn(List.of(discharge, rest, charge, ambiguous)); |
| | | var saved = create(request()); |
| | | var choices = saved.getJSONObject("settings").getJSONObject("curveStepSelections").getJSONObject("1|11|1"); |
| | | assertEquals(List.of(3), choices.getList("charge", Integer.class)); |
| | | assertEquals(List.of(1), choices.getList("discharge", Integer.class)); |
| | | assertEquals(250, saved.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact()); |
| | | var steps = saved.getJSONArray("items").getJSONObject(0).getJSONObject("chargeSteps").getJSONArray("1"); |
| | | assertEquals("REST", steps.getJSONObject(1).getString("direction")); |
| | | assertEquals("UNKNOWN", steps.getJSONObject(3).getString("direction")); |
| | | } |
| | | |
| | | @Test |
| | | void explicitSourceRefreshKeepsManualValuesAndHistoricalClassification() throws Exception |
| | | { |
| | | var imported = step(1, null, "1", "0.25"); |
| | | when(data.steps(1L)).thenReturn(List.of(imported)); |
| | | var first = create(request()); |
| | | long id = first.getLongValue("id"); |
| | | assertNull(first.getJSONObject("calculatedValues").get("1|11|1|capacity1v")); |
| | | imported.put("stepName", "恒流放电"); |
| | | var refreshed = JSON.parseObject(JSON.toJSONString(service.refresh(id, new CompareReportEditRequest.Refresh(1, "重新读取实际工步"), 1, "操作员"))); |
| | | assertEquals(250, refreshed.getJSONObject("calculatedValues").getBigDecimal("1|11|1|capacity1v").intValueExact()); |
| | | assertEquals("2", refreshed.getJSONObject("settings").getJSONObject("values").getString("1|11|1|manual")); |
| | | var historical = JSON.parseObject(JSON.toJSONString(service.get(id, 1))); |
| | | assertNull(historical.getJSONObject("calculatedValues").get("1|11|1|capacity1v")); |
| | | assertEquals("UNKNOWN", historical.getJSONArray("items").getJSONObject(0).getJSONObject("chargeSteps").getJSONArray("1").getJSONObject(0).getString("direction")); |
| | | } |
| | | |
| | | @Test |
| | | void automaticDirectionSelectionExcludesUnknownAndKeepsAllRealChargeSteps() throws Exception |
| | | { |
| | | when(data.steps(1L)).thenReturn(List.of(step(1,"cc","4.1","0.1"),step(2,"unknown","99","99"),step(3,"cc","4.2","0.2"),step(4,"dc","1","0.25"))); |