mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
fix(environment): end the nightly window on the person's today
The default lookback ended on the UTC date, so east of UTC the local today stayed out of the window until the next night's run.
This commit is contained in:
1 parent
2372fee376
commit
2f32a4458c
2 files changed
+57
-15
No files matched your search
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* The nightly environment window ends on the person's today, not the UTC
|
||||
* date: east of UTC the local day begins hours before the UTC one.
|
||||
*/
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/lib/db", () => ({ prisma: {} }));
|
||||
|
||||
import { resolveRange } from "../environment-fetch";
|
||||
|
||||
describe("resolveRange", () => {
|
||||
// 20:00 UTC on 1 March is already 2 March in Auckland.
|
||||
const now = new Date("2026-03-01T20:00:00.000Z");
|
||||
|
||||
it("ends the lookback on the local today", () => {
|
||||
expect(resolveRange({}, "Pacific/Auckland", now)).toEqual({
|
||||
startDate: "2026-02-23",
|
||||
endDate: "2026-03-02",
|
||||
});
|
||||
expect(resolveRange({}, "America/Los_Angeles", now)).toEqual({
|
||||
startDate: "2026-02-22",
|
||||
endDate: "2026-03-01",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps an explicit range as given", () => {
|
||||
expect(
|
||||
resolveRange(
|
||||
{ startDate: "2025-01-01", endDate: "2025-01-31" },
|
||||
"Pacific/Auckland",
|
||||
now,
|
||||
),
|
||||
).toEqual({ startDate: "2025-01-01", endDate: "2025-01-31" });
|
||||
});
|
||||
});
|
||||
@@ -39,8 +39,9 @@ import {
|
||||
ENVIRONMENT_MAX_BACKFILL_DAYS,
|
||||
fetchAndStoreEnvironment,
|
||||
fillAirQualityGaps,
|
||||
utcDayKey,
|
||||
} from "@/lib/environment/service";
|
||||
import { shiftDateKey, userDayKey } from "@/lib/tz/format";
|
||||
import { resolveUserTimezone } from "@/lib/tz/resolver";
|
||||
import { enumerateDayCount } from "@/lib/environment/day-span";
|
||||
import { recordError } from "@/lib/jobs/worker-status";
|
||||
import { jobDone, jobFailed, type JobOutcome } from "@/lib/jobs/job-outcome";
|
||||
@@ -54,8 +55,6 @@ export const ENVIRONMENT_FETCH_CRON = "10 2 * * *";
|
||||
|
||||
export const ENVIRONMENT_FETCH_CONCURRENCY = 1;
|
||||
|
||||
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Per-user fetch payload. An empty payload (no `userId`) is the daily discovery
|
||||
* tick. `startDate`/`endDate` (YYYY-MM-DD) drive the on-demand backfill; omitted
|
||||
@@ -67,18 +66,23 @@ export interface EnvironmentFetchPayload {
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
/** Resolve the date range for a per-user run (explicit range, else lookback). */
|
||||
function resolveRange(payload: EnvironmentFetchPayload): {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
} {
|
||||
const today = new Date();
|
||||
const endDate = payload.endDate ?? utcDayKey(today);
|
||||
/**
|
||||
* Resolve the date range for a per-user run (explicit range, else lookback).
|
||||
*
|
||||
* The lookback ends on the person's today, in the zone the service keys the
|
||||
* stored days in, not on the UTC date: east of UTC the local day starts up to
|
||||
* fourteen hours before the UTC one, and a window ending on the UTC date left
|
||||
* the local today out until the next night's run.
|
||||
*/
|
||||
export function resolveRange(
|
||||
payload: EnvironmentFetchPayload,
|
||||
tz: string,
|
||||
now: Date = new Date(),
|
||||
): { startDate: string; endDate: string } {
|
||||
const today = userDayKey(now, tz);
|
||||
const endDate = payload.endDate ?? today;
|
||||
const startDate =
|
||||
payload.startDate ??
|
||||
utcDayKey(
|
||||
new Date(today.getTime() - ENVIRONMENT_LOOKBACK_DAYS * MS_PER_DAY),
|
||||
);
|
||||
payload.startDate ?? shiftDateKey(today, -ENVIRONMENT_LOOKBACK_DAYS);
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
@@ -208,7 +212,10 @@ export async function handleEnvironmentFetch(
|
||||
return jobDone({ skipped: "environment_module_disabled" });
|
||||
}
|
||||
|
||||
const { startDate, endDate } = resolveRange(payload);
|
||||
const { startDate, endDate } = resolveRange(
|
||||
payload,
|
||||
await resolveUserTimezone(userId),
|
||||
);
|
||||
// Clamp the span so a crafted backfill can never fan out an unbounded range.
|
||||
const span = enumerateDayCount(startDate, endDate);
|
||||
if (span > ENVIRONMENT_MAX_BACKFILL_DAYS) {
|
||||
|
||||
Reference in new issue
Block a user