Conversation
…335) Implements two features for multi-area management: 1. Per-area "Exclude from All Areas" toggle that prevents outdoor/utility areas (garages, driveways) from contributing to All Areas aggregation. 2. Floor-based aggregate sensors auto-generated from HA's floor registry, providing per-floor occupancy probability, decay, prior, and binary occupancy sensors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughAdds floor-scoped aggregation via a new FloorAreas class and floor aggregators in the coordinator; introduces an exclude-from-all-areas flag (config, UI, migration, and AreaConfig support) and extends sensors/binary sensors to support floor-level aggregates and exclude behavior. Changes
Sequence DiagramsequenceDiagram
participant Setup as Setup/Config Flow
participant Coord as AreaOccupancyCoordinator
participant FloorAgg as FloorAreas
participant Sensors as Sensors/Binary Sensors
Setup->>Coord: _build_floor_aggregators()
Coord->>Coord: discover floors from area configs
Coord->>FloorAgg: instantiate FloorAreas(floor_id, floor_name)
Coord->>Coord: store in _floor_aggregators
Setup->>Sensors: async_setup_entry()
Sensors->>Coord: get_floor_aggregators()
Coord-->>Sensors: dict[str, FloorAreas]
Sensors->>Sensors: create per-floor aggregated sensors using FloorAreas
Note over FloorAgg: Aggregates included areas for its floor
Note over Sensors: Use FloorAreas for aggregated metrics (probability, decay, priors, etc.)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
custom_components/area_occupancy/sensor.py (1)
102-165:⚠️ Potential issue | 🟡 MinorScope aggregated attributes to the aggregator’s area set.
For FloorAreas,
extra_state_attributescurrently aggregates over all coordinator areas, and for AllAreas it ignoresexclude_from_all_areas. This makes floor/all-areas attributes misleading compared to the aggregated values.🐛 Suggested fix (sensor.py)
- area_names = self._all_areas.coordinator.get_area_names() - area_attrs = {} - for area_name in area_names: - area = self._all_areas.coordinator.get_area(area_name) - if area is None: - continue - area_attrs[area_name] = { + area_attrs = {} + for area in self._all_areas.areas(): + area_attrs[area.area_name] = { "global_prior": area.prior.global_prior, "combined_prior": area.area_prior(), "time_prior": area.prior.time_prior, "day_of_week": area.prior.day_of_week, "time_slot": area.prior.time_slot, } @@ - area_names = self._all_areas.coordinator.get_area_names() all_decaying = [] - for area_name in area_names: - area = self._all_areas.coordinator.get_area(area_name) - if area is None: - continue + for area in self._all_areas.areas(): all_decaying.extend( [ { - "area": area_name, + "area": area.area_name, "id": entity.entity_id, "decay": format_percentage(entity.decay.decay_factor), "half_life": entity.decay.half_life, } for entity in area.entities.decaying_entities ] )# all_areas.py (add public accessor on aggregators) class AllAreas: def areas(self) -> list[Area]: """Return areas included in aggregation.""" return self._included_areas() class FloorAreas: def areas(self) -> list[Area]: """Return areas on this floor.""" return self._floor_areas()Also applies to: 282-352
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@custom_components/area_occupancy/sensor.py` around lines 102 - 165, extra_state_attributes is currently iterating the coordinator's full area set, causing FloorAreas and AllAreas to report misleading aggregates; add public accessors areas() on AllAreas and FloorAreas (returning the included/ floor areas and respecting exclude_from_all_areas) and change extra_state_attributes to iterate self._all_areas.areas() (and use those Area objects directly) when self._all_areas is set, building area_attrs from that list; for the non-aggregate branch keep using self._get_area() but compute combined_prior and other priors from that Area instance as before. Ensure you reference the AllAreas.areas(), FloorAreas.areas(), Sensor.extra_state_attributes, and the _all_areas usage when making these changes.
🧹 Nitpick comments (3)
custom_components/area_occupancy/binary_sensor.py (1)
110-112: Finish the new inline comments with periods.Logic is fine; just adjust the new comments to be full sentences ending with a period.
♻️ Example fix
- # Let the coordinator know our entity_id (only for per-area entities, not aggregates) + # Let the coordinator know our entity_id (only for per-area entities, not aggregates).As per coding guidelines, Comments should be full sentences ending with a period.
Also applies to: 127-133
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@custom_components/area_occupancy/binary_sensor.py` around lines 110 - 112, Update the inline comments in the binary_sensor entity registration so they are full sentences ending with periods: adjust the comment above the per-area registration referencing self._handle and _get_area (the line that assigns area.occupancy_entity_id = self.entity_id) and any similar comments in the nearby block (around the aggregate/per-area handling, e.g., the block spanning the later 127-133 region) to end with a period while leaving the logic unchanged.custom_components/area_occupancy/sensor.py (1)
73-87: Add a trailing period to the new inline comment.Logic is fine; just make the inline comment a full sentence ending with a period.
♻️ Example fix
- # Only for specific areas, not "All Areas" or floor aggregates + # Only for specific areas, not "All Areas" or floor aggregates.As per coding guidelines, Comments should be full sentences ending with a period.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@custom_components/area_occupancy/sensor.py` around lines 73 - 87, Update the inline comment inside async_added_to_hass (the comment starting with "Only for specific areas, not "All Areas" or floor aggregates") to be a complete sentence by adding a trailing period; ensure the comment that references self._area_handle and the call to self._get_area() ends with a period to follow the project's comment-style guideline.custom_components/area_occupancy/coordinator.py (1)
66-67: End the new inline comments with periods.Minor style: these new comments are fragments. Please make them full sentences ending with a period.
♻️ Example fix
- # Floor-based aggregators: dict[floor_id, FloorAreas] + # Floor-based aggregators map floor_id to FloorAreas.As per coding guidelines, Comments should be full sentences ending with a period.
Also applies to: 360-361, 460-463, 655-656
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@custom_components/area_occupancy/coordinator.py` around lines 66 - 67, The inline comment on the Floor-based aggregators declaration (self._floor_aggregators) is a fragment — update it to be a full sentence ending with a period (e.g., "Floor-based aggregators: dict[floor_id, FloorAreas]."); apply the same change to the other similar inline comments in this module so all comments are full sentences that end with periods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@custom_components/area_occupancy/const.py`:
- Line 33: Add a new migration handler for v17→v18 in migrations.py that mirrors
the v16→v17 pattern: detect old_version == 17, copy existing config/data and
options entries intact, insert the new CONF_EXCLUDE_FROM_ALL_AREAS key into both
the data and options structures with its default value, and return {"version":
CONF_VERSION}; reference CONF_VERSION and CONF_EXCLUDE_FROM_ALL_AREAS and reuse
the same helper logic used by the v16_to_v17 migration to ensure no other keys
are changed.
In `@tests/test_area_all_areas.py`:
- Around line 17-23: The local helper function _mock_area should be replaced
with the shared test helper create_test_area to keep test setup consistent;
update tests that call _mock_area(**overrides) to call create_test_area(...) and
map the same override keys into create_test_area's parameters or its
post-creation mutation API, remove the _mock_area function, and ensure the
created area's config.exclude_from_all_areas is set to False via
create_test_area arguments or by setting area.config.exclude_from_all_areas
immediately after creation so behavior remains identical to the original helper.
---
Outside diff comments:
In `@custom_components/area_occupancy/sensor.py`:
- Around line 102-165: extra_state_attributes is currently iterating the
coordinator's full area set, causing FloorAreas and AllAreas to report
misleading aggregates; add public accessors areas() on AllAreas and FloorAreas
(returning the included/ floor areas and respecting exclude_from_all_areas) and
change extra_state_attributes to iterate self._all_areas.areas() (and use those
Area objects directly) when self._all_areas is set, building area_attrs from
that list; for the non-aggregate branch keep using self._get_area() but compute
combined_prior and other priors from that Area instance as before. Ensure you
reference the AllAreas.areas(), FloorAreas.areas(),
Sensor.extra_state_attributes, and the _all_areas usage when making these
changes.
---
Duplicate comments:
In `@custom_components/area_occupancy/translations/en.json`:
- Around line 727-739: Remove the stray duplicate marker/comment and ensure
there are no duplicated keys in the "data_description" block; specifically
verify the entries "threshold", "decay_enabled", "decay_half_life",
"min_prior_override", and "exclude_from_all_areas" are present exactly once,
remove the duplicate_comment artifact, and commit the cleaned en.json
translation block.
---
Nitpick comments:
In `@custom_components/area_occupancy/binary_sensor.py`:
- Around line 110-112: Update the inline comments in the binary_sensor entity
registration so they are full sentences ending with periods: adjust the comment
above the per-area registration referencing self._handle and _get_area (the line
that assigns area.occupancy_entity_id = self.entity_id) and any similar comments
in the nearby block (around the aggregate/per-area handling, e.g., the block
spanning the later 127-133 region) to end with a period while leaving the logic
unchanged.
In `@custom_components/area_occupancy/coordinator.py`:
- Around line 66-67: The inline comment on the Floor-based aggregators
declaration (self._floor_aggregators) is a fragment — update it to be a full
sentence ending with a period (e.g., "Floor-based aggregators: dict[floor_id,
FloorAreas]."); apply the same change to the other similar inline comments in
this module so all comments are full sentences that end with periods.
In `@custom_components/area_occupancy/sensor.py`:
- Around line 73-87: Update the inline comment inside async_added_to_hass (the
comment starting with "Only for specific areas, not "All Areas" or floor
aggregates") to be a complete sentence by adding a trailing period; ensure the
comment that references self._area_handle and the call to self._get_area() ends
with a period to follow the project's comment-style guideline.
…omment style - Add v17→v18 migration handler to bump entry version for exclude_from_all_areas support - Add areas() accessor to AllAreas and FloorAreas so extra_state_attributes iterates only relevant areas instead of all coordinator areas - Fix comment style to use full sentences ending with periods Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
custom_components/area_occupancy/area/all_areas.py (1)
177-188: Consider caching_floor_areas()results for performance.Each aggregation method (
probability(),occupied(),decay(), etc.) calls_floor_areas(), which performs registry lookups on every invocation. If floor aggregators are queried frequently during coordinator updates, this could become a hot path.Since floor assignments only change on integration reload (as documented in the docstring), consider caching the area list after the first call, or computing it once in
__init__/ a dedicated setup method.♻️ Example caching approach
class FloorAreas: def __init__( self, coordinator: AreaOccupancyCoordinator, floor_id: str, floor_name: str, ) -> None: self.coordinator = coordinator self.floor_id = floor_id self.floor_name = floor_name + self._cached_areas: list[Area] | None = None def _floor_areas(self) -> list[Area]: """Return areas that belong to this floor.""" + if self._cached_areas is not None: + return self._cached_areas from homeassistant.helpers import area_registry as ar # noqa: PLC0415 area_reg = ar.async_get(self.coordinator.hass) result: list[Area] = [] for area in self.coordinator.areas.values(): if area.config.area_id: area_entry = area_reg.async_get_area(area.config.area_id) if area_entry and area_entry.floor_id == self.floor_id: result.append(area) + self._cached_areas = result return result🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@custom_components/area_occupancy/area/all_areas.py` around lines 177 - 188, The _floor_areas() method currently performs registry lookups on every call (used by probability(), occupied(), decay(), etc.); cache its result to avoid repeated lookups by computing the list once (e.g., in __init__ or on first call) and store it on the instance (use a private attribute like _cached_floor_areas or initialize it in __init__ using self.coordinator and self.floor_id), then have _floor_areas() return the cached list; because floor assignments only change on integration reload no invalidation is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@custom_components/area_occupancy/area/all_areas.py`:
- Around line 177-188: The _floor_areas() method currently performs registry
lookups on every call (used by probability(), occupied(), decay(), etc.); cache
its result to avoid repeated lookups by computing the list once (e.g., in
__init__ or on first call) and store it on the instance (use a private attribute
like _cached_floor_areas or initialize it in __init__ using self.coordinator and
self.floor_id), then have _floor_areas() return the cached list; because floor
assignments only change on integration reload no invalidation is required.
Summary
Implements #335 — two features for multi-area management:
Changes
const.pyCONF_EXCLUDE_FROM_ALL_AREAS,DEFAULT_EXCLUDE_FROM_ALL_AREAS, bumpedCONF_VERSIONto 18data/config.pyexclude_from_all_areasinAreaConfig._load_config()area/all_areas.py_included_areas()filter toAllAreas, newFloorAreasclass, shared_avg()helperarea/__init__.pyFloorAreascoordinator.py_build_floor_aggregators), lifecycle integrationsensor.pyFloorAreas, floor entity creation, updated routing logicbinary_sensor.pyconfig_flow.pystrings.json/translations/en.jsontests/test_area_all_areas.pyexclude_from_all_areasattributeNotes
exclude_from_all_areaskey defaults toFalseTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores
Documentation