--- name: tou-template-scheduling description: 编写、校验并保存 FranklinWH 的 TOU(分时电价)调度模板,用于 EMS 调度配置。当用户需要构建、编辑、理解或上传 TOU 电价模板时使用——包括时段策略、光伏/负载优先级、充放电功率限制、省/市选择等。涵盖 updateOrSaveIotTouTemplate 请求体以及 MCP 工具 service_tou_template_*。 --- # FranklinWH TOU 调度模板 用于构建并保存 EMS 调度(TOU)模板。一个模板 = 基础信息 + 一个或多个「季节/日类型」策略;每个策略 = 覆盖一整天的时段列表。它对应云端 `updateOrSaveIotTouTemplate` 接口和设备侧 355/357 调度协议。 参考资料(完整协议与字段语义): `/home/jerry-remote/web/FranklinWH-Wiki/client/docs/Software/ScriptingForSchedulingConfiguration.md` 字段权威定义(Go DTO):`service/dto/fwhTestTouTemplateDTO.go` --- ## MCP 工具 所有工具都接受可选参数 `env`:`pro`(生产)或 `test`(测试)。 | 工具 | 用途 | env 默认值 | | ----------------------------- | -------------------------------------------- | -------------------- | | `service_tou_template_list` | 查询模板列表(按类型/状态/关键字筛选) | `pro` | | `service_tou_template_detail` | 按`id` 查询单个模板详情 | `pro` | | `service_tou_template_save` | 新增/更新模板,返回 templateId**【写操作】** | `test`(当前仅支持) | | `service_tou_template_cites` | 查询某国家下的省/市列表 | `test`(当前仅支持) | save 与 cites 目前仅支持 `test`。读操作默认走 `pro`,需要读测试环境时传 `env:"test"`。 典型流程:`cites`(拿 countryId/provinceId)→ 构建 payload → `save` → `detail` 校验。 --- ## 请求体结构(`service_tou_template_save` 的 `payload`) 顶层结构(`FwhTestUpdateOrSaveTouTemplatePayload`): ```json { "template": { /* 模板主体,见下表 */ }, "strategyList": [ { /* 策略,见下表 */ } ], "lang": "ZH_CN" } ``` - 字段按真实类型传值:**数值字段传数字(不是字符串)**,字符串字段传字符串。**可选的数值字段未设置时直接省略该 key**(不要传空串 `""`,也不要传 `null`)。 - `template.templateId`:**创建模板时不需要此字段(省略);更新模板时必须带此字段并填入模板 ID(数值)**。读取(`detail`/`list` 返回)时该字段必定存在。 - `template.sourceType`:**必填**(数值)。当前**仅允许写入系统模板 `0`**(`1`API模板 / `2`用户推荐 暂不支持写入,`save` 会拒绝)。 ### 1. `template`(模板主体 · `FwhTestTouTemplateInfo`) | 字段 | 类型 | 含义 / 取值 | | -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------- | | `countryId` | number | 国家 ID | | `provinceId` | number | 州/省 ID | | `derSchdule` | string | DER 调度配置 | | `eleCompanyFullName` | string | 电力公司全称 | | `electricCompany` | string | 电力公司(简称) | | `gridType` | string | 电网类型 / 套餐类型 | | `electricityType` | number | 电价类型:`1`分时电价 `2`阶梯电价 `3`固定电价 `4`分时&阶梯电价 `5`BB套餐 `6`Peak Demand | | `name` | string | 模板/策略表名称 | | `mookRunCount` | number | 模式执行次数(loop):`0`不限次数 `1`执行一次 | | `priority` | number | 模式优先级,默认`0`;`0` 最低,数字越大优先级越高 | | `env` | number(位域) | 模式使用条件,默认`0`;bit0~1 光伏;bit2~3 馈网;bit4~5 电网充电 | | `templateId` | number | 模板 ID。**创建时不需要(省略此字段);更新时必须带上并填 ID**。读取(detail/list)返回时该字段必定存在 | | `sourceType` | number(必填) | 模板类型。**必填,当前仅允许 `0`系统模板**(`1`API模板 `2`用户推荐 暂不支持写入) | ### 2. `strategyList[]`(策略 · `FwhTestTouTemplateStrategy`) 一个策略 = 一组「季节/日类型」+ 覆盖一整天的时段列表。 | 字段 | 类型 | 含义 / 取值 | | -------------- | ------------ | ---------------------------------------------------------------- | | `ccociateType` | number | 关联类型:`1`每天 `2`工作日 `3`周末/节假日(与 `dayType`/`dayName` 配套,见下方「日类型配套逻辑」) | | `ccociateSn` | string | 关联序号:每天为`""`;工作日与其配对的周末/节假日共用同一序号(从 `"0"` 递增) | | `detailVoList` | array | 时段明细表,**所有时段必须覆盖一整天**(见下表) | | `seasonName` | string | 季节名,用于标识策略范围,可自定义,如`summer`、`All months` | | `month` | string | 逗号分隔的月份,如`1,2,3,4,5,6,7,8,9,10,11,12` | | `dayName` | string | 日类型可读名:每天=`Every day` 工作日=`Weekdays` 周末/节假日=`Weekends,Holidays`(与 `dayType` 对应) | | `dayType` | number | 日类型编码:`1`工作日 `2`周末/节假日 `3`每天(可读名见 `dayName`) | #### 日类型配套逻辑(`ccociateType` / `ccociateSn` / `dayType` / `dayName`) 这四个字段必须成套填写,用于表示策略的「日类型」及工作日与周末/节假日的配对关系: | 日类型 | `ccociateType` | `ccociateSn` | `dayType` | `dayName` | | -------------- | :------------: | ---------------------------- | :-------: | --------------------- | | 每天 | `1` | `""` | `3` | `Every day` | | 工作日 | `2` | 序号(从 `"0"` 递增) | `1` | `Weekdays` | | 周末或节假日 | `3` | 同组工作日的序号 | `2` | `Weekends,Holidays` | - **每天**是独立策略,无需配对,`ccociateSn` 恒为空串 `""`。 - **工作日(2) 与 周末/节假日(3) 成对出现**,通过**相同的 `ccociateSn`** 关联为同一组。 - `ccociateSn` 是从 `"0"` 开始的序号字符串:第一对「工作日+周末/节假日」用 `"0"`,第二对用 `"1"`,以此类推(每新增一对 +1)。 - 因此,用「每天」时只有 1 条策略;用「工作日/周末」时至少成对出现 2 条策略(一条 `type=2`、一条 `type=3`,共用同一 `ccociateSn`)。 **策略级电价字段(可选)**:以下字段描述该策略各时段的电价,**类型为数值(number);未设置时直接省略该 key**(不要传 `""` 或 `null`)。 | 字段 | 类型 | 含义 | | ------------------------ | ------ | ----------------------- | | `eleticRatePeak` | number | 峰时购电电价 | | `eleticRateShoulder` | number | 平时购电电价 | | `eleticRateValley` | number | 谷时购电电价 | | `eleticRateSharp` | number | 尖峰购电电价 | | `eleticRateSuperOffPeak` | number | 超低谷购电电价 | | `eleticRateGridFee` | number | 电网附加费 | | `eleticSellPeak` | number | 峰时卖电(上网)电价 | | `eleticSellShoulder` | number | 平时卖电(上网)电价 | | `eleticSellValley` | number | 谷时卖电(上网)电价 | | `eleticSellSharp` | number | 尖峰卖电(上网)电价 | | `eleticSellSuperOffPeak` | number | 超低谷卖电(上网)电价 | ### 3. `detailVoList[]`(时段明细 · `FwhTestTouTemplateDetailVo`) 同一策略内的所有时段必须铺满一整天:每个 `endHourTime` 等于下一段的 `startHourTime`,第一段是 `00:00`,最后一段是 `24:00`。**数值字段传数字**(`waveType`、`solarCutoff` 每段必填;功率/SOC/使能等为可选);**可选数值字段未设置时直接省略该 key**(不要传 `""` 或 `null`)。字符串字段(时间、名称、优先级、`powerOffApower`)传字符串。 | 字段 | 类型 | 含义 / 取值 | | ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `startHourTime` | string | 开始时间,格式`HH:mm`,如 `06:30` | | `endHourTime` | string | 结束时间,格式`HH:mm`;需与下一段 `startHourTime` 衔接,所有时段覆盖一整天 | | `waveType` | number(flag,必填) | 峰平谷定义:`0`谷/off-peak `1`平/mid-peak `2`峰/on-peak `3`尖峰/super-on-peak `4`低谷/super-off-peak | | `name` | string | 时段名,一般为`supper-off-peak`、`off-peak`、`mid-peak`、`on-peak`、`supper-on-peak` 等 | | `briefDescribe` | string | 时段描述,如`Self-consumption` | | `solarPriority` | string | **光伏供电优先级**,格式 `"市电,负载,FHP"`,如 `"1,3,2"`;优先级 `0/1/2/3`,可相同(表示按比例/功率分配),`0`=不涉及,数字越大优先级越高 | | `loadPriority` | string | **负载取电优先级**,格式 `"市电,光伏,FHP"`,如 `"1,3,2"`;优先级 `0/1/2/3`,可相同(按比例/功率分配),`0`=不使用此源,数字越大优先级越高 | | `gridDischargeMax` | number(kW,0.1,可选) | 电池最大允许馈网功率;可选,默认`0` 不能馈网;`0`不可馈网,`-1`不限制 | | `gridChargeMax` | number(kW,0.1,可选) | 电网允许最大充电功率;可选,默认`0` 不允许;`0`不允许,`-1`不限制 | | `chargePower` | number(kW,0.1,可选) | 固定电池充电功率,按单台功率计算;可选,默认`-1` 未设置 | | `dischargePower` | number(kW,0.1,可选) | 固定电池放电功率,按单台功率计算;可选,默认`-1` 未设置 | | `chargeMax` | number(kW,0.1,可选) | 电池最大允许充电功率,按单台功率计算;`-1`无限制,默认 `-1` | | `dischargeMax` | number(kW,0.1,可选) | 电池最大允许放电功率,按单台功率计算;`-1`无限制,默认 `-1` | | `gcaoMax` | number(kW,0.1,可选) | GCAO(grid charge after an outage):停电后补电至预留 SOC 的单台市电取电功率;可选;`0`不允许,`-1`不限制;默认未设置(`-3`);使能后未设值则为 `5kw*N`(用 `-2` 标识) | | `gridFeedMax` | number(kW,0.1,可选) | 电网允许最大馈网功率;可选,默认`-2`;`0`不允许,`-1`不限制,`-2`未设置 | | `gridMax` | number(kW,0.1,可选) | 电网允许最大取电功率;可选,默认`-1` 不限制;`0`不允许,`-1`不限制 | | `minDischargeSoc` | number(%,整数,可选) | 放电截止 SOC;达到此 SOC 后电池不放电,可选,默认`0%`;若 `reserved_soc` 大于此值则以 `reserved_soc` 为准 | | `maxChargeSoc` | number(%,整数,可选) | 充电截止 SOC;可选,默认`100%` | | `heatEnable` | number(可选) | 充电加热使能:`0`未设置(默认) `1`不使能 `2`使能 | | `powerOffApower` | string | 主动关闭 APower;可选,`0`不控制(默认) `1`关机 `2`开机(预留) | | `rampTime` | number(秒,可选) | 爬坡时间,`0~300`;默认 `0`,BB套餐默认 `300` | | `offGrid` | number(可选) | 主动断开市电继电器:`0`不控制(默认) `1`断开 `2`闭合(预留) | | `solarCutoff` | number(必填) | 主动断开光伏继电器:`0`不控制(默认) `1`断开 `2`闭合(预留) | **要点**: - `solarPriority` / `loadPriority` 是 3 值字符串,顺序 `[市电, 负载或光伏, FHP/aPower]`;`0`=不参与,数字越大优先级越高,相同非 0 值=按比例分配。 - 功率字段单位 **kW、精度 0.1**,按单台 aPower 计(总功率 = 值 × 台数 N):`gridChargeMax`、`gridDischargeMax`、`gridFeedMax`、`gridMax`、`chargePower`、`dischargePower`、`chargeMax`、`dischargeMax`、`gcaoMax`。常见哨兵值:`-1`不限制、`0`不允许/不可、`-2`未设置(仅 `gridFeedMax`/`gcaoMax`)、`-3`未设置(仅 `gcaoMax`)。 ### 完整 detailVoList 单段示例 未设置的可选功率/SOC 字段直接省略(下例只保留有值字段): ```json { "startHourTime": "00:00", "endHourTime": "11:00", "waveType": 1, "name": "mid-peak", "briefDescribe": "Self-consumption", "solarPriority": "1,3,2", "loadPriority": "1,3,2", "solarCutoff": 0, "powerOffApower": "" } ``` 带部分功率设置的示例段(有值传数字,其余省略): ```json { "startHourTime": "18:00", "endHourTime": "21:00", "waveType": 2, "name": "on-peak", "briefDescribe": "Zero Import", "solarPriority": "9,9,9", "loadPriority": "9,9,9", "gridDischargeMax": 0.1, "gridFeedMax": 0.1, "dischargePower": 10, "gridMax": 0, "gcaoMax": 0, "heatEnable": 1, "solarCutoff": 0, "powerOffApower": "" } ``` ### selectCitesList 返回结构(`service_tou_template_cites`) `FwhTestCitesListData`:`countryList` / `provinceList` / `cityList` 三个数组。各项字段(注意此接口返回中 `id` 为**数值**,非字符串): | 结构 | 字段 | | --------------------- | -------------------------------------------------------------------- | | `FwhTestCountryInfo` | `id`(int), `nameEn`, `nameZh`, `latitude`, `longitude` | | `FwhTestProvinceInfo` | `id`(int), `nameEn`, `nameZh`, `latitude`, `longitude`, `cityList[]` | | `FwhTestCityInfo` | `id`(int), `nameEn`, `nameZh`, `latitude`, `longitude` | --- ## 优先级 → 调度策略 速查 常见 `solarPriority`/`loadPriority` 组合及其含义(来自 `templateTable.vue`): | 光伏优先级 | 负载优先级 | 策略 | | ---------- | ---------- | ------------------------------------------------------------- | | `1,2,0` | `1,3,2` | A | | `1,2,0` | `1,2,0` | B —— 电池不参与调度(光伏优先供载,剩余馈网,市电补充) | | `1,3,2` | `1,2,0` | C —— 光伏剩余充电(光伏优先供载,剩余给电池充电,电池不放电) | | `1,3,2` | `1,3,2` | D —— 自发自用(标准,最常用) | | `1,2,3` | `1,2,0` | E/G —— 光伏优先充电 | | `1,2,0` | `1,2,3` | F/H —— 电池优先供载 | 标准模板(来自 wiki《常规调度配置》): - **自发自用**:`solarPriority:"1,3,2"`,`loadPriority:"1,3,2"`,describe `"Self-consumption"`。 - **仅备电**:`gridChargeMax:"-1"`(不限充),`chargePower:"8"`(约 8kW 满充),describe `"Emergency backup"`。 - **强制放电/馈网收益**:`gridDischargeMax:"-1"`(不限馈网),`dischargePower:"10"`(约 10kW 满放)。 --- ## 规则 1. **时间覆盖**:每个策略的 `detailVoList` 必须覆盖 00:00→24:00,无空档、无重叠;`endHourTime` == 下一段 `startHourTime`;时间格式 `HH:mm`。 2. **按真实类型传值**:数值字段传数字(不是字符串);可选数值字段未设置时**省略该 key**(不传 `""`、不传 `null`)。字符串字段(时间、名称、优先级、`powerOffApower`)传字符串。 3. **功率单位是 kW**(步进 0.1),按单台 aPower 计。不要对这些模板字段套用设备侧的 W 或 `5kw*N/-2` 约定。 4. **优先级是 3 值字符串** `"a,b,c"`,顺序为 `[市电, 负载/光伏, aPower]`。 5. **新增 vs 更新**:`template.templateId` 留空为新增;填入 ID 为更新。 6. **写操作走 `test`**:`service_tou_template_save` 仅支持 `env:"test"`。保存前先与用户确认(会修改服务器数据)。保存后用 `service_tou_template_detail`(带 `env:"test"`)校验。 7. 执行写操作前,需要和用户确认最终写入或更新的信息,确认后才能调用 `service_tou_template_save` 创建或更新 8. 不确定某国家的 `provinceId` 时,先调用 `service_tou_template_cites` 传 `country_id`。 --- ## 示例流程:构建一个标准自发自用模板 1. `service_tou_template_cites { "country_id": 3 }` → 选出 `provinceId`。 2. 构建一个策略(「每天」类型:`ccociateType:1`、`ccociateSn:""`、`dayType:3`、`dayName:"Every day"`),用策略 D(`solarPriority:"1,3,2"`,`loadPriority:"1,3,2"`)铺一整天单一时段,或拆成 off/mid/on-peak 多个时段。若需区分工作日与周末/节假日,则改用成对策略(见「日类型配套逻辑」)。 3. `service_tou_template_save { "env":"test", "payload": "" }` → 记下返回的 `templateId`。 4. `service_tou_template_detail { "env":"test", "id": }` 确认结果。