From 99fd4ccdf5d410e78a3785375036c34a2132ee04 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期日, 11 十月 2026 01:51:41 +0800
Subject: [PATCH] 新威对外接口改为后端直连管道,撤销机器端取数程序

---
 battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java |  124 +++++++++++++++++++++++++++++++++++++++--
 1 files changed, 117 insertions(+), 7 deletions(-)

diff --git a/battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java b/battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java
index 849ad5a..179ccd9 100644
--- a/battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java
+++ b/battery-device-api/src/main/java/com/battery/device/api/TestDataObservationAdapter.java
@@ -3,28 +3,97 @@
 import java.math.BigDecimal;
 import java.time.Instant;
 import java.util.List;
-import com.battery.device.model.ChannelIdentity;
-import com.battery.device.model.Measurement;
+import com.battery.device.api.model.ChannelIdentity;
+import com.battery.device.api.model.Measurement;
 
 /** 供业务持久化使用的厂商无关测试数据观测扩展。 */
 public interface TestDataObservationAdapter extends DeviceObservationAdapter
 {
+    /** 返回指定检测柜通道最近一次实时观测;没有成功观测时返回空值。 */
     CabinetRealtime latestCabinet(ChannelIdentity channel);
 
-    CollectorRealtime latestCollector(long collectorId);
+    /**
+     * 返回采集器独立实时快照;不得通过历史下载补采。
+     * 采集器测量按"同一来源的辅助测量"表达(ADR-0064),所以必须指名是哪个来源实例,
+     * 否则多实例下无法确定该向哪台机器请求。
+     */
+    CollectorRealtime latestCollector(String adapterCode, String collectorCode);
 
+    /**
+     * 返回该通道的工步统计。来源有工步层时读设备工步层并原样交出;来源没有工步层时由适配器
+     * 从采样段推导(ADR-0067)。两种形态的记录里 {@code stepName} 都是**方向名称**
+     * ({@link com.battery.device.api.StepDirectionResolver.Direction#displayName()}):来源给出工步类型时按该类型解释,
+     * 来源没有类型列时按已核对的列语义判定;判定不出方向时留空,不写默认名。
+     * 未结束的段不交出,也不推导来源根本没有的工步层。
+     */
     List<StepRecord> stepRecords(ChannelIdentity channel);
 
+    /**
+     * 返回该来源的循环统计。设备直接提供循环汇总行时原样返回;设备只有工步层时由适配器按循环聚合,
+     * 并用 {@link CycleRecord#sourceKind()} 标注为聚合来源,使调用方与界面能区分推导值与设备事实。
+     * 不补齐来源未提供的循环,也不跨循环借值。
+     */
     List<CycleRecord> cycleRecords(ChannelIdentity channel);
 
+    /** 返回适配器定义的实时持久化槽位间隔。 */
     long persistenceIntervalMillis(ChannelIdentity channel);
 
+    /**
+     * 返回工步与循环这些汇总记录的读取间隔。默认与实时持久化间隔一致;来源的汇总产出节奏与
+     * 数据行节奏不同的实现覆盖它——推式来源的落地表在本地(实时行按展示节奏取),
+     * 而汇总最快也要等来源写下下一行才可能变化,按展示节奏重算只是空跑。
+     */
+    default long aggregateIntervalMillis(ChannelIdentity channel) { return persistenceIntervalMillis(channel); }
+
+    /** 返回设备计划方案;设备未提供时保持为空,不推导计划。 */
+    default TestPlan testPlan(ChannelIdentity channel) { return null; }
+
+    /**
+     * 读取通道当前的接收水位。水位是来源里已存在的最新一行(设备绝对时间 + 协议序号),
+     * 用于把"开始接收"和"继续接收"切在这一时刻之后:水位之前的数据行即使仍在来源里也不进本测试。
+     *
+     * <p>返回空值表示该来源不按设备数据行接收(例如由系统轮询产生槽位的来源),此时不启用逐行接收。
+     * 设备时间为空的水位表示这一刻来源里没有任何可用行,之后出现的行全部属于本次接收。
+     * 来源不可达时必须抛出异常,不能返回空值——空值会被误当作"来源不按行接收"。
+     */
+    default SampleWatermark currentWatermark(ChannelIdentity channel) { return null; }
+
+    /**
+     * 返回严格晚于水位的设备数据行,按设备时间与协议序号升序。
+     * 同一绝对秒内的多行各自成立,调用方不得按秒合并。
+     * 水位为空表示调用方尚未取得水位,此时返回空列表:不把来源里的设备历史并入本次接收。
+     */
+    default List<CabinetRealtime> samplesSince(ChannelIdentity channel, SampleWatermark watermark) { return List.of(); }
+
+    /** 接收水位:设备绝对时间与协议序号的二元组;序号在同一厂商测试内有效。 */
+    record SampleWatermark(Instant deviceTime, long sequenceId) { }
+
+    record TestPlan(int currentCycleNo, int currentStepNo, int plannedCycleCount, List<PlanRow> rows) { }
+    record PlanRow(String rowType, int stepNo, Integer cyclePosition, String stepName, double durationSeconds,
+            Double voltageV, String executionStatus, Integer cycleCount, Long recordCount) { }
+
+    /**
+     * 柜端成功观测;相对时长使用设备提供的秒数,缺失不按采样时间推算。
+     * {@code sourceRecordId} 是来源行的稳定身份(如厂商测试编号加协议序号),
+     * 供幂等与冲突判定使用;来源不提供时保持为空,调用方退化为按协议序号识别。
+     */
     record CabinetRealtime(long sequenceId, Instant deviceTime, double currentA, double voltageV,
             double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType,
-            Instant serverReceivedAt) { }
+            Instant serverReceivedAt, String deviceTimeRaw, String timezoneId, Long relativeTimeSeconds,
+            String sourceRecordId)
+    {
+        /** 兼容不携带来源行身份的既有适配器。 */
+        public CabinetRealtime(long sequenceId, Instant deviceTime, double currentA, double voltageV,
+                double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType,
+                Instant serverReceivedAt, String deviceTimeRaw, String timezoneId, Long relativeTimeSeconds)
+        {
+            this(sequenceId, deviceTime, currentA, voltageV, capacityAh, energyWh, cycleNo, stepNo, stepName,
+                    stepType, serverReceivedAt, deviceTimeRaw, timezoneId, relativeTimeSeconds, null);
+        }
+    }
 
     record CollectorRealtime(String collectorCode, boolean online, int sampleIntervalMs,
-            List<CollectorMeasurement> measurements)
+            List<CollectorMeasurement> measurements, String timezoneId)
     {
         public CollectorRealtime { measurements = measurements == null ? List.of() : List.copyOf(measurements); }
     }
