forge-openclaw-plugin 0.2.67 → 0.2.69
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/dist/assets/{board-DFNV9VAZ.js → board-BfqxFNiQ.js} +1 -1
- package/dist/assets/index-BfLQnCNZ.js +91 -0
- package/dist/assets/index-DIapFz9v.css +1 -0
- package/dist/assets/{motion-CXdn34ih.js → motion-C0ALlgho.js} +1 -1
- package/dist/assets/{table-CEq3bTDv.js → table-WcMjnJll.js} +1 -1
- package/dist/assets/{ui-g7FaEglG.js → ui-B5I-3U91.js} +1 -1
- package/dist/assets/vendor-B-Lq_OG3.css +1 -0
- package/dist/assets/vendor-C56o26_3.js +2163 -0
- package/dist/index.html +8 -8
- package/dist/server/server/migrations/060_psyche_devrage_metrics.sql +40 -0
- package/dist/server/server/migrations/061_health_workout_raw_evidence.sql +95 -0
- package/dist/server/server/src/app.js +94 -12
- package/dist/server/server/src/health-workout-analytics.js +572 -0
- package/dist/server/server/src/health.js +116 -3
- package/dist/server/server/src/openapi.js +238 -0
- package/dist/server/server/src/psyche-types.js +90 -0
- package/dist/server/server/src/services/devrage.js +412 -0
- package/dist/server/server/src/services/psyche.js +3 -0
- package/dist/server/src/lib/api.js +13 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server/migrations/060_psyche_devrage_metrics.sql +40 -0
- package/server/migrations/061_health_workout_raw_evidence.sql +95 -0
- package/skills/forge-openclaw/SKILL.md +25 -1
- package/skills/forge-openclaw/entity_conversation_playbooks.md +46 -14
- package/dist/assets/index-B9IVt8VN.js +0 -90
- package/dist/assets/index-DJlo9Tsp.css +0 -1
- package/dist/assets/vendor-BcOHGipZ.js +0 -1341
- package/dist/assets/vendor-DT3pnAKJ.css +0 -1
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { getDatabase, runInTransaction } from "./db.js";
|
|
4
4
|
import { HttpError } from "./errors.js";
|
|
5
5
|
import { buildWorkoutSessionPersistenceSeed, buildWorkoutSessionPresentation, workoutActivityDescriptorSchema, workoutDetailsSchema } from "./health-workout-adapters.js";
|
|
6
|
+
import { getHealthZoneProfile, getStoredWorkoutAnalytics, getWorkoutRawEvidence, healthZoneProfilePatchSchema, recomputeAndStoreWorkoutAnalytics, upsertWorkoutRoutePoints, upsertWorkoutTimeSeries, workoutCaptureQualitySchema, workoutRoutePointSchema, workoutTimeSeriesSampleSchema, patchHealthZoneProfile } from "./health-workout-analytics.js";
|
|
6
7
|
import { getMovementMobileBootstrap, ingestMovementSync, movementSyncPayloadSchema } from "./movement.js";
|
|
7
8
|
import { ingestScreenTimeSync, screenTimeSyncPayloadSchema } from "./screen-time.js";
|
|
8
9
|
import { recordActivityEvent } from "./repositories/activity-events.js";
|
|
@@ -245,6 +246,10 @@ export const mobileHealthSyncSchema = z.object({
|
|
|
245
246
|
sourceProductType: z.string().trim().optional(),
|
|
246
247
|
activity: workoutActivityDescriptorSchema.optional(),
|
|
247
248
|
details: workoutDetailsSchema.optional(),
|
|
249
|
+
timeSeriesSamples: z.array(workoutTimeSeriesSampleSchema).default([]),
|
|
250
|
+
routePoints: z.array(workoutRoutePointSchema).default([]),
|
|
251
|
+
captureQuality: workoutCaptureQualitySchema.optional(),
|
|
252
|
+
syncCursor: z.record(z.string(), z.unknown()).default({}),
|
|
248
253
|
links: z.array(healthLinkSchema).default([]),
|
|
249
254
|
annotations: workoutAnnotationSchema.partial().default({})
|
|
250
255
|
}))
|
|
@@ -788,6 +793,7 @@ function mapSleepRawLog(row) {
|
|
|
788
793
|
function mapWorkoutSession(row) {
|
|
789
794
|
const provenance = safeJsonParse(row.provenance_json, {});
|
|
790
795
|
const derived = safeJsonParse(row.derived_json, {});
|
|
796
|
+
const analytics = getStoredWorkoutAnalytics(row);
|
|
791
797
|
const presentation = buildWorkoutSessionPresentation({
|
|
792
798
|
source: row.source,
|
|
793
799
|
sourceType: row.source_type,
|
|
@@ -833,6 +839,7 @@ function mapWorkoutSession(row) {
|
|
|
833
839
|
annotations: safeJsonParse(row.annotations_json, {}),
|
|
834
840
|
provenance,
|
|
835
841
|
derived,
|
|
842
|
+
analytics,
|
|
836
843
|
generatedFromHabitId: row.generated_from_habit_id,
|
|
837
844
|
generatedFromCheckInId: row.generated_from_check_in_id,
|
|
838
845
|
reconciliationStatus: row.reconciliation_status,
|
|
@@ -1352,6 +1359,28 @@ export function getWorkoutSessionById(workoutId) {
|
|
|
1352
1359
|
.get(workoutId);
|
|
1353
1360
|
return row ? mapWorkoutSession(row) : undefined;
|
|
1354
1361
|
}
|
|
1362
|
+
export function getWorkoutSessionDetailById(workoutId, resolution = "adaptive") {
|
|
1363
|
+
const row = getDatabase()
|
|
1364
|
+
.prepare(`SELECT * FROM health_workout_sessions WHERE id = ?`)
|
|
1365
|
+
.get(workoutId);
|
|
1366
|
+
if (!row) {
|
|
1367
|
+
return undefined;
|
|
1368
|
+
}
|
|
1369
|
+
const workout = mapWorkoutSession(row);
|
|
1370
|
+
return {
|
|
1371
|
+
workout,
|
|
1372
|
+
analytics: getStoredWorkoutAnalytics(row),
|
|
1373
|
+
evidence: getWorkoutRawEvidence(row, resolution),
|
|
1374
|
+
zoneProfile: getHealthZoneProfile(row.user_id)
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
export { healthZoneProfilePatchSchema };
|
|
1378
|
+
export function getHealthZoneProfileForUser(userId) {
|
|
1379
|
+
return getHealthZoneProfile(userId);
|
|
1380
|
+
}
|
|
1381
|
+
export function patchHealthZoneProfileForUser(userId, patch) {
|
|
1382
|
+
return patchHealthZoneProfile(userId, patch);
|
|
1383
|
+
}
|
|
1355
1384
|
function listPairingRows(userIds) {
|
|
1356
1385
|
const params = [];
|
|
1357
1386
|
const where = userIds && userIds.length > 0
|
|
@@ -2072,6 +2101,7 @@ function insertOrUpdateWorkoutSession(pairing, input) {
|
|
|
2072
2101
|
? Number((input.distanceMeters / input.exerciseMinutes).toFixed(2))
|
|
2073
2102
|
: null
|
|
2074
2103
|
}), matchedGenerated.generated_from_habit_id ? "merged" : "standalone", now, matchedGenerated.id);
|
|
2104
|
+
persistWorkoutEvidenceForInput(matchedGenerated.id, pairing.user_id, input);
|
|
2075
2105
|
return {
|
|
2076
2106
|
mode: matchedGenerated.generated_from_habit_id || matchedGenerated.source !== "apple_health"
|
|
2077
2107
|
? "merged"
|
|
@@ -2105,8 +2135,43 @@ function insertOrUpdateWorkoutSession(pairing, input) {
|
|
|
2105
2135
|
? Number((input.distanceMeters / input.exerciseMinutes).toFixed(2))
|
|
2106
2136
|
: null
|
|
2107
2137
|
}), now, now);
|
|
2138
|
+
persistWorkoutEvidenceForInput(id, pairing.user_id, input);
|
|
2108
2139
|
return { mode: "created", id };
|
|
2109
2140
|
}
|
|
2141
|
+
function persistWorkoutEvidenceForInput(workoutId, userId, input) {
|
|
2142
|
+
if (input.timeSeriesSamples.length > 0) {
|
|
2143
|
+
upsertWorkoutTimeSeries({
|
|
2144
|
+
workoutId,
|
|
2145
|
+
userId,
|
|
2146
|
+
samples: input.timeSeriesSamples
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
if (input.routePoints.length > 0) {
|
|
2150
|
+
upsertWorkoutRoutePoints({
|
|
2151
|
+
workoutId,
|
|
2152
|
+
userId,
|
|
2153
|
+
points: input.routePoints
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
const row = getDatabase()
|
|
2157
|
+
.prepare(`SELECT * FROM health_workout_sessions WHERE id = ?`)
|
|
2158
|
+
.get(workoutId);
|
|
2159
|
+
if (row) {
|
|
2160
|
+
recomputeAndStoreWorkoutAnalytics(row);
|
|
2161
|
+
if (input.captureQuality) {
|
|
2162
|
+
const derived = safeJsonParse(row.derived_json, {});
|
|
2163
|
+
getDatabase()
|
|
2164
|
+
.prepare(`UPDATE health_workout_sessions
|
|
2165
|
+
SET derived_json = ?, updated_at = ?
|
|
2166
|
+
WHERE id = ?`)
|
|
2167
|
+
.run(JSON.stringify({
|
|
2168
|
+
...derived,
|
|
2169
|
+
captureQuality: input.captureQuality,
|
|
2170
|
+
syncCursor: input.syncCursor
|
|
2171
|
+
}), nowIso(), workoutId);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2110
2175
|
function clusterSleepRowsByGap(rows) {
|
|
2111
2176
|
const clusters = [];
|
|
2112
2177
|
let currentCluster = [];
|
|
@@ -2920,6 +2985,42 @@ export function getFitnessViewData(userIds) {
|
|
|
2920
2985
|
workoutTypeBreakdown.set(session.workoutType, current);
|
|
2921
2986
|
}
|
|
2922
2987
|
const orderedWorkoutTypes = [...workoutTypeBreakdown.entries()].sort((left, right) => right[1].totalMinutes - left[1].totalMinutes);
|
|
2988
|
+
const zoneTotals = new Map();
|
|
2989
|
+
let totalZoneSeconds = 0;
|
|
2990
|
+
let heartRateCoverageSum = 0;
|
|
2991
|
+
let heartRateCoverageCount = 0;
|
|
2992
|
+
let totalTrainingLoad = 0;
|
|
2993
|
+
let routeWorkoutCount = 0;
|
|
2994
|
+
for (const session of recent) {
|
|
2995
|
+
const analytics = session.analytics;
|
|
2996
|
+
for (const zone of analytics?.zoneDurations ?? []) {
|
|
2997
|
+
const current = zoneTotals.get(zone.key) ?? {
|
|
2998
|
+
label: zone.label,
|
|
2999
|
+
seconds: 0
|
|
3000
|
+
};
|
|
3001
|
+
current.seconds += zone.seconds;
|
|
3002
|
+
totalZoneSeconds += zone.seconds;
|
|
3003
|
+
zoneTotals.set(zone.key, current);
|
|
3004
|
+
}
|
|
3005
|
+
if (typeof analytics?.dataQuality?.sampleCoverage === "number") {
|
|
3006
|
+
heartRateCoverageSum += analytics.dataQuality.sampleCoverage;
|
|
3007
|
+
heartRateCoverageCount += 1;
|
|
3008
|
+
}
|
|
3009
|
+
if (typeof analytics?.load?.trimp === "number") {
|
|
3010
|
+
totalTrainingLoad += analytics.load.trimp;
|
|
3011
|
+
}
|
|
3012
|
+
if (analytics?.routeSummary?.hasRoute) {
|
|
3013
|
+
routeWorkoutCount += 1;
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
const zoneMix = [...zoneTotals.entries()].map(([key, value]) => ({
|
|
3017
|
+
key,
|
|
3018
|
+
label: value.label,
|
|
3019
|
+
seconds: Math.round(value.seconds),
|
|
3020
|
+
percentage: totalZoneSeconds > 0
|
|
3021
|
+
? Number((value.seconds / totalZoneSeconds).toFixed(4))
|
|
3022
|
+
: 0
|
|
3023
|
+
}));
|
|
2923
3024
|
return {
|
|
2924
3025
|
summary: {
|
|
2925
3026
|
workoutCount: weekly.length,
|
|
@@ -2940,7 +3041,13 @@ export function getFitnessViewData(userIds) {
|
|
|
2940
3041
|
topWorkoutType: orderedWorkoutTypes[0]?.[0] ?? null,
|
|
2941
3042
|
topWorkoutTypeLabel: recent.find((session) => session.workoutType === orderedWorkoutTypes[0]?.[0])
|
|
2942
3043
|
?.workoutTypeLabel ?? null,
|
|
2943
|
-
streakDays: Array.from(new Set(weekly.map((session) => dayKey(session.startedAt)))).length
|
|
3044
|
+
streakDays: Array.from(new Set(weekly.map((session) => dayKey(session.startedAt)))).length,
|
|
3045
|
+
averageHeartRateCoverage: heartRateCoverageCount > 0
|
|
3046
|
+
? Number((heartRateCoverageSum / heartRateCoverageCount).toFixed(3))
|
|
3047
|
+
: 0,
|
|
3048
|
+
totalTrainingLoad: Number(totalTrainingLoad.toFixed(1)),
|
|
3049
|
+
routeWorkoutCount,
|
|
3050
|
+
zoneMix
|
|
2944
3051
|
},
|
|
2945
3052
|
weeklyTrend: weekly
|
|
2946
3053
|
.map((session) => ({
|
|
@@ -2951,7 +3058,12 @@ export function getFitnessViewData(userIds) {
|
|
|
2951
3058
|
activityFamily: session.activityFamily,
|
|
2952
3059
|
activityFamilyLabel: session.activityFamilyLabel,
|
|
2953
3060
|
durationMinutes: Math.round(session.durationSeconds / 60),
|
|
2954
|
-
energyKcal: Math.round(session.totalEnergyKcal ?? session.activeEnergyKcal ?? 0)
|
|
3061
|
+
energyKcal: Math.round(session.totalEnergyKcal ?? session.activeEnergyKcal ?? 0),
|
|
3062
|
+
zoneDurations: session.analytics
|
|
3063
|
+
?.zoneDurations ?? [],
|
|
3064
|
+
trainingLoad: (session.analytics
|
|
3065
|
+
?.load?.trimp ?? null),
|
|
3066
|
+
heartRateCoverage: (session.analytics?.dataQuality?.sampleCoverage ?? 0)
|
|
2955
3067
|
}))
|
|
2956
3068
|
.reverse(),
|
|
2957
3069
|
typeBreakdown: orderedWorkoutTypes.map(([workoutType, metrics]) => ({
|
|
@@ -3482,6 +3594,7 @@ export function deleteWorkoutSession(workoutId, activity) {
|
|
|
3482
3594
|
if (!current) {
|
|
3483
3595
|
return undefined;
|
|
3484
3596
|
}
|
|
3597
|
+
const deletedWorkout = mapWorkoutSession(current);
|
|
3485
3598
|
getDatabase()
|
|
3486
3599
|
.prepare(`DELETE FROM health_workout_sessions WHERE id = ?`)
|
|
3487
3600
|
.run(workoutId);
|
|
@@ -3499,7 +3612,7 @@ export function deleteWorkoutSession(workoutId, activity) {
|
|
|
3499
3612
|
startedAt: current.started_at
|
|
3500
3613
|
}
|
|
3501
3614
|
});
|
|
3502
|
-
return
|
|
3615
|
+
return deletedWorkout;
|
|
3503
3616
|
}
|
|
3504
3617
|
export function updateWorkoutMetadata(workoutId, patch, activity) {
|
|
3505
3618
|
const parsed = updateWorkoutMetadataSchema.parse(patch);
|
|
@@ -4645,6 +4645,224 @@ export function buildOpenApiDocument() {
|
|
|
4645
4645
|
updatedAt: { type: "string", format: "date-time" }
|
|
4646
4646
|
}
|
|
4647
4647
|
};
|
|
4648
|
+
const devrageMetricPayload = {
|
|
4649
|
+
type: "object",
|
|
4650
|
+
additionalProperties: false,
|
|
4651
|
+
required: [
|
|
4652
|
+
"generatedAt",
|
|
4653
|
+
"hasData",
|
|
4654
|
+
"latestDateKey",
|
|
4655
|
+
"rawSwearCount",
|
|
4656
|
+
"swearingMessagePercent",
|
|
4657
|
+
"conversationsScanned",
|
|
4658
|
+
"messagesScanned",
|
|
4659
|
+
"messagesWithSwears",
|
|
4660
|
+
"dailyAverage",
|
|
4661
|
+
"weeklyAverage",
|
|
4662
|
+
"history",
|
|
4663
|
+
"sync"
|
|
4664
|
+
],
|
|
4665
|
+
properties: {
|
|
4666
|
+
generatedAt: { type: "string", format: "date-time" },
|
|
4667
|
+
hasData: { type: "boolean" },
|
|
4668
|
+
latestDateKey: nullable({ type: "string" }),
|
|
4669
|
+
rawSwearCount: { type: "number" },
|
|
4670
|
+
swearingMessagePercent: { type: "number" },
|
|
4671
|
+
conversationsScanned: { type: "integer" },
|
|
4672
|
+
messagesScanned: { type: "integer" },
|
|
4673
|
+
messagesWithSwears: { type: "integer" },
|
|
4674
|
+
dailyAverage: {
|
|
4675
|
+
type: "object",
|
|
4676
|
+
additionalProperties: false,
|
|
4677
|
+
required: ["rawSwearCount", "swearingMessagePercent"],
|
|
4678
|
+
properties: {
|
|
4679
|
+
rawSwearCount: { type: "number" },
|
|
4680
|
+
swearingMessagePercent: { type: "number" }
|
|
4681
|
+
}
|
|
4682
|
+
},
|
|
4683
|
+
weeklyAverage: {
|
|
4684
|
+
type: "object",
|
|
4685
|
+
additionalProperties: false,
|
|
4686
|
+
required: ["rawSwearCount", "swearingMessagePercent"],
|
|
4687
|
+
properties: {
|
|
4688
|
+
rawSwearCount: { type: "number" },
|
|
4689
|
+
swearingMessagePercent: { type: "number" }
|
|
4690
|
+
}
|
|
4691
|
+
},
|
|
4692
|
+
history: arrayOf({
|
|
4693
|
+
type: "object",
|
|
4694
|
+
additionalProperties: false,
|
|
4695
|
+
required: [
|
|
4696
|
+
"dateKey",
|
|
4697
|
+
"rawSwearCount",
|
|
4698
|
+
"swearingMessagePercent",
|
|
4699
|
+
"conversationsScanned",
|
|
4700
|
+
"messagesScanned",
|
|
4701
|
+
"messagesWithSwears"
|
|
4702
|
+
],
|
|
4703
|
+
properties: {
|
|
4704
|
+
dateKey: { type: "string" },
|
|
4705
|
+
rawSwearCount: { type: "number" },
|
|
4706
|
+
swearingMessagePercent: { type: "number" },
|
|
4707
|
+
conversationsScanned: { type: "integer" },
|
|
4708
|
+
messagesScanned: { type: "integer" },
|
|
4709
|
+
messagesWithSwears: { type: "integer" }
|
|
4710
|
+
}
|
|
4711
|
+
}),
|
|
4712
|
+
sync: {
|
|
4713
|
+
type: "object",
|
|
4714
|
+
additionalProperties: false,
|
|
4715
|
+
required: ["fullSyncCompletedAt", "lastDailySyncAt", "lastSyncedDateKey"],
|
|
4716
|
+
properties: {
|
|
4717
|
+
fullSyncCompletedAt: nullable({ type: "string", format: "date-time" }),
|
|
4718
|
+
lastDailySyncAt: nullable({ type: "string", format: "date-time" }),
|
|
4719
|
+
lastSyncedDateKey: nullable({ type: "string" })
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
};
|
|
4724
|
+
const dailyMetricDayRecord = {
|
|
4725
|
+
type: "object",
|
|
4726
|
+
additionalProperties: false,
|
|
4727
|
+
required: [
|
|
4728
|
+
"dateKey",
|
|
4729
|
+
"average",
|
|
4730
|
+
"minimum",
|
|
4731
|
+
"maximum",
|
|
4732
|
+
"latest",
|
|
4733
|
+
"total",
|
|
4734
|
+
"sampleCount",
|
|
4735
|
+
"latestSampleAt"
|
|
4736
|
+
],
|
|
4737
|
+
properties: {
|
|
4738
|
+
dateKey: { type: "string" },
|
|
4739
|
+
average: nullable({ type: "number" }),
|
|
4740
|
+
minimum: nullable({ type: "number" }),
|
|
4741
|
+
maximum: nullable({ type: "number" }),
|
|
4742
|
+
latest: nullable({ type: "number" }),
|
|
4743
|
+
total: nullable({ type: "number" }),
|
|
4744
|
+
sampleCount: { type: "integer" },
|
|
4745
|
+
latestSampleAt: nullable({ type: "string", format: "date-time" })
|
|
4746
|
+
}
|
|
4747
|
+
};
|
|
4748
|
+
const dailyMetricRecord = {
|
|
4749
|
+
type: "object",
|
|
4750
|
+
additionalProperties: false,
|
|
4751
|
+
required: [
|
|
4752
|
+
"metric",
|
|
4753
|
+
"label",
|
|
4754
|
+
"category",
|
|
4755
|
+
"unit",
|
|
4756
|
+
"aggregation",
|
|
4757
|
+
"latestValue",
|
|
4758
|
+
"latestDateKey",
|
|
4759
|
+
"baselineValue",
|
|
4760
|
+
"deltaValue",
|
|
4761
|
+
"coverageDays",
|
|
4762
|
+
"days"
|
|
4763
|
+
],
|
|
4764
|
+
properties: {
|
|
4765
|
+
metric: { type: "string" },
|
|
4766
|
+
label: { type: "string" },
|
|
4767
|
+
category: { type: "string" },
|
|
4768
|
+
unit: { type: "string" },
|
|
4769
|
+
aggregation: { type: "string", enum: ["discrete", "cumulative"] },
|
|
4770
|
+
latestValue: nullable({ type: "number" }),
|
|
4771
|
+
latestDateKey: nullable({ type: "string" }),
|
|
4772
|
+
baselineValue: nullable({ type: "number" }),
|
|
4773
|
+
deltaValue: nullable({ type: "number" }),
|
|
4774
|
+
coverageDays: { type: "integer" },
|
|
4775
|
+
days: arrayOf(dailyMetricDayRecord)
|
|
4776
|
+
}
|
|
4777
|
+
};
|
|
4778
|
+
const psycheMetricsViewData = {
|
|
4779
|
+
type: "object",
|
|
4780
|
+
additionalProperties: false,
|
|
4781
|
+
required: ["summary", "context", "metrics"],
|
|
4782
|
+
properties: {
|
|
4783
|
+
summary: {
|
|
4784
|
+
type: "object",
|
|
4785
|
+
additionalProperties: false,
|
|
4786
|
+
required: [
|
|
4787
|
+
"hasData",
|
|
4788
|
+
"trackedDays",
|
|
4789
|
+
"metricCount",
|
|
4790
|
+
"latestDateKey",
|
|
4791
|
+
"latestMetricCount",
|
|
4792
|
+
"categoryBreakdown"
|
|
4793
|
+
],
|
|
4794
|
+
properties: {
|
|
4795
|
+
hasData: { type: "boolean" },
|
|
4796
|
+
trackedDays: { type: "integer" },
|
|
4797
|
+
metricCount: { type: "integer" },
|
|
4798
|
+
latestDateKey: nullable({ type: "string" }),
|
|
4799
|
+
latestMetricCount: { type: "integer" },
|
|
4800
|
+
categoryBreakdown: arrayOf({
|
|
4801
|
+
type: "object",
|
|
4802
|
+
additionalProperties: false,
|
|
4803
|
+
required: ["category", "metricCount", "coverageDays"],
|
|
4804
|
+
properties: {
|
|
4805
|
+
category: { type: "string" },
|
|
4806
|
+
metricCount: { type: "integer" },
|
|
4807
|
+
coverageDays: { type: "integer" }
|
|
4808
|
+
}
|
|
4809
|
+
})
|
|
4810
|
+
}
|
|
4811
|
+
},
|
|
4812
|
+
context: {
|
|
4813
|
+
type: "object",
|
|
4814
|
+
additionalProperties: false,
|
|
4815
|
+
required: [
|
|
4816
|
+
"generatedAt",
|
|
4817
|
+
"conversationsScanned",
|
|
4818
|
+
"sourceCount",
|
|
4819
|
+
"messagesScanned",
|
|
4820
|
+
"messagesWithSwears",
|
|
4821
|
+
"totalSwears",
|
|
4822
|
+
"dailyAverage",
|
|
4823
|
+
"weeklyAverage",
|
|
4824
|
+
"sync"
|
|
4825
|
+
],
|
|
4826
|
+
properties: {
|
|
4827
|
+
generatedAt: { type: "string", format: "date-time" },
|
|
4828
|
+
conversationsScanned: { type: "integer" },
|
|
4829
|
+
sourceCount: { type: "integer" },
|
|
4830
|
+
messagesScanned: { type: "integer" },
|
|
4831
|
+
messagesWithSwears: { type: "integer" },
|
|
4832
|
+
totalSwears: { type: "number" },
|
|
4833
|
+
dailyAverage: {
|
|
4834
|
+
type: "object",
|
|
4835
|
+
additionalProperties: false,
|
|
4836
|
+
required: ["rawSwearCount", "swearingMessagePercent"],
|
|
4837
|
+
properties: {
|
|
4838
|
+
rawSwearCount: { type: "number" },
|
|
4839
|
+
swearingMessagePercent: { type: "number" }
|
|
4840
|
+
}
|
|
4841
|
+
},
|
|
4842
|
+
weeklyAverage: {
|
|
4843
|
+
type: "object",
|
|
4844
|
+
additionalProperties: false,
|
|
4845
|
+
required: ["rawSwearCount", "swearingMessagePercent"],
|
|
4846
|
+
properties: {
|
|
4847
|
+
rawSwearCount: { type: "number" },
|
|
4848
|
+
swearingMessagePercent: { type: "number" }
|
|
4849
|
+
}
|
|
4850
|
+
},
|
|
4851
|
+
sync: {
|
|
4852
|
+
type: "object",
|
|
4853
|
+
additionalProperties: false,
|
|
4854
|
+
required: ["fullSyncCompletedAt", "lastDailySyncAt", "lastSyncedDateKey"],
|
|
4855
|
+
properties: {
|
|
4856
|
+
fullSyncCompletedAt: nullable({ type: "string", format: "date-time" }),
|
|
4857
|
+
lastDailySyncAt: nullable({ type: "string", format: "date-time" }),
|
|
4858
|
+
lastSyncedDateKey: nullable({ type: "string" })
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
},
|
|
4863
|
+
metrics: arrayOf(dailyMetricRecord)
|
|
4864
|
+
}
|
|
4865
|
+
};
|
|
4648
4866
|
const psycheOverviewPayload = {
|
|
4649
4867
|
type: "object",
|
|
4650
4868
|
additionalProperties: false,
|
|
@@ -4657,6 +4875,7 @@ export function buildOpenApiDocument() {
|
|
|
4657
4875
|
"beliefs",
|
|
4658
4876
|
"modes",
|
|
4659
4877
|
"schemaPressure",
|
|
4878
|
+
"devrageMetric",
|
|
4660
4879
|
"reports",
|
|
4661
4880
|
"openInsights",
|
|
4662
4881
|
"openNotes",
|
|
@@ -4680,6 +4899,7 @@ export function buildOpenApiDocument() {
|
|
|
4680
4899
|
activationCount: { type: "integer" }
|
|
4681
4900
|
}
|
|
4682
4901
|
}),
|
|
4902
|
+
devrageMetric: devrageMetricPayload,
|
|
4683
4903
|
reports: arrayOf({ $ref: "#/components/schemas/TriggerReport" }),
|
|
4684
4904
|
openInsights: { type: "integer" },
|
|
4685
4905
|
openNotes: { type: "integer" },
|
|
@@ -4992,6 +5212,7 @@ export function buildOpenApiDocument() {
|
|
|
4992
5212
|
WorkoutSession: workoutSession,
|
|
4993
5213
|
SleepViewData: sleepViewData,
|
|
4994
5214
|
FitnessViewData: fitnessViewData,
|
|
5215
|
+
PsycheMetricsViewData: psycheMetricsViewData,
|
|
4995
5216
|
PsycheOverviewPayload: psycheOverviewPayload,
|
|
4996
5217
|
Insight: insight,
|
|
4997
5218
|
InsightFeedback: insightFeedback,
|
|
@@ -7044,6 +7265,23 @@ export function buildOpenApiDocument() {
|
|
|
7044
7265
|
}
|
|
7045
7266
|
}
|
|
7046
7267
|
},
|
|
7268
|
+
"/api/v1/psyche/metrics": {
|
|
7269
|
+
get: {
|
|
7270
|
+
summary: "Get daily Psyche metric history",
|
|
7271
|
+
responses: {
|
|
7272
|
+
"200": jsonResponse({
|
|
7273
|
+
type: "object",
|
|
7274
|
+
required: ["metrics"],
|
|
7275
|
+
properties: {
|
|
7276
|
+
metrics: {
|
|
7277
|
+
$ref: "#/components/schemas/PsycheMetricsViewData"
|
|
7278
|
+
}
|
|
7279
|
+
}
|
|
7280
|
+
}, "Psyche metrics view"),
|
|
7281
|
+
default: { $ref: "#/components/responses/Error" }
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
},
|
|
7047
7285
|
"/api/v1/psyche/values": {
|
|
7048
7286
|
get: {
|
|
7049
7287
|
summary: "List ACT-style values",
|
|
@@ -244,6 +244,95 @@ export const schemaPressureEntrySchema = z.object({
|
|
|
244
244
|
title: nonEmptyTrimmedString,
|
|
245
245
|
activationCount: z.number().int().nonnegative()
|
|
246
246
|
});
|
|
247
|
+
export const devrageMetricPayloadSchema = z.object({
|
|
248
|
+
generatedAt: z.string(),
|
|
249
|
+
hasData: z.boolean(),
|
|
250
|
+
latestDateKey: z.string().nullable(),
|
|
251
|
+
rawSwearCount: z.number().nonnegative(),
|
|
252
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
253
|
+
conversationsScanned: z.number().int().nonnegative(),
|
|
254
|
+
messagesScanned: z.number().int().nonnegative(),
|
|
255
|
+
messagesWithSwears: z.number().int().nonnegative(),
|
|
256
|
+
dailyAverage: z.object({
|
|
257
|
+
rawSwearCount: z.number().nonnegative(),
|
|
258
|
+
swearingMessagePercent: z.number().nonnegative()
|
|
259
|
+
}),
|
|
260
|
+
weeklyAverage: z.object({
|
|
261
|
+
rawSwearCount: z.number().nonnegative(),
|
|
262
|
+
swearingMessagePercent: z.number().nonnegative()
|
|
263
|
+
}),
|
|
264
|
+
history: z.array(z.object({
|
|
265
|
+
dateKey: z.string(),
|
|
266
|
+
rawSwearCount: z.number().nonnegative(),
|
|
267
|
+
swearingMessagePercent: z.number().nonnegative(),
|
|
268
|
+
conversationsScanned: z.number().int().nonnegative(),
|
|
269
|
+
messagesScanned: z.number().int().nonnegative(),
|
|
270
|
+
messagesWithSwears: z.number().int().nonnegative()
|
|
271
|
+
})),
|
|
272
|
+
sync: z.object({
|
|
273
|
+
fullSyncCompletedAt: z.string().nullable(),
|
|
274
|
+
lastDailySyncAt: z.string().nullable(),
|
|
275
|
+
lastSyncedDateKey: z.string().nullable()
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
export const psycheMetricDayRecordSchema = z.object({
|
|
279
|
+
dateKey: z.string(),
|
|
280
|
+
average: z.number().nullable(),
|
|
281
|
+
minimum: z.number().nullable(),
|
|
282
|
+
maximum: z.number().nullable(),
|
|
283
|
+
latest: z.number().nullable(),
|
|
284
|
+
total: z.number().nullable(),
|
|
285
|
+
sampleCount: z.number().int().nonnegative(),
|
|
286
|
+
latestSampleAt: z.string().nullable()
|
|
287
|
+
});
|
|
288
|
+
export const psycheMetricsViewDataSchema = z.object({
|
|
289
|
+
summary: z.object({
|
|
290
|
+
hasData: z.boolean(),
|
|
291
|
+
trackedDays: z.number().int().nonnegative(),
|
|
292
|
+
metricCount: z.number().int().nonnegative(),
|
|
293
|
+
latestDateKey: z.string().nullable(),
|
|
294
|
+
latestMetricCount: z.number().int().nonnegative(),
|
|
295
|
+
categoryBreakdown: z.array(z.object({
|
|
296
|
+
category: z.string(),
|
|
297
|
+
metricCount: z.number().int().nonnegative(),
|
|
298
|
+
coverageDays: z.number().int().nonnegative()
|
|
299
|
+
}))
|
|
300
|
+
}),
|
|
301
|
+
context: z.object({
|
|
302
|
+
generatedAt: z.string(),
|
|
303
|
+
conversationsScanned: z.number().int().nonnegative(),
|
|
304
|
+
sourceCount: z.number().int().nonnegative(),
|
|
305
|
+
messagesScanned: z.number().int().nonnegative(),
|
|
306
|
+
messagesWithSwears: z.number().int().nonnegative(),
|
|
307
|
+
totalSwears: z.number().nonnegative(),
|
|
308
|
+
dailyAverage: z.object({
|
|
309
|
+
rawSwearCount: z.number().nonnegative(),
|
|
310
|
+
swearingMessagePercent: z.number().nonnegative()
|
|
311
|
+
}),
|
|
312
|
+
weeklyAverage: z.object({
|
|
313
|
+
rawSwearCount: z.number().nonnegative(),
|
|
314
|
+
swearingMessagePercent: z.number().nonnegative()
|
|
315
|
+
}),
|
|
316
|
+
sync: z.object({
|
|
317
|
+
fullSyncCompletedAt: z.string().nullable(),
|
|
318
|
+
lastDailySyncAt: z.string().nullable(),
|
|
319
|
+
lastSyncedDateKey: z.string().nullable()
|
|
320
|
+
})
|
|
321
|
+
}),
|
|
322
|
+
metrics: z.array(z.object({
|
|
323
|
+
metric: z.string(),
|
|
324
|
+
label: z.string(),
|
|
325
|
+
category: z.string(),
|
|
326
|
+
unit: z.string(),
|
|
327
|
+
aggregation: z.enum(["discrete", "cumulative"]),
|
|
328
|
+
latestValue: z.number().nullable(),
|
|
329
|
+
latestDateKey: z.string().nullable(),
|
|
330
|
+
baselineValue: z.number().nullable(),
|
|
331
|
+
deltaValue: z.number().nullable(),
|
|
332
|
+
coverageDays: z.number().int().nonnegative(),
|
|
333
|
+
days: z.array(psycheMetricDayRecordSchema)
|
|
334
|
+
}))
|
|
335
|
+
});
|
|
247
336
|
export const psycheOverviewPayloadSchema = z.object({
|
|
248
337
|
generatedAt: z.string(),
|
|
249
338
|
domain: domainSchema,
|
|
@@ -254,6 +343,7 @@ export const psycheOverviewPayloadSchema = z.object({
|
|
|
254
343
|
modes: z.array(modeProfileSchema),
|
|
255
344
|
reports: z.array(triggerReportSchema),
|
|
256
345
|
schemaPressure: z.array(schemaPressureEntrySchema),
|
|
346
|
+
devrageMetric: devrageMetricPayloadSchema,
|
|
257
347
|
openInsights: z.number().int().nonnegative(),
|
|
258
348
|
openNotes: z.number().int().nonnegative(),
|
|
259
349
|
committedActions: z.array(trimmedString)
|