mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
feat(environment): resolve past days conservatively, never guessing from home
Rework the per-day location resolution so it can no longer fabricate weather for
days before the home was set. Precedence, first hit wins:
1. an explicit dated location period (a trip, or a corrected stretch of
history) covering the day;
2. DEVICE — a reserved slot for a future client-supplied coarse per-day
location (ranks between the period and home; not produced server-side yet);
3. the home location, but only for days on/after its effective-from date;
4. otherwise the day is skipped.
The home route stamps `homeSince` whenever the home is set/updated and returns
it. The overview exposes it. Backfill bounds are now optional and default to the
conservative `[homeSince .. today]` span instead of a fixed reach onto the
current home; days before `homeSince` still resolve to a skip in the worker, so
even a wider explicit start cannot back-fill weather for the pre-home past. To
reach further back a user adds an explicit location period for that range.
Add unit coverage for the precedence (including the effective-from boundary and
the skip-not-guess rule) and the default backfill range.
This commit is contained in:
1 parent
6aad4a465e
commit
c254135406
7 files changed
+209
-29
No files matched your search
@@ -21,7 +21,11 @@ import { requireModuleEnabled } from "@/lib/modules/gate";
|
||||
import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { environmentBackfillSchema } from "@/lib/validations/environment";
|
||||
import { ENVIRONMENT_MAX_BACKFILL_DAYS } from "@/lib/environment/service";
|
||||
import {
|
||||
ENVIRONMENT_MAX_BACKFILL_DAYS,
|
||||
defaultBackfillRange,
|
||||
utcDayKey,
|
||||
} from "@/lib/environment/service";
|
||||
import { enqueueEnvironmentFetch } from "@/lib/jobs/environment-fetch";
|
||||
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
@@ -49,7 +53,33 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
});
|
||||
}
|
||||
|
||||
const { startDate, endDate } = parsed.data;
|
||||
const profile = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { homeLat: true, homeSince: true },
|
||||
});
|
||||
if (profile?.homeLat == null) {
|
||||
return apiError("Set a home location before backfilling.", 409, {
|
||||
errorCode: "environment.no_home",
|
||||
});
|
||||
}
|
||||
|
||||
// Resolve the effective span. An omitted bound defaults to the conservative
|
||||
// [homeSince .. today] range — never a fixed reach into the past onto the
|
||||
// current home. Days before homeSince still resolve to SKIP in the worker, so
|
||||
// even an explicit wider start cannot fabricate weather for the pre-home past.
|
||||
const today = utcDayKey(new Date());
|
||||
const fallback = defaultBackfillRange(profile.homeSince) ?? {
|
||||
startDate: today,
|
||||
endDate: today,
|
||||
};
|
||||
const startDate = parsed.data.startDate ?? fallback.startDate;
|
||||
const endDate = parsed.data.endDate ?? fallback.endDate;
|
||||
if (startDate > endDate) {
|
||||
return apiError("startDate must be on or before endDate", 422, {
|
||||
errorCode: "environment.invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const [sy, sm, sd] = startDate.split("-").map(Number);
|
||||
const [ey, em, ed] = endDate.split("-").map(Number);
|
||||
const spanDays =
|
||||
@@ -64,16 +94,6 @@ export const POST = apiHandler(async (request: NextRequest) => {
|
||||
);
|
||||
}
|
||||
|
||||
const profile = await prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { homeLat: true },
|
||||
});
|
||||
if (profile?.homeLat == null) {
|
||||
return apiError("Set a home location before backfilling.", 409, {
|
||||
errorCode: "environment.no_home",
|
||||
});
|
||||
}
|
||||
|
||||
const enqueued = await enqueueEnvironmentFetch({
|
||||
userId: user.id,
|
||||
startDate,
|
||||
|
||||
@@ -41,11 +41,16 @@ export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
}
|
||||
|
||||
const { lat, lon, label, timezone } = parsed.data;
|
||||
// Stamp the effective-from instant: from now on this home resolves days; days
|
||||
// before it stay un-attributed (filled via explicit location periods). Every
|
||||
// set/update re-stamps — the home is "effective from the moment it is set".
|
||||
const homeSince = new Date();
|
||||
const home = {
|
||||
homeLat: roundCoarse(lat),
|
||||
homeLon: roundCoarse(lon),
|
||||
homeLabel: label,
|
||||
homeTimezone: timezone,
|
||||
homeSince,
|
||||
};
|
||||
|
||||
await prisma.user.update({ where: { id: user.id }, data: home });
|
||||
@@ -62,6 +67,7 @@ export const PUT = apiHandler(async (request: NextRequest) => {
|
||||
lon: home.homeLon,
|
||||
label: home.homeLabel,
|
||||
timezone: home.homeTimezone,
|
||||
since: homeSince.toISOString(),
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,7 @@ export const GET = apiHandler(async () => {
|
||||
homeLon: true,
|
||||
homeLabel: true,
|
||||
homeTimezone: true,
|
||||
homeSince: true,
|
||||
timezone: true,
|
||||
},
|
||||
}),
|
||||
@@ -59,6 +60,9 @@ export const GET = apiHandler(async () => {
|
||||
lon: profile.homeLon,
|
||||
label: profile.homeLabel,
|
||||
timezone: profile.homeTimezone ?? profile.timezone,
|
||||
// Effective-from instant: the settings surface uses it to label the
|
||||
// home and to prefill the backfill start (conservative default range).
|
||||
since: profile.homeSince?.toISOString() ?? null,
|
||||
}
|
||||
: null;
|
||||
|
||||
|
||||
Reference in new issue
Block a user