Add configurable market price hard refresh at scheduled time#367
Conversation
Dynamic tariff data (e.g. EPEX Spot next-day prices) is published around 12:00-13:00 UTC. The existing hourly fail-safe scheduler may not pick up fresh data promptly. This change adds a dedicated daily hard refresh that bypasses the cache and always fetches from the provider. Changes: - dynamictariff/baseclass.py: Add force=True parameter to refresh_data() to bypass next_update_ts and always fetch fresh data. - scheduler.py: Add optional tz parameter to schedule_at() / SchedulerThread to support timezone-aware daily scheduling via schedule library. - core.py: Read market_price_refresh_time from battery_control_expert config (default "12:30" UTC) and schedule a daily _hard_refresh_prices() call. - config/batcontrol_config_dummy.yaml: Add commented market_price_refresh_time option under battery_control_expert. - Tests: Cover force refresh behaviour, tz-aware schedule_at, and core wiring.
There was a problem hiding this comment.
Pull request overview
Adds a configurable daily “hard refresh” for dynamic market prices at a scheduled UTC time (default 12:30) to reliably pick up newly published next-day tariffs (issue #366).
Changes:
- Introduces
battery_control_expert.market_price_refresh_timeand schedules a daily UTC job that forces a tariff refresh. - Extends scheduler support to optionally schedule daily jobs in a specified timezone (
tz). - Extends
DynamicTariffBaseclass.refresh_data()with aforceflag and adds tests for forced refresh + scheduling behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/batcontrol/core.py |
Adds config-driven daily UTC job to force-refresh dynamic tariff prices. |
src/batcontrol/dynamictariff/baseclass.py |
Adds force parameter to bypass next_update_ts checks for refresh. |
src/batcontrol/scheduler.py |
Adds optional tz parameter to schedule_at() and logs timezone context. |
config/batcontrol_config_dummy.yaml |
Documents the new expert config option (commented) and its format/intent. |
tests/batcontrol/test_scheduler.py |
Adds tests ensuring schedule_at() registers jobs with/without timezone. |
tests/batcontrol/test_core.py |
Adds tests for default/custom refresh time and hard refresh calling force=True. |
tests/batcontrol/dynamictariff/test_baseclass.py |
Adds tests validating force bypass behavior and next_update_ts update. |
Add _validate_market_price_refresh_time() to catch invalid values like 'noon' or '25:99' early at startup with a clear error message, rather than failing silently at runtime when the scheduler tries to register the job. Addresses Copilot review comment on PR #367.
Copilot Review ResponseComment 1 —
|
- dynamictariff_interface.py: Add force: bool = False to refresh_data() so TariffInterface matches DynamicTariffBaseclass and type checkers do not report a mismatch when core calls refresh_data(force=True). - scheduler.py: Normalize empty/whitespace tz to None in schedule_at() so logging and schedule.Job.at() behaviour are always consistent. An empty string is now treated identically to None (local time). - tests: Add edge-case tests for tz="" and tz=" " in schedule_at().
Copilot Review Response — Runde 2Kommentar 3 —
|
The previous normalization checked tz.strip() to detect whitespace-only values but assigned the original tz (with spaces) to effective_tz. A value like ' UTC ' would pass the None-guard and be forwarded as-is to schedule.Job.at(), causing an UnknownTimeZoneError at runtime. Fix: assign tz.strip() instead of tz so the stripped value is used. Adds test for ' UTC ' (padded timezone string). Addresses Copilot review comment on PR #367.
Copilot Review Response — Runde 3Kommentar 5 —
|
schedule_every() already sets wrapped_job.__name__ = name for consistent job identification in logs and schedule's job representation. Apply the same pattern to schedule_at() and schedule_once() for consistency across all scheduling helpers. Addresses Copilot review comment on PR #367.
Copilot Review Response — Runde 4Kommentar 6 —
|
now = time.time() was captured before the optional random delay, then used to set next_update_ts after the fetch. This shortened the effective min_time_between_updates by up to delay_evaluation_by_seconds seconds. Fix: use time.time() directly when setting next_update_ts so the timestamp reflects when the successful fetch actually completed. Pre-existing issue, addressed while modifying refresh_data() for #366.
Copilot Review Response — Runde 5Kommentar 7 —
|
The module-level _job_registry is shared across Batcontrol instances in the same process. Without clearing jobs on shutdown, reconstructing Batcontrol (e.g. on restart) accumulates duplicate scheduled jobs and can trigger multiple forced price refreshes per cycle. Fix: call scheduler.clear_jobs() before stopping the scheduler thread in Batcontrol.shutdown(). Adds a test to verify the registry is empty after shutdown. Addresses Copilot review comment on PR #367.
Copilot Review Response — Runde 6Kommentar 8 —
|
clear_jobs() was called before stop(), which could race with the scheduler thread's run_pending() loop mutating the job list concurrently. schedule.Scheduler is not thread-safe. Fix: stop() (which joins the thread) first, then clear_jobs() once the thread has terminated. Addresses Copilot review comment on PR #367.
Copilot Review Response — Runde 7Kommentar 9 —
|
Summary
Implements a configurable daily hard refresh of market prices at a scheduled UTC time (default 12:30) to pick up newly published dynamic tariff data, such as EPEX Spot next-day prices. This addresses issue #366.
Key Changes
market_price_refresh_timeconfiguration parameter (default "12:30" UTC) inbattery_control_expertsection, with a new scheduled job that triggers_hard_refresh_prices()dailyDynamicTariffBaseclass.refresh_data()with aforceparameter that bypasses cache validity checks (next_update_ts) to ensure fresh data is fetchedschedule_at()function to support optional timezone parameter, allowing jobs to be scheduled in specific timezones (e.g., UTC) rather than only server local timeImplementation Details
schedule_at()withtz='UTC'to ensure consistent timing across deploymentsforce=Trueis passed torefresh_data(), it logs the action and skips thenext_update_tscheck, then updatesnext_update_tsafter fetching to prevent excessive requestsschedule_at()method now conditionally applies timezone to the job definition only when specified, maintaining backward compatibilityhttps://claude.ai/code/session_012kMDVz2ykrD1pRpfghzpPP