2026-06-23 19:56:02 +02:00
# Tibber API + 荷兰电价 + DSMR Reader 参考
> 本文件汇总 M6(动态/固定电价 → 实时买卖电费计算)所需的全部外部事实,供 `docs/design/m6-tibber-dynamic-energy.md` 与实现者直接 refer。
> 来源:Tibber GraphQL introspection(实证)、Tibber Explorer demo 数据、Tibber NL support 文档、用户实际合同与 DSMR Reader 配置(2026-06 整理)。
> 凡标「✅ 实证」的为代码/数据验证过的事实;金额类以用户真实账单 / 合同生效后真实 token 为最终准(见末尾「待真实数据核对」)。
---
## 1. Tibber GraphQL API
- **Endpoint**: `POST https://api.tibber.com/v1-beta/gql`
- **认证**: HTTP header `Authorization: Bearer <token>` ; token 在 [developer.tibber.com ](https://developer.tibber.com ) 登录后生成(每个 Tibber 账户一个)。
- **在线 Explorer**: [developer.tibber.com/explorer ](https://developer.tibber.com/explorer ),自带一个公共 **demo token** 。
- ⚠️ **demo token 现在只能做 schema introspection 与历史 demo 数据查询** ;对 `viewer` 的真实数据查询返回 `UNAUTHENTICATED` 。要拿自己家的实时价,必须用**自己账户的 token**。
### 1.1 `Price` 类型(✅ introspection 实证)
`Price` 对象恰好 6 个字段:
| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `total` | Float | **全包价 = energy + tax** (即 App 里的 All-in Price,含 spot+能源税+VAT 等)|
| `energy` | Float | 纯能源/现货分量(ex-VAT 市场价)|
| `tax` | Float | 税费分量(能源税 + VAT 合并)|
| `startsAt` | String | 该价格段起点(ISO8601,**含时区偏移**,如 `+02:00` ) |
| `currency` | String! | 币种(唯一 non-null 字段;NL 为 EUR, demo 为 SEK) |
| `level` | PriceLevel | 价格档枚举(`VERY_CHEAP/CHEAP/NORMAL/EXPENSIVE/VERY_EXPENSIVE` ) |
** ✅ 实证**: `total == energy + tax` 精确成立(见 §1.4 样本)。
### 1.2 15 分钟价格:`Subscription.priceInfoRange`(✅ introspection 实证)
- **字段**: `currentSubscription.priceInfoRange`
- **参数**: `resolution: PriceInfoRangeResolution!` (必填)、`first: Int` 、`last: Int` 、`before: String` 、`after: String` (游标分页)
- **resolution 枚举 `PriceInfoRangeResolution` **:值 `DAILY` / `HOURLY` / `QUARTER_HOURLY`
- ⚠️ 注意别用错枚举:另有 `PriceResolution` (只有 HOURLY/DAILY, **无 QUARTER_HOURLY**)和 `PriceInfoResolution` ——`priceInfoRange` 用的是 ** `PriceInfoRangeResolution` **。
- **返回**: `SubscriptionPriceConnection { nodes: [Price]!, edges: [...], pageInfo }`
- **废弃说明**:老的 `priceInfo.range` ( `deprecationReason: "use Subscription.priceInfoRange instead"` ) **不支持 QUARTER_HOURLY**,必须改用 `Subscription.priceInfoRange` 。
- **✅ 实证(demo key, 2018 历史日)**: `QUARTER_HOURLY` 返回 **96 个节点、间隔精确 15 分钟** ;无真 15 分钟价的历史日,API 把**小时价重复成 4 个相同刻钟**(00/15/30/45 同值,到下一整点才变);NL 当前数据则是真 15 分钟。
- **解析注意**:**不要假设固定 96 个节点 / 固定 15 分钟间隔**,按 `startsAt` 逐点存最稳。
**15 分钟查询(拉今天 96 个刻钟)** :
```graphql
{ viewer { homes { id currentSubscription {
priceInfoRange ( resolution : QUARTER_HOURLY , first : 96 ) {
nodes { startsAt total energy tax currency level }
} } } } }
```
### 1.3 小时价 / 当前价 / 探测:`priceInfo`
`currentSubscription.priceInfo` 含 `current` (当前价)、`today[]` 、`tomorrow[]` (次日价约 13:00 CET 发布)。探测账户能力 + 当前价:
```graphql
{ viewer { homes {
id appNickname
features { realTimeConsumptionEnabled }
currentSubscription { priceInfo {
current { total energy tax startsAt level currency }
today { total energy tax startsAt level }
tomorrow { total energy tax startsAt level }
} } } } }
```
### 1.4 现成 curl + 样本响应
```bash
# 15 分钟价(换成自己账户 token; demo token 拿不到 viewer 真实数据)
curl -s -X POST https://api.tibber.com/v1-beta/gql \
-H "Authorization: Bearer <YOUR_TOKEN>" -H "Content-Type: application/json" \
-d '{"query":"{ viewer { homes { id currentSubscription { priceInfoRange(resolution: QUARTER_HOURLY, first: 96) { nodes { startsAt total energy tax currency } } } } } }"}'
```
**样本:`priceInfo`( demo,瑞典家庭,SEK) **
```json
{ "current" : { "total" : 0.8239 , "energy" : 0.5663 , "tax" : 0.2576 ,
"startsAt" : "2026-06-23T14:00:00.000+02:00" , "level" : "NORMAL" , "currency" : "SEK" },
"today" : [{ "total" : 1.276 , "energy" : 0.928 , "tax" : 0.348 ,
"startsAt" : "2026-06-23T00:00:00.000+02:00" , "level" : "VERY_EXPENSIVE" }, … 24 项 ],
"tomorrow" :[ … 24 项 ]}
```
**样本:`priceInfoRange` QUARTER_HOURLY( demo, 2018-11-02, 96 节点)**
```json
{ "nodes" : [
{ "startsAt" : "2018-11-02T00:00:00.000+01:00" , "total" : 0.63 , "energy" : 0.435 , "tax" : 0.195 , "currency" : "SEK" },
{ "startsAt" : "2018-11-02T00:15:00.000+01:00" , "total" : 0.63 , "energy" : 0.435 , "tax" : 0.195 , "currency" : "SEK" },
{ "startsAt" : "2018-11-02T00:30:00.000+01:00" , "total" : 0.63 , "energy" : 0.435 , "tax" : 0.195 , "currency" : "SEK" },
{ "startsAt" : "2018-11-02T00:45:00.000+01:00" , "total" : 0.63 , "energy" : 0.435 , "tax" : 0.195 , "currency" : "SEK" },
{ "startsAt" : "2018-11-02T01:00:00.000+01:00" , "total" : 0.6141 , "energy" : 0.4223 , "tax" : 0.1918 , "currency" : "SEK" },
… 共 96 个,末节点 23 : 45 total 0.6342 ]}
```
(注意 00:00–00:45 四个刻钟同值 = 小时价被重复;`total==energy+tax` 。)
---
## 2. 荷兰电价构成(Tibber NL + 用户合同)
> 所有金额**含 VAT( incl. btw) **,除非另注。VAT/BTW 标准税率 **21%**。
| 分量 | 荷兰语 | 单位 | 值 / 说明 |
| --- | --- | --- | --- |
| 现货/市场价 | marktprijs / dynamische kwartierprijs | €/kWh | 每 15 分钟跟随交易所;= API `energy` |
| 买侧采购费 | **inkoopvergoeding** | €/kWh | ** €0.0248**(覆盖 onbalans + 绿证;见下「相等」事实)|
| 卖侧上网费 | **verkoopvergoeding** | €/kWh | ** €0.0248**( 2026-01-01 起)|
| 能源税 | energiebelasting | €/kWh(含 VAT) | 2026 第一档(0– 10000 kWh)≈ ** €0.1108**; 2025 ≈ €0.1228;高档更低 |
| 增值税 | BTW | % | **21%** |
| 固定月费 | vast bedrag / leveringskosten | €/月/合同 | Tibber ** €5.99/月**(电、气各一次)|
| 电网维护费 | netbeheerkosten / systeembeheerkosten | €/天或/月 | 电网公司定、供应商代收;按地区,2026 约 +3.38% |
| 能源税抵扣 | heffingskorting / vermindering energiebelasting | €/年/连接 | 政府年度减免,从总费用扣 |
| ODE | Opslag Duurzame Energie | €/kWh | **现为 0** (已并入 energiebelasting) |
** ✅ 关键事实(Tibber NL 文档原文)**:
> "De verkoopvergoeding van 2,48 cent is gelijk aan de inkoopvergoeding die je bij je afgenomen stroom betaalt."
> (卖侧 verkoopvergoeding 2.48 分 = 买侧 inkoopvergoeding。)
2026-07-20 13:50:13 +02:00
→ **买卖服务费金额相等(均 €0.0248/kWh),但两者对住户都是成本、不互相抵消** :
- 买侧 inkoopvergoeding 已经**包含在 Tibber API 的 `total` 里**(见下 §3.1 的实证拆解),买电按 `total` 计价即已含它。
- 卖侧 verkoopvergoeding 则是从回送价里**额外扣掉**的一笔——所以回送价 = `total − 0.0248` ,比买价低 0.0248/kWh。
- ⚠️ **早期版本误判为"一进一出抵消 → 回送=total"** ,这是错的:`total` 里那笔 inkoopvergoeding 不会退回来充抵 verkoopvergoeding。代码里用 `energy.sell_fee` (默认 0.0248)建模这笔卖侧费用。
2026-06-23 19:56:02 +02:00
---
## 3. 净计量(saldering)、回送(teruglevering)、负电价、2027
2026-07-20 13:50:13 +02:00
### 3.1 回送价(净计量期内,文档原文 + 实证)
2026-06-23 19:56:02 +02:00
> "Op het moment dat je teruglevert geven we je per kWh de beursprijs die op dat moment geldt …, inclusief energiebelasting en inkoopvergoeding plus de btw minus de verkoopvergoeding."
2026-07-20 13:50:13 +02:00
> **Worked example( Tibber NL 原文)**: "Stel dat tussen 14:00 en 14:15 de totale stroomprijs €0,28 per kWh incl. is, dan krijg je €0,28 − €0,0248 verkoopvergoeding = **€0,2552** per teruggeleverde kWh terug."
即净计量期内回送价 = `beursprijs + energiebelasting + inkoopvergoeding + btw − verkoopvergoeding` ,而官方例子直接写成 ** `回送价 = totale stroomprijs − verkoopvergoeding = total − 0.0248` **。能源税**退回**(留在 total 里没动),只有 verkoopvergoeding 这 0.0248 被扣。
** ✅ 实证(本项目生产库,2026-07-20 三个刻钟)**:按 21% VAT 拆 `total` : `total = 现货×1.21 + energiebelasting(0.11085) + inkoopvergoeding(0.0248)` ,三段解出的 inkoop 都精确等于 **0.0248** 。→ **我们存的 `tibber_price.total` 就是官方 "totale stroomprijs"(含 inkoopvergoeding 的买价)** ,因此:
- 买价 `buy = total` (已含 inkoopvergoeding,正确)。
- 净计量回送价 `sell = total − verkoopvergoeding = total − 0.0248` 。
- ⚠️ 所以 saldering 下"回送 1 度"仍比"用 1 度"少 0.0248——**不是完全 1:1**。代码用 `sell_fee` 建模这笔扣减,`sell_adjust` 只负责在净计量期把能源税补回(`sell_adjust = − energy_tax` )。
2026-06-23 19:56:02 +02:00
### 3.2 年末盈余 / 取消净计量后(文档原文,Scenario 2)
> "Voor de overproductie van 500 kWh heb je recht op de beursprijs en de inkoopvergoeding, maar heb je geen recht op de energiebelasting. … ontvang je nog een factuur van ons voor de te veel uitgekeerde belastingen …"
即**超额回送(或 2027 取消净计量后)**:盈余按 `beursprijs + inkoopvergoeding − verkoopvergoeding` (**无能源税**)计价 → 因两费抵消 → ** = 纯 spot**。
### 3.3 净计量机制
- 法定**按自然年**结算;可抵扣到「用电量」为止(回送抵到用电为止)。
- Tibber 不用预付,**净计量周期在第 12 张账单后结束**。
- **2027 起荷兰取消净计量(saldering stopt in 2027) **——用户据此设计:**不再考虑净计量,直接按实时买卖算**。
### 3.4 负电价
- 负价时**用电**:理论上你拿钱(但仍计能源税,净计量期内税会退回)。
- 负价时**回送**:你要为回送的电付那个负价(= 倒贴)。
- 因 Tibber 给的是 all-in `total` (已含能源税),**只有 spot 负到比能源税还多,total 才转负**;中等负价时 total 仍正(照付)。
- **结论:负电价不需要特殊处理**——`buy=total` 与 `sell=total−能源税` 符号自洽。
---
## 4. 本项目采用的买/卖价公式(M6)
> spot 取 API `energy`; `total = energy + tax`(全包)。**买价直接用 `total`**,卖价从 `total` 扣掉卖电不交的能源税。
2026-07-20 13:50:13 +02:00
- **Tibber 动态合同**:
- 买价 `buy = price.total` (含 energy_tax + VAT + inkoopvergoeding)
- 卖价 `sell = price.total − energy_tax − sell_fee − sell_adjust`
- `sell_fee` : verkoopvergoeding(卖侧上网费,默认 **0.0248** ,含 VAT),**始终扣除**——即使净计量期也扣(见 §3.1)。
- `sell_adjust` :手动修正项(默认 0)。**净计量期**设为 `− energy_tax` (把能源税补回),得 `sell = total − sell_fee` ;**2027 取消净计量后**设为 0,得 `sell = total − energy_tax − sell_fee` (无能源税、纯市场价再扣上网费)。
2026-06-23 19:56:02 +02:00
- **固定合同(manual,双费率)**:
- 买价 `buy_档 = energy_buy_档 + energy_tax` (档 ∈ {normal, dal})
- 卖价 `sell_档 = sell_档` (回送价,**无能源税**)
- **固定费/抵扣不进每度**: `network_fee` 、`management_fee` (月→天)、`heffingskorting` (年→天)在**日/月/年汇总**层加减。
- 详见 `docs/design/m6-tibber-dynamic-energy.md` §3.4。
---
## 5. 用户当前固定合同(manual 实例,2026-06)
> 双费率(NL: `_1`=dal/低谷、`_2`=normal/高峰)。以下为当前值,会随合同阶段/年度变(系统按"加新版本+生效日期"留底)。
| 项 | 当前值 | 备注 |
| --- | --- | --- |
| 能源价 normal(高) | €0.133/kWh | 买侧,含 VAT,不含能源税 |
| 能源价 dal(低) | €0.127/kWh | 买侧 |
| 回送价 normal | (现与 dal 同)| 卖侧,分档但现同价;系统仍分开填 |
| 回送价 dal | — | 卖侧 |
| 能源税 energiebelasting | 待填(≈€0.1108) | 含 VAT,加到买价 |
| ODE | 0 | 已并入能源税 |
| 电网维护费 | 待填 | 按天(合同按天收)|
| 供应商管理费 | ≈€0.329/天(≈€9.87/月)| 对应 Tibber 的 €5.99/月 |
| heffingskorting | 待填 | 年度减免,汇总时扣 |
| 回送阶梯罚金 terugleverkosten | **不做** | 按自然年累计、阶梯式;用户住不到年底算不准,M6 不实现 |
---
## 6. DSMR Reader → MQTT
### 6.1 取数方式
- 用 **Telegram JSON** (单 topic ** `dsmr/json` **,一条 = 一帧完整 telegram 的 JSON),**不用** split-topic(每字段一 topic、要按 id 拼)、**不用** raw(未解析 OBIS)。
- DSMR Reader **每秒一条** 。本项目按 **10 秒降采样** (仅秒数整 10 落盘)、**整帧存 JSON blob**(不做字段 allowlist)。
### 6.2 telegram JSON 字段(DSMR Reader 配置的 JSON mapping)
```
id, timestamp,
electricity_delivered_1, electricity_delivered_2, # 进口累计 kWh( _1=dal 低谷, _2=normal 高峰)
electricity_returned_1, electricity_returned_2, # 出口/回送累计 kWh
electricity_currently_delivered, electricity_currently_returned, # 瞬时进/出口功率 kW
phase_currently_delivered_l1/l2/l3, phase_currently_returned_l1/l2/l3, # 各相瞬时功率 kW
phase_voltage_l1/l2/l3, # 各相电压 V
phase_power_current_l1/l2/l3, # 各相电流 A
extra_device_timestamp, extra_device_delivered # 燃气表(m³,每 5 分钟更新)
```
### 6.3 实测样本(单相,`dsmr/json`)
```json
{
"id" : 200086230 ,
"timestamp" : "2026-06-23T12:16:48Z" ,
"electricity_delivered_1" : "20915.154" ,
"electricity_returned_1" : "2979.905" ,
"electricity_delivered_2" : "15212.090" ,
"electricity_returned_2" : "6786.406" ,
"electricity_currently_delivered" : "0.000" ,
"electricity_currently_returned" : "2.704" ,
"phase_currently_delivered_l1" : "0.000" ,
"phase_currently_delivered_l2" : null ,
"phase_currently_delivered_l3" : null ,
"extra_device_timestamp" : "2026-06-23T12:15:00Z" ,
"extra_device_delivered" : "6208.234" ,
"phase_currently_returned_l1" : "2.704" ,
"phase_currently_returned_l2" : null ,
"phase_currently_returned_l3" : null ,
"phase_voltage_l1" : "237.0" ,
"phase_voltage_l2" : null ,
"phase_voltage_l3" : null
}
```
### 6.4 解析要点(实测确认)
- **数值都是 JSON 字符串**( `"20915.154"` 、`"0.000"` )→ 计费读取转 **Decimal** (累计寄存器算钱要精度)。
- **缺测相位 = `null` **(不是缺 key)→ 视为缺测;单相只有 `*_l1` ,三相后 `_l2/_l3` 由 null 变数字。
- **进口总量** = `electricity_delivered_1 + _2` ; **出口总量** = `electricity_returned_1 + _2` (双费率档对动态合同无意义,求和;对固定合同分档计价)。
- **`timestamp` 为 UTC( Z) **; `id` 自增,做幂等去重。
- 历史样本里电压 key 曾被错配成带前缀 `dsmr/reading/phase_voltage_l1` ( DSMR Reader 的 JSON mapping 配置笔误,用户已改对)——解析仍按"键名容错"。
- **整表寄存器与相数无关**: `delivered/returned_1/2` 是整表总量,三相只多了各相瞬时通道 → **计费逻辑相数无关** 。
- 燃气 `extra_device_delivered` (m³)一并存入 blob;燃气计费 M6 不做(数据先留,未来按 commodity 扩展)。
---
## 7. 来源 URL
- Tibber GraphQL reference / explorer: `https://developer.tibber.com/docs/reference#rootsubscription` 、`https://developer.tibber.com/explorer`
- Tibber NL 费用构成:`https://support.tibber.com/nl/articles/5605892-de-kosten-bij-tibber`
- Tibber NL 净计量与回送:`https://support.tibber.com/nl/articles/4669873-salderen-en-terugleveren-bij-tibber`
- DSMR Reader( HA 集成):`https://www.home-assistant.io/integrations/dsmr_reader/`
---
## 8. 待真实数据核对(合同生效后用真实 token / 账单)
1. **真实 token 复核** :跑 §1.4 的 15 分钟 curl,确认 NL 返回**真** 15 分钟价(非重复小时价)+ 币种 EUR。
2026-07-20 13:50:13 +02:00
2. ~~**卖价残差**:确认 `total` 里 purchase fee 是否被卖侧 sales fee 完全抵掉~~ → **已核实(2026-07) ** : `total` 含 inkoopvergoeding( 0.0248),净计量回送价 = `total − verkoopvergoeding(0.0248)` ,两费**不抵消**;代码以 `sell_fee` (默认 0.0248)建模。仍待真实账单核对 `sell_fee` / VAT 口径的最终残差。
2026-06-23 19:56:02 +02:00
3. **双费率寄存器映射** :确认 `_1` =dal/`_2` =normal 没接反(差价小但要对)。
4. **能源税年值** :按当年实际值与年用电档位核 `energy_tax` 。
5. **固定合同数值** :回送两档价、电网费、heffingskorting 待用户从账单填。
</content>