feat(environment): add home effective-from and reserve a DEVICE location source

Add `User.homeSince` (nullable effective-from instant) and a `DEVICE` value to
the location-source enum (migration 0219). `homeSince` records when the home
location became effective so day resolution can attribute a past day to the
home only on/after that instant, rather than to the current home for every past
day. Existing accounts with a home get `homeSince` stamped to the migration
instant; their deep past is filled by explicit location periods instead.

`DEVICE` is reserved for a future client-supplied coarse per-day location: the
enum value and the wire contract accept it now so the resolver can rank it
later without rework. Nothing writes it server-side yet.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 17:35:45 +02:00
1 parent a27a1ee71d
commit 6aad4a465e
2 files changed
+56 -6

No files matched your search

@@ -0,0 +1,36 @@
-- v1.25 (W-ENV) — conservative past-location handling for the environmental
-- context module.
--
-- Adds `users.home_since` (effective-from instant for the home location) and a
-- `DEVICE` value to the location-source enum. Day resolution attributes a past
-- day to the home location ONLY when the day is on/after `home_since`; earlier
-- days are skipped rather than fabricated from the current home. The deep past
-- is filled by explicit dated location periods instead.
--
-- `DEVICE` is the reserved forward path for a future client-supplied coarse
-- per-day location (iOS v2). The value exists so the resolver and the wire
-- contract already accept it; nothing writes it server-side yet.
--
-- Backfill of existing accounts: any row that already has a home location gets
-- `home_since` stamped to the migration instant. This is conservative — the
-- exact day a pre-upgrade account moved into its home is unknown, so history
-- before the upgrade is left to explicit location periods rather than guessed.
--
-- Additive; no existing weather row touched. Idempotent guards so reruns are
-- safe on prod.
--
-- Reversibility (down):
-- ALTER TABLE "users" DROP COLUMN IF EXISTS "home_since";
-- -- (the `DEVICE` enum value cannot be dropped in place; it is inert until a
-- -- future client writes it, so a roll-back leaves it harmless.)
-- AlterTable: effective-from instant for the home location (nullable).
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "home_since" TIMESTAMP(3);
-- Backfill: stamp existing home rows so they resolve from the upgrade forward.
UPDATE "users"
SET "home_since" = CURRENT_TIMESTAMP
WHERE "home_lat" IS NOT NULL AND "home_since" IS NULL;
-- AlterEnum: reserve the DEVICE source for the iOS v2 forward path.
ALTER TYPE "environment_location_source" ADD VALUE IF NOT EXISTS 'DEVICE';
+20 -6
View File
@@ -63,6 +63,12 @@ model User {
homeLon Float? @map("home_lon")
homeLabel String? @map("home_label")
homeTimezone String? @map("home_timezone")
// v1.25 — effective-from instant for the home location. Stamped the moment a
// home is set/updated. Day resolution attributes a past day to home ONLY when
// the day is on/after this instant; earlier days are skipped (never fabricated
// from the current home). To fill the deep past the user adds an explicit
// dated location period instead. Backfill defaults its start to this instant.
homeSince DateTime? @map("home_since")
// Codex OAuth (ChatGPT subscription)
codexAccessTokenEncrypted String? @map("codex_access_token_encrypted")
@@ -811,11 +817,16 @@ model User {
// ─── v1.25 (W-ENV) Environmental-context module ──────────────────────────────
/// How the location for an environment observation was resolved. HOME = the
/// user's configured home location; TRAVEL = a manual date-range override.
/// (iOS coarse device location is a v2 follow-up and not represented here.)
/// user's configured home location; TRAVEL = an explicit dated location period
/// (a past trip OR a corrected stretch of history) that overrides home for the
/// days it covers; DEVICE = a future client-supplied coarse per-day location.
/// DEVICE is the priority forward path for the iOS v2 client: the value exists
/// so the resolver and the wire contract already accept it, but nothing writes
/// it server-side yet.
enum EnvironmentLocationSource {
HOME
TRAVEL
DEVICE
}
/// A per-day environmental observation (daily weather / daylight) for the
@@ -876,10 +887,13 @@ model EnvironmentContext {
@@map("environment_contexts")
}
/// A manual travel override: for any day in `[startDate, endDate]` the weather
/// fetch resolves against this location instead of the home location. Declared,
/// not inferred — a stated trip dominates the home fallback so a winter week in
/// a sunny locale is not mis-attributed to the home city. Coarse location only.
/// An explicit dated LOCATION PERIOD: for any day in `[startDate, endDate]` the
/// weather fetch resolves against this location instead of the home location.
/// Declared, not inferred — it dominates the home fallback so a stretch spent
/// elsewhere is not mis-attributed to the home city. Two uses share this one
/// model: a discrete trip away from home, AND the mechanism to correct history
/// (e.g. a whole-history home city for the years before the current home became
/// effective). Coarse location only. (Stored under `source = TRAVEL`.)
model EnvironmentTravelLocation {
id String @id @default(cuid())
userId String @map("user_id")