checks / unit (pull_request) Successful in 8m44s
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
84 lines
4.4 KiB
Markdown
84 lines
4.4 KiB
Markdown
# 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.
|