Skip to content

ADR-0021: Captured-location data for future place resolution

  • Status: Accepted
  • Date: 2026-08-14
  • Deciders: maintainers
  • Related: ADR-0010, ADR-0015

Context

Some transactions (e.g. card purchases tracked only by Google Wallet) carry no merchant name the parser can use. A device-location fix captured at the moment the notification arrives is a useful future signal for resolving where a purchase happened (reverse geocoding during parse). Capturing and storing location is also privacy-sensitive: the app currently ships with no location permissions and documents that it "never reads location", and it is offline-first with no network SDKs (ADR-0015).

Decision

Store the data structure for captured location now; do not capture, resolve, or request permissions yet.

  • New schema v6 adds seven nullable columns to both transactions and unmatched (see data-layer.md):
  • raw fix: latitude, longitude (REAL), accuracyMeters (REAL), locationProvider (TEXT), locationCapturedAt (INTEGER);
  • resolved-place placeholders: placeName (TEXT), placeId (TEXT) — filled by the future reverse-geocode step, so no later migration is needed.
  • A pure Kotlin CapturedLocation(latitude, longitude, accuracyMeters, provider, capturedAt) value type is the pipeline seam.
  • CapturePipeline.onNotification gains a location: CapturedLocation? = null parameter that maps the fix onto whatever row results (transaction or unmatched). The listener still passes null today.

The raw fix is stored on-device in the SQLCipher-encrypted DB (ADR-0010); it is only "potential" location until resolved.

Consequences

  • Schema and plumbing are ready for a future capture + resolution feature without a second migration for the resolved fields.
  • No permission is requested and no data is read today — the privacy claims in docs/user-guide/security.md and docs/PLAY_POLICY.md remain true until capture ships.
  • When capture does ship, it must: request ACCESS_COARSE/FINE_LOCATION, update the Play Data Safety form and the privacy docs, and decide a retention policy (strip the fix after resolution?).

Alternatives considered

  • A separate locations table keyed by FK — rejected: the payload is a 1:1 optional attach and inline nullable columns match the existing entity style.
  • Store only on transactions — rejected: unmatched notifications (parse failed) would lose the fix that manual re-parse could later use.
  • Ship capture now — rejected: needs permissions + Play policy updates, out of scope; this ADR only readies the data structure.