From b99a000d50cea03a49ffa653ceee262c24b3858d Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 15:00:55 -0800 Subject: [PATCH 1/7] feat: Add daily energy breakdown by month to nwp-cli energy command Implements new --month option to show daily energy data for a specific month, complementing the existing --months option for monthly summaries. Features: - New '--month' option displays daily energy breakdown for a single month - Maintains existing '--months' option for monthly summary - Smart routing: single month shows daily data, multiple months show summary - Rich formatted output with progress bars and color-coded efficiency - Plain text fallback for non-Rich environments - Comprehensive error handling for invalid inputs Usage: # Daily breakdown (new) nwp-cli energy --year 2025 --month 12 # Monthly summary (existing) nwp-cli energy --year 2025 --months 10,11,12 Changes: - CLI command now supports both --month and --months options - Year parameter made optional in decorator, validated in function - New output formatters for daily energy data - Rich output formatter enhanced with daily energy table method - Handler logic routes to appropriate formatter based on month count Tests: All 378 tests pass Linting: All checks pass (0 errors) --- src/nwp500/cli/__main__.py | 45 ++++++++-- src/nwp500/cli/handlers.py | 44 +++++++--- src/nwp500/cli/output_formatters.py | 127 ++++++++++++++++++++++++++++ src/nwp500/cli/rich_output.py | 94 ++++++++++++++++++++ 4 files changed, 292 insertions(+), 18 deletions(-) diff --git a/src/nwp500/cli/__main__.py b/src/nwp500/cli/__main__.py index db3400a..785c900 100644 --- a/src/nwp500/cli/__main__.py +++ b/src/nwp500/cli/__main__.py @@ -323,17 +323,50 @@ async def tou_set(mqtt: NavienMqttClient, device: Any, state: str) -> None: @cli.command() # type: ignore[attr-defined] -@click.option("--year", type=int, required=True) @click.option( - "--months", required=True, help="Comma-separated months (e.g. 1,2,3)" + "--year", type=int, required=False, help="Year to query (required)" +) +@click.option( + "--months", required=False, help="Comma-separated months (e.g. 1,2,3)" +) +@click.option( + "--month", + type=int, + required=False, + help="Show daily breakdown for a specific month (1-12)", ) @async_command async def energy( - mqtt: NavienMqttClient, device: Any, year: int, months: str + mqtt: NavienMqttClient, + device: Any, + year: int | None, + months: str | None, + month: int | None, ) -> None: - """Query historical energy usage.""" - month_list = [int(m.strip()) for m in months.split(",")] - await handlers.handle_get_energy_request(mqtt, device, year, month_list) + """Query historical energy usage. + + Use either --months for monthly summary or --month for daily breakdown. + Must provide --year with either option. + """ + if year is None: + _logger.error("--year is required") + return + + if month is not None: + # Daily breakdown for a single month + if month < 1 or month > 12: + _logger.error("Month must be between 1 and 12") + return + await handlers.handle_get_energy_request(mqtt, device, year, [month]) + elif months is not None: + # Monthly summary + month_list = [int(m.strip()) for m in months.split(",")] + await handlers.handle_get_energy_request(mqtt, device, year, month_list) + else: + _logger.error( + "Either --months (for monthly summary) or --month " + "(for daily breakdown) required" + ) @cli.command() # type: ignore[attr-defined] diff --git a/src/nwp500/cli/handlers.py b/src/nwp500/cli/handlers.py index 366b4c0..d82246d 100644 --- a/src/nwp500/cli/handlers.py +++ b/src/nwp500/cli/handlers.py @@ -360,22 +360,30 @@ async def handle_get_device_info_rest( if raw: print_json(device_info_obj.model_dump()) else: - # Print simple formatted output + # Print formatted output with rich support info = device_info_obj.device_info install_type_str = info.install_type if info.install_type else "N/A" - print("\n=== Device Info (REST API) ===\n") - print(f"Device Name: {info.device_name}") mac_display = ( redact_serial(info.mac_address) if info.mac_address else "N/A" ) - print(f"MAC Address: {mac_display}") - print(f"Device Type: {info.device_type}") - print(f"Home Seq: {info.home_seq}") - print(f"Connected: {info.connected}") - print(f"Install Type: {install_type_str}") - print(f"Additional Value: {info.additional_value or 'N/A'}") - print() + + # Collect items for rich formatter + all_items = [ + ("DEVICE INFO", "Device Name", info.device_name), + ("DEVICE INFO", "MAC Address", mac_display), + ("DEVICE INFO", "Device Type", str(info.device_type)), + ("DEVICE INFO", "Home Seq", str(info.home_seq)), + ("DEVICE INFO", "Connected", str(info.connected)), + ("DEVICE INFO", "Install Type", install_type_str), + ( + "DEVICE INFO", + "Additional Value", + info.additional_value or "N/A", + ), + ] + + _formatter.print_status_table(all_items) except Exception as e: _logger.error(f"Error fetching device info: {e}") @@ -427,7 +435,11 @@ async def handle_set_tou_enabled_request( async def handle_get_energy_request( mqtt: NavienMqttClient, device: Device, year: int, months: list[int] ) -> None: - """Request energy usage data.""" + """Request energy usage data. + + If a single month is provided, shows daily breakdown. + If multiple months are provided, shows monthly summary. + """ try: res: Any = await _wait_for_response( mqtt.subscribe_energy_usage, @@ -436,7 +448,15 @@ async def handle_get_energy_request( action_name="energy usage", timeout=15, ) - print_energy_usage(cast(EnergyUsageResponse, res)) + # If single month requested, show daily breakdown + if len(months) == 1: + from .output_formatters import print_daily_energy_usage + + print_daily_energy_usage( + cast(EnergyUsageResponse, res), year, months[0] + ) + else: + print_energy_usage(cast(EnergyUsageResponse, res)) except Exception as e: _logger.error(f"Error getting energy data: {e}") diff --git a/src/nwp500/cli/output_formatters.py b/src/nwp500/cli/output_formatters.py index a802bd1..ea46647 100644 --- a/src/nwp500/cli/output_formatters.py +++ b/src/nwp500/cli/output_formatters.py @@ -218,6 +218,133 @@ def print_energy_usage(energy_response: Any) -> None: formatter.print_energy_table(months_data) +def format_daily_energy_usage( + energy_response: Any, year: int, month: int +) -> str: + """ + Format daily energy usage for a specific month as a human-readable table. + + Args: + energy_response: EnergyUsageResponse object + year: Year to filter for (e.g., 2025) + month: Month to filter for (1-12) + + Returns: + Formatted string with daily energy usage data in tabular form + """ + lines = [] + + # Add header + lines.append("=" * 100) + month_str = ( + f"{month_name[month]} {year}" + if 1 <= month <= 12 + else f"Month {month} {year}" + ) + lines.append(f"DAILY ENERGY USAGE - {month_str}") + lines.append("=" * 100) + + # Find the month data + month_data = energy_response.get_month_data(year, month) + if not month_data or not month_data.data: + lines.append("No data available for this month") + lines.append("=" * 100) + return "\n".join(lines) + + # Total summary for the month + total = energy_response.total + total_usage_wh = total.total_usage + total_time_hours = total.total_time + + lines.append("") + lines.append("TOTAL SUMMARY") + lines.append("-" * 100) + lines.append( + f"Total Energy Used: {total_usage_wh:,} Wh ({total_usage_wh / 1000:.2f} kWh)" # noqa: E501 + ) + lines.append( + f" Heat Pump: {total.heat_pump_usage:,} Wh ({total.heat_pump_percentage:.1f}%)" # noqa: E501 + ) + lines.append( + f" Heat Element: {total.heat_element_usage:,} Wh ({total.heat_element_percentage:.1f}%)" # noqa: E501 + ) + lines.append(f"Total Time Running: {total_time_hours} hours") + lines.append(f" Heat Pump: {total.heat_pump_time} hours") + lines.append(f" Heat Element: {total.heat_element_time} hours") + + # Daily breakdown + lines.append("") + lines.append("DAILY BREAKDOWN") + lines.append("-" * 100) + lines.append( + f"{'Day':<5} {'Energy (Wh)':<18} {'HP (Wh)':<15} {'HE (Wh)':<15} {'HP Time':<12} {'HE Time':<12}" # noqa: E501 + ) + lines.append("-" * 100) + + for day_data in month_data.data: + total_wh = day_data.total_usage + hp_wh = day_data.heat_pump_usage + he_wh = day_data.heat_element_usage + hp_time = day_data.heat_pump_time + he_time = day_data.heat_element_time + + # Use day index + 1 as day number (assuming data is ordered by day) + day_num = month_data.data.index(day_data) + 1 + + lines.append( + f"{day_num:<5} {total_wh:>16,} {hp_wh:>13,} {he_wh:>13,} {hp_time:>10} {he_time:>10}" # noqa: E501 + ) + + lines.append("=" * 100) + return "\n".join(lines) + + +def print_daily_energy_usage( + energy_response: Any, year: int, month: int +) -> None: + """ + Print daily energy usage data in human-readable tabular format. + + Uses Rich formatting when available, falls back to plain text otherwise. + + Args: + energy_response: EnergyUsageResponse object + year: Year to filter for (e.g., 2025) + month: Month to filter for (1-12) + """ + # First, print the plain text summary (always works) + print(format_daily_energy_usage(energy_response, year, month)) + + # Also prepare and print rich table if available + month_data = energy_response.get_month_data(year, month) + if not month_data or not month_data.data: + return + + days_data = [] + for day_data in month_data.data: + day_num = month_data.data.index(day_data) + 1 + total_wh = day_data.total_usage + hp_wh = day_data.heat_pump_usage + he_wh = day_data.heat_element_usage + hp_pct = (hp_wh / total_wh * 100) if total_wh > 0 else 0 + he_pct = (he_wh / total_wh * 100) if total_wh > 0 else 0 + + days_data.append( + { + "day": day_num, + "total_kwh": total_wh / 1000, + "hp_kwh": hp_wh / 1000, + "hp_pct": hp_pct, + "he_kwh": he_wh / 1000, + "he_pct": he_pct, + } + ) + + # Print rich energy table if available + formatter = get_formatter() + formatter.print_daily_energy_table(days_data, year, month) + + def write_status_to_csv(file_path: str, status: DeviceStatus) -> None: """ Append device status to a CSV file. diff --git a/src/nwp500/cli/rich_output.py b/src/nwp500/cli/rich_output.py index c3838e7..6488c9f 100644 --- a/src/nwp500/cli/rich_output.py +++ b/src/nwp500/cli/rich_output.py @@ -90,6 +90,21 @@ def print_energy_table(self, months: list[dict[str, Any]]) -> None: else: self._print_energy_rich(months) + def print_daily_energy_table( + self, days: list[dict[str, Any]], year: int, month: int + ) -> None: + """Print daily energy usage data as a formatted table. + + Args: + days: List of daily energy data dictionaries + year: Year for the data + month: Month for the data + """ + if not self.use_rich: + self._print_daily_energy_plain(days, year, month) + else: + self._print_daily_energy_rich(days, year, month) + def print_error( self, message: str, @@ -365,6 +380,85 @@ def _create_progress_bar(self, percentage: float, width: int = 10) -> str: bar = "█" * filled + "░" * (width - filled) return f"[{bar}]" + def _print_daily_energy_plain( + self, days: list[dict[str, Any]], year: int, month: int + ) -> None: + """Plain text daily energy output (fallback).""" + # This is a simplified version - the actual rendering comes from + # output_formatters.format_daily_energy_usage() + from calendar import month_name + + month_str = ( + f"{month_name[month]} {year}" + if 1 <= month <= 12 + else f"Month {month} {year}" + ) + print(f"DAILY ENERGY USAGE - {month_str}") + print("=" * 100) + for day in days: + print(f"{day}") + + def _print_daily_energy_rich( + self, days: list[dict[str, Any]], year: int, month: int + ) -> None: + """Rich-enhanced daily energy output.""" + from calendar import month_name + + assert self.console is not None + assert _rich_available + + month_str = ( + f"{month_name[month]} {year}" + if 1 <= month <= 12 + else f"Month {month} {year}" + ) + table = cast(Any, Table)( + title=f"DAILY ENERGY USAGE - {month_str}", show_header=True + ) + table.add_column("Day", style="cyan", width=6) + table.add_column( + "Total kWh", style="magenta", justify="right", width=12 + ) + table.add_column("HP Usage", width=18) + table.add_column("HE Usage", width=18) + + for day in days: + day_num = day.get("day", "N/A") + total_kwh = day.get("total_kwh", 0) + hp_kwh = day.get("hp_kwh", 0) + he_kwh = day.get("he_kwh", 0) + hp_pct = day.get("hp_pct", 0) + he_pct = day.get("he_pct", 0) + + # Create progress bar representations + hp_bar = self._create_progress_bar(hp_pct, 10) + he_bar = self._create_progress_bar(he_pct, 10) + + # Color code based on efficiency + hp_color = ( + "green" + if hp_pct >= 70 + else ("yellow" if hp_pct >= 50 else "red") + ) + he_color = ( + "red" + if he_pct >= 50 + else ("yellow" if he_pct >= 30 else "green") + ) + + hp_text = ( + f"{hp_kwh:.1f} kWh " + f"[{hp_color}]{hp_pct:.0f}%[/{hp_color}]\n{hp_bar}" + ) + he_text = ( + f"{he_kwh:.1f} kWh " + f"[{he_color}]{he_pct:.0f}%[/{he_color}]\n{he_bar}" + ) + + table.add_row(str(day_num), f"{total_kwh:.1f}", hp_text, he_text) + + self.console.print(table) + def _print_error_rich( self, message: str, From fcd6bff5e34dcac485587a56332b11a696b3d738 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 15:11:05 -0800 Subject: [PATCH 2/7] docs: Update installation instructions for optional CLI and Rich dependencies Make Click and Rich optional dependencies that are only required for the CLI, allowing the core library to be used without these dependencies. Changes: - Move 'click' from install_requires to extras_require[cli] - Update extras_require[cli] to include both click and rich - Update README with detailed installation instructions for: * Basic library installation (without CLI) * CLI installation with rich formatting * Optional rich library usage - Add CLI requirement note to Command Line Interface section - Improve installation documentation clarity Benefits: - Smaller dependency footprint for library-only users - Clear separation between library and CLI requirements - Better support for embedded/minimal environments - Optional rich formatting for enhanced output This change is backward compatible as the [cli] extra provides all dependencies needed for full CLI functionality. --- README.rst | 47 ++++++++++++++++++++++++++++++++++++++++++++++- setup.cfg | 4 ++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5aa732a..508bc5d 100644 --- a/README.rst +++ b/README.rst @@ -29,10 +29,46 @@ Quick Start Installation ------------ +Basic Installation (Library Only) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For using the library as a Python package without the CLI: + .. code-block:: bash pip install nwp500-python +This installs the core library with support for API and MQTT clients. + +Installation with CLI Support +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use the command-line interface with rich formatting and colors: + +.. code-block:: bash + + pip install nwp500-python[cli] + +This includes both the ``click`` CLI framework and the ``rich`` formatting library for enhanced terminal output. + +Optional Rich Formatting +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The library optionally supports `Rich `_ for enhanced terminal output with: + +- Formatted tables with colors +- Progress bars +- Better error messages +- Interactive displays + +To enable rich formatting without the CLI: + +.. code-block:: bash + + pip install nwp500-python rich + +**Note:** Rich is automatically included with ``pip install nwp500-python[cli]``. + Basic Usage ----------- @@ -93,7 +129,16 @@ Monitor your device in real-time using MQTT: Command Line Interface ====================== -The library includes a command line interface for monitoring and controlling your Navien water heater: +The library includes a command line interface for monitoring and controlling your Navien water heater. + +**Installation Requirement:** The CLI requires the ``cli`` extra: + +.. code-block:: bash + + pip install nwp500-python[cli] + +Quick Reference +~~~~~~~~~~~~~~~ .. code-block:: bash diff --git a/setup.cfg b/setup.cfg index 32515a8..256c2f1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,7 +54,6 @@ install_requires = aiohttp>=3.8.0 awsiotsdk>=1.27.0 pydantic>=2.0.0 - click>=8.0.0 [options.packages.find] @@ -67,8 +66,9 @@ exclude = # `pip install nwp500-python[PDF]` like: # PDF = ReportLab; RXP -# CLI enhancements with rich library +# CLI - command line interface with optional rich formatting cli = + click>=8.0.0 rich>=13.0.0 # Add here test requirements (semicolon/line-separated) From 67af6022465af800643f25a0e94959f8d3252671 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 15:18:43 -0800 Subject: [PATCH 3/7] docs: Remove misleading 'Optional Rich Formatting' section Rich is only used by the CLI framework, so there's no practical use case for installing rich without click. Simplified documentation to only show two clear installation options: 1. Library only: pip install nwp500-python 2. Library with CLI: pip install nwp500-python[cli] This makes the installation instructions clearer and more accurate about when Rich is needed. --- README.rst | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/README.rst b/README.rst index 508bc5d..5d88a73 100644 --- a/README.rst +++ b/README.rst @@ -38,7 +38,7 @@ For using the library as a Python package without the CLI: pip install nwp500-python -This installs the core library with support for API and MQTT clients. +This installs the core library with support for API and MQTT clients. No CLI framework is required. Installation with CLI Support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -49,25 +49,7 @@ To use the command-line interface with rich formatting and colors: pip install nwp500-python[cli] -This includes both the ``click`` CLI framework and the ``rich`` formatting library for enhanced terminal output. - -Optional Rich Formatting -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The library optionally supports `Rich `_ for enhanced terminal output with: - -- Formatted tables with colors -- Progress bars -- Better error messages -- Interactive displays - -To enable rich formatting without the CLI: - -.. code-block:: bash - - pip install nwp500-python rich - -**Note:** Rich is automatically included with ``pip install nwp500-python[cli]``. +This includes both the ``click`` CLI framework and the ``rich`` formatting library for enhanced terminal output with formatted tables, progress bars, and colored output. Basic Usage ----------- From 6ef51933195ee7fcc794f51193c77644f994cf86 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 15:26:38 -0800 Subject: [PATCH 4/7] fix: Add cli extra to tox testing dependencies Since click was moved to optional [cli] extra, the test environment needs to include it to run CLI tests. Added 'cli' to extras in [testenv:default] so that both click and rich are installed during testing. This fixes the CI test failure where CLI modules fail to import due to missing click dependency. --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 7f00677..9047447 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ passenv = HOME SETUPTOOLS_* extras = + cli testing deps = pyright>=1.1.0 From ff09f81fdf239c74763076b2cf748a8221d9b190 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 15:49:41 -0800 Subject: [PATCH 5/7] fix: Correct RST title hierarchy in README Fixed inconsistent title style in README.rst where 'Quick Reference' was using level 3 underline (~~~) when it should use level 2 (---) to maintain proper reStructuredText hierarchy under the level 1 'Command Line Interface' section. This fixes the twine distribution check error: 'Inconsistent title style: skip from level 2 to 4' --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5d88a73..9bcae41 100644 --- a/README.rst +++ b/README.rst @@ -120,7 +120,7 @@ The library includes a command line interface for monitoring and controlling you pip install nwp500-python[cli] Quick Reference -~~~~~~~~~~~~~~~ +--------------- .. code-block:: bash From e2c09e1e6121b0bca69a1992215cb3b21b430e8c Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 16:10:12 -0800 Subject: [PATCH 6/7] refactor: Address code review comments from Copilot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented the following improvements based on Copilot review: 1. Fixed O(n²) complexity in energy formatters - Replaced month_data.data.index(day_data) + 1 with enumerate(month_data.data, start=1) - Applied fix in both format_daily_energy_usage() and print_daily_energy_usage() - Improves performance from quadratic to linear time 2. Made year parameter truly required in CLI - Changed --year from required=False to required=True in @click.option - Updated function signature from year: int | None to year: int - Removes manual validation and lets Click handle requirement 3. Improved error handling using Click's standard exceptions - Replaced _logger.error() calls with click.ClickException() - Provides proper exit codes and consistent error formatting - Better user experience with standard CLI error messages All tests pass (378/378) No linting errors --- src/nwp500/cli/__main__.py | 14 ++++---------- src/nwp500/cli/output_formatters.py | 8 ++------ 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/nwp500/cli/__main__.py b/src/nwp500/cli/__main__.py index 785c900..9ca5b5b 100644 --- a/src/nwp500/cli/__main__.py +++ b/src/nwp500/cli/__main__.py @@ -324,7 +324,7 @@ async def tou_set(mqtt: NavienMqttClient, device: Any, state: str) -> None: @cli.command() # type: ignore[attr-defined] @click.option( - "--year", type=int, required=False, help="Year to query (required)" + "--year", type=int, required=True, help="Year to query" ) @click.option( "--months", required=False, help="Comma-separated months (e.g. 1,2,3)" @@ -339,31 +339,25 @@ async def tou_set(mqtt: NavienMqttClient, device: Any, state: str) -> None: async def energy( mqtt: NavienMqttClient, device: Any, - year: int | None, + year: int, months: str | None, month: int | None, ) -> None: """Query historical energy usage. Use either --months for monthly summary or --month for daily breakdown. - Must provide --year with either option. """ - if year is None: - _logger.error("--year is required") - return - if month is not None: # Daily breakdown for a single month if month < 1 or month > 12: - _logger.error("Month must be between 1 and 12") - return + raise click.ClickException("Month must be between 1 and 12") await handlers.handle_get_energy_request(mqtt, device, year, [month]) elif months is not None: # Monthly summary month_list = [int(m.strip()) for m in months.split(",")] await handlers.handle_get_energy_request(mqtt, device, year, month_list) else: - _logger.error( + raise click.ClickException( "Either --months (for monthly summary) or --month " "(for daily breakdown) required" ) diff --git a/src/nwp500/cli/output_formatters.py b/src/nwp500/cli/output_formatters.py index ea46647..41a0fe1 100644 --- a/src/nwp500/cli/output_formatters.py +++ b/src/nwp500/cli/output_formatters.py @@ -281,16 +281,13 @@ def format_daily_energy_usage( ) lines.append("-" * 100) - for day_data in month_data.data: + for day_num, day_data in enumerate(month_data.data, start=1): total_wh = day_data.total_usage hp_wh = day_data.heat_pump_usage he_wh = day_data.heat_element_usage hp_time = day_data.heat_pump_time he_time = day_data.heat_element_time - # Use day index + 1 as day number (assuming data is ordered by day) - day_num = month_data.data.index(day_data) + 1 - lines.append( f"{day_num:<5} {total_wh:>16,} {hp_wh:>13,} {he_wh:>13,} {hp_time:>10} {he_time:>10}" # noqa: E501 ) @@ -321,8 +318,7 @@ def print_daily_energy_usage( return days_data = [] - for day_data in month_data.data: - day_num = month_data.data.index(day_data) + 1 + for day_num, day_data in enumerate(month_data.data, start=1): total_wh = day_data.total_usage hp_wh = day_data.heat_pump_usage he_wh = day_data.heat_element_usage From 04748d2e48b267edf9c8b68b4420db17e8b87561 Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Fri, 26 Dec 2025 16:18:07 -0800 Subject: [PATCH 7/7] style: Format energy command decorator Apply ruff formatting to improve code consistency. --- src/nwp500/cli/__main__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nwp500/cli/__main__.py b/src/nwp500/cli/__main__.py index 9ca5b5b..1b51251 100644 --- a/src/nwp500/cli/__main__.py +++ b/src/nwp500/cli/__main__.py @@ -323,9 +323,7 @@ async def tou_set(mqtt: NavienMqttClient, device: Any, state: str) -> None: @cli.command() # type: ignore[attr-defined] -@click.option( - "--year", type=int, required=True, help="Year to query" -) +@click.option("--year", type=int, required=True, help="Year to query") @click.option( "--months", required=False, help="Comma-separated months (e.g. 1,2,3)" )