@@ -32,10 +101,51 @@
     record CollectorMeasurement(String channelCode, long sequenceId, Instant deviceTime, String deviceTimeRaw,
             Instant serverReceivedAt, String metricCode, BigDecimal value, String unit) { }
 
+    /**
+     * 一条设备工步层记录。来源给出工步类型时 {@code stepType} 原样保留(新威为数字枚举),
+     * 适配器不按电流符号改写它。
+     *
+     * <p>{@code stepName} 是**方向名称**(`充电` / `放电` / `搁置`,ADR-0067 §4b):来源给出工步类型时
+     * 按该类型解释(新威的 {@code 1}/{@code 2}/{@code 4}),来源没有类型列时(推式来源的厂商库里既没有
+     * 工步层也没有循环汇总表,ADR-0067)按已核对列语义判定。方向判不出来时留空——**不是设备给的名称**,
+     * 界面据此标注来源,调用方不得用默认名把未知说成已知。
+     *
+     * <p>来源没有工步层时,除方向名称外该工步的其余一切仍是设备采样行上的原文事实。
+     *
+     * <p>{@code endVoltageV} 与 {@code endCurrentA} 是该工步结束时的设备读数。
+     * {@code capacityAh} 与 {@code energyWh} 是该工步自身累计的单项值,只在方向已确认时给出;
+     * 方向未知时保持为空,四个方向的容量能量仍在 {@code measurements} 中作为设备事实保留。
+     */
     record StepRecord(String sourceRecordId, int cycleNo, int stepNo, String stepName, String stepType,
-            Instant startedAt, Instant endedAt, double endVoltageV, double capacityAh, double energyWh) { }
+            Instant startedAt, Instant endedAt, double endVoltageV, double endCurrentA, Double capacityAh,
+            Double energyWh, String deviceTimeRaw, String timezoneId, Instant serverReceivedAt,
+            java.util.Map<String, BigDecimal> measurements) { }
 
+    /**
+     * 一条循环统计。{@code sourceRecordId} 是来源行的稳定身份,供幂等与冲突判定使用。
+     *
+     * <p>设备没有循环汇总行时,循环统计由工步层按循环聚合得出;{@code sourceKind} 记录这个来源事实,
+     * 使界面与导出能明确标注"这个数是聚合的还是设备给的",取值只能是下面三个常量之一。
+     *
+     * <p>{@code efficiency} 为单位为百分比的充放电效率。该循环没有可用的充电容量时保持为空:
+     * 未知比率不用零代替,零值只表示设备确实给出零。
+     */
     record CycleRecord(String sourceRecordId, int cycleNo, Instant startedAt, Instant endedAt,
             double chargeCapacityAh, double dischargeCapacityAh, double chargeEnergyWh,
-            double dischargeEnergyWh, double efficiency) { }
+            double dischargeEnergyWh, Double efficiency, String deviceTimeRaw, String timezoneId,
+            Instant serverReceivedAt, String sourceKind)
+    {
+        /** 来源类别:设备直接提供。 */
+        public static final String SOURCE_DEVICE = "DEVICE";
+        /** 来源类别:来源没有循环汇总行,由工步层按循环聚合得出。 */
+        public static final String SOURCE_AGGREGATED = "AGGREGATED";
+        /** 来源类别:授权文件导入。 */
+        public static final String SOURCE_IMPORTED = "IMPORTED";
+
+        public CycleRecord
+        {
+            // 未标注一律按设备事实:引入聚合之前写进库的循环记录都来自设备或文件。
+            sourceKind = sourceKind == null || sourceKind.isBlank() ? SOURCE_DEVICE : sourceKind;
+        }
+    }
 }

--
Gitblit v1.7.1