master 8ac1376e7dad cached
320 files
3.8 MB
1.0M tokens
1960 symbols
1 requests
Download .txt
Showing preview only (4,024K chars total). Download the full file or copy to clipboard to get everything.
Repository: swingerman/ha-dual-smart-thermostat
Branch: master
Commit: 8ac1376e7dad
Files: 320
Total size: 3.8 MB

Directory structure:
gitextract_t8ge55s3/

├── .copilot-instructions.md
├── .coveragerc
├── .devcontainer.json
├── .dockerignore
├── .github/
│   ├── DEPENDABOT_AUTO_MERGE.md
│   ├── FUNDING.yml
│   ├── RELEASE_TEMPLATE.md
│   ├── SECURITY_REMEDIATION.md
│   ├── dependabot.yml
│   ├── prompts/
│   │   ├── plan.prompt.md
│   │   ├── specify.prompt.md
│   │   └── tasks.prompt.md
│   ├── release.yml
│   ├── scripts/
│   │   └── update_hacs_manifest.py
│   └── workflows/
│       ├── claude.yml
│       ├── dependabot-auto-merge.yml
│       ├── hacs-validate.yaml
│       ├── linting.yaml
│       ├── quality-check.yaml
│       ├── security-check.yml
│       ├── tests.yaml
│       └── workflow-status.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .specify/
│   ├── memory/
│   │   ├── constitution.md
│   │   └── constitution_update_checklist.md
│   └── templates/
│       ├── agent-file-template.md
│       ├── checklist-template.md
│       ├── plan-template.md
│       ├── spec-template.md
│       └── tasks-template.md
├── CHANGELOG.md
├── CLAUDE.md
├── Dockerfile.dev
├── LICENSE
├── LICENSE.md
├── README-DOCKER.md
├── README.md
├── RELEASE_NOTES_v0.11.0.md
├── action/
│   ├── Dockerfile
│   ├── action.py
│   └── action.yaml
├── build_release.sh
├── config/
│   └── configuration.yaml
├── custom_components/
│   ├── __init__.py
│   └── dual_smart_thermostat/
│       ├── __init__.py
│       ├── climate.py
│       ├── config_flow.py
│       ├── config_validation.py
│       ├── const.py
│       ├── feature_steps/
│       │   ├── __init__.py
│       │   ├── fan.py
│       │   ├── floor.py
│       │   ├── humidity.py
│       │   ├── openings.py
│       │   ├── presets.py
│       │   └── shared.py
│       ├── flow_utils.py
│       ├── hvac_action_reason/
│       │   ├── __init__.py
│       │   ├── hvac_action_reason.py
│       │   ├── hvac_action_reason_auto.py
│       │   ├── hvac_action_reason_external.py
│       │   └── hvac_action_reason_internal.py
│       ├── hvac_controller/
│       │   ├── __init__.py
│       │   ├── cooler_controller.py
│       │   ├── generic_controller.py
│       │   ├── heater_controller.py
│       │   └── hvac_controller.py
│       ├── hvac_device/
│       │   ├── __init__.py
│       │   ├── controllable_hvac_device.py
│       │   ├── cooler_device.py
│       │   ├── cooler_fan_device.py
│       │   ├── dryer_device.py
│       │   ├── fan_device.py
│       │   ├── generic_hvac_device.py
│       │   ├── heat_pump_device.py
│       │   ├── heater_aux_heater_device.py
│       │   ├── heater_cooler_device.py
│       │   ├── heater_device.py
│       │   ├── hvac_device.py
│       │   ├── hvac_device_factory.py
│       │   └── multi_hvac_device.py
│       ├── managers/
│       │   ├── __init__.py
│       │   ├── auto_mode_evaluator.py
│       │   ├── environment_manager.py
│       │   ├── feature_manager.py
│       │   ├── hvac_power_manager.py
│       │   ├── opening_manager.py
│       │   ├── preset_manager.py
│       │   └── state_manager.py
│       ├── manifest.json
│       ├── models.py
│       ├── options_flow.py
│       ├── preset_env/
│       │   ├── __init__.py
│       │   └── preset_env.py
│       ├── schema_utils.py
│       ├── schemas.py
│       ├── sensor.py
│       ├── services.yaml
│       └── translations/
│           ├── en.json
│           └── sk.json
├── demo_openings_translations.py
├── demo_translations.py
├── docker-compose.yml
├── docs/
│   ├── TESTING.md
│   ├── config/
│   │   ├── CONFIG_FLOW.md
│   │   ├── CRITICAL_CONFIG_DEPENDENCIES.md
│   │   ├── DEPENDENCY_ANALYSIS_SUMMARY.md
│   │   └── FOCUSED_DEPENDENCIES_SUMMARY.md
│   ├── config_flow/
│   │   ├── ac_only_features.md
│   │   ├── architecture.md
│   │   └── step_ordering.md
│   ├── plans/
│   │   ├── 2026-01-21-fan-speed-control-design.md
│   │   └── 2026-01-21-fan-speed-control.md
│   ├── superpowers/
│   │   ├── plans/
│   │   │   ├── 2026-04-21-auto-mode-phase-0-action-reason-sensor.md
│   │   │   ├── 2026-04-22-auto-mode-phase-1-1-availability-detection.md
│   │   │   ├── 2026-04-27-auto-mode-phase-1-2-priority-engine.md
│   │   │   ├── 2026-04-29-auto-mode-phase-1-3-outside-bias.md
│   │   │   └── 2026-04-30-auto-mode-phase-1-4-apparent-temp.md
│   │   └── specs/
│   │       ├── 2026-04-21-auto-mode-phase-0-action-reason-sensor-design.md
│   │       ├── 2026-04-22-auto-mode-phase-1-1-availability-detection-design.md
│   │       ├── 2026-04-27-auto-mode-phase-1-2-priority-engine-design.md
│   │       ├── 2026-04-29-auto-mode-phase-1-3-outside-bias-design.md
│   │       └── 2026-04-30-auto-mode-phase-1-4-apparent-temp-design.md
│   └── troubleshooting.md
├── examples/
│   ├── README.md
│   ├── advanced_features/
│   │   ├── floor_heating_with_limits.yaml
│   │   ├── openings_with_timeout.yaml
│   │   ├── presets_advanced.yaml
│   │   ├── presets_with_templates.yaml
│   │   └── two_stage_heating.yaml
│   ├── basic_configurations/
│   │   ├── cooler_only.yaml
│   │   ├── heat_pump.yaml
│   │   ├── heater_cooler.yaml
│   │   └── heater_only.yaml
│   ├── integrations/
│   │   └── smart_scheduling.yaml
│   └── single_mode_wrapper/
│       ├── README.md
│       ├── automation.yaml
│       ├── configuration.yaml
│       └── helpers.yaml
├── hacs.json
├── manage/
│   ├── bump_frontend
│   ├── hacs
│   ├── integration_start
│   ├── lgtm.js
│   ├── update_manifest.py
│   └── update_requirements.py
├── pcap.py
├── pytest.ini
├── pytest.log
├── requirements-dev.txt
├── requirements.txt
├── scripts/
│   ├── devcontainer_install_deps.sh
│   ├── develop
│   ├── docker-lint
│   ├── docker-shell
│   ├── docker-test
│   ├── lint
│   └── setup
├── setup.cfg
├── sonar-project.properties
├── specs/
│   ├── 001-develop-config-and/
│   │   ├── FEATURE_TESTING_PLAN.md
│   │   ├── FEATURE_TESTING_PLAN_EXPANDED.md
│   │   ├── FLOW_SEPARATION_ANALYSIS.md
│   │   ├── GITHUB_ISSUES_UPDATE_PLAN.md
│   │   ├── HOUSEKEEPING.md
│   │   ├── OPTIONS_FLOW_BUG_FIX.md
│   │   ├── RECONFIGURE_FLOW_MIGRATION.md
│   │   ├── REORG.md
│   │   ├── UPDATED_TASKS_STRATEGY.md
│   │   ├── contracts/
│   │   │   └── step-handlers.md
│   │   ├── data-model.md
│   │   ├── github-issues-update.md
│   │   ├── github-sync-status.md
│   │   ├── plan.md
│   │   ├── quickstart.md
│   │   ├── research.md
│   │   ├── schema-consolidation-proposal.md
│   │   ├── spec.md
│   │   ├── tasks.md
│   │   └── test-preservation.md
│   ├── 002-separate-tolerances/
│   │   ├── checklists/
│   │   │   └── requirements.md
│   │   ├── contracts/
│   │   │   └── tolerance_selection_api.md
│   │   ├── data-model.md
│   │   ├── plan.md
│   │   ├── quickstart.md
│   │   ├── research.md
│   │   ├── spec.md
│   │   └── tasks.md
│   ├── 003-separate-tolerances/
│   │   ├── BEHAVIOR_DIAGRAM.md
│   │   ├── IMPLEMENTATION_COMPLETE.md
│   │   └── README.md
│   ├── 004-template-based-presets/
│   │   ├── IMPLEMENTATION_PROGRESS.md
│   │   ├── IMPLEMENTATION_STATUS.md
│   │   ├── PHASE10_COMPLETE.md
│   │   ├── PHASE4_COMPLETE.md
│   │   ├── PHASE5_COMPLETE.md
│   │   ├── PHASE6_COMPLETE.md
│   │   ├── PHASE7_COMPLETE.md
│   │   ├── PHASE9_COMPLETE.md
│   │   ├── analysis-report.md
│   │   ├── checklists/
│   │   │   └── requirements.md
│   │   ├── contracts/
│   │   │   └── preset_env_api.md
│   │   ├── data-model.md
│   │   ├── plan.md
│   │   ├── quickstart.md
│   │   ├── research.md
│   │   ├── spec.md
│   │   └── tasks.md
│   ├── README.md
│   └── issue-096-template-based-presets.md
├── test-results/
│   └── .last-run.json
├── tests/
│   ├── FEATURES.md
│   ├── __init__.py
│   ├── behavioral/
│   │   └── test_tolerance_thresholds.py
│   ├── common.py
│   ├── config_flow/
│   │   ├── __init__.py
│   │   ├── test_ac_only_advanced_settings.py
│   │   ├── test_ac_only_features.py
│   │   ├── test_ac_only_features_integration.py
│   │   ├── test_advanced_options.py
│   │   ├── test_config_flow.py
│   │   ├── test_config_flow_validation.py
│   │   ├── test_e2e_ac_only_persistence.py
│   │   ├── test_e2e_heat_pump_persistence.py
│   │   ├── test_e2e_heater_cooler_persistence.py
│   │   ├── test_e2e_simple_heater_persistence.py
│   │   ├── test_heat_pump_config_flow.py
│   │   ├── test_heat_pump_features_integration.py
│   │   ├── test_heat_pump_options_flow.py
│   │   ├── test_heater_cooler_features_integration.py
│   │   ├── test_heater_cooler_flow.py
│   │   ├── test_integration.py
│   │   ├── test_options_entry_helpers.py
│   │   ├── test_options_flow.py
│   │   ├── test_preset_templates_config_flow.py
│   │   ├── test_reconfigure_flow.py
│   │   ├── test_reconfigure_flow_e2e_ac_only.py
│   │   ├── test_reconfigure_flow_e2e_heat_pump.py
│   │   ├── test_reconfigure_flow_e2e_heater_cooler.py
│   │   ├── test_reconfigure_flow_e2e_simple_heater.py
│   │   ├── test_reconfigure_system_type_change.py
│   │   ├── test_simple_heater_advanced.py
│   │   ├── test_simple_heater_features_integration.py
│   │   ├── test_step_ordering.py
│   │   └── test_translations.py
│   ├── conftest.py
│   ├── const.py
│   ├── contracts/
│   │   ├── GREEN_PHASE_RESULTS.md
│   │   ├── RED_PHASE_RESULTS.md
│   │   ├── __init__.py
│   │   ├── test_feature_availability_contracts.py
│   │   ├── test_feature_ordering_contracts.py
│   │   └── test_feature_schema_contracts.py
│   ├── edge_cases/
│   │   ├── __init__.py
│   │   ├── test_issue_10_tolerance_precision.py
│   │   ├── test_issue_461_redundant_commands.py
│   │   ├── test_issue_467_idle_continuous_off.py
│   │   ├── test_issue_468_precision_rounding.py
│   │   ├── test_issue_469_off_state_control_bypass.py
│   │   ├── test_issue_480_heater_cooler_both_fire.py
│   │   ├── test_issue_484_keep_alive_timedelta.py
│   │   ├── test_issue_499_multiple_thermostats_unavailable.py
│   │   ├── test_issue_499_yaml_entity_unavailable_on_startup.py
│   │   ├── test_issue_506_behavior_tolerance_ignored.py
│   │   ├── test_issue_506_tolerance_in_range_mode.py
│   │   ├── test_issue_506_user_exact_scenario.py
│   │   ├── test_issue_506_yaml_tolerance_defaults.py
│   │   └── test_issue_518_heater_turns_off_prematurely.py
│   ├── features/
│   │   ├── test_ac_features_ux.py
│   │   ├── test_advanced_toggle_feature.py
│   │   ├── test_feature_hvac_mode_interactions.py
│   │   ├── test_heater_cooler_with_fan.py
│   │   ├── test_heater_cooler_with_humidity.py
│   │   ├── test_openings_with_hvac_modes.py
│   │   └── test_presets_with_all_features.py
│   ├── fixtures/
│   │   └── configuration.yaml
│   ├── managers/
│   │   ├── test_environment_manager.py
│   │   ├── test_hvac_device_factory.py
│   │   └── test_preset_manager_templates.py
│   ├── openings/
│   │   ├── test_openings_config_flow.py
│   │   ├── test_openings_multiselect.py
│   │   ├── test_openings_options_flow.py
│   │   └── test_scope_generation.py
│   ├── preset_env/
│   │   └── test_preset_env_templates.py
│   ├── presets/
│   │   ├── test_comprehensive_preset_logic.py
│   │   └── test_preset_form_organization.py
│   ├── test_auto_mode_availability.py
│   ├── test_auto_mode_evaluator.py
│   ├── test_auto_mode_integration.py
│   ├── test_auto_preset_selection.py
│   ├── test_config_flow.py
│   ├── test_cooler_mode.py
│   ├── test_cooler_mode_behavioral.py
│   ├── test_dry_mode.py
│   ├── test_dual_mode.py
│   ├── test_dual_mode_behavioral.py
│   ├── test_environment_manager.py
│   ├── test_fan_mode.py
│   ├── test_fan_speed_control.py
│   ├── test_heat_pump_mode.py
│   ├── test_heat_pump_mode_behavioral.py
│   ├── test_heater_mode.py
│   ├── test_heater_mode_behavioral.py
│   ├── test_hvac_action_reason_sensor.py
│   ├── test_hvac_action_reason_service.py
│   ├── test_init.py
│   ├── test_logger_multiple_instances.py
│   ├── test_presets_schema.py
│   └── unit/
│       ├── test_config_validation_integration.py
│       ├── test_heat_pump_schema.py
│       ├── test_heater_cooler_schema.py
│       ├── test_models.py
│       └── test_schema_utils.py
└── tools/
    ├── README.md
    ├── __init__.py
    ├── clean_db.py
    ├── config_validator.py
    ├── focused_config_dependencies.json
    └── focused_config_dependencies.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .copilot-instructions.md
================================================
# Copilot Instructions for Home Assistant Dual Smart Thermostat

## Project Overview

The `dual_smart_thermostat` is an enhanced version of the generic thermostat implemented in Home Assistant. It provides sophisticated thermostat logic with multiple HVAC modes, device types, and advanced features like floor temperature control, opening detection, and preset management.

## Architecture

The project follows a modular architecture designed for safety, maintainability, and feature isolation:

### Core Directory Structure

```
custom_components/dual_smart_thermostat/
├── __init__.py              # Component initialization
├── climate.py               # Main climate entity implementation
├── config_flow.py           # Configuration flow
├── const.py                 # Constants and configurations
├── services.yaml            # Service definitions
├── hvac_device/             # Device type implementations
├── managers/                # Shared logic managers
├── hvac_controller/         # Control logic
├── hvac_action_reason/      # Action reason tracking
├── preset_env/              # Preset environment handling
└── translations/            # Localization files
```

### Key Components

#### 1. HVAC Devices (`./hvac_device/`)
Device-specific implementations for different HVAC equipment types:
- `heater_device.py` - Standard heating devices
- `cooler_device.py` - Cooling/AC devices
- `heat_pump_device.py` - Heat pump systems
- `cooler_fan_device.py` - Fan-enabled cooling
- `heater_aux_heater_device.py` - Two-stage heating
- `heater_cooler_device.py` - Dual heating/cooling
- `generic_hvac_device.py` - Base device class
- `hvac_device_factory.py` - Device creation factory

#### 2. Managers (`./managers/`)
Shared logic components that handle specific aspects of thermostat operation:
- `environment_manager.py` - Environmental conditions tracking
- `feature_manager.py` - Feature enablement and configuration
- `hvac_power_manager.py` - Power management and cycling
- `opening_manager.py` - Window/door opening detection
- `preset_manager.py` - Preset mode handling
- `state_manager.py` - State persistence and restoration

#### 3. Controllers (`./hvac_controller/`)
Control logic for different HVAC operation modes:
- `generic_controller.py` - Base controller class
- `heater_controller.py` - Heating control logic
- `cooler_controller.py` - Cooling control logic
- `hvac_controller.py` - Main controller coordination

#### 4. Action Reasons (`./hvac_action_reason/`)
System for tracking why HVAC actions occur:
- `hvac_action_reason.py` - Base action reason handling
- `hvac_action_reason_internal.py` - Internal system reasons
- `hvac_action_reason_external.py` - External trigger reasons

## Development Guidelines

### Code Organization Principles

1. **Separation of Concerns**: Each component has a single, well-defined responsibility
2. **Device Abstraction**: Different HVAC equipment types are abstracted into separate device classes
3. **Manager Pattern**: Shared logic is extracted into manager classes to avoid duplication
4. **Controller Pattern**: Control logic is separated from device logic for flexibility

### Configuration Dependency Requirements

**CRITICAL: When adding new features or configuration parameters, you MUST update configuration dependencies:**

1. **New Configuration Parameters**: Any new parameter added to `const.py` or config flow MUST be analyzed for dependencies
2. **Conditional Parameters**: Parameters that only make sense when other parameters are configured MUST be documented in dependency files
3. **Required Updates**: All new features with conditional parameters require updating:
   - `tools/focused_config_dependencies.json` - Add new conditional dependencies
   - `tools/config_validator.py` - Update validation rules
   - `docs/config/CRITICAL_CONFIG_DEPENDENCIES.md` - Document new dependencies with examples

**Configuration Dependency Update Checklist:**
- [ ] Identify if new parameter depends on another parameter to function
- [ ] Add conditional dependency to `tools/focused_config_dependencies.json`
- [ ] Update validation rules in `tools/config_validator.py`
- [ ] Add documentation with examples to `docs/config/CRITICAL_CONFIG_DEPENDENCIES.md`
- [ ] Test validation with example configurations
- [ ] Verify config flow properly handles new dependencies

**Examples of Conditional Dependencies:**
- Parameters requiring enabling parameters: `max_floor_temp` requires `floor_sensor`
- Feature-specific parameters: `fan_mode` requires `fan` entity
- Mode-specific parameters: `target_temp_low` requires `heat_cool_mode: true`

**Validation Testing Required:**
```bash
# Test new dependencies
python tools/config_validator.py
```

### Configuration Flow Integration Requirements

**CRITICAL**: Every added or modified configuration option MUST be integrated into the appropriate configuration flows (config, reconfigure, or options flows). This is a mandatory requirement for all configuration changes.

#### When Flow Integration is Required

Flow integration is required whenever you:
1. Add a new configuration parameter to `const.py` or `schemas.py`
2. Modify an existing configuration parameter's behavior or validation
3. Add a new feature that requires user configuration
4. Change how configuration options interact with each other

#### Which Flow(s) to Update

Determine which flow(s) need updates based on the type of change:

1. **Initial Configuration Flow** (`config_flow.py`):
   - New system types or HVAC modes
   - New required entities (heater, cooler, sensors)
   - New features that should be configured during initial setup
   - Core system behavior changes

2. **Reconfigure Flow** (`config_flow.py` - reconfigure handlers):
   - Changes to existing system configuration that require reconfiguration
   - System type switching
   - Entity replacement or updates
   - Any change that affects the initial configuration flow

3. **Options Flow** (`options_flow.py`):
   - Feature toggles (enabling/disabling features)
   - Feature-specific settings (thresholds, timeouts, behaviors)
   - Preset configurations
   - Advanced settings that don't require reconfiguration
   - Any setting that users might want to change after initial setup

**Rule of Thumb**: If users need to configure it during initial setup, add it to config/reconfigure flows. If users might want to adjust it later, add it to options flow. Often, you'll need to add to both.

#### Flow Integration Process

1. **Add Constants and Schema**: Define configuration keys in `const.py` and validation schemas in `schemas.py`
2. **Add Configuration Step**: Create or update step handlers in `feature_steps/` or flow files
3. **Update Flow Navigation**: Modify `_determine_next_step()` or flow handler logic to include new step
4. **Add Data Validation**: Implement validation logic with clear error messages
5. **Update Translations**: Add user-facing text to `translations/en.json`
6. **Add Tests**: Create unit and integration tests in `tests/config_flow/`

#### Testing Requirements for Flow Changes

**REQUIRED**: All flow changes must include:
- Unit tests for step handler logic and validation
- Integration tests for complete flow with new option
- Persistence tests (config → options flow)
- Edge case testing
- Manual testing across different system types

#### Clarification Process

If it's unclear how to integrate a configuration change into the flows:

1. **Analyze the Feature**: Determine what it controls, whether it's core or optional, and its dependencies
2. **Review Similar Features**: Find and follow patterns from similar existing features
3. **Check Dependencies**: Identify where it should appear in step ordering
4. **Ask for Clarification**: Document your analysis and ask specifically which flow(s) to update

**Remember**: When in doubt, add to both config/reconfigure AND options flows to provide maximum flexibility.

For detailed examples and step-by-step guidance, see the "Configuration Flow Integration" section in `CLAUDE.md`.

### Configuration Flow Step Ordering Rules

**CRITICAL: Configuration flow step ordering must follow these rules:**

1. **Openings Steps Must Be Last Configuration Steps**: The openings configuration steps (`openings_toggle`, `openings_selection`, `openings_config`) MUST always be among the last configuration steps because their content depends on previously configured features (system type, heating/cooling entities, etc.).

2. **Presets Steps Must Be Final Steps**: The presets configuration steps (`preset_selection`, `presets`) MUST always be the absolute final configuration steps because:
   - Preset configuration depends on all other system settings
   - Preset temperature ranges depend on configured sensors and system capabilities
   - Preset behavior varies based on system type and features

3. **Features Configuration Step Ordering**: When adding or modifying feature configuration steps, ensure they are ordered logically:
   - System type and basic entity configuration first
   - Core feature toggles (floor heating, fan, humidity)
   - Feature-specific configuration steps
   - Openings configuration (depends on system type and entities)
   - Preset configuration (depends on all previous steps)

**Detailed Documentation**: See `docs/config_flow/step_ordering.md` for comprehensive rules and examples.

**Implementation Requirements:**
- The `_determine_next_step()` method in `config_flow.py` MUST respect this ordering
- The `OptionsFlowHandler` in `options_flow.py` MUST follow the same ordering rules
- Any new configuration steps MUST be inserted in the correct position based on their dependencies
- Test configuration flows to ensure step ordering is correct
- Add tests to verify that openings and presets steps are always positioned correctly in the flow

**Testing Requirements:**
- Test that openings configuration steps come after core feature configuration
- Test that preset configuration steps are always the final steps
- Test the complete flow for different system types to verify step ordering
- Add integration tests that verify the dependency-based ordering

**Example Correct Flow Order:**
1. System type selection
2. Basic entity configuration (heater, cooler, sensor)
3. System-specific configuration (heat pump, dual stage, etc.)
4. Feature toggles (floor heating, fan, humidity)
5. Feature-specific configuration
6. **Openings configuration** (among last steps)
7. **Presets configuration** (final steps)

### When to Update Documentation

**Matrix Updates Required:**
- Adding new HVAC modes (HVACMode.NEW_MODE)
- Creating new device types in `hvac_device/`
- Implementing new feature managers in `managers/`
- Adding comprehensive test coverage for existing features
- Fixing or updating existing tests

**Documentation Maintenance Checklist:**
- [ ] Update README.md feature matrix for user-visible changes
- [ ] Update tests/FEATURES.md for test coverage changes
- [ ] Ensure feature names match implementation
- [ ] Verify documentation links are valid
- [ ] Update test status indicators accurately

### Feature and Test Coverage Matrix Maintenance

**Key Documentation Files:**
- `README.md` - Main feature matrix (lines 17-33) for user-facing documentation
- `tests/FEATURES.md` - Detailed test coverage matrix for development tracking

### When to Update the Feature Matrix

**README.md Feature Matrix:**
1. **Adding New Features**: When implementing a new HVAC mode, device type, or major capability
2. **Feature Changes**: When modifying existing feature behavior or capabilities
3. **Documentation Updates**: When adding new documentation sections or reorganizing docs

**tests/FEATURES.md Test Coverage Matrix:**
1. **Adding Tests**: When creating new test files or adding significant test coverage
2. **Test Status Changes**: When fixing broken tests (! → X) or identifying missing tests (? → !)
3. **New HVAC Modes**: When adding support for new HVAC modes (add new column)
4. **Feature Implementation**: When implementing previously untested features

### How to Update the Matrices

**Feature Matrix in README.md:**
- Add new features as table rows with icon, description, and documentation link
- Keep feature names consistent with actual implementation
- Ensure documentation links point to valid sections
- Use clear, user-friendly feature names

**Test Coverage Matrix in tests/FEATURES.md:**
- Use legend: `X` = Test exists and passes, `!` = Needs attention, `?` = Missing/Unknown, `N/A` = Not applicable
- Add new HVAC modes as columns when supported modes expand
- Update test status when adding or fixing tests
- Include test file summary with test counts

### Automated Checks for Matrix Maintenance

When reviewing code changes, verify:
1. New device types in `hvac_device/` are reflected in feature matrix
2. New HVAC modes in device files are added to test matrix columns
3. New test files are included in test coverage tracking
4. Feature additions include corresponding documentation updates

### Matrix Update Examples

**Adding a new HVAC mode:**
```diff
# In README.md
| **New Mode Name** | ![icon](path) | [docs](#new-mode) |

# In tests/FEATURES.md
| Feature | Fan Mode | Cool Mode | Heat Mode | Heat Cool Mode | Dry Mode | Heat Pump Mode | New Mode |
```

**Updating test status:**
```diff
# When fixing a test
- | sensor bad value | X | X | ! | ! | X | X |
+ | sensor bad value | X | X | X | ! | X | X |
```

**Adding new feature:**
```diff
# In README.md - add after existing features
| **New Feature Name** | ![icon](docs/images/icon.png) | [docs](#new-feature) |

# In tests/FEATURES.md - add as new row
| new feature test | X | X | ? | ! | N/A | X |
```

### Basic Development Setup

**Python Environment**: Requires Python 3.12+ (project targets Python 3.13)

**Development Dependencies**: Install linting tools and development dependencies:
```bash
pip install -r requirements-dev.txt
```

**Code Validation**:
```bash
# Basic syntax check
python -m py_compile custom_components/dual_smart_thermostat/climate.py

# Run all linting tools (REQUIRED before committing)
isort . --recursive --diff    # Check import sorting
black --check .               # Check code formatting
flake8 .                      # Check code style/linting
codespell                     # Check spelling

# Fix linting issues automatically
isort .                       # Fix import sorting
black .                       # Fix code formatting

# Run pre-commit hooks (includes all linting tools)
pre-commit run --all-files
```

**VSCode Setup**:
- Configured to use black formatter automatically on save
- Pytest testing enabled
- Python analysis and auto-imports configured

### Feature Development Workflow

1. **Analysis**: Determine which components need modification
   - Device types: Add new device classes if needed
   - Shared logic: Use or extend existing managers
   - Control logic: Modify appropriate controllers

2. **Implementation**: Follow existing patterns
   - Inherit from base classes where appropriate
   - Use dependency injection for managers
   - Maintain consistent error handling

3. **Configuration Flow Integration**: **CRITICAL** - Integrate configuration changes into flows
   - Determine which flow(s) need updates (config, reconfigure, options)
   - Add configuration steps and update flow navigation
   - Add data validation and error handling
   - Update translations for user-facing text
   - See "Configuration Flow Integration Requirements" section above for detailed guidance

4. **Configuration Dependencies**: Update dependency tracking for new parameters
   - Check if new parameter requires another parameter to function
   - Update `tools/focused_config_dependencies.json` with new conditional dependencies
   - Add validation rules to `tools/config_validator.py`
   - Document with examples in `docs/config/CRITICAL_CONFIG_DEPENDENCIES.md`
   - Test validation: `python tools/config_validator.py`

5. **Testing**: All new features must be covered with tests
   - Unit tests for individual components
   - Integration tests for feature workflows
   - **Config flow tests** for configuration integration
   - Edge case testing for error conditions

### Testing Requirements

**Location**: All tests are in `./tests/`

**Coverage Requirements**:
- Every new feature MUST be covered with tests
- Tests should cover both success and failure scenarios
- Test files follow naming convention: `test_<feature_name>.py`

**Test Structure Examples**:
```python
# Unit test for device functionality
def test_heater_device_turn_on():
    # Test device-specific behavior

# Integration test for feature workflow
def test_two_stage_heating_activation():
    # Test complete feature from trigger to completion

# Edge case testing
def test_sensor_unavailable_handling():
    # Test error conditions and recovery
```

**Existing Test Files**:
- `test_cooler_mode.py` - Cooling mode functionality
- `test_heater_mode.py` - Heating mode functionality
- `test_heat_pump_mode.py` - Heat pump operations
- `test_dual_mode.py` - Dual heating/cooling mode
- `test_fan_mode.py` - Fan-only operations
- `test_dry_mode.py` - Humidity/dry mode

### Code Style and Quality

**Mandatory Linting Requirements**: All code changes MUST pass the following linting tools before being committed:

1. **isort** - Import sorting and organization
   - Configuration: `setup.cfg` [isort] section
   - Requirements: Multi-line imports, trailing commas, proper grouping
   - Run locally: `isort . --recursive --diff` (check) or `isort .` (fix)

2. **black** - Code formatting
   - Configuration: Line length 88 characters, Python 3.13 compatible
   - Requirements: Consistent formatting, proper spacing, quote style
   - Run locally: `black --check .` (check) or `black .` (fix)

3. **flake8** - Code linting and style checking
   - Configuration: `setup.cfg` [flake8] section with specific ignores
   - Requirements: No unused imports, proper variable naming, line length compliance
   - Run locally: `flake8 .`

4. **codespell** - Spell checking in code and comments
   - Configuration: `setup.cfg` [codespell] section
   - Requirements: No misspellings in code, comments, or docstrings
   - Run locally: `codespell`

5. **mypy** - Type checking (optional but recommended)
   - Configuration: `setup.cfg` [mypy] section
   - Requirements: Proper type hints for new code
   - Run locally: `mypy .`

**Common Linting Fixes**:
```bash
# Fix import ordering issues
isort .

# Fix code formatting issues
black .

# Check for remaining issues
flake8 .
codespell
```

**Pre-commit Hooks**: All changes go through quality checks
- Pre-commit hooks automatically run on commit and will prevent commits that fail linting
- Run `pre-commit run --all-files` to check all files manually
- Install pre-commit: `pre-commit install`

**Type Hints**: Use type hints for all new code:
```python
from typing import Optional, Dict, List
from homeassistant.core import HomeAssistant

def setup_device(hass: HomeAssistant, config: Dict[str, Any]) -> Optional[HVACDevice]:
    """Setup HVAC device with proper typing."""
```

## Key Features and Concepts

### HVAC Modes Supported
- **Heat Only**: Single heating device
- **Cool Only**: Single cooling device
- **Heat/Cool**: Dual heating and cooling
- **Heat Pump**: Single device for both heating/cooling
- **Fan Only**: Fan operation without heating/cooling
- **Two-Stage Heating**: Primary + auxiliary/secondary heater
- **Dry Mode**: Humidity control

### Advanced Features
- **Floor Temperature Control**: Min/max floor temperature limits
- **Opening Detection**: Window/door sensors that pause HVAC
- **Preset Modes**: Pre-configured temperature/humidity settings
- **HVAC Action Reasons**: Tracking why actions occur (internal vs external)
- **Tolerance Controls**: Fine-tuned temperature control
- **Keep-Alive**: Periodic device communication
- **Sensor Stale Detection**: Handling of failed sensors

### Configuration Patterns

**Device Configuration**:
```yaml
climate:
  - platform: dual_smart_thermostat
    name: Study
    heater: switch.study_heater          # Required: heating device
    cooler: switch.study_cooler          # Optional: cooling device
    target_sensor: sensor.study_temp     # Required: temperature sensor
```

**Advanced Features**:
```yaml
    # Two-stage heating
    secondary_heater: switch.aux_heater
    secondary_heater_timeout: 00:05:00

    # Floor protection
    floor_sensor: sensor.floor_temp
    max_floor_temp: 28
    min_floor_temp: 5

    # Opening detection
    openings:
      - binary_sensor.window1
      - entity_id: binary_sensor.window2
        timeout: 00:00:30
```

## Working with Different Components

### Adding New Device Types

1. Create new device class in `hvac_device/`
2. Inherit from `GenericHVACDevice` or appropriate base class
3. Implement required methods: `turn_on()`, `turn_off()`, `is_on()`
4. Add device creation logic to `hvac_device_factory.py`
5. Add comprehensive tests

### Extending Managers

1. Identify which manager handles related functionality
2. Add new methods following existing patterns
3. Maintain backward compatibility
4. Update relevant controller to use new functionality
5. Add tests for new manager methods

### Modifying Controllers

1. Controllers orchestrate between devices and managers
2. Follow existing error handling patterns
3. Maintain separation between control logic and device operations
4. Add logging for debugging
5. Test all control flow paths

## Common Development Patterns

### Error Handling
```python
try:
    await device.turn_on()
except Exception as err:
    _LOGGER.error("Failed to turn on device: %s", err)
    # Graceful degradation
```

### State Management
```python
# Use state manager for persistence
self._state_manager.set_hvac_mode(mode)
self._state_manager.save_state()
```

### Device Interaction
```python
# Always check device availability
if self._heater_device.is_available():
    await self._heater_device.turn_on()
```

### Manager Coordination
```python
# Managers work together
if self._opening_manager.is_any_opening_open():
    if self._feature_manager.is_floor_protection_enabled():
        # Handle complex feature interactions
```

## Debugging and Logging

**Log Levels**:
- `DEBUG`: Detailed operation flow
- `INFO`: Important state changes
- `WARNING`: Recoverable issues
- `ERROR`: Failed operations

**Log Categories**:
```python
_LOGGER = logging.getLogger(__name__)

# Device operations
_LOGGER.debug("Turning on heater device")

# State changes
_LOGGER.info("HVAC mode changed to %s", new_mode)

# Error conditions
_LOGGER.error("Sensor %s is unavailable", sensor_id)
```

## Best Practices

1. **Minimal Changes**: Make the smallest possible changes to achieve goals
2. **Test First**: Write tests before implementing features when possible
3. **Follow Patterns**: Use existing architectural patterns and coding styles
4. **Document Intent**: Add docstrings for complex logic
5. **Handle Errors**: Always consider failure scenarios
6. **Backward Compatibility**: Don't break existing configurations
7. **Performance**: Consider Home Assistant's async nature

## Example Development Workflow

1. **Understand the Feature**: Read existing documentation and code
2. **Plan Components**: Identify which devices/managers/controllers need changes
3. **Write Tests**: Create failing tests for the new functionality
4. **Implement Changes**: Make minimal changes following existing patterns
5. **Integrate into Configuration Flows**: **CRITICAL** - For new or modified configuration options:
   ```bash
   # Determine which flow(s) need updates (config, reconfigure, options)
   # Add configuration steps in config_flow.py or options_flow.py
   # Update flow navigation logic (_determine_next_step())
   # Add data validation and error handling
   # Update translations/en.json with user-facing text
   # Add config flow tests in tests/config_flow/
   ```
6. **Update Configuration Dependencies**: For new parameters or features:
   ```bash
   # Check if new parameter requires another parameter to function
   # Update tools/focused_config_dependencies.json with new conditional dependencies
   # Add validation rules to tools/config_validator.py
   # Document with examples in docs/config/CRITICAL_CONFIG_DEPENDENCIES.md
   # Test validation
   python tools/config_validator.py
   ```
7. **Run Linting**: Ensure code passes all linting requirements:
   ```bash
   isort . --recursive --diff  # Check imports
   black --check .             # Check formatting
   flake8 .                    # Check style/linting
   codespell                   # Check spelling
   ```
7. **Fix Linting Issues**: Run automatic fixes if needed:
   ```bash
   isort .     # Fix imports
   black .     # Fix formatting
   ```
8. **Run Tests**: Ensure all tests pass including existing ones
9. **Code Quality**: Run pre-commit hooks and fix any issues
10. **Documentation**: Update relevant documentation if needed

**Important**: All linting tools (isort, black, flake8, codespell) MUST pass before code can be committed. The GitHub workflow will automatically check these requirements.

This modular architecture allows for safe development and testing of new features while maintaining the sophisticated thermostat logic that users depend on.

================================================
FILE: .coveragerc
================================================
[run]
branch = True

[report]
skip_empty = True
include = custom_components/*

================================================
FILE: .devcontainer.json
================================================
{
	"name": "Dual Smart THermostat Integration",
	"image": "mcr.microsoft.com/devcontainers/python:dev-3.14-bookworm",
	"postCreateCommand": "scripts/devcontainer_install_deps.sh || true && scripts/setup",
	"forwardPorts": [
		8123
	],
	"portsAttributes": {
		"8123": {
			"label": "Home Assistant",
			"onAutoForward": "notify"
		}
	},
	"customizations": {
		"vscode": {
			"extensions": [
				"ms-python.python",
				"github.vscode-pull-request-github",
				"ryanluker.vscode-coverage-gutters",
				"ms-python.vscode-pylance"
			],
			"settings": {
				"files.eol": "\n",
				"editor.tabSize": 4,
				"python.pythonPath": "/usr/bin/python3",
				"python.analysis.autoSearchPaths": true,
				"python.analysis.indexing": true,
				"python.analysis.autoImportCompletions": true,
				"python.linting.enabled": true,
				"python.formatting.provider": "black",
				"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
				"editor.formatOnPaste": false,
				"editor.formatOnSave": true,
				"editor.formatOnType": true,
				"files.trimTrailingWhitespace": true
			}
		}
	},
	"remoteUser": "vscode",
	"features": {
		"ghcr.io/devcontainers/features/rust:1": {}
	}
}

================================================
FILE: .dockerignore
================================================
# Git files
.git
.gitignore
.gitattributes

# Python cache and artifacts
__pycache__
*.py[cod]
*$py.class
*.so
.Python
*.egg-info
dist
build
eggs
.eggs
*.egg

# Virtual environments
.venv
venv
ENV
env

# Testing and coverage
.pytest_cache
.tox
.coverage
.coverage.*
htmlcov
coverage.xml
*.cover
.hypothesis
.cache

# Type checking
.mypy_cache
.dmypy.json
dmypy.json

# Linting and formatting
.ruff_cache

# IDE and editor files
.vscode
.idea
*.swp
*.swo
*~
.DS_Store

# DevContainer (not needed in Docker builds)
.devcontainer
.devcontainer.json

# CI/CD
.github

# Documentation (not needed at runtime)
docs
*.md
LICENSE

# Config and data directories (mounted as volumes)
config
.storage

# Test and development files
tests
examples
tools
specs
.specify

# Pre-commit
.pre-commit-config.yaml

# Docker itself
Dockerfile*
docker-compose*.yml
.dockerignore

# Logs
*.log

# GitHub Actions
action/


================================================
FILE: .github/DEPENDABOT_AUTO_MERGE.md
================================================
# Dependabot Auto-Merge Configuration

This repository has been configured with automated Dependabot dependency updates and auto-merge functionality.

## Overview

The auto-merge system automatically merges Dependabot pull requests that meet specific safety criteria, reducing manual maintenance overhead while maintaining code quality and security.

## Configuration Files

### 1. Dependabot Configuration (`.github/dependabot.yml`)
- **GitHub Actions**: Weekly updates with proper commit message formatting
- **Python Dependencies**: Weekly updates with safety exclusions
- **Excluded Packages**: Home Assistant, major version updates for critical tools
- **Commit Messages**: Standardized with "chore" prefix and scope

### 2. Auto-Merge Workflow (`.github/workflows/dependabot-auto-merge.yml`)
- **Trigger**: Only on Dependabot PRs
- **Safety Checks**: Version analysis, critical package detection
- **Quality Gates**: Linting, testing, and code quality checks
- **Merge Strategy**: Squash merge with standardized commit messages

### 3. Enhanced Security Checks (`.github/workflows/security-check.yml`)
- **Security Scanning**: Safety, Bandit, Semgrep
- **Dependency Auditing**: pip-audit for vulnerability detection
- **Code Quality**: Radon complexity analysis, maintainability metrics
- **Schedule**: Weekly automated security scans

## Auto-Merge Criteria

### ✅ Safe to Auto-Merge
- **Patch/Minor Updates**: Only version updates that don't change major version
- **Non-Critical Packages**: Excludes core development tools and Home Assistant
- **Passing Checks**: All linting, testing, and quality checks must pass
- **Standard Dependencies**: Regular Python packages and GitHub Actions

### ❌ Manual Review Required
- **Major Version Updates**: Any dependency with breaking changes
- **Critical Packages**: pytest, black, isort, sonarcloud, homeassistant
- **Failing Checks**: Any linting, testing, or quality check failures
- **Security Issues**: Any detected vulnerabilities or security concerns

## Workflow Integration

### Build Workflows Enhanced
1. **Linting Workflow**: Added Flake8 and MyPy checks
2. **Testing Workflow**: Enhanced with coverage reporting and artifacts
3. **Security Workflow**: Comprehensive security and quality scanning
4. **E2E Workflow**: Maintained existing end-to-end testing

### Quality Gates
- **Linting**: isort, black, flake8, mypy
- **Testing**: pytest with coverage reporting
- **Security**: Safety, Bandit, Semgrep, pip-audit
- **Quality**: Radon complexity, Xenon maintainability

## Monitoring and Notifications

### PR Comments
The auto-merge workflow automatically comments on Dependabot PRs with:
- ✅ **Success**: Auto-merge approved and completed
- ❌ **Skipped**: Manual review required (with reasons)
- ❌ **Failed**: Checks did not pass (with details)

### Artifacts
- **Coverage Reports**: HTML and XML coverage reports
- **Security Reports**: JSON reports from all security tools
- **Quality Reports**: Complexity and maintainability metrics

## Manual Override

### Disabling Auto-Merge
To disable auto-merge for a specific PR:
1. Add the label `no-auto-merge` to the PR
2. Comment with `@dependabot ignore this dependency` for permanent exclusion

### Emergency Stop
To temporarily disable all auto-merge:
1. Add the `dependabot-auto-merge-disabled` label to the repository
2. Or modify the workflow file to add a condition

## Security Considerations

### Protected Updates
- **Home Assistant**: Never auto-updated (matches HACS requirements)
- **Testing Tools**: Major version updates require manual review
- **Security Tools**: All security-related updates require approval

### Vulnerability Response
- **Critical Vulnerabilities**: Auto-merge may be temporarily disabled
- **Security Alerts**: All security scans run on every PR
- **Audit Reports**: Weekly dependency vulnerability scanning

## Maintenance

### Regular Tasks
- **Weekly Security Scans**: Automated vulnerability detection
- **Quality Reports**: Code complexity and maintainability tracking
- **Dependency Updates**: Automated with safety checks

### Manual Reviews
- **Major Updates**: All major version changes require manual approval
- **Critical Dependencies**: Core development tools need human oversight
- **Security Issues**: Any detected vulnerabilities require investigation

## Troubleshooting

### Common Issues
1. **Auto-merge Skipped**: Check PR title format and package exclusions
2. **Checks Failing**: Review linting, testing, or security scan results
3. **Merge Conflicts**: Resolve conflicts and re-run checks

### Debug Information
- **Workflow Logs**: Check GitHub Actions logs for detailed information
- **PR Comments**: Auto-generated status comments explain decisions
- **Artifacts**: Download reports for detailed analysis

## Best Practices

### For Maintainers
- **Review Weekly**: Check security scan results and quality reports
- **Monitor Alerts**: Respond to security alerts and vulnerability reports
- **Update Exclusions**: Modify dependabot.yml for new critical dependencies

### For Contributors
- **Dependency Updates**: Most updates are automated, focus on feature development
- **Security Issues**: Report any security concerns immediately
- **Quality Gates**: Ensure code passes all automated checks

## Configuration Customization

### Adding Exclusions
Edit `.github/dependabot.yml` to add new packages to ignore:
```yaml
ignore:
  - dependency-name: "package-name"
    update-types: ["version-update:semver-major"]
```

### Modifying Safety Checks
Edit `.github/workflows/dependabot-auto-merge.yml` to adjust safety criteria:
```yaml
DANGEROUS_PACKAGES=("package1" "package2")
```

### Updating Quality Gates
Modify workflow files to add or remove quality checks as needed.

---

*This configuration provides a balance between automation and safety, ensuring dependencies stay updated while maintaining code quality and security.*

================================================
FILE: .github/FUNDING.yml
================================================
custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=S6NC9BYVDDJMA&source=url
custom: https://www.buymeacoffee.com/swingerman

================================================
FILE: .github/RELEASE_TEMPLATE.md
================================================
## What's Changed

<!-- Describe the changes in this release -->

## Notable Features

<!-- Highlight any new features or important changes -->

## Bug Fixes

<!-- List any bug fixes included in this release -->

## Breaking Changes

<!-- List any breaking changes that users need to be aware of -->

## Installation & Upgrade

This release is available through [HACS](https://hacs.xyz/). If you're upgrading from a previous version, please review the breaking changes section above.

[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=swingerman&repository=ha-dual-smart-thermostat&category=Integration)

---

## Support the Project

If this integration has been helpful to you, consider supporting its continued development. Your support helps maintain and improve this project for the entire Home Assistant community.

[![Donate](https://img.shields.io/badge/Donate-PayPal-yellowgreen?style=for-the-badge&logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=S6NC9BYVDDJMA&source=url)
[![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/swingerman)

Thank you for using the Dual Smart Thermostat integration! 🏠🌡️

================================================
FILE: .github/SECURITY_REMEDIATION.md
================================================
# Security Vulnerability Remediation Guide

## 🚨 Current Security Issues

The security scan has identified **3 critical vulnerabilities** in your dependencies that need immediate attention:

### 1. **urllib3** - CVE-2025-50181
- **Current Version**: 1.26.20
- **Vulnerability**: Possible to disable redirects for all requests
- **Risk Level**: Medium
- **Fix**: Upgrade to urllib3 >= 2.5.0

### 2. **requests** - CVE-2024-47081  
- **Current Version**: 2.32.3
- **Vulnerability**: URL parsing issue may leak .netrc credentials to third parties
- **Risk Level**: High
- **Fix**: Upgrade to requests >= 2.32.4

### 3. **aiohttp** - CVE-2025-53643
- **Current Version**: 3.11.13
- **Vulnerability**: Python parser vulnerability
- **Risk Level**: Medium
- **Fix**: Upgrade to aiohttp >= 3.12.14

## ✅ Immediate Actions Taken

### 1. **Updated Requirements**
Added security fixes to `requirements-dev.txt`:
```txt
# Fix security vulnerabilities
urllib3>=2.5.0
requests>=2.32.4
aiohttp>=3.12.14
```

### 2. **Enhanced Security Workflows**
- **Updated Safety command**: Changed from deprecated `check` to modern `scan` command
- **Enhanced auto-merge**: Added security scan as a blocking condition
- **Improved reporting**: Better vulnerability detection and reporting

### 3. **Auto-Merge Protection**
- **Security gate**: Auto-merge now blocks PRs with security vulnerabilities
- **Clear feedback**: Detailed comments explain why PRs are blocked
- **Manual review**: Security issues require human intervention

## 🔧 Next Steps

### 1. **Install Updated Dependencies**
```bash
pip install -r requirements-dev.txt
```

### 2. **Verify Security Fixes**
```bash
safety scan
```

### 3. **Test Application**
Ensure the updated dependencies don't break functionality:
```bash
pytest
python -m manage/update_requirements.py
```

### 4. **Monitor Future Updates**
- Dependabot will automatically create PRs for future security updates
- Auto-merge will only proceed if security scans pass
- Manual review required for major version updates

## 🛡️ Security Best Practices

### **Dependency Management**
- **Regular updates**: Weekly automated dependency updates
- **Security scanning**: Comprehensive vulnerability detection
- **Version pinning**: Specific version requirements for critical dependencies

### **Automated Protection**
- **Pre-merge checks**: Security scans before any auto-merge
- **Vulnerability blocking**: PRs with security issues are automatically blocked
- **Clear reporting**: Detailed feedback on security status

### **Manual Review Process**
- **Major updates**: All major version changes require manual approval
- **Critical packages**: Core development tools need human oversight
- **Security alerts**: Immediate notification of new vulnerabilities

## 📊 Monitoring and Alerts

### **Weekly Security Scans**
- **Automated scanning**: Every Monday at 2 AM
- **Comprehensive reports**: JSON artifacts with detailed findings
- **Trend analysis**: Track security posture over time

### **Real-time Protection**
- **PR blocking**: Security vulnerabilities prevent auto-merge
- **Immediate feedback**: Clear explanations for blocked PRs
- **Escalation path**: Security issues require manual resolution

## 🔍 Vulnerability Details

### **urllib3 CVE-2025-50181**
- **Impact**: Potential for request manipulation
- **Exploitability**: Low (requires specific configuration)
- **Mitigation**: Upgrade to 2.5.0+ immediately

### **requests CVE-2024-47081**
- **Impact**: Credential leakage to third parties
- **Exploitability**: Medium (network-based attack)
- **Mitigation**: Upgrade to 2.32.4+ immediately

### **aiohttp CVE-2025-53643**
- **Impact**: Parser vulnerability
- **Exploitability**: Medium (requires malicious input)
- **Mitigation**: Upgrade to 3.12.14+ immediately

## 🚀 Implementation Status

### ✅ **Completed**
- [x] Identified all security vulnerabilities
- [x] Updated dependency requirements
- [x] Enhanced security workflows
- [x] Added auto-merge protection
- [x] Improved reporting and feedback

### 🔄 **In Progress**
- [ ] Install updated dependencies
- [ ] Verify security fixes
- [ ] Test application functionality
- [ ] Monitor for new vulnerabilities

### 📋 **Next Actions**
1. **Install updates**: `pip install -r requirements-dev.txt`
2. **Verify fixes**: `safety scan`
3. **Test functionality**: Run full test suite
4. **Monitor**: Watch for future security updates

## 🆘 Emergency Response

### **If New Vulnerabilities Are Found**
1. **Immediate**: Security scan will block auto-merge
2. **Notification**: Clear feedback in PR comments
3. **Action**: Manual review and dependency update required
4. **Verification**: Re-run security scans after fixes

### **Contact Information**
- **Security Issues**: Create GitHub issue with `security` label
- **Critical Vulnerabilities**: Use GitHub security advisories
- **Emergency**: Disable auto-merge temporarily if needed

---

*This remediation guide ensures your repository maintains the highest security standards while providing clear guidance for addressing vulnerabilities.*

================================================
FILE: .github/dependabot.yml
================================================
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "dependabot[bot]"
    assignees:
      - "dependabot[bot]"
    commit-message:
      prefix: "chore"
      prefix-development: "chore"
      include: "scope"

  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    reviewers:
      - "dependabot[bot]"
    assignees:
      - "dependabot[bot]"
    commit-message:
      prefix: "chore"
      prefix-development: "chore"
      include: "scope"
    ignore:
      # Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json
      - dependency-name: "homeassistant"
      # Ignore major version updates for critical dependencies
      - dependency-name: "pytest"
        update-types: ["version-update:semver-major"]
      - dependency-name: "black"
        update-types: ["version-update:semver-major"]
      - dependency-name: "isort"
        update-types: ["version-update:semver-major"]

================================================
FILE: .github/prompts/plan.prompt.md
================================================
---
description: Execute the implementation planning workflow using the plan template to generate design artifacts.
---

Given the implementation details provided as an argument, do this:

1. Run `.specify/scripts/bash/setup-plan.sh --json` from the repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. All future file paths must be absolute.
2. Read and analyze the feature specification to understand:
   - The feature requirements and user stories
   - Functional and non-functional requirements
   - Success criteria and acceptance criteria
   - Any technical constraints or dependencies mentioned

3. Read the constitution at `.specify/memory/constitution.md` to understand constitutional requirements.

4. Execute the implementation plan template:
   - Load `.specify/templates/plan-template.md` (already copied to IMPL_PLAN path)
   - Set Input path to FEATURE_SPEC
   - Run the Execution Flow (main) function steps 1-10
   - The template is self-contained and executable
   - Follow error handling and gate checks as specified
   - Let the template guide artifact generation in $SPECS_DIR:
     * Phase 0 generates research.md
     * Phase 1 generates data-model.md, contracts/, quickstart.md
     * Phase 2 generates tasks.md
   - Incorporate user-provided details from arguments into Technical Context: $ARGUMENTS
   - Update Progress Tracking as you complete each phase

5. Verify execution completed:
   - Check Progress Tracking shows all phases complete
   - Ensure all required artifacts were generated
   - Confirm no ERROR states in execution

6. Report results with branch name, file paths, and generated artifacts.

Use absolute paths with the repository root for all file operations to avoid path issues.


================================================
FILE: .github/prompts/specify.prompt.md
================================================
---
description: Create or update the feature specification from a natural language feature description.
---

Given the feature description provided as an argument, do this:

1. Run the script `.specify/scripts/bash/create-new-feature.sh --json "$ARGUMENTS"` from repo root and parse its JSON output for BRANCH_NAME and SPEC_FILE. All file paths must be absolute.
2. Load `.specify/templates/spec-template.md` to understand required sections.
3. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
4. Report completion with branch name, spec file path, and readiness for the next phase.

Note: The script creates and checks out the new branch and initializes the spec file before writing.


================================================
FILE: .github/prompts/tasks.prompt.md
================================================
---
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
---

Given the context provided as an argument, do this:

1. Run `.specify/scripts/bash/check-task-prerequisites.sh --json` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute.
2. Load and analyze available design documents:
   - Always read plan.md for tech stack and libraries
   - IF EXISTS: Read data-model.md for entities
   - IF EXISTS: Read contracts/ for API endpoints
   - IF EXISTS: Read research.md for technical decisions
   - IF EXISTS: Read quickstart.md for test scenarios

   Note: Not all projects have all documents. For example:
   - CLI tools might not have contracts/
   - Simple libraries might not need data-model.md
   - Generate tasks based on what's available

3. Generate tasks following the template:
   - Use `.specify/templates/tasks-template.md` as the base
   - Replace example tasks with actual tasks based on:
     * **Setup tasks**: Project init, dependencies, linting
     * **Test tasks [P]**: One per contract, one per integration scenario
     * **Core tasks**: One per entity, service, CLI command, endpoint
     * **Integration tasks**: DB connections, middleware, logging
     * **Polish tasks [P]**: Unit tests, performance, docs

4. Task generation rules:
   - Each contract file → contract test task marked [P]
   - Each entity in data-model → model creation task marked [P]
   - Each endpoint → implementation task (not parallel if shared files)
   - Each user story → integration test marked [P]
   - Different files = can be parallel [P]
   - Same file = sequential (no [P])

5. Order tasks by dependencies:
   - Setup before everything
   - Tests before implementation (TDD)
   - Models before services
   - Services before endpoints
   - Core before integration
   - Everything before polish

6. Include parallel execution examples:
   - Group [P] tasks that can run together
   - Show actual Task agent commands

7. Create FEATURE_DIR/tasks.md with:
   - Correct feature name from implementation plan
   - Numbered tasks (T001, T002, etc.)
   - Clear file paths for each task
   - Dependency notes
   - Parallel execution guidance

Context for task generation: $ARGUMENTS

The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    labels:
      - ignore-for-release
    authors:
      - dependabot
  categories:
    - title: Breaking Changes 🛠
      labels:
        - Semver-Major
        - breaking-change
    - title: Exciting New Features 🎉
      labels:
        - Semver-Minor
        - enhancement
    - title: Other Changes
      labels:
        - "*"
  footer: |
    ## Support the Project

    If this integration has been helpful to you, consider supporting its continued development. Your support helps maintain and improve this project for the entire Home Assistant community.

    [![Donate](https://img.shields.io/badge/Donate-PayPal-yellowgreen?style=for-the-badge&logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=S6NC9BYVDDJMA&source=url)
    [![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/swingerman)

    Thank you for using the Dual Smart Thermostat integration! 🏠🌡️


================================================
FILE: .github/scripts/update_hacs_manifest.py
================================================
"""Update the manifest file."""

import json
import os
import sys


def update_manifest():
    """Update the manifest file."""
    version = "0.0.0"
    manifest_path = False
    dorequirements = False

    for index, value in enumerate(sys.argv):
        if value in ["--version", "-V"]:
            version = str(sys.argv[index + 1]).replace("v", "")
        if value in ["--path", "-P"]:
            manifest_path = str(sys.argv[index + 1])[1:-1]
        if value in ["--requirements", "-R"]:
            dorequirements = True

    if not manifest_path:
        sys.exit("Missing path to manifest file")

    with open(
        f"{os.getcwd()}/{manifest_path}/manifest.json",
        encoding="UTF-8",
    ) as manifestfile:
        manifest = json.load(manifestfile)

    manifest["version"] = version

    if dorequirements:
        requirements = []
        with open(
            f"{os.getcwd()}/requirements.txt",
            encoding="UTF-8",
        ) as file:
            for line in file:
                requirements.append(line.rstrip())

        new_requirements = []
        for requirement in requirements:
            req = requirement.split("==")[0].lower()
            new_requirements = [
                requirement
                for x in manifest["requirements"]
                if x.lower().startswith(req)
            ]
            new_requirements += [
                x for x in manifest["requirements"] if not x.lower().startswith(req)
            ]
            manifest["requirements"] = new_requirements

    with open(
        f"{os.getcwd()}/{manifest_path}/manifest.json",
        "w",
        encoding="UTF-8",
    ) as manifestfile:
        manifestfile.write(
            json.dumps(
                {
                    "domain": manifest["domain"],
                    "name": manifest["name"],
                    **{
                        k: v
                        for k, v in sorted(manifest.items())
                        if k not in ("domain", "name")
                    },
                },
                indent=4,
            )
        )


update_manifest()


================================================
FILE: .github/workflows/claude.yml
================================================
name: Claude Code

on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
  issues:
    types: [opened, assigned]

permissions:
  contents: write
  pull-requests: write
  issues: write
  actions: read

jobs:
  claude:
    if: |
      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
      (github.event_name == 'issues' && contains(github.event.issue.body, '@claude'))
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          additional_permissions: |
            actions: read
          claude_args: "--max-turns 50"
          settings: |
            {
              "permissions": {
                "allow": [
                  "Bash",
                  "Read",
                  "Write",
                  "Edit",
                  "Glob",
                  "Grep",
                  "WebFetch",
                  "WebSearch",
                  "NotebookEdit"
                ]
              }
            }


================================================
FILE: .github/workflows/dependabot-auto-merge.yml
================================================
name: Dependabot Auto-Merge

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  auto-merge:
    # Only run on Dependabot PRs
    if: github.actor == 'dependabot[bot]'
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          # Fetch all history for better analysis
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.14"
          cache: "pip"

      - name: Install dependencies
        run: |
          pip install -r requirements-dev.txt

      - name: Check if PR is safe to auto-merge
        id: safety-check
        run: |
          echo "Checking PR safety for auto-merge..."
          
          # Get PR details
          PR_TITLE="${{ github.event.pull_request.title }}"
          PR_BODY="${{ github.event.pull_request.body }}"
          PR_HEAD_REF="${{ github.event.pull_request.head.ref }}"
          
          echo "PR Title: $PR_TITLE"
          echo "PR Head Ref: $PR_HEAD_REF"
          
          # Check if it's a patch or minor version update
          if echo "$PR_TITLE" | grep -E "(Bump|Update).*from.*to.*" > /dev/null; then
            echo "✅ PR appears to be a dependency update"
            
            # Extract version numbers to check if it's a major version update
            if echo "$PR_TITLE" | grep -E "from [0-9]+\.[0-9]+\.[0-9]+ to [0-9]+\.[0-9]+\.[0-9]+" > /dev/null; then
              FROM_VERSION=$(echo "$PR_TITLE" | grep -oE "from [0-9]+\.[0-9]+\.[0-9]+" | cut -d' ' -f2)
              TO_VERSION=$(echo "$PR_TITLE" | grep -oE "to [0-9]+\.[0-9]+\.[0-9]+" | cut -d' ' -f2)
              
              FROM_MAJOR=$(echo "$FROM_VERSION" | cut -d'.' -f1)
              TO_MAJOR=$(echo "$TO_VERSION" | cut -d'.' -f1)
              
              if [ "$FROM_MAJOR" != "$TO_MAJOR" ]; then
                echo "❌ Major version update detected ($FROM_VERSION -> $TO_VERSION). Skipping auto-merge."
                echo "safe_to_merge=false" >> $GITHUB_OUTPUT
                exit 0
              fi
            fi
            
            # Check for specific packages that should not be auto-merged
            DANGEROUS_PACKAGES=("homeassistant" "pytest" "black" "isort" "sonarcloud")
            for package in "${DANGEROUS_PACKAGES[@]}"; do
              if echo "$PR_TITLE" | grep -i "$package" > /dev/null; then
                echo "❌ Update to critical package '$package' detected. Skipping auto-merge."
                echo "safe_to_merge=false" >> $GITHUB_OUTPUT
                exit 0
              fi
            done
            
            echo "✅ PR appears safe for auto-merge"
            echo "safe_to_merge=true" >> $GITHUB_OUTPUT
          else
            echo "❌ PR title doesn't match expected pattern for dependency updates"
            echo "safe_to_merge=false" >> $GITHUB_OUTPUT
          fi

      - name: Run linting checks
        if: steps.safety-check.outputs.safe_to_merge == 'true'
        run: |
          echo "Running linting checks..."
          isort . --check-only --diff
          black --check .

      - name: Run security scan
        if: steps.safety-check.outputs.safe_to_merge == 'true'
        id: security-scan
        run: |
          echo "Running security scan..."
          pip install safety
          # Safety v3 requires auth and Home Assistant locks dependencies
          # Making this informational only to not block auto-merge
          safety scan || echo "⚠️ Safety scan skipped or found issues in locked dependencies"
          echo "security_scan_passed=true" >> $GITHUB_OUTPUT
        continue-on-error: true

      - name: Run tests
        if: steps.safety-check.outputs.safe_to_merge == 'true'
        run: |
          echo "Running tests..."
          pytest --cov-report xml:coverage.xml || echo "⚠️ Tests failed but not blocking auto-merge"
        continue-on-error: true

      - name: Auto-merge PR
        if: steps.safety-check.outputs.safe_to_merge == 'true' && steps.security-scan.outputs.security_scan_passed == 'true' && success()
        uses: fastify/github-action-merge-dependabot@v3
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          target: "squash"
          merge-method: "squash"
          merge-message: "chore: ${{ github.event.pull_request.title }}"
          delete-branch: true

      - name: Comment on PR
        if: always()
        uses: actions/github-script@v9
        with:
          script: |
            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
            });
            
            const botComment = comments.find(comment => 
              comment.user.type === 'Bot' && 
              comment.body.includes('Dependabot Auto-Merge')
            );
            
            if (botComment) {
              console.log('Bot comment already exists, skipping...');
              return;
            }
            
            const safeToMerge = '${{ steps.safety-check.outputs.safe_to_merge }}' === 'true';
            const securityScanPassed = '${{ steps.security-scan.outputs.security_scan_passed }}' === 'true';
            const workflowSuccess = '${{ job.status }}' === 'success';
            
            let message = '## 🤖 Dependabot Auto-Merge Status\n\n';
            
            if (safeToMerge && securityScanPassed && workflowSuccess) {
              message += '✅ **Auto-merge approved!** This PR has been automatically merged.\n\n';
              message += '- ✅ Safety checks passed\n';
              message += '- ✅ Security scan passed\n';
              message += '- ✅ Linting checks passed\n';
              message += '- ✅ Tests passed\n';
              message += '- ✅ PR merged successfully\n';
            } else if (!safeToMerge) {
              message += '❌ **Auto-merge skipped** - This update requires manual review.\n\n';
              message += '**Reasons:**\n';
              if ('${{ steps.safety-check.outputs.safe_to_merge }}' === 'false') {
                message += '- ⚠️ Major version update or critical package detected\n';
                message += '- ⚠️ Manual review required for safety\n';
              }
            } else if (!securityScanPassed) {
              message += '❌ **Auto-merge blocked** - Security vulnerabilities detected.\n\n';
              message += '**Security Issues:**\n';
              message += '- ❌ Security scan failed - vulnerabilities found\n';
              message += '- 🔒 Manual review required to address security issues\n';
            } else {
              message += '❌ **Auto-merge failed** - Checks did not pass.\n\n';
              message += '**Issues:**\n';
              if ('${{ steps.safety-check.outputs.safe_to_merge }}' === 'false') {
                message += '- ❌ Safety checks failed\n';
              }
              if ('${{ steps.security-scan.outputs.security_scan_passed }}' === 'false') {
                message += '- ❌ Security scan failed\n';
              }
              if ('${{ job.status }}' !== 'success') {
                message += '- ❌ Linting or tests failed\n';
              }
            }
            
            message += '\n---\n*This is an automated message from the Dependabot Auto-Merge workflow.*';
            
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: message
            });

================================================
FILE: .github/workflows/hacs-validate.yaml
================================================
name: Validate with HACS

on:
  push:
    branches:
      - master

  pull_request:
    branches: "*"

  schedule:
    - cron: "0 0 * * *"

jobs:
  validate_hacs:
    name: Validate With HACS
    runs-on: "ubuntu-latest"
    steps:
      - uses: actions/checkout@v6
      - name: HACS validation
        uses: hacs/action@main
        with:
          category: "integration"

  validate_hassfest:
    name: Validate with Hassfest
    runs-on: "ubuntu-latest"
    steps:
      - uses: actions/checkout@v6
      - uses: home-assistant/actions/hassfest@master


================================================
FILE: .github/workflows/linting.yaml
================================================
name: Linting

on:
  push:
    branches:
      - master

  pull_request:
    branches: "*"

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.14"
          cache: "pip"

      - name: Install dependencies
        run: pip install -r requirements-dev.txt
      - name: isort
        run: isort . --recursive --diff
      - name: Black
        run: black --check .
      - name: Flake8
        run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
      - name: MyPy
        run: mypy . --ignore-missing-imports || true


================================================
FILE: .github/workflows/quality-check.yaml
================================================
name: Quality Check

on:
  push:
    branches:
      - master

  pull_request:
    branches: "*"

jobs:
  sonarcloud:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          # Disabling shallow clone is recommended for improving relevancy of reporting
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.14'

      - name: Install dependencies
        run: pip install -r requirements-dev.txt

      - name: Run tests with coverage
        run: |
          pytest --cov-report xml:coverage.xml --cov=custom_components

      - name: Verify coverage file exists
        run: |
          if [ -f coverage.xml ]; then
            echo "✓ Coverage file generated successfully"
            ls -lh coverage.xml
          else
            echo "✗ Coverage file not found!"
            exit 1
          fi

      - name: SonarCloud Scan
        uses: sonarsource/sonarcloud-github-action@master
        with:
          args: >
            -Dsonar.python.coverage.reportPaths=coverage.xml
            -Dsonar.tests=tests/
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}


================================================
FILE: .github/workflows/security-check.yml
================================================
name: Security and Quality Check

on:
  push:
    branches:
      - master
  pull_request:
    branches: "*"
  schedule:
    - cron: "0 2 * * 1"  # Weekly on Monday at 2 AM

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.14"
          cache: "pip"

      - name: Install dependencies
        run: |
          pip install -r requirements-dev.txt
          pip install safety bandit semgrep

      - name: Run Safety scan
        run: |
          echo "Running Safety scan for known security vulnerabilities..."
          # Safety v3 requires authentication, making it informational only
          safety scan --json --output safety-report.json || echo "⚠️ Safety scan skipped (requires auth)"
          safety scan || echo "⚠️ Safety scan completed with findings or auth required"
        continue-on-error: true

      - name: Run Bandit security linter
        run: |
          echo "Running Bandit security analysis..."
          # Exclude tests directory from security scan
          bandit -r . -x ./tests -f json -o bandit-report.json || true
          bandit -r . -x ./tests -f txt || echo "⚠️ Bandit found security issues (informational only)"
        continue-on-error: true

      - name: Run Semgrep security scan
        run: |
          echo "Running Semgrep security scan..."
          semgrep --config=auto --json --output=semgrep-report.json . || true
          semgrep --config=auto .

      - name: Check for secrets
        run: |
          echo "Checking for potential secrets..."
          # Check for common secret patterns
          if grep -r -E "(password|secret|key|token|api_key)" --include="*.py" --include="*.yaml" --include="*.yml" . | grep -v -E "(test_|example_|demo_)" | grep -v "__pycache__" | grep -v ".git"; then
            echo "⚠️ Potential secrets found in code. Please review."
          else
            echo "✅ No obvious secrets found in code."
          fi

      - name: Upload security reports
        uses: actions/upload-artifact@v7
        with:
          name: security-reports
          path: |
            safety-report.json
            bandit-report.json
            semgrep-report.json
          retention-days: 30
          if-no-files-found: ignore

  dependency-audit:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.14"
          cache: "pip"

      - name: Install dependencies
        run: |
          pip install -r requirements-dev.txt
          pip install pip-audit

      - name: Run pip-audit
        run: |
          echo "Running pip-audit for dependency vulnerabilities..."
          # Ignore known vulnerabilities in Home Assistant's pinned transitive dependencies.
          # These cannot be upgraded independently — HA controls their versions.
          # Will be resolved when migrating to HA 2026.x + Python 3.14.
          # aiohttp 3.11.x CVEs (fixed in 3.13.4):
          # pytest 9.0.0 CVE GHSA-6w46-j5rx-g56g (fixed in 9.0.3):
          # pinned to ==9.0.0 by pytest-homeassistant-custom-component.
          # pip 26.0.1 CVE GHSA-58qw-9mgm-455v (concatenated tar/ZIP handling):
          # pip 26.0.1 CVE GHSA-jp4c-xjxw-mgf9 (self-update timing, fixed in 26.1):
          # ships with the GitHub Actions runner; cannot be controlled by us.
          HA_IGNORES="\
            --ignore-vuln GHSA-jp4c-xjxw-mgf9 \
            --ignore-vuln GHSA-9548-qrrj-x5pj \
            --ignore-vuln GHSA-6mq8-rvhq-8wgg \
            --ignore-vuln GHSA-69f9-5gxw-wvc2 \
            --ignore-vuln GHSA-6jhg-hg63-jvvf \
            --ignore-vuln GHSA-g84x-mcqj-x9qq \
            --ignore-vuln GHSA-fh55-r93g-j68g \
            --ignore-vuln GHSA-54jq-c3m8-4m76 \
            --ignore-vuln GHSA-jj3x-wxrx-4x23 \
            --ignore-vuln GHSA-mqqc-3gqh-h2x8 \
            --ignore-vuln GHSA-p998-jp59-783m \
            --ignore-vuln GHSA-hcc4-c3v8-rx92 \
            --ignore-vuln GHSA-m5qp-6w8w-w647 \
            --ignore-vuln GHSA-3wq7-rqq7-wx6j \
            --ignore-vuln GHSA-mwh4-6h8g-pg8w \
            --ignore-vuln GHSA-966j-vmvw-g2g9 \
            --ignore-vuln GHSA-63hf-3vf5-4wqf \
            --ignore-vuln GHSA-c427-h43c-vf67 \
            --ignore-vuln GHSA-w2fm-2cpv-w7v5 \
            --ignore-vuln GHSA-2vrm-gr82-f7m5 \
            --ignore-vuln GHSA-w476-p2h3-79g9 \
            --ignore-vuln GHSA-gc5v-m9x4-r6x2 \
            --ignore-vuln GHSA-pqhf-p39g-3x64 \
            --ignore-vuln GHSA-cfh3-3jmp-rvhc \
            --ignore-vuln GHSA-gm62-xv2j-4w53 \
            --ignore-vuln GHSA-9ggr-2464-2j32 \
            --ignore-vuln GHSA-9hjg-9r4m-mvj7 \
            --ignore-vuln GHSA-2xpw-w6gg-jr37 \
            --ignore-vuln GHSA-38jv-5279-wg99 \
            --ignore-vuln GHSA-752w-5fwx-jx9f \
            --ignore-vuln GHSA-8qf3-x8v5-2pj8 \
            --ignore-vuln GHSA-pq67-6m6q-mj2v \
            --ignore-vuln GHSA-r6ph-v2qm-q3c2 \
            --ignore-vuln GHSA-m959-cc7f-wv43 \
            --ignore-vuln GHSA-mq77-rv97-285m \
            --ignore-vuln GHSA-pp3g-xmm4-5cw9 \
            --ignore-vuln GHSA-r584-6283-p7xc \
            --ignore-vuln GHSA-46j8-vpx8-6p72 \
            --ignore-vuln GHSA-hx9q-6w63-j58v \
            --ignore-vuln GHSA-vp96-hxj8-p424 \
            --ignore-vuln GHSA-5pwr-322w-8jr4 \
            --ignore-vuln GHSA-6w46-j5rx-g56g \
            --ignore-vuln GHSA-58qw-9mgm-455v"
          pip-audit --desc --format=json --output=pip-audit-report.json $HA_IGNORES || true
          pip-audit --desc $HA_IGNORES
        continue-on-error: false

      - name: Upload audit reports
        uses: actions/upload-artifact@v7
        with:
          name: audit-reports
          path: pip-audit-report.json
          retention-days: 30
          if-no-files-found: ignore

  code-quality:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: "3.14"
          cache: "pip"

      - name: Install dependencies
        run: |
          pip install -r requirements-dev.txt
          pip install radon xenon

      - name: Run code complexity analysis
        run: |
          echo "Running code complexity analysis with Radon..."
          radon cc . --json --output radon-complexity.json || true
          radon cc . --show-complexity
          
          echo "Running maintainability index..."
          radon mi . --json --output radon-maintainability.json || true
          radon mi . --show

      - name: Run code quality metrics
        run: |
          echo "Running Xenon complexity analysis..."
          xenon . --max-absolute B --max-modules A --max-average A || true

      - name: Check for TODO/FIXME comments
        run: |
          echo "Checking for TODO/FIXME comments..."
          if grep -r -i "todo\|fixme" --include="*.py" . | grep -v ".git" | grep -v "__pycache__"; then
            echo "⚠️ Found TODO/FIXME comments that should be addressed:"
            grep -r -i "todo\|fixme" --include="*.py" . | grep -v ".git" | grep -v "__pycache__"
          else
            echo "✅ No TODO/FIXME comments found."
          fi

      - name: Upload quality reports
        uses: actions/upload-artifact@v7
        with:
          name: quality-reports
          path: |
            radon-complexity.json
            radon-maintainability.json
          retention-days: 30
          if-no-files-found: ignore

================================================
FILE: .github/workflows/tests.yaml
================================================
name: Python tests

on:
  push:
    branches:
      - master

  pull_request:
    branches: "*"

jobs:
  tests:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.14"]

    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v6
        with:
          python-version: ${{ matrix.python-version }}

      # - name: Set PY env
      #   run: echo "::set-env name=PY::$(python -VV | sha256sum | cut -d' ' -f1)"

      - name: Install dependencies
        run: pip install -r requirements-dev.txt

      - name: Run pytest
        run: |
          pytest --cov-report xml:coverage.xml --cov-report term-missing --cov-report html:htmlcov
          
      - name: Upload coverage reports
        uses: actions/upload-artifact@v7
        with:
          name: coverage-report
          path: |
            coverage.xml
            htmlcov/
          retention-days: 7


================================================
FILE: .github/workflows/workflow-status.yml
================================================
name: Workflow Status Check

on:
  schedule:
    - cron: "0 9 * * 1"  # Weekly on Monday at 9 AM
  workflow_dispatch:  # Allow manual trigger

jobs:
  status-check:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Validate Dependabot configuration
        run: |
          echo "🔍 Validating Dependabot configuration..."
          if [ -f ".github/dependabot.yml" ]; then
            echo "✅ dependabot.yml found"
            # Basic YAML validation
            python -c "import yaml; yaml.safe_load(open('.github/dependabot.yml'))" && echo "✅ YAML syntax valid"
          else
            echo "❌ dependabot.yml not found"
            exit 1
          fi

      - name: Validate workflow files
        run: |
          echo "🔍 Validating workflow files..."
          for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do
            if [ -f "$workflow" ]; then
              echo "✅ Found workflow: $(basename "$workflow")"
              # Basic YAML validation
              python -c "import yaml; yaml.safe_load(open('$workflow'))" && echo "✅ YAML syntax valid for $(basename "$workflow")"
            fi
          done

      - name: Check requirements files
        run: |
          echo "🔍 Checking requirements files..."
          if [ -f "requirements.txt" ]; then
            echo "✅ requirements.txt found"
          fi
          if [ -f "requirements-dev.txt" ]; then
            echo "✅ requirements-dev.txt found"
            # Check if security tools are included
            if grep -q "safety\|bandit\|semgrep" requirements-dev.txt; then
              echo "✅ Security tools included in dev requirements"
            else
              echo "⚠️ Security tools not found in dev requirements"
            fi
          fi

      - name: Generate status report
        run: |
          echo "# Workflow Status Report" > workflow-status.md
          echo "Generated: $(date)" >> workflow-status.md
          echo "" >> workflow-status.md
          echo "## Configuration Status" >> workflow-status.md
          echo "- ✅ Dependabot configuration: Present" >> workflow-status.md
          echo "- ✅ Auto-merge workflow: Present" >> workflow-status.md
          echo "- ✅ Security checks: Present" >> workflow-status.md
          echo "- ✅ Enhanced build workflows: Present" >> workflow-status.md
          echo "" >> workflow-status.md
          echo "## Workflow Files" >> workflow-status.md
          for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do
            if [ -f "$workflow" ]; then
              echo "- $(basename "$workflow")" >> workflow-status.md
            fi
          done
          echo "" >> workflow-status.md
          echo "## Next Steps" >> workflow-status.md
          echo "1. Review and test the auto-merge workflow" >> workflow-status.md
          echo "2. Monitor security scan results" >> workflow-status.md
          echo "3. Adjust exclusions in dependabot.yml as needed" >> workflow-status.md

      - name: Upload status report
        uses: actions/upload-artifact@v7
        with:
          name: workflow-status-report
          path: workflow-status.md
          retention-days: 7

      - name: Comment on repository (if manual trigger)
        if: github.event_name == 'workflow_dispatch'
        uses: actions/github-script@v9
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('workflow-status.md', 'utf8');
            
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue?.number || 1,
              body: report
            });

================================================
FILE: .gitignore
================================================
# artifacts
__pycache__
.pytest*
*.egg-info
*/build/*
*/dist/*


# misc
.coverage
.vscode
!.vscode/settings.json
!.vscode/extensions.json
coverage.xml


# Home Assistant configuration
config/*
!config/configuration.yaml
.claude/settings.local.json


# specify
.specify/scripts/
.claude/


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
  - repo: https://github.com/psf/black-pre-commit-mirror
    rev: 24.2.0
    hooks:
      - id: black
        language_version: python3.13

  - repo: https://github.com/codespell-project/codespell
    rev: v2.2.6
    hooks:
      - id: codespell
        entry: codespell
        language: python
        types: [text]
        # Exclude translation JSON files except the canonical English file
        # This exclude stops codespell running on any translations path except en.json
        exclude: '(^custom_components/dual_smart_thermostat/translations/(?!en.json).*$)|(^.*/translations/.*$)'

  - repo: https://github.com/pycqa/flake8
    rev: '7.0.0'
    hooks:
      - id: flake8

  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort

  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy

================================================
FILE: .specify/memory/constitution.md
================================================
# [PROJECT_NAME] Constitution
<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->

## Core Principles

### [PRINCIPLE_1_NAME]
<!-- Example: I. Library-First -->
[PRINCIPLE_1_DESCRIPTION]
<!-- Example: Every feature starts as a standalone library; Libraries must be self-contained, independently testable, documented; Clear purpose required - no organizational-only libraries -->

### [PRINCIPLE_2_NAME]
<!-- Example: II. CLI Interface -->
[PRINCIPLE_2_DESCRIPTION]
<!-- Example: Every library exposes functionality via CLI; Text in/out protocol: stdin/args → stdout, errors → stderr; Support JSON + human-readable formats -->

### [PRINCIPLE_3_NAME]
<!-- Example: III. Test-First (NON-NEGOTIABLE) -->
[PRINCIPLE_3_DESCRIPTION]
<!-- Example: TDD mandatory: Tests written → User approved → Tests fail → Then implement; Red-Green-Refactor cycle strictly enforced -->

### [PRINCIPLE_4_NAME]
<!-- Example: IV. Integration Testing -->
[PRINCIPLE_4_DESCRIPTION]
<!-- Example: Focus areas requiring integration tests: New library contract tests, Contract changes, Inter-service communication, Shared schemas -->

### [PRINCIPLE_5_NAME]
<!-- Example: V. Observability, VI. Versioning & Breaking Changes, VII. Simplicity -->
[PRINCIPLE_5_DESCRIPTION]
<!-- Example: Text I/O ensures debuggability; Structured logging required; Or: MAJOR.MINOR.BUILD format; Or: Start simple, YAGNI principles -->

## [SECTION_2_NAME]
<!-- Example: Additional Constraints, Security Requirements, Performance Standards, etc. -->

[SECTION_2_CONTENT]
<!-- Example: Technology stack requirements, compliance standards, deployment policies, etc. -->

## [SECTION_3_NAME]
<!-- Example: Development Workflow, Review Process, Quality Gates, etc. -->

[SECTION_3_CONTENT]
<!-- Example: Code review requirements, testing gates, deployment approval process, etc. -->

## Governance
<!-- Example: Constitution supersedes all other practices; Amendments require documentation, approval, migration plan -->

[GOVERNANCE_RULES]
<!-- Example: All PRs/reviews must verify compliance; Complexity must be justified; Use [GUIDANCE_FILE] for runtime development guidance -->

**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE]
<!-- Example: Version: 2.1.1 | Ratified: 2025-06-13 | Last Amended: 2025-07-16 -->


================================================
FILE: .specify/memory/constitution_update_checklist.md
================================================
# Constitution Update Checklist

When amending the constitution (`/memory/constitution.md`), ensure all dependent documents are updated to maintain consistency.

## Templates to Update

### When adding/modifying ANY article:
- [ ] `/templates/plan-template.md` - Update Constitution Check section
- [ ] `/templates/spec-template.md` - Update if requirements/scope affected
- [ ] `/templates/tasks-template.md` - Update if new task types needed
- [ ] `/.claude/commands/plan.md` - Update if planning process changes
- [ ] `/.claude/commands/tasks.md` - Update if task generation affected
- [ ] `/CLAUDE.md` - Update runtime development guidelines

### Article-specific updates:

#### Article I (Library-First):
- [ ] Ensure templates emphasize library creation
- [ ] Update CLI command examples
- [ ] Add llms.txt documentation requirements

#### Article II (CLI Interface):
- [ ] Update CLI flag requirements in templates
- [ ] Add text I/O protocol reminders

#### Article III (Test-First):
- [ ] Update test order in all templates
- [ ] Emphasize TDD requirements
- [ ] Add test approval gates

#### Article IV (Integration Testing):
- [ ] List integration test triggers
- [ ] Update test type priorities
- [ ] Add real dependency requirements

#### Article V (Observability):
- [ ] Add logging requirements to templates
- [ ] Include multi-tier log streaming
- [ ] Update performance monitoring sections

#### Article VI (Versioning):
- [ ] Add version increment reminders
- [ ] Include breaking change procedures
- [ ] Update migration requirements

#### Article VII (Simplicity):
- [ ] Update project count limits
- [ ] Add pattern prohibition examples
- [ ] Include YAGNI reminders

## Validation Steps

1. **Before committing constitution changes:**
   - [ ] All templates reference new requirements
   - [ ] Examples updated to match new rules
   - [ ] No contradictions between documents

2. **After updating templates:**
   - [ ] Run through a sample implementation plan
   - [ ] Verify all constitution requirements addressed
   - [ ] Check that templates are self-contained (readable without constitution)

3. **Version tracking:**
   - [ ] Update constitution version number
   - [ ] Note version in template footers
   - [ ] Add amendment to constitution history

## Common Misses

Watch for these often-forgotten updates:
- Command documentation (`/commands/*.md`)
- Checklist items in templates
- Example code/commands
- Domain-specific variations (web vs mobile vs CLI)
- Cross-references between documents

## Template Sync Status

Last sync check: 2025-07-16
- Constitution version: 2.1.1
- Templates aligned: ❌ (missing versioning, observability details)

---

*This checklist ensures the constitution's principles are consistently applied across all project documentation.*

================================================
FILE: .specify/templates/agent-file-template.md
================================================
# [PROJECT NAME] Development Guidelines

Auto-generated from all feature plans. Last updated: [DATE]

## Active Technologies

[EXTRACTED FROM ALL PLAN.MD FILES]

## Project Structure

```text
[ACTUAL STRUCTURE FROM PLANS]
```

## Commands

[ONLY COMMANDS FOR ACTIVE TECHNOLOGIES]

## Code Style

[LANGUAGE-SPECIFIC, ONLY FOR LANGUAGES IN USE]

## Recent Changes

[LAST 3 FEATURES AND WHAT THEY ADDED]

<!-- MANUAL ADDITIONS START -->
<!-- MANUAL ADDITIONS END -->


================================================
FILE: .specify/templates/checklist-template.md
================================================
# [CHECKLIST TYPE] Checklist: [FEATURE NAME]

**Purpose**: [Brief description of what this checklist covers]
**Created**: [DATE]
**Feature**: [Link to spec.md or relevant documentation]

**Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements.

<!-- 
  ============================================================================
  IMPORTANT: The checklist items below are SAMPLE ITEMS for illustration only.
  
  The /speckit.checklist command MUST replace these with actual items based on:
  - User's specific checklist request
  - Feature requirements from spec.md
  - Technical context from plan.md
  - Implementation details from tasks.md
  
  DO NOT keep these sample items in the generated checklist file.
  ============================================================================
-->

## [Category 1]

- [ ] CHK001 First checklist item with clear action
- [ ] CHK002 Second checklist item
- [ ] CHK003 Third checklist item

## [Category 2]

- [ ] CHK004 Another category item
- [ ] CHK005 Item with specific criteria
- [ ] CHK006 Final item in this category

## Notes

- Check items off as completed: `[x]`
- Add comments or findings inline
- Link to relevant resources or documentation
- Items are numbered sequentially for easy reference


================================================
FILE: .specify/templates/plan-template.md
================================================
# Implementation Plan: [FEATURE]

**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]
**Input**: Feature specification from `/specs/[###-feature-name]/spec.md`

**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/templates/commands/plan.md` for the execution workflow.

## Summary

[Extract from feature spec: primary requirement + technical approach from research]

## Technical Context

<!--
  ACTION REQUIRED: Replace the content in this section with the technical details
  for the project. The structure here is presented in advisory capacity to guide
  the iteration process.
-->

**Language/Version**: [e.g., Python 3.11, Swift 5.9, Rust 1.75 or NEEDS CLARIFICATION]  
**Primary Dependencies**: [e.g., FastAPI, UIKit, LLVM or NEEDS CLARIFICATION]  
**Storage**: [if applicable, e.g., PostgreSQL, CoreData, files or N/A]  
**Testing**: [e.g., pytest, XCTest, cargo test or NEEDS CLARIFICATION]  
**Target Platform**: [e.g., Linux server, iOS 15+, WASM or NEEDS CLARIFICATION]
**Project Type**: [single/web/mobile - determines source structure]  
**Performance Goals**: [domain-specific, e.g., 1000 req/s, 10k lines/sec, 60 fps or NEEDS CLARIFICATION]  
**Constraints**: [domain-specific, e.g., <200ms p95, <100MB memory, offline-capable or NEEDS CLARIFICATION]  
**Scale/Scope**: [domain-specific, e.g., 10k users, 1M LOC, 50 screens or NEEDS CLARIFICATION]

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

[Gates determined based on constitution file]

## Project Structure

### Documentation (this feature)

```text
specs/[###-feature]/
├── plan.md              # This file (/speckit.plan command output)
├── research.md          # Phase 0 output (/speckit.plan command)
├── data-model.md        # Phase 1 output (/speckit.plan command)
├── quickstart.md        # Phase 1 output (/speckit.plan command)
├── contracts/           # Phase 1 output (/speckit.plan command)
└── tasks.md             # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
```

### Source Code (repository root)
<!--
  ACTION REQUIRED: Replace the placeholder tree below with the concrete layout
  for this feature. Delete unused options and expand the chosen structure with
  real paths (e.g., apps/admin, packages/something). The delivered plan must
  not include Option labels.
-->

```text
# [REMOVE IF UNUSED] Option 1: Single project (DEFAULT)
src/
├── models/
├── services/
├── cli/
└── lib/

tests/
├── contract/
├── integration/
└── unit/

# [REMOVE IF UNUSED] Option 2: Web application (when "frontend" + "backend" detected)
backend/
├── src/
│   ├── models/
│   ├── services/
│   └── api/
└── tests/

frontend/
├── src/
│   ├── components/
│   ├── pages/
│   └── services/
└── tests/

# [REMOVE IF UNUSED] Option 3: Mobile + API (when "iOS/Android" detected)
api/
└── [same as backend above]

ios/ or android/
└── [platform-specific structure: feature modules, UI flows, platform tests]
```

**Structure Decision**: [Document the selected structure and reference the real
directories captured above]

## Complexity Tracking

> **Fill ONLY if Constitution Check has violations that must be justified**

| Violation | Why Needed | Simpler Alternative Rejected Because |
|-----------|------------|-------------------------------------|
| [e.g., 4th project] | [current need] | [why 3 projects insufficient] |
| [e.g., Repository pattern] | [specific problem] | [why direct DB access insufficient] |


================================================
FILE: .specify/templates/spec-template.md
================================================
# Feature Specification: [FEATURE NAME]

**Feature Branch**: `[###-feature-name]`  
**Created**: [DATE]  
**Status**: Draft  
**Input**: User description: "$ARGUMENTS"

## User Scenarios & Testing *(mandatory)*

<!--
  IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
  Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
  you should still have a viable MVP (Minimum Viable Product) that delivers value.
  
  Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
  Think of each story as a standalone slice of functionality that can be:
  - Developed independently
  - Tested independently
  - Deployed independently
  - Demonstrated to users independently
-->

### User Story 1 - [Brief Title] (Priority: P1)

[Describe this user journey in plain language]

**Why this priority**: [Explain the value and why it has this priority level]

**Independent Test**: [Describe how this can be tested independently - e.g., "Can be fully tested by [specific action] and delivers [specific value]"]

**Acceptance Scenarios**:

1. **Given** [initial state], **When** [action], **Then** [expected outcome]
2. **Given** [initial state], **When** [action], **Then** [expected outcome]

---

### User Story 2 - [Brief Title] (Priority: P2)

[Describe this user journey in plain language]

**Why this priority**: [Explain the value and why it has this priority level]

**Independent Test**: [Describe how this can be tested independently]

**Acceptance Scenarios**:

1. **Given** [initial state], **When** [action], **Then** [expected outcome]

---

### User Story 3 - [Brief Title] (Priority: P3)

[Describe this user journey in plain language]

**Why this priority**: [Explain the value and why it has this priority level]

**Independent Test**: [Describe how this can be tested independently]

**Acceptance Scenarios**:

1. **Given** [initial state], **When** [action], **Then** [expected outcome]

---

[Add more user stories as needed, each with an assigned priority]

### Edge Cases

<!--
  ACTION REQUIRED: The content in this section represents placeholders.
  Fill them out with the right edge cases.
-->

- What happens when [boundary condition]?
- How does system handle [error scenario]?

## Requirements *(mandatory)*

<!--
  ACTION REQUIRED: The content in this section represents placeholders.
  Fill them out with the right functional requirements.
-->

### Functional Requirements

- **FR-001**: System MUST [specific capability, e.g., "allow users to create accounts"]
- **FR-002**: System MUST [specific capability, e.g., "validate email addresses"]  
- **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"]
- **FR-004**: System MUST [data requirement, e.g., "persist user preferences"]
- **FR-005**: System MUST [behavior, e.g., "log all security events"]

*Example of marking unclear requirements:*

- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?]
- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified]

### Key Entities *(include if feature involves data)*

- **[Entity 1]**: [What it represents, key attributes without implementation]
- **[Entity 2]**: [What it represents, relationships to other entities]

## Success Criteria *(mandatory)*

<!--
  ACTION REQUIRED: Define measurable success criteria.
  These must be technology-agnostic and measurable.
-->

### Measurable Outcomes

- **SC-001**: [Measurable metric, e.g., "Users can complete account creation in under 2 minutes"]
- **SC-002**: [Measurable metric, e.g., "System handles 1000 concurrent users without degradation"]
- **SC-003**: [User satisfaction metric, e.g., "90% of users successfully complete primary task on first attempt"]
- **SC-004**: [Business metric, e.g., "Reduce support tickets related to [X] by 50%"]


================================================
FILE: .specify/templates/tasks-template.md
================================================
---

description: "Task list template for feature implementation"
---

# Tasks: [FEATURE NAME]

**Input**: Design documents from `/specs/[###-feature-name]/`
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/

**Tests**: The examples below include test tasks. Tests are OPTIONAL - only include them if explicitly requested in the feature specification.

**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.

## Format: `[ID] [P?] [Story] Description`

- **[P]**: Can run in parallel (different files, no dependencies)
- **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3)
- Include exact file paths in descriptions

## Path Conventions

- **Single project**: `src/`, `tests/` at repository root
- **Web app**: `backend/src/`, `frontend/src/`
- **Mobile**: `api/src/`, `ios/src/` or `android/src/`
- Paths shown below assume single project - adjust based on plan.md structure

<!-- 
  ============================================================================
  IMPORTANT: The tasks below are SAMPLE TASKS for illustration purposes only.
  
  The /speckit.tasks command MUST replace these with actual tasks based on:
  - User stories from spec.md (with their priorities P1, P2, P3...)
  - Feature requirements from plan.md
  - Entities from data-model.md
  - Endpoints from contracts/
  
  Tasks MUST be organized by user story so each story can be:
  - Implemented independently
  - Tested independently
  - Delivered as an MVP increment
  
  DO NOT keep these sample tasks in the generated tasks.md file.
  ============================================================================
-->

## Phase 1: Setup (Shared Infrastructure)

**Purpose**: Project initialization and basic structure

- [ ] T001 Create project structure per implementation plan
- [ ] T002 Initialize [language] project with [framework] dependencies
- [ ] T003 [P] Configure linting and formatting tools

---

## Phase 2: Foundational (Blocking Prerequisites)

**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented

**⚠️ CRITICAL**: No user story work can begin until this phase is complete

Examples of foundational tasks (adjust based on your project):

- [ ] T004 Setup database schema and migrations framework
- [ ] T005 [P] Implement authentication/authorization framework
- [ ] T006 [P] Setup API routing and middleware structure
- [ ] T007 Create base models/entities that all stories depend on
- [ ] T008 Configure error handling and logging infrastructure
- [ ] T009 Setup environment configuration management

**Checkpoint**: Foundation ready - user story implementation can now begin in parallel

---

## Phase 3: User Story 1 - [Title] (Priority: P1) 🎯 MVP

**Goal**: [Brief description of what this story delivers]

**Independent Test**: [How to verify this story works on its own]

### Tests for User Story 1 (OPTIONAL - only if tests requested) ⚠️

> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**

- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py
- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py

### Implementation for User Story 1

- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py
- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py
- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013)
- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py
- [ ] T016 [US1] Add validation and error handling
- [ ] T017 [US1] Add logging for user story 1 operations

**Checkpoint**: At this point, User Story 1 should be fully functional and testable independently

---

## Phase 4: User Story 2 - [Title] (Priority: P2)

**Goal**: [Brief description of what this story delivers]

**Independent Test**: [How to verify this story works on its own]

### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️

- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py
- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py

### Implementation for User Story 2

- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py
- [ ] T021 [US2] Implement [Service] in src/services/[service].py
- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py
- [ ] T023 [US2] Integrate with User Story 1 components (if needed)

**Checkpoint**: At this point, User Stories 1 AND 2 should both work independently

---

## Phase 5: User Story 3 - [Title] (Priority: P3)

**Goal**: [Brief description of what this story delivers]

**Independent Test**: [How to verify this story works on its own]

### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️

- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py
- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py

### Implementation for User Story 3

- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py
- [ ] T027 [US3] Implement [Service] in src/services/[service].py
- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py

**Checkpoint**: All user stories should now be independently functional

---

[Add more user story phases as needed, following the same pattern]

---

## Phase N: Polish & Cross-Cutting Concerns

**Purpose**: Improvements that affect multiple user stories

- [ ] TXXX [P] Documentation updates in docs/
- [ ] TXXX Code cleanup and refactoring
- [ ] TXXX Performance optimization across all stories
- [ ] TXXX [P] Additional unit tests (if requested) in tests/unit/
- [ ] TXXX Security hardening
- [ ] TXXX Run quickstart.md validation

---

## Dependencies & Execution Order

### Phase Dependencies

- **Setup (Phase 1)**: No dependencies - can start immediately
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
- **User Stories (Phase 3+)**: All depend on Foundational phase completion
  - User stories can then proceed in parallel (if staffed)
  - Or sequentially in priority order (P1 → P2 → P3)
- **Polish (Final Phase)**: Depends on all desired user stories being complete

### User Story Dependencies

- **User Story 1 (P1)**: Can start after Foundational (Phase 2) - No dependencies on other stories
- **User Story 2 (P2)**: Can start after Foundational (Phase 2) - May integrate with US1 but should be independently testable
- **User Story 3 (P3)**: Can start after Foundational (Phase 2) - May integrate with US1/US2 but should be independently testable

### Within Each User Story

- Tests (if included) MUST be written and FAIL before implementation
- Models before services
- Services before endpoints
- Core implementation before integration
- Story complete before moving to next priority

### Parallel Opportunities

- All Setup tasks marked [P] can run in parallel
- All Foundational tasks marked [P] can run in parallel (within Phase 2)
- Once Foundational phase completes, all user stories can start in parallel (if team capacity allows)
- All tests for a user story marked [P] can run in parallel
- Models within a story marked [P] can run in parallel
- Different user stories can be worked on in parallel by different team members

---

## Parallel Example: User Story 1

```bash
# Launch all tests for User Story 1 together (if tests requested):
Task: "Contract test for [endpoint] in tests/contract/test_[name].py"
Task: "Integration test for [user journey] in tests/integration/test_[name].py"

# Launch all models for User Story 1 together:
Task: "Create [Entity1] model in src/models/[entity1].py"
Task: "Create [Entity2] model in src/models/[entity2].py"
```

---

## Implementation Strategy

### MVP First (User Story 1 Only)

1. Complete Phase 1: Setup
2. Complete Phase 2: Foundational (CRITICAL - blocks all stories)
3. Complete Phase 3: User Story 1
4. **STOP and VALIDATE**: Test User Story 1 independently
5. Deploy/demo if ready

### Incremental Delivery

1. Complete Setup + Foundational → Foundation ready
2. Add User Story 1 → Test independently → Deploy/Demo (MVP!)
3. Add User Story 2 → Test independently → Deploy/Demo
4. Add User Story 3 → Test independently → Deploy/Demo
5. Each story adds value without breaking previous stories

### Parallel Team Strategy

With multiple developers:

1. Team completes Setup + Foundational together
2. Once Foundational is done:
   - Developer A: User Story 1
   - Developer B: User Story 2
   - Developer C: User Story 3
3. Stories complete and integrate independently

---

## Notes

- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Each user story should be independently completable and testable
- Verify tests fail before implementing
- Commit after each task or logical group
- Stop at any checkpoint to validate story independently
- Avoid: vague tasks, same file conflicts, cross-story dependencies that break independence


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **Native Fan Speed Control** - Control fan speeds (low, medium, high, auto) directly from the thermostat interface, similar to built-in thermostats (#517)
  - Automatic detection of fan entity capabilities (preset_mode and percentage support)
  - Fan speed control works in FAN_ONLY mode, fan_on_with_ac mode, and fan tolerance mode
  - State persistence across Home Assistant restarts
  - Support for both preset_mode (named speeds) and percentage-based control
  - Automatic percentage-to-preset mapping for optimal compatibility
  - Full backward compatibility with switch-based fans (no fan speed control)

### Changed

- Fan entities now expose speed control capabilities when supported by the underlying fan entity
- FeatureManager enhanced to detect and track fan speed capabilities

### Documentation

- Added comprehensive fan speed control architecture documentation to CLAUDE.md
- Updated README.md with fan speed control usage examples and configuration guidance
- Added detailed fan speed control design and implementation documentation

## [v0.11.2] - 2025-01-XX

### Fixed

- Fixed heater/cooler turns off prematurely ignoring tolerance when active (#518) (#521)
- Corrected logger name handling for multiple thermostat instances (#511) (#513)
- Corrected inverted tolerance logic and added comprehensive behavioral tests (#506) (#507)

## [v0.11.0] - 2024-12-XX

See [RELEASE_NOTES_v0.11.0.md](RELEASE_NOTES_v0.11.0.md) for complete release notes.

### Major Features

- Complete UI Configuration - Set up your thermostat through Home Assistant's UI with guided wizard
- Template-Based Preset Temperatures - Dynamic presets using Home Assistant templates
- Input Boolean Support for Equipment - Use input_boolean entities for all equipment controls
- Docker-Based Development Environment - Professional development workflow for contributors

[Unreleased]: https://github.com/swingerman/ha-dual-smart-thermostat/compare/v0.11.2...HEAD
[v0.11.2]: https://github.com/swingerman/ha-dual-smart-thermostat/compare/v0.11.0...v0.11.2
[v0.11.0]: https://github.com/swingerman/ha-dual-smart-thermostat/releases/tag/v0.11.0


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Home Assistant Dual Smart Thermostat - An enhanced thermostat component supporting multiple HVAC modes (heating, cooling, heat pump, fan, humidity control), advanced features (floor temperature control, window/door sensors, presets), and sophisticated control logic.

**Target**: Home Assistant 2025.1.0+
**Language**: Python 3.13

## Essential Commands

### Development with Docker (Recommended)

**IMPORTANT: For Claude Code development, always use Docker scripts for testing and linting to ensure consistent environment and avoid local Python dependency issues.**

The project provides convenient Docker scripts in the `scripts/` folder:

```bash
# Testing - Use docker-test for all test runs
./scripts/docker-test                              # Run all tests
./scripts/docker-test tests/test_heater_mode.py    # Run specific test file
./scripts/docker-test -k "heater"                  # Run tests matching pattern
./scripts/docker-test --cov                        # Run with coverage report
./scripts/docker-test --log-cli-level=DEBUG        # Run with debug logging

# Linting - Use docker-lint for all code quality checks (REQUIRED before commit)
./scripts/docker-lint                              # Check all linting (isort, black, flake8, codespell, ruff)
./scripts/docker-lint --fix                        # Auto-fix linting issues

# Interactive Shell - For debugging and exploration
./scripts/docker-shell                             # Open bash shell in container
./scripts/docker-shell python                      # Open Python REPL in container
```

**Why use Docker scripts:**
- Guaranteed consistent Python 3.13 + HA 2025.1.0+ environment
- No local dependency conflicts or version mismatches
- Same environment as CI/CD pipeline
- Automatic image building if needed
- Live source code mounting (changes reflected immediately)

### Local Development (Alternative)

If you prefer local development without Docker:

```bash
# Install dependencies
pip install -r requirements-dev.txt
pre-commit install

# Testing (local alternative)
pytest                                    # Run all tests
pytest tests/test_heater_mode.py          # Run specific test file
pytest --log-cli-level=DEBUG              # Run with debug logging

# Linting (local alternative - ALL must pass before commit)
isort . --check-only --diff               # Import sorting
black --check .                           # Code formatting
flake8 .                                  # Style/linting
codespell                                 # Spell checking
ruff check .                              # Additional linting

# Auto-fix linting issues (local)
isort .
black .
ruff check . --fix
```

### Advanced Docker Usage

```bash
# Build with specific Home Assistant version
HA_VERSION=2025.2.0 docker-compose build dev
HA_VERSION=latest docker-compose build dev

# Run custom commands in container
docker-compose run --rm dev <command>
```

### Code Quality Requirements

**ALL code MUST pass linting checks before commit:**
- `isort` - Import sorting
- `black` - Code formatting (88 character line length)
- `flake8` - Style/linting
- `codespell` - Spell checking
- `ruff` - Additional linting

**Run `./scripts/docker-lint` before committing. GitHub workflows will reject failing commits.**

## Architecture Overview

### Modular Design Pattern

The codebase uses a **separation of concerns** architecture with distinct layers:

1. **Device Layer** (`hvac_device/`) - Hardware abstraction for different HVAC equipment types
2. **Manager Layer** (`managers/`) - Shared business logic (features, state, environment)
3. **Controller Layer** (`hvac_controller/`) - Orchestration between devices and managers
4. **Climate Entity** (`climate.py`) - Home Assistant integration interface

### Core Components

#### Device Types (`hvac_device/`)
Abstraction layer for different HVAC equipment:
- `heater_device.py` - Basic heating
- `cooler_device.py` - Air conditioning
- `heat_pump_device.py` - Combined heating/cooling (single switch)
- `heater_cooler_device.py` - Dual heating/cooling (separate switches)
- `heater_aux_heater_device.py` - Two-stage heating
- `fan_device.py` - Fan-only operation
- `dryer_device.py` - Humidity control
- `hvac_device_factory.py` - **Factory pattern** creates appropriate device based on configuration

#### Managers (`managers/`)
Shared logic components handling specific responsibilities:
- `state_manager.py` - Persistence and state restoration
- `environment_manager.py` - Environmental condition tracking (temperature, humidity, sensors)
- `feature_manager.py` - Feature enablement and configuration
- `opening_manager.py` - Window/door sensor handling
- `preset_manager.py` - Preset mode management
- `hvac_power_manager.py` - Power cycling and keep-alive logic

#### Controllers (`hvac_controller/`)
Orchestration of control logic:
- `generic_controller.py` - Base controller with common logic
- `heater_controller.py` - Heating-specific control
- `cooler_controller.py` - Cooling-specific control
- `hvac_controller.py` - Top-level coordinator

#### HVAC Action Reasons (`hvac_action_reason/`)
Tracking and reporting why HVAC actions occur:
- `hvac_action_reason_internal.py` - System-triggered reasons (temp reached, opening detected, etc.)
- `hvac_action_reason_external.py` - User/automation-triggered reasons (schedule, presence, emergency)

### Configuration Flow (`config_flow.py`, `options_flow.py`)

Multi-step wizard for configuration with **feature-based step generation**:
- `feature_steps/` - Modular configuration steps for different features
- Steps are generated dynamically based on system type and enabled features
- **Critical**: Step ordering follows dependency chain (base → features → openings → presets)

## Key Architectural Patterns

### Factory Pattern
Device creation uses factory pattern in `hvac_device_factory.py`:
```python
device = HVACDeviceFactory.create_device(hass, config, hvac_mode)
```

### Manager Coordination
Managers work together through dependency injection:
```python
if self._opening_manager.is_any_opening_open():
    if self._feature_manager.is_floor_protection_enabled():
        # Complex feature interaction
```

### State Machine
Climate entity manages HVAC mode state transitions with validation and callbacks.

## Critical Development Rules

### Before You Write Code

1. State how you will verify this change (test, batch command, browser check, etc.)
2. Write the test verification step first
3. Then implement the code
4. Run verification and iterate until it passes

### Configuration Flow Integration

**CRITICAL**: Every added or modified configuration option MUST be integrated into the appropriate configuration flows (config, reconfigure, or options flows). This is mandatory for all configuration changes.

#### When Flow Integration is Required

Flow integration is required whenever you:
1. Add a new configuration parameter to `const.py` or `schemas.py`
2. Modify an existing configuration parameter's behavior or validation
3. Add a new feature that requires user configuration
4. Change how configuration options interact with each other

#### Which Flow(s) to Update

Determine which flow(s) need updates based on the type of change:

1. **Initial Configuration Flow** (`config_flow.py`):
   - New system types or HVAC modes
   - New required entities (heater, cooler, sensors)
   - New features that should be configured during initial setup
   - Core system behavior changes

2. **Reconfigure Flow** (`config_flow.py` - reconfigure handlers):
   - Changes to existing system configuration that require reconfiguration
   - System type switching
   - Entity replacement or updates
   - Any change that affects the initial configuration flow

3. **Options Flow** (`options_flow.py`):
   - Feature toggles (enabling/disabling features)
   - Feature-specific settings (thresholds, timeouts, behaviors)
   - Preset configurations
   - Advanced settings that don't require reconfiguration
   - Any setting that users might want to change after initial setup

**Rule of Thumb**: If users need to configure it during initial setup, add it to config/reconfigure flows. If users might want to adjust it later, add it to options flow. Often, you'll need to add to both.

#### How to Integrate Changes into Flows

Follow this process to integrate configuration changes:

1. **Add Constants and Schema**:
   ```python
   # In const.py - Add configuration key constant
   CONF_NEW_FEATURE = "new_feature"

   # In schemas.py - Add to appropriate schema
   NEW_FEATURE_SCHEMA = vol.Schema({
       vol.Optional(CONF_NEW_FEATURE, default=False): cv.boolean,
   })
   ```

2. **Add Configuration Step** (if needed):
   ```python
   # In feature_steps/ - Create new step file if complex feature
   # Or add to existing step file

   async def async_step_new_feature(self, user_input=None):
       """Handle new feature configuration."""
       # Follow existing patterns from other step handlers
   ```

3. **Update Flow Navigation**:
   ```python
   # In config_flow.py or options_flow.py
   # Update _determine_next_step() or flow handler to include new step
   # Ensure proper step ordering (see Step Ordering section)
   ```

4. **Add Data Validation**:
   ```python
   # Add validation logic in step handler
   # Follow existing validation patterns
   # Provide clear error messages
   ```

5. **Update Translations**:
   ```json
   // In translations/en.json
   "step": {
       "new_feature": {
           "title": "Configure New Feature",
           "description": "Description of what this configures",
           "data": {
               "new_feature": "Enable new feature"
           }
       }
   }
   ```

#### Testing Flow Integration

**REQUIRED**: All flow changes must be tested:

1. **Unit Tests**: Add to `tests/config_flow/`
   - Test step handler logic
   - Test validation
   - Test error handling

2. **Integration Tests**: Add to appropriate integration test file
   - Test complete flow with new option
   - Test persistence (config → options flow)
   - Test edge cases

3. **Manual Testing**:
   - Test initial configuration flow
   - Test reconfigure flow (if applicable)
   - Test options flow with existing configurations
   - Test with different system types

#### Example Flow Integration

When adding a new floor temperature feature:

```python
# 1. Add to const.py
CONF_MAX_FLOOR_TEMP = "max_floor_temp"

# 2. Add to schemas.py
FLOOR_TEMP_SCHEMA = vol.Schema({
    vol.Optional(CONF_MAX_FLOOR_TEMP): vol.Coerce(float),
})

# 3. Add step in feature_steps/floor_heating_steps.py
async def async_step_floor_heating(self, user_input=None):
    """Configure floor heating options."""
    if user_input is not None:
        # Validate and store
        return self.async_create_entry(...)

    # Show form with floor temp options
    return self.async_show_form(...)

# 4. Update navigation in config_flow.py
def _determine_next_step(self):
    if self._has_floor_sensor():
        return "floor_heating"  # Add to flow sequence
    return "next_step"

# 5. Add tests in tests/config_flow/test_floor_heating_integration.py
async def test_floor_heating_config_flow():
    """Test floor heating configuration in flow."""
    # Test implementation
```

#### Clarification Process

If it's unclear how to integrate a configuration change into the flows:

1. **Analyze the Feature**:
   - What does this configuration control?
   - Is it a core feature or an optional enhancement?
   - Does it depend on other configuration?

2. **Review Similar Features**:
   - Find similar existing features in the codebase
   - Review their flow integration
   - Follow the same patterns

3. **Check Dependencies**:
   - Does this feature require other configuration first?
   - Should it be in the main flow or a separate step?
   - Where should it appear in the step ordering?

4. **Ask for Clarification**:
   - If still unclear, document your analysis
   - Ask specifically: "Should this be in config or options flow?"
   - Provide context about the feature and its dependencies

**Remember**: When in doubt, add to both config/reconfigure AND options flows to provide maximum flexibility.

### Configuration Dependencies

**CRITICAL**: When adding configuration parameters, update dependency tracking:

1. **Check for dependencies**: Does the new parameter require another parameter to function?
2. **Update tracking files**:
   - `tools/focused_config_dependencies.json` - Add conditional dependencies
   - `tools/config_validator.py` - Add validation rules
   - `docs/config/CRITICAL_CONFIG_DEPENDENCIES.md` - Document with examples
3. **Test validation**: `python tools/config_validator.py`

Example dependency: `max_floor_temp` requires `floor_sensor` to function.

### Configuration Flow Step Ordering

**CRITICAL**: Configuration steps MUST follow this order:

1. System type and basic entities (heater, cooler, sensors)
2. System-specific configuration (heat pump, dual stage)
3. Feature toggles (floor heating, fan, humidity)
4. Feature-specific configuration
5. **Openings configuration** (depends on system type and entities)
6. **Presets configuration** (depends on ALL previous configuration)

**Openings and presets must always be the last configuration steps** because they depend on all previously configured features.

See `docs/config_flow/step_ordering.md` for detailed rules.

### Linting Requirements

**ALL code MUST pass these checks before commit**:
- `isort` - Import sorting (configuration in `setup.cfg`)
- `black` - Code formatting (88 character line length)
- `flake8` - Style/linting (ignores configured in `setup.cfg`)
- `codespell` - Spell checking
- `ruff` - Additional linting checks

**Use `./scripts/docker-lint` to check all linting** (or `./scripts/docker-lint --fix` to auto-fix).

GitHub workflows will **reject** commits that fail linting.

## Testing Strategy

### Test Organization

The test suite is organized by functionality with a focus on consolidation and maintainability:

#### Core Functionality Tests
- `tests/test_<mode>_mode.py` - Mode-specific functionality (heater, cooler, heat pump, fan, dry, dual)
- `tests/presets/` - Preset functionality tests
- `tests/openings/` - Opening detection tests
- `tests/features/` - Feature-specific tests
- `tests/conftest.py` - Pytest fixtures and test utilities

#### Config Flow Tests (`tests/config_flow/`)

**IMPORTANT**: The config flow tests have been consolidated to reduce duplication. When adding new tests:

1. **Core Flow Tests** - General configuration and options flow behavior
   - `test_config_flow.py` - Basic config flow, system type selection, validation
   - `test_options_flow.py` - **CONSOLIDATED** - All options flow tests including:
     - Basic flow progression and step navigation
     - Feature persistence (fan, humidity settings pre-filled)
     - Preset detection and toggles
     - Complete flow integration tests
   - `test_advanced_options.py` - Advanced settings configuration

2. **E2E Persistence Tests** - End-to-end config→options flow testing
   - `test_e2e_simple_heater_persistence.py` - **CONSOLIDATED** - Includes:
     - Minimal config + all features persistence tests
     - Openings scope/timeout edge cases
   - `test_e2e_ac_only_persistence.py` - **CONSOLIDATED** - Minimal + all features
   - `test_e2e_heat_pump_persistence.py` - **CONSOLIDATED** - Minimal + all features
   - `test_e2e_heater_cooler_persistence.py` - **CONSOLIDATED** - Includes:
     - Minimal config + all features persistence tests
     - Fan mode persistence edge cases
     - Boolean False value persistence tests

3. **Reconfigure Flow Tests** - System reconfiguration
   - `test_reconfigure_flow.py` - General reconfigure mechanics
   - `test_reconfigure_flow_e2e_<system>.py` - Full reconfigure flow per system type
   - `test_reconfigure_system_type_change.py` - System type switching

4. **Feature Integration Tests** - Feature combinations per system type
   - `test_simple_heater_features_integration.py` - All feature combos for simple_heater
   - `test_ac_only_features_integration.py` - All feature combos for ac_only
   - `test_heat_pump_features_integration.py` - All feature combos for heat_pump
   - `test_heater_cooler_features_integration.py` - All feature combos for heater_cooler

5. **System-Specific Tests** - Unique system type behaviors
   - `test_heat_pump_config_flow.py`, `test_heat_pump_options_flow.py`
   - `test_heater_cooler_flow.py`
   - `test_ac_only_features.py`, `test_ac_only_advanced_settings.py`
   - `test_simple_heater_advanced.py`

6. **Utilities and Validation**
   - `test_integration.py` - **CONSOLIDATED** - Integration tests and transient flag handling
   - `test_step_ordering.py` - Config step dependency validation
   - `test_translations.py` - Localization support
   - `test_options_entry_helpers.py` - Helper function unit tests

### Adding New Config Flow Tests

**Where to add your test:**

1. **Bug fixes or edge cases?**
   - **DO NOT** create separate bug fix test files
   - Add to relevant consolidated file:
     - Feature persistence issues → `test_options_flow.py`
     - System-specific persistence → appropriate `test_e2e_<system>_persistence.py`
     - Openings edge cases → `test_e2e_simple_heater_persistence.py`
     - Fan edge cases → `test_e2e_heater_cooler_persistence.py`

2. **New system type behavior?**
   - Add to system-specific test file or create new if needed
   - Keep system-specific files focused and clear

3. **New feature integration?**
   - Add to appropriate `test_<system>_features_integration.py`

4. **New reconfigure scenario?**
   - Add to `test_reconfigure_flow.py` or system-specific reconfigure file

**Pattern to follow:**
```python
@pytest.mark.asyncio
async def test_descriptive_name_of_what_youre_testing(hass):
    """Clear docstring explaining the test purpose and what it validates.

    If this was a bug fix, mention the original issue here.
    """
    # Test implementation using pytest patterns
    # Use hass fixture from pytest-homeassistant-custom-component
```

### Test Requirements
- **Every new feature MUST have tests** covering success and failure scenarios
- Use async test fixtures from `conftest.py`
- Follow existing test patterns for consistency
- **DO NOT create standalone bug fix test files** - integrate into existing tests
- **Consolidate related tests** - avoid creating many small test files

### Running Tests

**Use Docker scripts for all testing** (recommended):

```bash
# All tests
./scripts/docker-test

# Config flow tests only
./scripts/docker-test tests/config_flow/

# Single test file
./scripts/docker-test tests/config_flow/test_e2e_simple_heater_persistence.py

# Single test function
./scripts/docker-test tests/config_flow/test_options_flow.py::test_options_flow_fan_settings_prefilled

# With debug logging
./scripts/docker-test --log-cli-level=DEBUG tests/test_heater_mode.py

# With coverage report
./scripts/docker-test --cov
```

**Local alternative** (if not using Docker):
```bash
pytest                           # All tests
pytest tests/config_flow/        # Specific directory
pytest --log-cli-level=DEBUG     # With debug logging
```

Configuration: `pytest.ini` sets asyncio mode and test discovery patterns.

## Common Development Workflows

### Adding a New Feature

1. **Identify components**:
   - New device type? → Add to `hvac_device/`
   - Shared logic? → Add to or extend `managers/`
   - Control logic? → Modify `hvac_controller/`

2. **Add configuration**:
   - Constants to `const.py`
   - Schema to `schemas.py`
   - **Integrate into configuration flows** (see Configuration Flow Integration above)
     - Determine which flow(s) to update (config, reconfigure, options)
     - Add configuration steps to `feature_steps/` or flow files
     - Update flow navigation and validation
     - Update translations
   - **Update configuration dependencies** (see Configuration Dependencies above)

3. **Implement logic**:
   - Follow existing patterns
   - Use dependency injection for managers
   - Handle errors gracefully

4. **Add tests** (following consolidation guidelines):
   - **Core functionality**: Add to `tests/features/` or mode-specific test
   - **Config flow integration**: Add to appropriate `test_<system>_features_integration.py`
   - **Persistence**: Add test cases to relevant `test_e2e_<system>_persistence.py`
   - **Options flow**: Add to `test_options_flow.py` if needed
   - **DO NOT** create new small test files - add to existing consolidated tests
   - Cover success and failure cases
   - Test feature interactions

5. **Code quality** (use Docker scripts):
   - Run linting: `./scripts/docker-lint` (checks all linters)
   - Auto-fix linting: `./scripts/docker-lint --fix`
   - Run tests: `./scripts/docker-test`
   - Run specific tests: `./scripts/docker-test tests/features/`

### Modifying Existing Features

1. **Understand the change**: Read relevant code in device/manager/controller layers
2. **Check dependencies**: Identify which components are affected
3. **Update tests first**: Modify tests to reflect new behavior
4. **Implement changes**: Make minimal changes following existing patterns
5. **Verify** (use Docker scripts):
   - Run affected tests: `./scripts/docker-test tests/test_heater_mode.py`
   - Run full test suite: `./scripts/docker-test`
   - Check linting: `./scripts/docker-lint`

### Debugging HVAC Logic

The integration uses structured logging:
```python
_LOGGER.debug("Device operation details")  # Detailed flow
_LOGGER.info("State changes")              # Important events
_LOGGER.warning("Recoverable issues")      # Potential problems
_LOGGER.error("Failed operations")         # Errors
```

Enable debug logging in Home Assistant to trace execution flow.

## Important Constraints

### Backward Compatibility
- Never break existing YAML configurations
- Configuration migrations must be handled gracefully
- State restoration must handle old and new formats

### Home Assistant Integration
- Use Home Assistant's async patterns (`async def`, `await`)
- Respect entity lifecycle (setup, update, remove)
- Follow Home Assistant coding standards

### Device Safety
- Always check device availability before operations
- Handle sensor failures gracefully (stale detection)
- Respect min cycle durations to prevent equipment damage
- Floor temperature limits prevent overheating

## File Structure Reference

```
custom_components/dual_smart_thermostat/
├── climate.py                    # Main climate entity
├── config_flow.py               # Initial configuration wizard
├── options_flow.py              # Configuration updates
├── const.py                     # Constants and config keys
├── schemas.py                   # Configuration schemas
├── services.yaml                # Service definitions
├── manifest.json                # Component metadata
├── hvac_device/                 # Device abstraction layer
│   ├── generic_hvac_device.py
│   ├── hvac_device_factory.py
│   └── [specific device types]
├── managers/                    # Business logic layer
│   ├── state_manager.py
│   ├── environment_manager.py
│   ├── feature_manager.py
│   ├── opening_manager.py
│   ├── preset_manager.py
│   └── hvac_power_manager.py
├── hvac_controller/             # Control logic layer
│   ├── generic_controller.py
│   ├── heater_controller.py
│   ├── cooler_controller.py
│   └── hvac_controller.py
├── hvac_action_reason/          # Action reason tracking
├── feature_steps/               # Config flow feature steps
└── translations/                # Localization files
```

## Special Considerations

### Heat Pump Mode
Single switch controls both heating and cooling based on `heat_pump_cooling` sensor state. Requires careful state tracking.

### Two-Stage Heating
Secondary heater activates after timeout if primary heater runs continuously. Day-based memory prevents premature secondary activation.

### Floor Temperature Protection
Min/max floor temperature limits prevent damage. These limits can be set globally and overridden per preset.

### Opening Detection
Window/door sensors pause HVAC operation. Supports timeout and closing_timeout for debouncing. Scope can be limited to specific HVAC modes.

### Preset Modes
Temperature/humidity presets depend on all other configuration. Must be configured last in flow.

### Fan Speed Control

Native fan speed control provides variable speed operation for fan-only mode. The implementation uses automatic capability detection to support different fan entity types.

#### Architecture

**Capability Detection Pattern** (`hvac_device/fan_device.py`):

The `FanDevice` class automatically detects fan speed capabilities during initialization using a three-tier detection strategy:

1. **Domain Check**: Only `fan` domain entities support speed control (not `switch` domain)
2. **Preset Mode Detection**: Check for `preset_modes` attribute (most specific control)
3. **Percentage Detection**: Check for `percentage` attribute (fallback to percentage-based control)

Implementation in `FanDevice.__init__()` and `_detect_fan_capabilities()`:
```python
def _detect_fan_capabilities(self) -> None:
    """Detect if fan entity supports speed control."""
    fan_state = self.hass.states.get(self.entity_id)

    # Domain check
    if fan_state.domain == "switch":
        return  # No speed control for switches

    # Check for preset_modes (native fan control)
    if fan_state.attributes.get("preset_modes"):
        self._supports_fan_mode = True
        self._fan_modes = list(preset_modes)
        self._uses_preset_modes = True

    # Check for percentage (fallback control)
    elif fan_state.attributes.get("percentage") is not None:
        self._supports_fan_mode = True
        self._fan_modes = ["auto", "low", "medium", "high"]
        self._uses_preset_modes = False
```

**Properties Exposed**:
- `supports_fan_mode` - Boolean flag indicating speed control capability
- `fan_modes` - List of available modes (from entity or default)
- `uses_preset_modes` - Boolean indicating control method (preset vs percentage)
- `current_fan_mode` - Current selected mode

**Service Call Routing** (`FanDevice.async_set_fan_mode()`):
- Preset mode fans → `fan.set_preset_mode` service
- Percentage fans → `fan.set_percentage` service (with mapping via `FAN_MODE_TO_PERCENTAGE`)

#### Feature Manager Integration

The `FeatureManager` (`managers/feature_manager.py`) provides access to fan speed control capabilities:

```python
@property
def supports_fan_mode(self) -> bool:
    """Dynamically check if fan device supports speed control."""
    return self._fan_device.supports_fan_mode

@property
def fan_modes(self) -> list[str]:
    """Return available fan modes from device."""
    return self._fan_device.fan_modes
```

Support flag is set dynamically in `set_support_flags()`:
```python
if self.supports_fan_mode:
    self._supported_features |= ClimateEntityFeature.FAN_MODE
```

#### Climate Entity Integration

The `DualSmartThermostat` climate entity (`climate.py`) exposes fan speed control through standard Home Assistant interfaces:

**Properties**:
- `fan_mode` → Returns `fan_device.current_fan_mode`
- `fan_modes` → Returns `features.fan_modes`
- `supported_features` → Includes `ClimateEntityFeature.FAN_MODE` if supported

**Service Method**:
```python
async def async_set_fan_mode(self, fan_mode: str) -> None:
    """Set fan speed mode."""
    fan_device = self.hvac_device_manager.get_device(HVACMode.FAN_ONLY)
    await fan_device.async_set_fan_mode(fan_mode)
```

#### State Persistence

Fan mode state is persisted through Home Assistant's state machine:

**Saving State** (`climate.py` - `extra_state_attributes`):
```python
if self.features.supports_fan_mode and self.fan_mode is not None:
    attributes[ATTR_FAN_MODE] = self.fan_mode
```

**Restoring State** (`FeatureManager._restore_fan_mode()`):
```python
old_fan_mode = old_state.attributes.get(ATTR_FAN_MODE)
if old_fan_mode is not None:
    self._fan_device.restore_fan_mode(old_fan_mode)
```

The `restore_fan_mode()` method in `FanDevice` validates the restored mode against current capabilities:
```python
def restore_fan_mode(self, fan_mode: str) -> None:
    """Restore fan mode from persisted state with validation."""
    if fan_mode in self._fan_modes:
        self._current_fan_mode = fan_mode
    else:
        _LOGGER.warning("Cannot restore invalid fan mode %s", fan_mode)
```

#### Mode Application

When fan is turned on, the selected mode is automatically applied:

```python
async def async_turn_on(self):
    """Turn on fan and apply selected mode."""
    await super().async_turn_on()  # Turn on device

    if self._supports_fan_mode and self._current_fan_mode is not None:
        await self.async_set_fan_mode(self._current_fan_mode)
```

This ensures the fan always operates at the user-selected speed, even after power cycles or restarts.

#### Design Trade-offs

1. **Automatic Detection vs Configuration**: Detection is automatic to reduce configuration complexity. Trade-off: Cannot manually override detected capabilities.

2. **Fallback to Percentage**: Default modes (`["auto", "low", "medium", "high"]`) used for percentage-based fans. Trade-off: Less precise than native preset modes, but provides consistent UX.

3. **Runtime Capability Check**: Capabilities detected at startup only. Trade-off: If fan entity capabilities change at runtime, requires restart to detect.

4. **Switch Domain Exclusion**: Switch-based fans cannot use speed control. Trade-off: Simpler implementation, but requires users to use fan domain entities for speed control.

#### Testing Patterns

Test fan speed control using these patterns (see `tests/test_fan_mode.py`):

```python
# Test capability detection
async def test_fan_speed_control_preset_modes(hass):
    """Test detection of preset mode capability."""
    # Mock fan entity with preset_modes attribute
    # Verify supports_fan_mode = True
    # Verify fan_modes matches entity preset_modes

# Test state persistence
async def test_fan_mode_persistence(hass):
    """Test fan mode is persisted and restored."""
    # Set fan mode
    # Restart thermostat
    # Verify mode restored from extra_state_attributes

# Test mode application
async def test_fan_mode_applied_on_turn_on(hass):
    """Test fan mode is applied when fan turns on."""
    # Set fan mode
    # Turn on fan
    # Verify correct service call (set_preset_mode or set_percentage)
```

### Development Rules for Claude Code

**CRITICAL - Testing and Linting Workflow:**

1. **Always use Docker scripts** for testing and linting:
   - `./scripts/docker-test` - Run tests (all or specific)
   - `./scripts/docker-lint` - Check all linting
   - `./scripts/docker-lint --fix` - Auto-fix linting issues
   - `./scripts/docker-shell` - Interactive debugging

2. **Before submitting code:**
   - Run `./scripts/docker-lint` to check all linting
   - Run `./scripts/docker-test` to verify tests pass
   - Fix any failures before showing code to user
   - Docker ensures consistent Python 3.13 + HA 2025.1.0+ environment

3. **Library documentation:**
   - Use context7 MCP tools for library/API documentation when needed
   - Automatically resolve library IDs and get docs without explicit user request

**Why Docker scripts are mandatory for Claude Code:**
- Consistent environment across all development sessions
- No local Python dependency conflicts
- Same environment as CI/CD pipeline
- Automatic dependency installation and caching

## Active Technologies
- Python 3.13 + Home Assistant 2025.1.0+, voluptuous (schema validation) (002-separate-tolerances)
- Home Assistant config entries (persistent JSON storage) (002-separate-tolerances)
- Python 3.13 + Home Assistant 2025.1.0+, Home Assistant Template Engine (homeassistant.helpers.template), voluptuous (schema validation) (004-template-based-presets)

## Recent Changes
- 002-separate-tolerances: Added Python 3.13 + Home Assistant 2025.1.0+, voluptuous (schema validation)
- Added Docker-based development workflow with support for testing multiple HA versions

## Development Environment Options

This repository supports **two development approaches**:

1. **Docker Compose Workflow** (Recommended for CI/CD and version testing)
   - Standalone Docker setup without VS Code
   - Easy testing with different Home Assistant versions
   - Ideal for running tests, linting, and CI/CD pipelines
   - See [README-DOCKER.md](README-DOCKER.md) for complete guide
   - Commands: `./scripts/docker-test`, `./scripts/docker-lint`, `./scripts/docker-shell`

2. **VS Code DevContainer** (Recommended for interactive development)
   - Integrated development experience in VS Code
   - Automatic environment setup
   - Full IDE features (debugging, IntelliSense, etc.)
   - Opens directly in container for seamless development

**Both approaches provide:**
- Python 3.13
- Home Assistant 2025.1.0+
- All development dependencies
- Consistent environment across machines

**Choose based on your workflow:**
- Use **Docker Compose** for testing, CI/CD, and multi-version testing
- Use **DevContainer** for daily development with VS Code
- Both can be used together for different tasks
- The core config registry is actually stored at config/.storage/core.config_entries. Useful to test/debug config flow issues

## Releases

While writing releases, focus on user value and key changes. Avoid technical jargon unless necessary.


================================================
FILE: Dockerfile.dev
================================================
# Development Dockerfile for Dual Smart Thermostat Integration
# This image is used for testing, linting, and development outside of VS Code devcontainer.
#
# Build with specific Home Assistant version:
#   docker build -f Dockerfile.dev --build-arg HA_VERSION=2025.1.0 -t dual-thermostat:dev .
#
# Build with latest Home Assistant:
#   docker build -f Dockerfile.dev -t dual-thermostat:dev .

ARG PYTHON_VERSION=3.14
FROM python:${PYTHON_VERSION}-slim-bookworm

# Metadata
LABEL maintainer="Dual Smart Thermostat Contributors"
LABEL description="Development environment for Dual Smart Thermostat Home Assistant integration"

# Build arguments
ARG HA_VERSION=2026.3.2
ARG DEBIAN_FRONTEND=noninteractive

# Environment variables
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    TERM=xterm-256color

# Install system dependencies required by Home Assistant and this integration
# - libpcap0.8, libpcap-dev: Packet capture (for network integrations)
# - ffmpeg: Media processing
# - libturbojpeg0, libjpeg-turbo-progs: JPEG processing
# - git: For pre-commit hooks and version control
# - build-essential: For building Python C extensions
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpcap0.8 \
    libpcap0.8-dev \
    libpcap-dev \
    ffmpeg \
    libturbojpeg0 \
    libjpeg-turbo-progs \
    git \
    build-essential \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Create working directory
WORKDIR /workspace

# Copy requirements files first (for Docker layer caching)
COPY requirements.txt requirements-dev.txt ./

# Install Python dependencies
# If a specific HA version is requested, install it explicitly
RUN pip install --upgrade pip setuptools wheel && \
    if [ "${HA_VERSION}" != "latest" ]; then \
        pip install "homeassistant==${HA_VERSION}"; \
    fi && \
    pip install -r requirements-dev.txt

# Attempt to install pypcap (best effort - may fail on Python 3.13)
# This is not critical for most integration functionality
RUN pip install --no-binary :all: pypcap || \
    echo "Warning: pypcap installation failed. This is expected on Python 3.13. Network discovery features may be limited."

# Copy the entire project
COPY . .

# Create config directory for Home Assistant
RUN mkdir -p /config

# Set Python path to include custom_components
ENV PYTHONPATH=/workspace

# Default command: run bash (can be overridden in docker-compose.yml or command line)
CMD ["/bin/bash"]

# Health check (optional - checks if Python is responsive)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD python3 -c "import sys; sys.exit(0)" || exit 1


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: LICENSE.md
================================================
# Copyright 2020 Miklos Szanyi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README-DOCKER.md
================================================
# Docker-Based Development Workflow

This guide explains how to use Docker for development, testing, and linting without opening the VS Code devcontainer. This approach is ideal for CI/CD, testing with different Home Assistant versions, or working outside of VS Code.

## Overview

The Docker-based workflow provides:
- **Isolated environment** - Consistent development environment across all systems
- **Version flexibility** - Test with different Home Assistant versions easily
- **No VS Code required** - Run commands and view logs from your terminal
- **Fast iteration** - Volume mounts for live code reloading
- **CI/CD ready** - Same environment used locally and in CI/CD pipelines

## Prerequisites

- Docker Desktop or Docker Engine installed
- Docker Compose (included with Docker Desktop)
- Basic familiarity with Docker concepts

## Quick Start

### 1. Build the Development Image

```bash
# Build with default Home Assistant version (2025.1.0)
docker-compose build dev

# Or build with a specific version
HA_VERSION=2025.2.0 docker-compose build dev
```

### 2. Run Tests

```bash
# Run all tests
./scripts/docker-test

# Run specific test file
./scripts/docker-test tests/test_heater_mode.py

# Run tests matching a pattern
./scripts/docker-test -k "test_heating"

# Run with coverage report
./scripts/docker-test --cov
```

### 3. Run Linting

```bash
# Check all linting rules (isort, black, flake8, codespell, ruff)
./scripts/docker-lint

# Auto-fix issues where possible
./scripts/docker-lint --fix
```

### 4. Interactive Shell

```bash
# Open bash shell in container
./scripts/docker-shell

# Open Python REPL
./scripts/docker-shell python
```

## Detailed Usage

### Building with Different Home Assistant Versions

You can test your integration with different Home Assistant versions by setting the `HA_VERSION` build argument:

```bash
# Test with HA 2025.1.0
HA_VERSION=2025.1.0 docker-compose build dev

# Test with HA 2025.2.0
HA_VERSION=2025.2.0 docker-compose build dev

# Test with latest HA (whatever is currently published)
HA_VERSION=latest docker-compose build dev
```

After building with a specific version, all commands (`docker-test`, `docker-lint`, etc.) will use that version until you rebuild.

### Running Tests

The `docker-test` script is a wrapper around `pytest` that runs in the Docker container:

```bash
# Run all tests
./scripts/docker-test

# Run specific test directory
./scripts/docker-test tests/config_flow/

# Run specific test file
./scripts/docker-test tests/test_heater_mode.py

# Run specific test function
./scripts/docker-test tests/test_heater_mode.py::test_heater_mode_on

# Run tests matching pattern
./scripts/docker-test -k "heater"

# Run with verbose output
./scripts/docker-test -v

# Run with debug logging
./scripts/docker-test --log-cli-level=DEBUG

# Run with coverage report
./scripts/docker-test --cov

# Generate HTML coverage report
./scripts/docker-test --cov --cov-report=html
```

### Running Linting

The `docker-lint` script runs all linting checks required before committing:

```bash
# Check all linting rules
./scripts/docker-lint

# Auto-fix issues (isort, black, ruff)
./scripts/docker-lint --fix
```

The linting checks include:
- **isort** - Import sorting
- **black** - Code formatting (88 character line length)
- **flake8** - Style/linting
- **codespell** - Spell checking
- **ruff** - Modern Python linter

### Interactive Development

Open an interactive shell in the container for manual testing and debugging:

```bash
# Open bash shell
./scripts/docker-shell

# Inside the container, you can run any command:
pytest tests/test_heater_mode.py -v
python -m pytest --collect-only
hass --version
```

### Direct Docker Compose Commands

You can also use `docker-compose` directly for more control:

```bash
# Run any command in the dev container
docker-compose run --rm dev <command>

# Examples:
docker-compose run --rm dev pytest
docker-compose run --rm dev black .
docker-compose run --rm dev python -c "import homeassistant; print(homeassistant.__version__)"

# Keep container running in background
docker-compose up -d dev

# View logs
docker-compose logs -f dev

# Stop containers
docker-compose down
```

## Configuration Directory Mounting

### Important: `/config` Folder for Home Assistant

The Docker setup properly mounts the `./config` directory to `/config` inside the container. This is **required** for Home Assistant to function correctly:

```yaml
# In docker-compose.yml
volumes:
  - .:/workspace:rw          # Source code (read-write)
  - ./config:/config:rw      # HA config directory (read-write)
```

**What this means:**
- Home Assistant stores its configuration in `/config`
- The `./config` directory in your project root is mounted to `/config` in the container
- Any changes in the container's `/config` are reflected in your local `./config` folder
- Scripts like `scripts/develop` that run Home Assistant will use this config directory

**First-time setup:**
The `./config` directory will be created automatically when you first run Home Assistant in the container. If you need to initialize it manually:

```bash
./scripts/docker-shell
# Inside container:
mkdir -p /config
hass --script ensure_config -c /config
```

### Running Home Assistant Development Server

To run a full Home Assistant instance with your integration:

```bash
# Open shell in container
./scripts/docker-shell

# Inside container, run the development server
bash scripts/develop
```

Or run directly:

```bash
docker-compose run --rm -p 8123:8123 dev bash scripts/develop
```

This will:
1. Create `/config` if it doesn't exist
2. Initialize Home Assistant configuration
3. Start Home Assistant on port 8123
4. Mount your integration at `/workspace`

Access Home Assistant at http://localhost:8123

### Optional: Full Home Assistant Service

If you want to run a complete Home Assistant instance alongside your development container, uncomment the `homeassistant` service in `docker-compose.yml`:

```yaml
homeassistant:
  image: ghcr.io/home-assistant/home-assistant:${HA_VERSION:-2025.1}
  container_name: dual_thermostat_homeassistant
  volumes:
    - ./config:/config:rw
    - ./custom_components/dual_smart_thermostat:/config/custom_components/dual_smart_thermostat:ro
  ports:
    - "8123:8123"
  environment:
    - TZ=UTC
  restart: unless-stopped
```

Then run:

```bash
# Start Home Assistant service
docker-compose up -d homeassistant

# View logs
docker-compose logs -f homeassistant

# Stop service
docker-compose down
```

## Volume Mounts and Caching

The Docker setup uses several volume mounts for performance and convenience:

### Source Code Mounting
```yaml
- .:/workspace:rw
```
Your source code is mounted as read-write, so changes you make locally are immediately reflected in the container (no rebuild needed).

### Config Directory
```yaml
- ./config:/config:rw
```
Home Assistant configuration directory, shared between your local system and the container.

### Cache Volumes
```yaml
- pip-cache:/root/.cache/pip        # Speeds up pip installs
- pytest-cache:/workspace/.pytest_cache  # Speeds up pytest
- mypy-cache:/workspace/.mypy_cache      # Speeds up mypy
```

These named volumes persist between container runs, making subsequent test/lint runs faster.

## Troubleshooting

### Build Issues

**Problem:** Build fails with dependency errors

```bash
# Clean build (no cache)
docker-compose build --no-cache dev

# Check which HA version is installed
docker-compose run --rm dev python -c "import homeassistant; print(homeassistant.__version__)"
```

**Problem:** `pypcap` installation fails

This is expected on Python 3.13 and is not critical for most integration functionality. The build will continue with a warning.

### Test Issues

**Problem:** Tests fail due to import errors

```bash
# Verify Python path
docker-compose run --rm dev python -c "import sys; print(sys.path)"

# Verify custom_components is accessible
docker-compose run --rm dev ls -la custom_components/
```

**Problem:** Tests are slow

Ensure you've built the image (don't use `--build` on every run):

```bash
# Bad (rebuilds every time):
docker-compose run --build dev pytest

# Good (reuses built image):
docker-compose run --rm dev pytest
```

### Permission Issues

**Problem:** Permission denied errors on Linux

Docker Desktop on macOS/Windows handles permissions automatically. On Linux, you may need to adjust the `Dockerfile.dev` to use a non-root user matching your host UID/GID.

### Config Directory Issues

**Problem:** Home Assistant can't find configuration

Ensure the config directory is properly mounted:

```bash
# Check mount inside container
docker-compose run --rm dev ls -la /config

# Check local directory exists
ls -la config/
```

**Problem:** Config changes aren't persisting

Verify the mount is read-write (`:rw`) in `docker-compose.yml`.

### Image Size Issues

**Problem:** Docker image is too large

The development image includes all testing/linting dependencies and can be 1-2GB. To reduce size:

1. Use `.dockerignore` to exclude unnecessary files (already configured)
2. Use multi-stage builds (future improvement)
3. Prune old images: `docker system prune -a`

## Comparison: Docker vs DevContainer

| Feature | Docker (this setup) | DevContainer |
|---------|-------------------|-------------|
| **IDE Required** | No | Yes (VS Code) |
| **Version Testing** | Easy (build args) | Harder (edit .devcontainer.json) |
| **CI/CD** | Perfect | Not designed for CI/CD |
| **Logs/Commands** | Terminal-based | VS Code integrated |
| **Setup Time** | Fast (one build) | Slower (VS Code startup) |
| **Interactive Dev** | Via `docker-shell` | Native VS Code experience |

**Use Docker when:**
- Running CI/CD pipelines
- Testing with multiple HA versions
- Working without VS Code
- Automating tests/linting

**Use DevContainer when:**
- Doing interactive development in VS Code
- Want IDE integration (debugging, IntelliSense)
- Prefer GUI tools over terminal

**Both approaches work together** - use DevContainer for daily development and Docker for testing/CI/CD.

## Advanced Usage

### Custom Python Versions

```bash
# Build with Python 3.12
PYTHON_VERSION=3.12 docker-compose build dev
```

### Multiple Versions in Parallel

Test with multiple HA versions simultaneously:

```bash
# Terminal 1: Test with HA 2025.1.0
HA_VERSION=2025.1.0 docker-compose build dev
./scripts/docker-test

# Terminal 2: Test with HA 2025.2.0
HA_VERSION=2025.2.0 docker-compose build dev
./scripts/docker-test
```

### Pre-commit Hooks in Docker

Run pre-commit hooks using Docker:

```bash
docker-compose run --rm dev pre-commit run --all-files
```

### Running Security Scans

```bash
# Run bandit security scanner
docker-compose run --rm dev bandit -r custom_components/

# Run safety checker
docker-compose run --rm dev safety check

# Run pip-audit
docker-compose run --rm dev pip-audit
```

## Integration with CI/CD

### GitHub Actions Example

```yaml
name: Docker Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        ha-version: ['2025.1.0', '2025.2.0']
    steps:
      - uses: actions/checkout@v3
      - name: Build Docker image
        run: |
          HA_VERSION=${{ matrix.ha-version }} docker-compose build dev
      - name: Run tests
        run: ./scripts/docker-test --cov
      - name: Run linting
        run: ./scripts/docker-lint
```

## File Structure

```
dual_smart_thermostat/
├── Dockerfile.dev              # Development Docker image
├── docker-compose.yml          # Docker Compose configuration
├── .dockerignore              # Files excluded from Docker builds
├── config/                    # Home Assistant config directory (auto-created)
├── scripts/
│   ├── docker-test           # Test runner script
│   ├── docker-lint           # Linting script
│   └── docker-shell          # Interactive shell script
└── README-DOCKER.md          # This file
```

## Additional Resources

- [Home Assistant Developer Docs](https://developers.home-assistant.io/)
- [Docker Compose Documentation](https://docs.docker.com/compose/)
- [pytest Documentation](https://docs.pytest.org/)
- [Project CLAUDE.md](./CLAUDE.md) - Development guidelines

## Getting Help

If you encounter issues:

1. Check this README's Troubleshooting section
2. Verify your Docker installation: `docker --version && docker-compose --version`
3. Rebuild from scratch: `docker-compose build --no-cache dev`
4. Check Docker logs: `docker-compose logs dev`
5. Open an issue on GitHub with:
   - Your OS and Docker version
   - The command you ran
   - Full error output
   - Output of `docker-compose config`


================================================
FILE: README.md
================================================
# Home Assistant Dual Smart Thermostat component


The `dual_smart_thermostat` is an enhanced version of generic thermostat implemented in Home Assistant.

[![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=for-the-badge)](https://github.com/swingerman/ha-dual-smart-thermostat) ![Release](https://img.shields.io/github/v/release/swingerman/ha-dual-smart-thermostat?style=for-the-badge) [![Python tests](https://img.shields.io/github/actions/workflow/status/swingerman/ha-dual-smart-thermostat/tests.yaml?style=for-the-badge&label=tests)](https://github.com/swingerman/ha-dual-smart-thermostat/actions/workflows/tests.yaml) [![Coverage](https://img.shields.io/sonar/coverage/swingerman_ha-dual-smart-thermostat?server=https%3A%2F%2Fsonarcloud.io&style=for-the-badge)](https://sonarcloud.io/dashboard?id=swingerman_ha-dual-smart-thermostat) [![Donate](https://img.shields.io/badge/Donate-PayPal-yellowgreen?style=for-the-badge&logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=S6NC9BYVDDJMA&source=url)


[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=swingerman&repository=ha-dual-smart-thermostat&category=Integration)


## Table of contents

- [Features](#features)
- [Examples](#examples)
- [Services](#services)
- [Configuration variables](#configuration-variables)
- [Troubleshooting](#troubleshooting)
- [Installation](#installation)

## Features

| Feature | Icon | Documentation |
| :--- | :---: | :---: |
| **Heater/Cooler Mode (Heat-Cool)** | ![cooler](docs/images/sun-snowflake-custom.png) | [docs](#heatcool-mode) |
| **Heater Only Mode** | ![heating](/docs/images/fire-custom.png) | [docs](#heater-only-mode) |
| **Cooler Only Mode** | ![cool](/docs/images/snowflake-custom.png) | [docs](#cooler-only-mode) |
| **Two Stage (AUX) Heating Mode** | ![heating](/docs/images/fire-custom.png) ![heating](/docs/images/radiator-custom.png) | [docs](#two-stage-heating) |
| **Fan Only Mode** | ![fan](/docs/images/fan-custom.png) | [docs](#fan-only-mode) |
| **Fan With Cooler Mode** | ![fan](/docs/images/fan-custom.png)  ![cool](/docs/images/snowflake-custom.png) | [docs](#fan-with-cooler-mode) |
| **Fan Speed Control** | ![fan](/docs/images/fan-custom.png) | [docs](#fan-speed-control) |
| **Dry Mode (Humidity Control)** | ![humidity](docs/images/water-percent-custom.png) | [docs](#dry-mode) |
| **Heat Pump Mode** | ![heat/cool](docs/images/sun-snowflake-custom.png) | [docs](#heat-pump-one-switch-heatcool-mode) |
| **Floor Temperature Control** | ![heating-coil](docs/images/heating-coil-custom.png) ![snowflake-thermometer](docs/images/snowflake-thermometer-custom.png)  ![thermometer-alert](docs/images/thermometer-alert-custom.png) | [docs](#floor-heating-temperature-control) |
| **Window/Door Sensor Integration (Openings)** | ![window-open](docs/images/window-open-custom.png)  ![window-open](docs/images/door-open-custom.png) ![chevron-right](docs/images/chevron-right-custom.png) ![timer-cog](docs/images/timer-cog-outline-custom.png)  ![chevron-right](docs/images/chevron-right-custom.png) ![hvac-off](docs/images/hvac-off-custom.png)| [docs](#openings) |
| **Preset Modes Support** |  | [docs](#presets) |
| **Auto Mode (Priority Engine)** | | [docs](#auto-mode) |
| **HVAC Action Reason Tracking** | | [docs](#hvac-action-reason) |

## Examples

Looking for ready-to-use configurations? Check out our **[examples directory](examples/)** with:

- **[Basic Configurations](examples/basic_configurations/)** - Simple setups for heater-only, cooler-only, heat pumps, and dual-mode systems
- **[Advanced Features](examples/advanced_features/)** - Floor heating limits, two-stage heating, opening detection, and presets
- **[Integration Patterns](examples/integrations/)** - Smart scheduling and automation examples
- **[Single-Mode Thermostat Wrapper](examples/single_mode_wrapper/)** - Create Nest-like "Keep Between" functionality on single-mode thermostats

Each example includes complete YAML configurations with detailed explanations, troubleshooting tips, and best practices.

## Heat/Cool Mode

If both [`heater`](#heater) and [`cooler`](#cooler) entities configured. The thermostat can control heating and cooling and you are able to set min/max low and min/max high temperatures.
In this mode you can turn the thermostat to heat only, cooler only and back to heat/cool mode.

## Heat/Cool With Fan Mode

If the [`fan`](#fan) entity is set the thermostat can control the fan mode of the AC. The fan will turn on when the temperature is above the target temperature and the fan_hot_tolerance is not reached. If the temperature is above the target temperature and the fan_hot_tolerance is reached the AC will turn on.

[all features ⤴️](#features)

## Heater Only Mode

If only the [`heater`](#heater) entity is set the thermostat works only in heater mode.

[all features ⤴️](#features)

## Two Stage (AUX) Heating

Two stage or AUX heating can be enabled by adding the [required configuration](#two-stage-heating-example) entities: [`secondary_heater`](#secondary_heater), [`secondary heater_timeout`](#secondary_heater_timeout). If these are set the feature will enable automatically.
Optionally you can set [`secondary heater_dual_mode`](#secondar_heater_dual_mode) to `true` to turn on the secondary heater together with the primary heater.

### How Two Stage Heating Works?

If the timeout ends and the [`heater`](#heater) was on for the whole time, the thermostat switches to the [`secondary heater`](#secondary_heater). In this case, the primary heater ([`heater`](#heater)) will be turned off. This will be remembered for the day it turned on, and in the next heating cycle, the [`secondary heater`](#secondary_heater) will turn on automatically.
On the following day the primary heater will turn on again, and the second stage will again only turn on after a timeout.
If the third [`secondary heater_dual_mode`](#secondar_heater_dual_mode) is set to `true`, the secondary heater will be turned on together with the primary heater.

### Two Stage Heating Example

```yaml
secondary_heater: switch.study_secondary_heater   # <-- required
secondary_heater_timeout: 00:00:30                 # <-- required
secondary_heater_timeout: true                   # <-- optional
```

## Fan Only Mode

If the [`fan_mode`](#fan_mode) entity is set to true the thermostat works only in fan mode. The heater entity will be treated as a fan only device.

### Fan Only Mode Example

```yaml
heater: switch.study_heater
fan_mode: true
```

## Fan With Cooler Mode

If the [`ac_mode`](#ac_mode) is set to true and the [`fan`](#fan) entity is also set, the heater entity will be treated as a cooler (AC) device with an additional fan device. This will allow not only the use of a separate physical fan device but also turning on the fan mode of an AC using advanced switches.
With this setup, you can use your AC's fan mode more easily.

### Fan With Cooler Mode Example

```yaml
heater: switch.study_heater
ac_mode: true
fan: switch.study_fan
```
#### Fan Hot Tolerance

If you also set the [`fan_hot_tolerance`](#fan_hot_tolerance) the fan will turn on when the temperature is above the target temperature and the fan_hot_tolerance is not reached. If the temperature is above the target temperature and the fan_hot_tolerance is reached the AC will turn on.

##### Cooler With Auto Fan Mode Example

```yaml
heater: switch.study_heater
ac_mode: true
fan: switch.study_fan
fan_hot_tolerance: 0.5
```

#### Outside Temperature And Fan Hot Tolerance

If you set the [`fan_hot_tolerance`](#fan_hot_tolerance), [`outside_sensor`](#outside_sensor)  and the [`fan_air_outside`](#fan_air_outside) the fan will turn on only if the outside temperature is colder than the inside temperature and the fan_hot_tolerance is not reached. If the outside temperature is colder than the inside temperature and the fan_hot_tolerance is reached the AC will turn on.

## Fan Speed Control

The `dual_smart_thermostat` automatically detects and enables fan speed control when you configure a `fan` entity that supports speed capabilities. This allows you to control your HVAC fan speeds (low, medium, high, auto) directly from the thermostat interface, just like built-in thermostats.

### Automatic Detection

The thermostat automatically detects whether your fan entity supports speed control based on its capabilities:

- **Native fan entities** (`fan` domain) with `preset_mode` or `percentage` attributes → Fan speed control enabled automatically
- **Switch entities** (`switch` domain) → Traditional on/off control (backward compatible)

**No configuration changes required** - the thermostat detects capabilities at runtime.

### Fan Speed Control Example

```yaml
climate:
  - platform: dual_smart_thermostat
    name: My Thermostat
    heater: switch.study_heater
    fan: fan.hvac_fan  # Native fan entity - speeds automatically detected
    target_sensor: sensor.study_temperature
```

With this configuration, you'll see fan speed controls in the thermostat UI allowing you to select speeds like "auto", "low", "medium", "high" depending on what your fan entity supports.

### Backward Compatibility

Existing configurations using switch entities continue working unchanged:

```yaml
climate:
  - platform: dual_smart_thermostat
    name: My Thermostat
    heater: switch.study_heater
    fan: switch.fan_relay  # Switch entity - on/off only (no speed control)
    target_sensor: sensor.study_temperature
```

### Integration with Existing Features

Fan speed control works seamlessly with all existing fan-related features:

- **FAN_ONLY Mode**: Fan runs at selected speed in fan-only mode
- **Fan with AC** (`fan_on_with_ac`): Fan runs at selected speed when AC is active
- **Fan Hot Tolerance**: Fan activates at selected speed when temperature tolerance is exceeded
- **Heat Pump Mode**: Fan speed applies to both heating and cooling operations

Your fan speed selection persists across heating/cooling cycles and restarts.

### Upgrading Switch-Based Fans

If you currently use a `switch` entity for your fan but want speed control, you can create a template fan entity that wraps your switch. Here are several examples:

#### Template Fan with Input Select (Preset Modes)

This example uses an input_select helper to provide speed presets:

```yaml
# Helper for fan speed selection
input_select:
  hvac_fan_speed:
    name: HVAC Fan Speed
    options:
      - "auto"
      - "low"
      - "medium"
      - "high"
    initial: "auto"

# Template fan wrapping switch + speed control
fan:
  - platform: template
    fans:
      hvac_fan:
        friendly_name: "HVAC Fan"
        value_template: "{{ is_state('switch.fan_relay', 'on') }}"
        preset_mode_template: "{{ states('input_select.hvac_fan_speed') }}"
        preset_modes:
          - "auto"
          - "low"
          - "medium"
          - "high"
        turn_on:
          service: switch.turn_on
          target:
            entity_id: switch.fan_relay
        turn_off:
          service: switch.turn_off
          target:
            entity_id: switch.fan_relay
        set_preset_mode:
          service: input_select.select_option
          target:
            entity_id: input_select.hvac_fan_speed
          data:
            option: "{{ preset_mode }}"

# Use in thermostat
climate:
  - platform: dual_smart_thermostat
    name: My Thermostat
    heater: switch.study_heater
    fan: fan.hvac_fan  # Uses template fan with speed control
    target_sensor: sensor.study_temperature
```

#### Template Fan with Percentage Control

This example uses an input_number helper for percentage-based speed control:

```yaml
# Helper for fan percentage
input_number:
  hvac_fan_speed:
    name: HVAC Fan Speed
    min: 0
    max: 100
    step: 1
    unit_of_measurement: "%"

# Template fan with percentage support
fan:
  - platform: template
    fans:
      hvac_fan:
        friendly_name: "HVAC Fan"
        value_template: "{{ is_state('switch.fan_relay', 'on') }}"
        percentage_template: "{{ states('input_number.hvac_fan_speed') | int }}"
        turn_on:
          service: switch.turn_on
          target:
            entity_id: switch.fan_relay
        turn_off:
          service: switch.turn_off
          target:
            entity_id: switch.fan_relay
        set_percentage:
          - service: input_number.set_value
            target:
              entity_id: input_number.hvac_fan_speed
            data:
              value: "{{ percentage }}"

# Use in thermostat
climate:
  - platform: dual_smart_thermostat
    name: My Thermostat
    heater: switch.study_heater
    fan: fan.hvac_fan  # Percentage-based speed control
    target_sensor: sensor.study_temperature
```

#### Template Fan for IR/RF Controlled Fans

For fans controlled via Broadlink, IR blaster, or RF remote:

```yaml
# Helpers to track state
input_boolean:
  fan_state:
    name: Fan State

input_select:
  hvac_fan_speed:
    name: HVAC Fan Speed
    options:
      - "low"
      - "medium"
      - "high"
    initial: "low"

# Template fan for IR/RF control
fan:
  - platform: template
    fans:
      hvac_fan:
        friendly_name: "HVAC Fan"
        value_template: "{{ is_state('input_boolean.fan_state', 'on') }}"
        preset_mode_template: "{{ states('input_select.hvac_fan_speed') }}"
        preset_modes:
          - "low"
          - "medium"
          - "high"
        turn_on:
          - service: input_boolean.turn_on
            target:
              entity_id: input_boolean.fan_state
          - service: remote.send_command
            target:
              entity_id: remote.living_room
            data:
              command: "fan_on"
        turn_off:
          - service: input_boolean.turn_off
            target:
              entity_id: input_boolean.fan_state
          - service: remote.send_command
            target:
              entity_id: remote.living_room
            data:
              command: "fan_off"
        set_preset_mode:
          - service: input_select.select_option
            target:
              entity_id: input_select.hvac_fan_speed
            data:
              option: "{{ preset_mode }}"
          - service: remote.send_command
            target:
              entity_id: remote.living_room
            data:
              command: "fan_{{ preset_mode }}"

# Use in thermostat
climate:
  - platform: dual_smart_thermostat
    name: My Thermostat
    heater: switch.study_heater
    fan: fan.hvac_fan  # IR/RF controlled with speed support
    target_sensor: sensor.study_temperature
```

**Benefits of Template Fans:**
- Use existing switch hardware without buying new devices
- Add speed control functionality to any fan
- Automatic detection by the thermostat
- Full UI integration with speed controls
- Works with IR/RF remotes, relays, or any controllable device

**Reference:** [Home Assistant Template Fan Documentation](https://www.home-assistant.io/integrations/fan.template/)

[all features ⤴️](#features)

## AC With Fan Switch Support

Some AC systems have independent fan controls to cycle the house air for filtering or humidity control, without using the heating or cooling elements. Central AC systems require the thermostat to turn on both the AC wire ("Y" wire) and the air-handler/fan wire ("G" wire) to activate the AC

This feature lets you do just that.

To use this feature, you need to set the [`heater`](#heater) entity, the [`ac_mode`](#ac_mode), and the [`fan)`](#fan) entity and the [`fan_on_with_ac`](#fan_on_with_ac) to `true`.


### example
```yaml
heater: switch.study_heater
ac_mode: true
fan: switch.study_fan
fan_on_with_ac: true
```

## Cooler Only Mode

If only the [`cooler`](#cooler) entity is set, the thermostat works only in cooling mode.

[all features ⤴️](#features)

## Dry mode

If the [`dryer`](#dryer) entity is set, the thermostat can switch to dry mode. The dryer will turn on when the humidity is above the target humidity and the [`moist_tolerance`](#moist_tolerance) is not reached. If the humidity is above the target humidity and the [`moist_tolerance`](#moist_tolerance) is reached, the dryer will stop.


### Dry Mode Example with cooler

```yaml
heater: switch.study_heater
target_sensor: sensor.study_temperature
ac_mode: true
dryer: switch.study_dryer
humidity_sensor: sensor.study_humidity
moist_tolerance: 5
dry_tolerance: 5
```

### Dryer example in dual mode

```yaml
heater: switch.study_heater
cooler: switch.study_cooler
target_sensor: sensor.study_temperature
dryer: switch.study_dryer
humidity_sensor: sensor.study_humidity
moist_tolerance: 5
dry_tolerance: 5
```

### Heat Pump (one switch heat/cool) mode

This setup allows you to use a single switch for both heating and cooling. To enable this mode, you define only a single switch for the heater and set your heat pump's current state (heating or cooling) as for the [`heat_pump_cooling`](#heat_pump_cooling) attribute. This must be an entity ID of a sensor with a state of `on` or `off`.

The entity can be a Boolean input for manual control or an entity provided by the heat pump.

```yaml
heater: switch.study_heat_pump
target_sensor: sensor.study_temperature
heat_pump_cooling: sensor.study_heat_pump_state
```

#### Heat Pump HVAC Modes

##### Heat-Cool Mode

```yaml
heater: switch.study_heat_pump
target_sensor: sensor.study_temperature
heat_pump_cooling: sensor.study_heat_pump_state
heat_cool_mode: true
```

**heating** _(heat_pump_cooling: false)_:
- heat/cool
- heat
- off

**cooling** _(heat_pump_cooling: true)_:
- heat/cool
- cool
- off

##### Single mode

```yaml
heater: switch.study_heat_pump
target_sensor: sensor.study_temperature
heat_pump_cooling: sensor.study_heat_pump_state
heat_cool_mode: false # <-- or not set
```

**heating** _(heat_pump_cooling: false)_:
- heat
- off

**cooling** _(heat_pump_cooling: true)_:
- cool
- off


## Openings

The `dual_smart_thermostat` can turn off heating or cooling when a window or door is opened and turn it back on when the door or window is closed, saving energy.
The `openings` configuration variable accepts a list of opening entities and opening objects.

### Opening entities and objects

An opening entity is a sensor that can be in two states: `on` or `off`. If the state is `on`, the opening is considered open; if the state is `off`, the opening is considered closed.
The opening object can contain a `timeout` and a `closing_timeout` property that defines the time for which the opening is still considered closed or open, even if the state is `on` or `off`. This is useful if you want to ignore windows that are only open or closed for a short time.

### Openings Scope

The `openings_scope` configuration variable defines the scope of the openings. If set to `all` or not defined, any open openings will turn off the current HVAC device, and it will be in the idle state. If set, only devices that are operating in the defined HVAC modes will be turned off. For example, if set to `heat`, only the heater will be turned off if any of the openings are open.

### Openings Scope Configuration

```yaml
openings_scope: [heat, cool, heat_cool, fan_only, dry]
```

```yaml
openings_scope:
  - heat
  - cool
```

## Openings Configuration

```yaml
# Example configuration.yaml entry
climate:
  - platform: dual_smart_thermostat
    name: Study
    heater: switch.study_heater
    cooler: switch.study_cooler
    openings:
      - binary_sensor.window1
      - entity_id: binary_sensor.window2
        timeout: 00:00:30
      - entity_id: binary_sensor.window3
        timeout: 00:00:30
        closing_timeout: 00:00:15
    openings_scope: [heat, cool]
    target_sensor: sensor.study_temperature
```

[all features ⤴️](#features)

## Floor heating temperature control

The `dual_smart_thermostat` can control the floor heating temperature. The thermostat can turn off if the floor heating reaches the maximum allowed temperature you define in order to protect the floor from overheating and damage.
These limits also can be set in presets.

### Maximum floor temperature

The `dual_smart_thermostat` can turn off if the floor heating reaches the maximum allowed temperature you define in order to protect the floor from overheating and damage.
There is a default value of 28 degrees Celsius as per industry recommendations.
To enable this protection you need to set two variables:

```yaml
floor_sensor: sensor.floor_temp
max_floor_temp: 28
```

#### Set in presets

You can also set the `max_floor_temp` in the presets configuration. This will allow you to set different maximum floor temperatures for different presets.

```yaml
floor_sensor: sensor.floor_temp
max_floor_temp: 28
preset_name:
  max_floor_temp: 25
```

### Minimum floor temperature

The `dual_smart_thermostat` can turn on if the floor temperature reaches the minimum required temperature you define in order to protect the floor from freezing or to keep it on a comfortable temperature.

```yaml
floor_sensor: sensor.floor_temp
min_floor_temp: 5
```

#### Set in presets

You can also set the `min_floor_temp` in the presets configuration. This will allow you to set different minimum floor temperatures for different presets.

```yaml
floor_sensor: sensor.floor_temp
min_floor_temp: 5
preset_name:
  min_floor_temp: 8
```

### Floor Temperature Control Configuration

```yaml
# Example configuration.yaml entry
climate:
  - platform: dual_smart_thermostat
    name: Study
    unique_id: study
    heater: switch.study_heater
    cooler: switch.study_cooler
    target_sensor: sensor.study_temperature
    floor_sensor: sensor.floor_temp
    max_floor_temp: 28
    min_floor_temp: 5
```

[all features ⤴️](#features)

## Presets

Currently supported presets are:

- none
- [home](#home)
- [away](#away)
- [eco](#eco)
- [sleep](#sleep)
- [comfort](#comfort)
- [anti freeze](#anti_freeze)
- [activity](#activity)
- [boost](#boost)

To set presets you need to add entries for them in the configuration file like this:

You have 6 options here:

1. Set the `temperature` for heat, cool or fan-only mode
2. Set the `target_temp_low` and `target_temp_high` for heat_cool mode. If `temperature` is not set but `target_temp_low` and `target_temp_high` are set, the `temperature` will be picked based on hvac mode. For heat mode it will be `target_temp_low` and for cool, fan_only mode it will be `target_temp_high`
3. Set the `humidity` for dry mode
4. Set `min_floor_temp` for floor heating temperature control
5. Set `max_floor_temp` for floor heating temperature control
6. Set all above

### Presets Configuration

```yaml
preset_name:
  temperature: 13
  humidity: 50 # <-- only if dry mode configured
  target_temp_low: 12
  target_temp_high: 14
  min_floor_temp: 5
  max_floor_temp: 28
```

## Auto Mode

When the thermostat is configured with at least two distinct climate capabilities (any of heating, cooling, drying, fan), the integration exposes `auto` as one of its HVAC modes. In Auto Mode the integration picks between HEAT, COOL, DRY, and FAN_ONLY automatically based on the current environment, configured tolerances, and a fixed priority table:

1. **Safety** — floor-temperature limit and window/door openings preempt all other decisions.
2. **Urgent** (2× tolerance) — temperature or humidity beyond 2× the configured tolerance switches the mode immediately, even if a different mode is currently active.
3. **Normal** (1× tolerance) — temperature or humidity beyond the configured tolerance picks the matching mode.
4. **Comfort** — when the room is mildly above target and a fan is configured, run the fan instead of cooling.
5. **Idle** — when all targets are met, stop actuators.

The thermostat continues to report `auto` as its `hvac_mode`; the underlying actuator (heater / cooler / dryer / fan) reflects the chosen sub-mode in `hvac_action`. Mode-flap prevention keeps the chosen sub-mode running until its goal is reached or a higher-priority concern arises.

The active priority is exposed via the `hvac_action_reason` sensor as `auto_priority_temperature`, `auto_priority_humidity`, or `auto_priority_comfort`. See [HVAC Action Reason Auto values](#hvac-action-reason-auto-values).

Auto Mode requires a temperature sensor; the humidity-priority paths additionally require a humidity sensor. Phase 1.3 will add outside-temperature bias; Phase 1.4 will add apparent-temperature support; Phase 2 will add a PID controller option.

## HVAC Action Reason

The `dual_smart_thermostat` tracks **why** the current HVAC action is happening and exposes it in two places:

- **Sensor entity (preferred):** `sensor.<climate_name>_hvac_action_reason` — a diagnostic enum sensor created automatically alongside each climate entity. Use this for automations, templates, and dashboards going forward.
- **State attribute (deprecated):** `hvac_action_reason` on the climate entity. Still populated for backward compatibility; slated for removal in a future major release. Please migrate templates and automations to the sensor entity above.

Both surfaces carry the same raw enum value at all times.

### HVAC Action Reason values

The reason is grouped into three categories:

- [Internal values](#hvac-action-reason-internal-values) — set by the component itself.
- [External values](#hvac-action-reason-external-values) — set by automations or scripts via the `set_hvac_action_reason` service.
- [Auto values](#hvac-action-reason-auto-values) — reserved for Auto Mode (Phase 1 of the Auto Mode roadmap, issue #563). Declared in the sensor's options list but not yet emitted by any controller.

#### HVAC Action Reason Internal values

| Value | Description |
|-------|-------------|
| `none` | No action reason |
| `target_temp_not_reached` | The target temperature has not been reached |
| `target_temp_not_reached_with_fan` | The target temperature has not been reached trying it with a fan |
| `target_temp_reached` | The target temperature has been reached |
| `target_humidity_reached` | The target humidity has been reached |
| `target_humidity_not_reached` | The target humidity has not been reached |
| `misconfiguration` | The thermostat is misconfigured |
| `opening` | The thermostat is idle because an opening is open |
| `limit` | The thermostat is idle because the floor temperature is at the limit |
| `overheat` | The thermostat is idle because the floor temperature is too high |
| `temperature_sensor_stalled` | The thermostat is idle because the temperature sensor is not provided data for the defined time that could indicate a malfunctioning sensor |
| `humidity_sensor_sstalled` | The thermostat is idle because the temperature sensor is not provided data for the defined time that could indicate a malfunctioning sensor |

#### HVAC Action Reason External values

| Value | Description |
|-------|-------------|
| `none` | No action reason |
| `presence`| the last HVAc action was triggered by presence |
| `schedule` | the last HVAc action was triggered by schedule |
| `emergency` | the last HVAc action was triggered by emergency |
| `malfunction` | the last HVAc action was triggered by malfunction |

#### HVAC Action Reason Auto values

> **Reserved.** These values are declared so the sensor's `options` list is stable across Auto Mode development phases. They are **not yet emitted** by any controller. Phase 1 (see issue #563) will wire the priority engine to emit them.

| Value | Description |
|-------|-------------|
| `auto_priority_humidity` | Auto Mode prioritised humidity control (→ DRY) |
| `auto_priority_temperature` | Auto Mode prioritised temperature control (→ HEAT / COOL) |
| `auto_priority_comfort` | Auto Mode chose fan for comfort (→ FAN_ONLY) |

[all features ⤴️](#features)

## Services

### Set HVAC Action Reason

`dial_smart_thermostat.set_hvac_action_reason` is exposed for automations to set the `hvac_action_reason` attribute. The service accepts the following parameters:

| Parameter | Description | Type | Required |
|-----------|-------------|------|----------|
| entity_id | The entity id of the thermostat | string | yes |
| hvac_action_reason | The reason for the current action of the thermostat | [HVACActionReasonExternal](#hvac-action-reason-external-values) | yes |

> The service updates both the deprecated `hvac_action_reason` state attribute and the new `sensor.<climate_name>_hvac_action_reason` entity. Automations reading either surface continue to work.

## Configuration variables

### name

  _(required) (string)_ Name of thermostat

  _default: Dual Smart_

### unique_id

  _(optional) (string)_ the unique id for the thermostat. It allows you to customize it in the UI and to assign the component to an area.

  _default: none

### heater

  _(required) (string)_ "`entity_id` for heater switch, must be a toggle device. Becomes air conditioning switch when `ac_mode` is set to `true`"

### secondary_heater

  _(optional, **required for two stage heating**) (string)_ "`entity_id` for secondary heater switch, must be a toggle device.

### secondary_heater_timeout

  _(optional, **required for two stage heating**) (time, integer)_  Set a minimum amount of time that the switch specified in the _heater_ option must be in its ON state before secondary heater devices needs to be turned on.

### secondary_heater_dual_mode

  _(optional, (bool)_  If set true the secondary (aux) heater will be turned on together with the primary heater.

### cooler

  _(optional) (string)_ "`entity_id` for cooler switch, must be a toggle device."

### fan_mode

  _(optional) (bool)_ If set to `true` the heater entity will be treated as a fan only device.

### fan

  _(optional) (string)_ "`entity_id` for fan switch, must be a toggle device."

### fan_hot_tolerance

  _(optional) (float)_ Temperature range above `hot_tolerance` where the fan is used instead of the AC. This creates an intermediate zone where the fan attempts to cool before engaging the AC.

  **Example:** With target temperature 25°C, `hot_tolerance` 1°C, and `fan_hot_tolerance` 0.5°C:
  - At 26°C (target + hot_tolerance): Fan turns on
  - At 26.5°C (target + hot_tolerance + fan_hot_tolerance): AC turns on (fan turns off)

  This feature helps save energy by using the fan for minor temperature increases before engaging the more power-intensive AC.

  _default: 0.5_

  _requires: `fan`_

### fan_hot_tolerance_toggle

  _(optional) (string)_ `entity_id` for an `input_boolean` or `binary_sensor` that dynamically enables/disables the `fan_hot_tolerance` feature.

  - When the toggle entity is `on` (or not configured): The fan_hot_tolerance feature is active
  - When the toggle entity is `off`: The AC is used immediately when `hot_tolerance` is exceeded (bypasses fan zone)

  Useful for automations that disable fan-first behavior during extreme heat, high humidity, or other conditions where immediate AC is preferred.

  _default: Feature enabled (behaves as if toggle is `on`)_

  _requires: `fan`_

### fan_on_with_ac

  _(optional) (boolean)_ If set to `true` the fan will be turned on together with the AC. This is useful for central AC systems that require the fan to be turned on together with the AC.

  _requires: `fan`_

### fan_air_outside

  _(optional) (boolean)_ "If set to `true` the fan will be turned on only if the outside temperature is colder than the inside temperature and the `fan_hot_tolerance` is not reached. If the outside temperature is colder than the inside temperature and the `fan_hot_tolerance` is reached the AC will turn on."

  _requires: `fan` , `sensor_outside`_


### dryer

  _(optional) (string)_ "`entity_id` for dryer switch, must be a toggle device."

### moist_tolerance

  _(optional) (float)_ Set a minimum amount of difference between the humidity read by the sensor specified in the _humidity_sensor_ option and the target humidity that must change prior to being switched on. For example, if the target humidity is 50 and the tolerance is 5 the dryer will start when the sensor equals or goes below 45.

  _requires: `dryer`, `humidity_sensor`_

### dry_tolerance

  _(optional) (float)_ Set a minimum amount of difference between the humidity read by the sensor specified in the _humidity_sensor_ option and the target humidity that must change prior to being switched off. For example, if the target humidity is 50 and the tolerance is 5 the dryer will stop when the sensor equals or goes above 55.

  _requires: `dryer`, `humidity_sensor`_

### humidity_sensor

  _(optional) (string)_ "`entity_id` for a humidity sensor, humidity_sensor.state must be humidity."

### target_sensor

  _(required) (string)_  "`entity_id` for a temperature sensor, target_sensor.state must be temperature."

### sensor_stale_duration

  _(optional) (timedelta)_  Set a delay for the target sensor to be considered not stalled. If the sensor is not available for the specified time or doesn't get updated the thermostat will be turned off.

  _requires: `target_sensor` and/or `huidity
Download .txt
gitextract_t8ge55s3/

├── .copilot-instructions.md
├── .coveragerc
├── .devcontainer.json
├── .dockerignore
├── .github/
│   ├── DEPENDABOT_AUTO_MERGE.md
│   ├── FUNDING.yml
│   ├── RELEASE_TEMPLATE.md
│   ├── SECURITY_REMEDIATION.md
│   ├── dependabot.yml
│   ├── prompts/
│   │   ├── plan.prompt.md
│   │   ├── specify.prompt.md
│   │   └── tasks.prompt.md
│   ├── release.yml
│   ├── scripts/
│   │   └── update_hacs_manifest.py
│   └── workflows/
│       ├── claude.yml
│       ├── dependabot-auto-merge.yml
│       ├── hacs-validate.yaml
│       ├── linting.yaml
│       ├── quality-check.yaml
│       ├── security-check.yml
│       ├── tests.yaml
│       └── workflow-status.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .specify/
│   ├── memory/
│   │   ├── constitution.md
│   │   └── constitution_update_checklist.md
│   └── templates/
│       ├── agent-file-template.md
│       ├── checklist-template.md
│       ├── plan-template.md
│       ├── spec-template.md
│       └── tasks-template.md
├── CHANGELOG.md
├── CLAUDE.md
├── Dockerfile.dev
├── LICENSE
├── LICENSE.md
├── README-DOCKER.md
├── README.md
├── RELEASE_NOTES_v0.11.0.md
├── action/
│   ├── Dockerfile
│   ├── action.py
│   └── action.yaml
├── build_release.sh
├── config/
│   └── configuration.yaml
├── custom_components/
│   ├── __init__.py
│   └── dual_smart_thermostat/
│       ├── __init__.py
│       ├── climate.py
│       ├── config_flow.py
│       ├── config_validation.py
│       ├── const.py
│       ├── feature_steps/
│       │   ├── __init__.py
│       │   ├── fan.py
│       │   ├── floor.py
│       │   ├── humidity.py
│       │   ├── openings.py
│       │   ├── presets.py
│       │   └── shared.py
│       ├── flow_utils.py
│       ├── hvac_action_reason/
│       │   ├── __init__.py
│       │   ├── hvac_action_reason.py
│       │   ├── hvac_action_reason_auto.py
│       │   ├── hvac_action_reason_external.py
│       │   └── hvac_action_reason_internal.py
│       ├── hvac_controller/
│       │   ├── __init__.py
│       │   ├── cooler_controller.py
│       │   ├── generic_controller.py
│       │   ├── heater_controller.py
│       │   └── hvac_controller.py
│       ├── hvac_device/
│       │   ├── __init__.py
│       │   ├── controllable_hvac_device.py
│       │   ├── cooler_device.py
│       │   ├── cooler_fan_device.py
│       │   ├── dryer_device.py
│       │   ├── fan_device.py
│       │   ├── generic_hvac_device.py
│       │   ├── heat_pump_device.py
│       │   ├── heater_aux_heater_device.py
│       │   ├── heater_cooler_device.py
│       │   ├── heater_device.py
│       │   ├── hvac_device.py
│       │   ├── hvac_device_factory.py
│       │   └── multi_hvac_device.py
│       ├── managers/
│       │   ├── __init__.py
│       │   ├── auto_mode_evaluator.py
│       │   ├── environment_manager.py
│       │   ├── feature_manager.py
│       │   ├── hvac_power_manager.py
│       │   ├── opening_manager.py
│       │   ├── preset_manager.py
│       │   └── state_manager.py
│       ├── manifest.json
│       ├── models.py
│       ├── options_flow.py
│       ├── preset_env/
│       │   ├── __init__.py
│       │   └── preset_env.py
│       ├── schema_utils.py
│       ├── schemas.py
│       ├── sensor.py
│       ├── services.yaml
│       └── translations/
│           ├── en.json
│           └── sk.json
├── demo_openings_translations.py
├── demo_translations.py
├── docker-compose.yml
├── docs/
│   ├── TESTING.md
│   ├── config/
│   │   ├── CONFIG_FLOW.md
│   │   ├── CRITICAL_CONFIG_DEPENDENCIES.md
│   │   ├── DEPENDENCY_ANALYSIS_SUMMARY.md
│   │   └── FOCUSED_DEPENDENCIES_SUMMARY.md
│   ├── config_flow/
│   │   ├── ac_only_features.md
│   │   ├── architecture.md
│   │   └── step_ordering.md
│   ├── plans/
│   │   ├── 2026-01-21-fan-speed-control-design.md
│   │   └── 2026-01-21-fan-speed-control.md
│   ├── superpowers/
│   │   ├── plans/
│   │   │   ├── 2026-04-21-auto-mode-phase-0-action-reason-sensor.md
│   │   │   ├── 2026-04-22-auto-mode-phase-1-1-availability-detection.md
│   │   │   ├── 2026-04-27-auto-mode-phase-1-2-priority-engine.md
│   │   │   ├── 2026-04-29-auto-mode-phase-1-3-outside-bias.md
│   │   │   └── 2026-04-30-auto-mode-phase-1-4-apparent-temp.md
│   │   └── specs/
│   │       ├── 2026-04-21-auto-mode-phase-0-action-reason-sensor-design.md
│   │       ├── 2026-04-22-auto-mode-phase-1-1-availability-detection-design.md
│   │       ├── 2026-04-27-auto-mode-phase-1-2-priority-engine-design.md
│   │       ├── 2026-04-29-auto-mode-phase-1-3-outside-bias-design.md
│   │       └── 2026-04-30-auto-mode-phase-1-4-apparent-temp-design.md
│   └── troubleshooting.md
├── examples/
│   ├── README.md
│   ├── advanced_features/
│   │   ├── floor_heating_with_limits.yaml
│   │   ├── openings_with_timeout.yaml
│   │   ├── presets_advanced.yaml
│   │   ├── presets_with_templates.yaml
│   │   └── two_stage_heating.yaml
│   ├── basic_configurations/
│   │   ├── cooler_only.yaml
│   │   ├── heat_pump.yaml
│   │   ├── heater_cooler.yaml
│   │   └── heater_only.yaml
│   ├── integrations/
│   │   └── smart_scheduling.yaml
│   └── single_mode_wrapper/
│       ├── README.md
│       ├── automation.yaml
│       ├── configuration.yaml
│       └── helpers.yaml
├── hacs.json
├── manage/
│   ├── bump_frontend
│   ├── hacs
│   ├── integration_start
│   ├── lgtm.js
│   ├── update_manifest.py
│   └── update_requirements.py
├── pcap.py
├── pytest.ini
├── pytest.log
├── requirements-dev.txt
├── requirements.txt
├── scripts/
│   ├── devcontainer_install_deps.sh
│   ├── develop
│   ├── docker-lint
│   ├── docker-shell
│   ├── docker-test
│   ├── lint
│   └── setup
├── setup.cfg
├── sonar-project.properties
├── specs/
│   ├── 001-develop-config-and/
│   │   ├── FEATURE_TESTING_PLAN.md
│   │   ├── FEATURE_TESTING_PLAN_EXPANDED.md
│   │   ├── FLOW_SEPARATION_ANALYSIS.md
│   │   ├── GITHUB_ISSUES_UPDATE_PLAN.md
│   │   ├── HOUSEKEEPING.md
│   │   ├── OPTIONS_FLOW_BUG_FIX.md
│   │   ├── RECONFIGURE_FLOW_MIGRATION.md
│   │   ├── REORG.md
│   │   ├── UPDATED_TASKS_STRATEGY.md
│   │   ├── contracts/
│   │   │   └── step-handlers.md
│   │   ├── data-model.md
│   │   ├── github-issues-update.md
│   │   ├── github-sync-status.md
│   │   ├── plan.md
│   │   ├── quickstart.md
│   │   ├── research.md
│   │   ├── schema-consolidation-proposal.md
│   │   ├── spec.md
│   │   ├── tasks.md
│   │   └── test-preservation.md
│   ├── 002-separate-tolerances/
│   │   ├── checklists/
│   │   │   └── requirements.md
│   │   ├── contracts/
│   │   │   └── tolerance_selection_api.md
│   │   ├── data-model.md
│   │   ├── plan.md
│   │   ├── quickstart.md
│   │   ├── research.md
│   │   ├── spec.md
│   │   └── tasks.md
│   ├── 003-separate-tolerances/
│   │   ├── BEHAVIOR_DIAGRAM.md
│   │   ├── IMPLEMENTATION_COMPLETE.md
│   │   └── README.md
│   ├── 004-template-based-presets/
│   │   ├── IMPLEMENTATION_PROGRESS.md
│   │   ├── IMPLEMENTATION_STATUS.md
│   │   ├── PHASE10_COMPLETE.md
│   │   ├── PHASE4_COMPLETE.md
│   │   ├── PHASE5_COMPLETE.md
│   │   ├── PHASE6_COMPLETE.md
│   │   ├── PHASE7_COMPLETE.md
│   │   ├── PHASE9_COMPLETE.md
│   │   ├── analysis-report.md
│   │   ├── checklists/
│   │   │   └── requirements.md
│   │   ├── contracts/
│   │   │   └── preset_env_api.md
│   │   ├── data-model.md
│   │   ├── plan.md
│   │   ├── quickstart.md
│   │   ├── research.md
│   │   ├── spec.md
│   │   └── tasks.md
│   ├── README.md
│   └── issue-096-template-based-presets.md
├── test-results/
│   └── .last-run.json
├── tests/
│   ├── FEATURES.md
│   ├── __init__.py
│   ├── behavioral/
│   │   └── test_tolerance_thresholds.py
│   ├── common.py
│   ├── config_flow/
│   │   ├── __init__.py
│   │   ├── test_ac_only_advanced_settings.py
│   │   ├── test_ac_only_features.py
│   │   ├── test_ac_only_features_integration.py
│   │   ├── test_advanced_options.py
│   │   ├── test_config_flow.py
│   │   ├── test_config_flow_validation.py
│   │   ├── test_e2e_ac_only_persistence.py
│   │   ├── test_e2e_heat_pump_persistence.py
│   │   ├── test_e2e_heater_cooler_persistence.py
│   │   ├── test_e2e_simple_heater_persistence.py
│   │   ├── test_heat_pump_config_flow.py
│   │   ├── test_heat_pump_features_integration.py
│   │   ├── test_heat_pump_options_flow.py
│   │   ├── test_heater_cooler_features_integration.py
│   │   ├── test_heater_cooler_flow.py
│   │   ├── test_integration.py
│   │   ├── test_options_entry_helpers.py
│   │   ├── test_options_flow.py
│   │   ├── test_preset_templates_config_flow.py
│   │   ├── test_reconfigure_flow.py
│   │   ├── test_reconfigure_flow_e2e_ac_only.py
│   │   ├── test_reconfigure_flow_e2e_heat_pump.py
│   │   ├── test_reconfigure_flow_e2e_heater_cooler.py
│   │   ├── test_reconfigure_flow_e2e_simple_heater.py
│   │   ├── test_reconfigure_system_type_change.py
│   │   ├── test_simple_heater_advanced.py
│   │   ├── test_simple_heater_features_integration.py
│   │   ├── test_step_ordering.py
│   │   └── test_translations.py
│   ├── conftest.py
│   ├── const.py
│   ├── contracts/
│   │   ├── GREEN_PHASE_RESULTS.md
│   │   ├── RED_PHASE_RESULTS.md
│   │   ├── __init__.py
│   │   ├── test_feature_availability_contracts.py
│   │   ├── test_feature_ordering_contracts.py
│   │   └── test_feature_schema_contracts.py
│   ├── edge_cases/
│   │   ├── __init__.py
│   │   ├── test_issue_10_tolerance_precision.py
│   │   ├── test_issue_461_redundant_commands.py
│   │   ├── test_issue_467_idle_continuous_off.py
│   │   ├── test_issue_468_precision_rounding.py
│   │   ├── test_issue_469_off_state_control_bypass.py
│   │   ├── test_issue_480_heater_cooler_both_fire.py
│   │   ├── test_issue_484_keep_alive_timedelta.py
│   │   ├── test_issue_499_multiple_thermostats_unavailable.py
│   │   ├── test_issue_499_yaml_entity_unavailable_on_startup.py
│   │   ├── test_issue_506_behavior_tolerance_ignored.py
│   │   ├── test_issue_506_tolerance_in_range_mode.py
│   │   ├── test_issue_506_user_exact_scenario.py
│   │   ├── test_issue_506_yaml_tolerance_defaults.py
│   │   └── test_issue_518_heater_turns_off_prematurely.py
│   ├── features/
│   │   ├── test_ac_features_ux.py
│   │   ├── test_advanced_toggle_feature.py
│   │   ├── test_feature_hvac_mode_interactions.py
│   │   ├── test_heater_cooler_with_fan.py
│   │   ├── test_heater_cooler_with_humidity.py
│   │   ├── test_openings_with_hvac_modes.py
│   │   └── test_presets_with_all_features.py
│   ├── fixtures/
│   │   └── configuration.yaml
│   ├── managers/
│   │   ├── test_environment_manager.py
│   │   ├── test_hvac_device_factory.py
│   │   └── test_preset_manager_templates.py
│   ├── openings/
│   │   ├── test_openings_config_flow.py
│   │   ├── test_openings_multiselect.py
│   │   ├── test_openings_options_flow.py
│   │   └── test_scope_generation.py
│   ├── preset_env/
│   │   └── test_preset_env_templates.py
│   ├── presets/
│   │   ├── test_comprehensive_preset_logic.py
│   │   └── test_preset_form_organization.py
│   ├── test_auto_mode_availability.py
│   ├── test_auto_mode_evaluator.py
│   ├── test_auto_mode_integration.py
│   ├── test_auto_preset_selection.py
│   ├── test_config_flow.py
│   ├── test_cooler_mode.py
│   ├── test_cooler_mode_behavioral.py
│   ├── test_dry_mode.py
│   ├── test_dual_mode.py
│   ├── test_dual_mode_behavioral.py
│   ├── test_environment_manager.py
│   ├── test_fan_mode.py
│   ├── test_fan_speed_control.py
│   ├── test_heat_pump_mode.py
│   ├── test_heat_pump_mode_behavioral.py
│   ├── test_heater_mode.py
│   ├── test_heater_mode_behavioral.py
│   ├── test_hvac_action_reason_sensor.py
│   ├── test_hvac_action_reason_service.py
│   ├── test_init.py
│   ├── test_logger_multiple_instances.py
│   ├── test_presets_schema.py
│   └── unit/
│       ├── test_config_validation_integration.py
│       ├── test_heat_pump_schema.py
│       ├── test_heater_cooler_schema.py
│       ├── test_models.py
│       └── test_schema_utils.py
└── tools/
    ├── README.md
    ├── __init__.py
    ├── clean_db.py
    ├── config_validator.py
    ├── focused_config_dependencies.json
    └── focused_config_dependencies.py
Download .txt
Showing preview only (219K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (1960 symbols across 148 files)

FILE: .github/scripts/update_hacs_manifest.py
  function update_manifest (line 8) | def update_manifest():

FILE: action/action.py
  function error (line 44) | def error(error: str):
  function get_event_data (line 49) | def get_event_data():
  function chose_repository (line 56) | def chose_repository(category):
  function chose_category (line 74) | def chose_category():
  function preflight (line 80) | async def preflight():
  function validate_repository (line 131) | async def validate_repository(repository, category, ref=None):

FILE: custom_components/dual_smart_thermostat/__init__.py
  function async_setup_entry (line 11) | async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> ...
  function config_entry_update_listener (line 18) | async def config_entry_update_listener(hass: HomeAssistant, entry: Confi...
  function async_unload_entry (line 23) | async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) ->...

FILE: custom_components/dual_smart_thermostat/climate.py
  function async_setup_entry (line 285) | async def async_setup_entry(
  function async_setup_platform (line 308) | async def async_setup_platform(
  function _normalize_config_numeric_values (line 322) | def _normalize_config_numeric_values(config: dict[str, Any]) -> dict[str...
  function _async_setup_config (line 394) | async def _async_setup_config(
  class DualSmartThermostat (line 520) | class DualSmartThermostat(ClimateEntity, RestoreEntity):
    method __init__ (line 523) | def __init__(
    method _setup_template_listeners (line 645) | async def _setup_template_listeners(self) -> None:
    method _remove_template_listeners (line 694) | async def _remove_template_listeners(self) -> None:
    method _async_template_entity_changed (line 712) | async def _async_template_entity_changed(
    method async_added_to_hass (line 770) | async def async_added_to_hass(self) -> None:
    method async_will_remove_from_hass (line 984) | async def async_will_remove_from_hass(self) -> None:
    method should_poll (line 1000) | def should_poll(self) -> bool:
    method precision (line 1005) | def precision(self) -> float:
    method target_temperature_step (line 1012) | def target_temperature_step(self) -> float:
    method current_temperature (line 1020) | def current_temperature(self) -> float | None:
    method current_humidity (line 1025) | def current_humidity(self) -> float | None:
    method target_humidity (line 1030) | def target_humidity(self) -> float | None:
    method current_floor_temperature (line 1035) | def current_floor_temperature(self) -> float | None:
    method _compute_attr_hvac_modes (line 1039) | def _compute_attr_hvac_modes(self) -> list[HVACMode]:
    method hvac_mode (line 1052) | def hvac_mode(self) -> HVACMode | None:
    method hvac_action (line 1062) | def hvac_action(self) -> HVACAction:
    method target_temperature (line 1067) | def target_temperature(self) -> float | None:
    method target_temperature_high (line 1072) | def target_temperature_high(self) -> float | None:
    method target_temperature_low (line 1077) | def target_temperature_low(self) -> float | None:
    method min_temp (line 1082) | def min_temp(self) -> float:
    method max_temp (line 1091) | def max_temp(self) -> float:
    method min_humidity (line 1100) | def min_humidity(self) -> float:
    method max_humidity (line 1109) | def max_humidity(self) -> float:
    method fan_mode (line 1118) | def fan_mode(self) -> str | None:
    method fan_modes (line 1129) | def fan_modes(self) -> list[str] | None:
    method extra_state_attributes (line 1136) | def extra_state_attributes(self) -> dict:
    method _set_support_flags (line 1179) | def _set_support_flags(self) -> None:
    method _async_update_sensors_initial_state (line 1188) | async def _async_update_sensors_initial_state(self) -> bool:
    method async_set_hvac_mode (line 1231) | async def async_set_hvac_mode(
    method async_set_temperature (line 1284) | async def async_set_temperature(self, **kwargs) -> None:
    method async_set_humidity (line 1319) | async def async_set_humidity(self, humidity: float) -> None:
    method async_set_fan_mode (line 1332) | async def async_set_fan_mode(self, fan_mode: str) -> None:
    method _set_temperatures_dual_mode (line 1351) | def _set_temperatures_dual_mode(self, temperatures: TargetTemperatures...
    method _async_sensor_changed_event (line 1388) | async def _async_sensor_changed_event(
    method _async_sensor_changed (line 1398) | async def _async_sensor_changed(
    method _async_sensor_not_responding (line 1434) | async def _async_sensor_not_responding(self, now: datetime | None = No...
    method _async_humidity_sensor_not_responding (line 1454) | async def _async_humidity_sensor_not_responding(
    method _async_outside_sensor_not_responding (line 1477) | async def _async_outside_sensor_not_responding(
    method _async_sensor_floor_changed_event (line 1494) | async def _async_sensor_floor_changed_event(
    method _async_sensor_floor_changed (line 1503) | async def _async_sensor_floor_changed(
    method _async_sensor_outside_changed_event (line 1516) | async def _async_sensor_outside_changed_event(
    method _async_sensor_outside_changed (line 1525) | async def _async_sensor_outside_changed(
    method _async_sensor_humidity_changed_event (line 1555) | async def _async_sensor_humidity_changed_event(
    method _async_sensor_humidity_changed (line 1564) | async def _async_sensor_humidity_changed(
    method _async_entity_heat_pump_cooling_changed_event (line 1598) | async def _async_entity_heat_pump_cooling_changed_event(
    method _async_entity_heat_pump_cooling_changed (line 1613) | async def _async_entity_heat_pump_cooling_changed(
    method _check_device_initial_state (line 1623) | async def _check_device_initial_state(self) -> None:
    method _async_opening_changed (line 1632) | async def _async_opening_changed(self, event: Event[EventStateChangedD...
    method _async_control_climate (line 1670) | async def _async_control_climate(self, time=None, force=False) -> None:
    method _async_control_climate_forced (line 1693) | async def _async_control_climate_forced(self, time=None) -> None:
    method _async_control_climate_no_time (line 1700) | async def _async_control_climate_no_time(self, time=None, force=False)...
    method _async_evaluate_auto_and_dispatch (line 1704) | async def _async_evaluate_auto_and_dispatch(
    method _async_hvac_mode_changed (line 1751) | def _async_hvac_mode_changed(self, hvac_mode) -> None:
    method _async_switch_changed_event (line 1759) | def _async_switch_changed_event(self, event: Event[EventStateChangedDa...
    method _async_switch_changed (line 1766) | def _async_switch_changed(
    method _resume_from_state (line 1783) | def _resume_from_state(self, old_state: State, new_state: State) -> None:
    method _is_device_active (line 1808) | def _is_device_active(self) -> bool:
    method async_set_preset_mode (line 1812) | async def async_set_preset_mode(self, preset_mode: str) -> None:
    method _publish_hvac_action_reason (line 1848) | def _publish_hvac_action_reason(self, reason) -> None:
    method _set_hvac_action_reason (line 1869) | def _set_hvac_action_reason(self, *args) -> None:
    method _check_auto_preset_selection (line 1884) | async def _check_auto_preset_selection(self) -> None:
    method async_turn_on (line 1897) | async def async_turn_on(self) -> None:
    method async_turn_off (line 1922) | async def async_turn_off(self) -> None:

FILE: custom_components/dual_smart_thermostat/config_flow.py
  class ConfigFlowHandler (line 66) | class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
    method __init__ (line 72) | def __init__(self) -> None:
    method _clean_config_for_storage (line 84) | def _clean_config_for_storage(self, config: dict[str, Any]) -> dict[st...
    method _normalize_config_from_storage (line 121) | def _normalize_config_from_storage(self, config: dict[str, Any]) -> di...
    method async_step_user (line 159) | async def async_step_user(
    method async_step_reconfigure (line 180) | async def async_step_reconfigure(
    method async_step_reconfigure_confirm (line 224) | async def async_step_reconfigure_confirm(
    method _async_step_system_config (line 276) | async def _async_step_system_config(self) -> FlowResult:
    method async_step_basic (line 296) | async def async_step_basic(
    method async_step_basic_ac_only (line 343) | async def async_step_basic_ac_only(
    method async_step_features (line 374) | async def async_step_features(
    method async_step_heater_cooler (line 414) | async def async_step_heater_cooler(
    method async_step_heat_pump (line 452) | async def async_step_heat_pump(
    method async_step_dual_stage (line 487) | async def async_step_dual_stage(
    method async_step_dual_stage_config (line 515) | async def async_step_dual_stage_config(
    method async_step_floor_heating (line 540) | async def async_step_floor_heating(
    method async_step_openings_toggle (line 554) | async def async_step_openings_toggle(
    method async_step_fan_toggle (line 562) | async def async_step_fan_toggle(
    method async_step_humidity_toggle (line 570) | async def async_step_humidity_toggle(
    method async_step_floor_config (line 578) | async def async_step_floor_config(
    method async_step_openings_selection (line 586) | async def async_step_openings_selection(
    method async_step_openings_config (line 594) | async def async_step_openings_config(
    method async_step_heat_cool_mode (line 602) | async def async_step_heat_cool_mode(
    method async_step_fan (line 615) | async def async_step_fan(
    method async_step_humidity (line 628) | async def async_step_humidity(
    method async_step_additional_sensors (line 641) | async def async_step_additional_sensors(
    method async_step_preset_selection (line 654) | async def async_step_preset_selection(
    method async_step_presets (line 698) | async def async_step_presets(
    method _validate_basic_config (line 706) | async def _validate_basic_config(self, user_input: dict[str, Any]) -> ...
    method _determine_next_step (line 710) | async def _determine_next_step(self) -> FlowResult:
    method _async_finish_flow (line 817) | async def _async_finish_flow(self) -> FlowResult:
    method _detect_configured_features (line 857) | def _detect_configured_features(self) -> dict[str, Any]:
    method _clear_unchecked_features (line 892) | def _clear_unchecked_features(self, user_input: dict[str, Any]) -> None:
    method _has_both_heating_and_cooling (line 942) | def _has_both_heating_and_cooling(self) -> bool:
    method async_config_entry_title (line 952) | def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
    method async_get_options_flow (line 958) | def async_get_options_flow(config_entry):
    method async_step_import (line 964) | async def async_step_import(self, import_config: dict[str, Any]) -> Fl...

FILE: custom_components/dual_smart_thermostat/config_validation.py
  function _duration_to_seconds (line 36) | def _duration_to_seconds(value: Any) -> int:
  function validate_config_with_models (line 66) | def validate_config_with_models(config: dict[str, Any]) -> bool:
  function _config_dict_to_model (line 83) | def _config_dict_to_model(config: dict[str, Any]) -> ThermostatConfig:
  function get_system_type (line 150) | def get_system_type(config: dict[str, Any]) -> str:
  function has_feature (line 162) | def has_feature(config: dict[str, Any], feature_key: str) -> bool:

FILE: custom_components/dual_smart_thermostat/const.py
  class SystemType (line 30) | class SystemType(enum.StrEnum):
  class ToleranceDevice (line 177) | class ToleranceDevice(enum.StrEnum):

FILE: custom_components/dual_smart_thermostat/feature_steps/fan.py
  class FanSteps (line 16) | class FanSteps:
    method __init__ (line 19) | def __init__(self):
    method async_step_toggle (line 23) | async def async_step_toggle(
    method async_step_config (line 40) | async def async_step_config(
    method async_step_options (line 68) | async def async_step_options(

FILE: custom_components/dual_smart_thermostat/feature_steps/floor.py
  class FloorSteps (line 21) | class FloorSteps:
    method __init__ (line 24) | def __init__(self) -> None:
    method async_step_heating (line 27) | async def async_step_heating(
    method async_step_config (line 79) | async def async_step_config(
    method async_step_options (line 96) | async def async_step_options(

FILE: custom_components/dual_smart_thermostat/feature_steps/humidity.py
  class HumiditySteps (line 12) | class HumiditySteps:
    method __init__ (line 15) | def __init__(self):
    method async_step_toggle (line 19) | async def async_step_toggle(
    method async_step_config (line 36) | async def async_step_config(
    method async_step_options (line 54) | async def async_step_options(

FILE: custom_components/dual_smart_thermostat/feature_steps/openings.py
  class OpeningsSteps (line 24) | class OpeningsSteps:
    method __init__ (line 27) | def __init__(self):
    method _call_next_step (line 31) | async def _call_next_step(self, next_step_handler):
    method async_step_toggle (line 43) | async def async_step_toggle(
    method async_step_selection (line 68) | async def async_step_selection(
    method async_step_config (line 103) | async def async_step_config(
    method async_step_options (line 271) | async def async_step_options(

FILE: custom_components/dual_smart_thermostat/feature_steps/presets.py
  class PresetsSteps (line 15) | class PresetsSteps:
    method __init__ (line 18) | def __init__(self):
    method async_step_selection (line 22) | async def async_step_selection(
    method async_step_config (line 103) | async def async_step_config(
    method _process_preset_config_input (line 121) | async def _process_preset_config_input(
    method _flatten_presets_for_form (line 141) | def _flatten_presets_for_form(self, config: dict) -> dict:
    method _transform_preset_fields_to_new_format (line 186) | def _transform_preset_fields_to_new_format(self, user_input: dict) -> ...
    method _validate_preset_temperature_fields (line 241) | def _validate_preset_temperature_fields(self, user_input: dict) -> dict:
    method _show_preset_form_with_errors (line 267) | def _show_preset_form_with_errors(
    method _finish_preset_config_flow (line 278) | async def _finish_preset_config_flow(
    method async_step_options (line 290) | async def async_step_options(

FILE: custom_components/dual_smart_thermostat/feature_steps/shared.py
  function build_schema_context_from_flow (line 10) | def build_schema_context_from_flow(

FILE: custom_components/dual_smart_thermostat/flow_utils.py
  class EntityValidator (line 17) | class EntityValidator:
    method validate_basic_config (line 21) | def validate_basic_config(user_input: dict[str, Any]) -> bool:
    method get_validation_errors (line 44) | def get_validation_errors(user_input: dict[str, Any]) -> dict[str, str]:
  class OpeningsProcessor (line 66) | class OpeningsProcessor:
    method process_openings_config (line 70) | def process_openings_config(
    method extract_selected_entities_from_config (line 153) | def extract_selected_entities_from_config(openings_config: list) -> li...
    method clean_openings_scope (line 172) | def clean_openings_scope(collected_config: dict[str, Any]) -> None:
  class FlowStepTracker (line 196) | class FlowStepTracker:
    method __init__ (line 199) | def __init__(self, collected_config: dict[str, Any]):
    method is_step_shown (line 207) | def is_step_shown(self, step_name: str) -> bool:
    method mark_step_shown (line 218) | def mark_step_shown(self, step_name: str) -> None:
    method should_show_step (line 226) | def should_show_step(self, step_name: str) -> bool:
  class LegacyCompatibility (line 238) | class LegacyCompatibility:
    method convert_legacy_cooler_to_heater (line 242) | def convert_legacy_cooler_to_heater(user_input: dict[str, Any]) -> None:
  class FormHelper (line 252) | class FormHelper:
    method create_step_result (line 256) | def create_step_result(

FILE: custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason.py
  class HVACActionReason (line 12) | class HVACActionReason(enum.StrEnum):

FILE: custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason_auto.py
  class HVACActionReasonAuto (line 4) | class HVACActionReasonAuto(enum.StrEnum):

FILE: custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason_external.py
  class HVACActionReasonExternal (line 4) | class HVACActionReasonExternal(enum.StrEnum):

FILE: custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason_internal.py
  class HVACActionReasonInternal (line 4) | class HVACActionReasonInternal(enum.StrEnum):

FILE: custom_components/dual_smart_thermostat/hvac_controller/cooler_controller.py
  class CoolerHvacController (line 14) | class CoolerHvacController(GenericHvacController):
    method __init__ (line 16) | def __init__(

FILE: custom_components/dual_smart_thermostat/hvac_controller/generic_controller.py
  class GenericHvacController (line 21) | class GenericHvacController(HvacController):
    method __init__ (line 27) | def __init__(
    method _is_valve (line 52) | def _is_valve(self) -> bool:
    method hvac_action_reason (line 58) | def hvac_action_reason(self) -> HVACActionReason:
    method is_active (line 62) | def is_active(self) -> bool:
    method ran_long_enough (line 77) | def ran_long_enough(self) -> bool:
    method needs_control (line 100) | def needs_control(
    method async_control_device_when_on (line 132) | async def async_control_device_when_on(
    method async_control_device_when_off (line 174) | async def async_control_device_when_off(

FILE: custom_components/dual_smart_thermostat/hvac_controller/heater_controller.py
  class HeaterHvacConroller (line 16) | class HeaterHvacConroller(GenericHvacController):
    method __init__ (line 18) | def __init__(
    method async_control_device_when_on (line 39) | async def async_control_device_when_on(
    method async_control_device_when_off (line 79) | async def async_control_device_when_off(

FILE: custom_components/dual_smart_thermostat/hvac_controller/hvac_controller.py
  class HvacGoal (line 17) | class HvacGoal(enum.StrEnum):
  class HvacEnvStrategy (line 24) | class HvacEnvStrategy:
    method __init__ (line 27) | def __init__(
    method hvac_goal_reached (line 42) | def hvac_goal_reached(self) -> bool:
    method hvac_goal_not_reached (line 54) | def hvac_goal_not_reached(self) -> bool:
  class HvacController (line 60) | class HvacController(ABC):
    method __init__ (line 72) | def __init__(
    method hvac_action_reason (line 95) | def hvac_action_reason(self) -> HVACActionReason:
    method async_control_device_when_on (line 99) | def async_control_device_when_on(
    method async_control_device_when_off (line 108) | def async_control_device_when_off(
    method needs_control (line 117) | def needs_control(self, active: bool, hvac_mode: HVACMode, time=None) ...

FILE: custom_components/dual_smart_thermostat/hvac_device/controllable_hvac_device.py
  class ControlableHVACDevice (line 13) | class ControlableHVACDevice(ABC):
    method async_control_hvac (line 27) | async def async_control_hvac(self, time=None, force=False):
    method get_device_ids (line 31) | def get_device_ids(self) -> list[str]:
    method hvac_mode (line 35) | def hvac_mode(self) -> HVACMode:
    method hvac_action (line 39) | def hvac_action(self) -> HVACAction:
    method hvac_mode (line 44) | def hvac_mode(self, hvac_mode: HVACMode):
    method async_set_hvac_mode (line 48) | async def async_set_hvac_mode(self, hvac_mode: HVACMode):
    method async_on_remove (line 64) | def async_on_remove(self, func: CALLBACK_TYPE) -> None:
    method on_entity_state_change (line 71) | def on_entity_state_change(self, entity_id: str, new_state: State) -> ...
    method call_on_remove_callbacks (line 77) | def call_on_remove_callbacks(self) -> None:
    method set_context (line 85) | def set_context(self, context: Context):
    method async_on_startup (line 89) | async def async_on_startup(self):
    method _async_check_device_initial_state (line 93) | async def _async_check_device_initial_state(self) -> None:
    method async_turn_on (line 97) | async def async_turn_on(self):
    method async_turn_off (line 102) | async def async_turn_off(self):
    method is_active (line 106) | def is_active(self) -> bool:
    method HVACActionReason (line 110) | def HVACActionReason(self) -> HVACActionReason:
    method HVACActionReason (line 114) | def HVACActionReason(self, hvac_action_reason: HVACActionReason):
    method on_entity_state_changed (line 117) | def on_entity_state_changed(self, entity_id: str, new_state: State) ->...
    method on_target_temperature_change (line 121) | def on_target_temperature_change(self, temperatures: TargetTemperature...

FILE: custom_components/dual_smart_thermostat/hvac_device/cooler_device.py
  class CoolerDevice (line 18) | class CoolerDevice(GenericHVACDevice):
    method __init__ (line 22) | def __init__(
    method target_env_attr (line 56) | def target_env_attr(self) -> str:
    method hvac_action (line 64) | def hvac_action(self) -> HVACAction:

FILE: custom_components/dual_smart_thermostat/hvac_device/cooler_fan_device.py
  class CoolerFanDevice (line 20) | class CoolerFanDevice(MultiHvacDevice):
    method __init__ (line 22) | def __init__(
    method _set_fan_hot_tolerance_on_state (line 50) | def _set_fan_hot_tolerance_on_state(self):
    method hvac_mode (line 75) | def hvac_mode(self) -> HVACMode:
    method hvac_mode (line 79) | def hvac_mode(self, hvac_mode: HVACMode):  # noqa: F811
    method async_on_startup (line 85) | async def async_on_startup(self, async_write_ha_state_cb: Callable = N...
    method _async_fan_hot_tolerance_on_changed (line 100) | async def _async_fan_hot_tolerance_on_changed(
    method _async_check_device_initial_state (line 120) | async def _async_check_device_initial_state(self) -> None:
    method async_control_hvac (line 124) | async def async_control_hvac(self, time=None, force=False):
    method _async_control_when_fan_on_with_cooler (line 151) | async def _async_control_when_fan_on_with_cooler(self, time=None, forc...
    method _async_control_cooler (line 156) | async def _async_control_cooler(self, time=None, force=False):

FILE: custom_components/dual_smart_thermostat/hvac_device/dryer_device.py
  class DryerDevice (line 18) | class DryerDevice(GenericHVACDevice):
    method __init__ (line 24) | def __init__(
    method hvac_action (line 48) | def hvac_action(self) -> HVACAction:
    method _set_self_active (line 56) | def _set_self_active(self) -> None:
    method target_env_attr_reached_reason (line 77) | def target_env_attr_reached_reason(self) -> HVACActionReason:
    method target_env_attr_not_reached_reason (line 81) | def target_env_attr_not_reached_reason(self) -> HVACActionReason:
    method is_below_target_env_attr (line 85) | def is_below_target_env_attr(self) -> bool:
    method is_above_target_env_attr (line 90) | def is_above_target_env_attr(self) -> bool:

FILE: custom_components/dual_smart_thermostat/hvac_device/fan_device.py
  class FanDevice (line 17) | class FanDevice(CoolerDevice):
    method __init__ (line 22) | def __init__(
    method _detect_fan_capabilities (line 54) | def _detect_fan_capabilities(self) -> None:
    method supports_fan_mode (line 103) | def supports_fan_mode(self) -> bool:
    method fan_modes (line 108) | def fan_modes(self) -> list[str]:
    method uses_preset_modes (line 113) | def uses_preset_modes(self) -> bool:
    method current_fan_mode (line 118) | def current_fan_mode(self) -> str | None:
    method restore_fan_mode (line 122) | def restore_fan_mode(self, fan_mode: str) -> None:
    method async_set_fan_mode (line 142) | async def async_set_fan_mode(self, fan_mode: str) -> None:
    method async_turn_on (line 186) | async def async_turn_on(self):
    method hvac_action (line 209) | def hvac_action(self) -> HVACAction:

FILE: custom_components/dual_smart_thermostat/hvac_device/generic_hvac_device.py
  class GenericHVACDevice (line 36) | class GenericHVACDevice(
    method __init__ (line 44) | def __init__(
    method set_context (line 91) | def set_context(self, context: Context):
    method get_device_ids (line 94) | def get_device_ids(self) -> list[str]:
    method _entity_state (line 98) | def _entity_state(self) -> str:
    method _is_valve (line 102) | def _is_valve(self) -> bool:
    method _entity_features (line 107) | def _entity_features(self) -> int:
    method _supports_open_valve (line 115) | def _supports_open_valve(self) -> bool:
    method _supports_close_valve (line 120) | def _supports_close_valve(self) -> bool:
    method target_env_attr (line 124) | def target_env_attr(self) -> str:
    method is_active (line 128) | def is_active(self) -> bool:
    method is_on (line 133) | def is_on(self) -> bool:
    method is_below_target_env_attr (line 136) | def is_below_target_env_attr(self) -> bool:
    method is_above_target_env_attr (line 140) | def is_above_target_env_attr(self) -> bool:
    method target_env_attr_reached_reason (line 144) | def target_env_attr_reached_reason(self) -> HVACActionReason:
    method target_env_attr_not_reached_reason (line 147) | def target_env_attr_not_reached_reason(self) -> HVACActionReason:
    method _set_self_active (line 150) | def _set_self_active(self) -> None:
    method async_control_hvac (line 173) | async def async_control_hvac(self, time=None, force=False):
    method async_on_startup (line 226) | async def async_on_startup(self, async_write_ha_state_cb: Callable = N...
    method _async_check_device_initial_state (line 237) | async def _async_check_device_initial_state(self) -> None:
    method async_turn_on (line 246) | async def async_turn_on(self):
    method async_turn_off (line 261) | async def async_turn_off(self):
    method _async_turn_on_entity (line 279) | async def _async_turn_on_entity(self) -> None:
    method _async_turn_off_entity (line 313) | async def _async_turn_off_entity(self) -> None:
    method _async_open_valve_entity (line 344) | async def _async_open_valve_entity(self) -> None:
    method _async_close_valve_entity (line 359) | async def _async_close_valve_entity(self) -> None:

FILE: custom_components/dual_smart_thermostat/hvac_device/heat_pump_device.py
  class HeatPumpDevice (line 21) | class HeatPumpDevice(GenericHVACDevice):
    method __init__ (line 25) | def __init__(
    method target_env_attr (line 102) | def target_env_attr(self) -> str:
    method hvac_action (line 113) | def hvac_action(self) -> HVACAction:
    method on_entity_state_changed (line 125) | def on_entity_state_changed(self, entity_id: str, new_state: State) ->...
    method _apply_heat_pump_cooling_state (line 141) | def _apply_heat_pump_cooling_state(self, state: State = None) -> None:
    method _change_hvac_strategy (line 172) | def _change_hvac_strategy(self, heat_pump_is_cooling: bool) -> None:
    method _change_hvac_modes (line 183) | def _change_hvac_modes(self, heat_pump_is_cooling: bool) -> None:
    method _change_hvac_mode (line 202) | def _change_hvac_mode(self, heat_pump_is_cooling: bool) -> None:
    method on_target_temperature_change (line 222) | def on_target_temperature_change(self, temperatures: TargetTemperature...

FILE: custom_components/dual_smart_thermostat/hvac_device/heater_aux_heater_device.py
  class HeaterAUXHeaterDevice (line 21) | class HeaterAUXHeaterDevice(MultiHvacDevice):
    method __init__ (line 23) | def __init__(
    method _target_env_attr (line 45) | def _target_env_attr(self) -> str:
    method async_control_hvac (line 48) | async def async_control_hvac(self, time=None, force=False):
    method async_control_devices (line 59) | async def async_control_devices(self, time=None, force=False):
    method async_control_devices_forced (line 67) | async def async_control_devices_forced(self, time=None) -> None:
    method _async_control_devices_when_off (line 72) | async def _async_control_devices_when_off(self, time=None) -> None:
    method _async_handle_aux_heater_ran_today (line 120) | async def _async_handle_aux_heater_ran_today(self) -> None:
    method _async_handle_aux_heater_havent_run_today (line 126) | async def _async_handle_aux_heater_havent_run_today(self) -> None:
    method _async_control_devices_when_on (line 142) | async def _async_control_devices_when_on(self, time=None) -> None:
    method _first_stage_heating_timed_out (line 208) | def _first_stage_heating_timed_out(self, timeout=None) -> bool:
    method _has_aux_heating_ran_today (line 221) | def _has_aux_heating_ran_today(self) -> bool:

FILE: custom_components/dual_smart_thermostat/hvac_device/heater_cooler_device.py
  class HeaterCoolerDevice (line 17) | class HeaterCoolerDevice(MultiHvacDevice):
    method __init__ (line 19) | def __init__(
    method hvac_mode (line 51) | def hvac_mode(self) -> HVACMode:
    method hvac_mode (line 55) | def hvac_mode(self, hvac_mode: HVACMode):
    method async_control_hvac (line 63) | async def async_control_hvac(self, time=None, force: bool = False):
    method is_cold_or_hot (line 77) | def is_cold_or_hot(self) -> tuple[bool, bool, ToleranceDevice]:
    method async_set_hvac_mode (line 117) | async def async_set_hvac_mode(self, hvac_mode: HVACMode):
    method _async_control_heat_cool (line 127) | async def _async_control_heat_cool(self, time=None, force=False) -> None:
    method async_heater_cooler_toggle (line 147) | async def async_heater_cooler_toggle(self, time=None, force=False) -> ...
    method _async_auto_toggle (line 169) | async def _async_auto_toggle(
    method _async_check_device_initial_state (line 189) | async def _async_check_device_initial_state(self) -> None:

FILE: custom_components/dual_smart_thermostat/hvac_device/heater_device.py
  class HeaterDevice (line 18) | class HeaterDevice(GenericHVACDevice):
    method __init__ (line 22) | def __init__(
    method target_env_attr (line 56) | def target_env_attr(self) -> str:
    method hvac_action (line 62) | def hvac_action(self) -> HVACAction:

FILE: custom_components/dual_smart_thermostat/hvac_device/hvac_device.py
  function merge_hvac_modes (line 15) | def merge_hvac_modes(first: list[HVACMode], second: list[HVACMode]):
  class Switchable (line 19) | class Switchable(ABC):
    method async_turn_on (line 21) | async def async_turn_on(self):
    method async_turn_off (line 25) | async def async_turn_off(self):
  class TargetsEnvironmentAttribute (line 29) | class TargetsEnvironmentAttribute(ABC):
    method target_env_attr (line 35) | def target_env_attr(self) -> str:
  class HVACDevice (line 39) | class HVACDevice:
    method __init__ (line 46) | def __init__(
    method set_context (line 60) | def set_context(self, context: Context):
    method init_hvac_modes (line 64) | def init_hvac_modes(

FILE: custom_components/dual_smart_thermostat/hvac_device/hvac_device_factory.py
  class HVACDeviceFactory (line 38) | class HVACDeviceFactory:
    method __init__ (line 40) | def __init__(
    method create_device (line 73) | def create_device(
    method _create_cooler_device (line 266) | def _create_cooler_device(

FILE: custom_components/dual_smart_thermostat/hvac_device/multi_hvac_device.py
  class MultiHvacDevice (line 17) | class MultiHvacDevice(HVACDevice, ControlableHVACDevice):
    method __init__ (line 21) | def __init__(
    method set_context (line 45) | def set_context(self, context: Context):
    method on_entity_state_changed (line 50) | def on_entity_state_changed(self, entity_id: str, new_state: State) ->...
    method get_device_ids (line 61) | def get_device_ids(self) -> list[str]:
    method set_initial_hvac_mode (line 68) | def set_initial_hvac_mode(self, initial_hvac_mode: HVACMode):
    method is_active (line 76) | def is_active(self) -> bool:
    method hvac_mode (line 83) | def hvac_mode(self) -> HVACMode:
    method hvac_mode (line 87) | def hvac_mode(self, hvac_mode: HVACMode):
    method hvac_action (line 92) | def hvac_action(self) -> HVACAction:
    method set_sub_devices_hvac_mode (line 101) | def set_sub_devices_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method async_set_hvac_mode (line 107) | async def async_set_hvac_mode(self, hvac_mode: HVACMode):
    method async_control_hvac (line 137) | async def async_control_hvac(self, time=None, force: bool = False):
    method async_on_startup (line 158) | async def async_on_startup(self, async_write_ha_state_cb: Callable = N...
    method async_turn_on (line 163) | async def async_turn_on(self):
    method async_turn_off (line 166) | async def async_turn_off(self):
    method async_turn_off_all (line 169) | async def async_turn_off_all(self, time):
    method _async_check_device_initial_state (line 174) | async def _async_check_device_initial_state(self) -> None:

FILE: custom_components/dual_smart_thermostat/managers/auto_mode_evaluator.py
  class AutoDecision (line 27) | class AutoDecision:
  class AutoModeEvaluator (line 39) | class AutoModeEvaluator:
    method __init__ (line 42) | def __init__(
    method _can_heat (line 56) | def _can_heat(self) -> bool:
    method _can_cool (line 64) | def _can_cool(self) -> bool:
    method _dryer_configured (line 73) | def _dryer_configured(self) -> bool:
    method _outside_promotes_to_urgent (line 76) | def _outside_promotes_to_urgent(
    method _free_cooling_applies (line 106) | def _free_cooling_applies(
    method evaluate (line 129) | def evaluate(
    method _goal_pending (line 186) | def _goal_pending(
    method _urgent_decision (line 205) | def _urgent_decision(
    method _full_scan (line 232) | def _full_scan(
    method _humidity_at (line 303) | def _humidity_at(env, *, multiplier: int) -> bool:
    method _cold_target (line 310) | def _cold_target(self, env) -> float | None:
    method _hot_target (line 316) | def _hot_target(self, env) -> float | None:
    method _temp_too_cold (line 322) | def _temp_too_cold(self, env, cold_tolerance: float, *, multiplier: in...
    method _temp_too_hot (line 328) | def _temp_too_hot(self, env, hot_tolerance: float, *, multiplier: int)...
    method _fan_band (line 335) | def _fan_band(self, env) -> bool:

FILE: custom_components/dual_smart_thermostat/managers/environment_manager.py
  class TargetTemperatures (line 58) | class TargetTemperatures:
    method __init__ (line 63) | def __init__(self, temperature: float, temp_high: float, temp_low: flo...
  class EnvironmentAttributeType (line 69) | class EnvironmentAttributeType(enum.StrEnum):
  function _rothfusz_heat_index_f (line 76) | def _rothfusz_heat_index_f(t_f: float, rh: float) -> float:
  class EnvironmentManager (line 98) | class EnvironmentManager(StateManager):
    method __init__ (line 101) | def __init__(self, hass: HomeAssistant, config: ConfigType):
    method sensor_entity_id (line 151) | def sensor_entity_id(self) -> str | None:
    method cur_temp (line 156) | def cur_temp(self) -> float:
    method cur_temp (line 160) | def cur_temp(self, temp: float) -> None:
    method cur_floor_temp (line 165) | def cur_floor_temp(self) -> float:
    method cur_floor_temp (line 169) | def cur_floor_temp(self, temperature) -> None:
    method cur_outside_temp (line 173) | def cur_outside_temp(self) -> float:
    method apparent_temp (line 177) | def apparent_temp(self) -> float | None:
    method effective_temp_for_mode (line 208) | def effective_temp_for_mode(self, mode: HVACMode) -> float | None:
    method target_temp (line 220) | def target_temp(self) -> float:
    method target_temp (line 224) | def target_temp(self, temp: float) -> None:
    method target_temp_high (line 229) | def target_temp_high(self) -> float:
    method target_temp_high (line 233) | def target_temp_high(self, temp: float) -> None:
    method target_temp_low (line 237) | def target_temp_low(self) -> float:
    method target_temp_low (line 241) | def target_temp_low(self, temp: float) -> None:
    method target_temperature_step (line 246) | def target_temperature_step(self) -> float:
    method max_temp (line 250) | def max_temp(self) -> float:
    method min_temp (line 258) | def min_temp(self) -> float:
    method max_floor_temp (line 266) | def max_floor_temp(self) -> float:
    method max_floor_temp (line 270) | def max_floor_temp(self, temp: float) -> None:
    method min_floor_temp (line 274) | def min_floor_temp(self) -> float:
    method min_floor_temp (line 278) | def min_floor_temp(self, temp: float) -> None:
    method saved_target_temp (line 282) | def saved_target_temp(self) -> float:
    method saved_target_temp (line 286) | def saved_target_temp(self, temp: float) -> None:
    method saved_target_temp_low (line 291) | def saved_target_temp_low(self) -> float:
    method saved_target_temp_low (line 295) | def saved_target_temp_low(self, temp: float) -> None:
    method saved_target_temp_high (line 300) | def saved_target_temp_high(self) -> float:
    method saved_target_temp_high (line 304) | def saved_target_temp_high(self, temp: float) -> None:
    method saved_target_humidity (line 308) | def saved_target_humidity(self) -> float:
    method saved_target_humidity (line 312) | def saved_target_humidity(self, humidity: float) -> None:
    method fan_hot_tolerance (line 316) | def fan_hot_tolerance(self) -> float:
    method max_humidity (line 320) | def max_humidity(self) -> float:
    method min_humidity (line 324) | def min_humidity(self) -> float:
    method target_humidity (line 328) | def target_humidity(self) -> float:
    method target_humidity (line 332) | def target_humidity(self, humidity: float) -> None:
    method cur_humidity (line 336) | def cur_humidity(self) -> float:
    method humidity_sensor_stalled (line 340) | def humidity_sensor_stalled(self) -> bool:
    method humidity_sensor_stalled (line 344) | def humidity_sensor_stalled(self, value: bool) -> None:
    method get_env_attr_type (line 347) | def get_env_attr_type(self, attr: str) -> EnvironmentAttributeType:
    method set_hvac_mode (line 354) | def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
    method _get_active_tolerance_for_mode (line 367) | def _get_active_tolerance_for_mode(self) -> tuple[float, float]:
    method set_temperature_range_from_saved (line 448) | def set_temperature_range_from_saved(self) -> None:
    method set_temperature_range_from_hvac_mode (line 452) | def set_temperature_range_from_hvac_mode(
    method set_temperature_target (line 464) | def set_temperature_target(self, temperature: float) -> None:
    method set_temperature_range (line 472) | def set_temperature_range(
    method is_within_fan_tolerance (line 499) | def is_within_fan_tolerance(self, target_attr="_target_temp") -> bool:
    method is_warmer_outside (line 525) | def is_warmer_outside(self) -> bool:
    method is_too_cold (line 537) | def is_too_cold(self, target_attr="_target_temp") -> bool:
    method is_too_hot (line 554) | def is_too_hot(self, target_attr="_target_temp") -> bool:
    method is_equal_to_target (line 581) | def is_equal_to_target(self, target_attr="_target_temp") -> bool:
    method is_too_moist (line 590) | def is_too_moist(self) -> bool:
    method is_too_dry (line 597) | def is_too_dry(self) -> bool:
    method is_floor_hot (line 610) | def is_floor_hot(self) -> bool:
    method is_floor_cold (line 622) | def is_floor_cold(self) -> bool:
    method update_temp_from_state (line 634) | def update_temp_from_state(self, state: State) -> None:
    method update_floor_temp_from_state (line 645) | def update_floor_temp_from_state(self, state: State):
    method update_outside_temp_from_state (line 656) | def update_outside_temp_from_state(self, state: State):
    method update_humidity_from_state (line 667) | def update_humidity_from_state(self, state: State):
    method set_default_target_humidity (line 677) | def set_default_target_humidity(self) -> None:
    method set_default_target_temps (line 685) | def set_default_target_temps(
    method _set_default_temps_target_mode (line 701) | def _set_default_temps_target_mode(self, hvac_mode: HVACMode) -> None:
    method _set_default_temps_range_mode (line 747) | def _set_default_temps_range_mode(self) -> None:
    method set_humidity_from_preset (line 771) | def set_humidity_from_preset(
    method set_temepratures_from_hvac_mode_and_presets (line 795) | def set_temepratures_from_hvac_mode_and_presets(
    method _set_temps_when_no_preset_mode (line 828) | def _set_temps_when_no_preset_mode(
    method _set_temps_when_have_preset_mode (line 852) | def _set_temps_when_have_preset_mode(
    method _set_temps_when_range_mode (line 946) | def _set_temps_when_range_mode(self, old_preset_mode: str | None) -> N...
    method _set_temps_when_target_mode (line 977) | def _set_temps_when_target_mode(
    method _set_floor_temp_limits_from_preset (line 1018) | def _set_floor_temp_limits_from_preset(self, preset_env: PresetEnv) ->...
    method _set_floor_temp_limits_from_config (line 1034) | def _set_floor_temp_limits_from_config(self) -> None:
    method apply_old_state (line 1043) | def apply_old_state(self, old_state: State) -> None:

FILE: custom_components/dual_smart_thermostat/managers/feature_manager.py
  class FeatureManager (line 47) | class FeatureManager(StateManager):
    method __init__ (line 49) | def __init__(
    method heat_pump_cooling_entity_id (line 89) | def heat_pump_cooling_entity_id(self) -> str:
    method supported_features (line 93) | def supported_features(self) -> int:
    method is_target_mode (line 98) | def is_target_mode(self) -> bool:
    method is_range_mode (line 108) | def is_range_mode(self) -> bool:
    method is_configured_for_heater_mode (line 115) | def is_configured_for_heater_mode(self) -> bool:
    method is_configured_for_cooler_mode (line 124) | def is_configured_for_cooler_mode(self) -> bool:
    method is_configured_for_dual_mode (line 129) | def is_configured_for_dual_mode(self) -> bool:
    method is_configured_for_heat_cool_mode (line 137) | def is_configured_for_heat_cool_mode(self) -> bool:
    method is_configured_for_aux_heating_mode (line 150) | def is_configured_for_aux_heating_mode(self) -> bool:
    method aux_heater_timeout (line 161) | def aux_heater_timeout(self) -> int:
    method aux_heater_dual_mode (line 166) | def aux_heater_dual_mode(self) -> bool:
    method is_configured_for_fan_mode (line 171) | def is_configured_for_fan_mode(self) -> bool:
    method is_configured_fan_mode_tolerance (line 176) | def is_configured_fan_mode_tolerance(self) -> bool:
    method is_configured_for_fan_only_mode (line 181) | def is_configured_for_fan_only_mode(self) -> bool:
    method is_configured_for_fan_on_with_cooler (line 190) | def is_configured_for_fan_on_with_cooler(self) -> bool:
    method is_fan_uses_outside_air (line 195) | def is_fan_uses_outside_air(self) -> bool:
    method fan_hot_tolerance_on_entity (line 199) | def fan_hot_tolerance_on_entity(self) -> bool:
    method is_configured_for_dryer_mode (line 203) | def is_configured_for_dryer_mode(self) -> bool:
    method is_configured_for_heat_pump_mode (line 211) | def is_configured_for_heat_pump_mode(self) -> bool:
    method is_configured_for_hvac_power_levels (line 216) | def is_configured_for_hvac_power_levels(self) -> bool:
    method is_configured_for_auto_mode (line 224) | def is_configured_for_auto_mode(self) -> bool:
    method set_support_flags (line 246) | def set_support_flags(
    method apply_old_state (line 301) | def apply_old_state(
    method hvac_modes_support_range_temp (line 331) | def hvac_modes_support_range_temp(self, hvac_modes: list[HVACMode]) ->...
    method set_fan_device (line 336) | def set_fan_device(self, fan_device: FanDevice | None) -> None:
    method fan_device (line 341) | def fan_device(self) -> FanDevice | None:
    method supports_fan_mode (line 346) | def supports_fan_mode(self) -> bool:
    method fan_modes (line 353) | def fan_modes(self) -> list[str]:
    method _restore_fan_mode (line 359) | def _restore_fan_mode(self, old_state: State) -> None:

FILE: custom_components/dual_smart_thermostat/managers/hvac_power_manager.py
  class HvacPowerManager (line 20) | class HvacPowerManager:
    method __init__ (line 25) | def __init__(
    method hvac_power_level (line 73) | def hvac_power_level(self) -> int:
    method hvac_power_percent (line 77) | def hvac_power_percent(self) -> int:
    method _get_hvac_power_tolerance (line 80) | def _get_hvac_power_tolerance(self, is_temperature: bool) -> int:
    method update_hvac_power (line 102) | def update_hvac_power(
    method _calculate_power (line 130) | def _calculate_power(self, target_env_attr: str):
    method _calculate_power_level (line 160) | def _calculate_power_level(self, step_value: float, env_difference: fl...
    method _calculate_power_percent (line 178) | def _calculate_power_percent(

FILE: custom_components/dual_smart_thermostat/managers/opening_manager.py
  class OpeningHvacModeScope (line 33) | class OpeningHvacModeScope(enum.StrEnum):
  class OpeningManager (line 44) | class OpeningManager:
    method __init__ (line 47) | def __init__(self, hass: HomeAssistant, config: ConfigType) -> None:
    method conform_openings_list (line 62) | def conform_openings_list(openings: list) -> list:
    method conform_opening_entities (line 70) | def conform_opening_entities(openings: [TIMED_OPENING_SCHEMA]) -> list...
    method _is_opening_available (line 74) | def _is_opening_available(self, opening: TIMED_OPENING_SCHEMA) -> bool...
    method _has_timeout_mode (line 93) | def _has_timeout_mode(self, opening: TIMED_OPENING_SCHEMA, is_open: bo...
    method _is_opening_open_state (line 98) | def _is_opening_open_state(self, opening: TIMED_OPENING_SCHEMA) -> boo...
    method any_opening_open (line 110) | def any_opening_open(
    method _is_opening_open (line 142) | def _is_opening_open(self, opening: TIMED_OPENING_SCHEMA) -> bool:  # ...
    method _is_opening_timed_out (line 187) | def _is_opening_timed_out(self, opening: TIMED_OPENING_SCHEMA, check_o...

FILE: custom_components/dual_smart_thermostat/managers/preset_manager.py
  class PresetManager (line 22) | class PresetManager(StateManager):
    method __init__ (line 26) | def __init__(
    method presets (line 51) | def presets(self):
    method preset_modes (line 55) | def preset_modes(self) -> list[str]:
    method preset_mode (line 59) | def preset_mode(self):
    method has_presets (line 63) | def has_presets(self):
    method preset_env (line 67) | def preset_env(self) -> PresetEnv:
    method _get_preset_modes_from_config (line 70) | def _get_preset_modes_from_config(
    method set_preset_mode (line 113) | def set_preset_mode(self, preset_mode: str) -> None:
    method _set_presets_when_have_preset_mode (line 134) | def _set_presets_when_have_preset_mode(self, preset_mode: str):
    method apply_old_state (line 153) | async def apply_old_state(self, old_state: State):
    method _apply_range_mode_state (line 176) | async def _apply_range_mode_state(
    method _apply_single_temp_mode_state (line 207) | async def _apply_single_temp_mode_state(
    method _restore_range_temps_from_preset (line 231) | async def _restore_range_temps_from_preset(
    method _restore_temp_from_preset (line 257) | async def _restore_temp_from_preset(self, preset):
    method _restore_temperature_fallback (line 278) | def _restore_temperature_fallback(
    method find_matching_preset (line 287) | def find_matching_preset(self) -> str | None:
    method _values_match_preset (line 332) | def _values_match_preset(
    method _check_temperature_match (line 366) | def _check_temperature_match(self, preset_env, current_temp: float | N...
    method _check_temperature_range_match (line 382) | def _check_temperature_range_match(
    method _check_humidity_match (line 412) | def _check_humidity_match(self, preset_env, current_humidity: float | ...
    method _check_floor_temp_limits_match (line 422) | def _check_floor_temp_limits_match(
    method _values_equal (line 449) | def _values_equal(

FILE: custom_components/dual_smart_thermostat/managers/state_manager.py
  class StateManager (line 6) | class StateManager(ABC):
    method apply_old_state (line 9) | def apply_old_state(self, old_state: State) -> None:

FILE: custom_components/dual_smart_thermostat/models.py
  class CoreSettingsBase (line 20) | class CoreSettingsBase:
    method to_dict (line 28) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 33) | def from_dict(cls, data: dict[str, Any]) -> CoreSettingsBase:
  class SimpleHeaterCoreSettings (line 44) | class SimpleHeaterCoreSettings(CoreSettingsBase):
  class ACOnlyCoreSettings (line 51) | class ACOnlyCoreSettings(CoreSettingsBase):
  class HeaterCoolerCoreSettings (line 59) | class HeaterCoolerCoreSettings(CoreSettingsBase):
  class HeatPumpCoreSettings (line 68) | class HeatPumpCoreSettings(CoreSettingsBase):
  class FanFeatureSettings (line 76) | class FanFeatureSettings:
    method to_dict (line 84) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 89) | def from_dict(cls, data: dict[str, Any]) -> FanFeatureSettings:
  class HumidityFeatureSettings (line 95) | class HumidityFeatureSettings:
    method to_dict (line 106) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 111) | def from_dict(cls, data: dict[str, Any]) -> HumidityFeatureSettings:
  class OpeningConfig (line 117) | class OpeningConfig:
    method to_dict (line 124) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 129) | def from_dict(cls, data: dict[str, Any]) -> OpeningConfig:
  class OpeningsFeatureSettings (line 135) | class OpeningsFeatureSettings:
    method to_dict (line 141) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 149) | def from_dict(cls, data: dict[str, Any]) -> OpeningsFeatureSettings:
  class FloorHeatingFeatureSettings (line 160) | class FloorHeatingFeatureSettings:
    method to_dict (line 167) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 172) | def from_dict(cls, data: dict[str, Any]) -> FloorHeatingFeatureSettings:
  class PresetConfig (line 178) | class PresetConfig:
    method to_dict (line 186) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 191) | def from_dict(cls, data: dict[str, Any]) -> PresetConfig:
  class PresetsFeatureSettings (line 197) | class PresetsFeatureSettings:
    method to_dict (line 202) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 207) | def from_dict(cls, data: dict[str, Any]) -> PresetsFeatureSettings:
  class ThermostatConfig (line 213) | class ThermostatConfig:
    method to_dict (line 230) | def to_dict(self) -> dict[str, Any]:
    method from_dict (line 252) | def from_dict(cls, data: dict[str, Any]) -> ThermostatConfig:

FILE: custom_components/dual_smart_thermostat/options_flow.py
  class OptionsFlowHandler (line 66) | class OptionsFlowHandler(OptionsFlow):
    method __init__ (line 69) | def __init__(self, config_entry) -> None:
    method _get_excluded_flags (line 89) | def _get_excluded_flags() -> set[str]:
    method _normalize_config_from_storage (line 111) | def _normalize_config_from_storage(self, config: dict[str, Any]) -> di...
    method _get_current_config (line 147) | def _get_current_config(self) -> dict[str, Any]:
    method _build_options_schema (line 180) | def _build_options_schema(self, current_config: dict[str, Any]) -> vol...
    method async_step_init (line 505) | async def async_step_init(
    method _determine_options_next_step (line 557) | async def _determine_options_next_step(self) -> FlowResult:
    method async_step_dual_stage_options (line 715) | async def async_step_dual_stage_options(
    method async_step_floor_options (line 754) | async def async_step_floor_options(
    method async_step_fan_options (line 767) | async def async_step_fan_options(
    method async_step_humidity_options (line 781) | async def async_step_humidity_options(
    method async_step_openings_options (line 794) | async def async_step_openings_options(
    method async_step_openings_config (line 806) | async def async_step_openings_config(
    method async_step_preset_selection (line 825) | async def async_step_preset_selection(
    method _has_both_heating_and_cooling (line 833) | def _has_both_heating_and_cooling(self) -> bool:
    method async_step_presets (line 853) | async def async_step_presets(
    method _get_entry (line 861) | def _get_entry(self):
    method _get_merged_config (line 885) | def _get_merged_config(self):
    method config_entry (line 897) | def config_entry(self):

FILE: custom_components/dual_smart_thermostat/preset_env/preset_env.py
  class TargeTempEnv (line 19) | class TargeTempEnv:
    method __init__ (line 22) | def __init__(self, **kwargs) -> None:
  class RangeTempEnv (line 27) | class RangeTempEnv:
    method __init__ (line 31) | def __init__(self, **kwargs) -> None:
  class FloorTempLimitEnv (line 37) | class FloorTempLimitEnv:
    method __init__ (line 41) | def __init__(self, **kwargs) -> None:
  class TempEnv (line 48) | class TempEnv(TargeTempEnv, RangeTempEnv, FloorTempLimitEnv):
    method __init__ (line 49) | def __init__(self, **kwargs) -> None:
  class HumidityEnv (line 54) | class HumidityEnv:
    method __init__ (line 57) | def __init__(self, **kwargs) -> None:
  class PresetEnv (line 63) | class PresetEnv(TempEnv, HumidityEnv):
    method __init__ (line 64) | def __init__(self, **kwargs):
    method _process_field (line 82) | def _process_field(self, field_name: str, value: Any) -> None:
    method _extract_entities (line 113) | def _extract_entities(self, template_str: str) -> None:
    method get_temperature (line 135) | def get_temperature(self, hass: HomeAssistant) -> float | None:
    method get_target_temp_low (line 141) | def get_target_temp_low(self, hass: HomeAssistant) -> float | None:
    method get_target_temp_high (line 147) | def get_target_temp_high(self, hass: HomeAssistant) -> float | None:
    method _evaluate_template (line 153) | def _evaluate_template(self, hass: HomeAssistant, field_name: str) -> ...
    method referenced_entities (line 183) | def referenced_entities(self) -> set[str]:
    method has_templates (line 187) | def has_templates(self) -> bool:
    method to_dict (line 192) | def to_dict(self) -> dict:
    method has_temp_range (line 195) | def has_temp_range(self) -> bool:
    method has_temp (line 198) | def has_temp(self) -> bool:
    method has_humidity (line 201) | def has_humidity(self) -> bool:
    method has_floor_temp_limits (line 204) | def has_floor_temp_limits(self) -> bool:

FILE: custom_components/dual_smart_thermostat/schema_utils.py
  function seconds_to_duration (line 11) | def seconds_to_duration(seconds: int) -> dict[str, int]:
  function get_temperature_selector (line 31) | def get_temperature_selector(
  function get_tolerance_selector (line 82) | def get_tolerance_selector(
  function get_percentage_selector (line 137) | def get_percentage_selector(
  function get_time_selector (line 154) | def get_time_selector(
  function get_entity_selector (line 169) | def get_entity_selector(domain: str | list[str]) -> selector.EntitySelec...
  function get_boolean_selector (line 181) | def get_boolean_selector() -> selector.BooleanSelector:
  function get_select_selector (line 186) | def get_select_selector(
  function get_multi_select_selector (line 199) | def get_multi_select_selector(
  function get_text_selector (line 212) | def get_text_selector(

FILE: custom_components/dual_smart_thermostat/schemas.py
  function _load_translations_sync (line 83) | def _load_translations_sync() -> dict:
  function _load_translations (line 104) | def _load_translations() -> dict:
  function validate_template_or_number (line 109) | def validate_template_or_number(value: Any) -> Any:
  function get_system_type_schema (line 166) | def get_system_type_schema(default: str | None = None):
  function get_base_schema (line 188) | def get_base_schema():
  function get_tolerance_fields (line 200) | def get_tolerance_fields(
  function get_timing_fields_for_section (line 258) | def get_timing_fields_for_section(
  function get_basic_ac_schema (line 310) | def get_basic_ac_schema(hass=None, defaults=None, include_name=True):
  function get_simple_heater_schema (line 359) | def get_simple_heater_schema(hass=None, defaults=None, include_name=True):
  function get_heater_cooler_schema (line 409) | def get_heater_cooler_schema(hass=None, defaults=None, include_name=True):
  function get_heat_pump_schema (line 476) | def get_heat_pump_schema(hass=None, defaults=None, include_name=True):
  function get_grouped_schema (line 552) | def get_grouped_schema(
  function get_heating_schema (line 607) | def get_heating_schema():
  function get_cooling_schema (line 618) | def get_cooling_schema():
  function get_dual_stage_schema (line 629) | def get_dual_stage_schema():
  function get_floor_heating_schema (line 649) | def get_floor_heating_schema(hass=None, defaults: dict[str, Any] | None ...
  function get_openings_toggle_schema (line 671) | def get_openings_toggle_schema():
  function get_fan_toggle_schema (line 676) | def get_fan_toggle_schema():
  function get_humidity_toggle_schema (line 681) | def get_humidity_toggle_schema():
  function get_features_schema (line 686) | def get_features_schema(
  function get_ac_only_features_schema (line 757) | def get_ac_only_features_schema(defaults: dict[str, Any] | None = None):
  function get_simple_heater_features_schema (line 765) | def get_simple_heater_features_schema(defaults: dict[str, Any] | None = ...
  function get_system_features_schema (line 773) | def get_system_features_schema(system_type: str):
  function get_core_schema (line 781) | def get_core_schema(
  function get_openings_selection_schema (line 885) | def get_openings_selection_schema(
  function get_openings_schema (line 905) | def get_openings_schema(selected_entities: list[str]):
  function get_fan_schema (line 933) | def get_fan_schema(hass=None, defaults: dict[str, Any] | None = None):
  function get_humidity_schema (line 981) | def get_humidity_schema(defaults: dict[str, Any] | None = None):
  function get_additional_sensors_schema (line 1019) | def get_additional_sensors_schema():
  function get_heat_cool_mode_schema (line 1026) | def get_heat_cool_mode_schema():
  function get_advanced_settings_schema (line 1033) | def get_advanced_settings_schema(hass=None):
  function get_preset_selection_schema (line 1088) | def get_preset_selection_schema(defaults: list[str] | None = None):
  function get_presets_schema (line 1154) | def get_presets_schema(user_input: dict[str, Any]) -> vol.Schema:

FILE: custom_components/dual_smart_thermostat/sensor.py
  function _build_options (line 34) | def _build_options() -> tuple[str, ...]:
  class HvacActionReasonSensor (line 43) | class HvacActionReasonSensor(SensorEntity, RestoreEntity):
    method __init__ (line 52) | def __init__(self, sensor_key: str, name: str) -> None:
    method async_added_to_hass (line 61) | async def async_added_to_hass(self) -> None:
    method async_will_remove_from_hass (line 83) | async def async_will_remove_from_hass(self) -> None:
    method _handle_reason_update (line 91) | def _handle_reason_update(self, reason) -> None:
  function async_setup_entry (line 115) | async def async_setup_entry(  # NOSONAR python:S7503 - HA platform API
  function async_setup_platform (line 129) | async def async_setup_platform(  # NOSONAR python:S7503 - HA platform API

FILE: demo_openings_translations.py
  function demo_openings_scope_translations (line 8) | def demo_openings_scope_translations():

FILE: demo_translations.py
  function load_translations (line 8) | def load_translations(lang="en"):
  function demo_scope_translations (line 19) | def demo_scope_translations():

FILE: manage/update_manifest.py
  function update_manifest (line 8) | def update_manifest() -> None:

FILE: pcap.py
  class PcapBpfProgram (line 31) | class PcapBpfProgram(Structure):
  class PcapIf (line 35) | class PcapIf(Structure):
  function findalldevs (line 83) | def findalldevs():
  class Pcap (line 103) | class Pcap:
    method __init__ (line 104) | def __init__(self, device, snaplen=65535, promisc=1, to_ms=1000):
    method compile_filter (line 118) | def compile_filter(self, filter_expr, optimize=True, netmask=0xFFFFFFFF):
    method setfilter (line 136) | def setfilter(self, prog):
    method close (line 141) | def close(self):
    method __enter__ (line 146) | def __enter__(self):
    method __exit__ (line 149) | def __exit__(self, exc_type, exc, tb):
  function compile_filter_on_device (line 153) | def compile_filter_on_device(filter_expr):

FILE: tests/__init__.py
  function setup_comp_1 (line 49) | async def setup_comp_1(hass: HomeAssistant) -> None:
  function setup_comp_heat (line 57) | async def setup_comp_heat(hass: HomeAssistant) -> None:
  function setup_comp_heat_valve (line 79) | async def setup_comp_heat_valve(hass: HomeAssistant) -> None:
  function setup_comp_heat_safety_delay (line 101) | async def setup_comp_heat_safety_delay(hass: HomeAssistant) -> None:
  function setup_comp_heat_floor_sensor (line 124) | async def setup_comp_heat_floor_sensor(hass: HomeAssistant) -> None:
  function setup_comp_heat_floor_opening_sensor (line 147) | async def setup_comp_heat_floor_opening_sensor(hass: HomeAssistant) -> N...
  function setup_comp_heat_cycle (line 171) | async def setup_comp_heat_cycle(hass: HomeAssistant) -> None:
  function setup_comp_heat_cycle_precision (line 194) | async def setup_comp_heat_cycle_precision(hass: HomeAssistant) -> None:
  function setup_comp_heat_ac_cool (line 219) | async def setup_comp_heat_ac_cool(hass: HomeAssistant) -> None:
  function setup_comp_heat_ac_cool_safety_delay (line 243) | async def setup_comp_heat_ac_cool_safety_delay(hass: HomeAssistant) -> N...
  function setup_comp_fan_only_config (line 268) | async def setup_comp_fan_only_config(hass: HomeAssistant) -> None:
  function setup_comp_fan_only_config_cycle (line 292) | async def setup_comp_fan_only_config_cycle(hass: HomeAssistant) -> None:
  function setup_comp_fan_only_config_keep_alive (line 317) | async def setup_comp_fan_only_config_keep_alive(hass: HomeAssistant) -> ...
  function setup_comp_fan_only_config_presets (line 341) | async def setup_comp_fan_only_config_presets(hass: HomeAssistant) -> None:
  function setup_comp_heat_ac_cool_fan_config (line 372) | async def setup_comp_heat_ac_cool_fan_config(hass: HomeAssistant) -> None:
  function setup_comp_heat_ac_cool_fan_config_tolerance (line 397) | async def setup_comp_heat_ac_cool_fan_config_tolerance(hass: HomeAssista...
  function setup_comp_heat_ac_cool_fan_config_tolerance_min_cycle (line 423) | async def setup_comp_heat_ac_cool_fan_config_tolerance_min_cycle(
  function setup_comp_heat_ac_cool_fan_config_cycle (line 452) | async def setup_comp_heat_ac_cool_fan_config_cycle(hass: HomeAssistant) ...
  function setup_comp_heat_ac_cool_fan_config_keep_alive (line 478) | async def setup_comp_heat_ac_cool_fan_config_keep_alive(hass: HomeAssist...
  function setup_comp_heat_ac_cool_fan_config_presets (line 504) | async def setup_comp_heat_ac_cool_fan_config_presets(hass: HomeAssistant...
  function setup_comp_heat_ac_cool_presets (line 536) | async def setup_comp_heat_ac_cool_presets(hass: HomeAssistant) -> None:
  function setup_comp_heat_ac_cool_presets_range (line 567) | async def setup_comp_heat_ac_cool_presets_range(hass: HomeAssistant) -> ...
  function setup_comp_heat_ac_cool_cycle (line 630) | async def setup_comp_heat_ac_cool_cycle(hass: HomeAssistant) -> None:
  function setup_comp_heat_presets (line 656) | async def setup_comp_heat_presets(hass: HomeAssistant) -> None:
  function setup_comp_heat_presets_floor (line 686) | async def setup_comp_heat_presets_floor(hass: HomeAssistant) -> None:
  function setup_comp_cool (line 748) | async def setup_comp_cool(hass: HomeAssistant) -> None:
  function setup_comp_dual (line 770) | async def setup_comp_dual(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_1 (line 793) | async def setup_comp_heat_cool_1(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_2 (line 817) | async def setup_comp_heat_cool_2(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_3 (line 842) | async def setup_comp_heat_cool_3(hass: HomeAssistant) -> None:
  function setup_comp_dual_fan_config (line 870) | async def setup_comp_dual_fan_config(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_fan_config (line 894) | async def setup_comp_heat_cool_fan_config(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_fan_config_tolerance (line 919) | async def setup_comp_heat_cool_fan_config_tolerance(hass: HomeAssistant)...
  function setup_comp_heat_cool_fan_config_2 (line 945) | async def setup_comp_heat_cool_fan_config_2(hass: HomeAssistant) -> None:
  function setup_comp_dual_presets (line 974) | async def setup_comp_dual_presets(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_presets (line 1018) | async def setup_comp_heat_cool_presets(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_presets_range_only (line 1077) | async def setup_comp_heat_cool_presets_range_only(hass: HomeAssistant) -...
  function setup_comp_heat_cool_safety_delay (line 1129) | async def setup_comp_heat_cool_safety_delay(hass: HomeAssistant) -> None:
  function setup_comp_heat_cool_fan_presets (line 1154) | async def setup_comp_heat_cool_fan_presets(hass: HomeAssistant) -> None:
  function setup_component (line 1213) | async def setup_component(hass: HomeAssistant, mock_config: dict) -> Moc...
  function setup_comp_heat_cool_dual_switch (line 1225) | async def setup_comp_heat_cool_dual_switch(hass: HomeAssistant) -> None:
  function setup_sensor (line 1282) | def setup_sensor(hass: HomeAssistant, temp: float) -> None:
  function setup_floor_sensor (line 1287) | def setup_floor_sensor(hass: HomeAssistant, temp: float) -> None:
  function setup_outside_sensor (line 1292) | def setup_outside_sensor(hass: HomeAssistant, temp: float) -> None:
  function setup_humidity_sensor (line 1297) | def setup_humidity_sensor(hass: HomeAssistant, humidity: float) -> None:
  function setup_boolean (line 1302) | def setup_boolean(hass: HomeAssistant, entity, state) -> None:
  function setup_switch (line 1307) | def setup_switch(
  function setup_valve (line 1325) | def setup_valve(hass: HomeAssistant, is_open: bool) -> None:
  function setup_fan_heat_tolerance_toggle (line 1345) | def setup_fan_heat_tolerance_toggle(hass: HomeAssistant, is_on: bool) ->...
  function setup_heat_pump_cooling_status (line 1363) | def setup_heat_pump_cooling_status(hass: HomeAssistant, is_on: bool) -> ...
  function setup_switch_dual (line 1381) | def setup_switch_dual(
  function setup_switch_heat_cool_fan (line 1400) | def setup_switch_heat_cool_fan(
  function setup_fan (line 1420) | def setup_fan(hass: HomeAssistant, is_on: bool) -> None:

FILE: tests/behavioral/test_tolerance_thresholds.py
  function test_heater_cold_tolerance_threshold_heating_mode (line 32) | async def test_heater_cold_tolerance_threshold_heating_mode(hass: HomeAs...
  function test_cooler_hot_tolerance_threshold_cooling_mode (line 111) | async def test_cooler_hot_tolerance_threshold_cooling_mode(hass: HomeAss...
  function test_heat_cool_mode_dual_thresholds (line 195) | async def test_heat_cool_mode_dual_thresholds(hass: HomeAssistant):
  function test_zero_tolerance_immediate_response (line 291) | async def test_zero_tolerance_immediate_response(hass: HomeAssistant):
  function test_large_tolerance_wide_dead_band (line 347) | async def test_large_tolerance_wide_dead_band(hass: HomeAssistant):

FILE: tests/common.py
  function async_set_preset_mode (line 81) | async def async_set_preset_mode(hass, preset_mode, entity_id=ENTITY_MATC...
  function async_set_temperature (line 91) | async def async_set_temperature(
  function async_set_temperature_range (line 117) | async def async_set_temperature_range(
  function async_set_humidity (line 141) | async def async_set_humidity(
  function set_temperature (line 160) | def set_temperature(
  function async_set_hvac_mode (line 184) | async def async_set_hvac_mode(hass, hvac_mode, entity_id=ENTITY_MATCH_AL...
  function async_toggle (line 194) | async def async_toggle(hass, entity_id=ENTITY_MATCH_ALL) -> None:
  function set_operation_mode (line 205) | def set_operation_mode(hass, hvac_mode, entity_id=ENTITY_MATCH_ALL) -> N...
  function async_turn_on (line 215) | async def async_turn_on(hass, entity_id=ENTITY_MATCH_ALL) -> None:
  function async_turn_off (line 225) | async def async_turn_off(hass, entity_id=ENTITY_MATCH_ALL) -> None:
  function async_set_hvac_action_reason (line 235) | async def async_set_hvac_action_reason(
  function get_action_reason_sensor_entity_id (line 251) | def get_action_reason_sensor_entity_id(climate_entity_id: str) -> str:
  function get_action_reason_sensor_state (line 261) | def get_action_reason_sensor_state(hass, climate_entity_id: str):
  function threadsafe_callback_factory (line 269) | def threadsafe_callback_factory(func):
  function async_fire_time_changed_exact (line 287) | def async_fire_time_changed_exact(
  function async_fire_time_changed (line 306) | def async_fire_time_changed(
  function _async_fire_time_changed (line 336) | def _async_fire_time_changed(
  function get_scheduled_timer_handles (line 367) | def get_scheduled_timer_handles(loop: asyncio.AbstractEventLoop) -> list...
  function mock_restore_cache (line 373) | def mock_restore_cache(hass: HomeAssistant, states: Sequence[State]) -> ...
  function mock_restore_cache_with_extra_data (line 399) | def mock_restore_cache_with_extra_data(
  function async_mock_service (line 426) | def async_mock_service(
  function get_fixture_path (line 466) | def get_fixture_path(filename: str, integration: str | None = None) -> p...
  function async_mock_signal (line 472) | def async_mock_signal(

FILE: tests/config_flow/test_ac_only_advanced_settings.py
  class TestACOnlyAdvancedSettings (line 19) | class TestACOnlyAdvancedSettings:
    method test_config_flow_ac_only_has_advanced_section (line 22) | def test_config_flow_ac_only_has_advanced_section(self):
    method test_options_flow_ac_only_has_advanced_section (line 40) | def test_options_flow_ac_only_has_advanced_section(self):
    method test_options_flow_init_step_ac_only (line 70) | async def test_options_flow_init_step_ac_only(self):

FILE: tests/config_flow/test_ac_only_features.py
  function test_ac_only_features_flow (line 13) | async def test_ac_only_features_flow():
  function run_test (line 110) | def run_test():

FILE: tests/config_flow/test_ac_only_features_integration.py
  function mock_hass (line 45) | def mock_hass():
  class TestAcOnlyNoFeatures (line 54) | class TestAcOnlyNoFeatures:
    method test_config_flow_no_features (line 57) | async def test_config_flow_no_features(self, mock_hass):
  class TestAcOnlyFanOnly (line 113) | class TestAcOnlyFanOnly:
    method test_config_flow_fan_only (line 116) | async def test_config_flow_fan_only(self, mock_hass):
  class TestAcOnlyHumidityOnly (line 169) | class TestAcOnlyHumidityOnly:
    method test_config_flow_humidity_only (line 172) | async def test_config_flow_humidity_only(self, mock_hass):
  class TestAcOnlyFanAndHumidity (line 229) | class TestAcOnlyFanAndHumidity:
    method test_config_flow_fan_and_humidity (line 232) | async def test_config_flow_fan_and_humidity(self, mock_hass):
  class TestAcOnlyAllFeatures (line 303) | class TestAcOnlyAllFeatures:
    method test_config_flow_all_features (line 306) | async def test_config_flow_all_features(self, mock_hass):
  class TestAcOnlyBlockedFeatures (line 418) | class TestAcOnlyBlockedFeatures:
    method test_floor_heating_not_in_schema (line 421) | async def test_floor_heating_not_in_schema(self, mock_hass):
    method test_available_features_only (line 440) | async def test_available_features_only(self, mock_hass):
  class TestAcOnlyFeatureOrdering (line 469) | class TestAcOnlyFeatureOrdering:
    method test_fan_before_humidity (line 472) | async def test_fan_before_humidity(self, mock_hass):
    method test_humidity_before_openings (line 516) | async def test_humidity_before_openings(self, mock_hass):
  class TestAcOnlyPartialOverride (line 561) | class TestAcOnlyPartialOverride:
    method test_tolerance_partial_override_cool_only (line 564) | async def test_tolerance_partial_override_cool_only(self, mock_hass):

FILE: tests/config_flow/test_advanced_options.py
  function test_issue_reproduction (line 26) | def test_issue_reproduction():
  function test_user_workflow (line 81) | def test_user_workflow():
  function test_edge_cases (line 149) | def test_edge_cases():
  function test_flow_determination_logic (line 179) | def test_flow_determination_logic():
  function test_separate_advanced_step (line 232) | def test_separate_advanced_step():
  function main (line 265) | def main():

FILE: tests/config_flow/test_config_flow.py
  function mock_hass (line 27) | def mock_hass():
  function test_config_flow_system_type_selection (line 35) | async def test_config_flow_system_type_selection():
  function test_ac_only_config_flow (line 56) | async def test_ac_only_config_flow():
  function test_ac_only_config_flow_without_advanced_settings (line 97) | async def test_ac_only_config_flow_without_advanced_settings():
  function test_ac_only_config_flow_with_custom_tolerances (line 125) | async def test_ac_only_config_flow_with_custom_tolerances():
  function test_ac_only_features_selection (line 159) | async def test_ac_only_features_selection():
  function test_simple_heater_config_flow (line 193) | async def test_simple_heater_config_flow():
  function test_dual_system_config_flow (line 220) | async def test_dual_system_config_flow():
  function test_heater_cooler_schema_includes_name (line 252) | async def test_heater_cooler_schema_includes_name():
  function test_preset_selection_flow (line 290) | async def test_preset_selection_flow():
  function test_preset_skip_logic (line 325) | async def test_preset_skip_logic():
  function run_all_tests (line 357) | async def run_all_tests():

FILE: tests/config_flow/test_e2e_ac_only_persistence.py
  function test_ac_only_minimal_config_persistence (line 38) | async def test_ac_only_minimal_config_persistence(hass):
  function test_ac_only_all_features_persistence (line 206) | async def test_ac_only_all_features_persistence(hass):
  function test_ac_only_fan_only_persistence (line 444) | async def test_ac_only_fan_only_persistence(hass):
  function test_ac_only_repeated_options_flow_persistence (line 501) | async def test_ac_only_repeated_options_flow_persistence(hass):

FILE: tests/config_flow/test_e2e_heat_pump_persistence.py
  function test_heat_pump_full_config_then_options_flow_persistence (line 50) | async def test_heat_pump_full_config_then_options_flow_persistence(hass):
  function test_heat_pump_all_features_full_persistence (line 239) | async def test_heat_pump_all_features_full_persistence(hass):
  function test_heat_pump_floor_heating_only_persistence (line 497) | async def test_heat_pump_floor_heating_only_persistence(hass):
  function test_heat_pump_options_flow_preserves_unmodified_fields (line 561) | async def test_heat_pump_options_flow_preserves_unmodified_fields(hass):
  function test_heat_pump_cooling_sensor_persistence (line 637) | async def test_heat_pump_cooling_sensor_persistence(hass):
  class TestHeatPumpModeSpecificTolerancesPersistence (line 707) | class TestHeatPumpModeSpecificTolerancesPersistence:
    method test_mode_specific_tolerances_persist_through_config_and_options_flow (line 710) | async def test_mode_specific_tolerances_persist_through_config_and_opt...
  class TestHeatPumpMixedTolerancesPersistence (line 867) | class TestHeatPumpMixedTolerancesPersistence:
    method test_mixed_tolerances_persist_legacy_plus_partial_override (line 870) | async def test_mixed_tolerances_persist_legacy_plus_partial_override(s...
  function test_heat_pump_repeated_options_flow_precision_persistence (line 997) | async def test_heat_pump_repeated_options_flow_precision_persistence(hass):

FILE: tests/config_flow/test_e2e_heater_cooler_persistence.py
  function test_heater_cooler_minimal_config_persistence (line 57) | async def test_heater_cooler_minimal_config_persistence(hass):
  function test_heater_cooler_options_flow_preserves_unmodified_fields (line 245) | async def test_heater_cooler_options_flow_preserves_unmodified_fields(ha...
  function test_heater_cooler_all_features_full_persistence (line 317) | async def test_heater_cooler_all_features_full_persistence(hass):
  function test_heater_cooler_floor_heating_only_persistence (line 568) | async def test_heater_cooler_floor_heating_only_persistence(hass):
  function test_heater_cooler_fan_mode_persists_in_config_flow (line 635) | async def test_heater_cooler_fan_mode_persists_in_config_flow(hass):
  function test_heater_cooler_fan_mode_persists_in_options_flow (line 681) | async def test_heater_cooler_fan_mode_persists_in_options_flow(hass):
  function test_heater_cooler_fan_mode_default_is_false_when_not_set (line 727) | async def test_heater_cooler_fan_mode_default_is_false_when_not_set(hass):
  function test_heater_cooler_fan_mode_true_shown_as_default_in_options_flow (line 776) | async def test_heater_cooler_fan_mode_true_shown_as_default_in_options_f...
  function test_heater_cooler_fan_mode_false_when_explicitly_set_to_false (line 825) | async def test_heater_cooler_fan_mode_false_when_explicitly_set_to_false...
  function test_heater_cooler_fan_mode_missing_from_user_input_when_not_changed (line 858) | async def test_heater_cooler_fan_mode_missing_from_user_input_when_not_c...
  function test_heater_cooler_fan_on_with_ac_false_persists_in_config_flow (line 907) | async def test_heater_cooler_fan_on_with_ac_false_persists_in_config_flo...
  function test_heater_cooler_multiple_fan_booleans_false_persist_in_config_flow (line 942) | async def test_heater_cooler_multiple_fan_booleans_false_persist_in_conf...
  function test_heater_cooler_fan_on_with_ac_false_shown_in_options_flow (line 977) | async def test_heater_cooler_fan_on_with_ac_false_shown_in_options_flow(...
  function test_heater_cooler_fan_on_with_ac_false_not_in_config_shows_true_default (line 1025) | async def test_heater_cooler_fan_on_with_ac_false_not_in_config_shows_tr...
  function test_heater_cooler_fan_mode_true_persists_and_shows_in_options (line 1073) | async def test_heater_cooler_fan_mode_true_persists_and_shows_in_options...
  class TestHeaterCoolerModeSpecificTolerancesPersistence (line 1148) | class TestHeaterCoolerModeSpecificTolerancesPersistence:
    method test_mode_specific_tolerances_persist_through_config_and_options_flow (line 1151) | async def test_mode_specific_tolerances_persist_through_config_and_opt...
  function test_heater_cooler_repeated_options_flow_precision_persistence (line 1302) | async def test_heater_cooler_repeated_options_flow_precision_persistence...

FILE: tests/config_flow/test_e2e_simple_heater_persistence.py
  function test_simple_heater_minimal_config_persistence (line 39) | async def test_simple_heater_minimal_config_persistence(hass):
  function test_simple_heater_all_features_persistence (line 233) | async def test_simple_heater_all_features_persistence(hass):
  function test_simple_heater_floor_heating_only_persistence (line 421) | async def test_simple_heater_floor_heating_only_persistence(hass):
  function test_simple_heater_openings_scope_and_timeout_saved (line 486) | async def test_simple_heater_openings_scope_and_timeout_saved(hass):
  function test_simple_heater_openings_scope_all_is_cleaned (line 553) | async def test_simple_heater_openings_scope_all_is_cleaned(hass):
  function test_simple_heater_openings_multiple_timeout_values (line 610) | async def test_simple_heater_openings_multiple_timeout_values(hass):
  class TestSimpleHeaterLegacyTolerancesPersistence (line 676) | class TestSimpleHeaterLegacyTolerancesPersistence:
    method test_legacy_tolerances_persist_without_mode_specific (line 679) | async def test_legacy_tolerances_persist_without_mode_specific(self, h...
  function test_simple_heater_repeated_options_flow_precision_persistence (line 768) | async def test_simple_heater_repeated_options_flow_precision_persistence...

FILE: tests/config_flow/test_heat_pump_config_flow.py
  function mock_hass (line 29) | def mock_hass():
  class TestHeatPumpConfigFlow (line 38) | class TestHeatPumpConfigFlow:
    method test_config_flow_completes_without_error (line 41) | async def test_config_flow_completes_without_error(self, mock_hass):
    method test_valid_configuration_created (line 75) | async def test_valid_configuration_created(self, mock_hass):
    method test_all_required_fields_present (line 118) | async def test_all_required_fields_present(self, mock_hass):
    method test_advanced_settings_flattened_correctly (line 145) | async def test_advanced_settings_flattened_correctly(self, mock_hass):
    method test_validation_same_heater_sensor_entity (line 175) | async def test_validation_same_heater_sensor_entity(self, mock_hass):
    method test_heat_pump_cooling_entity_id_accepted (line 196) | async def test_heat_pump_cooling_entity_id_accepted(self, mock_hass):
    method test_heat_pump_cooling_optional (line 227) | async def test_heat_pump_cooling_optional(self, mock_hass):
    method test_name_field_collected_in_config_flow (line 254) | async def test_name_field_collected_in_config_flow(self, mock_hass):
  class TestHeatPumpFieldValidation (line 277) | class TestHeatPumpFieldValidation:
    method test_numeric_fields_have_correct_defaults (line 280) | async def test_numeric_fields_have_correct_defaults(self, mock_hass):
    method test_field_types_match_expected_types (line 299) | async def test_field_types_match_expected_types(self, mock_hass):

FILE: tests/config_flow/test_heat_pump_features_integration.py
  function mock_hass (line 51) | def mock_hass():
  class TestHeatPumpNoFeatures (line 60) | class TestHeatPumpNoFeatures:
    method test_config_flow_no_features (line 63) | async def test_config_flow_no_features(self, mock_hass):
  class TestHeatPumpFloorHeatingOnly (line 122) | class TestHeatPumpFloorHeatingOnly:
    method test_config_flow_floor_heating_only (line 125) | async def test_config_flow_floor_heating_only(self, mock_hass):
  class TestHeatPumpFanOnly (line 181) | class TestHeatPumpFanOnly:
    method test_config_flow_fan_only (line 184) | async def test_config_flow_fan_only(self, mock_hass):
  class TestHeatPumpAllFeatures (line 236) | class TestHeatPumpAllFeatures:
    method test_config_flow_all_features (line 239) | async def test_config_flow_all_features(self, mock_hass):
  class TestHeatPumpFeatureOrdering (line 369) | class TestHeatPumpFeatureOrdering:
    method test_complete_feature_ordering (line 372) | async def test_complete_feature_ordering(self, mock_hass):
  class TestHeatPumpAvailableFeatures (line 473) | class TestHeatPumpAvailableFeatures:
    method test_all_features_available (line 476) | async def test_all_features_available(self, mock_hass):
  class TestHeatPumpCoolingSensorHandling (line 506) | class TestHeatPumpCoolingSensorHandling:
    method test_heat_pump_cooling_sensor_optional (line 509) | async def test_heat_pump_cooling_sensor_optional(self, mock_hass):
    method test_heat_pump_cooling_sensor_saved_when_provided (line 534) | async def test_heat_pump_cooling_sensor_saved_when_provided(self, mock...
  class TestHeatPumpPartialOverride (line 562) | class TestHeatPumpPartialOverride:
    method test_tolerance_partial_override_heat_only (line 565) | async def test_tolerance_partial_override_heat_only(self, mock_hass):
    method test_tolerance_partial_override_cool_only (line 631) | async def test_tolerance_partial_override_cool_only(self, mock_hass):

FILE: tests/config_flow/test_heat_pump_options_flow.py
  function mock_hass (line 27) | def mock_hass():
  class TestHeatPumpOptionsFlow (line 34) | class TestHeatPumpOptionsFlow:
    method test_options_flow_omits_name_field (line 37) | async def test_options_flow_omits_name_field(self, mock_hass):
    method test_options_flow_prefills_all_fields (line 71) | async def test_options_flow_prefills_all_fields(self, mock_hass):
    method test_options_flow_preserves_unmodified_fields (line 125) | async def test_options_flow_preserves_unmodified_fields(self, mock_hass):
    method test_options_flow_system_type_display_non_editable (line 168) | async def test_options_flow_system_type_display_non_editable(self, moc...
    method test_options_flow_completes_without_error (line 207) | async def test_options_flow_completes_without_error(self, mock_hass):
    method test_options_flow_updated_config_matches_data_model (line 236) | async def test_options_flow_updated_config_matches_data_model(self, mo...

FILE: tests/config_flow/test_heater_cooler_features_integration.py
  function mock_hass (line 51) | def mock_hass():
  class TestHeaterCoolerNoFeatures (line 60) | class TestHeaterCoolerNoFeatures:
    method test_config_flow_no_features (line 63) | async def test_config_flow_no_features(self, mock_hass):
  class TestHeaterCoolerFloorHeatingOnly (line 119) | class TestHeaterCoolerFloorHeatingOnly:
    method test_config_flow_floor_heating_only (line 122) | async def test_config_flow_floor_heating_only(self, mock_hass):
  class TestHeaterCoolerFanOnly (line 180) | class TestHeaterCoolerFanOnly:
    method test_config_flow_fan_only (line 183) | async def test_config_flow_fan_only(self, mock_hass):
  class TestHeaterCoolerHumidityOnly (line 236) | class TestHeaterCoolerHumidityOnly:
    method test_config_flow_humidity_only (line 239) | async def test_config_flow_humidity_only(self, mock_hass):
  class TestHeaterCoolerAllFeatures (line 297) | class TestHeaterCoolerAllFeatures:
    method test_config_flow_all_features (line 300) | async def test_config_flow_all_features(self, mock_hass):
  class TestHeaterCoolerCommonCombinations (line 435) | class TestHeaterCoolerCommonCombinations:
    method test_floor_and_openings (line 438) | async def test_floor_and_openings(self, mock_hass):
    method test_fan_and_humidity (line 486) | async def test_fan_and_humidity(self, mock_hass):
  class TestHeaterCoolerFeatureOrdering (line 534) | class TestHeaterCoolerFeatureOrdering:
    method test_complete_feature_ordering (line 537) | async def test_complete_feature_ordering(self, mock_hass):
  class TestHeaterCoolerAvailableFeatures (line 637) | class TestHeaterCoolerAvailableFeatures:
    method test_all_features_available (line 640) | async def test_all_features_available(self, mock_hass):
  class TestHeaterCoolerPartialOverride (line 670) | class TestHeaterCoolerPartialOverride:
    method test_tolerance_partial_override_heat_only (line 673) | async def test_tolerance_partial_override_heat_only(self, mock_hass):
    method test_tolerance_partial_override_cool_only (line 739) | async def test_tolerance_partial_override_cool_only(self, mock_hass):
    method test_tolerance_partial_override_mixed (line 805) | async def test_tolerance_partial_override_mixed(self, mock_hass):

FILE: tests/config_flow/test_heater_cooler_flow.py
  function mock_hass (line 30) | def mock_hass():
  class TestHeaterCoolerConfigFlow (line 39) | class TestHeaterCoolerConfigFlow:
    method test_config_flow_completes_without_error (line 42) | async def test_config_flow_completes_without_error(self, mock_hass):
    method test_valid_configuration_created (line 77) | async def test_valid_configuration_created(self, mock_hass):
    method test_all_required_fields_present (line 120) | async def test_all_required_fields_present(self, mock_hass):
    method test_advanced_settings_flattened_correctly (line 148) | async def test_advanced_settings_flattened_correctly(self, mock_hass):
    method test_validation_same_heater_cooler_entity (line 179) | async def test_validation_same_heater_cooler_entity(self, mock_hass):
    method test_validation_same_heater_sensor_entity (line 202) | async def test_validation_same_heater_sensor_entity(self, mock_hass):
  class TestHeaterCoolerOptionsFlow (line 225) | class TestHeaterCoolerOptionsFlow:
    method test_options_flow_omits_name_field (line 228) | async def test_options_flow_omits_name_field(self, mock_hass):
    method test_options_flow_prefills_all_fields (line 262) | async def test_options_flow_prefills_all_fields(self, mock_hass):
    method test_options_flow_preserves_unmodified_fields (line 317) | async def test_options_flow_preserves_unmodified_fields(self, mock_hass):
    method test_options_flow_system_type_display_non_editable (line 357) | async def test_options_flow_system_type_display_non_editable(self, moc...
    method test_options_flow_completes_without_error (line 396) | async def test_options_flow_completes_without_error(self, mock_hass):
    method test_options_flow_updated_config_matches_data_model (line 425) | async def test_options_flow_updated_config_matches_data_model(self, mo...

FILE: tests/config_flow/test_integration.py
  function hass_mock (line 49) | def hass_mock():
  function config_entry_with_openings (line 58) | def config_entry_with_openings():
  function test_options_flow_openings_schema_creation (line 81) | async def test_options_flow_openings_schema_creation(
  function test_options_flow_openings_data_processing (line 111) | async def test_options_flow_openings_data_processing(
  function test_options_flow_openings_removal (line 179) | async def test_options_flow_openings_removal(hass_mock, config_entry_wit...
  function test_options_flow_with_real_config_entry (line 216) | async def test_options_flow_with_real_config_entry(hass):
  function test_config_flow_does_not_save_transient_flags (line 296) | async def test_config_flow_does_not_save_transient_flags(hass):
  function run_tests (line 349) | async def run_tests():

FILE: tests/config_flow/test_options_entry_helpers.py
  function test_get_entry_fallback_and_instance_attr (line 8) | def test_get_entry_fallback_and_instance_attr():

FILE: tests/config_flow/test_options_flow.py
  function mock_hass (line 56) | def mock_hass():
  function ac_only_config_entry (line 67) | def ac_only_config_entry():
  function dual_system_config_entry (line 84) | def dual_system_config_entry():
  function heat_pump_config_entry (line 102) | def heat_pump_config_entry():
  function dual_stage_config_entry (line 119) | def dual_stage_config_entry():
  function simple_heater_config_entry (line 136) | def simple_heater_config_entry():
  function config_entry_with_presets (line 151) | def config_entry_with_presets():
  function test_ac_only_options_flow_progression (line 175) | async def test_ac_only_options_flow_progression(mock_hass, ac_only_confi...
  function test_ac_only_features_step (line 208) | async def test_ac_only_features_step(mock_hass, ac_only_config_entry):
  function test_options_flow_step_progression (line 239) | async def test_options_flow_step_progression(mock_hass, ac_only_config_e...
  function test_system_type_preservation (line 291) | async def test_system_type_preservation(mock_hass, dual_system_config_en...
  function test_comprehensive_options_flow_multiple_systems (line 319) | async def test_comprehensive_options_flow_multiple_systems(
  function test_openings_configuration_in_options (line 388) | async def test_openings_configuration_in_options(mock_hass, ac_only_conf...
  function test_openings_two_step_options_flow (line 438) | async def test_openings_two_step_options_flow(mock_hass, ac_only_config_...
  function test_simple_heater_select_only_openings_shows_only_openings (line 465) | async def test_simple_heater_select_only_openings_shows_only_openings(
  function test_system_features_fields_and_floor_redirect (line 519) | async def test_system_features_fields_and_floor_redirect(
  function test_heat_pump_options_flow_parity (line 558) | async def test_heat_pump_options_flow_parity(mock_hass, heat_pump_config...
  function test_dual_stage_options_flow_parity (line 581) | async def test_dual_stage_options_flow_parity(mock_hass, dual_stage_conf...
  function test_floor_options_preselects_configured_sensor (line 604) | async def test_floor_options_preselects_configured_sensor(
  function test_heater_cooler_fan_settings_prefilled_in_options_flow (line 639) | async def test_heater_cooler_fan_settings_prefilled_in_options_flow(mock...
  function test_simple_heater_fan_settings_prefilled_in_options_flow (line 723) | async def test_simple_heater_fan_settings_prefilled_in_options_flow(mock...
  function test_ac_only_fan_settings_prefilled_in_options_flow (line 764) | async def test_ac_only_fan_settings_prefilled_in_options_flow(mock_hass):
  function test_heater_cooler_humidity_settings_prefilled_in_options_flow (line 809) | async def test_heater_cooler_humidity_settings_prefilled_in_options_flow...
  function test_simple_heater_humidity_settings_prefilled_in_options_flow (line 849) | async def test_simple_heater_humidity_settings_prefilled_in_options_flow...
  function test_fan_not_configured_skips_fan_step (line 893) | async def test_fan_not_configured_skips_fan_step(mock_hass):
  function test_preset_toggle_checked_when_presets_configured (line 932) | async def test_preset_toggle_checked_when_presets_configured(
  function test_deselected_presets_are_cleaned_up (line 964) | async def test_deselected_presets_are_cleaned_up(mock_hass):
  function test_all_presets_deselected_cleans_all_preset_data (line 1021) | async def test_all_presets_deselected_cleans_all_preset_data(mock_hass):
  function test_ac_only_options_flow_with_fan_and_humidity_enabled (line 1074) | async def test_ac_only_options_flow_with_fan_and_humidity_enabled(mock_h...
  function test_keep_alive_and_min_cycle_always_available_in_options (line 1179) | async def test_keep_alive_and_min_cycle_always_available_in_options(hass):
  function test_options_flow_persists_auto_outside_delta_boost (line 1236) | async def test_options_flow_persists_auto_outside_delta_boost(mock_hass):
  function test_options_flow_persists_use_apparent_temp (line 1290) | async def test_options_flow_persists_use_apparent_temp(mock_hass):

FILE: tests/config_flow/test_preset_templates_config_flow.py
  class TestPresetTemplatesConfigFlow (line 12) | class TestPresetTemplatesConfigFlow:
    method test_config_flow_accepts_template_input (line 16) | async def test_config_flow_accepts_template_input(self, hass: HomeAssi...
    method test_config_flow_static_value_backward_compatible (line 31) | async def test_config_flow_static_value_backward_compatible(
    method test_config_flow_template_syntax_validation (line 50) | async def test_config_flow_template_syntax_validation(self, hass: Home...
    method test_config_flow_valid_template_syntax_accepted (line 67) | async def test_config_flow_valid_template_syntax_accepted(
    method test_config_flow_none_value_accepted (line 90) | async def test_config_flow_none_value_accepted(self, hass: HomeAssista...
    method test_config_flow_invalid_type_rejected (line 103) | async def test_config_flow_invalid_type_rejected(self, hass: HomeAssis...

FILE: tests/config_flow/test_reconfigure_flow.py
  function mock_config_entry (line 22) | def mock_config_entry():
  function test_reconfigure_entry_point (line 35) | async def test_reconfigure_entry_point(mock_config_entry):
  function test_reconfigure_preserves_name (line 61) | async def test_reconfigure_preserves_name(mock_config_entry):
  function test_reconfigure_system_type_change (line 81) | async def test_reconfigure_system_type_change(mock_config_entry):
  function test_reconfigure_keeps_system_type (line 107) | async def test_reconfigure_keeps_system_type(mock_config_entry):
  function test_reconfigure_updates_entity (line 133) | async def test_reconfigure_updates_entity(mock_config_entry):
  function test_reconfigure_uses_update_reload_and_abort (line 166) | async def test_reconfigure_uses_update_reload_and_abort():
  function test_config_flow_uses_create_entry (line 205) | async def test_config_flow_uses_create_entry():
  function test_reconfigure_all_system_types (line 234) | async def test_reconfigure_all_system_types():
  function test_reconfigure_uses_data_parameter_not_data_updates (line 278) | async def test_reconfigure_uses_data_parameter_not_data_updates():
  function run_all_tests (line 356) | async def run_all_tests():

FILE: tests/config_flow/test_reconfigure_flow_e2e_ac_only.py
  function ac_only_entry (line 26) | def ac_only_entry():
  function test_reconfigure_ac_only_minimal_flow (line 39) | async def test_reconfigure_ac_only_minimal_flow(ac_only_entry):
  function test_reconfigure_ac_only_with_fan (line 100) | async def test_reconfigure_ac_only_with_fan(ac_only_entry):
  function test_reconfigure_ac_only_with_humidity (line 163) | async def test_reconfigure_ac_only_with_humidity(ac_only_entry):
  function test_reconfigure_ac_only_with_fan_and_humidity (line 226) | async def test_reconfigure_ac_only_with_fan_and_humidity(ac_only_entry):
  function test_reconfigure_ac_only_all_features (line 303) | async def test_reconfigure_ac_only_all_features(ac_only_entry):
  function test_reconfigure_ac_only_preserves_data (line 424) | async def test_reconfigure_ac_only_preserves_data(ac_only_entry):

FILE: tests/config_flow/test_reconfigure_flow_e2e_heat_pump.py
  function heat_pump_entry (line 30) | def heat_pump_entry():
  function test_reconfigure_heat_pump_minimal_flow (line 44) | async def test_reconfigure_heat_pump_minimal_flow(heat_pump_entry):
  function test_reconfigure_heat_pump_with_floor_heating (line 102) | async def test_reconfigure_heat_pump_with_floor_heating(heat_pump_entry):
  function test_reconfigure_heat_pump_with_fan (line 168) | async def test_reconfigure_heat_pump_with_fan(heat_pump_entry):
  function test_reconfigure_heat_pump_with_humidity (line 221) | async def test_reconfigure_heat_pump_with_humidity(heat_pump_entry):
  function test_reconfigure_heat_pump_all_features (line 274) | async def test_reconfigure_heat_pump_all_features(heat_pump_entry):
  function test_reconfigure_heat_pump_preserves_data (line 412) | async def test_reconfigure_heat_pump_preserves_data(heat_pump_entry):

FILE: tests/config_flow/test_reconfigure_flow_e2e_heater_cooler.py
  function heater_cooler_entry (line 30) | def heater_cooler_entry():
  function test_reconfigure_heater_cooler_minimal_flow (line 44) | async def test_reconfigure_heater_cooler_minimal_flow(heater_cooler_entry):
  function test_reconfigure_heater_cooler_with_floor_heating (line 107) | async def test_reconfigure_heater_cooler_with_floor_heating(heater_coole...
  function test_reconfigure_heater_cooler_with_fan (line 173) | async def test_reconfigure_heater_cooler_with_fan(heater_cooler_entry):
  function test_reconfigure_heater_cooler_with_humidity (line 226) | async def test_reconfigure_heater_cooler_with_humidity(heater_cooler_ent...
  function test_reconfigure_heater_cooler_all_features (line 279) | async def test_reconfigure_heater_cooler_all_features(heater_cooler_entry):
  function test_reconfigure_heater_cooler_preserves_data (line 417) | async def test_reconfigure_heater_cooler_preserves_data(heater_cooler_en...

FILE: tests/config_flow/test_reconfigure_flow_e2e_simple_heater.py
  function simple_heater_entry (line 27) | def simple_heater_entry():
  function test_reconfigure_simple_heater_minimal_flow (line 40) | async def test_reconfigure_simple_heater_minimal_flow(simple_heater_entry):
  function test_reconfigure_simple_heater_with_floor_heating (line 95) | async def test_reconfigure_simple_heater_with_floor_heating(simple_heate...
  function test_reconfigure_simple_heater_with_openings (line 158) | async def test_reconfigure_simple_heater_with_openings(simple_heater_ent...
  function test_reconfigure_simple_heater_with_presets (line 219) | async def test_reconfigure_simple_heater_with_presets(simple_heater_entry):
  function test_reconfigure_simple_heater_all_features (line 280) | async def test_reconfigure_simple_heater_all_features(simple_heater_entry):
  function test_reconfigure_simple_heater_preserves_data (line 387) | async def test_reconfigure_simple_heater_preserves_data(simple_heater_en...

FILE: tests/config_flow/test_reconfigure_system_type_change.py
  function heat_pump_entry_with_features (line 33) | def heat_pump_entry_with_features():
  function heater_cooler_entry_with_features (line 52) | def heater_cooler_entry_with_features():
  function test_system_type_unchanged_preserves_config (line 68) | async def test_system_type_unchanged_preserves_config(heat_pump_entry_wi...
  function test_system_type_change_clears_config (line 112) | async def test_system_type_change_clears_config(heat_pump_entry_with_fea...
  function test_heat_pump_to_heater_cooler_clears_heat_pump_cooling (line 149) | async def test_heat_pump_to_heater_cooler_clears_heat_pump_cooling(
  function test_heater_cooler_to_ac_only_clears_heater (line 177) | async def test_heater_cooler_to_ac_only_clears_heater(
  function test_system_type_change_allows_fresh_configuration (line 209) | async def test_system_type_change_allows_fresh_configuration(
  function test_system_type_change_flag_cleared_before_storage (line 252) | async def test_system_type_change_flag_cleared_before_storage(
  function test_multiple_system_type_changes (line 302) | async def test_multiple_system_type_changes(heat_pump_entry_with_features):
  function test_features_step_shows_configured_features (line 353) | async def test_features_step_shows_configured_features(heat_pump_entry_w...
  function test_uncheck_floor_heating_clears_config (line 391) | async def test_uncheck_floor_heating_clears_config(heat_pump_entry_with_...
  function test_uncheck_fan_clears_config (line 436) | async def test_uncheck_fan_clears_config(heat_pump_entry_with_features):
  function test_uncheck_all_features_clears_all_config (line 478) | async def test_uncheck_all_features_clears_all_config(heat_pump_entry_wi...

FILE: tests/config_flow/test_simple_heater_advanced.py
  function config_flow (line 23) | def config_flow(hass: HomeAssistant):
  function test_simple_heater_advanced_settings_config_flow (line 31) | async def test_simple_heater_advanced_settings_config_flow(
  function test_simple_heater_default_advanced_settings (line 107) | async def test_simple_heater_default_advanced_settings(

FILE: tests/config_flow/test_simple_heater_features_integration.py
  function mock_hass (line 44) | def mock_hass():
  class TestSimpleHeaterNoFeatures (line 53) | class TestSimpleHeaterNoFeatures:
    method test_config_flow_no_features (line 56) | async def test_config_flow_no_features(self, mock_hass):
  class TestSimpleHeaterFloorHeatingOnly (line 115) | class TestSimpleHeaterFloorHeatingOnly:
    method test_config_flow_floor_heating_only (line 118) | async def test_config_flow_floor_heating_only(self, mock_hass):
    method test_floor_heating_schema_contains_required_fields (line 172) | async def test_floor_heating_schema_contains_required_fields(self, moc...
  class TestSimpleHeaterOpeningsOnly (line 197) | class TestSimpleHeaterOpeningsOnly:
    method test_config_flow_openings_only (line 200) | async def test_config_flow_openings_only(self, mock_hass):
  class TestSimpleHeaterPresetsOnly (line 261) | class TestSimpleHeaterPresetsOnly:
    method test_config_flow_presets_only (line 264) | async def test_config_flow_presets_only(self, mock_hass):
  class TestSimpleHeaterAllFeatures (line 323) | class TestSimpleHeaterAllFeatures:
    method test_config_flow_all_features (line 326) | async def test_config_flow_all_features(self, mock_hass):
  class TestSimpleHeaterBlockedFeatures (line 427) | class TestSimpleHeaterBlockedFeatures:
    method test_fan_feature_not_in_schema (line 430) | async def test_fan_feature_not_in_schema(self, mock_hass):
    method test_humidity_feature_not_in_schema (line 449) | async def test_humidity_feature_not_in_schema(self, mock_hass):
    method test_available_features_only (line 468) | async def test_available_features_only(self, mock_hass):
  class TestSimpleHeaterFeatureOrdering (line 496) | class TestSimpleHeaterFeatureOrdering:
    method test_floor_heating_before_openings (line 499) | async def test_floor_heating_before_openings(self, mock_hass):
    method test_openings_before_presets (line 543) | async def test_openings_before_presets(self, mock_hass):
  class TestSimpleHeaterPartialOverride (line 590) | class TestSimpleHeaterPartialOverride:
    method test_tolerance_partial_override_heat_only (line 593) | async def test_tolerance_partial_override_heat_only(self, mock_hass):

FILE: tests/config_flow/test_step_ordering.py
  class TestConfigStepOrdering (line 20) | class TestConfigStepOrdering:
    method test_ac_only_system_step_ordering (line 24) | async def test_ac_only_system_step_ordering(self):
    method test_heat_pump_system_step_ordering (line 93) | async def test_heat_pump_system_step_ordering(self):
    method test_openings_scope_has_all_feature_data (line 204) | async def test_openings_scope_has_all_feature_data(self):
    method test_simple_heater_openings_after_features (line 257) | async def test_simple_heater_openings_after_features(self):

FILE: tests/config_flow/test_translations.py
  function test_config_flow_translations (line 10) | def test_config_flow_translations():

FILE: tests/conftest.py
  function auto_enable_custom_integrations (line 24) | def auto_enable_custom_integrations(enable_custom_integrations):
  function setup_template_test_entities (line 29) | async def setup_template_test_entities(hass: HomeAssistant):

FILE: tests/contracts/test_feature_availability_contracts.py
  function mock_hass (line 43) | def mock_hass():
  class TestFeatureAvailabilityContracts (line 52) | class TestFeatureAvailabilityContracts:
    method test_available_features_per_system_type (line 93) | async def test_available_features_per_system_type(
    method test_blocked_features_per_system_type (line 139) | async def test_blocked_features_per_system_type(
    method test_openings_available_for_all_system_types (line 181) | async def test_openings_available_for_all_system_types(
    method test_presets_available_for_all_system_types (line 218) | async def test_presets_available_for_all_system_types(self, mock_hass,...
    method test_floor_heating_availability_by_system_type (line 253) | async def test_floor_heating_availability_by_system_type(
    method test_fan_availability_by_system_type (line 296) | async def test_fan_availability_by_system_type(
    method test_humidity_availability_by_system_type (line 338) | async def test_humidity_availability_by_system_type(

FILE: tests/contracts/test_feature_ordering_contracts.py
  function mock_hass (line 63) | def mock_hass():
  class TestFeatureOrderingContracts (line 72) | class TestFeatureOrderingContracts:
    method test_features_selection_comes_after_core_settings (line 75) | async def test_features_selection_comes_after_core_settings(self, mock...
    method test_openings_comes_before_presets (line 114) | async def test_openings_comes_before_presets(self, mock_hass):
    method test_presets_is_final_configuration_step (line 136) | async def test_presets_is_final_configuration_step(self, mock_hass):
    method test_complete_step_ordering_per_system_type (line 186) | async def test_complete_step_ordering_per_system_type(
    method _get_core_input_for_system_type (line 242) | def _get_core_input_for_system_type(self, system_type):
    method test_feature_config_steps_come_after_features_selection (line 285) | async def test_feature_config_steps_come_after_features_selection(self...
    method test_no_feature_config_steps_when_features_disabled (line 306) | async def test_no_feature_config_steps_when_features_disabled(self, mo...

FILE: tests/contracts/test_feature_schema_contracts.py
  function mock_hass (line 43) | def mock_hass():
  class TestFeatureSchemaContracts (line 52) | class TestFeatureSchemaContracts:
    method test_floor_heating_schema_keys (line 55) | async def test_floor_heating_schema_keys(self, mock_hass):
    method test_fan_schema_keys (line 80) | async def test_fan_schema_keys(self, mock_hass):
    method test_humidity_schema_keys (line 110) | async def test_humidity_schema_keys(self, mock_hass):
    method test_openings_schema_has_list_configuration (line 160) | async def test_openings_schema_has_list_configuration(self, mock_hass):
    method test_presets_schema_supports_dynamic_presets (line 194) | async def test_presets_schema_supports_dynamic_presets(self, mock_hass):
    method test_floor_heating_schema_has_numeric_defaults (line 222) | async def test_floor_heating_schema_has_numeric_defaults(self, mock_ha...
    method test_fan_schema_has_boolean_selectors (line 249) | async def test_fan_schema_has_boolean_selectors(self, mock_hass):
    method test_humidity_schema_has_numeric_fields (line 277) | async def test_humidity_schema_has_numeric_fields(self, mock_hass):
    method test_openings_scope_configuration_exists (line 309) | async def test_openings_scope_configuration_exists(self, mock_hass):
    method test_presets_temperature_fields_adapt_to_heat_cool_mode (line 341) | async def test_presets_temperature_fields_adapt_to_heat_cool_mode(self...
  class TestFeatureSchemaDefaults (line 365) | class TestFeatureSchemaDefaults:
    method test_floor_heating_defaults_are_reasonable (line 368) | async def test_floor_heating_defaults_are_reasonable(self, mock_hass):
    method test_fan_hot_tolerance_has_default (line 405) | async def test_fan_hot_tolerance_has_default(self, mock_hass):
    method test_humidity_target_has_default (line 426) | async def test_humidity_target_has_default(self, mock_hass):

FILE: tests/edge_cases/test_issue_10_tolerance_precision.py
  function _setup_sensor (line 38) | def _setup_sensor(hass, sensor, temp):
  function async_set_temperature (line 43) | async def async_set_temperature(
  function setup_comp_issue_10 (line 70) | async def setup_comp_issue_10(hass):
  function test_issue_10_tolerance_precision_heat_cool_mode (line 76) | async def test_issue_10_tolerance_precision_heat_cool_mode(hass, setup_c...
  function test_issue_10_cooling_side (line 219) | async def test_issue_10_cooling_side(hass, setup_comp_issue_10):

FILE: tests/edge_cases/test_issue_461_redundant_commands.py
  function test_issue_461_ac_dual_system_sensor_updates (line 26) | async def test_issue_461_ac_dual_system_sensor_updates(hass: HomeAssista...
  function test_issue_461_ac_cooling_with_default_keepalive (line 167) | async def test_issue_461_ac_cooling_with_default_keepalive(hass: HomeAss...
  function test_issue_461_solution_disable_keepalive (line 280) | async def test_issue_461_solution_disable_keepalive(hass: HomeAssistant)...

FILE: tests/edge_cases/test_issue_467_idle_continuous_off.py
  function test_idle_mode_no_continuous_turn_off (line 37) | async def test_idle_mode_no_continuous_turn_off(hass: HomeAssistant) -> ...
  function test_heat_to_idle_transition_single_turn_off (line 123) | async def test_heat_to_idle_transition_single_turn_off(hass: HomeAssista...
  function test_idle_keep_alive_respects_device_state (line 175) | async def test_idle_keep_alive_respects_device_state(hass: HomeAssistant...
  function test_idle_device_unexpectedly_on_keep_alive_turns_off (line 232) | async def test_idle_device_unexpectedly_on_keep_alive_turns_off(
  function test_cooler_idle_mode_no_continuous_turn_off (line 307) | async def test_cooler_idle_mode_no_continuous_turn_off(hass: HomeAssista...
  function test_heat_pump_idle_mode_no_continuous_turn_off (line 380) | async def test_heat_pump_idle_mode_no_continuous_turn_off(hass: HomeAssi...

FILE: tests/edge_cases/test_issue_468_precision_rounding.py
  class TestIssue468PrecisionFromConfigEntry (line 33) | class TestIssue468PrecisionFromConfigEntry:
    method test_precision_string_from_config_entry_is_converted_to_float (line 43) | async def test_precision_string_from_config_entry_is_converted_to_float(
  class TestCorrectFloatPrecisionBehavior (line 117) | class TestCorrectFloatPrecisionBehavior:
    method test_current_temperature_with_float_precision (line 120) | async def test_current_temperature_with_float_precision(self, hass: Ho...
    method test_target_temperature_with_float_precision (line 147) | async def test_target_temperature_with_float_precision(self, hass: Hom...
    method test_set_non_whole_temperature_with_float_precision (line 174) | async def test_set_non_whole_temperature_with_float_precision(
  class TestIssue468AllEdgeCases (line 210) | class TestIssue468AllEdgeCases:
    method test_edge_case_1_sensor_temperature_not_rounded (line 220) | async def test_edge_case_1_sensor_temperature_not_rounded(
    method test_edge_case_2_preset_target_temp_matches_config (line 264) | async def test_edge_case_2_preset_target_temp_matches_config(
    method test_edge_case_2b_auto_preset_selection_with_string_preset_temps (line 308) | async def test_edge_case_2b_auto_preset_selection_with_string_preset_t...
    method test_edge_case_3_set_temperature_not_rounded (line 392) | async def test_edge_case_3_set_temperature_not_rounded(self, hass: Hom...
    method test_edge_case_4_temp_step_increments_correctly (line 441) | async def test_edge_case_4_temp_step_increments_correctly(
  class TestTemplatePresetsYAMLWithAutoSelection (line 516) | class TestTemplatePresetsYAMLWithAutoSelection:
    method test_yaml_template_preset_auto_selection_single_temp (line 523) | async def test_yaml_template_preset_auto_selection_single_temp(
    method test_yaml_template_preset_dynamic_value_change (line 592) | async def test_yaml_template_preset_dynamic_value_change(
    method test_yaml_old_style_template_preset (line 653) | async def test_yaml_old_style_template_preset(

FILE: tests/edge_cases/test_issue_469_off_state_control_bypass.py
  function setup_dual_thermostat (line 38) | async def setup_dual_thermostat(hass: HomeAssistant, config_overrides=No...
  function test_off_mode_temperature_change_does_not_turn_on (line 84) | async def test_off_mode_temperature_change_does_not_turn_on(
  function test_off_mode_temperature_change_hot_does_not_turn_on (line 132) | async def test_off_mode_temperature_change_hot_does_not_turn_on(
  function test_off_mode_sensor_update_does_not_turn_on (line 167) | async def test_off_mode_sensor_update_does_not_turn_on(
  function test_off_mode_stays_off_with_time_trigger (line 214) | async def test_off_mode_stays_off_with_time_trigger(
  function test_off_mode_multiple_temperature_changes (line 256) | async def test_off_mode_multiple_temperature_changes(

FILE: tests/edge_cases/test_issue_480_heater_cooler_both_fire.py
  function setup_sensor (line 33) | def setup_sensor(hass: HomeAssistant, temp: float) -> None:
  function setup_switch_dual_heater_cooler (line 38) | def setup_switch_dual_heater_cooler(
  function setup_comp_issue_480_config1 (line 62) | async def setup_comp_issue_480_config1(hass: HomeAssistant) -> None:
  function setup_comp_issue_480_config2 (line 104) | async def setup_comp_issue_480_config2(hass: HomeAssistant) -> None:
  class TestIssue480HeaterCoolerBothFire (line 144) | class TestIssue480HeaterCoolerBothFire:
    method test_initial_heat_cool_mode_with_temp_sensor_available (line 148) | async def test_initial_heat_cool_mode_with_temp_sensor_available(
    method test_heat_cool_mode_temp_within_range_neither_fires (line 220) | async def test_heat_cool_mode_temp_within_range_neither_fires(
    method test_heat_cool_mode_temp_too_cold_only_heater_fires (line 270) | async def test_heat_cool_mode_temp_too_cold_only_heater_fires(
    method test_heat_cool_mode_temp_too_hot_only_cooler_fires (line 317) | async def test_heat_cool_mode_temp_too_hot_only_cooler_fires(
    method test_switch_from_off_to_heat_cool_temp_in_range (line 363) | async def test_switch_from_off_to_heat_cool_temp_in_range(
    method test_switch_from_off_to_heat_cool_temp_too_cold (line 416) | async def test_switch_from_off_to_heat_cool_temp_too_cold(
    method test_switch_from_off_to_heat_cool_temp_too_hot (line 450) | async def test_switch_from_off_to_heat_cool_temp_too_hot(
    method test_restored_state_heat_cool_mode (line 484) | async def test_restored_state_heat_cool_mode(
    method test_heat_cool_mode_prevents_duplicate_toggle_calls (line 566) | async def test_heat_cool_mode_prevents_duplicate_toggle_calls(

FILE: tests/edge_cases/test_issue_484_keep_alive_timedelta.py
  function test_keep_alive_float_converted_to_timedelta (line 34) | async def test_keep_alive_float_converted_to_timedelta(hass: HomeAssista...
  function test_min_cycle_duration_int_converted_to_timedelta (line 77) | async def test_min_cycle_duration_int_converted_to_timedelta(hass: HomeA...
  function test_stale_duration_float_converted_to_timedelta (line 113) | async def test_stale_duration_float_converted_to_timedelta(hass: HomeAss...
  function test_timedelta_values_preserved (line 149) | async def test_timedelta_values_preserved(hass: HomeAssistant):
  function test_mixed_numeric_and_time_normalization (line 186) | async def test_mixed_numeric_and_time_normalization(hass: HomeAssistant):
  function test_keep_alive_dict_deserialized_to_timedelta (line 227) | async def test_keep_alive_dict_deserialized_to_timedelta(hass: HomeAssis...
  function test_min_cycle_duration_dict_to_timedelta (line 276) | async def test_min_cycle_duration_dict_to_timedelta(hass: HomeAssistant):
  function test_stale_duration_dict_to_timedelta (line 307) | async def test_stale_duration_dict_to_timedelta(hass: HomeAssistant):
  function test_options_flow_with_dict_keep_alive (line 338) | async def test_options_flow_with_dict_keep_alive(hass: HomeAssistant):

FILE: tests/edge_cases/test_issue_499_multiple_thermostats_unavailable.py
  function master_bedroom_config (line 29) | def master_bedroom_config():
  function computer_room_config (line 53) | def computer_room_config():
  function first_floor_config (line 85) | def first_floor_config():
  function setup_entities_for_config (line 117) | async def setup_entities_for_config(hass: HomeAssistant, config: dict):
  function setup_thermostat_with_config (line 146) | async def setup_thermostat_with_config(
  function test_master_bedroom_thermostat_availability_on_restart (line 170) | async def test_master_bedroom_thermostat_availability_on_restart(
  function test_computer_room_thermostat_availability_on_restart (line 266) | async def test_computer_room_thermostat_availability_on_restart(
  function test_first_floor_thermostat_availability_on_restart (line 356) | async def test_first_floor_thermostat_availability_on_restart(
  function test_all_thermostats_together_with_restart (line 445) | async def test_all_thermostats_together_with_restart(

FILE: tests/edge_cases/test_issue_499_yaml_entity_unavailable_on_startup.py
  function test_yaml_heater_cooler_unavailable_entities_on_startup (line 28) | async def test_yaml_heater_cooler_unavailable_entities_on_startup(hass: ...
  function test_yaml_secondary_heater_cooler_unavailable_on_startup (line 146) | async def test_yaml_secondary_heater_cooler_unavailable_on_startup(hass:...
  function test_yaml_multiple_thermostats_unavailable_entities (line 239) | async def test_yaml_multiple_thermostats_unavailable_entities(hass: Home...
  function test_yaml_heater_cooler_none_temperature_on_startup (line 352) | async def test_yaml_heater_cooler_none_temperature_on_startup(hass: Home...

FILE: tests/edge_cases/test_issue_506_behavior_tolerance_ignored.py
  function test_heating_behavior_with_tolerance (line 40) | async def test_heating_behavior_with_tolerance(hass: HomeAssistant):
  function test_cooling_behavior_with_tolerance (line 165) | async def test_cooling_behavior_with_tolerance(hass: HomeAssistant):
  function test_heat_cool_mode_range_with_tolerance (line 265) | async def test_heat_cool_mode_range_with_tolerance(hass: HomeAssistant):

FILE: tests/edge_cases/test_issue_506_tolerance_in_range_mode.py
  function test_heater_uses_hot_tolerance_in_range_mode (line 38) | async def test_heater_uses_hot_tolerance_in_range_mode(
  function test_cooler_uses_cold_tolerance_in_range_mode (line 137) | async def test_cooler_uses_cold_tolerance_in_range_mode(
  function test_heater_stays_on_between_target_and_tolerance (line 231) | async def test_heater_stays_on_between_target_and_tolerance(

FILE: tests/edge_cases/test_issue_506_user_exact_scenario.py
  function test_user_exact_config_with_hot_tolerance_set (line 39) | async def test_user_exact_config_with_hot_tolerance_set(hass: HomeAssist...
  function test_user_exact_config_without_tolerances (line 105) | async def test_user_exact_config_without_tolerances(hass: HomeAssistant):
  function test_tolerance_actually_used_in_heat_cool_mode (line 160) | async def test_tolerance_actually_used_in_heat_cool_mode(hass: HomeAssis...

FILE: tests/edge_cases/test_issue_506_yaml_tolerance_defaults.py
  function test_yaml_config_tolerance_defaults_applied (line 40) | async def test_yaml_config_tolerance_defaults_applied(hass: HomeAssistant):
  function test_yaml_config_explicit_tolerance_values_respected (line 106) | async def test_yaml_config_explicit_tolerance_values_respected(hass: Hom...
  function test_yaml_config_zero_tolerance_values_respected (line 163) | async def test_yaml_config_zero_tolerance_values_respected(hass: HomeAss...

FILE: tests/edge_cases/test_issue_518_heater_turns_off_prematurely.py
  function test_heater_stays_on_until_target_plus_hot_tolerance (line 29) | async def test_heater_stays_on_until_target_plus_hot_tolerance(hass: Hom...
  function test_cooler_stays_on_until_target_minus_cold_tolerance (line 146) | async def test_cooler_stays_on_until_target_minus_cold_tolerance(hass: H...

FILE: tests/features/test_ac_features_ux.py
  function test_user_experience_flow (line 17) | def test_user_experience_flow():
  function test_form_responsiveness (line 85) | def test_form_responsiveness():
  function test_feature_discoverability (line 122) | def test_feature_discoverability():
  function test_progressive_disclosure (line 146) | def test_progressive_disclosure():
  function main (line 210) | def main():

FILE: tests/features/test_advanced_toggle_feature.py
  function test_basic_schema (line 19) | def test_basic_schema():
  function test_advanced_schema (line 74) | def test_advanced_schema():
  function test_schema_differences (line 110) | def test_schema_differences():
  function test_schema_validation (line 132) | def test_schema_validation():
  function test_realistic_flow (line 182) | def test_realistic_flow():
  function main (line 236) | def main():

FILE: tests/features/test_feature_hvac_mode_interactions.py
  function mock_hass (line 49) | def mock_hass():
  class TestAcOnlyModeInteractions (line 58) | class TestAcOnlyModeInteractions:
    method test_fan_feature_adds_fan_only_mode (line 61) | async def test_fan_feature_adds_fan_only_mode(self, mock_hass):
    method test_humidity_feature_adds_dry_mode (line 106) | async def test_humidity_feature_adds_dry_mode(self, mock_hass):
    method test_fan_and_humidity_add_both_modes (line 153) | async def test_fan_and_humidity_add_both_modes(self, mock_hass):
  class TestHeaterCoolerModeInteractions (line 209) | class TestHeaterCoolerModeInteractions:
    method test_fan_feature_adds_fan_only_mode (line 212) | async def test_fan_feature_adds_fan_only_mode(self, mock_hass):
    method test_humidity_feature_adds_dry_mode (line 256) | async def test_humidity_feature_adds_dry_mode(self, mock_hass):
  class TestHeatPumpModeInteractions (line 302) | class TestHeatPumpModeInteractions:
    method test_fan_feature_adds_fan_only_mode (line 305) | async def test_fan_feature_adds_fan_only_mode(self, mock_hass):
    method test_humidity_feature_adds_dry_mode (line 349) | async def test_humidity_feature_adds_dry_mode(self, mock_hass):
  class TestSimpleHeaterModeInteractions (line 395) | class TestSimpleHeaterModeInteractions:
    method test_no_additional_modes_for_simple_heater (line 398) | async def test_no_additional_modes_for_simple_heater(self, mock_hass):

FILE: tests/features/test_heater_cooler_with_fan.py
  function mock_hass (line 29) | def mock_hass():
  class TestHeaterCoolerWithFan (line 38) | class TestHeaterCoolerWithFan:
    method test_fan_feature_configuration_step_appears (line 41) | async def test_fan_feature_configuration_step_appears(self, mock_hass):
    method test_fan_settings_saved_under_correct_keys (line 72) | async def test_fan_settings_saved_under_correct_keys(self, mock_hass):
    method test_fan_hot_tolerance_has_default_value (line 110) | async def test_fan_hot_tolerance_has_default_value(self, mock_hass):
    method test_fan_hot_tolerance_toggle_is_optional (line 135) | async def test_fan_hot_tolerance_toggle_is_optional(self, mock_hass):
    method test_fan_feature_with_heater_cooler_complete_flow (line 161) | async def test_fan_feature_with_heater_cooler_complete_flow(self, mock...
    method test_fan_feature_settings_match_schema (line 208) | async def test_fan_feature_settings_match_schema(self, mock_hass):

FILE: tests/features/test_heater_cooler_with_humidity.py
  function mock_hass (line 28) | def mock_hass():
  class TestHeaterCoolerWithHumidity (line 37) | class TestHeaterCoolerWithHumidity:
    method test_humidity_feature_configuration_step_appears (line 40) | async def test_humidity_feature_configuration_step_appears(self, mock_...
    method test_humidity_settings_saved_under_correct_keys (line 71) | async def test_humidity_settings_saved_under_correct_keys(self, mock_h...
    method test_humidity_feature_with_heater_cooler_complete_flow (line 105) | async def test_humidity_feature_with_heater_cooler_complete_flow(self,...
    method test_humidity_feature_settings_match_schema (line 152) | async def test_humidity_feature_settings_match_schema(self, mock_hass):
    method test_humidity_sensor_is_optional_entity_field (line 168) | async def test_humidity_sensor_is_optional_entity_field(self, mock_hass):
    method test_humidity_with_fan_both_enabled (line 189) | async def test_humidity_with_fan_both_enabled(self, mock_hass):

FILE: tests/features/test_openings_with_hvac_modes.py
  function mock_hass (line 43) | def mock_hass():
  class TestOpeningsHeaterCooler (line 52) | class TestOpeningsHeaterCooler:
    method test_openings_single_sensor (line 55) | async def test_openings_single_sensor(self, mock_hass):
    method test_openings_multiple_sensors (line 103) | async def test_openings_multiple_sensors(self, mock_hass):
  class TestOpeningsSimpleHeater (line 158) | class TestOpeningsSimpleHeater:
    method test_openings_simple_heater (line 161) | async def test_openings_simple_heater(self, mock_hass):
  class TestOpeningsAcOnly (line 204) | class TestOpeningsAcOnly:
    method test_openings_ac_only (line 207) | async def test_openings_ac_only(self, mock_hass):
  class TestOpeningsHeatPump (line 252) | class TestOpeningsHeatPump:
    method test_openings_heat_pump (line 255) | async def test_openings_heat_pump(self, mock_hass):
  class TestOpeningsWithOtherFeatures (line 303) | class TestOpeningsWithOtherFeatures:
    method test_openings_with_fan_and_humidity (line 306) | async def test_openings_with_fan_and_humidity(self, mock_hass):

FILE: tests/features/test_presets_with_all_features.py
  function mock_hass (line 53) | def mock_hass():
  class TestPresetsBaseline (line 62) | class TestPresetsBaseline:
    method test_presets_only_simple_heater (line 65) | async def test_presets_only_simple_heater(self, mock_hass):
  class TestPresetsWithFloorHeating (line 118) | class TestPresetsWithFloorHeating:
    method test_presets_after_floor_heating (line 121) | async def test_presets_after_floor_heating(self, mock_hass):
  class TestPresetsWithOpenings (line 180) | class TestPresetsWithOpenings:
    method test_presets_after_openings (line 183) | async def test_presets_after_openings(self, mock_hass):
  class TestPresetsWithFanAndHumidity (line 244) | class TestPresetsWithFanAndHumidity:
    method test_presets_after_fan_and_humidity (line 247) | async def test_presets_after_fan_and_humidity(self, mock_hass):
  class TestPresetsWithAllFeatures (line 318) | class TestPresetsWithAllFeatures:
    method test_presets_last_with_all_features (line 321) | async def test_presets_last_with_all_features(self, mock_hass):
  class TestPresetSelection (line 427) | class TestPresetSelection:
    method test_multiple_presets_selected (line 430) | async def test_multiple_presets_selected(self, mock_hass):
    method test_single_preset_selected (line 480) | async def test_single_preset_selected(self, mock_hass):
  class TestPresetStepOrdering (line 519) | class TestPresetStepOrdering:
    method test_presets_always_last_before_create_entry (line 522) | async def test_presets_always_last_before_create_entry(self, mock_hass):

FILE: tests/managers/test_environment_manager.py
  function basic_config (line 26) | def basic_config():
  function config_with_mode_specific_tolerances (line 36) | def config_with_mode_specific_tolerances():
  function environment_manager (line 48) | def environment_manager(hass, basic_config):
  function environment_manager_with_tolerances (line 54) | def environment_manager_with_tolerances(hass, config_with_mode_specific_...
  class TestSetHvacMode (line 59) | class TestSetHvacMode:
    method test_set_hvac_mode_stores_mode_correctly (line 63) | async def test_set_hvac_mode_stores_mode_correctly(self, hass, environ...
  class TestGetActiveToleranceForMode (line 78) | class TestGetActiveToleranceForMode:
    method test_heat_mode_uses_heat_tolerance (line 82) | async def test_heat_mode_uses_heat_tolerance(
    method test_cool_mode_uses_cool_tolerance (line 95) | async def test_cool_mode_uses_cool_tolerance(
    method test_fan_only_mode_uses_cool_tolerance (line 108) | async def test_fan_only_mode_uses_cool_tolerance(
    method test_heat_cool_mode_heating_uses_heat_tolerance (line 121) | async def test_heat_cool_mode_heating_uses_heat_tolerance(
    method test_heat_cool_mode_cooling_uses_cool_tolerance (line 136) | async def test_heat_cool_mode_cooling_uses_cool_tolerance(
    method test_legacy_fallback_when_heat_tolerance_none (line 151) | async def test_legacy_fallback_when_heat_tolerance_none(
    method test_legacy_fallback_when_cool_tolerance_none (line 164) | async def test_legacy_fallback_when_cool_tolerance_none(
    method test_legacy_fallback_when_both_tolerances_none (line 177) | async def test_legacy_fallback_when_both_tolerances_none(self, hass):
    method test_tolerance_selection_with_none_hvac_mode (line 193) | async def test_tolerance_selection_with_none_hvac_mode(
  class TestIsTooColdWithModeAwareness (line 207) | class TestIsTooColdWithModeAwareness:
    method test_is_too_cold_uses_heat_tolerance_in_heat_mode (line 211) | async def test_is_too_cold_uses_heat_tolerance_in_heat_mode(
    method test_is_too_cold_uses_legacy_when_no_mode_specific (line 234) | async def test_is_too_cold_uses_legacy_when_no_mode_specific(
  class TestIsTooHotWithModeAwareness (line 255) | class TestIsTooHotWithModeAwareness:
    method test_is_too_hot_uses_cool_tolerance_in_cool_mode (line 259) | async def test_is_too_hot_uses_cool_tolerance_in_cool_mode(
    method test_is_too_hot_uses_legacy_when_no_mode_specific (line 282) | async def test_is_too_hot_uses_legacy_when_no_mode_specific(
  class TestSetTempsFromPresetWithTemplates (line 303) | class TestSetTempsFromPresetWithTemplates:
    method test_template_preset_target_temp_evaluated (line 311) | async def test_template_preset_target_temp_evaluated(
    method test_template_preset_range_temps_evaluated (line 335) | async def test_template_preset_range_temps_evaluated(
    method test_template_preset_range_fallback_to_heat (line 364) | async def test_template_preset_range_fallback_to_heat(
    method test_template_preset_range_fallback_to_cool (line 391) | async def test_template_preset_range_fallback_to_cool(
  class TestIsWithinFanTolerance (line 418) | class TestIsWithinFanTolerance:
    method test_fan_tolerance_zero_never_triggers (line 429) | async def test_fan_tolerance_zero_never_triggers(self, hass):
    method test_fan_tolerance_positive_creates_valid_zone (line 459) | async def test_fan_tolerance_positive_creates_valid_zone(self, hass):
    method test_fan_tolerance_none_returns_false (line 495) | async def test_fan_tolerance_none_returns_false(self, hass):
    method test_fan_tolerance_with_no_current_temp (line 509) | async def test_fan_tolerance_with_no_current_temp(self, hass):

FILE: tests/managers/test_hvac_device_factory.py
  class TestFanHotToleranceWithoutCooler (line 16) | class TestFanHotToleranceWithoutCooler:
    method test_fan_hot_tolerance_without_cooler_logs_warning (line 25) | async def test_fan_hot_tolerance_without_cooler_logs_warning(
    method test_fan_hot_tolerance_with_cooler_no_warning (line 71) | async def test_fan_hot_tolerance_with_cooler_no_warning(
    method test_fan_hot_tolerance_with_ac_mode_no_warning (line 118) | async def test_fan_hot_tolerance_with_ac_mode_no_warning(

FILE: tests/managers/test_preset_manager_templates.py
  class TestPresetManagerTemplateIntegration (line 15) | class TestPresetManagerTemplateIntegration:
    method test_preset_manager_calls_template_evaluation (line 19) | async def test_preset_manager_calls_template_evaluation(
    method test_preset_manager_applies_evaluated_temperature (line 53) | async def test_preset_manager_applies_evaluated_temperature(
    method test_preset_manager_range_mode_with_templates (line 93) | async def test_preset_manager_range_mode_with_templates(

FILE: tests/openings/test_openings_config_flow.py
  function test_openings_processing_logic (line 4) | def test_openings_processing_logic():

FILE: tests/openings/test_openings_multiselect.py
  function test_openings_multiselect_processing (line 4) | def test_openings_multiselect_processing():
  function test_entity_display_name_extraction (line 89) | def test_entity_display_name_extraction():

FILE: tests/openings/test_openings_options_flow.py
  function mock_config_entry (line 17) | def mock_config_entry():
  function test_options_flow_includes_openings_step (line 33) | def test_options_flow_includes_openings_step():
  function test_options_flow_skips_openings_when_not_configured (line 64) | def test_options_flow_skips_openings_when_not_configured():

FILE: tests/openings/test_scope_generation.py
  class MockFlowInstance (line 9) | class MockFlowInstance:
    method async_show_form (line 12) | def async_show_form(self, step_id, data_schema, description_placeholde...
  function extract_scope_options_from_schema (line 17) | def extract_scope_options_from_schema(schema_dict):
  class TestOpeningsScopeGeneration (line 45) | class TestOpeningsScopeGeneration:
    method test_ac_only_system_scope_options (line 49) | async def test_ac_only_system_scope_options(self):
    method test_simple_heater_scope_options (line 85) | async def test_simple_heater_scope_options(self):
    method test_heat_pump_scope_options (line 120) | async def test_heat_pump_scope_options(self):
    method test_dual_system_full_features_scope_options (line 152) | async def test_dual_system_full_features_scope_options(self):
    method test_fan_mode_only_scope_options (line 186) | async def test_fan_mode_only_scope_options(self):
    method test_dual_system_without_heat_cool_mode (line 217) | async def test_dual_system_without_heat_cool_mode(self):
  function test_ac_only_system_scope_options (line 254) | async def test_ac_only_system_scope_options(self):
  function test_simple_heater_scope_options (line 296) | async def test_simple_heater_scope_options(self):
  function test_heat_pump_scope_options (line 337) | async def test_heat_pump_scope_options(self):
  function test_dual_system_full_features_scope_options (line 376) | async def test_dual_system_full_features_scope_options(self):
  function test_fan_mode_only_scope_options (line 417) | async def test_fan_mode_only_scope_options(self):
  function test_dual_system_without_heat_cool_mode (line 455) | async def test_dual_system_without_heat_cool_mode(self):

FILE: tests/preset_env/test_preset_env_templates.py
  class TestStaticValueBackwardCompatibility (line 14) | class TestStaticValueBackwardCompatibility:
    method test_static_value_backward_compatible (line 18) | async def test_static_value_backward_compatible(self, hass: HomeAssist...
    method test_static_value_no_template_tracking (line 31) | async def test_static_value_no_template_tracking(self, hass: HomeAssis...
    method test_get_temperature_static_value (line 47) | async def test_get_temperature_static_value(self, hass: HomeAssistant):
    method test_static_range_mode_temperatures (line 59) | async def test_static_range_mode_temperatures(self, hass: HomeAssistant):
    method test_integer_converted_to_float (line 75) | async def test_integer_converted_to_float(self, hass: HomeAssistant):
  class TestTemplateDetectionAndEvaluation (line 88) | class TestTemplateDetectionAndEvaluation:
    method test_template_detection_string_value (line 92) | async def test_template_detection_string_value(
    method test_entity_extraction_simple (line 106) | async def test_entity_extraction_simple(
    method test_template_evaluation_success (line 118) | async def test_template_evaluation_success(
    method test_template_evaluation_entity_unavailable (line 135) | async def test_template_evaluation_entity_unavailable(
    method test_template_evaluation_fallback_to_default (line 159) | async def test_template_evaluation_fallback_to_default(
    method test_template_with_filters (line 174) | async def test_template_with_filters(
    method test_range_mode_with_templates (line 190) | async def test_range_mode_with_templates(
  class TestComplexConditionalTemplates (line 212) | class TestComplexConditionalTemplates:
    method test_template_complex_conditional (line 216) | async def test_template_complex_conditional(
    method test_entity_extraction_multiple_entities (line 242) | async def test_entity_extraction_multiple_entities(
    method test_template_with_multiple_conditions (line 260) | async def test_template_with_multiple_conditions(
  class TestRangeModeWithTemplates (line 299) | class TestRangeModeWithTemplates:
    method test_range_mode_mixed_static_template (line 303) | async def test_range_mode_mixed_static_template(

FILE: tests/presets/test_comprehensive_preset_logic.py
  function test_comprehensive_preset_logic (line 12) | async def test_comprehensive_preset_logic():
  function run_test (line 274) | def run_test():

FILE: tests/presets/test_preset_form_organization.py
  function test_preset_selection_schema (line 19) | def test_preset_selection_schema():
  function test_preset_schema_with_selected_presets_only (line 30) | def test_preset_schema_with_selected_presets_only():
  function test_preset_schema_with_selected_presets_and_features (line 56) | def test_preset_schema_with_selected_presets_and_features():
  function test_preset_schema_backward_compatibility (line 86) | def test_preset_schema_backward_compatibility():
  function test_preset_schema_basic_only (line 101) | def test_preset_schema_basic_only():
  function test_preset_schema_with_humidity (line 114) | def test_preset_schema_with_humidity():
  function test_preset_schema_with_heat_cool_mode (line 127) | def test_preset_schema_with_heat_cool_mode():
  function test_preset_schema_with_floor_heating (line 138) | def test_preset_schema_with_floor_heating():
  function test_preset_schema_with_fan_mode (line 149) | def test_preset_schema_with_fan_mode():
  function test_preset_schema_comprehensive (line 164) | def test_preset_schema_comprehensive():
  function test_preset_organization_by_preset (line 184) | def test_preset_organization_by_preset():

FILE: tests/test_auto_mode_availability.py
  function _make_feature_manager (line 22) | def _make_feature_manager(config: dict) -> FeatureManager:
  function test_is_configured_for_auto_mode_true (line 101) | def test_is_configured_for_auto_mode_true(config: dict) -> None:
  function test_is_configured_for_auto_mode_false (line 147) | def test_is_configured_for_auto_mode_false(config: dict) -> None:

FILE: tests/test_auto_mode_evaluator.py
  function _make_evaluator (line 18) | def _make_evaluator(**overrides) -> AutoModeEvaluator:
  function test_evaluator_constructs_with_managers (line 66) | def test_evaluator_constructs_with_managers() -> None:
  function test_auto_decision_is_frozen_dataclass (line 72) | def test_auto_decision_is_frozen_dataclass() -> None:
  function test_floor_hot_returns_overheat (line 83) | def test_floor_hot_returns_overheat() -> None:
  function test_opening_open_returns_opening_idle (line 91) | def test_opening_open_returns_opening_idle() -> None:
  function test_temperature_stall_returns_temperature_stall (line 100) | def test_temperature_stall_returns_temperature_stall() -> None:
  function test_floor_hot_preempts_opening_and_stall (line 108) | def test_floor_hot_preempts_opening_and_stall() -> None:
  function test_opening_preempts_stall (line 116) | def test_opening_preempts_stall() -> None:
  function test_humidity_urgent_2x_returns_dry (line 124) | def test_humidity_urgent_2x_returns_dry() -> None:
  function test_humidity_normal_returns_dry (line 134) | def test_humidity_normal_returns_dry() -> None:
  function test_humidity_priority_skipped_when_no_dryer (line 143) | def test_humidity_priority_skipped_when_no_dryer() -> None:
  function test_humidity_stall_suppresses_humidity_priorities (line 153) | def test_humidity_stall_suppresses_humidity_priorities() -> None:
  function test_humidity_below_target_does_not_trigger (line 162) | def test_humidity_below_target_does_not_trigger() -> None:
  function test_temp_urgent_cold_2x_returns_heat (line 171) | def test_temp_urgent_cold_2x_returns_heat() -> None:
  function test_temp_urgent_hot_2x_returns_cool (line 180) | def test_temp_urgent_hot_2x_returns_cool() -> None:
  function test_temp_normal_cold_returns_heat (line 190) | def test_temp_normal_cold_returns_heat() -> None:
  function test_temp_normal_hot_returns_cool (line 198) | def test_temp_normal_hot_returns_cool() -> None:
  function test_humidity_urgent_preempts_temp_normal (line 207) | def test_humidity_urgent_preempts_temp_normal() -> None:
  function test_temp_urgent_preempts_humidity_normal (line 217) | def test_temp_urgent_preempts_humidity_normal() -> None:
  function test_fan_band_returns_fan_only (line 227) | def test_fan_band_returns_fan_only() -> None:
  function test_fan_skipped_when_no_fan_configured (line 237) | def test_fan_skipped_when_no_fan_configured() -> None:
  function test_temp_normal_hot_preempts_fan_band (line 246) | def test_temp_normal_hot_preempts_fan_band() -> None:
  function test_idle_when_all_targets_met (line 257) | def test_idle_when_all_targets_met() -> None:
  function test_idle_after_dry_uses_humidity_reached_reason (line 265) | def test_idle_after_dry_uses_humidity_reached_reason() -> None:
  function test_range_mode_uses_target_temp_low_for_heat (line 277) | def test_range_mode_uses_target_temp_low_for_heat() -> None:
  function test_range_mode_uses_target_temp_high_for_cool (line 289) | def test_range_mode_uses_target_temp_high_for_cool() -> None:
  function test_range_mode_idle_between_targets (line 302) | def test_range_mode_idle_between_targets() -> None:
  function test_flap_prevention_stays_heat_while_goal_pending (line 314) | def test_flap_prevention_stays_heat_while_goal_pending() -> None:
  function test_flap_prevention_switches_to_dry_on_urgent_humidity (line 325) | def test_flap_prevention_switches_to_dry_on_urgent_humidity() -> None:
  function test_flap_prevention_normal_humidity_does_not_preempt_heat (line 338) | def test_flap_prevention_normal_humidity_does_not_preempt_heat() -> None:
  function test_flap_prevention_rescans_when_goal_reached (line 351) | def test_flap_prevention_rescans_when_goal_reached() -> None:
  function test_flap_prevention_dry_stays_until_dry_goal_reached (line 363) | def test_flap_prevention_dry_stays_until_dry_goal_reached() -> None:
  function test_flap_prevention_cool_stays_until_cool_goal_reached (line 375) | def test_flap_prevention_cool_stays_until_cool_goal_reached() -> None:
  function test_flap_prevention_fan_only_stays_until_fan_band_exited (line 387) | def test_flap_prevention_fan_only_stays_until_fan_band_exited() -> None:
  function test_flap_prevention_unknown_mode_falls_through_to_full_scan (line 399) | def test_flap_prevention_unknown_mode_falls_through_to_full_scan() -> None:
  function test_no_cooler_capability_skips_cool_priorities (line 411) | def test_no_cooler_capability_skips_cool_priorities() -> None:
  function test_no_cooler_with_urgent_hot_does_not_pick_cool (line 424) | def test_no_cooler_with_urgent_hot_does_not_pick_cool() -> None:
  function test_no_heater_capability_skips_heat_priorities (line 435) | def test_no_heater_capability_skips_heat_priorities() -> None:
  function test_evaluator_accepts_outside_delta_boost_threshold (line 445) | def test_evaluator_accepts_outside_delta_boost_threshold() -> None:
  function test_evaluator_default_outside_delta_boost_is_none (line 454) | def test_evaluator_default_outside_delta_boost_is_none() -> None:
  function test_evaluate_accepts_outside_temp_and_stall_flag (line 463) | def test_evaluate_accepts_outside_temp_and_stall_flag() -> None:
  function test_evaluate_outside_temp_defaults_to_none (line 475) | def test_evaluate_outside_temp_defaults_to_none() -> None:
  function test_outside_promotion_threshold_disabled_when_none (line 482) | def test_outside_promotion_threshold_disabled_when_none() -> None:
  function test_outside_promotion_skipped_when_outside_temp_none (line 495) | def test_outside_promotion_skipped_when_outside_temp_none() -> None:
  function test_outside_promotion_skipped_when_outside_stalled (line 508) | def test_outside_promotion_skipped_when_outside_stalled() -> None:
  function test_outside_promotion_skipped_when_cur_temp_none (line 521) | def test_outside_promotion_skipped_when_cur_temp_none() -> None:
  function test_outside_promotion_heat_fires_when_delta_meets_threshold_and_outside_colder (line 534) | def test_outside_promotion_heat_fires_when_delta_meets_threshold_and_out...
  function test_outside_promotion_heat_skipped_when_delta_below_threshold (line 549) | def test_outside_promotion_heat_skipped_when_delta_below_threshold() -> ...
  function test_outside_promotion_heat_skipped_when_outside_warmer_than_inside (line 562) | def test_outside_promotion_heat_skipped_when_outside_warmer_than_inside(...
  function test_outside_promotion_cool_fires_when_outside_hotter (line 575) | def test_outside_promotion_cool_fires_when_outside_hotter() -> None:
  function test_outside_promotion_cool_skipped_when_outside_cooler (line 588) | def test_outside_promotion_cool_skipped_when_outside_cooler() -> None:
  function test_outside_promotion_skipped_for_non_temp_modes (line 601) | def test_outside_promotion_skipped_for_non_temp_modes() -> None:
  function test_full_scan_promotes_normal_heat_to_urgent_with_outside_bias (line 620) | def test_full_scan_promotes_normal_heat_to_urgent_with_outside_bias() ->...
  function test_full_scan_normal_heat_unaffected_when_outside_delta_below_threshold (line 640) | def test_full_scan_normal_heat_unaffected_when_outside_delta_below_thres...
  function test_full_scan_promotes_normal_cool_to_urgent_with_outside_bias (line 654) | def test_full_scan_promotes_normal_cool_to_urgent_with_outside_bias() ->...
  function test_full_scan_outside_bias_skipped_when_below_target (line 668) | def test_full_scan_outside_bias_skipped_when_below_target() -> None:
  function test_free_cooling_skipped_when_no_fan_configured (line 681) | def test_free_cooling_skipped_when_no_fan_configured() -> None:
  function test_free_cooling_skipped_when_outside_temp_none (line 692) | def test_free_cooling_skipped_when_outside_temp_none() -> None:
  function test_free_cooling_skipped_when_outside_stalled (line 703) | def test_free_cooling_skipped_when_outside_stalled() -> None:
  function test_free_cooling_skipped_when_cur_temp_none (line 714) | def test_free_cooling_skipped_when_cur_temp_none() -> None:
  function test_free_cooling_fires_when_outside_more_than_margin_cooler (line 725) | def test_free_cooling_fires_when_outside_more_than_margin_cooler() -> None:
  function test_free_cooling_skipped_when_outside_within_margin (line 736) | def test_free_cooling_skipped_when_outside_within_margin() -> None:
  function test_free_cooling_skipped_when_outside_warmer_than_inside (line 747) | def test_free_cooling_skipped_when_outside_warmer_than_inside() -> None:
  function test_full_scan_picks_fan_for_free_cooling_in_normal_cool_tier (line 758) | def test_full_scan_picks_fan_for_free_cooling_in_normal_cool_tier() -> N...
  function test_full_scan_does_not_pick_fan_when_free_cooling_margin_not_met (line 774) | def test_full_scan_does_not_pick_fan_when_free_cooling_margin_not_met() ...
  function test_full_scan_skips_free_cooling_in_urgent_tier (line 787) | def test_full_scan_skips_free_cooling_in_urgent_tier() -> None:
  function test_full_scan_skips_free_cooling_when_outside_promotes_to_urgent (line 800) | def test_full_scan_skips_free_cooling_when_outside_promotes_to_urgent() ...
  function test_full_scan_picks_cool_when_apparent_above_target_even_if_raw_below (line 820) | def test_full_scan_picks_cool_when_apparent_above_target_even_if_raw_bel...
  function test_full_scan_does_not_pick_cool_when_raw_below_target_and_no_apparent_substitution (line 844) | def test_full_scan_does_not_pick_cool_when_raw_below_target_and_no_appar...
  function test_full_scan_apparent_only_affects_cool_decisions (line 859) | def test_full_scan_apparent_only_affects_cool_decisions() -> None:

FILE: tests/test_auto_mode_integration.py
  function _heater_cooler_yaml (line 35) | def _heater_cooler_yaml(
  function test_heater_only_does_not_expose_auto (line 66) | async def test_heater_only_does_not_expose_auto(hass: HomeAssistant) -> ...
  function test_heater_cooler_exposes_auto_in_hvac_modes (line 101) | async def test_heater_cooler_exposes_auto_in_hvac_modes(hass: HomeAssist...
  function test_heater_cooler_auto_picks_heat_when_cold (line 117) | async def test_heater_cooler_auto_picks_heat_when_cold(hass: HomeAssista...
  function test_heater_cooler_auto_picks_cool_when_hot (line 142) | async def test_heater_cooler_auto_picks_cool_when_hot(hass: HomeAssistan...
  function test_heater_cooler_auto_idle_when_at_target (line 167) | async def test_heater_cooler_auto_idle_when_at_target(hass: HomeAssistan...
  function test_heater_cooler_auto_restored_after_restart (line 186) | async def test_heater_cooler_auto_restored_after_restart(hass: HomeAssis...
  function test_heat_pump_exposes_auto_and_survives_mode_swap (line 213) | async def test_heat_pump_exposes_auto_and_survives_mode_swap(
  function test_heater_dryer_auto_picks_dry_when_humid (line 258) | async def test_heater_dryer_auto_picks_dry_when_humid(hass: HomeAssistan...
  function test_auto_keep_alive_forwards_time_to_controller (line 314) | async def test_auto_keep_alive_forwards_time_to_controller(
  function test_auto_min_cycle_duration_propagates_to_controller (line 368) | async def test_auto_min_cycle_duration_propagates_to_controller(
  function test_auto_outside_sensor_unconfigured_loads_cleanly (line 407) | async def test_auto_outside_sensor_unconfigured_loads_cleanly(
  function test_auto_helsinki_winter_loads_with_outside_sensor (line 439) | async def test_auto_helsinki_winter_loads_with_outside_sensor(
  function test_auto_free_cooling_picks_fan_over_cool_in_normal_tier (line 474) | async def test_auto_free_cooling_picks_fan_over_cool_in_normal_tier(
  function test_auto_without_outside_sensor_behaves_like_phase_1_2 (line 508) | async def test_auto_without_outside_sensor_behaves_like_phase_1_2(
  function test_heat_pump_auto_outside_bias_emits_temperature_reason (line 536) | async def test_heat_pump_auto_outside_bias_emits_temperature_reason(
  function test_heater_cooler_auto_picks_cool_via_apparent_temp (line 587) | async def test_heater_cooler_auto_picks_cool_via_apparent_temp(
  function test_heater_cooler_standalone_cool_uses_apparent_temp (line 630) | async def test_heater_cooler_standalone_cool_uses_apparent_temp(
  function test_heater_cooler_apparent_temp_off_matches_phase_1_3 (line 673) | async def test_heater_cooler_apparent_temp_off_matches_phase_1_3(
  function test_heat_pump_auto_picks_cool_via_apparent_temp (line 714) | async def test_heat_pump_auto_picks_cool_via_apparent_temp(
  function test_heat_pump_apparent_temp_off_matches_phase_1_3 (line 760) | async def test_heat_pump_apparent_temp_off_matches_phase_1_3(

FILE: tests/test_auto_preset_selection.py
  function setup_thermostat_with_presets (line 31) | async def setup_thermostat_with_presets(hass: HomeAssistant) -> None:
  function setup_thermostat_with_range_presets (line 53) | async def setup_thermostat_with_range_presets(hass: HomeAssistant) -> None:
  function setup_thermostat_with_floor_heating_presets (line 76) | async def setup_thermostat_with_floor_heating_presets(hass: HomeAssistan...
  function setup_thermostat_with_humidity_presets (line 112) | async def setup_thermostat_with_humidity_presets(hass: HomeAssistant) ->...
  class TestAutoPresetSelection (line 134) | class TestAutoPresetSelection:
    method test_auto_select_preset_single_temperature_match (line 137) | async def test_auto_select_preset_single_temperature_match(
    method test_auto_select_preset_temperature_range_match (line 162) | async def test_auto_select_preset_temperature_range_match(
    method test_auto_select_preset_with_floor_heating_match (line 192) | async def test_auto_select_preset_with_floor_heating_match(
    method test_auto_select_preset_with_humidity_match (line 220) | async def test_auto_select_preset_with_humidity_match(
    method test_no_auto_select_when_partial_match (line 252) | async def test_no_auto_select_when_partial_match(
    method test_no_auto_select_when_no_presets_configured (line 281) | async def test_no_auto_select_when_no_presets_configured(self, hass: H...
    method test_no_auto_select_when_already_in_matching_preset (line 316) | async def test_no_auto_select_when_already_in_matching_preset(
    method test_auto_select_first_matching_preset_when_multiple_match (line 345) | async def test_auto_select_first_matching_preset_when_multiple_match(
    method test_auto_select_preset_tolerance_handling (line 370) | async def test_auto_select_preset_tolerance_handling(
    method test_auto_select_preset_preserves_existing_preset_when_no_match (line 394) | async def test_auto_select_preset_preserves_existing_preset_when_no_ma...

FILE: tests/test_config_flow.py
  function test_config_flow_basic (line 24) | async def test_config_flow_basic(hass: HomeAssistant) -> None:
  function test_config_flow_validation_errors (line 69) | async def test_config_flow_validation_errors(hass: HomeAssistant) -> None:
  function test_config_flow_with_presets (line 99) | async def test_config_flow_with_presets(hass: HomeAssistant) -> None:
  function test_options_flow (line 152) | async def test_options_flow(hass: HomeAssistant) -> None:

FILE: tests/test_cooler_mode.py
  function test_unique_id (line 81) | async def test_unique_id(
  function test_setup_defaults_to_unknown (line 121) | async def test_setup_defaults_to_unknown(hass: HomeAssistant) -> None:  ...
  function test_setup_gets_current_temp_from_sensor (line 142) | async def test_setup_gets_current_temp_from_sensor(
  function test_get_hvac_modes (line 174) | async def test_get_hvac_modes(
  function test_set_preset_mode (line 197) | async def test_set_preset_mode(
  function test_set_preset_mode_and_restore_prev_temp (line 221) | async def test_set_preset_mode_and_restore_prev_temp(
  function test_set_preset_modet_twice_and_restore_prev_temp (line 251) | async def test_set_preset_modet_twice_and_restore_prev_temp(
  function test_set_preset_mode_invalid (line 268) | async def test_set_preset_mode_invalid(
  function test_set_preset_mode_set_temp_keeps_preset_mode (line 299) | async def test_set_preset_mode_set_temp_keeps_preset_mode(
  function test_set_same_preset_mode_restores_preset_temp_from_modified (line 358) | async def test_set_same_preset_mode_restores_preset_temp_from_modified(
  function test_set_preset_mode_picks_temp_from_preset (line 411) | async def test_set_preset_mode_picks_temp_from_preset(
  function test_set_target_temp_ac_off (line 457) | async def test_set_target_temp_ac_off(
  function test_set_target_temp_ac_and_hvac_mode (line 472) | async def test_set_target_temp_ac_and_hvac_mode(
  function test_turn_away_mode_on_cooling (line 493) | async def test_turn_away_mode_on_cooling(
  function test_toggle (line 520) | async def test_toggle(
  function test_hvac_mode_cool (line 544) | async def test_hvac_mode_cool(
  function test_sensor_chhange_dont_control_ac_on_when_off (line 564) | async def test_sensor_chhange_dont_control_ac_on_when_off(
  function test_set_target_temp_ac_on (line 589) | async def test_set_target_temp_ac_on(
  function test_temp_change_ac_off_within_tolerance (line 604) | async def test_temp_change_ac_off_within_tolerance(
  function test_set_temp_change_ac_off_outside_tolerance (line 615) | async def test_set_temp_change_ac_off_outside_tolerance(
  function test_temp_change_ac_on_within_tolerance (line 630) | async def test_temp_change_ac_on_within_tolerance(
  function test_temp_change_ac_on_outside_tolerance (line 641) | async def test_temp_change_ac_on_outside_tolerance(
  function test_running_when_operating_mode_is_off_2 (line 656) | async def test_running_when_operating_mode_is_off_2(
  function test_no_state_change_when_operation_mode_off_2 (line 670) | async def test_no_state_change_when_operation_mode_off_2(
  function test_temp_change_ac_trigger_long_enough (line 684) | async def test_temp_change_ac_trigger_long_enough(
  function test_time_change_ac_trigger_long_enough (line 733) | async def test_time_change_ac_trigger_long_enough(
  function test_mode_change_ac_trigger_not_long_enough (line 771) | async def test_mode_change_ac_trigger_not_long_enough(
  function test_sensor_unknown_secure_ac_off_outside_stale_duration (line 809) | async def test_sensor_unknown_secure_ac_off_outside_stale_duration(
  function test_sensor_stalled_secure_ac_off_outside_stale_duration_reason (line 841) | async def test_sensor_stalled_secure_ac_off_outside_stale_duration_reason(
  function test_sensor_restores_after_state_changes (line 873) | async def test_sensor_restores_after_state_changes(
  function test_cooler_mode (line 916) | async def test_cooler_mode(hass: HomeAssistant, setup_comp_1) -> None:  ...
  function test_cooler_mode_change (line 963) | async def test_cooler_mode_change(
  function test_cooler_mode_from_off_to_idle (line 1016) | async def test_cooler_mode_from_off_to_idle(
  function test_cooler_mode_off_switch_change_keeps_off (line 1064) | async def test_cooler_mode_off_switch_change_keeps_off(
  function test_cooler_mode_tolerance (line 1114) | async def test_cooler_mode_tolerance(
  function test_cooler_mode_cycle (line 1182) | async def test_cooler_mode_cycle(
  function test_cooler_mode_opening_hvac_action_reason (line 1245) | async def test_cooler_mode_opening_hvac_action_reason(
  function test_cooler_mode_hvac_power_value (line 1376) | async def test_cooler_mode_hvac_power_value(
  function test_cooler_mode_hvac_power_value_2 (line 1465) | async def test_cooler_mode_hvac_power_value_2(
  function test_cooler_mode_opening (line 1549) | async def test_cooler_mode_opening(
  function test_cooler_mode_opening_scope (line 1658) | async def test_cooler_mode_opening_scope(
  function test_legacy_config_cool_mode_behaves_identically (line 1746) | async def test_legacy_config_cool_mode_behaves_identically(
  function test_ac_only_cool_uses_apparent_temp_when_flag_on (line 1814) | async def test_ac_only_cool_uses_apparent_temp_when_flag_on(
  function test_ac_only_apparent_temp_off_does_not_cool_when_raw_below (line 1860) | async def test_ac_only_apparent_temp_off_does_not_cool_when_raw_below(

FILE: tests/test_cooler_mode_behavioral.py
  function test_cooler_threshold_boundary_with_default_tolerance (line 22) | async def test_cooler_threshold_boundary_with_default_tolerance(hass: Ho...
  function test_cooler_threshold_boundary_with_custom_tolerance (line 106) | async def test_cooler_threshold_boundary_with_custom_tolerance(hass: Hom...
  function test_cooler_zero_tolerance_exact_threshold (line 189) | async def test_cooler_zero_tolerance_exact_threshold(hass: HomeAssistant):

FILE: tests/test_dry_mode.py
  function test_unique_id (line 81) | async def test_unique_id(
  function test_setup_defaults_to_unknown (line 126) | async def test_setup_defaults_to_unknown(hass: HomeAssistant) -> None:  ...
  function test_setup_gets_current_humidity_from_sensor (line 149) | async def test_setup_gets_current_humidity_from_sensor(
  function setup_comp_heat_ac_cool_dry (line 184) | async def setup_comp_heat_ac_cool_dry(hass: HomeAssistant) -> None:
  function test_get_hvac_modes (line 212) | async def test_get_hvac_modes(
  function setup_comp_heat_ac_cool_dry_presets (line 222) | async def setup_comp_heat_ac_cool_dry_presets(hass: HomeAssistant) -> None:
  function test_set_preset_mode (line 268) | async def test_set_preset_mode(
  function test_set_preset_mode_and_restore_prev_humidity (line 298) | async def test_set_preset_mode_and_restore_prev_humidity(
  function test_set_preset_modet_twice_and_restore_prev_humidity (line 334) | async def test_set_preset_modet_twice_and_restore_prev_humidity(
  function test_set_preset_mode_invalid (line 360) | async def test_set_preset_mode_invalid(
  function test_set_preset_mode_set_temp_keeps_preset_mode (line 392) | async def test_set_preset_mode_set_temp_keeps_preset_mode(
  function test_set_target_temp_ac_dry_off (line 429) | async def test_set_target_temp_ac_dry_off(
  function test_turn_away_mode_on_drying (line 444) | async def test_turn_away_mode_on_drying(
  function test_toggle (line 474) | async def test_toggle(
  function test_hvac_mode_cdry (line 497) | async def test_hvac_mode_cdry(
  function test_sensor_chhange_dont_control_dryer_when_off (line 519) | async def test_sensor_chhange_dont_control_dryer_when_off(
  function test_set_target_temp_ac_dryer_on (line 538) | async def test_set_target_temp_ac_dryer_on(
  function test_temp_change_ac_dry_off_within_tolerance (line 554) | async def test_temp_change_ac_dry_off_within_tolerance(
  function test_set_temp_change_ac_dry_off_outside_tolerance (line 570) | async def test_set_temp_change_ac_dry_off_outside_tolerance(
  function test_temp_change_ac_dryer_on_within_tolerance (line 585) | async def test_temp_change_ac_dryer_on_within_tolerance(
  function test_temp_change_ac_on_outside_tolerance (line 596) | async def test_temp_change_ac_on_outside_tolerance(
  function test_running_when_operating_mode_is_off_2 (line 612) | async def test_running_when_operating_mode_is_off_2(
  function test_no_state_change_when_operation_mode_off_2 (line 626) | async def test_no_state_change_when_operation_mode_off_2(
  function setup_comp_heat_ac_cool_dry_cycle (line 639) | async def setup_comp_heat_ac_cool_dry_cycle(hass: HomeAssistant) -> None:
  function test_temp_change_ac_dry_trigger_on_long_enough (line 669) | async def test_temp_change_ac_dry_trigger_on_long_enough(
  function test_time_change_ac_dry_trigger_on_long_enough (line 718) | async def test_time_change_ac_dry_trigger_on_long_enough(
  function test_mode_change_ac_dry_trigger_off_not_long_enough (line 756) | async def test_mode_change_ac_dry_trigger_off_not_long_enough(
  function setup_comp_heat_ac_cool_dry_stale_duration (line 774) | async def setup_comp_heat_ac_cool_dry_stale_duration(hass: HomeAssistant...
  function test_sensor_unknown_secure_ac_dry_off_outside_stale_duration (line 807) | async def test_sensor_unknown_secure_ac_dry_off_outside_stale_duration(
  function test_sensor_unknown_secure_ac_dry_off_outside_stale_duration_reason (line 850) | async def test_sensor_unknown_secure_ac_dry_off_outside_stale_duration_r...
  function test_dryer_mode (line 877) | async def test_dryer_mode(hass: HomeAssistant, setup_comp_1) -> None:  #...
  function test_dryer_mode_change (line 939) | async def test_dryer_mode_change(
  function test_dryer_mode_from_off_to_idle (line 1007) | async def test_dryer_mode_from_off_to_idle(
  function test_dryer_mode_off_switch_change_keeps_off (line 1069) | async def test_dryer_mode_off_switch_change_keeps_off(
  function test_dryer_mode_tolerance (line 1130) | async def test_dryer_mode_tolerance(
  function test_dryer_mode_cycle (line 1210) | async def test_dryer_mode_cycle(
  function test_dryer_mode_opening_hvac_action_reason (line 1288) | async def test_dryer_mode_opening_hvac_action_reason(
  function test_dryer_mode_opening (line 1442) | async def test_dryer_mode_opening(
  function test_cooler_mode_opening_scope (line 1567) | async def test_cooler_mode_opening_scope(
  function setup_comp_dry_no_target_humidity_yaml (line 1661) | async def setup_comp_dry_no_target_humidity_yaml(hass: HomeAssistant) ->...
  function test_target_humidity_initialized_without_yaml_config (line 1698) | async def test_target_humidity_initialized_without_yaml_config(
  function test_humidity_control_works_after_yaml_setup (line 1725) | async def test_humidity_control_works_after_yaml_setup(
  function test_humidity_target_restored_on_restart (line 1771) | async def test_humidity_target_restored_on_restart(

FILE: tests/test_dual_mode.py
  function test_unique_id (line 98) | async def test_unique_id(
  function test_setup_defaults_to_unknown (line 143) | async def test_setup_defaults_to_unknown(hass: HomeAssistant) -> None:  ...
  function test_setup_gets_current_temp_from_sensor (line 165) | async def test_setup_gets_current_temp_from_sensor(
  function test_restore_state_while_off (line 193) | async def test_restore_state_while_off(hass: HomeAssistant) -> None:
  function test_presets_use_case_80 (line 234) | async def test_presets_use_case_80(
  function test_presets_use_case_150 (line 278) | async def test_presets_use_case_150(
  function test_presets_use_case_150_2 (line 311) | async def test_presets_use_case_150_2(
  function test_dual_default_setup_params (line 389) | async def test_dual_default_setup_params(
  function test_heat_cool_default_setup_params (line 399) | async def test_heat_cool_default_setup_params(
  function test_get_hvac_modes_dual (line 416) | async def test_get_hvac_modes_dual(
  function test_get_hvac_modes_heat_cool (line 427) | async def test_get_hvac_modes_heat_cool(
  function test_get_hvac_modes_heat_cool_2 (line 444) | async def test_get_hvac_modes_heat_cool_2(
  function test_dual_get_hvac_modes_fan_configured (line 510) | async def test_dual_get_hvac_modes_fan_configured(
  function test_heat_cool_get_hvac_modes_fan_configured (line 527) | async def test_heat_cool_get_hvac_modes_fan_configured(
  function test_set_hvac_mode_chnage_trarget_temp (line 545) | async def test_set_hvac_mode_chnage_trarget_temp(
  function test_set_target_temp_dual (line 564) | async def test_set_target_temp_dual(
  function test_set_target_temp_heat_cool (line 577) | async def test_set_target_temp_heat_cool(
  function test_dual_set_preset_mode (line 605) | async def test_dual_set_preset_mode(
  function test_heat_cool_set_preset_mode (line 631) | async def test_heat_cool_set_preset_mode(
  function test_dual_set_preset_mode_and_restore_prev_temp (line 659) | async def test_dual_set_preset_mode_and_restore_prev_temp(
  function test_set_heat_cool_preset_mode_and_restore_prev_temp (line 688) | async def test_set_heat_cool_preset_mode_and_restore_prev_temp(
  function test_set_heat_cool_preset_mode_and_restore_prev_temp_2 (line 722) | async def test_set_heat_cool_preset_mode_and_restore_prev_temp_2(
  function test_set_heat_cool_fan_preset_mode_and_restore_prev_temp (line 780) | async def test_set_heat_cool_fan_preset_mode_and_restore_prev_temp(
  function test_set_heat_cool_fan_restore_state (line 806) | async def test_set_heat_cool_fan_restore_state(
  function test_set_heat_cool_fan_restore_state_2 (line 943) | async def test_set_heat_cool_fan_restore_state_2(
  function test_set_dual_preset_mode_twice_and_restore_prev_temp (line 1002) | async def test_set_dual_preset_mode_twice_and_restore_prev_temp(
  function test_set_heat_cool_preset_mode_twice_and_restore_prev_temp (line 1032) | async def test_set_heat_cool_preset_mode_twice_and_restore_prev_temp(
  function test_set_heat_cool_preset_mode_and_restore_prev_temp_apply_preset_again (line 1068) | async def test_set_heat_cool_preset_mode_and_restore_prev_temp_apply_pre...
  function test_set_dual_preset_mode_invalid (line 1119) | async def test_set_dual_preset_mode_invalid(
  function test_set_heat_cool_preset_mode_invalid (line 1136) | async def test_set_heat_cool_preset_mode_invalid(
  function test_sensor_unknown_secure_heat_cool_off_outside_stale_duration_cooler (line 1158) | async def test_sensor_unknown_secure_heat_cool_off_outside_stale_duratio...
  function test_sensor_unknown_secure_heat_cool_off_outside_stale_duration_heater (line 1189) | async def test_sensor_unknown_secure_heat_cool_off_outside_stale_duratio...
  function test_sensor_unknown_secure_heat_cool_off_outside_stale_duration (line 1228) | async def test_sensor_unknown_secure_heat_cool_off_outside_stale_duration(
  function test_sensor_unknown_secure_heat_cool_off_outside_stale_duration_reason (line 1275) | async def test_sensor_unknown_secure_heat_cool_off_outside_stale_duratio...
  function test_sensor_restores_after_state_changes (line 1324) | async def test_sensor_restores_after_state_changes(
  function test_dual_set_preset_mode_set_temp_keeps_preset_mode (line 1385) | async def test_dual_set_preset_mode_set_temp_keeps_preset_mode(
  function test_heat_cool_set_preset_mode_set_temp_keeps_preset_mode (line 1428) | async def test_heat_cool_set_preset_mode_set_temp_keeps_preset_mode(
  function test_heat_cool_set_preset_mode_in_non_range_mode (line 1489) | async def test_heat_cool_set_preset_mode_in_non_range_mode(
  function test_heat_cool_set_preset_mode_auto_target_temps_if_range_only_presets (line 1523) | async def test_heat_cool_set_preset_mode_auto_target_temps_if_range_only...
  function test_heat_cool_fan_set_preset_mode_set_temp_keeps_preset_mode (line 1579) | async def test_heat_cool_fan_set_preset_mode_set_temp_keeps_preset_mode(
  function test_heat_cool_fan_set_preset_mode_change_hvac_mode (line 1631) | async def test_heat_cool_fan_set_preset_mode_change_hvac_mode(
  function test_dual_toggle (line 1696) | async def test_dual_toggle(
  function test_heat_cool_toggle (line 1722) | async def test_heat_cool_toggle(
  function test_dual_toggle_with_fan (line 1755) | async def test_dual_toggle_with_fan(
  function test_heat_cool_toggle_with_fan (line 1789) | async def test_heat_cool_toggle_with_fan(
  function test_hvac_mode_mode_heat_cool (line 1813) | async def test_hvac_mode_mode_heat_cool(
  function test_hvac_mode_mode_heat_cool_fan_tolerance (line 1934) | async def test_hvac_mode_mode_heat_cool_fan_tolerance(
  function test_hvac_mode_mode_heat_cool_ignore_fan_tolerance (line 2038) | async def test_hvac_mode_mode_heat_cool_ignore_fan_tolerance(
  function test_hvac_mode_mode_heat_cool_dont_ignore_fan_tolerance (line 2156) | async def test_hvac_mode_mode_heat_cool_dont_ignore_fan_tolerance(
  function test_hvac_mode_mode_heat_cool_fan_tolerance_with_floor_sensor (line 2274) | async def test_hvac_mode_mode_heat_cool_fan_tolerance_with_floor_sensor(
  function test_hvac_mode_mode_heat_cool_hvac_modes_temps (line 2372) | async def test_hvac_mode_mode_heat_cool_hvac_modes_temps(
  function test_hvac_mode_mode_heat_cool_hvac_modes_temps_avoid_unrealism (line 2457) | async def test_hvac_mode_mode_heat_cool_hvac_modes_temps_avoid_unrealism(
  function test_hvac_mode_mode_heat_cool_hvac_modes_temps_picks_range_values (line 2537) | async def test_hvac_mode_mode_heat_cool_hvac_modes_temps_picks_range_val...
  function test_hvac_mode_heat_cool_floor_temp (line 2608) | async def test_hvac_mode_heat_cool_floor_temp(
  function test_hvac_mode_mode_heat_cool_aux_heat (line 2718) | async def test_hvac_mode_mode_heat_cool_aux_heat(
  function test_hvac_mode_cool (line 2868) | async def test_hvac_mode_cool(hass: HomeAssistant, setup_comp_1):  # noq...
  function test_hvac_mode_cool_hvac_action_reason (line 2927) | async def test_hvac_mode_cool_hvac_action_reason(
  function test_hvac_mode_heat_hvac_action_reason (line 2994) | async def test_hvac_mode_heat_hvac_action_reason(
  function test_hvac_mode_cool_cycle (line 3069) | async def test_hvac_mode_cool_cycle(
  function test_hvac_mode_heat_cycle (line 3142) | async def test_hvac_mode_heat_cycle(
  function test_hvac_mode_heat_cool_cycle (line 3216) | async def test_hvac_mode_heat_cool_cycle(
  function test_hvac_mode_heat_cool_switch_preset_modes (line 3282) | async def test_hvac_mode_heat_cool_switch_preset_modes(
  function test_hvac_mode_heat_cool_dry_mode (line 3338) | async def test_hvac_mode_heat_cool_dry_mode(
  function test_hvac_mode_heat_cool_tolerances (line 3438) | async def test_hvac_mode_heat_cool_tolerances(
  function test_hvac_mode_heat_cool_floor_temp_hvac_action_reason (line 3554) | async def test_hvac_mode_heat_cool_floor_temp_hvac_action_reason(
  function test_heat_cool_mode_opening_scope (line 3678) | async def test_heat_cool_mode_opening_scope(
  function test_heat_cool_mode_opening_timeout (line 3773) | async def test_heat_cool_mode_opening_timeout(
  function track_turn_off_calls (line 3999) | def track_turn_off_calls(hass, entity_id):
  function test_heat_cool_mode_does_not_turn_off_idle_cooler_when_heating (line 4019) | async def test_heat_cool_mode_does_not_turn_off_idle_cooler_when_heating(
  function test_heat_cool_mode_does_not_turn_off_idle_heater_when_cooling (line 4052) | async def test_heat_cool_mode_does_not_turn_off_idle_heater_when_cooling(
  function test_heat_cool_mode_does_not_turn_off_either_idle_device_when_temp_in_range (line 4084) | async def test_heat_cool_mode_does_not_turn_off_either_idle_device_when_...

FILE: tests/test_dual_mode_behavioral.py
  function test_dual_mode_heating_threshold_with_default_tolerance (line 23) | async def test_dual_mode_heating_threshold_with_default_tolerance(hass: ...
  function test_dual_mode_cooling_threshold_with_default_tolerance (line 104) | async def test_dual_mode_cooling_threshold_with_default_tolerance(hass: ...
  function test_dual_mode_heat_cool_dual_thresholds (line 185) | async def test_dual_mode_heat_cool_dual_thresholds(hass: HomeAssistant):
  function test_dual_mode_custom_tolerance_values (line 298) | async def test_dual_mode_custom_tolerance_values(hass: HomeAssistant):

FILE: tests/test_environment_manager.py
  function test_rothfusz_heat_index_at_threshold_minimum_humidity (line 14) | def test_rothfusz_heat_index_at_threshold_minimum_humidity() -> None:
  function test_rothfusz_heat_index_high_humidity_above_threshold (line 20) | def test_rothfusz_heat_index_high_humidity_above_threshold() -> None:
  function test_rothfusz_heat_index_hot_humid (line 26) | def test_rothfusz_heat_index_hot_humid() -> None:
  function test_rothfusz_heat_index_low_humidity_extreme_temp (line 32) | def test_rothfusz_heat_index_low_humidity_extreme_temp() -> None:
  function _make_env (line 38) | def _make_env(**config_overrides) -> EnvironmentManager:
  function test_env_manager_default_use_apparent_temp_is_false (line 47) | def test_env_manager_default_use_apparent_temp_is_false() -> None:
  function test_env_manager_reads_use_apparent_temp_from_config (line 53) | def test_env_manager_reads_use_apparent_temp_from_config() -> None:
  function test_env_manager_humidity_sensor_stalled_default_false (line 61) | def test_env_manager_humidity_sensor_stalled_default_false() -> None:
  function test_env_manager_humidity_sensor_stalled_setter_updates_flag (line 67) | def test_env_manager_humidity_sensor_stalled_setter_updates_flag() -> None:
  function test_apparent_temp_falls_back_when_flag_off (line 74) | def test_apparent_temp_falls_back_when_flag_off() -> None:
  function test_apparent_temp_falls_back_when_cur_temp_none (line 82) | def test_apparent_temp_falls_back_when_cur_temp_none() -> None:
  function test_apparent_temp_falls_back_when_humidity_none (line 92) | def test_apparent_temp_falls_back_when_humidity_none() -> None:
  function test_apparent_temp_falls_back_when_humidity_stalled (line 102) | def test_apparent_temp_falls_back_when_humidity_stalled() -> None:
  function test_apparent_temp_falls_back_below_27c_threshold (line 113) | def test_apparent_temp_falls_back_below_27c_threshold() -> None:
  function test_apparent_temp_above_threshold_humid_celsius (line 123) | def test_apparent_temp_above_threshold_humid_celsius() -> None:
  function test_apparent_temp_fahrenheit_input_conversion (line 136) | def test_apparent_temp_fahrenheit_input_conversion() -> None:
  function test_effective_temp_for_mode_returns_cur_when_flag_off (line 150) | def test_effective_temp_for_mode_returns_cur_when_flag_off() -> None:
  function test_effective_temp_for_mode_cool_returns_apparent_when_eligible (line 165) | def test_effective_temp_for_mode_cool_returns_apparent_when_eligible() -...
  function test_effective_temp_for_mode_non_cool_returns_cur (line 177) | def test_effective_temp_for_mode_non_cool_returns_cur() -> None:
  function test_is_too_hot_uses_apparent_when_mode_cool_and_flag_on (line 188) | def test_is_too_hot_uses_apparent_when_mode_cool_and_flag_on() -> None:
  function test_is_too_hot_uses_raw_when_mode_not_cool (line 216) | def test_is_too_hot_uses_raw_when_mode_not_cool() -> None:
  function test_is_too_hot_uses_raw_when_flag_off (line 238) | def test_is_too_hot_uses_raw_when_flag_off() -> None:

FILE: tests/test_fan_mode.py
  function test_cooler_fan_unique_id (line 80) | async def test_cooler_fan_unique_id(
  function test_fan_only_unique_id (line 123) | async def test_fan_only_unique_id(
  function test_setup_defaults_to_unknown (line 165) | async def test_setup_defaults_to_unknown(hass: HomeAssistant) -> None:  ...
  function test_cool_fan_setup_defaults_to_unknown (line 186) | async def test_cool_fan_setup_defaults_to_unknown(
  function test_setup_gets_current_temp_from_sensor (line 210) | async def test_setup_gets_current_temp_from_sensor(
  function test_setup_cool_fan_gets_current_temp_from_sensor (line 237) | async def test_setup_cool_fan_gets_current_temp_from_sensor(
  function test_get_hvac_modes_cool_fan_configured (line 270) | async def test_get_hvac_modes_cool_fan_configured(
  function test_get_hvac_modes_fan_only_configured (line 281) | async def test_get_hvac_modes_fan_only_configured(
  function test_set_preset_mode (line 304) | async def test_set_preset_mode(
  function test_fan_only_set_preset_mode (line 331) | async def test_fan_only_set_preset_mode(
  function test_set_preset_mode_and_restore_prev_temp (line 355) | async def test_set_preset_mode_and_restore_prev_temp(
  function test_fan_only_set_preset_mode_and_restore_prev_temp (line 388) | async def test_fan_only_set_preset_mode_and_restore_prev_temp(
  function test_set_preset_modet_twice_and_restore_prev_temp (line 418) | async def test_set_preset_modet_twice_and_restore_prev_temp(
  function test_fan_only_set_preset_modet_twice_and_restore_prev_temp (line 452) | async def test_fan_only_set_preset_modet_twice_and_restore_prev_temp(
  function test_set_preset_mode_invalid (line 469) | async def test_set_preset_mode_invalid(
  function test_fan_only_set_preset_mode_invalid (line 486) | async def test_fan_only_set_preset_mode_invalid(
  function test_set_preset_mode_set_temp_keeps_preset_mode (line 517) | async def test_set_preset_mode_set_temp_keeps_preset_mode(
  function test_fan_only_set_preset_mode_set_temp_keeps_preset_mode (line 560) | async def test_fan_only_set_preset_mode_set_temp_keeps_preset_mode(
  function test_turn_away_mode_on_fan (line 586) | async def test_turn_away_mode_on_fan(
  function test_turn_away_mode_on_cooling (line 601) | async def test_turn_away_mode_on_cooling(
  function test_toggle_fan_only (line 621) | async def test_toggle_fan_only(
  function test_hvac_mode_fan_only (line 642) | async def test_hvac_mode_fan_only(
  function test_toggle_cool_fan (line 670) | async def test_toggle_cool_fan(
  function test_hvac_mode_cool_fan (line 694) | async def test_hvac_mode_cool_fan(
  function test_set_target_temp_fan_off (line 728) | async def test_set_target_temp_fan_off(
  function test_set_target_temp_cool_fan_off (line 743) | async def test_set_target_temp_cool_fan_off(
  function test_set_target_temp_fan_on (line 767) | async def test_set_target_temp_fan_on(
  function test_set_target_temp_cooler_on (line 782) | async def test_set_target_temp_cooler_on(
  function test_set_target_temp_cooler_fan_on (line 799) | async def test_set_target_temp_cooler_fan_on(
  function test_temp_change_fan_off_within_tolerance (line 816) | async def test_temp_change_fan_off_within_tolerance(
  function test_temp_change_cooler_fan_ac_off_within_tolerance (line 827) | async def test_temp_change_cooler_fan_ac_off_within_tolerance(
  function test_temp_change_cooler_fan_off_within_tolerance (line 839) | async def test_temp_change_cooler_fan_off_within_tolerance(
  function test_set_temp_change_fan_off_outside_tolerance (line 851) | async def test_set_temp_change_fan_off_outside_tolerance(
  function test_set_temp_change_cooler_fan_ac_off_outside_tolerance (line 866) | async def test_set_temp_change_cooler_fan_ac_off_outside_tolerance(
  function test_set_temp_change_cooler_fan_off_outside_tolerance (line 882) | async def test_set_temp_change_cooler_fan_off_outside_tolerance(
  function test_temp_change_fan_on_within_tolerance (line 898) | async def test_temp_change_fan_on_within_tolerance(
  function test_temp_change_cooler_fan_ac_on_within_tolerance (line 909) | async def test_temp_change_cooler_fan_ac_on_within_tolerance(
  function test_temp_change_cooler_fan_on_within_tolerance (line 921) | async def test_temp_change_cooler_fan_on_within_tolerance(
  function test_temp_change_fan_on_outside_tolerance (line 933) | async def test_temp_change_fan_on_outside_tolerance(
  function test_temp_change_cooler_fan_ac_on_outside_tolerance (line 948) | async def test_temp_change_cooler_fan_ac_on_outside_tolerance(
  function test_temp_change_cooler_fan_on_outside_tolerance (line 964) | async def test_temp_change_cooler_fan_on_outside_tolerance(
  function test_running_fan_when_operating_mode_is_off_2 (line 980) | async def test_running_fan_when_operating_mode_is_off_2(
  function test_running_cooler_fan_ac_when_operating_mode_is_off_2 (line 994) | async def test_running_cooler_fan_ac_when_operating_mode_is_off_2(
  function test_running_cooler_fan_when_operating_mode_is_off_2 (line 1009) | async def test_running_cooler_fan_when_operating_mode_is_off_2(
  function test_no_state_change_fan_when_operation_mode_off_2 (line 1024) | async def test_no_state_change_fan_when_operation_mode_off_2(
  function test_no_state_cooler_fan_ac_change_when_operation_mode_off_2 (line 1036) | async def test_no_state_cooler_fan_ac_change_when_operation_mode_off_2(
  function test_no_state_cooler_fan_change_when_operation_mode_off_2 (line 1049) | async def test_no_state_cooler_fan_change_when_operation_mode_off_2(
  function test_temp_change_fan_trigger_long_enough (line 1064) | async def test_temp_change_fan_trigger_long_enough(
  function test_time_change_fan_trigger_long_enough (line 1113) | async def test_time_change_fan_trigger_long_enough(
  function test_mode_change_fan_trigger_not_long_enough (line 1151) | async def test_mode_change_fan_trigger_not_long_enough(
  function test_temp_change_cooler_fan_ac_trigger_on_long_enough (line 1170) | async def test_temp_change_cooler_fan_ac_trigger_on_long_enough(
  function test_time_change_cooler_fan_ac_trigger_on_long_enough (line 1220) | async def test_time_change_cooler_fan_ac_trigger_on_long_enough(
  function test_temp_change_cooler_fan_trigger_on_long_enough (line 1259) | async def test_temp_change_cooler_fan_trigger_on_long_enough(
  function test_time_change_cooler_fan_trigger_on_long_enough (line 1309) | async def test_time_change_cooler_fan_trigger_on_long_enough(
  function test_mode_change_cooler_fan_ac_trigger_off_not_long_enough (line 1348) | async def test_mode_change_cooler_fan_ac_trigger_off_not_long_enough(
  function test_mode_change_cooler_fan_trigger_off_not_long_enough (line 1368) | async def test_mode_change_cooler_fan_trigger_off_not_long_enough(
  function test_time_change_fan_trigger_keep_alive (line 1388) | async def test_time_change_fan_trigger_keep_alive(
  function test_time_change_ac_trigger_keep_alive (line 1430) | async def test_time_change_ac_trigger_keep_alive(
  function test_time_change_ac_fan_trigger_keep_alive (line 1471) | async def test_time_change_ac_fan_trigger_keep_alive(
  function test_fan_mode (line 1510) | async def test_fan_mode(hass: HomeAssistant, setup_comp_1) -> None:  # n...
  function test_cooler_fan_cool_mode (line 1557) | async def test_cooler_fan_cool_mode(
  function test_cooler_fan_fan_mode (line 1611) | async def test_cooler_fan_fan_mode(
  function test_fan_mode_from_off_to_idle (line 1665) | async def test_fan_mode_from_off_to_idle(
  function test_cooler_fan_cooler_mode_from_off_to_idle (line 1713) | async def test_cooler_fan_cooler_mode_from_off_to_idle(
  function test_cooler_fan_fan_mode_from_off_to_idle (line 1763) | async def test_cooler_fan_fan_mode_from_off_to_idle(
  function test_fan_mode_tolerance (line 1813) | async def test_fan_mode_tolerance(
  function test_cooler_fan_cooler_mode_tolerance (line 1872) | async def test_cooler_fan_cooler_mode_tolerance(
  function test_cooler_fan_mode_tolerance (line 1938) | async def test_cooler_fan_mode_tolerance(
  function test_cooler_fan_ac_and_mode (line 2004) | async def test_cooler_fan_ac_and_mode(
  function test_fan_mode_cycle (line 2079) | async def test_fan_mode_cycle(
  function test_cooler_fan_mode_cycle (line 2147) | async def test_cooler_fan_mode_cycle(
  function test_hvac_mode_cool_fan_only (line 2220) | async def test_hvac_mode_cool_fan_only(
  function test_set_target_temp_ac_fan_on (line 2240) | async def test_set_target_temp_ac_fan_on(
  function test_set_target_temp_ac_on_tolerance_and_cycle (line 2258) | async def test_set_target_temp_ac_on_tolerance_and_cycle(
  function test_set_target_temp_ac_on_after_fan_tolerance (line 2341) | async def test_set_target_temp_ac_on_after_fan_tolerance(
  function test_set_target_temp_ac_on_dont_switch_to_fan_during_cycle1 (line 2367) | async def test_set_target_temp_ac_on_dont_switch_to_fan_during_cycle1(
  function test_set_target_temp_ac_on_dont_switch_to_fan_during_cycle2 (line 2401) | async def test_set_target_temp_ac_on_dont_switch_to_fan_during_cycle2(
  function test_set_target_temp_ac_on_dont_switch_to_fan_during_cycle3 (line 2421) | async def test_set_target_temp_ac_on_dont_switch_to_fan_during_cycle3(
  function test_set_target_temp_ac_on_after_fan_tolerance_2 (line 2459) | async def test_set_target_temp_ac_on_after_fan_tolerance_2(
  function test_set_target_temp_ac_on_after_fan_tolerance_toggle_off (line 2545) | async def test_set_target_temp_ac_on_after_fan_tolerance_toggle_off(
  function test_set_target_temp_ac_on_after_fan_tolerance_toggle_when_idle (line 2654) | async def test_set_target_temp_ac_on_after_fan_tolerance_toggle_when_idle(
  function test_set_target_temp_ac_on_ignore_fan_tolerance (line 2730) | async def test_set_target_temp_ac_on_ignore_fan_tolerance(
  function test_set_target_temp_ac_on_dont_ignore_fan_tolerance (line 2825) | async def test_set_target_temp_ac_on_dont_ignore_fan_tolerance(
  function test_fan_mode_opening_hvac_action_reason (line 2925) | async def test_fan_mode_opening_hvac_action_reason(
  function test_cooler_fan_mode_opening_hvac_action_reason (line 3048) | async def test_cooler_fan_mode_opening_hvac_action_reason(
  function test_fan_mode_opening (line 3182) | async def test_fan_mode_opening(
  function test_cooler_fan_mode_opening (line 3281) | async def test_cooler_fan_mode_opening(
  function test_cooler_fan_mode_opening_scope (line 3437) | async def test_cooler_fan_mode_opening_scope(

FILE: tests/test_fan_speed_control.py
  function setup_fan_services (line 31) | def setup_fan_services(hass: HomeAssistant) -> list:
  function test_fan_mode_percentage_mappings_exist (line 51) | def test_fan_mode_percentage_mappings_exist():
  function test_percentage_to_fan_mode_mapping (line 64) | def test_percentage_to_fan_mode_mapping():
  function test_auto_mode_uses_100_percent_same_as_high (line 75) | def test_auto_mode_uses_100_percent_same_as_high():
  function test_fan_device_detects_preset_modes (line 90) | async def test_fan_device_detects_preset_modes(hass: HomeAssistant):
  function test_fan_device_detects_percentage_support (line 127) | async def test_fan_device_detects_percentage_support(hass: HomeAssistant):
  function test_fan_device_switch_no_speed_control (line 160) | async def test_fan_device_switch_no_speed_control(hass: HomeAssistant):
  function test_fan_device_missing_entity_no_speed_control (line 186) | async def test_fan_device_missing_entity_no_speed_control(hass: HomeAssi...
  function test_set_fan_mode_invalid_mode (line 210) | async def test_set_fan_mode_invalid_mode(hass: HomeAssistant):
  function test_set_fan_mode_unsupported_device (line 246) | async def test_set_fan_mode_unsupported_device(hass: HomeAssistant):
  function test_turn_on_applies_fan_mode_preset (line 278) | async def test_turn_on_applies_fan_mode_preset(hass: HomeAssistant):
  function test_turn_on_applies_fan_mode_percentage (line 335) | async def test_turn_on_applies_fan_mode_percentage(hass: HomeAssistant):
  function test_turn_on_without_fan_mode_set (line 391) | async def test_turn_on_without_fan_mode_set(hass: HomeAssistant):
  function test_turn_on_switch_device_no_fan_mode_applied (line 436) | async def test_turn_on_switch_device_no_fan_mode_applied(hass: HomeAssis...
  function test_turn_on_handles_fan_mode_service_failure_preset (line 471) | async def test_turn_on_handles_fan_mode_service_failure_preset(
  function test_turn_on_handles_fan_mode_service_failure_percentage (line 534) | async def test_turn_on_handles_fan_mode_service_failure_percentage(
  function test_feature_manager_supports_fan_mode_with_preset_modes (line 599) | async def test_feature_manager_supports_fan_mode_with_preset_modes(hass:...
  function test_feature_manager_supports_fan_mode_with_percentage (line 645) | async def test_feature_manager_supports_fan_mode_with_percentage(hass: H...
  function test_feature_manager_no_fan_mode_support_switch (line 690) | async def test_feature_manager_no_fan_mode_support_switch(hass: HomeAssi...
  function test_feature_manager_fan_device_none (line 729) | async def test_feature_manager_fan_device_none(hass: HomeAssistant):
  function test_climate_supported_features_includes_fan_mode_when_supported (line 752) | async def test_climate_supported_features_includes_fan_mode_when_supported(
  function test_climate_supported_features_excludes_fan_mode_when_switch (line 821) | async def test_climate_supported_features_excludes_fan_mode_when_switch(
  function test_climate_supported_features_excludes_fan_mode_when_no_fan (line 883) | async def test_climate_supported_features_excludes_fan_mode_when_no_fan(
  function test_climate_fan_mode_property_returns_current_mode (line 941) | async def test_climate_fan_mode_property_returns_current_mode(hass: Home...
  function test_climate_fan_mode_property_none_when_not_supported (line 1005) | async def test_climate_fan_mode_property_none_when_not_supported(hass: H...
  function test_climate_fan_modes_property_returns_available_modes (line 1062) | async def test_climate_fan_modes_property_returns_available_modes(hass: ...
  function test_climate_fan_modes_property_none_when_not_supported (line 1126) | async def test_climate_fan_modes_property_none_when_not_supported(hass: ...
  function test_climate_async_set_fan_mode_service (line 1183) | async def test_climate_async_set_fan_mode_service(hass: HomeAssistant):
  function test_climate_async_set_fan_mode_updates_state (line 1255) | async def test_climate_async_set_fan_mode_updates_state(hass: HomeAssist...
  function test_climate_async_set_fan_mode_when_not_supported (line 1330) | async def test_climate_async_set_fan_mode_when_not_supported(hass: HomeA...
  function test_fan_mode_appears_in_extra_state_attributes (line 1396) | async def test_fan_mode_appears_in_extra_state_attributes(hass: HomeAssi...
  function test_fan_mode_not_in_attributes_when_not_supported (line 1470) | async def test_fan_mode_not_in_attributes_when_not_supported(hass: HomeA...
  function test_fan_mode_restored_after_restart (line 1528) | async def test_fan_mode_restored_after_restart(hass: HomeAssistant):
  function test_fan_mode_restoration_when_old_state_has_no_fan_mode (line 1613) | async def test_fan_mode_restoration_when_old_state_has_no_fan_mode(hass:...
  function test_fan_mode_restoration_when_fan_device_does_not_support_mode (line 1696) | async def test_fan_mode_restoration_when_fan_device_does_not_support_mode(
  function test_fan_activates_with_restored_fan_mode (line 1770) | async def test_fan_activates_with_restored_fan_mode(hass: HomeAssistant):

FILE: tests/test_heat_pump_mode.py
  function test_unique_id (line 50) | async def test_unique_id(
  function test_setup_defaults_to_unknown (line 94) | async def test_setup_defaults_to_unknown(hass: HomeAssistant) -> None:  ...
  function test_setup_gets_current_temperature_from_sensor (line 115) | async def test_setup_gets_current_temperature_from_sensor(
  function setup_comp_heat_pump (line 148) | async def setup_comp_heat_pump(hass: HomeAssistant) -> None:
  function test_get_hvac_modes (line 186) | async def test_get_hvac_modes(
  function test_heat_pump_with_fan_exposes_fan_only_mode (line 237) | async def test_heat_pump_with_fan_exposes_fan_only_mode(
  function test_heat_pump_with_fan_fan_only_mode_runs_fan_only (line 277) | async def test_heat_pump_with_fan_fan_only_mode_runs_fan_only(
  function setup_comp_heat_pump_presets (line 334) | async def setup_comp_heat_pump_presets(hass: HomeAssistant) -> None:
  function setup_comp_heat_pump_heat_cool_presets (line 380) | async def setup_comp_heat_pump_heat_cool_presets(hass: HomeAssistant) ->...
  function test_set_preset_mode (line 456) | async def test_set_preset_mode(
  function test_set_preset_mode_heat_cool (line 483) | async def test_set_preset_mode_heat_cool(
  function test_set_preset_mode_and_restore_prev_temp (line 513) | async def test_set_preset_mode_and_restore_prev_temp(
  function test_set_preset_mode_heat_cool_and_restore_prev_temp (line 544) | async def test_set_preset_mode_heat_cool_and_restore_prev_temp(
  function test_set_preset_mode_twice_and_restore_prev_temp (line 579) | async def test_set_preset_mode_twice_and_restore_prev_temp(
  function test_set_preset_mode_heat_cool_twice_and_restore_prev_temp (line 611) | async def test_set_preset_mode_heat_cool_twice_and_restore_prev_temp(
  function test_set_preset_mode_invalid (line 633) | async def test_set_preset_mode_invalid(
  function test_set_preset_mode_set_temp_keeps_preset_mode (line 665) | async def test_set_preset_mode_set_temp_keeps_preset_mode(
  function test_set_preset_mode_heat_cool_set_temp_keeps_preset_mode (line 712) | async def test_set_preset_mode_heat_cool_set_temp_keeps_preset_mode(
  function test_toggle (line 789) | async def test_toggle(
  function test_hvac_mode_cool (line 817) | async def test_hvac_mode_cool(
  function test_hvac_mode_heat (line 840) | async def test_hvac_mode_heat(
  function test_hvac_mode_heat_switches_to_cool (line 863) | async def test_hvac_mode_heat_switches_to_cool(
  function test_hvac_mode_cool_switches_to_heat (line 907) | async def test_hvac_mode_cool_switches_to_heat(
  function test_heat_cool_mode_switches_between_heat_cool_tolerances (line 956) | async def test_heat_cool_mode_switches_between_heat_cool_tolerances(
  function test_heat_pump_initial_hvac_mode_applied (line 1084) | async def test_heat_pump_initial_hvac_mode_applied(

FILE: tests/test_heat_pump_mode_behavioral.py
  function test_heat_pump_heating_threshold_with_default_tolerance (line 23) | async def test_heat_pump_heating_threshold_with_default_tolerance(hass: ...
  function test_heat_pump_cooling_threshold_with_default_tolerance (line 106) | async def test_heat_pump_cooling_threshold_with_default_tolerance(hass: ...
  function test_heat_pump_custom_tolerance_heating (line 189) | async def test_heat_pump_custom_tolerance_heating(hass: HomeAssistant):
  function test_heat_pump_custom_tolerance_cooling (line 273) | async def test_heat_pump_custom_tolerance_cooling(hass: HomeAssistant):
  function test_heat_pump_zero_tolerance (line 357) | async def test_heat_pump_zero_tolerance(hass: HomeAssistant):

FILE: tests/test_heater_mode.py
  function test_unique_id (line 88) | async def test_unique_id(
  function test_setup_defaults_to_unknown (line 128) | async def test_setup_defaults_to_unknown(hass: HomeAssistant) -> None:  ...
  function test_setup_gets_current_temp_from_sensor (line 148) | async def test_setup_gets_current_temp_from_sensor(
  function test_default_setup_params (line 174) | async def test_default_setup_params(
  function test_restore_state (line 189) | async def test_restore_state(hass: HomeAssistant, hvac_mode) -> None:
  function test_no_restore_state (line 224) | async def test_no_restore_state(hass: HomeAssistant) -> None:
  function test_reload (line 264) | async def test_reload(hass: HomeAssistant) -> None:
  function test_custom_setup_params (line 299) | async def test_custom_setup_params(hass: HomeAssistant) -> None:
  function test_sensor_bad_value (line 331) | async def test_sensor_bad_value(
  function test_sensor_unknown (line 354) | async def test_sensor_unknown(hass: HomeAssistant) -> None:  # noqa: F811
  function test_sensor_unavailable (line 374) | async def test_sensor_unavailable(hass: HomeAssistant) -> None:  # noqa:...
  function test_floor_sensor_bad_value (line 394) | async def test_floor_sensor_bad_value(
  function test_floor_sensor_unknown (line 417) | async def test_floor_sensor_unknown(hass: HomeAssistant) -> None:  # noq...
  function test_floor_sensor_unavailable (line 440) | async def test_floor_sensor_unavailable(hass: HomeAssistant) -> None:  #...
  function test_heater_unknown_to_available (line 463) | async def test_heater_unknown_to_available(
  function test_get_hvac_modes (line 547) | async def test_get_hvac_modes(
  function test_set_target_temp (line 556) | async def test_set_target_temp(
  function test_set_target_temp_and_hvac_mode (line 569) | async def test_set_target_temp_and_hvac_mode(
  function test_set_preset_mode (line 604) | async def test_set_preset_mode(
  function 
Condensed preview — 320 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (4,148K chars).
[
  {
    "path": ".copilot-instructions.md",
    "chars": 25302,
    "preview": "# Copilot Instructions for Home Assistant Dual Smart Thermostat\n\n## Project Overview\n\nThe `dual_smart_thermostat` is an "
  },
  {
    "path": ".coveragerc",
    "chars": 77,
    "preview": "[run]\nbranch = True\n\n[report]\nskip_empty = True\ninclude = custom_components/*"
  },
  {
    "path": ".devcontainer.json",
    "chars": 1169,
    "preview": "{\n\t\"name\": \"Dual Smart THermostat Integration\",\n\t\"image\": \"mcr.microsoft.com/devcontainers/python:dev-3.14-bookworm\",\n\t\""
  },
  {
    "path": ".dockerignore",
    "chars": 897,
    "preview": "# Git files\n.git\n.gitignore\n.gitattributes\n\n# Python cache and artifacts\n__pycache__\n*.py[cod]\n*$py.class\n*.so\n.Python\n*"
  },
  {
    "path": ".github/DEPENDABOT_AUTO_MERGE.md",
    "chars": 5904,
    "preview": "# Dependabot Auto-Merge Configuration\n\nThis repository has been configured with automated Dependabot dependency updates "
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 149,
    "preview": "custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=S6NC9BYVDDJMA&source=url\ncustom: https://ww"
  },
  {
    "path": ".github/RELEASE_TEMPLATE.md",
    "chars": 1373,
    "preview": "## What's Changed\n\n<!-- Describe the changes in this release -->\n\n## Notable Features\n\n<!-- Highlight any new features o"
  },
  {
    "path": ".github/SECURITY_REMEDIATION.md",
    "chars": 5061,
    "preview": "# Security Vulnerability Remediation Guide\n\n## 🚨 Current Security Issues\n\nThe security scan has identified **3 critical "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 1250,
    "preview": "# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabo"
  },
  {
    "path": ".github/prompts/plan.prompt.md",
    "chars": 1745,
    "preview": "---\ndescription: Execute the implementation planning workflow using the plan template to generate design artifacts.\n---\n"
  },
  {
    "path": ".github/prompts/specify.prompt.md",
    "chars": 843,
    "preview": "---\ndescription: Create or update the feature specification from a natural language feature description.\n---\n\nGiven the "
  },
  {
    "path": ".github/prompts/tasks.prompt.md",
    "chars": 2421,
    "preview": "---\ndescription: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts"
  },
  {
    "path": ".github/release.yml",
    "chars": 977,
    "preview": "changelog:\n  exclude:\n    labels:\n      - ignore-for-release\n    authors:\n      - dependabot\n  categories:\n    - title: "
  },
  {
    "path": ".github/scripts/update_hacs_manifest.py",
    "chars": 2117,
    "preview": "\"\"\"Update the manifest file.\"\"\"\n\nimport json\nimport os\nimport sys\n\n\ndef update_manifest():\n    \"\"\"Update the manifest fi"
  },
  {
    "path": ".github/workflows/claude.yml",
    "chars": 1243,
    "preview": "name: Claude Code\n\non:\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n  issue"
  },
  {
    "path": ".github/workflows/dependabot-auto-merge.yml",
    "chars": 7681,
    "preview": "name: Dependabot Auto-Merge\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  auto-merge:\n    # O"
  },
  {
    "path": ".github/workflows/hacs-validate.yaml",
    "chars": 557,
    "preview": "name: Validate with HACS\n\non:\n  push:\n    branches:\n      - master\n\n  pull_request:\n    branches: \"*\"\n\n  schedule:\n    -"
  },
  {
    "path": ".github/workflows/linting.yaml",
    "chars": 681,
    "preview": "name: Linting\n\non:\n  push:\n    branches:\n      - master\n\n  pull_request:\n    branches: \"*\"\n\njobs:\n  lint:\n    runs-on: u"
  },
  {
    "path": ".github/workflows/quality-check.yaml",
    "chars": 1251,
    "preview": "name: Quality Check\n\non:\n  push:\n    branches:\n      - master\n\n  pull_request:\n    branches: \"*\"\n\njobs:\n  sonarcloud:\n  "
  },
  {
    "path": ".github/workflows/security-check.yml",
    "chars": 7843,
    "preview": "name: Security and Quality Check\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches: \"*\"\n  schedule:"
  },
  {
    "path": ".github/workflows/tests.yaml",
    "chars": 1014,
    "preview": "name: Python tests\n\non:\n  push:\n    branches:\n      - master\n\n  pull_request:\n    branches: \"*\"\n\njobs:\n  tests:\n    runs"
  },
  {
    "path": ".github/workflows/workflow-status.yml",
    "chars": 3777,
    "preview": "name: Workflow Status Check\n\non:\n  schedule:\n    - cron: \"0 9 * * 1\"  # Weekly on Monday at 9 AM\n  workflow_dispatch:  #"
  },
  {
    "path": ".gitignore",
    "chars": 287,
    "preview": "# artifacts\n__pycache__\n.pytest*\n*.egg-info\n*/build/*\n*/dist/*\n\n\n# misc\n.coverage\n.vscode\n!.vscode/settings.json\n!.vscod"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 874,
    "preview": "repos:\n  - repo: https://github.com/psf/black-pre-commit-mirror\n    rev: 24.2.0\n    hooks:\n      - id: black\n        lan"
  },
  {
    "path": ".specify/memory/constitution.md",
    "chars": 2336,
    "preview": "# [PROJECT_NAME] Constitution\n<!-- Example: Spec Constitution, TaskFlow Constitution, etc. -->\n\n## Core Principles\n\n### "
  },
  {
    "path": ".specify/memory/constitution_update_checklist.md",
    "chars": 2794,
    "preview": "# Constitution Update Checklist\n\nWhen amending the constitution (`/memory/constitution.md`), ensure all dependent docume"
  },
  {
    "path": ".specify/templates/agent-file-template.md",
    "chars": 464,
    "preview": "# [PROJECT NAME] Development Guidelines\n\nAuto-generated from all feature plans. Last updated: [DATE]\n\n## Active Technolo"
  },
  {
    "path": ".specify/templates/checklist-template.md",
    "chars": 1312,
    "preview": "# [CHECKLIST TYPE] Checklist: [FEATURE NAME]\n\n**Purpose**: [Brief description of what this checklist covers]\n**Created**"
  },
  {
    "path": ".specify/templates/plan-template.md",
    "chars": 3509,
    "preview": "# Implementation Plan: [FEATURE]\n\n**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]\n**Input**: Feat"
  },
  {
    "path": ".specify/templates/spec-template.md",
    "chars": 3960,
    "preview": "# Feature Specification: [FEATURE NAME]\n\n**Feature Branch**: `[###-feature-name]`  \n**Created**: [DATE]  \n**Status**: Dr"
  },
  {
    "path": ".specify/templates/tasks-template.md",
    "chars": 9140,
    "preview": "---\n\ndescription: \"Task list template for feature implementation\"\n---\n\n# Tasks: [FEATURE NAME]\n\n**Input**: Design docume"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2412,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CLAUDE.md",
    "chars": 33607,
    "preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
  },
  {
    "path": "Dockerfile.dev",
    "chars": 2701,
    "preview": "# Development Dockerfile for Dual Smart Thermostat Integration\n# This image is used for testing, linting, and developmen"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "LICENSE.md",
    "chars": 1054,
    "preview": "# Copyright 2020 Miklos Szanyi\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this sof"
  },
  {
    "path": "README-DOCKER.md",
    "chars": 12677,
    "preview": "# Docker-Based Development Workflow\n\nThis guide explains how to use Docker for development, testing, and linting without"
  },
  {
    "path": "README.md",
    "chars": 56234,
    "preview": "# Home Assistant Dual Smart Thermostat component\n\n\nThe `dual_smart_thermostat` is an enhanced version of generic thermos"
  },
  {
    "path": "RELEASE_NOTES_v0.11.0.md",
    "chars": 6548,
    "preview": "# v0.11.0 - Production Ready & Enhanced Flexibility 🚀\n\n> **Stable Release**: Set up your smart thermostat in minutes wit"
  },
  {
    "path": "action/Dockerfile",
    "chars": 169,
    "preview": "FROM ludeeus/container:hacs-action\n\nRUN git clone https://github.com/hacs/default.git /default\n\nCOPY action.py /hacs/act"
  },
  {
    "path": "action/action.py",
    "chars": 4522,
    "preview": "\"\"\"Validate a GitHub repository to be used with HACS.\"\"\"\n\nimport asyncio\nimport json\nimport os\n\nfrom aiogithubapi import"
  },
  {
    "path": "action/action.yaml",
    "chars": 324,
    "preview": "name: \"HACS\"\ndescription: \"GitHub action for HACS.\"\ninputs:\n  github_token:\n    description: 'Your personal GitHub Acces"
  },
  {
    "path": "build_release.sh",
    "chars": 377,
    "preview": "#!/usr/bin/env bash\n\nset -ex\n\nROOT_DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nTEMP_DIR=`mkt"
  },
  {
    "path": "config/configuration.yaml",
    "chars": 15028,
    "preview": "default_config:\n\nrecorder:\n\ninput_boolean:\n  heater_on:\n    name: Heater toggle\n  aux_heater_on:\n    name: AUX Heater to"
  },
  {
    "path": "custom_components/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "custom_components/dual_smart_thermostat/__init__.py",
    "chars": 976,
    "preview": "\"\"\"The dual_smart_thermostat component.\"\"\"\n\nfrom homeassistant.config_entries import ConfigEntry\nfrom homeassistant.cons"
  },
  {
    "path": "custom_components/dual_smart_thermostat/climate.py",
    "chars": 72794,
    "preview": "\"\"\"Adds support for dual smart thermostat units.\"\"\"\n\nimport asyncio\nfrom collections.abc import Callable\nfrom datetime i"
  },
  {
    "path": "custom_components/dual_smart_thermostat/config_flow.py",
    "chars": 40716,
    "preview": "\"\"\"Config flow for Dual Smart Thermostat integration.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing"
  },
  {
    "path": "custom_components/dual_smart_thermostat/config_validation.py",
    "chars": 5966,
    "preview": "\"\"\"Configuration validation using data models.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import"
  },
  {
    "path": "custom_components/dual_smart_thermostat/const.py",
    "chars": 5367,
    "preview": "\"\"\"const.\"\"\"\n\nimport enum\n\nfrom homeassistant.components.climate.const import (\n    PRESET_ACTIVITY,\n    PRESET_AWAY,\n  "
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/__init__.py",
    "chars": 344,
    "preview": "\"\"\"Feature-specific configuration steps for dual smart thermostat.\"\"\"\n\nfrom .fan import FanSteps\nfrom .floor import Floo"
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/fan.py",
    "chars": 3845,
    "preview": "\"\"\"Fan configuration steps.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import Any\n\nfrom homeassi"
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/floor.py",
    "chars": 5242,
    "preview": "\"\"\"Floor heating configuration steps shared between config and options flows.\"\"\"\n\nfrom __future__ import annotations\n\nfr"
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/humidity.py",
    "chars": 2173,
    "preview": "\"\"\"Humidity configuration steps.\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Any\n\nfrom homeassistant.data"
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/openings.py",
    "chars": 14150,
    "preview": "\"\"\"Openings configuration steps.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import Any\n\nfrom hom"
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/presets.py",
    "chars": 15165,
    "preview": "\"\"\"Presets configuration steps.\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Any\n\nfrom homeassistant.confi"
  },
  {
    "path": "custom_components/dual_smart_thermostat/feature_steps/shared.py",
    "chars": 3150,
    "preview": "\"\"\"Shared helpers for feature step handlers.\"\"\"\n\nfrom __future__ import annotations\n\nimport inspect\nfrom typing import A"
  },
  {
    "path": "custom_components/dual_smart_thermostat/flow_utils.py",
    "chars": 9924,
    "preview": "\"\"\"Shared utilities for config and options flows.\"\"\"\n\nfrom __future__ import annotations\n\nfrom typing import Any\n\nfrom ."
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_action_reason/__init__.py",
    "chars": 32,
    "preview": "\"\"\"Hvac Action Reason Module\"\"\"\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason.py",
    "chars": 756,
    "preview": "import enum\nfrom itertools import chain\n\nfrom ..hvac_action_reason.hvac_action_reason_auto import HVACActionReasonAuto\nf"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason_auto.py",
    "chars": 492,
    "preview": "import enum\n\n\nclass HVACActionReasonAuto(enum.StrEnum):\n    \"\"\"Auto-mode-selected HVAC Action Reason.\n\n    Values declar"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason_external.py",
    "chars": 235,
    "preview": "import enum\n\n\nclass HVACActionReasonExternal(enum.StrEnum):\n    \"\"\"External HVAC Action Reason for climate devices.\"\"\"\n\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_action_reason/hvac_action_reason_internal.py",
    "chars": 729,
    "preview": "import enum\n\n\nclass HVACActionReasonInternal(enum.StrEnum):\n    \"\"\"Internal HVAC Action Reason for climate devices.\"\"\"\n\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_controller/__init__.py",
    "chars": 56,
    "preview": "\"\"\"HVAC controller module for Dual Smart Thermostat.\"\"\"\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_controller/cooler_controller.py",
    "chars": 957,
    "preview": "from datetime import timedelta\nimport logging\nfrom typing import Callable\n\nfrom homeassistant.core import HomeAssistant\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_controller/generic_controller.py",
    "chars": 7910,
    "preview": "from datetime import timedelta\nimport logging\nfrom typing import Callable\n\nfrom homeassistant.components.climate import "
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_controller/heater_controller.py",
    "chars": 5168,
    "preview": "from datetime import timedelta\nimport logging\nfrom typing import Callable\n\nfrom homeassistant.core import HomeAssistant\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_controller/hvac_controller.py",
    "chars": 3258,
    "preview": "from abc import ABC, abstractmethod\nfrom datetime import timedelta\nimport enum\nimport logging\nfrom typing import Callabl"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/__init__.py",
    "chars": 26,
    "preview": "\"\"\"Hvac Device Module.\"\"\"\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/controllable_hvac_device.py",
    "chars": 3765,
    "preview": "from abc import ABC, abstractmethod\nimport logging\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfr"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/cooler_device.py",
    "chars": 1971,
    "preview": "from datetime import timedelta\nimport logging\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfrom ho"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/cooler_fan_device.py",
    "chars": 8488,
    "preview": "from datetime import datetime, timezone\nimport logging\nfrom typing import Callable\n\nfrom homeassistant.components.climat"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/dryer_device.py",
    "chars": 2980,
    "preview": "from datetime import timedelta\nimport logging\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfrom ho"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/fan_device.py",
    "chars": 7479,
    "preview": "from datetime import timedelta\nimport logging\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfrom ho"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/generic_hvac_device.py",
    "chars": 12754,
    "preview": "from datetime import timedelta\nimport logging\nfrom typing import Callable\n\nfrom homeassistant.components.climate import "
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/heat_pump_device.py",
    "chars": 9096,
    "preview": "from datetime import timedelta\nimport logging\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfrom ho"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/heater_aux_heater_device.py",
    "chars": 8978,
    "preview": "import datetime\nfrom datetime import timedelta\nimport logging\n\nfrom homeassistant.components.climate import HVACMode\nfro"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/heater_cooler_device.py",
    "chars": 7848,
    "preview": "import logging\n\nfrom homeassistant.components.climate import HVACMode\nfrom homeassistant.core import HomeAssistant\n\nfrom"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/heater_device.py",
    "chars": 2103,
    "preview": "from datetime import timedelta\nimport logging\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfrom ho"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/hvac_device.py",
    "chars": 1790,
    "preview": "from abc import ABC, abstractmethod\nimport logging\nfrom typing import Self\n\nfrom homeassistant.components.climate import"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/hvac_device_factory.py",
    "chars": 9787,
    "preview": "from datetime import timedelta\nimport logging\n\nfrom homeassistant.core import HomeAssistant\nfrom homeassistant.helpers.t"
  },
  {
    "path": "custom_components/dual_smart_thermostat/hvac_device/multi_hvac_device.py",
    "chars": 5955,
    "preview": "import logging\nfrom typing import Callable\n\nfrom homeassistant.components.climate import HVACAction, HVACMode\nfrom homea"
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/__init__.py",
    "chars": 21,
    "preview": "\"\"\"Manager Module\"\"\"\n"
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/auto_mode_evaluator.py",
    "chars": 12863,
    "preview": "\"\"\"Auto Mode priority evaluator.\n\nPure decision class. Reads from injected EnvironmentManager / OpeningManager /\nFeature"
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/environment_manager.py",
    "chars": 40483,
    "preview": "from datetime import timedelta\nimport enum\nimport logging\nimport math\n\nfrom homeassistant.components.climate import (\n  "
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/feature_manager.py",
    "chars": 13615,
    "preview": "from __future__ import annotations\n\nfrom functools import cached_property\nimport logging\nfrom typing import TYPE_CHECKIN"
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/hvac_power_manager.py",
    "chars": 6994,
    "preview": "import logging\n\nfrom homeassistant.components.climate import HVACAction\nfrom homeassistant.core import HomeAssistant\nfro"
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/opening_manager.py",
    "chars": 7150,
    "preview": "\"\"\"Opening Manager for Dual Smart Thermostat.\"\"\"\n\nimport enum\nfrom itertools import chain\nimport logging\nfrom typing imp"
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/preset_manager.py",
    "chars": 16841,
    "preview": "import logging\n\nfrom homeassistant.components.climate.const import (\n    ATTR_PRESET_MODE,\n    ATTR_TARGET_TEMP_HIGH,\n  "
  },
  {
    "path": "custom_components/dual_smart_thermostat/managers/state_manager.py",
    "chars": 192,
    "preview": "from abc import ABC, abstractmethod\n\nfrom homeassistant.core import State\n\n\nclass StateManager(ABC):\n\n    @abstractmetho"
  },
  {
    "path": "custom_components/dual_smart_thermostat/manifest.json",
    "chars": 579,
    "preview": "{\n  \"domain\": \"dual_smart_thermostat\",\n  \"name\": \"Dual Smart Thermostat\",\n  \"codeowners\": [\n    \"@swingerman\"\n  ],\n  \"co"
  },
  {
    "path": "custom_components/dual_smart_thermostat/models.py",
    "chars": 9773,
    "preview": "\"\"\"Data models for Dual Smart Thermostat configuration.\n\nThis module provides type-safe dataclasses representing the can"
  },
  {
    "path": "custom_components/dual_smart_thermostat/options_flow.py",
    "chars": 36633,
    "preview": "\"\"\"Options flow for Dual Smart Thermostat.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom typing import Any"
  },
  {
    "path": "custom_components/dual_smart_thermostat/preset_env/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "custom_components/dual_smart_thermostat/preset_env/preset_env.py",
    "chars": 8064,
    "preview": "import logging\nimport re\nfrom typing import Any\n\nfrom homeassistant.components.climate.const import (\n    ATTR_HUMIDITY,"
  },
  {
    "path": "custom_components/dual_smart_thermostat/schema_utils.py",
    "chars": 7393,
    "preview": "\"\"\"Schema utilities for config and options flows.\"\"\"\n\nfrom __future__ import annotations\n\nfrom homeassistant.const impor"
  },
  {
    "path": "custom_components/dual_smart_thermostat/schemas.py",
    "chars": 45168,
    "preview": "\"\"\"Schema definitions for dual smart thermostat configuration.\"\"\"\n\nfrom __future__ import annotations\n\nfrom datetime imp"
  },
  {
    "path": "custom_components/dual_smart_thermostat/sensor.py",
    "chars": 5629,
    "preview": "\"\"\"Sensor platform for dual_smart_thermostat.\n\nPhase 0 of the Auto Mode roadmap (#563): exposes each climate entity's\n``"
  },
  {
    "path": "custom_components/dual_smart_thermostat/services.yaml",
    "chars": 586,
    "preview": "reload:\n   name: Reload Dual Smart Thermostat\n   description: Reload all Dual Smart Thermostat entities.\n\nset_hvac_actio"
  },
  {
    "path": "custom_components/dual_smart_thermostat/translations/en.json",
    "chars": 87531,
    "preview": "{\n    \"title\": \"Dual Smart Thermostat\",\n    \"entity\": {\n        \"sensor\": {\n            \"hvac_action_reason\": {\n        "
  },
  {
    "path": "custom_components/dual_smart_thermostat/translations/sk.json",
    "chars": 7016,
    "preview": "{\n  \"entity\": {\n    \"sensor\": {\n      \"hvac_action_reason\": {\n        \"state\": {\n          \"none\": \"None\",\n          \"mi"
  },
  {
    "path": "demo_openings_translations.py",
    "chars": 2749,
    "preview": "#!/usr/bin/env python3\n\"\"\"Demo script to show the improved openings scope translations.\"\"\"\n\nfrom custom_components.dual_"
  },
  {
    "path": "demo_translations.py",
    "chars": 2303,
    "preview": "#!/usr/bin/env python3\n\"\"\"Demo script to verify translation functionality for openings scope options.\"\"\"\n\nimport json\nim"
  },
  {
    "path": "docker-compose.yml",
    "chars": 2022,
    "preview": "version: '3.8'\n\nservices:\n  # Development service - for running tests, linting, and development commands\n  dev:\n    buil"
  },
  {
    "path": "docs/TESTING.md",
    "chars": 11413,
    "preview": "# Testing Guide\n\nThis document provides comprehensive guidance on the test structure and how to add new tests.\n\n## Test "
  },
  {
    "path": "docs/config/CONFIG_FLOW.md",
    "chars": 4875,
    "preview": "# Dual Smart Thermostat Config Flow\n\nThis document describes the implementation of the config flow for the Dual Smart Th"
  },
  {
    "path": "docs/config/CRITICAL_CONFIG_DEPENDENCIES.md",
    "chars": 19290,
    "preview": "# Critical Configuration Parameter Dependencies\n\n## Overview\n\nThis document focuses **exclusively** on configuration par"
  },
  {
    "path": "docs/config/DEPENDENCY_ANALYSIS_SUMMARY.md",
    "chars": 7219,
    "preview": "# Dual Smart Thermostat Parameter Dependency Analysis - Summary\n\n## 📊 Analysis Results\n\nThis comprehensive parameter dep"
  },
  {
    "path": "docs/config/FOCUSED_DEPENDENCIES_SUMMARY.md",
    "chars": 4360,
    "preview": "# Dual Smart Thermostat - Focused Configuration Dependencies\n\n## 🎯 Executive Summary\n\nThis analysis identifies **22 crit"
  },
  {
    "path": "docs/config_flow/ac_only_features.md",
    "chars": 7177,
    "preview": "# AC-Only Features Configuration\n\n## Overview\n\nAC-only systems have specialized configuration options designed for air c"
  },
  {
    "path": "docs/config_flow/architecture.md",
    "chars": 6886,
    "preview": "# Configuration Flow Architecture\n\n## Overview\n\nThe dual smart thermostat integration uses Home Assistant's config flow "
  },
  {
    "path": "docs/config_flow/step_ordering.md",
    "chars": 3139,
    "preview": "# Configuration Flow Step Ordering Rules\n\n## Overview\n\nThe dual smart thermostat configuration flow must follow specific"
  },
  {
    "path": "docs/plans/2026-01-21-fan-speed-control-design.md",
    "chars": 15995,
    "preview": "# Fan Speed Control Design\n\n**Issue:** #517 - Support for fan speeds\n**Date:** 2026-01-21\n**Status:** Design Complete\n\n#"
  },
  {
    "path": "docs/plans/2026-01-21-fan-speed-control.md",
    "chars": 39294,
    "preview": "# Fan Speed Control Implementation Plan\n\n> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implem"
  },
  {
    "path": "docs/superpowers/plans/2026-04-21-auto-mode-phase-0-action-reason-sensor.md",
    "chars": 47579,
    "preview": "# Auto Mode — Phase 0: `hvac_action_reason` Sensor Entity — Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB"
  },
  {
    "path": "docs/superpowers/plans/2026-04-22-auto-mode-phase-1-1-availability-detection.md",
    "chars": 14930,
    "preview": "# Auto Mode — Phase 1.1: Availability Detection — Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Us"
  },
  {
    "path": "docs/superpowers/plans/2026-04-27-auto-mode-phase-1-2-priority-engine.md",
    "chars": 65928,
    "preview": "# Auto Mode — Phase 1.2: Priority Engine — Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use super"
  },
  {
    "path": "docs/superpowers/plans/2026-04-29-auto-mode-phase-1-3-outside-bias.md",
    "chars": 59530,
    "preview": "# Auto Mode Phase 1.3 — Outside-Temperature Bias Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use"
  },
  {
    "path": "docs/superpowers/plans/2026-04-30-auto-mode-phase-1-4-apparent-temp.md",
    "chars": 56454,
    "preview": "# Auto Mode Phase 1.4 — Apparent Temperature Implementation Plan\n\n> **For agentic workers:** REQUIRED SUB-SKILL: Use sup"
  },
  {
    "path": "docs/superpowers/specs/2026-04-21-auto-mode-phase-0-action-reason-sensor-design.md",
    "chars": 13118,
    "preview": "# Auto Mode — Phase 0: `hvac_action_reason` as Sensor Entity\n\n- **Status:** Approved (design)\n- **Date:** 2026-04-21\n- *"
  },
  {
    "path": "docs/superpowers/specs/2026-04-22-auto-mode-phase-1-1-availability-detection-design.md",
    "chars": 7797,
    "preview": "# Auto Mode — Phase 1.1: Availability Detection\n\n- **Status:** Approved (design)\n- **Date:** 2026-04-22\n- **Branch:** `f"
  },
  {
    "path": "docs/superpowers/specs/2026-04-27-auto-mode-phase-1-2-priority-engine-design.md",
    "chars": 22128,
    "preview": "# Auto Mode — Phase 1.2: Priority Evaluation Engine\n\n- **Status:** Approved (design)\n- **Date:** 2026-04-27\n- **Branch:*"
  },
  {
    "path": "docs/superpowers/specs/2026-04-29-auto-mode-phase-1-3-outside-bias-design.md",
    "chars": 8421,
    "preview": "# Auto Mode Phase 1.3 — Outside-Temperature Bias\n\n**Date:** 2026-04-29\n**Roadmap issue:** #563\n**Depends on:** Phase 1.2"
  },
  {
    "path": "docs/superpowers/specs/2026-04-30-auto-mode-phase-1-4-apparent-temp-design.md",
    "chars": 9781,
    "preview": "# Auto Mode Phase 1.4 — Apparent (\"Feels-Like\") Temperature\n\n**Date:** 2026-04-30\n**Roadmap issue:** #563\n**Depends on:*"
  },
  {
    "path": "docs/troubleshooting.md",
    "chars": 22753,
    "preview": "# Troubleshooting Guide\n\nThis document provides solutions to common issues with the Dual Smart Thermostat integration.\n\n"
  },
  {
    "path": "examples/README.md",
    "chars": 2425,
    "preview": "# Dual Smart Thermostat Examples\n\nThis directory contains practical examples and use cases for the Dual Smart Thermostat"
  },
  {
    "path": "examples/advanced_features/floor_heating_with_limits.yaml",
    "chars": 5300,
    "preview": "# Floor Heating with Temperature Limits\n#\n# Prevents floor damage and discomfort by enforcing min/max floor temperatures"
  },
  {
    "path": "examples/advanced_features/openings_with_timeout.yaml",
    "chars": 10674,
    "preview": "# Opening Detection (Window/Door Sensors)\n#\n# Automatically pauses HVAC when windows or doors are open to save energy.\n#"
  },
  {
    "path": "examples/advanced_features/presets_advanced.yaml",
    "chars": 10535,
    "preview": "# Advanced Preset Configuration\n#\n# Presets allow different temperature settings for various scenarios.\n# Perfect for: A"
  },
  {
    "path": "examples/advanced_features/presets_with_templates.yaml",
    "chars": 20548,
    "preview": "# Presets with Template-Based Temperatures\n#\n# Templates allow preset temperatures to dynamically adjust based on:\n# - O"
  },
  {
    "path": "examples/advanced_features/two_stage_heating.yaml",
    "chars": 7917,
    "preview": "# Two-Stage (AUX/Emergency) Heating\n#\n# Automatically activates auxiliary heating when primary heat is insufficient.\n# P"
  },
  {
    "path": "examples/basic_configurations/cooler_only.yaml",
    "chars": 2755,
    "preview": "# Cooler Only (AC) Configuration\n#\n# Air conditioning only - no heating capability.\n# Perfect for: Window AC units, port"
  },
  {
    "path": "examples/basic_configurations/heat_pump.yaml",
    "chars": 3704,
    "preview": "# Heat Pump Configuration (Single Switch)\n#\n# For heat pumps that use ONE switch for both heating and cooling.\n# The sys"
  },
  {
    "path": "examples/basic_configurations/heater_cooler.yaml",
    "chars": 4140,
    "preview": "# Heater + Cooler (Dual Mode) Configuration\n#\n# For systems with SEPARATE heating and cooling switches.\n# This gives you"
  },
  {
    "path": "examples/basic_configurations/heater_only.yaml",
    "chars": 2214,
    "preview": "# Heater Only Configuration\n#\n# This is the simplest configuration - only heating, no cooling.\n# Perfect for: Baseboard "
  },
  {
    "path": "examples/integrations/smart_scheduling.yaml",
    "chars": 13069,
    "preview": "# Smart Scheduling Examples\n#\n# Automate your thermostat based on time, presence, and conditions.\n# Perfect for: Daily r"
  },
  {
    "path": "examples/single_mode_wrapper/README.md",
    "chars": 10451,
    "preview": "# Single-Mode Thermostat Wrapper\n\n**Use Case**: Create Nest-like \"Keep Between\" functionality on top of a single-mode th"
  },
  {
    "path": "examples/single_mode_wrapper/automation.yaml",
    "chars": 5987,
    "preview": "# Reconciliation Automation for Single-Mode Thermostat Wrapper\n#\n# This automation translates the virtual dual thermosta"
  },
  {
    "path": "examples/single_mode_wrapper/configuration.yaml",
    "chars": 1482,
    "preview": "# Dual Smart Thermostat Configuration for Single-Mode Wrapper\n#\n# This creates a virtual dual-mode thermostat that wraps"
  },
  {
    "path": "examples/single_mode_wrapper/helpers.yaml",
    "chars": 1061,
    "preview": "# Helper entities for Single-Mode Thermostat Wrapper\n#\n# These helpers are used by the dual smart thermostat and automat"
  },
  {
    "path": "hacs.json",
    "chars": 187,
    "preview": "{\n  \"name\": \"Dual Smart Thermostat\",\n  \"render_readme\": true,\n  \"hide_default_branch\": true,\n  \"country\": [],\n  \"homeass"
  },
  {
    "path": "manage/bump_frontend",
    "chars": 691,
    "preview": " #!/bin/bash\n\nversion=$(curl -sSL -f \"https://github.com/hacs/frontend/releases/latest\" | grep \"<title>\" | awk -F\" \" '{p"
  },
  {
    "path": "manage/hacs",
    "chars": 254,
    "preview": " #!/bin/bash\n\nfunction hacs-update-requirements {\n    echo \"Updating requirements.\"\n    python3 ./manage/update_requirem"
  },
  {
    "path": "manage/integration_start",
    "chars": 545,
    "preview": "#!/usr/bin/env bash\n\n# Make the config dir\nmkdir -p /tmp/config\n\n\n# Symplink the custom_components dir\nif [ -d \"/tmp/con"
  },
  {
    "path": "manage/lgtm.js",
    "chars": 47,
    "preview": "console.log(\"Dummy file to make LGTM happy...\")"
  },
  {
    "path": "manage/update_manifest.py",
    "chars": 651,
    "preview": "\"\"\"Update the manifest file.\"\"\"\n\nimport json\nimport os\nimport sys\n\n\ndef update_manifest() -> None:\n    \"\"\"Update the man"
  },
  {
    "path": "manage/update_requirements.py",
    "chars": 1455,
    "preview": "import json\nimport os\n\nimport requests\n\nharequire = []\nrequest = requests.get(\n    \"https://raw.githubusercontent.com/ho"
  },
  {
    "path": "pcap.py",
    "chars": 5503,
    "preview": "\"\"\"Lightweight ctypes-based wrapper around libpcap for basic operations.\n\nThis is a minimal shim to allow compiling and "
  },
  {
    "path": "pytest.ini",
    "chars": 252,
    "preview": "[pytest]\nasyncio_mode = auto\nasyncio_default_fixture_loop_scope = function\nfilterwarnings =\n\tignore::pytest.PytestReturn"
  },
  {
    "path": "pytest.log",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "requirements-dev.txt",
    "chars": 523,
    "preview": "-r requirements.txt\npip>=24.1.2,<27.0\npytest-homeassistant-custom-component==0.13.324\n# Explicit pytest dependencies (ma"
  },
  {
    "path": "requirements.txt",
    "chars": 1133,
    "preview": "# Python requirements for development.\n#\n# NOTE: Some runtime dependencies (packet capture, ffmpeg, turbojpeg) are provi"
  },
  {
    "path": "scripts/devcontainer_install_deps.sh",
    "chars": 2798,
    "preview": "#!/usr/bin/env bash\n# Install system dependencies needed by Home Assistant dev environment inside the devcontainer.\n# Th"
  },
  {
    "path": "scripts/develop",
    "chars": 577,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncd \"$(dirname \"$0\")/..\"\n\n# Create config dir if not present\nif [[ ! -d \"${PWD}/config\" ]]; "
  },
  {
    "path": "scripts/docker-lint",
    "chars": 1030,
    "preview": "#!/usr/bin/env bash\n# Run linting checks in Docker container\n# Usage:\n#   ./scripts/docker-lint           # Run all lint"
  },
  {
    "path": "scripts/docker-shell",
    "chars": 522,
    "preview": "#!/usr/bin/env bash\n# Open an interactive shell in the Docker development container\n# Usage:\n#   ./scripts/docker-shell "
  },
  {
    "path": "scripts/docker-test",
    "chars": 652,
    "preview": "#!/usr/bin/env bash\n# Run tests in Docker container\n# Usage:\n#   ./scripts/docker-test                    # Run all test"
  },
  {
    "path": "scripts/lint",
    "chars": 72,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncd \"$(dirname \"$0\")/..\"\n\nruff check . --fix"
  },
  {
    "path": "scripts/setup",
    "chars": 140,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncd \"$(dirname \"$0\")/..\"\n\nsudo apt-get install ffmpeg\n\npython3 -m pip install --requirement "
  },
  {
    "path": "setup.cfg",
    "chars": 2083,
    "preview": "[coverage:run]\nsource =\n  custom_components\n\n[coverage:report]\nexclude_lines =\n    pragma: no cover\n    raise NotImpleme"
  },
  {
    "path": "sonar-project.properties",
    "chars": 232,
    "preview": "sonar.organization=swingerman\nsonar.projectKey=swingerman_ha-dual-smart-thermostat\n\nsonar.sources=./custom_components/du"
  },
  {
    "path": "specs/001-develop-config-and/FEATURE_TESTING_PLAN.md",
    "chars": 20776,
    "preview": "# Feature Testing Plan: TDD Approach for Config & Options Flows\n\n## Executive Summary\n\n**Problem**: Features have strict"
  },
  {
    "path": "specs/001-develop-config-and/FEATURE_TESTING_PLAN_EXPANDED.md",
    "chars": 16939,
    "preview": "# Feature Testing Plan: EXPANDED with E2E Tests\n\n## Executive Summary\n\n**Problem**: Features have strict ordering depend"
  },
  {
    "path": "specs/001-develop-config-and/FLOW_SEPARATION_ANALYSIS.md",
    "chars": 9100,
    "preview": "# Flow Separation Analysis: Config vs Reconfigure vs Options\n\nBased on [Home Assistant Documentation](https://developers"
  },
  {
    "path": "specs/001-develop-config-and/GITHUB_ISSUES_UPDATE_PLAN.md",
    "chars": 7192,
    "preview": "# GitHub Issues Update Plan\n\n**Date**: 2025-01-17\n**Context**: Update GitHub issues to reflect refined testing strategy "
  },
  {
    "path": "specs/001-develop-config-and/HOUSEKEEPING.md",
    "chars": 4236,
    "preview": "# Housekeeping Instructions for All Tasks\n\nThis document explains how to mark tasks as complete in the specification fil"
  },
  {
    "path": "specs/001-develop-config-and/OPTIONS_FLOW_BUG_FIX.md",
    "chars": 6163,
    "preview": "# Options Flow Bug Fix: Feature Settings Not Persisting\n\n## UPDATE: Second Bug Found and Fixed\n\nAfter the initial fix, m"
  },
  {
    "path": "specs/001-develop-config-and/RECONFIGURE_FLOW_MIGRATION.md",
    "chars": 28946,
    "preview": "# Migration Plan: Config/Reconfigure/Options Flow Architecture\n\n**Created**: 2025-10-21\n**Status**: Planning\n**Related S"
  },
  {
    "path": "specs/001-develop-config-and/REORG.md",
    "chars": 1216,
    "preview": "# Test Reorganization Plan\n\nPurpose: reorganize existing tests into a clearer folder structure without changing behavior"
  },
  {
    "path": "specs/001-develop-config-and/UPDATED_TASKS_STRATEGY.md",
    "chars": 6063,
    "preview": "# Updated Task Strategy: Minimal E2E, Comprehensive Python Unit Tests\n\n**Date**: 2025-01-17\n**Context**: After implement"
  },
  {
    "path": "specs/001-develop-config-and/contracts/step-handlers.md",
    "chars": 3353,
    "preview": "# Contracts: Step Handlers\n\nThis document lists the expected contracts for config/options step handlers used by the inte"
  },
  {
    "path": "specs/001-develop-config-and/data-model.md",
    "chars": 19279,
    "preview": "# Data Model: Config & Options Flow (dual_smart_thermostat)\n\n## Purpose\n\nThis document defines the canonical data struct"
  },
  {
    "path": "specs/001-develop-config-and/github-issues-update.md",
    "chars": 7759,
    "preview": "# GitHub Issues Update - Acceptance Criteria (2025-01-06)\n\nThis document contains the updated acceptance criteria for Gi"
  },
  {
    "path": "specs/001-develop-config-and/github-sync-status.md",
    "chars": 4077,
    "preview": "# GitHub Issues Sync Status (2025-01-06)\n\n## Summary\n✅ All GitHub issues are now synced with tasks.md\n\n## Issues Status\n"
  },
  {
    "path": "specs/001-develop-config-and/plan.md",
    "chars": 33596,
    "preview": "# Implementation Plan: [FEATURE]\n\n\n**Branch**: `[###-feature-name]` | **Date**: [DATE] | **Spec**: [link]\n**Input**: Fea"
  },
  {
    "path": "specs/001-develop-config-and/quickstart.md",
    "chars": 14543,
    "preview": "# Quickstart: Implementing Config & Options Flow (iteration per system type)\n\n## Getting Started\n\n1. Checkout the featur"
  },
  {
    "path": "specs/001-develop-config-and/research.md",
    "chars": 1650,
    "preview": "# Research: Implementing Config & Options Flow (dual_smart_thermostat)\n\n## Purpose\nCapture context, prior work, and risk"
  },
  {
    "path": "specs/001-develop-config-and/schema-consolidation-proposal.md",
    "chars": 2774,
    "preview": "# Schema Consolidation Proposal\n\nStatus: Draft\n\nSummary\n-------\nThis document evaluates options to consolidate duplicate"
  },
  {
    "path": "specs/001-develop-config-and/spec.md",
    "chars": 11820,
    "preview": "# Feature Specification: Develop config and options flow for dual_smart_thermostat\n\n**Feature Branch**: `001-develop-con"
  },
  {
    "path": "specs/001-develop-config-and/tasks.md",
    "chars": 37817,
    "preview": "# Tasks for Feature: Develop Config & Options Flows (Phase 1 authoritative)\n\nThis `tasks.md` is generated from `specs/00"
  },
  {
    "path": "specs/001-develop-config-and/test-preservation.md",
    "chars": 1608,
    "preview": "# Test Preservation Guide\n\nPurpose\n-------\nEnsure currently passing unit tests remain passing while implementing the fea"
  },
  {
    "path": "specs/002-separate-tolerances/checklists/requirements.md",
    "chars": 3282,
    "preview": "# Specification Quality Checklist: Separate Temperature Tolerances\n\n**Purpose**: Validate specification completeness and"
  },
  {
    "path": "specs/002-separate-tolerances/contracts/tolerance_selection_api.md",
    "chars": 17142,
    "preview": "# API Contract: Tolerance Selection Interface\n\n**Version**: 1.0.0\n**Date**: 2025-10-29\n**Component**: `EnvironmentManage"
  },
  {
    "path": "specs/002-separate-tolerances/data-model.md",
    "chars": 16501,
    "preview": "# Data Model: Separate Temperature Tolerances\n\n**Date**: 2025-10-29\n**Branch**: `002-separate-tolerances`\n**Purpose**: D"
  },
  {
    "path": "specs/002-separate-tolerances/plan.md",
    "chars": 19715,
    "preview": "# Implementation Plan: Separate Temperature Tolerances for Heating and Cooling Modes\n\n**Branch**: `002-separate-toleranc"
  },
  {
    "path": "specs/002-separate-tolerances/quickstart.md",
    "chars": 13979,
    "preview": "# Developer Quickstart: Separate Temperature Tolerances\n\n**Feature**: Separate Temperature Tolerances for Heating and Co"
  },
  {
    "path": "specs/002-separate-tolerances/research.md",
    "chars": 14784,
    "preview": "# Research: Separate Temperature Tolerances\n\n**Date**: 2025-10-29\n**Branch**: `002-separate-tolerances`\n**Purpose**: Res"
  },
  {
    "path": "specs/002-separate-tolerances/spec.md",
    "chars": 29402,
    "preview": "# Feature Specification: Separate Temperature Tolerances for Heating and Cooling Modes\n\n**Feature Branch**: `002-separat"
  },
  {
    "path": "specs/002-separate-tolerances/tasks.md",
    "chars": 19680,
    "preview": "# Tasks: Separate Temperature Tolerances for Heating and Cooling Modes\n\n**Input**: Design documents from `/specs/002-sep"
  },
  {
    "path": "specs/003-separate-tolerances/BEHAVIOR_DIAGRAM.md",
    "chars": 9887,
    "preview": "# Tolerance Behavior Diagrams\n\nVisual representation of how temperature tolerances work in current vs proposed implement"
  },
  {
    "path": "specs/003-separate-tolerances/IMPLEMENTATION_COMPLETE.md",
    "chars": 9782,
    "preview": "# Issue #407: Separate Tolerances - IMPLEMENTATION COMPLETE ✅\n\n**Status**: Fully Implemented and Tested\n**Branch**: 002-"
  },
  {
    "path": "specs/003-separate-tolerances/README.md",
    "chars": 3873,
    "preview": "# Separate Tolerances Feature - Completed Implementation\n\n**Feature**: Mode-specific temperature tolerances for dual-mod"
  },
  {
    "path": "specs/004-template-based-presets/IMPLEMENTATION_PROGRESS.md",
    "chars": 11567,
    "preview": "# Implementation Progress: Template-Based Preset Temperatures\n\n**Feature Branch**: `004-template-based-presets`\n**Last U"
  },
  {
    "path": "specs/004-template-based-presets/IMPLEMENTATION_STATUS.md",
    "chars": 12195,
    "preview": "# Template-Based Presets Implementation Status\n\n**Last Updated**: 2025-12-01\n**Overall Progress**: 56/112 tasks (50.0%) "
  },
  {
    "path": "specs/004-template-based-presets/PHASE10_COMPLETE.md",
    "chars": 18788,
    "preview": "# Phase 10 Complete: Documentation\n\n**Date**: 2025-12-03\n**Status**: ✅ 5/5 tasks (100%)\n**Progress**: 75/112 tasks (67%)"
  },
  {
    "path": "specs/004-template-based-presets/PHASE4_COMPLETE.md",
    "chars": 11027,
    "preview": "# Phase 4 Complete: Simple Template with Entity Reference\n\n**Date**: 2025-12-01\n**Status**: ✅ User Story 2 (P2) COMPLETE"
  },
  {
    "path": "specs/004-template-based-presets/PHASE5_COMPLETE.md",
    "chars": 12211,
    "preview": "# Phase 5 Complete: Complex Conditional Templates\n\n**Date**: 2025-12-01\n**Status**: ✅ User Story 3 (P3) COMPLETE\n**Progr"
  },
  {
    "path": "specs/004-template-based-presets/PHASE6_COMPLETE.md",
    "chars": 9382,
    "preview": "# Phase 6 Complete: Range Mode Template Support\n\n**Date**: 2025-12-01\n**Status**: ✅ User Story 4 (P3) COMPLETE\n**Progres"
  },
  {
    "path": "specs/004-template-based-presets/PHASE7_COMPLETE.md",
    "chars": 13129,
    "preview": "# Phase 7 Complete: Config Flow Integration with Template Validation\n\n**Date**: 2025-12-01\n**Status**: ✅ User Story 5 (P"
  },
  {
    "path": "specs/004-template-based-presets/PHASE9_COMPLETE.md",
    "chars": 8039,
    "preview": "# Phase 9 Mostly Complete: Integration Testing\n\n**Date**: 2025-12-01\n**Status**: ✅ 4/8 tasks (50%)\n**Progress**: 70/112 "
  }
]

// ... and 120 more files (download for full content)

About this extraction

This page contains the full source code of the swingerman/ha-dual-smart-thermostat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 320 files (3.8 MB), approximately 1.0M tokens, and a symbol index with 1960 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!