- Trustworthy analysis - users should be able to rely on EntityMap's dependency and impact reports
- Minimal footprint - avoid entity bloat, excessive logging, or recorder churn
- Maintainability - clean module boundaries, typed models, testable adapters
- Extensibility - adding new sources or finding types should be straightforward
- Native feel - integrate naturally with Home Assistant's UI patterns
EntityMap does not use DataUpdateCoordinator. Here's why:
| Approach | Pros | Cons | Verdict |
|---|---|---|---|
| DataUpdateCoordinator | Familiar HA pattern, automatic retry | Designed for polling external APIs; unnecessary overhead for local computation | ❌ Not appropriate |
| Pure event-driven | Responsive, no wasted computation | May miss changes not covered by events; complex to ensure completeness | |
| Pure on-demand | User controls when scans run | Graph can be stale; poor UX for dashboard | |
| Hybrid | Responsive + reliable + user-controllable | Slightly more complex setup | ✅ Chosen |
- Startup scan: After
EVENT_HOMEASSISTANT_STARTED, a full graph build runs (if enabled) - Event-driven refresh: Listens to
entity_registry_updatedanddevice_registry_updatedevents; triggers rebuild - Periodic reconciliation: Timer-based full rescan at configurable intervals (default: 6 hours)
- Manual rescan: Button entity or
entitymap.scanservice
DependencyGraph
├── nodes: dict[str, GraphNode] # Keyed by node_id
├── edges: list[GraphEdge] # All directed edges
├── _inbound: dict[str, list[Edge]] # Index: target → edges
└── _outbound: dict[str, list[Edge]]# Index: source → edges
- Devices:
device.{device_id} - Entities:
{domain}.{object_id}(the entity_id) - Areas:
area.{area_id}
Edges point from dependent → dependency:
automation.motion_light → light.living_roommeans the automation depends on the lightlight.living_room → device.abc123means the entity belongs to the device
Each adapter implements SourceAdapter.async_populate(graph):
RegistryAdapter - devices, entities, areas (always runs first)
AutomationAdapter - automation triggers, conditions, actions
ScriptAdapter - script sequences and service calls
SceneAdapter - scene entity membership
GroupAdapter - group member entities
TemplateAdapter - template entity Jinja2 references
Adapters run sequentially. RegistryAdapter must run first to establish base nodes; others add edges and placeholder nodes for missing references.
The fragility engine runs synchronously on the in-memory graph. It detects patterns by iterating edges and comparing against node metadata:
- Missing references: Edge target doesn't exist in nodes
- Device ID references: Edge kind is
DEVICE_TRIGGER/CONDITION/ACTION - Disabled/unavailable: Target node is disabled or unavailable
- Tight coupling: Same automation has 3+ device_id refs to one device
- Hidden dependencies: Automation calls a script with 3+ sub-dependencies
Each finding gets a deterministic ID (MD5 hash of key fields) for stable tracking.
Impact analysis combines:
- Direct dependents - one-hop inbound edges
- Transitive dependents - full BFS traversal of inbound graph
- Risk scoring - weighted formula based on dependent count, type, and edge fragility
- Migration suggestions - pattern-matched recommendations based on edge types
The panel is a vanilla Web Component (HTMLElement with Shadow DOM):
- D3.js force simulation for graph layout
- SVG rendering for crisp scaling
- Node shapes/colors distinguish types
- Click → neighborhood highlight + detail panel
- Search filters nodes and their neighbors
- Findings view shows a card grid sorted by severity
D3.js is loaded from CDN on first panel load to keep the integration package small.
- Memory: Graph is stored in-memory; typical HA installation uses <10MB
- CPU: Full scan is async and completes in <2s for most installations
- Storage: No persistent storage beyond the config entry
- Recorder: Summary sensors update only on graph changes (not continuously)
- Network: Zero external network calls