Skip to content

feat(influxdb): export peak power, enriched Tempo, cost simulation, contract, address and collection health - #628

Open
Germwalker wants to merge 7 commits into
MyElectricalData:mainfrom
Germwalker:feat/influxdb-additional-exports
Open

feat(influxdb): export peak power, enriched Tempo, cost simulation, contract, address and collection health#628
Germwalker wants to merge 7 commits into
MyElectricalData:mainfrom
Germwalker:feat/influxdb-additional-exports

Conversation

@Germwalker

Copy link
Copy Markdown

What this adds

Six new, independently-gated InfluxDB exports, each following the existing patterns already
used by ExportInfluxDB (for the data shape) and ExportMqtt (for the source query), plus one
bug fix discovered while validating the three "instant" exports end-to-end. All are disabled
by default
— nothing changes for existing installs until a user opts in.

  1. power_max — daily peak power. One point per day from the
    consumption_daily_max_power cache: peak power in VA, exact timestamp of the peak, and
    usage percentage against the contract's subscribed power when known.
  2. Tempo enrichmentExportInfluxDB.tempo() now also writes, on the current day's point
    only, color_tomorrow, the 6 configured Tempo prices (price_<color>) and the
    day-per-color counters (days_<color>). These share the same measurement/tags/timestamp as
    the existing color field, so InfluxDB merges them into the same point.
  3. cost_simulation — simulated costs by tariff offer (BASE/HC/HP/TEMPO with its 6
    sub-periods), sourced from the existing statistic/price_consumption cache. Tag model
    (granularity = year/month, offer, period) is designed so a
    sum(euro) group by (year) where granularity="year" query gives the yearly total per offer
    without a cartesian product against the monthly rows.
  4. contract_export — contract details (subscribed power, also converted to VA; plan;
    meter type; segment; distribution tariff; status; last activation date; the 7
    offpeak_hours_N fields), sourced from db.get_contract(), already used by
    ExportMqtt.contract().
  5. address_export — delivery point address (street, postal code, city, INSEE code),
    sourced from db.get_addresse(), already used by ExportMqtt.address().
  6. health_export — collection health (API quota used/limit, last call timestamp, last
    error, and consentement_days_left: days left before the Enedis consent expires, negative
    if already expired), sourced from db.get_usage_point(), already used by
    ExportMqtt.status().

Each export adds: one method on ExportInfluxDB (src/models/export_influxdb.py), one
conditional call in Job.run() (src/models/jobs.py), and one configuration key with
enable: False by default plus a <name>_config() accessor (src/models/config.py).

Included fix: wrong timestamp on the three "instant" exports

contract(), address() and health() write a single point per cycle with
self.tz.localize(datetime.now()). datetime.now() returns the container's local wall-clock
time (e.g. Europe/Paris via the TZ env var); localize() then labels that value as-is in
self.tz (UTC in the default config) without converting it — so the point lands in the
future by exactly the timezone offset (2h in summer, observed end-to-end: the points existed
in InfluxDB but a range(stop: now()) query without a future bound would not surface them).
Fixed by using datetime.now(pytz.utc).astimezone(self.tz), which computes the correct instant
regardless of the configured timezone. This only affects the three exports introduced in this
PR, so it is included here rather than filed as a separate fix.

Scope

3 files, 7 commits, +343/-2 lines total:

  • src/models/config.py (+65)
  • src/models/export_influxdb.py (+249/-2, includes the timestamp fix)
  • src/models/jobs.py (+31)

Notes for reviewers

  • Happy to split this into one PR per export if you'd rather review/merge them independently —
    grouped them here since they share the same three files and the same small pattern.
  • No existing behavior changes: every new call in jobs.py is gated behind its own
    <name>.enable configuration flag, defaulting to False.

Add ExportInfluxDB.max_power(), following the same pattern as tempo():
one point per day from the consumption_daily_max_power cache, with the
peak power in VA, the exact timestamp of the peak, and the usage
percentage against the contract's subscribed power when it is known.

Wired conditionally into Job.run() (jobs.py), same pattern as the
existing tempo export, and gated behind a new "power_max.enable"
configuration key, disabled by default so existing installs are
unaffected until opted in.
… day counts

Extend ExportInfluxDB.tempo(): in addition to the color field already
exported for every cached day, the current day's point now also gets
color_tomorrow, the 6 configured Tempo prices (price_<color>) and the
day-per-color counters (days_<color>), read from tempo_config. Since
these extra fields share the same measurement/tags/timestamp as the
existing color field, InfluxDB merges them into the same point instead
of creating a new one.
Add ExportInfluxDB.cost_simulation(), sourced from the existing
statistic/price_consumption cache (already used by the cost simulation
page), a nested JSON {year: {month: {offer: {euro,kWh,Wh}}}} with
offer among BASE, HC, HP and TEMPO (itself split by sub-period).

One point is written per (year[, month], offer[, TEMPO period]), with
a tag model designed to let InfluxDB queries sum by offer and by year
without a cartesian product: `granularity` (year/month) separates the
yearly total from its monthly components, and `offer`/`period` avoid
mixing TEMPO's six sub-periods with the single BASE/HC/HP amounts.

Wired conditionally into Job.run() (jobs.py) and gated behind a new
"cost_simulation.enable" configuration key, disabled by default.
Add ExportInfluxDB.contract(), sourced from db.get_contract(), already
used by ExportMqtt.contract(). Writes a single point in time (now),
overwritten on every cycle, with the subscribed power, plan, meter
type, segment, distribution tariff, contract status, last activation
date and the 7 offpeak_hours_N fields. Subscribed power is additionally
converted to VA (subscribed_power_va) for direct use in dashboards and
alerting, alongside the original string.

Wired conditionally into Job.run() (jobs.py) and gated behind a new
"contract_export.enable" configuration key, disabled by default.
Add ExportInfluxDB.address(), sourced from db.get_addresse(), already
used by ExportMqtt.address(). Writes a single point in time (now),
overwritten on every cycle, with street, postal code, city and INSEE
code as text fields; no tag beyond usage_point_id since none of these
values has a cardinality worth indexing on.

Wired conditionally into Job.run() (jobs.py) and gated behind a new
"address_export.enable" configuration key, disabled by default.
Add ExportInfluxDB.health(), sourced from db.get_usage_point(), already
used by ExportMqtt.status(). Writes a single point in time (now),
overwritten on every cycle, with the API call quota used and its
limit, last call timestamp, last error, and consentement_days_left:
the number of days left before the Enedis consent expires (negative if
already expired), computed here so Grafana/InfluxDB alerting does not
need to recompute it.

Wired conditionally into Job.run() (jobs.py) and gated behind a new
"health_export.enable" configuration key, disabled by default.
contract(), address() and health() used self.tz.localize(datetime.now()):
datetime.now() returns the container's local wall-clock time (e.g.
Europe/Paris via the TZ env var), and localize() labels that value
as-is in self.tz (UTC here) without converting it, so the point was
written with a timestamp in the FUTURE (offset = the timezone, 2h in
summer). Found during end-to-end verification: the points existed in
InfluxDB but a `range(stop: now())` query without a future bound could
not see them.

Fix: use datetime.now(pytz.utc).astimezone(self.tz), which computes
the correct current instant regardless of the configured timezone.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant