一、策略算得出来,为什么落不下去
一个典型工商业光储站点包含屋顶逆变器、储能 PCS、电池簇 BMS、关口表、并网点电表、辐照仪,设备来自三到五家厂商。业务上要产出的东西很简单:未来 24 小时、15 分钟粒度的充放电计划,并在第二天拿到结算侧的核对结果。
工程上难的是两头。上游是数据收敛:同一个物理量,不同厂商的语义不同,采集链路还存在断点。下游是执行落地:计划下发之后,究竟有没有被执行、执行偏差来自哪一侧,如果系统答不出来,策略优化就没有反馈信号。
本文讨论的是这两头的设计取舍,以及部署在云上之后暴露出的几个具体问题。
二、五条硬约束
设计之前先把约束写清楚,比先画架构更有用。
- 数据源异构且不可控。 协议覆盖 Modbus TCP/RTU、DL/T 645、IEC 104 和部分厂商私有 HTTP 接口,现场网络质量参差。
- 时序写入量大、读放大严重。 单站点几百到上千点位,1 分钟原始点位乘以站点数,很快到亿级行;而查询绝大多数是"某站某天某指标"的窄区间聚合。
- 策略有时效窗口。 分时电价在固定时刻切换,现货价格滚动发布,计划必须在切换点前落库并下发,晚到即无效。
- 下发是带副作用的写操作。 一次重复执行可能造成真实电量损失,不能靠"至少一次投递"直接怼到设备。
- 成本可算。 冷数据占存储大头但极少被查,不能按热数据的成本存。
三、总体架构
按数据流向分五段,每段职责单一,段间用消息解耦。
边缘采集。 网关侧跑协议插件,本地落盘队列(轻量嵌入式库即可),断网期间继续采集,恢复后按时间区间补传。补传必须带原始采集时间戳,不能用上报时间,否则后续所有电量积分都会错位。
接入与规范化。 上行用 MQTT,topic 按 site/device/type 分层。接入服务做三件事:原始报文整体落对象存储留档(审计与回溯用,保留期按合规要求定),点位语义映射到内部标准模型,写入时序库。映射表随固件版本号走,不同版本并存,避免厂商升级后历史数据被新语义污染。
存储分层。 原始层存 1 分钟粒度,按站点与月份分区;聚合层存 15 分钟与小时粒度,供策略与看板查询;归档层把超过一年的原始数据转对象存储,只保留聚合结果。
计算层。 策略求解、收益核算、健康度诊断三类任务,离线批处理与在线计算分离,各自独立扩缩容。
应用层。 计划确认、收益核对、告警工单。这一层不直接读写原始时序,只读聚合结果与计划表。
四、关键设计
4.1 点位规范化:先定语义,再谈采集
内部标准点位按"物理量 + 方向 + 口径"三元组命名,例如 pv_energy_ac_export(光伏交流侧上网电量)、ess_energy_charge_ac(储能交流侧充电量)。三元组里任何一项不明确,就不要入库。
历史数据迁移时最容易出问题的是"看起来一样、其实不一样":有的厂商电量寄存器是累计值且会溢出回绕,有的在换表后从零开始。规范化的做法是同时存"原始累计值"和"由相邻差值积分得到的增量",用增量做业务计算,用累计值做一致性校验,两者偏差超过阈值就标记该区间为可疑,不参与结算。
4.2 时序模型:窄表 + 聚合表,不做大宽表
窄表(站点、设备、点位、时间、值、质量位)写入稳定,压缩率高;查询侧靠聚合表兜住 90% 的请求。质量位单独一列,标记缺测、补传、可疑三种状态——这一点在后续做偏差归因时价值极高,缺测数据如果被当成真实值参与计算,会污染整条策略链路。
大宽表在点位数量增长后维护成本高,加一个点位就要改 schema,不适合设备型号频繁变化的场景。
4.3 策略求解:约束剪枝 + 96 点动态规划
把问题写成混合整数规划在数学上更优雅,但单站点 96 个时段、多设备、多约束时,求解时间不稳定,遇到电池簇分组还会膨胀。生产上更实用的是两段式:先按硬约束剪枝,再在可行域内做动态规划。
# 简化后的求解骨架(单站点、单储能单元)
SOC_MIN, SOC_MAX = 0.10, 0.90
DT = 0.25 # 15 分钟
def feasible_slots(plan, soc0, cap, pmax):
"""按 SOC 与功率边界剪掉不可行时段,返回每段允许的功率区间"""
bounds = []
soc = soc0
for p in plan: # p: 该时段申报功率
lo = max(-pmax, (SOC_MIN - soc) * cap / DT)
hi = min(pmax, (SOC_MAX - soc) * cap / DT)
bounds.append((lo, hi))
soc = min(max(soc + p * DT / cap, SOC_MIN), SOC_MAX)
return bounds
def score(plan, price):
# 价差收益 - 循环寿命摊销 - 考核偏差风险
return (sum(p * price[t] * DT for t, p in enumerate(plan))
- LAMBDA_DEG * sum(abs(p) for p in plan)
- LAMBDA_PEN * max(0, deviation_risk(plan)))
三个参数需要按站点标定:LAMBDA_DEG 由电池循环寿命与更换成本折算,LAMBDA_PEN 由考核规则折算,deviation_risk 来自历史预测误差分布。这三个值不标定,模型输出会偏向"多充多放",看起来收益高,实际在消耗寿命。
4.4 幂等下发:状态机比算法更重要
指令表以 command_id(站点 + 计划版本 + 时段哈希)为主键,写入即幂等。状态机如下:
draft → simulated → approved → dispatched → acked → reconciled → closed
↘ rejected ↘ timeout(30s) → rolled_back
几个实现细节值得写下来。网关侧维护最近 N 条已执行 command_id 的环形缓存,收到重复指令直接返回上次结果,不再下发;simulated 阶段把计划回放到最近 7 天历史数据上,检查 SOC 轨迹与并网容量越界,越界直接打回;timeout 走回退而不是重试,回退目标是配置里的安全策略(通常是保持当前功率或归零),安全策略必须可配置,不能写死在代码里。
深夜无人值守时,回退能力比优化精度重要得多。这一点在第一次遇到网关掉线、计划悬空之后就会认同。
4.5 偏差归因:把三类原因分开
结算核对时,实际曲线与计划曲线的差值必须归因,否则模型会自我循环。归因按证据分三类:预测偏差(价格或出力预判错,看预测误差分布)、执行偏差(设备未响应或限功率,看告警与设备日志)、人工干预(运营手动压计划,看操作审计)。三类占比按月统计,是判断系统是否真的在用的核心指标——如果人工干预占比长期偏高,说明策略输出不可信,先修策略而不是加功能。
五、云上部署与成本
计算弹性。 电价发布后的重算高峰是脉冲式的,策略计算与采集链路分池部署,用按量实例或弹性容器承接峰值,避免重算任务把采集服务的内存挤爆。批处理任务用队列驱动,不用固定频率轮询扫全表。
任务时效。 电价切换点前的计划刷新是硬时限,调度器按站点分片并行,单片失败只重跑该片;给刷新任务设"最晚开始时间",超时则直接启用安全策略并告警,不追求在时限内勉强完成。
可观测。 四个指标够用:采集缺口率(按站点按日)、策略生成耗时 P95、指令 ack 成功率、策略采纳率(自动执行占比)。前两个是技术健康度,后两个是业务可用度,缺一个都会出现"系统在跑但没人信"的状态。
成本。 时序数据成本主要来自明细保留期,把原始数据保留期与合规要求对齐、查询全部走聚合表,存储成本能降一个量级;对象存储放原始报文,用生命周期策略转低频。
六、踩过的坑
补传数据按上报时间入库。 结果是断网期间的曲线整体右移,电量对不上,排查了两天才定位。补传必须携带原始采集时间。
聚合表用均值填充缺测。 缺测被平均成合理值,看板完全正常,结算时差额对不上。缺测要留空并打质量位,不能填。
指令重试没有去重。 弱网下 ack 丢失,重试导致同一时段执行两次,储能多放了一次电。幂等表必须在下发前写。
告警只做阈值。 单点阈值在设备老化时频繁误报,运营直接关掉通知。后来改成同站点横向对比加趋势判断才可用。
七、不做的事
系统输出计划与建议,不替代并网调度指令;涉及现货申报、考核结算的动作以当地规则与并网协议为准。算法不弥补数据治理,计量表未校准、台账与实装机不一致的站点,先做数据治理。以上取舍来自鲸能云在工商业光储项目中的实践,不同并网方式与结算模式下结论会有差异,不宜直接照搬。
常见问题
多品牌设备接入,协议适配的工作量怎么估?
按品牌数与私有协议比例估。标准 Modbus 设备通常几天可完成,私有接口依赖厂商文档配合,周期不可控。建议协议插件做成可配置而非硬编码,否则每加一个型号都要发版。
时序库选型有硬性要求吗?
没有。关键在分区键(站点 + 时间)与聚合表设计,以及是否支持质量位与乱序写入。缺测与补传是常态,写入乱序必须支持。
计划下发后怎么验证真的执行了?
看 ack 与回采两条链路。ack 只证明网关收到,回采的实际曲线才证明设备执行。两者都要有,且要能对齐到同一 command_id。