trainbud 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +11 -0
- package/CHANGELOG.md +675 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +182 -0
- package/README.md +322 -0
- package/dist/appDb.d.ts +83 -0
- package/dist/appDb.d.ts.map +1 -0
- package/dist/appDb.js +239 -0
- package/dist/appDb.js.map +1 -0
- package/dist/check.d.ts +12 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +230 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +457 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +241 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard.d.ts +8 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +1004 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/dashboardCharts.d.ts +99 -0
- package/dist/dashboardCharts.d.ts.map +1 -0
- package/dist/dashboardCharts.js +317 -0
- package/dist/dashboardCharts.js.map +1 -0
- package/dist/dashboardData.d.ts +89 -0
- package/dist/dashboardData.d.ts.map +1 -0
- package/dist/dashboardData.js +228 -0
- package/dist/dashboardData.js.map +1 -0
- package/dist/detect/baseline.d.ts +17 -0
- package/dist/detect/baseline.d.ts.map +1 -0
- package/dist/detect/baseline.js +62 -0
- package/dist/detect/baseline.js.map +1 -0
- package/dist/detect/countdown.d.ts +30 -0
- package/dist/detect/countdown.d.ts.map +1 -0
- package/dist/detect/countdown.js +79 -0
- package/dist/detect/countdown.js.map +1 -0
- package/dist/detect/detectors.d.ts +20 -0
- package/dist/detect/detectors.d.ts.map +1 -0
- package/dist/detect/detectors.js +291 -0
- package/dist/detect/detectors.js.map +1 -0
- package/dist/detect/findings.d.ts +29 -0
- package/dist/detect/findings.d.ts.map +1 -0
- package/dist/detect/findings.js +2 -0
- package/dist/detect/findings.js.map +1 -0
- package/dist/detect/forecast.d.ts +33 -0
- package/dist/detect/forecast.d.ts.map +1 -0
- package/dist/detect/forecast.js +122 -0
- package/dist/detect/forecast.js.map +1 -0
- package/dist/detect/index.d.ts +50 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +114 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/sleepQuality.d.ts +21 -0
- package/dist/detect/sleepQuality.d.ts.map +1 -0
- package/dist/detect/sleepQuality.js +164 -0
- package/dist/detect/sleepQuality.js.map +1 -0
- package/dist/detect/trimp.d.ts +22 -0
- package/dist/detect/trimp.d.ts.map +1 -0
- package/dist/detect/trimp.js +77 -0
- package/dist/detect/trimp.js.map +1 -0
- package/dist/detect/week.d.ts +31 -0
- package/dist/detect/week.d.ts.map +1 -0
- package/dist/detect/week.js +184 -0
- package/dist/detect/week.js.map +1 -0
- package/dist/garmin/auth.d.ts +10 -0
- package/dist/garmin/auth.d.ts.map +1 -0
- package/dist/garmin/auth.js +81 -0
- package/dist/garmin/auth.js.map +1 -0
- package/dist/garmin/cache.d.ts +42 -0
- package/dist/garmin/cache.d.ts.map +1 -0
- package/dist/garmin/cache.js +141 -0
- package/dist/garmin/cache.js.map +1 -0
- package/dist/garmin/client.d.ts +22 -0
- package/dist/garmin/client.d.ts.map +1 -0
- package/dist/garmin/client.js +237 -0
- package/dist/garmin/client.js.map +1 -0
- package/dist/garmin/daily.d.ts +24 -0
- package/dist/garmin/daily.d.ts.map +1 -0
- package/dist/garmin/daily.js +96 -0
- package/dist/garmin/daily.js.map +1 -0
- package/dist/garmin/garminApiTypes.d.ts +70 -0
- package/dist/garmin/garminApiTypes.d.ts.map +1 -0
- package/dist/garmin/garminApiTypes.js +3 -0
- package/dist/garmin/garminApiTypes.js.map +1 -0
- package/dist/garmin/garminConnect.d.ts +17 -0
- package/dist/garmin/garminConnect.d.ts.map +1 -0
- package/dist/garmin/garminConnect.js +5 -0
- package/dist/garmin/garminConnect.js.map +1 -0
- package/dist/garmin/partial.d.ts +32 -0
- package/dist/garmin/partial.d.ts.map +1 -0
- package/dist/garmin/partial.js +38 -0
- package/dist/garmin/partial.js.map +1 -0
- package/dist/garmin/rawApi.d.ts +59 -0
- package/dist/garmin/rawApi.d.ts.map +1 -0
- package/dist/garmin/rawApi.js +113 -0
- package/dist/garmin/rawApi.js.map +1 -0
- package/dist/garmin/types.d.ts +85 -0
- package/dist/garmin/types.d.ts.map +1 -0
- package/dist/garmin/types.js +11 -0
- package/dist/garmin/types.js.map +1 -0
- package/dist/history/capture.d.ts +12 -0
- package/dist/history/capture.d.ts.map +1 -0
- package/dist/history/capture.js +63 -0
- package/dist/history/capture.js.map +1 -0
- package/dist/history/context.d.ts +51 -0
- package/dist/history/context.d.ts.map +1 -0
- package/dist/history/context.js +122 -0
- package/dist/history/context.js.map +1 -0
- package/dist/history/fallback.d.ts +72 -0
- package/dist/history/fallback.d.ts.map +1 -0
- package/dist/history/fallback.js +185 -0
- package/dist/history/fallback.js.map +1 -0
- package/dist/history/ingest.d.ts +44 -0
- package/dist/history/ingest.d.ts.map +1 -0
- package/dist/history/ingest.js +347 -0
- package/dist/history/ingest.js.map +1 -0
- package/dist/history/scheduler.d.ts +20 -0
- package/dist/history/scheduler.d.ts.map +1 -0
- package/dist/history/scheduler.js +54 -0
- package/dist/history/scheduler.js.map +1 -0
- package/dist/history/schema.d.ts +41 -0
- package/dist/history/schema.d.ts.map +1 -0
- package/dist/history/schema.js +91 -0
- package/dist/history/schema.js.map +1 -0
- package/dist/history/store.d.ts +112 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/store.js +282 -0
- package/dist/history/store.js.map +1 -0
- package/dist/httpServer.d.ts +55 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +1027 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/mcpConfig.d.ts +56 -0
- package/dist/mcpConfig.d.ts.map +1 -0
- package/dist/mcpConfig.js +106 -0
- package/dist/mcpConfig.js.map +1 -0
- package/dist/pairApi.d.ts +14 -0
- package/dist/pairApi.d.ts.map +1 -0
- package/dist/pairApi.js +38 -0
- package/dist/pairApi.js.map +1 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +105 -0
- package/dist/paths.js.map +1 -0
- package/dist/profile.d.ts +123 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +327 -0
- package/dist/profile.js.map +1 -0
- package/dist/promptApi.d.ts +43 -0
- package/dist/promptApi.d.ts.map +1 -0
- package/dist/promptApi.js +328 -0
- package/dist/promptApi.js.map +1 -0
- package/dist/promptSuggestions.d.ts +16 -0
- package/dist/promptSuggestions.d.ts.map +1 -0
- package/dist/promptSuggestions.js +141 -0
- package/dist/promptSuggestions.js.map +1 -0
- package/dist/selfTest.d.ts +53 -0
- package/dist/selfTest.d.ts.map +1 -0
- package/dist/selfTest.js +225 -0
- package/dist/selfTest.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +210 -0
- package/dist/setup.js.map +1 -0
- package/dist/toolErrors.d.ts +2 -0
- package/dist/toolErrors.d.ts.map +1 -0
- package/dist/toolErrors.js +18 -0
- package/dist/toolErrors.js.map +1 -0
- package/dist/tools/activities.d.ts +18 -0
- package/dist/tools/activities.d.ts.map +1 -0
- package/dist/tools/activities.js +182 -0
- package/dist/tools/activities.js.map +1 -0
- package/dist/tools/bodyComposition.d.ts +10 -0
- package/dist/tools/bodyComposition.d.ts.map +1 -0
- package/dist/tools/bodyComposition.js +139 -0
- package/dist/tools/bodyComposition.js.map +1 -0
- package/dist/tools/context.d.ts +8 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +148 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/findings.d.ts +7 -0
- package/dist/tools/findings.d.ts.map +1 -0
- package/dist/tools/findings.js +51 -0
- package/dist/tools/findings.js.map +1 -0
- package/dist/tools/heartRate.d.ts +10 -0
- package/dist/tools/heartRate.d.ts.map +1 -0
- package/dist/tools/heartRate.js +105 -0
- package/dist/tools/heartRate.js.map +1 -0
- package/dist/tools/index.d.ts +66 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +101 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/payloads.d.ts +148 -0
- package/dist/tools/payloads.d.ts.map +1 -0
- package/dist/tools/payloads.js +2 -0
- package/dist/tools/payloads.js.map +1 -0
- package/dist/tools/recovery.d.ts +31 -0
- package/dist/tools/recovery.d.ts.map +1 -0
- package/dist/tools/recovery.js +350 -0
- package/dist/tools/recovery.js.map +1 -0
- package/dist/tools/sleep.d.ts +10 -0
- package/dist/tools/sleep.d.ts.map +1 -0
- package/dist/tools/sleep.js +124 -0
- package/dist/tools/sleep.js.map +1 -0
- package/dist/tools/stress.d.ts +11 -0
- package/dist/tools/stress.d.ts.map +1 -0
- package/dist/tools/stress.js +108 -0
- package/dist/tools/stress.js.map +1 -0
- package/dist/tools/trainingInsights.d.ts +15 -0
- package/dist/tools/trainingInsights.d.ts.map +1 -0
- package/dist/tools/trainingInsights.js +79 -0
- package/dist/tools/trainingInsights.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/vo2Max.d.ts +11 -0
- package/dist/tools/vo2Max.d.ts.map +1 -0
- package/dist/tools/vo2Max.js +110 -0
- package/dist/tools/vo2Max.js.map +1 -0
- package/dist/tools/week.d.ts +12 -0
- package/dist/tools/week.d.ts.map +1 -0
- package/dist/tools/week.js +74 -0
- package/dist/tools/week.js.map +1 -0
- package/dist/usage.d.ts +104 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +247 -0
- package/dist/usage.js.map +1 -0
- package/dist/utils/batch.d.ts +2 -0
- package/dist/utils/batch.d.ts.map +1 -0
- package/dist/utils/batch.js +18 -0
- package/dist/utils/batch.js.map +1 -0
- package/dist/utils/helpers.d.ts +32 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +188 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/jsonFile.d.ts +19 -0
- package/dist/utils/jsonFile.d.ts.map +1 -0
- package/dist/utils/jsonFile.js +24 -0
- package/dist/utils/jsonFile.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/secretFile.d.ts +15 -0
- package/dist/utils/secretFile.d.ts.map +1 -0
- package/dist/utils/secretFile.js +47 -0
- package/dist/utils/secretFile.js.map +1 -0
- package/dist/utils/stdio.d.ts +10 -0
- package/dist/utils/stdio.d.ts.map +1 -0
- package/dist/utils/stdio.js +70 -0
- package/dist/utils/stdio.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/dist/watchApi.d.ts +264 -0
- package/dist/watchApi.d.ts.map +1 -0
- package/dist/watchApi.js +333 -0
- package/dist/watchApi.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
// SECTION: Fixture capture
|
|
4
|
+
//
|
|
5
|
+
// The stress mapper shipped broken because its test was written against a
|
|
6
|
+
// payload nobody had ever received: it read `overallStressLevel`, Connect sends
|
|
7
|
+
// `avgStressLevel`, and the test passed because it asserted against the same
|
|
8
|
+
// invention. tests/rawApi.test.ts still carries both versions side by side as
|
|
9
|
+
// the record of it.
|
|
10
|
+
//
|
|
11
|
+
// Detectors are far more elaborate than that mapper, so their fixtures have to
|
|
12
|
+
// come off the wire. Ingest is already keeping the raw responses; this only
|
|
13
|
+
// scrubs the identifiers so one can be committed.
|
|
14
|
+
const REDACTION = "<redacted>";
|
|
15
|
+
/**
|
|
16
|
+
* Matched case-insensitively against the key name. Identity and credentials
|
|
17
|
+
* only -- never a measurement, never a date. A fixture with its numbers
|
|
18
|
+
* redacted would be worse than the invented payload it replaces.
|
|
19
|
+
*/
|
|
20
|
+
export const REDACTED_KEYS = [
|
|
21
|
+
"userprofilepk",
|
|
22
|
+
"userprofileid",
|
|
23
|
+
"userid",
|
|
24
|
+
"displayname",
|
|
25
|
+
"fullname",
|
|
26
|
+
"email",
|
|
27
|
+
"ownerid",
|
|
28
|
+
"ownerdisplayname",
|
|
29
|
+
"ownerfullname",
|
|
30
|
+
"ownerprofileimageurl",
|
|
31
|
+
"devicename",
|
|
32
|
+
"deviceid",
|
|
33
|
+
"unitid",
|
|
34
|
+
];
|
|
35
|
+
function isRedactedKey(key) {
|
|
36
|
+
const lower = key.toLowerCase();
|
|
37
|
+
if (REDACTED_KEYS.includes(lower)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return lower.endsWith("token") || lower.endsWith("key") || lower.endsWith("secret");
|
|
41
|
+
}
|
|
42
|
+
/** Deep clone with identity fields replaced. Never mutates its input. */
|
|
43
|
+
export function redactPayload(payload) {
|
|
44
|
+
if (payload === null || typeof payload !== "object") {
|
|
45
|
+
return payload;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(payload)) {
|
|
48
|
+
return payload.map((entry) => redactPayload(entry));
|
|
49
|
+
}
|
|
50
|
+
const redacted = {};
|
|
51
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
52
|
+
redacted[key] = isRedactedKey(key) ? REDACTION : redactPayload(value);
|
|
53
|
+
}
|
|
54
|
+
return redacted;
|
|
55
|
+
}
|
|
56
|
+
/** Writes one redacted response to `<dir>/<source>-<date>.json`. */
|
|
57
|
+
export function writeFixture(directory, source, date, payload) {
|
|
58
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
59
|
+
const file = path.join(directory, `${source}-${date}.json`);
|
|
60
|
+
fs.writeFileSync(file, `${JSON.stringify(redactPayload(payload), null, 2)}\n`, "utf8");
|
|
61
|
+
return file;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/history/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,2BAA2B;AAC3B,EAAE;AACF,0EAA0E;AAC1E,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,oBAAoB;AACpB,EAAE;AACF,+EAA+E;AAC/E,4EAA4E;AAC5E,kDAAkD;AAElD,MAAM,SAAS,GAAG,YAAY,CAAC;AAE/B;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,eAAe;IACf,eAAe;IACf,QAAQ;IACR,aAAa;IACb,UAAU;IACV,OAAO;IACP,SAAS;IACT,kBAAkB;IAClB,eAAe;IACf,sBAAsB;IACtB,YAAY;IACZ,UAAU;IACV,QAAQ;CACT,CAAC;AAEF,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAEhC,IAAI,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACtF,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IACtD,CAAC;IAED,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAE7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAkC,CAAC,EAAE,CAAC;QAC9E,QAAQ,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAC1B,SAAiB,EACjB,MAAoB,EACpB,IAAY,EACZ,OAAgB;IAEhB,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,MAAM,IAAI,IAAI,OAAO,CAAC,CAAC;IAC5D,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEvF,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ContextKind, SubjectiveKind } from "./schema.js";
|
|
2
|
+
export interface ContextEntry {
|
|
3
|
+
id: number;
|
|
4
|
+
kind: ContextKind;
|
|
5
|
+
text: string;
|
|
6
|
+
effectiveFrom: string;
|
|
7
|
+
effectiveTo: string | null;
|
|
8
|
+
createdAt: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SubjectivePoint {
|
|
11
|
+
date: string;
|
|
12
|
+
value: number;
|
|
13
|
+
note: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface AddContextOptions {
|
|
16
|
+
effectiveFrom?: string;
|
|
17
|
+
effectiveTo?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Entries carry a date range rather than an is-current flag. An injury heals
|
|
21
|
+
* and a race happens, and an entry that can only ever be "current" has to be
|
|
22
|
+
* deleted to stop being true -- which loses the fact that it *was* true. "How
|
|
23
|
+
* did the last build go, when the achilles was bad" is exactly the question
|
|
24
|
+
* this layer exists to answer.
|
|
25
|
+
*/
|
|
26
|
+
export declare function addContextEntry(kind: ContextKind, text: string, options?: AddContextOptions): ContextEntry;
|
|
27
|
+
/** Everything true on the given date, newest first. */
|
|
28
|
+
export declare function activeContext(onDate: string): ContextEntry[];
|
|
29
|
+
/**
|
|
30
|
+
* Entries dated after `onDate`, soonest first.
|
|
31
|
+
*
|
|
32
|
+
* activeContext deliberately excludes these: an entry is "true on a date" only
|
|
33
|
+
* from its effective_from onwards, and a race in six weeks is not a fact about
|
|
34
|
+
* today. But a future race is the single most useful piece of context this
|
|
35
|
+
* store holds — it changes what every other number means, because the same load
|
|
36
|
+
* ratio is a warning eleven weeks out and the plan three days out — and until
|
|
37
|
+
* now nothing could read one, because the only reader excluded it by design.
|
|
38
|
+
*/
|
|
39
|
+
export declare function upcomingContext(onDate: string): ContextEntry[];
|
|
40
|
+
/** Every entry ever recorded, newest first — for a dashboard or a tool listing. */
|
|
41
|
+
export declare function allContext(): ContextEntry[];
|
|
42
|
+
/** Ends an entry without deleting it. Returns false if there was no such entry. */
|
|
43
|
+
export declare function closeContextEntry(id: number, onDate?: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The 1-10 scale is what makes these comparable across days, so a 0 or a 12 is
|
|
46
|
+
* a typo rather than a reading. Storing it silently would poison every average
|
|
47
|
+
* built on top.
|
|
48
|
+
*/
|
|
49
|
+
export declare function logSubjective(date: string, kind: SubjectiveKind, value: number, note?: string): void;
|
|
50
|
+
export declare function subjectiveSeries(kind: SubjectiveKind, startDate: string, endDate: string): SubjectivePoint[];
|
|
51
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/history/context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAS/D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAaD,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,iBAAsB,GAC9B,YAAY,CAuBd;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAgB5D;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAe9D;AAED,mFAAmF;AACnF,wBAAgB,UAAU,IAAI,YAAY,EAAE,CAc3C;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAE,MAAgB,GAAG,OAAO,CAM/E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,GACZ,IAAI,CAiBN;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,eAAe,EAAE,CAQnB"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { getHistoryDb } from "./store.js";
|
|
3
|
+
const SUBJECTIVE_MIN = 1;
|
|
4
|
+
const SUBJECTIVE_MAX = 10;
|
|
5
|
+
function nowSeconds() {
|
|
6
|
+
return Math.floor(Date.now() / 1000);
|
|
7
|
+
}
|
|
8
|
+
function today() {
|
|
9
|
+
return DateTime.local().toISODate() ?? "";
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Entries carry a date range rather than an is-current flag. An injury heals
|
|
13
|
+
* and a race happens, and an entry that can only ever be "current" has to be
|
|
14
|
+
* deleted to stop being true -- which loses the fact that it *was* true. "How
|
|
15
|
+
* did the last build go, when the achilles was bad" is exactly the question
|
|
16
|
+
* this layer exists to answer.
|
|
17
|
+
*/
|
|
18
|
+
export function addContextEntry(kind, text, options = {}) {
|
|
19
|
+
const trimmed = text.trim();
|
|
20
|
+
if (trimmed.length === 0) {
|
|
21
|
+
throw new Error("Context entry text cannot be empty.");
|
|
22
|
+
}
|
|
23
|
+
const entry = {
|
|
24
|
+
kind,
|
|
25
|
+
text: trimmed,
|
|
26
|
+
effectiveFrom: options.effectiveFrom ?? today(),
|
|
27
|
+
effectiveTo: options.effectiveTo ?? null,
|
|
28
|
+
createdAt: nowSeconds(),
|
|
29
|
+
};
|
|
30
|
+
const result = getHistoryDb()
|
|
31
|
+
.prepare(`INSERT INTO context_entry (kind, text, effective_from, effective_to, created_at)
|
|
32
|
+
VALUES (?, ?, ?, ?, ?)`)
|
|
33
|
+
.run(entry.kind, entry.text, entry.effectiveFrom, entry.effectiveTo, entry.createdAt);
|
|
34
|
+
return { id: Number(result.lastInsertRowid), ...entry };
|
|
35
|
+
}
|
|
36
|
+
/** Everything true on the given date, newest first. */
|
|
37
|
+
export function activeContext(onDate) {
|
|
38
|
+
return getHistoryDb()
|
|
39
|
+
.prepare(`SELECT
|
|
40
|
+
id,
|
|
41
|
+
kind,
|
|
42
|
+
text,
|
|
43
|
+
effective_from AS effectiveFrom,
|
|
44
|
+
effective_to AS effectiveTo,
|
|
45
|
+
created_at AS createdAt
|
|
46
|
+
FROM context_entry
|
|
47
|
+
WHERE effective_from <= ?
|
|
48
|
+
AND (effective_to IS NULL OR effective_to > ?)
|
|
49
|
+
ORDER BY effective_from DESC, id DESC`)
|
|
50
|
+
.all(onDate, onDate);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Entries dated after `onDate`, soonest first.
|
|
54
|
+
*
|
|
55
|
+
* activeContext deliberately excludes these: an entry is "true on a date" only
|
|
56
|
+
* from its effective_from onwards, and a race in six weeks is not a fact about
|
|
57
|
+
* today. But a future race is the single most useful piece of context this
|
|
58
|
+
* store holds — it changes what every other number means, because the same load
|
|
59
|
+
* ratio is a warning eleven weeks out and the plan three days out — and until
|
|
60
|
+
* now nothing could read one, because the only reader excluded it by design.
|
|
61
|
+
*/
|
|
62
|
+
export function upcomingContext(onDate) {
|
|
63
|
+
return getHistoryDb()
|
|
64
|
+
.prepare(`SELECT
|
|
65
|
+
id,
|
|
66
|
+
kind,
|
|
67
|
+
text,
|
|
68
|
+
effective_from AS effectiveFrom,
|
|
69
|
+
effective_to AS effectiveTo,
|
|
70
|
+
created_at AS createdAt
|
|
71
|
+
FROM context_entry
|
|
72
|
+
WHERE effective_from > ?
|
|
73
|
+
ORDER BY effective_from ASC, id ASC`)
|
|
74
|
+
.all(onDate);
|
|
75
|
+
}
|
|
76
|
+
/** Every entry ever recorded, newest first — for a dashboard or a tool listing. */
|
|
77
|
+
export function allContext() {
|
|
78
|
+
return getHistoryDb()
|
|
79
|
+
.prepare(`SELECT
|
|
80
|
+
id,
|
|
81
|
+
kind,
|
|
82
|
+
text,
|
|
83
|
+
effective_from AS effectiveFrom,
|
|
84
|
+
effective_to AS effectiveTo,
|
|
85
|
+
created_at AS createdAt
|
|
86
|
+
FROM context_entry
|
|
87
|
+
ORDER BY effective_from DESC, id DESC`)
|
|
88
|
+
.all();
|
|
89
|
+
}
|
|
90
|
+
/** Ends an entry without deleting it. Returns false if there was no such entry. */
|
|
91
|
+
export function closeContextEntry(id, onDate = today()) {
|
|
92
|
+
const result = getHistoryDb()
|
|
93
|
+
.prepare("UPDATE context_entry SET effective_to = ? WHERE id = ?")
|
|
94
|
+
.run(onDate, id);
|
|
95
|
+
return result.changes > 0;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The 1-10 scale is what makes these comparable across days, so a 0 or a 12 is
|
|
99
|
+
* a typo rather than a reading. Storing it silently would poison every average
|
|
100
|
+
* built on top.
|
|
101
|
+
*/
|
|
102
|
+
export function logSubjective(date, kind, value, note) {
|
|
103
|
+
if (!Number.isFinite(value) || value < SUBJECTIVE_MIN || value > SUBJECTIVE_MAX) {
|
|
104
|
+
throw new Error(`A ${kind} rating must be a number between ${SUBJECTIVE_MIN} and ${SUBJECTIVE_MAX}.`);
|
|
105
|
+
}
|
|
106
|
+
getHistoryDb()
|
|
107
|
+
.prepare(`INSERT INTO subjective (date, kind, value, note, created_at)
|
|
108
|
+
VALUES (?, ?, ?, ?, ?)
|
|
109
|
+
ON CONFLICT(date, kind) DO UPDATE SET
|
|
110
|
+
value = excluded.value,
|
|
111
|
+
note = excluded.note,
|
|
112
|
+
created_at = excluded.created_at`)
|
|
113
|
+
.run(date, kind, value, note?.trim() || null, nowSeconds());
|
|
114
|
+
}
|
|
115
|
+
export function subjectiveSeries(kind, startDate, endDate) {
|
|
116
|
+
return getHistoryDb()
|
|
117
|
+
.prepare(`SELECT date, value, note FROM subjective
|
|
118
|
+
WHERE kind = ? AND date >= ? AND date <= ?
|
|
119
|
+
ORDER BY date ASC`)
|
|
120
|
+
.all(kind, startDate, endDate);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/history/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { DayFetchResult } from "../garmin/partial.js";
|
|
2
|
+
import type { ActivitySummary } from "../garmin/types.js";
|
|
3
|
+
import type { IngestSource, MetricKind } from "./schema.js";
|
|
4
|
+
/** Where the days in an answer came from. Additive; the watch ignores it. */
|
|
5
|
+
export interface StoredFallbackInfo {
|
|
6
|
+
/** How many days in the answer came from the local store, not from Garmin. */
|
|
7
|
+
storedDays: number;
|
|
8
|
+
/** Newest date the stored part covers, so a reader can say how old it is. */
|
|
9
|
+
storedThrough: string | null;
|
|
10
|
+
/**
|
|
11
|
+
* True when the days returned are NOT the days asked about.
|
|
12
|
+
*
|
|
13
|
+
* Every tool's window is anchored to today. With the record ending
|
|
14
|
+
* 2026-08-21 and Garmin unreachable on 2026-09-03, "the last 7 nights"
|
|
15
|
+
* selects seven days the store has no row for, so the fallback found nothing
|
|
16
|
+
* and the tool reported an absence -- with seventy nights in the table. The
|
|
17
|
+
* window moves to the newest days on record instead, and this flag is what
|
|
18
|
+
* forces every renderer to say so. A reader that ignored it would present
|
|
19
|
+
* a fortnight-old week as this week, which is a worse failure than the one
|
|
20
|
+
* being fixed.
|
|
21
|
+
*/
|
|
22
|
+
storedWindowMoved: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface FallbackResult<T> extends DayFetchResult<T>, StoredFallbackInfo {
|
|
25
|
+
}
|
|
26
|
+
export interface FallbackOptions<T> {
|
|
27
|
+
/** The days asked about, in the order the caller wants them back. */
|
|
28
|
+
dates: Date[];
|
|
29
|
+
source: IngestSource;
|
|
30
|
+
/** The live fetch. May reject; that is the case this exists for. */
|
|
31
|
+
live: () => Promise<DayFetchResult<T>>;
|
|
32
|
+
/** The live mapper, re-run over an archived response. */
|
|
33
|
+
fromRaw: (date: Date, payload: unknown) => T | null;
|
|
34
|
+
/** Thin reconstruction for a day older than the raw archive. */
|
|
35
|
+
fromMetrics?: (date: string, metrics: Map<MetricKind, number>) => T | null;
|
|
36
|
+
/**
|
|
37
|
+
* The date a value belongs to. Usually the day asked about; for VO2 max it is
|
|
38
|
+
* the day the measurement was actually taken, which is what collapses one
|
|
39
|
+
* reading returned for thirty requested days into one entry.
|
|
40
|
+
*/
|
|
41
|
+
dateOf: (value: T) => string;
|
|
42
|
+
}
|
|
43
|
+
/** The newest archived response for a day, or null when none was kept. */
|
|
44
|
+
export declare function storedRawPayload(date: string, source: IngestSource): unknown;
|
|
45
|
+
export declare function fetchDaysOrStore<T>(options: FallbackOptions<T>): Promise<FallbackResult<T>>;
|
|
46
|
+
/**
|
|
47
|
+
* A value that leans on the store is cached briefly, not for the full TTL.
|
|
48
|
+
*
|
|
49
|
+
* A stored answer is correct and it is also a symptom: the connection is down.
|
|
50
|
+
* Freezing it in for two hours would keep serving yesterday's record for two
|
|
51
|
+
* hours after the tunnel came back.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isPartialOrStored(result: {
|
|
54
|
+
unreachableDays: number;
|
|
55
|
+
storedDays: number;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The one sentence that keeps three different facts apart: nothing was
|
|
59
|
+
* recorded, nothing could be fetched, and this came from our own record rather
|
|
60
|
+
* than from Connect just now.
|
|
61
|
+
*
|
|
62
|
+
* The third is the one that matters. "No sleep data found for the last 7
|
|
63
|
+
* nights" is an assertion about the user's life, and it was being produced by a
|
|
64
|
+
* failed login on top of a store holding ten weeks of nights.
|
|
65
|
+
*/
|
|
66
|
+
export declare function storedFetchNote(result: DayFetchResult<unknown> & StoredFallbackInfo, noun: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Activities are not addressed by date upstream -- they are a paged list -- so
|
|
69
|
+
* they get their own reader rather than going through `fetchDaysOrStore`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function storedActivitiesBetween(startDate: string, endDate: string): ActivitySummary[];
|
|
72
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../src/history/fallback.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuC5D,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,CAAC,EAAE,kBAAkB;CAAG;AAEnF,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,qEAAqE;IACrE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;IACrB,oEAAoE;IACpE,IAAI,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,yDAAyD;IACzD,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,CAAC,GAAG,IAAI,CAAC;IACpD,gEAAgE;IAChE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC3E;;;;OAIG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;CAC9B;AAED,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAgB5E;AAyBD,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CA6F5B;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAEV;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,kBAAkB,EACpD,IAAI,EAAE,MAAM,GACX,MAAM,CA6BR;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,eAAe,EAAE,CAgBnB"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { partialFetchNote } from "../garmin/partial.js";
|
|
2
|
+
import { formatIsoDate, parseIsoDate } from "../utils/helpers.js";
|
|
3
|
+
import { logger } from "../utils/logger.js";
|
|
4
|
+
import { getActivitiesBetween, getMetricsOn, newestStoredDates, rawPayloadRevisions, } from "./store.js";
|
|
5
|
+
/** The newest archived response for a day, or null when none was kept. */
|
|
6
|
+
export function storedRawPayload(date, source) {
|
|
7
|
+
const revisions = rawPayloadRevisions(date, source);
|
|
8
|
+
const newest = revisions.at(-1);
|
|
9
|
+
if (!newest) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(newest.json);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
// An archive row that will not parse is a corrupt row, not an answer.
|
|
17
|
+
// Recorded rather than swallowed: this is the class of fault that spent a
|
|
18
|
+
// session being reported as "no public URL is configured".
|
|
19
|
+
logger.warn({ err: error, date, source }, "Archived payload could not be parsed");
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function readStoredDay(date, jsDate, options) {
|
|
24
|
+
const raw = storedRawPayload(date, options.source);
|
|
25
|
+
if (raw !== null) {
|
|
26
|
+
const mapped = options.fromRaw(jsDate, raw);
|
|
27
|
+
if (mapped !== null) {
|
|
28
|
+
return mapped;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!options.fromMetrics) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const metrics = getMetricsOn(date);
|
|
35
|
+
return metrics.size > 0 ? options.fromMetrics(date, metrics) : null;
|
|
36
|
+
}
|
|
37
|
+
function sortNewestFirst(byDate) {
|
|
38
|
+
return Array.from(byDate.entries())
|
|
39
|
+
.sort(([left], [right]) => right.localeCompare(left))
|
|
40
|
+
.map(([, value]) => value);
|
|
41
|
+
}
|
|
42
|
+
export async function fetchDaysOrStore(options) {
|
|
43
|
+
const requestedDays = options.dates.length;
|
|
44
|
+
let live;
|
|
45
|
+
let liveFailed = false;
|
|
46
|
+
try {
|
|
47
|
+
live = await options.live();
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
liveFailed = true;
|
|
51
|
+
// The whole call failed rather than a day inside it: no session, a login
|
|
52
|
+
// rate limit, no network. Every day is unreachable, which is exactly the
|
|
53
|
+
// state the store was built for. The error is not rethrown -- it used to
|
|
54
|
+
// reach the MCP client as a tool error, and a tool error is what a model
|
|
55
|
+
// reports as "I cannot access your data".
|
|
56
|
+
logger.info({ err: error, source: options.source, days: requestedDays }, "Live fetch failed; answering from the stored history");
|
|
57
|
+
live = { values: [], unreachableDays: requestedDays, requestedDays };
|
|
58
|
+
}
|
|
59
|
+
if (live.unreachableDays === 0) {
|
|
60
|
+
return { ...live, storedDays: 0, storedThrough: null, storedWindowMoved: false };
|
|
61
|
+
}
|
|
62
|
+
const byDate = new Map();
|
|
63
|
+
for (const value of live.values) {
|
|
64
|
+
byDate.set(options.dateOf(value), value);
|
|
65
|
+
}
|
|
66
|
+
const storedDates = [];
|
|
67
|
+
for (const jsDate of options.dates) {
|
|
68
|
+
const date = formatIsoDate(jsDate);
|
|
69
|
+
if (byDate.has(date)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const stored = readStoredDay(date, jsDate, options);
|
|
73
|
+
if (stored === null) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Keyed by the value's own date rather than by the date asked about, so a
|
|
77
|
+
// reading that answers for many requested days lands once.
|
|
78
|
+
const key = options.dateOf(stored);
|
|
79
|
+
if (byDate.has(key)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
byDate.set(key, stored);
|
|
83
|
+
storedDates.push(key);
|
|
84
|
+
}
|
|
85
|
+
let values = sortNewestFirst(byDate);
|
|
86
|
+
// Nothing anywhere for the days asked about, and the live call is down: the
|
|
87
|
+
// window is pointing at days that were never recorded. Move it to the newest
|
|
88
|
+
// days the store does hold rather than reporting an absence.
|
|
89
|
+
let storedWindowMoved = false;
|
|
90
|
+
if (values.length === 0 && liveFailed) {
|
|
91
|
+
for (const date of newestStoredDates(options.source, requestedDays)) {
|
|
92
|
+
const stored = readStoredDay(date, parseIsoDate(date), options);
|
|
93
|
+
if (stored === null) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const key = options.dateOf(stored);
|
|
97
|
+
if (byDate.has(key)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
byDate.set(key, stored);
|
|
101
|
+
storedDates.push(key);
|
|
102
|
+
storedWindowMoved = true;
|
|
103
|
+
}
|
|
104
|
+
values = sortNewestFirst(byDate);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
values,
|
|
108
|
+
// Days the store could not answer either. A day covered from the archive is
|
|
109
|
+
// no longer unreachable, and reporting it as such would put the sentence
|
|
110
|
+
// "could not be fetched" next to the figures it just fetched. A moved
|
|
111
|
+
// window answers none of the days asked about, so all of them stay counted.
|
|
112
|
+
unreachableDays: storedWindowMoved
|
|
113
|
+
? live.unreachableDays
|
|
114
|
+
: Math.max(0, live.unreachableDays - storedDates.length),
|
|
115
|
+
requestedDays,
|
|
116
|
+
storedDays: storedDates.length,
|
|
117
|
+
storedThrough: storedDates.length > 0 ? (storedDates.slice().sort().at(-1) ?? null) : null,
|
|
118
|
+
storedWindowMoved,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A value that leans on the store is cached briefly, not for the full TTL.
|
|
123
|
+
*
|
|
124
|
+
* A stored answer is correct and it is also a symptom: the connection is down.
|
|
125
|
+
* Freezing it in for two hours would keep serving yesterday's record for two
|
|
126
|
+
* hours after the tunnel came back.
|
|
127
|
+
*/
|
|
128
|
+
export function isPartialOrStored(result) {
|
|
129
|
+
return result.unreachableDays > 0 || result.storedDays > 0;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The one sentence that keeps three different facts apart: nothing was
|
|
133
|
+
* recorded, nothing could be fetched, and this came from our own record rather
|
|
134
|
+
* than from Connect just now.
|
|
135
|
+
*
|
|
136
|
+
* The third is the one that matters. "No sleep data found for the last 7
|
|
137
|
+
* nights" is an assertion about the user's life, and it was being produced by a
|
|
138
|
+
* failed login on top of a store holding ten weeks of nights.
|
|
139
|
+
*/
|
|
140
|
+
export function storedFetchNote(result, noun) {
|
|
141
|
+
if (result.storedDays === 0) {
|
|
142
|
+
return partialFetchNote(result, noun);
|
|
143
|
+
}
|
|
144
|
+
const through = result.storedThrough ?? "an earlier date";
|
|
145
|
+
const liveDays = result.values.length - result.storedDays;
|
|
146
|
+
// The window moved. This has to be unmissable: the dates below are NOT the
|
|
147
|
+
// dates asked about, and a reader that assumed otherwise would describe a
|
|
148
|
+
// fortnight-old week as this week.
|
|
149
|
+
if (result.storedWindowMoved) {
|
|
150
|
+
return [
|
|
151
|
+
`Garmin could not be reached, and TrainBud's record holds nothing at all for the last ${result.requestedDays} ${noun}.`,
|
|
152
|
+
`THESE ARE NOT THE LAST ${result.requestedDays} ${noun.toUpperCase()}. They are the ${result.storedDays} most recent ${noun} on record, ending ${through}.`,
|
|
153
|
+
`Every date is given below; use them. Say which period this describes, and that the record stops at ${through} because the connection is down — not because there is no data about this user.`,
|
|
154
|
+
].join(" ");
|
|
155
|
+
}
|
|
156
|
+
const stored = liveDays > 0
|
|
157
|
+
? `Note: Garmin could not be reached for ${result.storedDays} of ${result.requestedDays} ${noun}, so those came from TrainBud's own stored history (through ${through}). They are measurements previously fetched, not estimates.`
|
|
158
|
+
: `Garmin could not be reached just now, so these ${result.storedDays} ${noun} come from TrainBud's own stored history, through ${through}. These are real measurements previously fetched from Connect — this is a connection problem, not an absence of data about this user.`;
|
|
159
|
+
if (result.unreachableDays === 0) {
|
|
160
|
+
return stored;
|
|
161
|
+
}
|
|
162
|
+
return `${stored} ${result.unreachableDays} of the ${result.requestedDays} ${noun} asked about are in neither place.`;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Activities are not addressed by date upstream -- they are a paged list -- so
|
|
166
|
+
* they get their own reader rather than going through `fetchDaysOrStore`.
|
|
167
|
+
*/
|
|
168
|
+
export function storedActivitiesBetween(startDate, endDate) {
|
|
169
|
+
return getActivitiesBetween(startDate, endDate)
|
|
170
|
+
.map((row) => ({
|
|
171
|
+
activityId: row.activityId,
|
|
172
|
+
name: row.name,
|
|
173
|
+
type: row.type,
|
|
174
|
+
startTimeLocal: row.startTimeLocal,
|
|
175
|
+
distanceMeters: row.distanceMeters,
|
|
176
|
+
durationSeconds: row.durationSeconds,
|
|
177
|
+
averageHeartRate: row.avgHr,
|
|
178
|
+
maxHeartRate: row.maxHr,
|
|
179
|
+
elevationGainMeters: row.elevationGainMeters,
|
|
180
|
+
calories: row.calories,
|
|
181
|
+
averageSpeedMps: row.averageSpeedMps,
|
|
182
|
+
}))
|
|
183
|
+
.sort((left, right) => right.startTimeLocal.localeCompare(left.startTimeLocal));
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=fallback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.js","sourceRoot":"","sources":["../../src/history/fallback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import type { GarminConnectInstance } from "../garmin/garminConnect.js";
|
|
3
|
+
import type { IngestOutcome, IngestSource } from "./schema.js";
|
|
4
|
+
/** The sources a full run covers. Exported so no caller keeps its own copy. */
|
|
5
|
+
export declare const DEFAULT_SOURCES: IngestSource[];
|
|
6
|
+
export interface IngestOptions {
|
|
7
|
+
days?: number;
|
|
8
|
+
delayMs?: number;
|
|
9
|
+
sources?: IngestSource[];
|
|
10
|
+
now?: DateTime;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
onProgress?: (progress: IngestProgress) => void;
|
|
13
|
+
/** Write each redacted response here, for use as a test fixture. */
|
|
14
|
+
captureDir?: string;
|
|
15
|
+
/** Abandon a source after this many consecutive empty days. 0 disables. */
|
|
16
|
+
stopAfterEmptyDays?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface IngestProgress {
|
|
19
|
+
date: string;
|
|
20
|
+
source: IngestSource;
|
|
21
|
+
outcome: IngestOutcome;
|
|
22
|
+
done: number;
|
|
23
|
+
total: number;
|
|
24
|
+
}
|
|
25
|
+
export interface IngestResult {
|
|
26
|
+
fetched: number;
|
|
27
|
+
skipped: number;
|
|
28
|
+
errors: number;
|
|
29
|
+
firstDate: string | null;
|
|
30
|
+
lastDate: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Set when the run stopped early because the upstream asked it to. The days
|
|
33
|
+
* not reached are counted in `skipped` and are deliberately not checkpointed,
|
|
34
|
+
* so the next run picks them up.
|
|
35
|
+
*/
|
|
36
|
+
stoppedBy?: "rate_limit";
|
|
37
|
+
}
|
|
38
|
+
export interface IngestUnit {
|
|
39
|
+
date: string;
|
|
40
|
+
source: IngestSource;
|
|
41
|
+
}
|
|
42
|
+
export declare function pendingWork(options?: IngestOptions): IngestUnit[];
|
|
43
|
+
export declare function runIngest(client: GarminConnectInstance, options?: IngestOptions): Promise<IngestResult>;
|
|
44
|
+
//# sourceMappingURL=ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/history/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAQjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAexE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AA8B3E,+EAA+E;AAC/E,eAAO,MAAM,eAAe,EAAE,YAAY,EAOzC,CAAC;AAQF,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IAChD,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;CAC1B;AAcD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;CACtB;AAuDD,wBAAgB,WAAW,CAAC,OAAO,GAAE,aAAkB,GAAG,UAAU,EAAE,CAqCrE;AAkJD,wBAAsB,SAAS,CAC7B,MAAM,EAAE,qBAAqB,EAC7B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAuJvB"}
|