diff --git a/prisma/migrations/0219_v125_env_home_since/migration.sql b/prisma/migrations/0219_v125_env_home_since/migration.sql new file mode 100644 index 000000000..16c2453d2 --- /dev/null +++ b/prisma/migrations/0219_v125_env_home_since/migration.sql @@ -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'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 073e17c88..3f82e6ac2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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")