251 files

This commit is contained in:
Home Assistant Version Control
2026-10-16 12:38:25 +00:00
parent 3acf0cdf8d
commit 64eb0c647c
251 changed files with 6380 additions and 14560 deletions
+37 -2
View File
@@ -68,8 +68,11 @@ def _compute_common(coordinator: TaskMateCoordinator) -> dict:
"""
data = coordinator.data
data_id = id(data)
# External entities TaskMate reads (a child's photo among them) are not part
# of ``data``, so their version is part of the key.
external = getattr(coordinator, "external_state_version", 0)
cached = getattr(coordinator, _COMMON_CACHE_ATTR, None)
if cached and cached.get("data_id") == data_id:
if cached and cached.get("data_id") == data_id and cached.get("external") == external:
return cached["common"]
children = data.get("children", [])
@@ -142,10 +145,15 @@ def _compute_common(coordinator: TaskMateCoordinator) -> dict:
"pool_by_child_reward": pool_by_child_reward,
"pool_total_by_reward": pool_total_by_reward,
"total_allocated_by_child": total_allocated_by_child,
# Each child's photo (from their chosen picture entity), resolved once
# per update: every call site that draws a child's face reads this.
"child_avatar_images": {c.id: coordinator.child_avatar_image(c) for c in children},
# This week's points earned vs. the most on offer, per child.
"child_week_progress": coordinator.week_progress(children, chores, all_completions),
# Sent back to redo by an inspection (#981): not "done" on the card.
"inspection_redo_ids": _inspection_redo_ids(coordinator),
}
setattr(coordinator, _COMMON_CACHE_ATTR, {"data_id": data_id, "common": common})
setattr(coordinator, _COMMON_CACHE_ATTR, {"data_id": data_id, "external": external, "common": common})
return common
@@ -193,6 +201,8 @@ def _build_children_summary(coordinator: TaskMateCoordinator, common: dict) -> l
if getattr(c, "is_guest", False)
else {}
),
# Grown-ups (#1061): cards leave these out unless show_adults is set.
**({"is_adult": True} if getattr(c, "is_adult", False) else {}),
# Chore roulette (#677): today's pick + spins left, so the card can
# show the result and disable the button once the allowance is used.
**(
@@ -217,6 +227,10 @@ def _build_children_summary(coordinator: TaskMateCoordinator, common: dict) -> l
"total_points_earned": getattr(c, "total_points_earned", 0) or 0,
"total_chores_completed": getattr(c, "total_chores_completed", 0) or 0,
"avatar": getattr(c, "avatar", "mdi:account-circle") or "mdi:account-circle",
# The child's photo, or "" — cards fall back to `avatar`.
"avatar_image": common.get("child_avatar_images", {}).get(c.id, ""),
"week_points_earned": common.get("child_week_progress", {}).get(c.id, {}).get("earned", 0),
"week_points_available": common.get("child_week_progress", {}).get(c.id, {}).get("available", 0),
"last_completion_date": getattr(c, "last_completion_date", None),
"streak_paused": getattr(c, "streak_paused", False),
"on_vacation": coordinator._is_child_on_vacation(c),
@@ -698,6 +712,9 @@ def _build_pending_reward_claims(common: dict) -> list[dict]:
"reward_id": rc.reward_id,
"child_id": rc.child_id,
"child_name": child.name,
# No avatar_image here on purpose: the cards resolve a claim's
# face from the overview sensor's children[], and a copy per
# claim pushed this sensor past the 16KB recorder limit.
"child_avatar": getattr(child, "avatar", "mdi:account-circle") or "mdi:account-circle",
"reward_name": reward.name,
"reward_icon": reward.icon or "mdi:gift",
@@ -1575,6 +1592,7 @@ class ChildPointsSensor(TaskMateBaseSensor):
"child_id": child.id,
"child_name": child.name,
"avatar": child.avatar,
"avatar_image": self.coordinator.child_avatar_image(child),
"total_points_earned": child.total_points_earned,
"total_chores_completed": child.total_chores_completed,
"current_streak": child.current_streak,
@@ -1615,6 +1633,18 @@ class ChildStatsSensor(TaskMateBaseSensor):
child = self.coordinator.get_child(self.child_id)
return child.avatar if child else "mdi:account-circle"
@property
def entity_picture(self) -> str | None:
"""The child's photo, so HA's own UI shows their face too.
HA prefers a picture over ``icon`` when both are set, so returning
None (the default) leaves the MDI avatar above in charge.
"""
child = self.coordinator.get_child(self.child_id)
if not child:
return None
return self.coordinator.child_avatar_image(child) or None
@property
def extra_state_attributes(self) -> dict:
"""Return additional attributes."""
@@ -1650,6 +1680,7 @@ class ChildStatsSensor(TaskMateBaseSensor):
"child_id": child.id,
"child_name": child.name,
"avatar": child.avatar,
"avatar_image": self.coordinator.child_avatar_image(child),
"points": child.points,
"total_points_earned": child.total_points_earned,
"total_chores_completed": child.total_chores_completed,
@@ -1869,6 +1900,10 @@ class ChildBadgesSensor(TaskMateBaseSensor):
earned.sort(key=lambda e: e.get("earned_at") or "", reverse=True)
return {
# Who the badges belong to, so the card can put a face to them.
"child_name": child.name,
"child_avatar": getattr(child, "avatar", "mdi:account-circle") or "mdi:account-circle",
"avatar_image": self.coordinator.child_avatar_image(child),
"earned": earned,
"available": available,
"total_badges": len(applicable),