"""Domain entity: TipDismissal. A workspace-scoped record that a specific tip (`Tip.id`, see `app.services.tips_service`) has been acknowledged/dismissed by the user. Keyed by (workspace_id, tip_id) — see the UNIQUE constraint on the `tip_dismissal` table — so re-dismissing the same tip refreshes `data_hash` and `dismissed_at` instead of creating a duplicate row. `data_hash` snapshots `app.utils.tip_predicates.hash_tip_data(tip.data)` at dismiss time. `TipsService` only treats a dismissal as active while the rule's *current* output hashes to the same value — once the underlying numbers move (more days now projected to expire at age 4, a different `fourth_birthday`, ...), the dismissal is stale and the tip resurfaces. See `TipsService._filter_dismissed` for why: permanently silencing a warning like `preserve_for_age_4` because its *old* figures were once dismissed would be dangerous. """ from dataclasses import dataclass from datetime import datetime from uuid import UUID @dataclass class TipDismissal: id: UUID workspace_id: UUID tip_id: str data_hash: str dismissed_at: datetime | None = None