yeg
4 天以前 3194ad3ce5526148df9009aeaa917b888dc53b40
新威的工步名称按设备工步类型填方向名称

cap_step.step_type 实测 1 充电 / 2 放电 / 4 搁置,但工步记录与实时行此前把 stepName
硬编码成 null,「实时」「工步」两页的工步名称一列全是 -。现在按该类型解释出的方向名称
填写,与 22 所同词(充电/放电/搁置);未确认取值(实测 3/5/7/13)留空显示 -,不按电流
符号或容量出现在哪一组猜方向。step_type 仍原样保留设备给的数字。

方向名称词表统一放到 StepDirectionResolver.Direction.displayName(),推式来源的枚举改为
委托它,两处不再各写一套词;两条路径的分工随之明确为「来源给了类型就按类型解释,来源没
有类型列才按已核对的列语义判定」。见 ADR-0067 §4b 补记。
5个文件已修改
72 ■■■■■ 已修改文件
battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-device-api/src/main/java/com/battery/device/api/StepDirectionResolver.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
battery-business/src/main/java/com/battery/business/service/impl/PushIngestAdapter.java
@@ -17,6 +17,7 @@
import com.alibaba.fastjson2.JSONObject;
import com.battery.device.api.DeviceAdapter;
import com.battery.device.api.DeviceAdapterException;
import com.battery.device.api.StepDirectionResolver.Direction;
import com.battery.device.api.TestDataObservationAdapter;
import com.battery.device.api.model.AdapterCapabilities;
import com.battery.device.api.model.ChannelIdentity;
@@ -439,23 +440,24 @@
     */
    private enum StepDirection
    {
        CHARGE("charge", "充电"), DISCHARGE("discharge", "放电"), REST("rest", "搁置"), UNKNOWN(null, null);
        CHARGE("charge", Direction.CHARGE), DISCHARGE("discharge", Direction.DISCHARGE),
        REST("rest", Direction.REST), UNKNOWN(null, Direction.UNKNOWN);
        private final String label;
        private final String displayName;
        private final Direction direction;
        StepDirection(String label, String displayName) { this.label = label; this.displayName = displayName; }
        StepDirection(String label, Direction direction) { this.label = label; this.direction = direction; }
        /** 写进工步记录的类型:来源没给类型时交出的是判定出的方向名称,未知方向留空。 */
        String label() { return label; }
        /**
         * 交出去的工步名称。这类来源的工步方案里只有「步_N」这种标号,没有描述性名称
         * (2026-10-09 现场核对 .pcj:<Step StepLabel="步_1"> + CtrlTypeName=Rest/Current);
         * 而甲方软件那一列「状态」显示的就是充电/放电/搁置——所以用同一套词当工步名称,
         * 值同样来自电流符号判定,**不是设备给的名称**(现场清单与 ADR-0067 都记了这一条)。
         * 交出去的工步名称:方向名称的词表与显示口径统一在 {@link Direction#displayName()}(甲方那一列的原词)。
         * 这类来源的工步方案里只有「步_N」这种标号,没有描述性名称(2026-10-09 现场核对 .pcj:
         * {@code <Step StepLabel="步_1">} + CtrlTypeName=Rest/Current),名称是按电流符号判定出来的,
         * **不是设备给的名称**;方向未知时留空。
         */
        String displayName() { return displayName; }
        String displayName() { return direction.displayName(); }
        /** 该工步自身累计的单项值。搁置段实测两向都不增长,所以它的累计是 0 而不是缺失。 */
        BigDecimal own(BigDecimal charge, BigDecimal discharge)
battery-device-api/src/main/java/com/battery/device/api/StepDirectionResolver.java
@@ -3,7 +3,20 @@
/** 将已保存的实际工步类型解释为充电、放电、静置或未知,不依据测量值猜测。 */
public interface StepDirectionResolver
{
    enum Direction { CHARGE, DISCHARGE, REST, UNKNOWN }
    enum Direction
    {
        CHARGE("充电"), DISCHARGE("放电"), REST("搁置"), UNKNOWN(null);
        private final String displayName;
        Direction(String displayName) { this.displayName = displayName; }
        /**
         * 界面上显示的方向名称,用甲方「实时」页那一列的同一个词;工步名称填的就是它(ADR-0067 §4b)。
         * 方向未确认时为空:调用方不得用默认名把未知说成已知。
         */
        public String displayName() { return displayName; }
    }
    /** 返回有明确协议依据的方向;未定义或混合类型保持未知。 */
    Direction resolve(String stepType);
battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java
@@ -21,7 +21,9 @@
    /**
     * 返回该通道的工步统计。来源有工步层时读设备工步层并原样交出;来源没有工步层时由适配器
     * 从采样段推导(ADR-0067),此时记录里的方向是推导出的方向名称(见 {@link StepRecord})。
     * 从采样段推导(ADR-0067)。两种形态的记录里 {@code stepName} 都是**方向名称**
     * ({@link com.battery.device.api.StepDirectionResolver.Direction#displayName()}):来源给出工步类型时按该类型解释,
     * 来源没有类型列时按已核对的列语义判定;判定不出方向时留空,不写默认名。
     * 未结束的段不交出,也不推导来源根本没有的工步层。
     */
    List<StepRecord> stepRecords(ChannelIdentity channel);
@@ -101,11 +103,14 @@
    /**
     * 一条设备工步层记录。来源给出工步类型时 {@code stepType} 原样保留(新威为数字枚举),
     * 方向由调用方按该类型解释,适配器不按电流符号改写它。
     * 适配器不按电流符号改写它。
     *
     * <p>来源**没有**工步类型列时(推式来源的厂商库里既没有工步层也没有循环汇总表,ADR-0067),
     * 适配器交出的是按已核对列语义判定出的方向名称({@code charge} / {@code discharge} / {@code rest}),
     * 判定不出来时留空;此时该工步的其余一切仍是设备采样行上的原文事实。
     * <p>{@code stepName} 是**方向名称**(`充电` / `放电` / `搁置`,ADR-0067 §4b):来源给出工步类型时
     * 按该类型解释(新威的 {@code 1}/{@code 2}/{@code 4}),来源没有类型列时(推式来源的厂商库里既没有
     * 工步层也没有循环汇总表,ADR-0067)按已核对列语义判定。方向判不出来时留空——**不是设备给的名称**,
     * 界面据此标注来源,调用方不得用默认名把未知说成已知。
     *
     * <p>来源没有工步层时,除方向名称外该工步的其余一切仍是设备采样行上的原文事实。
     *
     * <p>{@code endVoltageV} 与 {@code endCurrentA} 是该工步结束时的设备读数。
     * {@code capacityAh} 与 {@code energyWh} 是该工步自身累计的单项值,只在方向已确认时给出;
battery-device-neware/src/main/java/com/battery/device/neware/NewareMysqlAdapter.java
@@ -54,7 +54,8 @@
 * </ul>
 *
 * <p>单位换算按实测公式:电压与电流除以 10000(原单位 0.1 mV / 0.1 mA),容量与能量除以 36000000
 * (原单位 1e-4 C / 1e-4 J)。工步类型保留设备给出的数字,不按电流符号猜测方向;
 * (原单位 1e-4 C / 1e-4 J)。工步类型保留设备给出的数字,不按电流符号猜测方向;工步名称由该类型
 * 经 {@link NewareStepDirectionResolver} 解释出的方向名称填写(未确认取值留空,ADR-0067 §4b);
 * 内阻与温度的单位语义仍未确认,因此不对外暴露。
 */
public final class NewareMysqlAdapter implements DeviceAdapter, TestDataObservationAdapter
@@ -436,8 +437,8 @@
    }
    /**
     * 一条工步记录。工步时间取设备给的 {@code test_time}(毫秒 → 秒),四组容量能量与中值电压
     * 放进扩展测量;单项容量只在方向已确认时给出。
     * 一条工步记录。工步名称是设备工步类型解释出的方向名称(类型未确认时留空);工步时间取设备给的
     * {@code test_time}(毫秒 → 秒),四组容量能量与中值电压放进扩展测量;单项容量只在方向已确认时给出。
     */
    private StepRecord stepRecord(long testId, StepGroup group)
    {
@@ -475,7 +476,7 @@
        Double capacityAh = capacity == null ? null : scaled(capacity, CAPACITY_ENERGY_DIVISOR);
        Double energyWh = energy == null ? null : scaled(energy, CAPACITY_ENERGY_DIVISOR);
        return new StepRecord(sourceRecordId(testId, "step", group.key().cycle() + ":" + group.key().stepId()),
                group.key().cycle(), group.key().stepId(), null, last.stepType(), first.deviceTime(),
                group.key().cycle(), group.key().stepId(), direction.displayName(), last.stepType(), first.deviceTime(),
                last.deviceTime(), scaled(last.voltage(), VOLTAGE_CURRENT_DIVISOR),
                scaled(last.current(), VOLTAGE_CURRENT_DIVISOR), capacityAh, energyWh,
                last.deviceTimeRaw(), deviceZone.getId(), last.deviceTime(), measurements);
@@ -527,7 +528,9 @@
        BigDecimal energy = charging ? row.energyCharge() : row.energyDischarge();
        return new CabinetRealtime(row.sequenceId(), row.deviceTime(), scaled(row.current(), VOLTAGE_CURRENT_DIVISOR),
                scaled(row.voltage(), VOLTAGE_CURRENT_DIVISOR), scaled(capacity, CAPACITY_ENERGY_DIVISOR),
                scaled(energy, CAPACITY_ENERGY_DIVISOR), row.cycle(), row.stepId(), null, row.stepType(),
                scaled(energy, CAPACITY_ENERGY_DIVISOR), row.cycle(), row.stepId(),
                // 工步名称填设备工步类型解释出的方向名称;类型未确认时留空,不用默认名冒充已知。
                directions.resolve(row.stepType()).displayName(), row.stepType(),
                clock.instant(), row.deviceTimeRaw(), deviceZone.getId(),
                row.testTimeMillis() == null ? null : row.testTimeMillis() / 1000,
                "neware:" + row.testId() + ":" + row.sequenceId());
battery-device-neware/src/test/java/com/battery/device/neware/NewareMysqlAdapterContractTest.java
@@ -132,8 +132,10 @@
        assertEquals(3, latest.cycleNo());
        assertEquals(12, latest.stepNo());
        assertEquals("2", latest.stepType());
        // 工步名称由设备工步类型解释出的方向填写:2 = 放电。类型本身仍保留设备给的数字。
        assertEquals("放电", latest.stepName());
        assertEquals("neware:440:7435", latest.sourceRecordId());
        // 工步类型保留设备给出的数字,不按电流符号猜方向;同一秒的另一行靠工步与序号区分。
        // 同一秒的另一行靠工步与序号区分。
        assertEquals(9, rows.get(1).stepNo());
    }
@@ -224,7 +226,8 @@
        StepRecord rest = step(steps, 0, 1);
        assertEquals("4", rest.stepType());
        assertNull(rest.stepName());
        // 工步名称按设备工步类型解释出的方向:4 = 搁置。
        assertEquals("搁置", rest.stepName());
        assertEquals(Instant.parse("2026-09-28T01:00:00Z"), rest.startedAt());
        assertEquals(Instant.parse("2026-09-28T01:00:38Z"), rest.endedAt());
        assertEquals(1.3588, rest.endVoltageV(), 1e-9);
@@ -237,6 +240,7 @@
        StepRecord charge = step(steps, 0, 4);
        assertEquals("1", charge.stepType());
        assertEquals("充电", charge.stepName());
        // 单项容量只在方向已确认时给出:充电工步取充电侧。
        assertEquals(119277040d / 36000000d, charge.capacityAh(), 1e-9);
        assertEquals(195220576d / 36000000d, charge.energyWh(), 1e-9);
@@ -246,6 +250,7 @@
        StepRecord discharge = step(steps, 0, 6);
        assertEquals("2", discharge.stepType());
        assertEquals("放电", discharge.stepName());
        assertEquals(68462032d / 36000000d, discharge.capacityAh(), 1e-9);
        assertTrue(discharge.measurements().containsKey("dischargeMedianVoltage"));
        // 四组容量能量都是设备事实:每一组都保留,不只保留方向匹配的那一组。
@@ -263,6 +268,8 @@
        List<StepRecord> steps = adapter(stepLayerDatabase()).stepRecords(CHANNEL);
        StepRecord unknown = step(steps, 1, 3);
        assertEquals("7", unknown.stepType());
        // 类型未确认时工步名称留空:不按电流符号也不按名称猜方向。
        assertNull(unknown.stepName());
        // 单项容量留空:方向未确认时不给出可归属的单值,但四组明细照原样保留供核对。
        assertNull(unknown.capacityAh());
        assertNull(unknown.energyWh());