Address Format Canonicalisation
Consistent LOT-prefixed address format with parenthesised street number across dossier-api, autocomplete, and the Project entity
Overview
A long-standing Trello request (zH3TlNMp, first raised June 2024, re-raised December 2025) asked for addresses across the platform to use one canonical format:
LOT [lot_number] ([street_number]) [street_name] [suburb] [postcode]Three example transforms from the card:
| Current | Canonical |
|---|---|
LOT 119, 14 GLENLOCH LOOP GISBORNE 3437 | LOT 119 (14) GLENLOCH LOOP GISBORNE 3437 |
65 MULGA STREET CARDROSS 3496 | LOT 562~B (65) MULGA STREET CARDROSS 3496 |
LOT 555 5 KIELDER CRESCENT CLYDE 3978 | LOT 555 (5) KIELDER CRESCENT CLYDE 3978 |
This release applies the canonical format at every layer: dossier-api responses, the address autocomplete's input field on selection, the Project entity setter/getter, and the enrichment service. Idempotent - already-canonical values are no-ops.
What Changed
dossier-api Canonicaliser
Added canonicaliseLotAddress() helper in TypeScript and applied at the paths where raw input was being echoed:
api.ts- at thelookup_pendingfallback (unresolved SPI) wherelot_address: addressused to echo the raw string.parcel-summary.ts- at the return value wherefirstString(aggregate.lot_address, selectedProperty.lot_address, selectedProperty.street_address)could fall through to a rawstreet_address.
The two files duplicate the helper because api.ts already imports from parcel-summary.ts, so a shared import would create a circular dependency. Small helper, fine to duplicate.
Address Autocomplete Selection
Added buildCanonicalLotAddress(address, lotNumber) method to both:
app/assets/controllers/address_autocomplete_controller.jsordering/assets/controllers/address_autocomplete_controller.js
Called on selectAddress() when setting this.input.value. Handles three cases:
- Already canonical (
LOT 119 (14) GLENLOCH LOOP GISBORNE 3437) - returned unchanged. - LOT prefix, no parens (
LOT 555 5 KIELDER CRESCENT CLYDE 3978) - wraps the street number in parens. - No LOT prefix, but
lot_numberseparately resolved (5 KIELDER CRESCENT CLYDE 3978+lot_number=555) - prependsLOT 555and wraps the street number.
The third case matters because dossier-api sometimes resolves lot_number separately from lot_address: when the parcel lookup hits the street_address fallback it can lose the LOT prefix even though the lot_number field is populated.
Project Entity
Added Project::canonicaliseLotAddress() static helper in both app/src/Entity/Project.php and ordering/src/Entity/Project.php. Applied in:
setKnownAddress()- new writes persist canonically.getKnownAddress()- existing legacy DB rows render canonically without a migration.
Because Twig's project.knownAddress calls the getter, every template - dashboard grids, admin listings, email notifications, invoices - displays the canonical format automatically.
Enrichment Service
ProjectInsightEnrichmentService::hydrateFromDossierInsights() now prefers ps['lot_address'] over raw ezi_address when populating knownAddress:
$addressForKnown = $this->isMeaningful($lotAddress) ? $lotAddress : $eziAddress;
if ($this->isMeaningful($addressForKnown) && !$this->looksLikeLotLabel($project->getKnownAddress())) {
$project->setKnownAddress($addressForKnown);
}This works symmetrically in app/ and ordering/ services.
Dead Code Removed
The private Project::formatLotAddress() helper (both entities) - had a single commented-out caller and conflated the "add LOT prefix" and "wrap street number" concerns. Removed in favour of the new static canonicaliser.
Regex
The canonicalisation pattern (same logic in PHP, TypeScript, and JavaScript):
^(LOT\s+[A-Z0-9~\-\/]+)[\s,]+(\d+[A-Z0-9\-\/]*)\s+(.+)$Capture groups:
LOT+ lot identifier (allows alphanumerics,~,-,/for lots like562~BorT835).- Street number (digits, optionally with letter / hyphen / slash suffix).
- Everything after (street + road type + suburb + postcode).
Rewrite: {1} ({2}) {3}.
The separator between groups 1 and 2 is [\s,]+ so the comma variant (LOT 119, 14) also normalises.
Verification
After deploy:
$ curl -s 'https://dossier-api.ftsonline.com.au/api/insights?address=LOT%20555%205%20KIELDER%20CRESCENT%20CLYDE%203978' \
| jq .parcelSummary.lot_address
"LOT 555 (5) KIELDER CRESCENT CLYDE 3978"Files Touched
| File | Change |
|---|---|
dossier-api/src/server/api.ts | canonicaliseLotAddress() + apply at pending fallback |
dossier-api/src/server/services/parcel-summary.ts | Local helper + apply at return |
app/assets/controllers/address_autocomplete_controller.js | buildCanonicalLotAddress() method |
ordering/assets/controllers/address_autocomplete_controller.js | Same |
app/src/Entity/Project.php | Project::canonicaliseLotAddress() + setter/getter |
ordering/src/Entity/Project.php | Same |
app/src/Service/ProjectInsightEnrichmentService.php | Prefer lot_address over ezi_address |
ordering/src/Service/ProjectInsightEnrichmentService.php | Same |
Follow-up (Not Urgent)
- One-off migration of existing
knownAddressrows viaFixProjectLotAddressCommandto backfill the canonical format on historical records. Not blocking because the getter handles it on read; useful for keeping the DB clean.
Changelog Reference
- fix(address): use canonical lot_address format when enriching project (64d9733e)
- fix(address): canonicalise LOT address format on Project entity (0b780dc1)
- fix(address): canonicalise LOT address format across dossier-api + autocomplete (27e2e46a)
Trello: zH3TlNMp - Address Format Update