mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
refactor(healthkit): fold the per-type arrival tally into the ledger module
The batch route counted arrivals inline and grew past its size baseline with the new-sample rule. tallyHealthKitArrivals now owns the fold, the route hands it each landed entry and hour with whether it brought a new value, and the size baselines move down to what the route is now. Refs #1259
This commit is contained in:
1 parent
190a83c513
commit
7bbe64ad1a
5 files changed
+46
-56
No files matched your search
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
|
||||
"count": 124,
|
||||
"total": 147278,
|
||||
"total": 147270,
|
||||
"entries": {
|
||||
"app/api/dashboard/summary/route.ts": 1032,
|
||||
"app/api/documents/inbound/route.ts": 811,
|
||||
"app/api/insights/generate/route.ts": 1209,
|
||||
"app/api/measurements/batch/route.ts": 1569,
|
||||
"app/api/measurements/batch/route.ts": 1562,
|
||||
"app/api/measurements/route.ts": 938,
|
||||
"app/api/medications/[id]/route.ts": 1281,
|
||||
"app/api/medications/intake/bulk/route.ts": 1044,
|
||||
@@ -46,7 +46,7 @@
|
||||
"lib/ai/coach/results/metric-table-tool.ts": 1102,
|
||||
"lib/ai/coach/snapshot.ts": 1362,
|
||||
"lib/ai/coach/stream-events.ts": 817,
|
||||
"lib/ai/coach/tools/executor.ts": 1523,
|
||||
"lib/ai/coach/tools/executor.ts": 1522,
|
||||
"lib/ai/coach/turn/model.ts": 820,
|
||||
"lib/ai/coach/types.ts": 1011,
|
||||
"lib/ai/codex-client.ts": 1094,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
|
||||
"count": 410,
|
||||
"total": 148056,
|
||||
"total": 148049,
|
||||
"entries": {
|
||||
"app/achievements/page.tsx :: AchievementsPage": 206,
|
||||
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
|
||||
@@ -38,7 +38,7 @@
|
||||
"app/api/mcp/oauth/token/route.ts :: POST > withBackgroundEvent(arg)": 248,
|
||||
"app/api/measurement-reminders/[id]/route.ts :: apiHandler(arg) #2": 240,
|
||||
"app/api/measurements/[id]/route.ts :: apiHandler(arg) #2": 203,
|
||||
"app/api/measurements/batch/route.ts :: postBatch": 1262,
|
||||
"app/api/measurements/batch/route.ts :: postBatch": 1255,
|
||||
"app/api/measurements/route.ts :: apiHandler(arg)": 286,
|
||||
"app/api/measurements/route.ts :: postMeasurement": 377,
|
||||
"app/api/measurements/series/route.ts :: apiHandler(arg)": 549,
|
||||
|
||||
@@ -88,7 +88,7 @@ import {
|
||||
BACKFILL_SYNC_TRIGGER,
|
||||
HEALTHKIT_REQUEST_SYNC_TRIGGERS,
|
||||
recordHealthKitTypeArrivals,
|
||||
type HealthKitTypeArrival,
|
||||
tallyHealthKitArrivals,
|
||||
} from "@/lib/integrations/healthkit-type-sync";
|
||||
import {
|
||||
classifyHourBucket,
|
||||
@@ -657,11 +657,7 @@ async function postBatch(request: NextRequest): Promise<Response> {
|
||||
* and in nothing that starts work.
|
||||
*/
|
||||
let unchangedStatsCount = 0;
|
||||
/**
|
||||
* The `stats:` re-posts answered `updated` without a write because nothing
|
||||
* changed. Not an arrival of their type (#1259).
|
||||
*/
|
||||
const unchangedStatsRows = new Set<Prepared>();
|
||||
const unchangedStatsRows = new Set<Prepared>(); // #1259: not an arrival
|
||||
let duplicateCount = 0;
|
||||
let failedCount = 0;
|
||||
// v1.44 — subset of `insertedCount` written by the set-based insert.
|
||||
@@ -1109,8 +1105,7 @@ async function postBatch(request: NextRequest): Promise<Response> {
|
||||
let hourInsertedCount = 0;
|
||||
let hourUpdatedCount = 0;
|
||||
const hourLanded: HourBucketWrite[] = [];
|
||||
/** The landed hours that wrote a new or different value (#1259). */
|
||||
const hourChanged = new Set<HourBucketWrite>();
|
||||
const hourChanged = new Set<HourBucketWrite>(); // #1259: a new value
|
||||
if (hourWrites.length > 0 && accountTz) {
|
||||
const { writes, superseded } = combineHourWrites(hourWrites);
|
||||
for (const index of superseded) {
|
||||
@@ -1131,13 +1126,10 @@ async function postBatch(request: NextRequest): Promise<Response> {
|
||||
);
|
||||
for (const { write, contributors } of writes) {
|
||||
const status = statuses.get(write.index) ?? "inserted";
|
||||
// An unchanged hour still reads `updated` on the wire (#1259).
|
||||
const wire = status === "inserted" ? "inserted" : "updated";
|
||||
for (const index of contributors) {
|
||||
// An hour posted again with the same value reads `updated` on the
|
||||
// wire, as before; only the arrival ledger tells the two apart.
|
||||
results[index] = {
|
||||
index,
|
||||
status: status === "inserted" ? "inserted" : "updated",
|
||||
};
|
||||
results[index] = { index, status: wire };
|
||||
if (status === "inserted") hourInsertedCount++;
|
||||
else hourUpdatedCount++;
|
||||
}
|
||||
@@ -1223,40 +1215,20 @@ async function postBatch(request: NextRequest): Promise<Response> {
|
||||
const syncedAt = new Date();
|
||||
const deliveredWithoutTheApp =
|
||||
syncTrigger === "background" || syncTrigger === "push";
|
||||
// v1.42 (#1173) — what this sync brought per type, for the per-type
|
||||
// freshness list on the Apple Health card. Apple Health rows only, and
|
||||
// under the same conditions as the checkpoint beside it. Since #1259 only
|
||||
// a new or changed row makes a type arrive: a duplicate, an unchanged
|
||||
// `stats:` re-post and an hour posted again with its stored value are
|
||||
// carried (`accepted`) but bring nothing (`newSamples`), so a replay or a
|
||||
// retry cannot make a stalled type look live.
|
||||
const arrivals = new Map<MeasurementType, HealthKitTypeArrival>();
|
||||
for (const p of prepared) {
|
||||
if (p.row.source !== "APPLE_HEALTH") continue;
|
||||
const status = results[p.index]?.status;
|
||||
if (
|
||||
status !== "inserted" &&
|
||||
status !== "updated" &&
|
||||
status !== "duplicate"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const type = p.row.type as MeasurementType;
|
||||
const entry = arrivals.get(type) ?? { accepted: 0, newSamples: 0 };
|
||||
entry.accepted += 1;
|
||||
if (status !== "duplicate" && !unchangedStatsRows.has(p)) {
|
||||
entry.newSamples += 1;
|
||||
}
|
||||
arrivals.set(type, entry);
|
||||
}
|
||||
// The hours the phone posted are carried by their type as well, and
|
||||
// bring something only when the slot's value changed.
|
||||
for (const w of hourLanded) {
|
||||
const entry = arrivals.get(w.type) ?? { accepted: 0, newSamples: 0 };
|
||||
entry.accepted += 1;
|
||||
if (hourChanged.has(w)) entry.newSamples += 1;
|
||||
arrivals.set(w.type, entry);
|
||||
}
|
||||
// v1.42 (#1173) — per-type arrivals for the Apple Health card, Apple
|
||||
// Health rows and hours only, under the checkpoint's conditions. Since
|
||||
// #1259 only a new or changed one brings something (`brought`).
|
||||
const arrivals = tallyHealthKitArrivals([
|
||||
...prepared.flatMap((p) => {
|
||||
const status = results[p.index]?.status;
|
||||
if (p.row.source !== "APPLE_HEALTH" || !status) return [];
|
||||
if (status === "skipped" || status === "failed") return [];
|
||||
const unchanged = unchangedStatsRows.has(p);
|
||||
const brought = status !== "duplicate" && !unchanged;
|
||||
return [{ type: p.row.type as MeasurementType, brought }];
|
||||
}),
|
||||
...hourLanded.map((w) => ({ type: w.type, brought: hourChanged.has(w) })),
|
||||
]);
|
||||
if (arrivals.size > 0) {
|
||||
await recordHealthKitTypeArrivals(prisma, {
|
||||
userId: user.id,
|
||||
|
||||
@@ -1008,9 +1008,8 @@ async function postBulk(request: NextRequest): Promise<Response> {
|
||||
// trigger, when a sync last brought a new or changed dose, and when a
|
||||
// background sync last carried them. Entries sent with `source:
|
||||
// "APPLE_HEALTH"` only; a dose taken in the app is not an Apple Health
|
||||
// arrival. A batch of replays answered `duplicate` alone is not an arrival
|
||||
// (#1259): it stamps only `lastSeenDuplicateAt`, the same rule as the batch
|
||||
// route.
|
||||
// arrival. Replays answered `duplicate` alone are not one (#1259), the
|
||||
// same rule as the batch route.
|
||||
const doseArrival = { accepted: 0, newSamples: 0 };
|
||||
for (const result of results) {
|
||||
if (entries[result.index]?.source !== "APPLE_HEALTH") continue;
|
||||
|
||||
@@ -124,6 +124,25 @@ export interface HealthKitTypeArrival {
|
||||
newSamples: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold a batch's landed Apple Health entries and hours into what it did per
|
||||
* type. `brought` is true for an entry or hour that wrote a new or different
|
||||
* value; a duplicate, an unchanged `stats:` re-post and an hour posted again
|
||||
* with its stored value are carried but bring nothing (#1259).
|
||||
*/
|
||||
export function tallyHealthKitArrivals(
|
||||
items: Iterable<{ type: MeasurementType; brought: boolean }>,
|
||||
): Map<MeasurementType, HealthKitTypeArrival> {
|
||||
const arrivals = new Map<MeasurementType, HealthKitTypeArrival>();
|
||||
for (const { type, brought } of items) {
|
||||
const entry = arrivals.get(type) ?? { accepted: 0, newSamples: 0 };
|
||||
entry.accepted += 1;
|
||||
if (brought) entry.newSamples += 1;
|
||||
arrivals.set(type, entry);
|
||||
}
|
||||
return arrivals;
|
||||
}
|
||||
|
||||
/**
|
||||
* One statement for the whole batch (`unnest` over at most a few dozen
|
||||
* types). A type counts as arrived only when the batch brought at least one
|
||||
|
||||
Reference in new issue
Block a user