Files
goodwe-addon/goodwe_controller/DECISIONS/TEL-14-pv-sign.md
T
adminandClaude Opus 5.5 7fa045af85
checks / unit (pull_request) Successful in 8m44s
TEL-14 N1: trim DECISIONS (drop probe transcript, §1 to the reason), move the mutation ledger there with M2/M5
test_pv.py part 11 now prints a one-line pointer instead of the 12-row table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
2026-09-25 01:11:40 +02:00

84 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TEL-14 — the PV sign, declared per entity
Owner report on 0.12.0: `sensor.espressif_vermogen` (ESPHome clamp) and
`sensor.kwh_meter_actueel_vermogen` (kWh meter in the PV circuit) read
**positive for consumption, negative for production**. At night the page said
"✓ PV production: 13 W" — standby draw. By day production would publish negative.
## 1. This reverses TEL-11 §2, and why
TEL-11 §2 refused a `pv_invert` on two grounds. "PV is read by nothing in the
control path" still holds: the flip lives in `pv.total()`, and `test_pv.py`
part 12 re-runs part 6's 40-cycle identity with a flip set. "A reversed sensor
announces itself" did not survive a real site: these sensors follow a different
convention, read a plausible small positive number at night, and the only other
fix is a template sensor, the place TEL-12 §2 says goes quietly wrong.
## 2. The shape: a parallel list, `pv_invert_entities`
Measured by `tools/supervisor_shape_probe.py` §H (Supervisor 2026.08.0, against
a 0.12.0 blob); re-run it for the transcript.
| shape | schema | why it lost / won |
|---|---|---|
| (a) `-sensor.x` prefix | accepted | invisible: the sign is a syntax only the description explains, in a field an installer fills with the entity picker's text |
| (b) parallel list | accepted, 0.12.0 blob unchanged | **ships**: its own labelled, translated field ("PV entities whose sign is reversed"); an installer lists the odd ones out |
| (c) dicts on `pv_entities` | **refuses 0.12.0's stored strings** | bricks the upgrade — TEL-12's trap again |
| (c2) dicts as a third key | accepted | per-entry labels render raw (`entity`, `invert`) — probe §F; and a third PV surface |
| (d) global bool | accepted | one sign cannot describe inverter + meter in one roster |
(b)'s known cost is a typo in the second list: `pv.stray_inverts()` turns it into
a named caution. `[]` in `options:` is load-bearing (§H, isolated counterfactual).
Default `[]` flips nothing, so every 0.12.0 install is byte-identical (part 12).
## 3. The negative caution: per entity, below −50 W
The owner's two meters draw ~13 W at night, so a *correctly* signed meter reads
about −13 W then. −50 W clears that with room for a few such devices behind one
entity; a reversed sensor by day reads hundreds to thousands of watts below
zero. It is tested **per entity, not on the total** (a deviation from the brief):
three correct meters at −20 W sum to −60 W and must stay quiet, while a reversed
−1500 W hidden inside a positive total must not. It is a caution, never `bad`;
a wrong threshold costs a spurious or missed caution, never data. The text says
ADD (unflipped) or REMOVE (already flipped).
## 4. Mutations
Fresh `cp -a` each, `__pycache__` purged, CI image, `:ro`, `--network none`;
`test_pv.py` only unless stated. T1-T12 at 7559d21; M2/M5 are review #1628's,
red since B1 (merged into release/1.0 @ 43b2740).
| # | mutation | bytes | reds |
|---|---|---|---|
| T1 | flip ignored in `total()` | −45 | 9 |
| T2 | flip applied to every member | −7 | 6 |
| T3 | `0.0 - w` → `-w` | −5 | 1 |
| T4 | flip written as `abs()` | −1 | 2 |
| T5 | `_read_pv()` stops passing the flip | −26 | 7 |
| T6 | caution threshold −50 → 0 W | −17 | 3 |
| T7 | `[sign inverted]` marker dropped | −39 | 3 |
| T8 | negative caution made `bad` | +1 | 1 |
| T9 | `stray_inverts()` always empty | −50 | 1 |
| T10 | `inverted()` stops stripping | −8 | 1 |
| T11 | ADD/REMOVE advice swapped | +4 | 2 |
| T12 | `pv_invert_entities: []` deleted from `options:` | −25 | 2 test_translations + 17 probe |
| M2 | `stray_inverts()` roster ignores `pv_entity` | +15 | 2 (REPORTED CASE, PADDING) |
| M5 | `_read_pv()` flip set not stripped | +25 | 1 (PADDING) |
## 5. History
OPT-10's `sensor.goodwe_pv_power` recorded since 2026-09-24 on an install with
such sensors carries the old sign; nothing rewrites it.
## 6. CHANGELOG entry for the next PKG cut
Not written into `CHANGELOG.md`: `test_version_gate.py` 11b/11d refuse an
`## Unreleased` heading (measured on this branch: 4 reds). For PKG to paste:
> **`pv_invert_entities` (TEL-14).** A PV sensor that reads negative while
> producing — typically a kWh meter or power clamp in the PV circuit — can now
> be listed under *PV entities whose sign is reversed* in the Configuration tab;
> its sign is flipped before the sum and the status page marks it. Empty (the
> default) changes nothing. ⚠️ PV history recorded before you set it keeps the
> old sign.