ADR-0021: Captured-location data for future place resolution
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
transactionsandunmatched(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.onNotificationgains alocation: CapturedLocation? = nullparameter that maps the fix onto whatever row results (transaction or unmatched). The listener still passesnulltoday.
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.mdanddocs/PLAY_POLICY.mdremain 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
locationstable 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.