0022 — Property tickers
- Status: Active
- Date: 2026-05-09
- Deciders: Fede + Jose
Context
PropFlow displays work orders across many surfaces (tenant SMS, vendor Telegram, operator dashboard, log lines, Pipeline Lab output). The PMS's numeric identifier alone (604) doesn't tell the reader which property the work order belongs to. When an operator looked at the cleanup tool's dryRun output for one tenant and saw 50 rows of PMS-numeric ids, the property attribution was invisible.
Decision
Property.ticker — 3-4 uppercase ASCII letters per property (CAM, YALE). Generated by Claude (Haiku) with a rules-prompt that prefers identity-preserving codes over mechanical acronyms (YALE not Y2S). Globally unique across the org.
The ticker is the render-layer prefix on WorkOrder.displayId (CAM-604). See ADR-0030 for the WorkOrder identifier model and ADR-0024 for the principle that displayId is the canonical user-facing label.
Consequences
Ticker immutability. Once stamped, tickers are treated as immutable. Renaming a ticker orphans every existing WorkOrder.displayId referencing the old form (those strings are stable by design — tenants who got CAM-604 via SMS continue to see the same ID across the WO's lifetime). The rename procedure when truly necessary is documented in the backfill script header.
TOCTOU on ticker uniqueness. Both backends do read-then-write via assertTickerUnique; concurrent saves with the same proposed ticker could both pass the read-check. Property writes are admin-rare and the backfill script is documented as not-parallel-safe. A future improvement is a DDB ConditionExpression: attribute_not_exists(...) on a uniqueness GSI; today the operator-facing failure mode ("ticker XYZ already in use by property abc") is acceptable.
Cross-org ticker uniqueness. Today the uniqueness check scans all properties globally. When ADR-0019's multi-org data scoping ships, the roster query must be filtered by organizationId — otherwise two PMCs both choosing CAM for their respective Camellia properties would produce false collisions. TODO(ADR-0019) comments at both saveProperty call sites flag this for the org-scoping sprint.
LLM cost. Ticker generation is one Haiku call per new property, capped at max_tokens: 20, retried up to 3x on collision/format violation. Properties are added rarely. Cost ≈ pennies per onboarding.
Alternatives considered
Mechanical acronym function. Cheaper, but produces hostile codes for real names: Yale 25 Station → Y2S loses the recognizable identity. The whole point of the ticker is to be instantly recognizable; a function that's right 50% of the time is worse than asking the LLM.
Per-property short codes from AppFolio. AppFolio assigns a numeric property ID. Using AF45-604 instead of CAM-604 would ride on existing IDs, but it's both ugly and PMS-specific. Tickers are PMS-agnostic and brand-aligned.