First to Site
Release 3.7

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:

CurrentCanonical
LOT 119, 14 GLENLOCH LOOP GISBORNE 3437LOT 119 (14) GLENLOCH LOOP GISBORNE 3437
65 MULGA STREET CARDROSS 3496LOT 562~B (65) MULGA STREET CARDROSS 3496
LOT 555 5 KIELDER CRESCENT CLYDE 3978LOT 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 the lookup_pending fallback (unresolved SPI) where lot_address: address used to echo the raw string.
  • parcel-summary.ts - at the return value where firstString(aggregate.lot_address, selectedProperty.lot_address, selectedProperty.street_address) could fall through to a raw street_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.js
  • ordering/assets/controllers/address_autocomplete_controller.js

Called on selectAddress() when setting this.input.value. Handles three cases:

  1. Already canonical (LOT 119 (14) GLENLOCH LOOP GISBORNE 3437) - returned unchanged.
  2. LOT prefix, no parens (LOT 555 5 KIELDER CRESCENT CLYDE 3978) - wraps the street number in parens.
  3. No LOT prefix, but lot_number separately resolved (5 KIELDER CRESCENT CLYDE 3978 + lot_number=555) - prepends LOT 555 and 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:

  1. LOT + lot identifier (allows alphanumerics, ~, -, / for lots like 562~B or T835).
  2. Street number (digits, optionally with letter / hyphen / slash suffix).
  3. 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

FileChange
dossier-api/src/server/api.tscanonicaliseLotAddress() + apply at pending fallback
dossier-api/src/server/services/parcel-summary.tsLocal helper + apply at return
app/assets/controllers/address_autocomplete_controller.jsbuildCanonicalLotAddress() method
ordering/assets/controllers/address_autocomplete_controller.jsSame
app/src/Entity/Project.phpProject::canonicaliseLotAddress() + setter/getter
ordering/src/Entity/Project.phpSame
app/src/Service/ProjectInsightEnrichmentService.phpPrefer lot_address over ezi_address
ordering/src/Service/ProjectInsightEnrichmentService.phpSame

Follow-up (Not Urgent)

  • One-off migration of existing knownAddress rows via FixProjectLotAddressCommand to 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