refactor(measurements): drop the write-only aggregation columns (migration 0272)

Migration 0263 added three columns beside the Apple Health `stats:*`
authority ladder. Only `aggregation_provenance` is ever read back: the
external-measurement reconciler selects it and refuses a lower-authority write
against a higher-authority canonical row, and the cumulative drain reads it
too. That column stays.

`aggregation_contributor_count` and `aggregation_selected_source_hash` are
written by three call sites and read by none. No select, no raw SQL, no export,
no backup, no API contract, no repair path. The import-time selection keeps its
deterministic tiebreak, which compares an in-memory map key computed per flush
rather than a value read back from the table.

Both are plain nullable scalars with no index, unique, check or foreign key, so
the drop is a catalog-only operation that touches no measured value.
This commit is contained in:
Marc-André Bombeck committed 2026-07-25 15:03:44 +02:00
1 parent a3f1d780e7
commit f8f460bb66
9 files changed
+41 -48

No files matched your search

-2
View File
@@ -544,8 +544,6 @@ async function postBatch(request: NextRequest): Promise<Response> {
.rhythmClassification as Prisma.MeasurementUncheckedCreateInput["rhythmClassification"],
deviceType: p.row.deviceType as string | null,
aggregationProvenance: statsRow ? "HEALTHKIT_STATISTICS" : null,
aggregationContributorCount: null,
aggregationSelectedSourceHash: null,
},
{ exactExternalMatch: statsRow ? "update" : "duplicate" },
),
@@ -652,11 +652,7 @@ describe("streamParseExportXml — cumulative source-day estimates", () => {
expect(prisma._measurements[0]).toMatchObject({
value: 8_526,
aggregationProvenance: "EXPORT_XML_SOURCE_MAX",
aggregationContributorCount: 2,
});
expect(prisma._measurements[0]?.aggregationSelectedSourceHash).toMatch(
/^[a-f0-9]{64}$/,
);
expect(result.cumulativeEstimates).toEqual({ days: 1, rows: 1 });
});
@@ -675,7 +671,6 @@ describe("streamParseExportXml — cumulative source-day estimates", () => {
type: mapping.measurementType,
value: 30,
aggregationProvenance: "EXPORT_XML_SOURCE_MAX",
aggregationContributorCount: 2,
});
},
);
@@ -694,7 +689,6 @@ describe("streamParseExportXml — cumulative source-day estimates", () => {
expect(prisma._measurements[0]).toMatchObject({
type: mapping.measurementType,
value: 50,
aggregationContributorCount: 1,
});
},
);
@@ -712,7 +706,6 @@ describe("streamParseExportXml — cumulative source-day estimates", () => {
expect(prisma._measurements).toHaveLength(1);
expect(prisma._measurements[0]).toMatchObject({
value: 100,
aggregationContributorCount: 1,
});
});
@@ -1171,7 +1164,6 @@ describe("streamParseExportXml — memory ceiling", () => {
expect(prisma._measurements).toHaveLength(1);
expect(prisma._measurements[0]).toMatchObject({
value: 10_000,
aggregationContributorCount: 1,
});
// Heap delta should be modest — we tolerate a generous 100 MB so a
@@ -40,8 +40,6 @@ function row(overrides: Record<string, unknown> = {}): Record<string, unknown> {
externalId: "sleep:new",
externalSourceVersion: null,
aggregationProvenance: null,
aggregationContributorCount: null,
aggregationSelectedSourceHash: null,
glucoseContext: null,
sleepStage: "DEEP",
rhythmClassification: null,
@@ -224,8 +222,6 @@ describe("reconcileExternalMeasurement", () => {
sleepStage: null,
externalId: "stats:HKQuantityTypeIdentifierStepCount:2026-07-20",
aggregationProvenance: "HEALTHKIT_STATISTICS",
aggregationContributorCount: null,
aggregationSelectedSourceHash: null,
});
const { tx, measurement } = transaction([native]);
@@ -240,8 +236,6 @@ describe("reconcileExternalMeasurement", () => {
sleepStage: null,
externalId: "stats:HKQuantityTypeIdentifierStepCount:2026-07-20",
aggregationProvenance: "EXPORT_XML_SOURCE_MAX",
aggregationContributorCount: 2,
aggregationSelectedSourceHash: "source-hash",
}),
{ exactExternalMatch: "update" },
);
@@ -350,8 +344,6 @@ describe("reconcileExternalMeasurement", () => {
sleepStage: null,
externalId: "stats:HKQuantityTypeIdentifierStepCount:2026-07-20",
aggregationProvenance: "HEALTHKIT_STATISTICS",
aggregationContributorCount: null,
aggregationSelectedSourceHash: null,
}),
{ exactExternalMatch: "update" },
);
@@ -366,8 +358,6 @@ describe("reconcileExternalMeasurement", () => {
data: expect.objectContaining({
value: 8_600,
aggregationProvenance: "HEALTHKIT_STATISTICS",
aggregationContributorCount: null,
aggregationSelectedSourceHash: null,
syncVersion: { increment: 1 },
}),
}),
@@ -352,8 +352,6 @@ export async function drainPerSampleCumulative(
externalId,
deletedAt: null,
aggregationProvenance: "LEGACY_UNKNOWN",
aggregationContributorCount: null,
aggregationSelectedSourceHash: null,
syncVersion: { increment: 1 },
...(slotIsFreeForTarget
? { measuredAt: canonicalTimestamp }
@@ -680,7 +680,10 @@ export async function streamParseExportXml(
}
}
if (!selected) continue;
const [selectedSourceHash, selectedSubtotal] = selected;
// Only the subtotal is carried forward. The source hash stays an
// in-memory map key — it is what makes the max-subtotal selection
// above deterministic on a tie, and it is not persisted.
const [, selectedSubtotal] = selected;
if (validateMeasurementRange(type, selectedSubtotal) !== null) {
unknown[`${type}::aggregate_out_of_range`] =
(unknown[`${type}::aggregate_out_of_range`] ?? 0) + 1;
@@ -704,8 +707,6 @@ export async function streamParseExportXml(
sleepStage: null,
deviceType: null,
aggregationProvenance: "EXPORT_XML_SOURCE_MAX",
aggregationContributorCount: bySource.size,
aggregationSelectedSourceHash: selectedSourceHash,
},
{ exactExternalMatch: "update" },
),