-
Notifications
You must be signed in to change notification settings - Fork 3
ADR 028 Areas Trades Structured Dimensions
Accepted
The existing tagging system provides a generic, flat organizational structure for work items and household items. While flexible, tags lack the semantic structure needed for meaningful cost analysis and project navigation:
- No spatial hierarchy: Homeowners need to view and filter items by location in the house (e.g., "Kitchen > Island", "2nd Floor > Master Bedroom"). Tags cannot represent parent-child relationships.
-
No craft classification: Vendors have a free-text
specialtyfield, and there is no structured way to group work items by trade (e.g., "all plumbing work across the project"). - No cost breakdown by dimension: The budget overview and breakdown endpoints aggregate by budget category, but cannot answer questions like "how much did we spend on the kitchen?" or "what is the total plumbing cost?"
-
Vendor assignment gap: Work items track
assigned_user_idfor user assignment but have no direct vendor assignment. The vendor relationship only exists through budget lines, which is indirect and insufficient for scheduling and responsibility tracking. -
Room field redundancy: Household items have a free-text
roomfield that overlaps with what a structured area hierarchy would provide, but without consistency or cross-referencing with work items.
Replace the generic tagging system with two purpose-built dimensions: Areas (hierarchical spatial organization) and Trades (flat craft/skill classification).
D1: Area hierarchy — arbitrary depth via self-referencing parent_id
Areas use a self-referencing parent_id foreign key to support arbitrary nesting depth. No fixed levels (floor/room/zone) are enforced. Examples: "Kitchen", "2nd Floor > Master Bedroom", "Garage > Workshop > Bench Area". This gives homeowners maximum flexibility without imposing a structural opinion.
D2: Area cardinality — M:1 (one area per item, nullable)
Each work item or household item belongs to at most one area. This simplifies queries (no junction table needed) and matches the real-world model: a construction task happens in one place. Items that span multiple areas (e.g., "whole-house wiring") can be assigned to a parent area or left unassigned.
D3: Trade transitivity — computed via JOINs, not denormalized
A work item's trade is derived transitively: work_item -> assigned_vendor -> trade. There is no trade_id column on work_items. This avoids denormalization and data inconsistency. When querying "all work items for trade X", the application JOINs work_items.assigned_vendor_id -> vendors.trade_id. At the expected scale (<200 work items, <50 vendors), this JOIN is negligible.
D4: Vendor assignment — mutually exclusive with user assignment
Work items gain an assigned_vendor_id column alongside the existing assigned_user_id. A CHECK constraint enforces mutual exclusivity: a work item can be assigned to a user OR a vendor, but not both. This models the real-world distinction: either you do the work yourself, or a contractor does it.
D5: Room field — dropped without data migration
The room free-text field on household_items is dropped. Its functionality is superseded by the structured area_id foreign key. No data migration is performed for existing room values — users re-assign items to areas after the migration. This is acceptable for the <5 user scale.
D6: Vendor specialty to trade migration
The specialty free-text field on vendors is replaced by a trade_id foreign key to the new trades table. Existing specialty values are not automatically migrated to trade entities (the values are too inconsistent for reliable automated migration). Instead, 15 default trades are seeded, and users update vendor trade assignments manually.
D7: Budget category default changes are conditional
New default budget categories and household item categories are added. Existing defaults that no longer fit the project's direction are conditionally deleted — only if they have zero references from any budget lines. This prevents data loss while cleaning up unused defaults.
-
New tables:
areas(with self-referencingparent_id),trades -
Modified tables:
vendors(dropspecialty, addtrade_id),work_items(addarea_id,assigned_vendor_id, CHECK constraint),household_items(droproom, addarea_id) -
Dropped tables:
tags,work_item_tags,household_item_tags
-
New endpoints:
/api/areas(CRUD),/api/trades(CRUD) -
Modified endpoints: Work item, household item, and vendor CRUD endpoints updated to use
areaId/tradeIdinstead oftagIds/room/specialty -
Removed endpoints: All
/api/tags/*endpoints -
Timeline: Work item entries include
areaobject instead oftagsarray - CalDAV feeds: Vendor contacts use trade name instead of specialty for the TITLE vCard property
- Cost analysis by location: Budget breakdowns can aggregate by area, answering "how much did the kitchen cost?"
- Cost analysis by trade: Budget breakdowns can aggregate by vendor trade, answering "how much did plumbing cost?"
- Spatial navigation: Users can browse and filter items by hierarchical areas
- Vendor-work item relationship: Direct vendor assignment on work items enables clearer responsibility tracking and scheduling
- Data consistency: Structured dimensions with foreign keys replace free-text fields that were prone to inconsistency
-
Breaking API change: All clients must update to use
areaId/assignedVendorIdinstead oftagIds. Thetagsarray in response shapes is replaced with anareaobject. This is a major version bump. - Loss of generic tagging flexibility: Users can no longer create arbitrary organizational labels. The two dimensions cover the most important use cases but remove the "anything goes" nature of tags.
- Area tree management: Users must manage a hierarchical structure instead of a flat list. The UI must support tree creation, reordering, and reparenting.
- Trade assignment is indirect for work items: Work item trade is derived from the assigned vendor's trade. Work items without a vendor have no trade. This is by design (trade is a property of the craftsperson, not the task) but may be unintuitive.