mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
Closed analytics blocks survive a write that says it reached no further back, so a writer that never invalidated left them on their pre-write values. The Apple Health and Health Connect imports, the CSV and JSON imports, the score engines, the WHOOP webhook, the Withings activity, sleep and ECG legs, the Oura history backfill and the sleep-segment sweep wrote measurements without touching the read caches; a long-history import showed the old all-time figures until the entries aged out. The shared post-mutation tail now marks the caches stale back to the oldest row it is handed, and the Withings and Oura legs that hand-rolled the same tail run through it. The two export imports share one tail that refolds the rollups, marks the caches stale back to the oldest sample written (all of them when none was) and re-warms the status assessments. A sweep that tombstones a sleep segment drops every cached block.
41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
/**
|
|
* The key part that marks an analytics entry as a closed block: a value that
|
|
* depends only on readings measured before `before`. Such a block is the
|
|
* same however many syncs land after it, as long as none of them touches a
|
|
* reading that old, so a measurement write that says how far back it
|
|
* reached (`touchedFrom`) leaves it in place. Every other invalidation, and
|
|
* every write that does not say, drops it as before.
|
|
*/
|
|
export function closedBlockKeyPart(before: Date | number): string {
|
|
const ms = typeof before === "number" ? before : before.getTime();
|
|
return `before:${ms}`;
|
|
}
|
|
|
|
const CLOSED_BLOCK_RE = /\|before:(\d+)(?:\||$)/;
|
|
|
|
/**
|
|
* Whether `key` is a closed block that a write reaching back to
|
|
* `touchedFrom` cannot have changed. Exported for its test.
|
|
*/
|
|
export function closedBlockUntouched(key: string, touchedFrom: Date): boolean {
|
|
const match = CLOSED_BLOCK_RE.exec(key);
|
|
return match !== null && Number(match[1]) <= touchedFrom.getTime();
|
|
}
|
|
|
|
/**
|
|
* The earliest `measuredAt` among rows a write touched: the `touchedFrom` a
|
|
* writer that knows every row it wrote hands to `invalidateUserMeasurements`.
|
|
* `undefined` for no rows, which makes the invalidation drop closed blocks too.
|
|
*/
|
|
export function earliestMeasuredAt(
|
|
rows: Iterable<{ measuredAt: Date }>,
|
|
): Date | undefined {
|
|
let earliest: Date | undefined;
|
|
for (const row of rows) {
|
|
if (earliest === undefined || row.measuredAt < earliest) {
|
|
earliest = row.measuredAt;
|
|
}
|
|
}
|
|
return earliest;
|
|
}
|