@adminide-stack/yantra-mobile 12.0.141 → 12.0.142-alpha.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.
@@ -1 +1 @@
1
- {"version":3,"file":"readings.js","sources":["../../../src/features/devices/readings.ts"],"sourcesContent":["/**\n * What a paired Bluetooth device measured, and how it reaches the Fitness\n * backend.\n *\n * The backend keeps ONE day per source and replaces it whole on every upload,\n * so a reading is never sent alone: the app keeps every reading it took (the\n * device store), and each upload rebuilds the full `device` day for the dates\n * that changed. A blood-pressure reading in the evening never wipes the\n * morning's weigh-in.\n *\n * Reading ids are stable (device + what + when): a monitor that sends its\n * whole memory on every connection, or the same reading arriving twice, is\n * stored once.\n */\n\nimport type { BodyComposition } from './bodyComposition';\nimport { sanitizeDay, hasData } from '../health/buildDays';\nimport type { TimedValue } from '../health/model';\nimport type { HealthDay, HealthReading, HealthWorkout, MealRelation } from '../health/types';\nimport type { ZoneClock } from '../health/time';\nimport { zoneMinutes } from '../health/zones';\n\ninterface ReadingBase {\n id: string;\n /** Epoch ms of the measurement. */\n at: number;\n deviceId: string;\n deviceName: string;\n}\n\nexport interface BodyReading extends ReadingBase {\n kind: 'body';\n weightKg?: number;\n bodyFatPct?: number;\n leanMassKg?: number;\n bmi?: number;\n /**\n * Whole-body impedance in ohms, when the scale sends it. A scale that only\n * reports weight and impedance (RENPHO's common protocol) leaves body fat\n * to Yantra's own estimate; one that computes body fat itself (R-A017) also\n * fills bodyFatPct and bmi.\n */\n impedanceOhm?: number;\n /**\n * The full body composition (muscle, bone, water, BMR, metabolic age ...),\n * computed from weight + body fat + the person's height/age/sex when all are\n * known (bodyComposition.ts). Shown on the reading; the backend day keeps\n * the fields it supports.\n */\n composition?: BodyComposition;\n}\n\nexport interface BloodPressureReading extends ReadingBase {\n kind: 'blood-pressure';\n systolic: number;\n diastolic: number;\n pulse?: number;\n /** What the monitor flagged (moved, loose cuff, irregular pulse), in words. */\n problems?: string[];\n}\n\nexport interface TemperatureReading extends ReadingBase {\n kind: 'temperature';\n celsius: number;\n}\n\nexport interface OxygenReading extends ReadingBase {\n kind: 'spo2';\n spo2: number;\n pulse?: number;\n}\n\nexport interface GlucoseReading extends ReadingBase {\n kind: 'glucose';\n mgdl: number;\n meal?: MealRelation;\n}\n\n/** A heart-rate strap worn for a session: the samples, summed up when it ended. */\nexport interface HeartRateSession extends ReadingBase {\n kind: 'heart-rate';\n /** `at` is the start. */\n end: number;\n /** run | walk | cycle | strength | hiit | other, as the person picked it. */\n workoutType: string;\n avgHr: number;\n maxHr: number;\n /** At most one sample every few seconds (see thinSamples); emptied once saved, see compactSession. */\n samples: TimedValue[];\n /** Minutes per heart-rate zone, kept when the samples are let go. */\n zoneMinutes?: number[];\n}\n\n/**\n * A finished exercise a device recorded itself: a jump-rope session or a\n * treadmill walk/run. Becomes one HealthWorkout in the day (readings with no\n * heart-rate samples, unlike HeartRateSession).\n */\nexport interface WorkoutReading extends ReadingBase {\n kind: 'workout';\n /** `at` is the start. */\n end: number;\n /** jump-rope | run | walk | ... lowercased. */\n workoutType: string;\n durationMin: number;\n calories?: number;\n distanceMeters?: number;\n /** Jump rope only: the number of skips. */\n jumps?: number;\n avgHr?: number;\n maxHr?: number;\n}\n\n/** A tape-measure girth reading of one body part. Only the waist has a home in the day. */\nexport interface BodyMeasurementReading extends ReadingBase {\n kind: 'body-measurement';\n /** waist | chest | hip | left_arm | ... as the tape reported it. */\n bodyPart: string;\n cm: number;\n}\n\nexport type DeviceReading =\n | BodyReading\n | BloodPressureReading\n | TemperatureReading\n | OxygenReading\n | GlucoseReading\n | HeartRateSession\n | WorkoutReading\n | BodyMeasurementReading;\n\nconst round1 = (n: number) => Math.round(n * 10) / 10;\n\n/** A stable id: the same measurement from the same device always gets the same one. */\nexport function readingId(deviceId: string, kind: DeviceReading['kind'], at: number, extra?: string | number): string {\n return [deviceId, kind, Math.round(at / 1000), extra].filter((p) => p !== undefined && p !== '').join(':');\n}\n\n/** Keep one heart-rate sample per `everyMs`: enough for zones, few enough to store and save. */\nexport function thinSamples(samples: readonly TimedValue[], everyMs = 5_000): TimedValue[] {\n const out: TimedValue[] = [];\n let last = -Infinity;\n for (const s of [...samples].sort((a, b) => a.t - b.t)) {\n if (s.t - last >= everyMs) {\n out.push(s);\n last = s.t;\n }\n }\n return out;\n}\n\nconst latestOf = <T extends DeviceReading>(list: readonly T[], has: (r: T) => boolean): T | undefined =>\n list.filter(has).reduce<T | undefined>((a, b) => (!a || b.at >= a.at ? b : a), undefined);\n\nexport interface DeviceDaysOptions {\n clock: ZoneClock;\n syncedAt: string;\n /** 208 - 0.7 x age, for heart-rate zones; undefined when the birth date is unknown. */\n maxHr?: number;\n /** Only these dates (the ones a new reading touched); every date when omitted. */\n dates?: ReadonlySet<string>;\n}\n\n/** Every reading -> one `device` HealthDay per local date, cleaned exactly as the backend cleans. */\nexport function deviceHealthDays(\n readings: readonly DeviceReading[],\n opts: DeviceDaysOptions,\n): { days: HealthDay[]; dropped: string[] } {\n const byDate = new Map<string, DeviceReading[]>();\n for (const r of readings) {\n const date = opts.clock.dateOf(r.at);\n if (opts.dates && !opts.dates.has(date)) continue;\n byDate.set(date, [...(byDate.get(date) ?? []), r]);\n }\n const dropped: string[] = [];\n const days: HealthDay[] = [];\n for (const [date, list] of [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b))) {\n const day: HealthDay = { date, source: 'device' };\n const body = list.filter((r): r is BodyReading => r.kind === 'body');\n const weight = latestOf(body, (r) => r.weightKg !== undefined);\n if (weight) day.weightKg = weight.weightKg;\n const fat = latestOf(body, (r) => r.bodyFatPct !== undefined);\n if (fat) day.bodyFatPct = fat.bodyFatPct;\n const lean = latestOf(body, (r) => r.leanMassKg !== undefined);\n if (lean) day.leanMassKg = lean.leanMassKg;\n const bmi = latestOf(body, (r) => r.bmi !== undefined);\n if (bmi) day.bmi = bmi.bmi;\n const temp = latestOf(\n list.filter((r): r is TemperatureReading => r.kind === 'temperature'),\n () => true,\n );\n if (temp) day.bodyTempC = temp.celsius;\n const spo2 = latestOf(\n list.filter((r): r is OxygenReading => r.kind === 'spo2'),\n () => true,\n );\n if (spo2) day.spo2 = spo2.spo2;\n const waist = latestOf(\n list.filter((r): r is BodyMeasurementReading => r.kind === 'body-measurement' && r.bodyPart === 'waist'),\n () => true,\n );\n if (waist) day.waistCm = waist.cm;\n\n const readingsOut: HealthReading[] = [];\n for (const r of list) {\n if (r.kind === 'blood-pressure') {\n readingsOut.push({\n id: r.id,\n at: new Date(r.at).toISOString(),\n kind: 'blood-pressure',\n systolic: r.systolic,\n diastolic: r.diastolic,\n ...(r.pulse !== undefined ? { pulse: r.pulse } : {}),\n device: r.deviceName,\n });\n } else if (r.kind === 'glucose') {\n readingsOut.push({\n id: r.id,\n at: new Date(r.at).toISOString(),\n kind: 'glucose',\n glucoseMgDl: r.mgdl,\n ...(r.meal ? { meal: r.meal } : {}),\n device: r.deviceName,\n });\n }\n }\n if (readingsOut.length) day.readings = readingsOut.sort((a, b) => a.at.localeCompare(b.at));\n\n const workouts: HealthWorkout[] = list\n .filter((r): r is HeartRateSession => r.kind === 'heart-rate')\n .sort((a, b) => a.at - b.at)\n .map((s) => {\n const w: HealthWorkout = {\n id: s.id,\n start: new Date(s.at).toISOString(),\n end: new Date(s.end).toISOString(),\n type: s.workoutType,\n durationMin: round1((s.end - s.at) / 60_000),\n avgHr: s.avgHr,\n maxHr: s.maxHr,\n };\n if (opts.maxHr && s.samples.length) {\n w.zoneMinutes = zoneMinutes(s.samples, { start: s.at, end: s.end }, opts.maxHr);\n } else if (s.zoneMinutes) w.zoneMinutes = s.zoneMinutes;\n return w;\n });\n // Device-recorded workouts (jump rope, treadmill): same HealthWorkout shape, no HR samples.\n for (const s of list.filter((r): r is WorkoutReading => r.kind === 'workout').sort((a, b) => a.at - b.at)) {\n const w: HealthWorkout = {\n id: s.id,\n start: new Date(s.at).toISOString(),\n end: new Date(s.end).toISOString(),\n type: s.workoutType,\n durationMin: round1(s.durationMin),\n ...(s.calories !== undefined ? { calories: s.calories } : {}),\n ...(s.distanceMeters !== undefined ? { distanceMeters: s.distanceMeters } : {}),\n ...(s.avgHr !== undefined ? { avgHr: s.avgHr } : {}),\n ...(s.maxHr !== undefined ? { maxHr: s.maxHr } : {}),\n };\n workouts.push(w);\n }\n if (workouts.length) day.workouts = workouts.sort((a, b) => a.start.localeCompare(b.start));\n\n const clean = sanitizeDay(day, dropped);\n if (hasData(clean)) days.push({ ...clean, syncedAt: opts.syncedAt });\n }\n return { days, dropped };\n}\n\n/** One line for a reading, as the person reads it (\"82.4 kg, body fat 23.1%\"). */\nexport function describeReading(r: DeviceReading, units: 'metric' | 'imperial' = 'metric'): string {\n const kg = (v: number) => (units === 'imperial' ? `${round1(v / 0.45359237)} lb` : `${round1(v)} kg`);\n switch (r.kind) {\n case 'body': {\n const parts: string[] = [];\n if (r.weightKg !== undefined) parts.push(kg(r.weightKg));\n if (r.bodyFatPct !== undefined) parts.push(`body fat ${round1(r.bodyFatPct)}%`);\n if (r.bmi !== undefined) parts.push(`BMI ${round1(r.bmi)}`);\n return parts.join(', ') || 'A measurement with no weight';\n }\n case 'blood-pressure':\n return `${Math.round(r.systolic)}/${Math.round(r.diastolic)} mmHg${r.pulse ? `, pulse ${Math.round(r.pulse)}` : ''}`;\n case 'temperature':\n return units === 'imperial' ? `${round1((r.celsius * 9) / 5 + 32)} °F` : `${round1(r.celsius)} °C`;\n case 'spo2':\n return `Blood oxygen ${Math.round(r.spo2)}%${r.pulse ? `, pulse ${Math.round(r.pulse)}` : ''}`;\n case 'glucose':\n return `${Math.round(r.mgdl)} mg/dL${r.meal ? `, ${r.meal.replace('-', ' ')}` : ''}`;\n case 'heart-rate': {\n const minutes = Math.round((r.end - r.at) / 60_000);\n return `${minutes} min, average ${Math.round(r.avgHr)} bpm, max ${Math.round(r.maxHr)}`;\n }\n case 'workout': {\n const parts: string[] = [`${Math.round(r.durationMin)} min`];\n if (r.jumps !== undefined) parts.unshift(`${r.jumps} jumps`);\n if (r.distanceMeters !== undefined) parts.push(`${round1(r.distanceMeters / 1000)} km`);\n if (r.calories !== undefined) parts.push(`${Math.round(r.calories)} kcal`);\n return parts.join(', ');\n }\n case 'body-measurement': {\n const part = r.bodyPart.replace(/_/g, ' ');\n return units === 'imperial' ? `${part} ${round1(r.cm / 2.54)} in` : `${part} ${round1(r.cm)} cm`;\n }\n default:\n return '';\n }\n}\n\n/**\n * A session's samples are only needed until they are uploaded and saved to\n * the health store; after that the zones are kept and the samples let go, so\n * the phone's store stays small.\n */\nexport function compactSession(s: HeartRateSession, maxHr: number | undefined): HeartRateSession {\n if (!s.samples.length) return s;\n const zones = maxHr ? zoneMinutes(s.samples, { start: s.at, end: s.end }, maxHr) : s.zoneMinutes;\n return { ...s, samples: [], ...(zones ? { zoneMinutes: zones } : {}) };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"readings.js","sources":["../../../src/features/devices/readings.ts"],"sourcesContent":["/**\n * What a paired Bluetooth device measured, and how it reaches the Fitness\n * backend.\n *\n * The backend keeps ONE day per source and replaces it whole on every upload,\n * so a reading is never sent alone: the app keeps every reading it took (the\n * device store), and each upload rebuilds the full `device` day for the dates\n * that changed. A blood-pressure reading in the evening never wipes the\n * morning's weigh-in.\n *\n * Reading ids are stable (device + what + when): a monitor that sends its\n * whole memory on every connection, or the same reading arriving twice, is\n * stored once.\n */\n\nimport { sanitizeDay, hasData } from '../health/buildDays';\nimport type { TimedValue } from '../health/model';\nimport type { HealthDay, HealthReading, HealthWorkout, MealRelation } from '../health/types';\nimport type { ZoneClock } from '../health/time';\nimport { zoneMinutes } from '../health/zones';\n\ninterface ReadingBase {\n id: string;\n /** Epoch ms of the measurement. */\n at: number;\n deviceId: string;\n deviceName: string;\n}\n\nexport interface BodyReading extends ReadingBase {\n kind: 'body';\n weightKg?: number;\n bodyFatPct?: number;\n leanMassKg?: number;\n bmi?: number;\n /**\n * Whole-body impedance in ohms, when the scale sends it. A scale that only\n * reports weight and impedance (RENPHO's common protocol) leaves body fat\n * to Yantra's own estimate; one that computes body fat itself (R-A017) also\n * fills bodyFatPct and bmi.\n */\n impedanceOhm?: number;\n}\n\nexport interface BloodPressureReading extends ReadingBase {\n kind: 'blood-pressure';\n systolic: number;\n diastolic: number;\n pulse?: number;\n /** What the monitor flagged (moved, loose cuff, irregular pulse), in words. */\n problems?: string[];\n}\n\nexport interface TemperatureReading extends ReadingBase {\n kind: 'temperature';\n celsius: number;\n}\n\nexport interface OxygenReading extends ReadingBase {\n kind: 'spo2';\n spo2: number;\n pulse?: number;\n}\n\nexport interface GlucoseReading extends ReadingBase {\n kind: 'glucose';\n mgdl: number;\n meal?: MealRelation;\n}\n\n/** A heart-rate strap worn for a session: the samples, summed up when it ended. */\nexport interface HeartRateSession extends ReadingBase {\n kind: 'heart-rate';\n /** `at` is the start. */\n end: number;\n /** run | walk | cycle | strength | hiit | other, as the person picked it. */\n workoutType: string;\n avgHr: number;\n maxHr: number;\n /** At most one sample every few seconds (see thinSamples); emptied once saved, see compactSession. */\n samples: TimedValue[];\n /** Minutes per heart-rate zone, kept when the samples are let go. */\n zoneMinutes?: number[];\n}\n\n/**\n * A finished exercise a device recorded itself: a jump-rope session or a\n * treadmill walk/run. Becomes one HealthWorkout in the day (readings with no\n * heart-rate samples, unlike HeartRateSession).\n */\nexport interface WorkoutReading extends ReadingBase {\n kind: 'workout';\n /** `at` is the start. */\n end: number;\n /** jump-rope | run | walk | ... lowercased. */\n workoutType: string;\n durationMin: number;\n calories?: number;\n distanceMeters?: number;\n /** Jump rope only: the number of skips. */\n jumps?: number;\n avgHr?: number;\n maxHr?: number;\n}\n\n/** A tape-measure girth reading of one body part. Only the waist has a home in the day. */\nexport interface BodyMeasurementReading extends ReadingBase {\n kind: 'body-measurement';\n /** waist | chest | hip | left_arm | ... as the tape reported it. */\n bodyPart: string;\n cm: number;\n}\n\nexport type DeviceReading =\n | BodyReading\n | BloodPressureReading\n | TemperatureReading\n | OxygenReading\n | GlucoseReading\n | HeartRateSession\n | WorkoutReading\n | BodyMeasurementReading;\n\nconst round1 = (n: number) => Math.round(n * 10) / 10;\n\n/** A stable id: the same measurement from the same device always gets the same one. */\nexport function readingId(deviceId: string, kind: DeviceReading['kind'], at: number, extra?: string | number): string {\n return [deviceId, kind, Math.round(at / 1000), extra].filter((p) => p !== undefined && p !== '').join(':');\n}\n\n/** Keep one heart-rate sample per `everyMs`: enough for zones, few enough to store and save. */\nexport function thinSamples(samples: readonly TimedValue[], everyMs = 5_000): TimedValue[] {\n const out: TimedValue[] = [];\n let last = -Infinity;\n for (const s of [...samples].sort((a, b) => a.t - b.t)) {\n if (s.t - last >= everyMs) {\n out.push(s);\n last = s.t;\n }\n }\n return out;\n}\n\nconst latestOf = <T extends DeviceReading>(list: readonly T[], has: (r: T) => boolean): T | undefined =>\n list.filter(has).reduce<T | undefined>((a, b) => (!a || b.at >= a.at ? b : a), undefined);\n\nexport interface DeviceDaysOptions {\n clock: ZoneClock;\n syncedAt: string;\n /** 208 - 0.7 x age, for heart-rate zones; undefined when the birth date is unknown. */\n maxHr?: number;\n /** Only these dates (the ones a new reading touched); every date when omitted. */\n dates?: ReadonlySet<string>;\n}\n\n/** Every reading -> one `device` HealthDay per local date, cleaned exactly as the backend cleans. */\nexport function deviceHealthDays(\n readings: readonly DeviceReading[],\n opts: DeviceDaysOptions,\n): { days: HealthDay[]; dropped: string[] } {\n const byDate = new Map<string, DeviceReading[]>();\n for (const r of readings) {\n const date = opts.clock.dateOf(r.at);\n if (opts.dates && !opts.dates.has(date)) continue;\n byDate.set(date, [...(byDate.get(date) ?? []), r]);\n }\n const dropped: string[] = [];\n const days: HealthDay[] = [];\n for (const [date, list] of [...byDate.entries()].sort(([a], [b]) => a.localeCompare(b))) {\n const day: HealthDay = { date, source: 'device' };\n const body = list.filter((r): r is BodyReading => r.kind === 'body');\n const weight = latestOf(body, (r) => r.weightKg !== undefined);\n if (weight) day.weightKg = weight.weightKg;\n const fat = latestOf(body, (r) => r.bodyFatPct !== undefined);\n if (fat) day.bodyFatPct = fat.bodyFatPct;\n const lean = latestOf(body, (r) => r.leanMassKg !== undefined);\n if (lean) day.leanMassKg = lean.leanMassKg;\n const bmi = latestOf(body, (r) => r.bmi !== undefined);\n if (bmi) day.bmi = bmi.bmi;\n const temp = latestOf(\n list.filter((r): r is TemperatureReading => r.kind === 'temperature'),\n () => true,\n );\n if (temp) day.bodyTempC = temp.celsius;\n const spo2 = latestOf(\n list.filter((r): r is OxygenReading => r.kind === 'spo2'),\n () => true,\n );\n if (spo2) day.spo2 = spo2.spo2;\n const waist = latestOf(\n list.filter((r): r is BodyMeasurementReading => r.kind === 'body-measurement' && r.bodyPart === 'waist'),\n () => true,\n );\n if (waist) day.waistCm = waist.cm;\n\n const readingsOut: HealthReading[] = [];\n for (const r of list) {\n if (r.kind === 'blood-pressure') {\n readingsOut.push({\n id: r.id,\n at: new Date(r.at).toISOString(),\n kind: 'blood-pressure',\n systolic: r.systolic,\n diastolic: r.diastolic,\n ...(r.pulse !== undefined ? { pulse: r.pulse } : {}),\n device: r.deviceName,\n });\n } else if (r.kind === 'glucose') {\n readingsOut.push({\n id: r.id,\n at: new Date(r.at).toISOString(),\n kind: 'glucose',\n glucoseMgDl: r.mgdl,\n ...(r.meal ? { meal: r.meal } : {}),\n device: r.deviceName,\n });\n }\n }\n if (readingsOut.length) day.readings = readingsOut.sort((a, b) => a.at.localeCompare(b.at));\n\n const workouts: HealthWorkout[] = list\n .filter((r): r is HeartRateSession => r.kind === 'heart-rate')\n .sort((a, b) => a.at - b.at)\n .map((s) => {\n const w: HealthWorkout = {\n id: s.id,\n start: new Date(s.at).toISOString(),\n end: new Date(s.end).toISOString(),\n type: s.workoutType,\n durationMin: round1((s.end - s.at) / 60_000),\n avgHr: s.avgHr,\n maxHr: s.maxHr,\n };\n if (opts.maxHr && s.samples.length) {\n w.zoneMinutes = zoneMinutes(s.samples, { start: s.at, end: s.end }, opts.maxHr);\n } else if (s.zoneMinutes) w.zoneMinutes = s.zoneMinutes;\n return w;\n });\n // Device-recorded workouts (jump rope, treadmill): same HealthWorkout shape, no HR samples.\n for (const s of list.filter((r): r is WorkoutReading => r.kind === 'workout').sort((a, b) => a.at - b.at)) {\n const w: HealthWorkout = {\n id: s.id,\n start: new Date(s.at).toISOString(),\n end: new Date(s.end).toISOString(),\n type: s.workoutType,\n durationMin: round1(s.durationMin),\n ...(s.calories !== undefined ? { calories: s.calories } : {}),\n ...(s.distanceMeters !== undefined ? { distanceMeters: s.distanceMeters } : {}),\n ...(s.avgHr !== undefined ? { avgHr: s.avgHr } : {}),\n ...(s.maxHr !== undefined ? { maxHr: s.maxHr } : {}),\n };\n workouts.push(w);\n }\n if (workouts.length) day.workouts = workouts.sort((a, b) => a.start.localeCompare(b.start));\n\n const clean = sanitizeDay(day, dropped);\n if (hasData(clean)) days.push({ ...clean, syncedAt: opts.syncedAt });\n }\n return { days, dropped };\n}\n\n/** One line for a reading, as the person reads it (\"82.4 kg, body fat 23.1%\"). */\nexport function describeReading(r: DeviceReading, units: 'metric' | 'imperial' = 'metric'): string {\n const kg = (v: number) => (units === 'imperial' ? `${round1(v / 0.45359237)} lb` : `${round1(v)} kg`);\n switch (r.kind) {\n case 'body': {\n const parts: string[] = [];\n if (r.weightKg !== undefined) parts.push(kg(r.weightKg));\n if (r.bodyFatPct !== undefined) parts.push(`body fat ${round1(r.bodyFatPct)}%`);\n if (r.bmi !== undefined) parts.push(`BMI ${round1(r.bmi)}`);\n return parts.join(', ') || 'A measurement with no weight';\n }\n case 'blood-pressure':\n return `${Math.round(r.systolic)}/${Math.round(r.diastolic)} mmHg${r.pulse ? `, pulse ${Math.round(r.pulse)}` : ''}`;\n case 'temperature':\n return units === 'imperial' ? `${round1((r.celsius * 9) / 5 + 32)} °F` : `${round1(r.celsius)} °C`;\n case 'spo2':\n return `Blood oxygen ${Math.round(r.spo2)}%${r.pulse ? `, pulse ${Math.round(r.pulse)}` : ''}`;\n case 'glucose':\n return `${Math.round(r.mgdl)} mg/dL${r.meal ? `, ${r.meal.replace('-', ' ')}` : ''}`;\n case 'heart-rate': {\n const minutes = Math.round((r.end - r.at) / 60_000);\n return `${minutes} min, average ${Math.round(r.avgHr)} bpm, max ${Math.round(r.maxHr)}`;\n }\n case 'workout': {\n const parts: string[] = [`${Math.round(r.durationMin)} min`];\n if (r.jumps !== undefined) parts.unshift(`${r.jumps} jumps`);\n if (r.distanceMeters !== undefined) parts.push(`${round1(r.distanceMeters / 1000)} km`);\n if (r.calories !== undefined) parts.push(`${Math.round(r.calories)} kcal`);\n return parts.join(', ');\n }\n case 'body-measurement': {\n const part = r.bodyPart.replace(/_/g, ' ');\n return units === 'imperial' ? `${part} ${round1(r.cm / 2.54)} in` : `${part} ${round1(r.cm)} cm`;\n }\n default:\n return '';\n }\n}\n\n/**\n * A session's samples are only needed until they are uploaded and saved to\n * the health store; after that the zones are kept and the samples let go, so\n * the phone's store stays small.\n */\nexport function compactSession(s: HeartRateSession, maxHr: number | undefined): HeartRateSession {\n if (!s.samples.length) return s;\n const zones = maxHr ? zoneMinutes(s.samples, { start: s.at, end: s.end }, maxHr) : s.zoneMinutes;\n return { ...s, samples: [], ...(zones ? { zoneMinutes: zones } : {}) };\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {deviceKind}from'./catalog.js';import {describeBleError}from'./link.js';import {thinSamples,readingId}from'./readings.js';import'./brands/frames.js';import'./brands/uuids.js';import'./gatt/uuids.js';import {detectBrand}from'./brands/detect.js';import {startDriver}from'./drivers.js';import {computeBodyComposition}from'./bodyComposition.js';var __defProp = Object.defineProperty;
1
+ import {deviceKind}from'./catalog.js';import {describeBleError}from'./link.js';import {thinSamples,readingId}from'./readings.js';import'./brands/frames.js';import'./brands/uuids.js';import'./gatt/uuids.js';import {detectBrand}from'./brands/detect.js';import {startDriver}from'./drivers.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -24,26 +24,6 @@ function ageFrom(birthDate, now) {
24
24
  const t = Date.parse(`${birthDate}T00:00:00Z`);
25
25
  return Number.isFinite(t) ? Math.floor((now - t) / (365.25 * 864e5)) : void 0;
26
26
  }
27
- function enrichWithComposition(reading, facts) {
28
- var _a, _b, _c;
29
- if (reading.kind !== "body" || reading.weightKg === void 0 || reading.bodyFatPct === void 0) {
30
- return reading;
31
- }
32
- if (!facts.heightCm || facts.age === void 0) return reading;
33
- const composition = computeBodyComposition({
34
- weightKg: reading.weightKg,
35
- bodyFatPct: reading.bodyFatPct,
36
- heightCm: facts.heightCm,
37
- age: facts.age,
38
- sex: (_a = facts.sex) != null ? _a : "unspecified",
39
- impedanceOhm: reading.impedanceOhm
40
- });
41
- return __spreadProps(__spreadValues({}, reading), {
42
- bmi: (_b = reading.bmi) != null ? _b : composition.bmi,
43
- leanMassKg: (_c = reading.leanMassKg) != null ? _c : composition.leanMassKg,
44
- composition
45
- });
46
- }
47
27
  class DeviceSession {
48
28
  constructor(deps) {
49
29
  this.deps = deps;
@@ -207,7 +187,7 @@ class DeviceSession {
207
187
  } : void 0,
208
188
  scaleUser: paired == null ? void 0 : paired.scaleUser,
209
189
  saveScaleUser: (user) => store.saveScaleUser(link.id, user),
210
- onReading: (r) => void this.keep([enrichWithComposition(r, facts)]),
190
+ onReading: (r) => void this.keep([r]),
211
191
  onLive: (v) => this.onLive(v),
212
192
  onNotice: (m) => this.set({
213
193
  notices: [...this.snapshot.notices, m]
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sources":["../../../src/features/devices/session.ts"],"sourcesContent":["/**\n * One \"take a reading\" from start to end, for the screen: is Bluetooth on and\n * allowed, find the device (or the one already paired), connect, run its\n * driver, keep what it measures, and end cleanly when it goes to sleep (a\n * scale switches off seconds after a weighing; that is the normal end, not an\n * error).\n *\n * Heart-rate straps and continuous oximeters stream: the person starts and\n * stops a session (heart rate) or saves the value they see (oxygen).\n *\n * Plain class with injected transport and store, like the other stores: the\n * screen subscribes with useSyncExternalStore, tests drive it with fakes.\n */\nimport type { TimedValue } from '../health/model';\nimport { deviceKind, type DeviceKindId } from './catalog';\nimport { describeBleError, type BleTransport, type ConnectedLink, type FoundDevice, type RadioState } from './link';\nimport { detectBrand } from './brands';\nimport type { DeviceStore } from './deviceStore';\nimport { startDriver, type DriverHandle, type LiveValue, type PersonFacts } from './drivers';\nimport { computeBodyComposition } from './bodyComposition';\nimport { readingId, thinSamples, type DeviceReading, type HeartRateSession } from './readings';\n\nexport type SessionState =\n | { step: 'starting' }\n | { step: 'unavailable'; reason: string }\n | { step: 'radio'; radio: Exclude<RadioState, 'ready'> }\n | { step: 'scanning'; found: FoundDevice[]; hint?: string }\n | { step: 'connecting'; device: FoundDevice }\n | { step: 'connected'; device: { id: string; name: string } }\n | { step: 'ended'; device: { id: string; name: string } }\n | { step: 'error'; message: string };\n\nexport interface SessionSnapshot {\n state: SessionState;\n kind: DeviceKindId;\n /** What this session measured, newest first. */\n readings: DeviceReading[];\n live?: LiveValue;\n recording?: { startedAt: number; workoutType: string; avgHr?: number; maxHr?: number };\n notices: string[];\n}\n\nexport interface SessionDeps {\n transport: BleTransport | null;\n store: Pick<DeviceStore, 'pair' | 'addReadings' | 'saveScaleUser' | 'person' | 'getSnapshot'>;\n kind: DeviceKindId;\n /** Reconnect to this paired device instead of offering a list. */\n deviceId?: string;\n now?: () => number;\n}\n\n/** Scanning this long without finding anything: tell the person how to wake it. */\nexport const SCAN_HINT_MS = 20_000;\n\nfunction ageFrom(birthDate: string | undefined, now: number): number | undefined {\n if (!birthDate) return undefined;\n const t = Date.parse(`${birthDate}T00:00:00Z`);\n return Number.isFinite(t) ? Math.floor((now - t) / (365.25 * 86_400_000)) : undefined;\n}\n\n/**\n * Fill in the full body composition for a body reading once the scale has given\n * weight and body fat and the person's height/age/sex are known. Other readings\n * pass through unchanged.\n */\nfunction enrichWithComposition(reading: DeviceReading, facts: PersonFacts): DeviceReading {\n if (reading.kind !== 'body' || reading.weightKg === undefined || reading.bodyFatPct === undefined) {\n return reading;\n }\n if (!facts.heightCm || facts.age === undefined) return reading;\n const composition = computeBodyComposition({\n weightKg: reading.weightKg,\n bodyFatPct: reading.bodyFatPct,\n heightCm: facts.heightCm,\n age: facts.age,\n sex: facts.sex ?? 'unspecified',\n impedanceOhm: reading.impedanceOhm,\n });\n return {\n ...reading,\n bmi: reading.bmi ?? composition.bmi,\n leanMassKg: reading.leanMassKg ?? composition.leanMassKg,\n composition,\n };\n}\n\nexport class DeviceSession {\n private snapshot: SessionSnapshot;\n\n private listeners = new Set<() => void>();\n\n private stopScan: (() => void) | null = null;\n\n private link: ConnectedLink | null = null;\n\n private driver: DriverHandle | null = null;\n\n private hintTimer: ReturnType<typeof setTimeout> | null = null;\n\n private hr: { samples: TimedValue[]; sum: number; count: number; max: number } | null = null;\n\n private closed = false;\n\n constructor(private readonly deps: SessionDeps) {\n this.snapshot = { state: { step: 'starting' }, kind: deps.kind, readings: [], notices: [] };\n }\n\n subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): SessionSnapshot => this.snapshot;\n\n private now(): number {\n return this.deps.now ? this.deps.now() : Date.now();\n }\n\n private set(patch: Partial<SessionSnapshot>) {\n if (this.closed) return;\n this.snapshot = { ...this.snapshot, ...patch };\n this.listeners.forEach((l) => l());\n }\n\n async start(): Promise<void> {\n const { transport } = this.deps;\n if (!transport) {\n this.set({\n state: { step: 'unavailable', reason: 'Bluetooth devices need the latest version of the Yantra app.' },\n });\n return;\n }\n if (!(await transport.requestPermissions())) {\n this.set({ state: { step: 'radio', radio: 'unauthorized' } });\n return;\n }\n const radio = await transport.radio();\n if (radio !== 'ready' && radio !== 'unknown') {\n this.set({ state: { step: 'radio', radio } });\n return;\n }\n this.scan();\n }\n\n /** Back to looking for the device (after the radio came on, or to measure again). */\n scan(): void {\n const { transport } = this.deps;\n if (!transport || this.closed) return;\n this.stopScan?.();\n this.set({ state: { step: 'scanning', found: [] } });\n this.hintTimer = setTimeout(() => {\n const s = this.snapshot.state;\n if (s.step === 'scanning' && !s.found.length) {\n this.set({ state: { ...s, hint: deviceKind(this.deps.kind).steps[0] } });\n }\n }, SCAN_HINT_MS);\n const kind = this.deps.kind;\n this.stopScan = transport.scan(\n {\n services: deviceKind(kind).services,\n // Brand devices do not advertise a standard service: recognise this kind of them.\n match: (adv) => {\n const brand = detectBrand(adv);\n return brand && brand.kind === kind ? brand : undefined;\n },\n },\n (found) => {\n const s = this.snapshot.state;\n if (s.step !== 'scanning') return;\n if (this.deps.deviceId && found.id === this.deps.deviceId) {\n void this.pick(found);\n return;\n }\n if (this.deps.deviceId) return;\n this.set({\n state: {\n ...s,\n hint: undefined,\n found: [...s.found, found].sort((a, b) => (b.rssi ?? -999) - (a.rssi ?? -999)),\n },\n });\n },\n (message) => this.set({ state: { step: 'error', message } }),\n );\n }\n\n async pick(found: FoundDevice): Promise<void> {\n const { transport, store } = this.deps;\n if (!transport) return;\n this.clearScan();\n this.set({ state: { step: 'connecting', device: found } });\n try {\n const link = await transport.connect(found.id, found.name, () => this.onDisconnect());\n this.link = link;\n const paired = store.getSnapshot().devices.find((d) => d.id === link.id);\n const person = await store.person();\n const facts: PersonFacts = {\n age: ageFrom(person?.birthDate, this.now()),\n sex: person?.sex,\n heightCm: person?.heightCm,\n birthDate: person?.birthDate,\n weightKg: paired?.lastWeightKg,\n };\n this.driver = await startDriver(this.deps.kind, link, {\n now: () => this.now(),\n person: facts,\n identity: found.protocol\n ? {\n protocol: found.protocol,\n mac: found.mac,\n deviceType: found.deviceType,\n model: found.model,\n kind: found.kind,\n }\n : undefined,\n scaleUser: paired?.scaleUser,\n saveScaleUser: (user) => store.saveScaleUser(link.id, user),\n onReading: (r) => void this.keep([enrichWithComposition(r, facts)]),\n onLive: (v) => this.onLive(v),\n onNotice: (m) => this.set({ notices: [...this.snapshot.notices, m] }),\n });\n await store.pair({\n id: link.id,\n name: link.name,\n kind: this.deps.kind,\n ...(found.protocol ? { protocol: found.protocol } : {}),\n });\n this.set({ state: { step: 'connected', device: { id: link.id, name: link.name } } });\n } catch (err) {\n await this.link?.close();\n this.link = null;\n this.set({ state: { step: 'error', message: describeErrorOrOwn(err) } });\n }\n }\n\n private async keep(readings: DeviceReading[]): Promise<void> {\n const fresh = await this.deps.store.addReadings(readings);\n if (fresh.length) this.set({ readings: [...fresh.reverse(), ...this.snapshot.readings] });\n }\n\n private onLive(v: LiveValue) {\n this.set({ live: { ...this.snapshot.live, ...v } });\n if (this.hr && v.bpm) {\n const t = this.now();\n this.hr.samples.push({ t, value: v.bpm });\n this.hr.sum += v.bpm;\n this.hr.count += 1;\n this.hr.max = Math.max(this.hr.max, v.bpm);\n const rec = this.snapshot.recording;\n if (rec)\n this.set({ recording: { ...rec, avgHr: Math.round(this.hr.sum / this.hr.count), maxHr: this.hr.max } });\n }\n }\n\n /** Heart rate: start recording a session of the given kind (run, walk, cycle, strength ...). */\n startRecording(workoutType: string): void {\n this.hr = { samples: [], sum: 0, count: 0, max: 0 };\n this.set({ recording: { startedAt: this.now(), workoutType } });\n }\n\n /** Heart rate: end the session and keep it. Nothing is kept for a session with no heart rate. */\n async stopRecording(): Promise<HeartRateSession | null> {\n const rec = this.snapshot.recording;\n const hr = this.hr;\n this.hr = null;\n this.set({ recording: undefined });\n const device = this.link;\n if (!rec || !hr || !hr.count || !device) return null;\n const end = this.now();\n const session: HeartRateSession = {\n kind: 'heart-rate',\n id: readingId(device.id, 'heart-rate', rec.startedAt),\n at: rec.startedAt,\n end,\n deviceId: device.id,\n deviceName: device.name,\n workoutType: rec.workoutType,\n avgHr: Math.round(hr.sum / hr.count),\n maxHr: hr.max,\n samples: thinSamples(hr.samples),\n };\n await this.keep([session]);\n return session;\n }\n\n /** Continuous oximeter: keep the value on screen as a reading. */\n async saveLive(): Promise<void> {\n const live = this.snapshot.live;\n const device = this.link;\n if (!device || live?.spo2 === undefined) return;\n const at = this.now();\n await this.keep([\n {\n kind: 'spo2',\n id: readingId(device.id, 'spo2', at, live.spo2),\n at,\n deviceId: device.id,\n deviceName: device.name,\n spo2: live.spo2,\n ...(live.pulse !== undefined ? { pulse: live.pulse } : {}),\n },\n ]);\n }\n\n private onDisconnect() {\n this.driver?.stop();\n this.driver = null;\n const link = this.link;\n this.link = null;\n if (this.hr) void this.stopRecording();\n const s = this.snapshot.state;\n if (s.step === 'connected' && link) {\n // A scale or cuff switches off after measuring: the normal end.\n this.set({ state: { step: 'ended', device: { id: link.id, name: link.name } }, live: undefined });\n }\n }\n\n private clearScan() {\n this.stopScan?.();\n this.stopScan = null;\n if (this.hintTimer) clearTimeout(this.hintTimer);\n this.hintTimer = null;\n }\n\n /** Leave: finish what was being assembled, stop scanning, disconnect. */\n async close(): Promise<void> {\n if (this.hr) await this.stopRecording();\n this.clearScan();\n this.driver?.stop();\n this.driver = null;\n const link = this.link;\n this.link = null;\n this.closed = true;\n await link?.close();\n }\n}\n\nfunction describeErrorOrOwn(err: unknown): string {\n // The drivers' own messages are already in plain words (\"does not send weight the standard way\").\n if (err instanceof Error && /standard way|does not know how/.test(err.message)) return err.message;\n return describeBleError(err);\n}\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"session.js","sources":["../../../src/features/devices/session.ts"],"sourcesContent":["/**\n * One \"take a reading\" from start to end, for the screen: is Bluetooth on and\n * allowed, find the device (or the one already paired), connect, run its\n * driver, keep what it measures, and end cleanly when it goes to sleep (a\n * scale switches off seconds after a weighing; that is the normal end, not an\n * error).\n *\n * Heart-rate straps and continuous oximeters stream: the person starts and\n * stops a session (heart rate) or saves the value they see (oxygen).\n *\n * Plain class with injected transport and store, like the other stores: the\n * screen subscribes with useSyncExternalStore, tests drive it with fakes.\n */\nimport type { TimedValue } from '../health/model';\nimport { deviceKind, type DeviceKindId } from './catalog';\nimport { describeBleError, type BleTransport, type ConnectedLink, type FoundDevice, type RadioState } from './link';\nimport { detectBrand } from './brands';\nimport type { DeviceStore } from './deviceStore';\nimport { startDriver, type DriverHandle, type LiveValue, type PersonFacts } from './drivers';\nimport { readingId, thinSamples, type DeviceReading, type HeartRateSession } from './readings';\n\nexport type SessionState =\n | { step: 'starting' }\n | { step: 'unavailable'; reason: string }\n | { step: 'radio'; radio: Exclude<RadioState, 'ready'> }\n | { step: 'scanning'; found: FoundDevice[]; hint?: string }\n | { step: 'connecting'; device: FoundDevice }\n | { step: 'connected'; device: { id: string; name: string } }\n | { step: 'ended'; device: { id: string; name: string } }\n | { step: 'error'; message: string };\n\nexport interface SessionSnapshot {\n state: SessionState;\n kind: DeviceKindId;\n /** What this session measured, newest first. */\n readings: DeviceReading[];\n live?: LiveValue;\n recording?: { startedAt: number; workoutType: string; avgHr?: number; maxHr?: number };\n notices: string[];\n}\n\nexport interface SessionDeps {\n transport: BleTransport | null;\n store: Pick<DeviceStore, 'pair' | 'addReadings' | 'saveScaleUser' | 'person' | 'getSnapshot'>;\n kind: DeviceKindId;\n /** Reconnect to this paired device instead of offering a list. */\n deviceId?: string;\n now?: () => number;\n}\n\n/** Scanning this long without finding anything: tell the person how to wake it. */\nexport const SCAN_HINT_MS = 20_000;\n\nfunction ageFrom(birthDate: string | undefined, now: number): number | undefined {\n if (!birthDate) return undefined;\n const t = Date.parse(`${birthDate}T00:00:00Z`);\n return Number.isFinite(t) ? Math.floor((now - t) / (365.25 * 86_400_000)) : undefined;\n}\n\nexport class DeviceSession {\n private snapshot: SessionSnapshot;\n\n private listeners = new Set<() => void>();\n\n private stopScan: (() => void) | null = null;\n\n private link: ConnectedLink | null = null;\n\n private driver: DriverHandle | null = null;\n\n private hintTimer: ReturnType<typeof setTimeout> | null = null;\n\n private hr: { samples: TimedValue[]; sum: number; count: number; max: number } | null = null;\n\n private closed = false;\n\n constructor(private readonly deps: SessionDeps) {\n this.snapshot = { state: { step: 'starting' }, kind: deps.kind, readings: [], notices: [] };\n }\n\n subscribe = (listener: () => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n getSnapshot = (): SessionSnapshot => this.snapshot;\n\n private now(): number {\n return this.deps.now ? this.deps.now() : Date.now();\n }\n\n private set(patch: Partial<SessionSnapshot>) {\n if (this.closed) return;\n this.snapshot = { ...this.snapshot, ...patch };\n this.listeners.forEach((l) => l());\n }\n\n async start(): Promise<void> {\n const { transport } = this.deps;\n if (!transport) {\n this.set({\n state: { step: 'unavailable', reason: 'Bluetooth devices need the latest version of the Yantra app.' },\n });\n return;\n }\n if (!(await transport.requestPermissions())) {\n this.set({ state: { step: 'radio', radio: 'unauthorized' } });\n return;\n }\n const radio = await transport.radio();\n if (radio !== 'ready' && radio !== 'unknown') {\n this.set({ state: { step: 'radio', radio } });\n return;\n }\n this.scan();\n }\n\n /** Back to looking for the device (after the radio came on, or to measure again). */\n scan(): void {\n const { transport } = this.deps;\n if (!transport || this.closed) return;\n this.stopScan?.();\n this.set({ state: { step: 'scanning', found: [] } });\n this.hintTimer = setTimeout(() => {\n const s = this.snapshot.state;\n if (s.step === 'scanning' && !s.found.length) {\n this.set({ state: { ...s, hint: deviceKind(this.deps.kind).steps[0] } });\n }\n }, SCAN_HINT_MS);\n const kind = this.deps.kind;\n this.stopScan = transport.scan(\n {\n services: deviceKind(kind).services,\n // Brand devices do not advertise a standard service: recognise this kind of them.\n match: (adv) => {\n const brand = detectBrand(adv);\n return brand && brand.kind === kind ? brand : undefined;\n },\n },\n (found) => {\n const s = this.snapshot.state;\n if (s.step !== 'scanning') return;\n if (this.deps.deviceId && found.id === this.deps.deviceId) {\n void this.pick(found);\n return;\n }\n if (this.deps.deviceId) return;\n this.set({\n state: {\n ...s,\n hint: undefined,\n found: [...s.found, found].sort((a, b) => (b.rssi ?? -999) - (a.rssi ?? -999)),\n },\n });\n },\n (message) => this.set({ state: { step: 'error', message } }),\n );\n }\n\n async pick(found: FoundDevice): Promise<void> {\n const { transport, store } = this.deps;\n if (!transport) return;\n this.clearScan();\n this.set({ state: { step: 'connecting', device: found } });\n try {\n const link = await transport.connect(found.id, found.name, () => this.onDisconnect());\n this.link = link;\n const paired = store.getSnapshot().devices.find((d) => d.id === link.id);\n const person = await store.person();\n const facts: PersonFacts = {\n age: ageFrom(person?.birthDate, this.now()),\n sex: person?.sex,\n heightCm: person?.heightCm,\n birthDate: person?.birthDate,\n weightKg: paired?.lastWeightKg,\n };\n this.driver = await startDriver(this.deps.kind, link, {\n now: () => this.now(),\n person: facts,\n identity: found.protocol\n ? {\n protocol: found.protocol,\n mac: found.mac,\n deviceType: found.deviceType,\n model: found.model,\n kind: found.kind,\n }\n : undefined,\n scaleUser: paired?.scaleUser,\n saveScaleUser: (user) => store.saveScaleUser(link.id, user),\n onReading: (r) => void this.keep([r]),\n onLive: (v) => this.onLive(v),\n onNotice: (m) => this.set({ notices: [...this.snapshot.notices, m] }),\n });\n await store.pair({\n id: link.id,\n name: link.name,\n kind: this.deps.kind,\n ...(found.protocol ? { protocol: found.protocol } : {}),\n });\n this.set({ state: { step: 'connected', device: { id: link.id, name: link.name } } });\n } catch (err) {\n await this.link?.close();\n this.link = null;\n this.set({ state: { step: 'error', message: describeErrorOrOwn(err) } });\n }\n }\n\n private async keep(readings: DeviceReading[]): Promise<void> {\n const fresh = await this.deps.store.addReadings(readings);\n if (fresh.length) this.set({ readings: [...fresh.reverse(), ...this.snapshot.readings] });\n }\n\n private onLive(v: LiveValue) {\n this.set({ live: { ...this.snapshot.live, ...v } });\n if (this.hr && v.bpm) {\n const t = this.now();\n this.hr.samples.push({ t, value: v.bpm });\n this.hr.sum += v.bpm;\n this.hr.count += 1;\n this.hr.max = Math.max(this.hr.max, v.bpm);\n const rec = this.snapshot.recording;\n if (rec)\n this.set({ recording: { ...rec, avgHr: Math.round(this.hr.sum / this.hr.count), maxHr: this.hr.max } });\n }\n }\n\n /** Heart rate: start recording a session of the given kind (run, walk, cycle, strength ...). */\n startRecording(workoutType: string): void {\n this.hr = { samples: [], sum: 0, count: 0, max: 0 };\n this.set({ recording: { startedAt: this.now(), workoutType } });\n }\n\n /** Heart rate: end the session and keep it. Nothing is kept for a session with no heart rate. */\n async stopRecording(): Promise<HeartRateSession | null> {\n const rec = this.snapshot.recording;\n const hr = this.hr;\n this.hr = null;\n this.set({ recording: undefined });\n const device = this.link;\n if (!rec || !hr || !hr.count || !device) return null;\n const end = this.now();\n const session: HeartRateSession = {\n kind: 'heart-rate',\n id: readingId(device.id, 'heart-rate', rec.startedAt),\n at: rec.startedAt,\n end,\n deviceId: device.id,\n deviceName: device.name,\n workoutType: rec.workoutType,\n avgHr: Math.round(hr.sum / hr.count),\n maxHr: hr.max,\n samples: thinSamples(hr.samples),\n };\n await this.keep([session]);\n return session;\n }\n\n /** Continuous oximeter: keep the value on screen as a reading. */\n async saveLive(): Promise<void> {\n const live = this.snapshot.live;\n const device = this.link;\n if (!device || live?.spo2 === undefined) return;\n const at = this.now();\n await this.keep([\n {\n kind: 'spo2',\n id: readingId(device.id, 'spo2', at, live.spo2),\n at,\n deviceId: device.id,\n deviceName: device.name,\n spo2: live.spo2,\n ...(live.pulse !== undefined ? { pulse: live.pulse } : {}),\n },\n ]);\n }\n\n private onDisconnect() {\n this.driver?.stop();\n this.driver = null;\n const link = this.link;\n this.link = null;\n if (this.hr) void this.stopRecording();\n const s = this.snapshot.state;\n if (s.step === 'connected' && link) {\n // A scale or cuff switches off after measuring: the normal end.\n this.set({ state: { step: 'ended', device: { id: link.id, name: link.name } }, live: undefined });\n }\n }\n\n private clearScan() {\n this.stopScan?.();\n this.stopScan = null;\n if (this.hintTimer) clearTimeout(this.hintTimer);\n this.hintTimer = null;\n }\n\n /** Leave: finish what was being assembled, stop scanning, disconnect. */\n async close(): Promise<void> {\n if (this.hr) await this.stopRecording();\n this.clearScan();\n this.driver?.stop();\n this.driver = null;\n const link = this.link;\n this.link = null;\n this.closed = true;\n await link?.close();\n }\n}\n\nfunction describeErrorOrOwn(err: unknown): string {\n // The drivers' own messages are already in plain words (\"does not send weight the standard way\").\n if (err instanceof Error && /standard way|does not know how/.test(err.message)) return err.message;\n return describeBleError(err);\n}\n"],"names":["_a"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import {jsx,jsxs}from'react/jsx-runtime';import {useMemo,useState,useRef,useCallback,useEffect}from'react';import {Alert,BackHandler,Share,View,StyleSheet,ScrollView,Text,Linking}from'react-native';import {MaterialCommunityIcons}from'@expo/vector-icons';import {useSafeInsets}from'../canvas/withRemoteSafeArea.js';import {createRemoteUploadMedia,createRemoteConnectorExec}from'../canvas/videoEditor/remoteMediaClient.js';import {useAppTheme}from'../../theme/AppThemeProvider.js';import {Tap,DIM}from'../inbox/components/Tap.js';import {DictationList}from'./components/DictationList.js';import {NoteView}from'./components/NoteView.js';import {RecordingPanel,StartPanel}from'./components/RecordPanel.js';import {writeNoteFromRecording,dictateErrorText}from'./dictateClient.js';import {dictateTheme}from'./dictateTheme.js';import {createDictationStore}from'./dictationStore.js';import {noteToText}from'./noteText.js';import {useDictationRecorder,MicrophoneDeniedError}from'./useDictationRecorder.js';var __defProp = Object.defineProperty;
1
+ import {jsx,jsxs}from'react/jsx-runtime';import {useMemo,useState,useEffect,useCallback,useRef}from'react';import {Alert,BackHandler,Share,View,StyleSheet,ScrollView,Text,Linking}from'react-native';import {MaterialCommunityIcons}from'@expo/vector-icons';import {useSafeInsets}from'../canvas/withRemoteSafeArea.js';import {createRemoteUploadMedia,createRemoteConnectorExec}from'../canvas/videoEditor/remoteMediaClient.js';import {useAppTheme}from'../../theme/AppThemeProvider.js';import {Tap,DIM}from'../inbox/components/Tap.js';import {DictationList}from'./components/DictationList.js';import {NoteView}from'./components/NoteView.js';import {RecordingPanel,StartPanel}from'./components/RecordPanel.js';import {writeNoteFromRecording,dictateErrorText,suggestFromNote,suggestErrorText}from'./dictateClient.js';import {readShowIdentifiers,writeShowIdentifiers}from'./dictatePreference.js';import {dictateTheme}from'./dictateTheme.js';import {createDictationStore}from'./dictationStore.js';import {noteToText}from'./noteText.js';import {labelText}from'./privacy.js';import {shownNote}from'./shownNote.js';import {useDictationRecorder,MicrophoneDeniedError}from'./useDictationRecorder.js';var __defProp = Object.defineProperty;
2
2
  var __defProps = Object.defineProperties;
3
3
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
@@ -41,7 +41,7 @@ function DictateExperience({
41
41
  deps,
42
42
  store: injectedStore
43
43
  }) {
44
- var _a, _b;
44
+ var _a, _b, _c;
45
45
  const appTheme = useAppTheme();
46
46
  const theme = useMemo(() => dictateTheme(hostDark != null ? hostDark : appTheme.isDark), [hostDark, appTheme.isDark]);
47
47
  const insets = useSafeInsets();
@@ -55,6 +55,17 @@ function DictateExperience({
55
55
  const [starting, setStarting] = useState(false);
56
56
  const [error, setError] = useState(null);
57
57
  const [micDenied, setMicDenied] = useState(false);
58
+ const [showIdentifiers, setShowIdentifiers] = useState(false);
59
+ const [suggestStates, setSuggestStates] = useState({});
60
+ useEffect(() => {
61
+ void readShowIdentifiers().then(setShowIdentifiers);
62
+ }, []);
63
+ const toggleIdentifiers = useCallback(() => {
64
+ setShowIdentifiers((show) => {
65
+ writeShowIdentifiers(!show);
66
+ return !show;
67
+ });
68
+ }, []);
58
69
  const tokenRef = useRef(token);
59
70
  tokenRef.current = token;
60
71
  const agent = useMemo(() => deps != null ? deps : {
@@ -103,6 +114,7 @@ function DictateExperience({
103
114
  status: "ready",
104
115
  note: written.note,
105
116
  transcript: written.transcript,
117
+ redaction: written.redaction,
106
118
  model: written.model,
107
119
  error: void 0
108
120
  });
@@ -114,9 +126,9 @@ function DictateExperience({
114
126
  });
115
127
  } finally {
116
128
  setStages((_a2) => {
117
- var _c = _a2, {
129
+ var _c2 = _a2, {
118
130
  [_b2 = record.id]: _done
119
- } = _c, rest = __objRest(_c, [
131
+ } = _c2, rest = __objRest(_c2, [
120
132
  __restKey(_b2)
121
133
  ]);
122
134
  return rest;
@@ -124,6 +136,40 @@ function DictateExperience({
124
136
  await refresh();
125
137
  }
126
138
  }, [agent, store, refresh]);
139
+ const suggest = useCallback(async (record) => {
140
+ if (!record.redaction) return;
141
+ setSuggestStates((s) => __spreadProps(__spreadValues({}, s), {
142
+ [record.id]: {
143
+ status: "loading"
144
+ }
145
+ }));
146
+ try {
147
+ const {
148
+ items,
149
+ identifiers
150
+ } = await suggestFromNote(record.redaction, agent);
151
+ await store.update(record.id, {
152
+ suggestions: {
153
+ items,
154
+ identifiers,
155
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
156
+ }
157
+ });
158
+ await refresh();
159
+ setSuggestStates((s) => __spreadProps(__spreadValues({}, s), {
160
+ [record.id]: {
161
+ status: "idle"
162
+ }
163
+ }));
164
+ } catch (err) {
165
+ setSuggestStates((s) => __spreadProps(__spreadValues({}, s), {
166
+ [record.id]: {
167
+ status: "error",
168
+ message: suggestErrorText(err)
169
+ }
170
+ }));
171
+ }
172
+ }, [agent, store, refresh]);
127
173
  const start = useCallback(async () => {
128
174
  setError(null);
129
175
  setStarting(true);
@@ -217,11 +263,13 @@ function DictateExperience({
217
263
  const open = screen.kind === "note" ? (_a = records.find((r) => r.id === screen.id)) != null ? _a : null : null;
218
264
  const share = useCallback(() => {
219
265
  if (!(open == null ? void 0 : open.note)) return;
266
+ const shown = shownNote(open, open.note, showIdentifiers);
267
+ const text = noteToText(shown.note, shown.transcript, shown.suggestions);
220
268
  void Share.share({
221
- title: open.note.title,
222
- message: noteToText(open.note)
269
+ title: labelText(shown.note.title),
270
+ message: labelText(text)
223
271
  });
224
- }, [open]);
272
+ }, [open, showIdentifiers]);
225
273
  const remove = useCallback(() => {
226
274
  if (!open) return;
227
275
  const removed = open.note ? "The note and transcript are removed from this phone." : "The recording is removed from this phone.";
@@ -247,6 +295,10 @@ function DictateExperience({
247
295
  if (screen.kind === "note" && open) {
248
296
  body = /* @__PURE__ */ jsx(NoteView, { record: open, stage: (_b = stages[open.id]) != null ? _b : null, theme, bottomInset: insets.bottom, onRetry: () => {
249
297
  void write(open);
298
+ }, showIdentifiers, onToggleIdentifiers: toggleIdentifiers, suggestState: (_c = suggestStates[open.id]) != null ? _c : {
299
+ status: "idle"
300
+ }, onSuggest: () => {
301
+ void suggest(open);
250
302
  } });
251
303
  } else if (screen.kind === "record" && recorder.phase !== "idle") {
252
304
  body = /* @__PURE__ */ jsx(View, { style: styles.recordBody, children: /* @__PURE__ */ jsx(RecordingPanel, { theme, phase: recorder.phase, pauseReason: recorder.pauseReason, elapsedMs: recorder.elapsedMs, level: recorder.level, noInput: recorder.noInput, onPause: recorder.pause, onResume: recorder.resume, onDone: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"DictateExperience.js","sources":["../../../src/features/dictate/DictateExperience.tsx"],"sourcesContent":["/**\n * Dictate: a clinician speaks a visit note; Yantra writes it up as a SOAP note\n * with the points to check before it is used.\n *\n * Record (screen kept awake, paused honestly if the app leaves the screen) ->\n * the recording is kept on the phone at once -> uploaded and handed to\n * YANTRA_MEETINGS_CLINICAL_NOTE (medical speech-to-text, then the note) ->\n * the note is saved on the phone and the recording deleted. A note that could\n * not be written keeps its recording for Retry, across restarts.\n *\n * The host screen passes the session (token + surface index URL); everything\n * else - the recorder, the store, the agent calls - lives here.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Alert, BackHandler, Linking, ScrollView, Share, StyleSheet, Text, View } from 'react-native';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { useSafeInsets } from '../canvas/withRemoteSafeArea';\nimport { createRemoteConnectorExec, createRemoteUploadMedia } from '../canvas/videoEditor/remoteMediaClient';\nimport { useAppTheme } from '../../theme/AppThemeProvider';\nimport { DIM, Tap } from '../inbox/components/Tap';\nimport { DictationList } from './components/DictationList';\nimport { NoteView } from './components/NoteView';\nimport { RecordingPanel, StartPanel } from './components/RecordPanel';\nimport { dictateErrorText, writeNoteFromRecording, type DictateDeps, type DictateStage } from './dictateClient';\nimport { dictateTheme } from './dictateTheme';\nimport { createDictationStore, type DictationStore } from './dictationStore';\nimport { noteToText } from './noteText';\nimport type { DictationRecord } from './types';\nimport { MicrophoneDeniedError, useDictationRecorder } from './useDictationRecorder';\n\nexport interface DictateExperienceProps {\n isDark?: boolean;\n token?: string | null;\n /** The surface index URL; the agent (uploads, the note) is derived from it. */\n indexUrl: string;\n sessionError?: string | null;\n onRetrySession?: () => void;\n onExit: () => void;\n /** Tests and previews inject these; the app builds them from the session. */\n deps?: DictateDeps;\n store?: DictationStore;\n}\n\ntype Screen = { kind: 'home' } | { kind: 'record' } | { kind: 'note'; id: string };\n\nexport const PREVIEW_NOTICE =\n 'Preview: dictate made-up visits only. Real patient information needs your practice’s HIPAA agreement with Yantra first.';\n\nexport function DictateExperience({\n isDark: hostDark,\n token,\n indexUrl,\n sessionError,\n onRetrySession,\n onExit,\n deps,\n store: injectedStore,\n}: DictateExperienceProps) {\n const appTheme = useAppTheme();\n const theme = useMemo(() => dictateTheme(hostDark ?? appTheme.isDark), [hostDark, appTheme.isDark]);\n const insets = useSafeInsets();\n const store = useMemo(() => injectedStore ?? createDictationStore(), [injectedStore]);\n const recorder = useDictationRecorder();\n const [screen, setScreen] = useState<Screen>({ kind: 'home' });\n const [records, setRecords] = useState<DictationRecord[]>([]);\n const [stages, setStages] = useState<Record<string, DictateStage>>({});\n const [starting, setStarting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [micDenied, setMicDenied] = useState(false);\n\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const agent = useMemo<DictateDeps>(\n () =>\n deps ?? {\n exec: (connector, action, params, options) => {\n const { current } = tokenRef;\n if (!current) return Promise.reject(new Error('Network request failed: not signed in yet'));\n return createRemoteConnectorExec(current, indexUrl)(connector, action, params, options);\n },\n upload: (uri) => {\n const { current } = tokenRef;\n if (!current) return Promise.reject(new Error('Network request failed: not signed in yet'));\n return createRemoteUploadMedia(current, indexUrl)(uri);\n },\n },\n [deps, indexUrl],\n );\n\n const refresh = useCallback(async () => setRecords(await store.list()), [store]);\n\n // A note being written when the app last closed never finished: offer it again.\n useEffect(() => {\n void (async () => {\n for (const r of await store.list()) {\n if (r.status === 'writing') await store.update(r.id, { status: r.audioUri ? 'saved' : 'failed' });\n }\n await refresh();\n })();\n }, [store, refresh]);\n\n const write = useCallback(\n async (record: DictationRecord) => {\n if (!record.audioUri) return;\n setStages((s) => ({ ...s, [record.id]: 'uploading' }));\n await store.update(record.id, { status: 'writing', error: undefined });\n await refresh();\n try {\n const written = await writeNoteFromRecording(record.audioUri, agent, {}, (stage) =>\n setStages((s) => ({ ...s, [record.id]: stage })),\n );\n await store.update(record.id, {\n status: 'ready',\n note: written.note,\n transcript: written.transcript,\n model: written.model,\n error: undefined,\n });\n await store.dropAudio(record.id);\n } catch (err) {\n await store.update(record.id, { status: 'failed', error: dictateErrorText(err) });\n } finally {\n setStages(({ [record.id]: _done, ...rest }) => rest);\n await refresh();\n }\n },\n [agent, store, refresh],\n );\n\n const start = useCallback(async () => {\n setError(null);\n setStarting(true);\n try {\n await recorder.start();\n setMicDenied(false);\n setScreen({ kind: 'record' });\n } catch (err) {\n // A declined microphone is not an error to retry: it is a setting, so offer the way there.\n if (err instanceof MicrophoneDeniedError) setMicDenied(true);\n else setError('The microphone could not start. Try again.');\n } finally {\n setStarting(false);\n }\n }, [recorder]);\n\n const done = useCallback(async () => {\n const out = await recorder.finish();\n if (!out) {\n setScreen({ kind: 'home' });\n setError('The recording could not be saved. Try again.');\n return;\n }\n const record = await store.keepRecording(out.uri, out.durationMs);\n await refresh();\n setScreen({ kind: 'note', id: record.id });\n void write(record);\n }, [recorder, store, refresh, write]);\n\n const discard = useCallback(() => {\n Alert.alert('Discard this dictation?', 'The recording is deleted and no note is written.', [\n { text: 'Keep dictating', style: 'cancel' },\n {\n text: 'Discard',\n style: 'destructive',\n onPress: () => {\n void (async () => {\n const out = await recorder.finish();\n if (out) await store.discardRecording(out.uri);\n setScreen({ kind: 'home' });\n })();\n },\n },\n ]);\n }, [recorder, store]);\n\n const back = useCallback(() => {\n if (screen.kind === 'record') {\n if (recorder.phase === 'recording') recorder.pause();\n Alert.alert('Stop dictating?', 'Write the note from what you have recorded, or discard it.', [\n { text: 'Keep dictating', style: 'cancel' },\n {\n text: 'Write the note',\n onPress: () => {\n void done();\n },\n },\n {\n text: 'Discard',\n style: 'destructive',\n onPress: () => {\n void (async () => {\n const out = await recorder.finish();\n if (out) await store.discardRecording(out.uri);\n setScreen({ kind: 'home' });\n })();\n },\n },\n ]);\n return true;\n }\n if (screen.kind === 'note') {\n setScreen({ kind: 'home' });\n return true;\n }\n onExit();\n return true;\n }, [screen.kind, recorder, done, store, onExit]);\n\n useEffect(() => {\n const sub = BackHandler.addEventListener('hardwareBackPress', back);\n return () => sub.remove();\n }, [back]);\n\n const open = screen.kind === 'note' ? (records.find((r) => r.id === screen.id) ?? null) : null;\n\n const share = useCallback(() => {\n if (!open?.note) return;\n void Share.share({ title: open.note.title, message: noteToText(open.note) });\n }, [open]);\n\n const remove = useCallback(() => {\n if (!open) return;\n const removed = open.note\n ? 'The note and transcript are removed from this phone.'\n : 'The recording is removed from this phone.';\n Alert.alert('Delete this dictation?', removed, [\n { text: 'Cancel', style: 'cancel' },\n {\n text: 'Delete',\n style: 'destructive',\n onPress: () => {\n void (async () => {\n await store.remove(open.id);\n await refresh();\n setScreen({ kind: 'home' });\n })();\n },\n },\n ]);\n }, [open, store, refresh]);\n\n const title = screen.kind === 'note' ? 'Note' : 'Dictate';\n\n // The note, the live recording, or the home list - whichever screen is open.\n let body: React.ReactNode;\n if (screen.kind === 'note' && open) {\n body = (\n <NoteView\n record={open}\n stage={stages[open.id] ?? null}\n theme={theme}\n bottomInset={insets.bottom}\n onRetry={() => {\n void write(open);\n }}\n />\n );\n } else if (screen.kind === 'record' && recorder.phase !== 'idle') {\n body = (\n <View style={styles.recordBody}>\n <RecordingPanel\n theme={theme}\n phase={recorder.phase}\n pauseReason={recorder.pauseReason}\n elapsedMs={recorder.elapsedMs}\n level={recorder.level}\n noInput={recorder.noInput}\n onPause={recorder.pause}\n onResume={recorder.resume}\n onDone={() => {\n void done();\n }}\n onDiscard={discard}\n />\n </View>\n );\n } else {\n body = (\n <ScrollView contentContainerStyle={[styles.home, { paddingBottom: 24 + insets.bottom }]}>\n <Text style={[styles.lead, { color: theme.ink }]}>Dictate a visit</Text>\n <Text style={[styles.leadDetail, { color: theme.muted }]}>\n Speak your note. Yantra writes it up as a SOAP note and lists what to check before you use it.\n </Text>\n <StartPanel\n theme={theme}\n starting={starting}\n recordsInBackground={recorder.recordsInBackground}\n onStart={() => {\n void start();\n }}\n />\n {error ? <Text style={[styles.error, { color: theme.crit }]}>{error}</Text> : null}\n {micDenied ? (\n <View style={[styles.banner, { backgroundColor: theme.recSoft }]}>\n <MaterialCommunityIcons name=\"microphone-off\" size={18} color={theme.crit} />\n <Text style={[styles.bannerText, { color: theme.crit }]}>\n Microphone access is off for Yantra.\n </Text>\n <Tap\n onPress={() => {\n void Linking.openSettings();\n }}\n accessibilityRole=\"button\"\n style={[styles.bannerAction, { backgroundColor: theme.card, borderColor: theme.border }]}\n pressedStyle={DIM}\n >\n <Text style={[styles.bannerActionText, { color: theme.ink }]}>Open Settings</Text>\n </Tap>\n </View>\n ) : null}\n {sessionError ? (\n <Tap\n onPress={onRetrySession}\n accessibilityRole=\"button\"\n style={[styles.banner, { backgroundColor: theme.recSoft }]}\n pressedStyle={DIM}\n >\n <Text style={[styles.bannerText, { color: theme.crit }]}>\n Not connected, so notes cannot be written yet. Tap to retry.\n </Text>\n </Tap>\n ) : null}\n <View\n style={[\n styles.banner,\n {\n backgroundColor: theme.warnSoft,\n borderColor: theme.warnBorder,\n borderWidth: StyleSheet.hairlineWidth,\n },\n ]}\n >\n <MaterialCommunityIcons name=\"shield-alert-outline\" size={18} color={theme.warnInk} />\n <Text style={[styles.bannerText, { color: theme.warnInk }]}>{PREVIEW_NOTICE}</Text>\n </View>\n <Text style={[styles.sectionLabel, { color: theme.muted }]}>RECENT</Text>\n <DictationList records={records} theme={theme} onOpen={(r) => setScreen({ kind: 'note', id: r.id })} />\n </ScrollView>\n );\n }\n\n return (\n <View style={[styles.screen, { backgroundColor: theme.screen, paddingTop: insets.top }]}>\n <View style={[styles.header, { borderBottomColor: theme.border }]}>\n <Tap\n onPress={back}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Back\"\n style={styles.headerButton}\n pressedStyle={DIM}\n >\n <MaterialCommunityIcons name=\"chevron-left\" size={28} color={theme.ink} />\n </Tap>\n <Text style={[styles.headerTitle, { color: theme.ink }]}>{title}</Text>\n <View style={styles.headerActions}>\n {open?.note ? (\n <Tap\n onPress={share}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Share the note\"\n style={styles.headerButton}\n pressedStyle={DIM}\n >\n <MaterialCommunityIcons name=\"export-variant\" size={22} color={theme.ink} />\n </Tap>\n ) : null}\n {/* Any dictation can go, note or not; just not while its note is being written. */}\n {open && !stages[open.id] ? (\n <Tap\n onPress={remove}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Delete\"\n style={styles.headerButton}\n pressedStyle={DIM}\n >\n <MaterialCommunityIcons name=\"trash-can-outline\" size={22} color={theme.ink} />\n </Tap>\n ) : null}\n </View>\n </View>\n\n {body}\n </View>\n );\n}\n\nexport default DictateExperience;\n\nconst styles = StyleSheet.create({\n screen: { flex: 1 },\n header: {\n height: 52,\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 4,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n headerButton: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' },\n headerTitle: { flex: 1, fontSize: 17, fontWeight: '600' },\n headerActions: { flexDirection: 'row', alignItems: 'center' },\n home: { paddingHorizontal: 16, paddingTop: 20 },\n lead: { fontSize: 24, fontWeight: '700' },\n leadDetail: { marginTop: 6, fontSize: 15, lineHeight: 21 },\n error: { textAlign: 'center', fontSize: 13, marginBottom: 8 },\n banner: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 10,\n borderRadius: 12,\n paddingHorizontal: 12,\n paddingVertical: 10,\n marginBottom: 12,\n },\n bannerText: { flex: 1, fontSize: 13, lineHeight: 18 },\n bannerAction: {\n paddingHorizontal: 12,\n paddingVertical: 7,\n borderRadius: 999,\n borderWidth: StyleSheet.hairlineWidth,\n },\n bannerActionText: { fontSize: 13, fontWeight: '600' },\n sectionLabel: { marginTop: 12, marginBottom: 8, fontSize: 12, fontWeight: '700', letterSpacing: 0.8 },\n recordBody: { flex: 1, justifyContent: 'center', paddingHorizontal: 16 },\n});\n"],"names":["_b","_a"],"mappings":"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"}
1
+ {"version":3,"file":"DictateExperience.js","sources":["../../../src/features/dictate/DictateExperience.tsx"],"sourcesContent":["/**\n * Dictate: a clinician speaks a visit note; Yantra writes it up as a SOAP note\n * with the points to check before it is used.\n *\n * Record (screen kept awake, paused honestly if the app leaves the screen) ->\n * the recording is kept on the phone at once -> uploaded and handed to\n * YANTRA_MEETINGS_CLINICAL_NOTE (medical speech-to-text, then the note) ->\n * the note is saved on the phone and the recording deleted. A note that could\n * not be written keeps its recording for Retry, across restarts.\n *\n * The host screen passes the session (token + surface index URL); everything\n * else - the recorder, the store, the agent calls - lives here.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Alert, BackHandler, Linking, ScrollView, Share, StyleSheet, Text, View } from 'react-native';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport { useSafeInsets } from '../canvas/withRemoteSafeArea';\nimport { createRemoteConnectorExec, createRemoteUploadMedia } from '../canvas/videoEditor/remoteMediaClient';\nimport { useAppTheme } from '../../theme/AppThemeProvider';\nimport { DIM, Tap } from '../inbox/components/Tap';\nimport { DictationList } from './components/DictationList';\nimport { NoteView } from './components/NoteView';\nimport { RecordingPanel, StartPanel } from './components/RecordPanel';\nimport {\n dictateErrorText,\n suggestErrorText,\n suggestFromNote,\n writeNoteFromRecording,\n type DictateDeps,\n type DictateStage,\n} from './dictateClient';\nimport { readShowIdentifiers, writeShowIdentifiers } from './dictatePreference';\nimport { dictateTheme } from './dictateTheme';\nimport { createDictationStore, type DictationStore } from './dictationStore';\nimport { noteToText } from './noteText';\nimport { labelText } from './privacy';\nimport { shownNote, type SuggestState } from './shownNote';\nimport type { DictationRecord } from './types';\nimport { MicrophoneDeniedError, useDictationRecorder } from './useDictationRecorder';\n\nexport interface DictateExperienceProps {\n isDark?: boolean;\n token?: string | null;\n /** The surface index URL; the agent (uploads, the note) is derived from it. */\n indexUrl: string;\n sessionError?: string | null;\n onRetrySession?: () => void;\n onExit: () => void;\n /** Tests and previews inject these; the app builds them from the session. */\n deps?: DictateDeps;\n store?: DictationStore;\n}\n\ntype Screen = { kind: 'home' } | { kind: 'record' } | { kind: 'note'; id: string };\n\nexport const PREVIEW_NOTICE =\n 'Preview: dictate made-up visits only. Real patient information needs your practice’s HIPAA agreement with Yantra first.';\n\nexport function DictateExperience({\n isDark: hostDark,\n token,\n indexUrl,\n sessionError,\n onRetrySession,\n onExit,\n deps,\n store: injectedStore,\n}: DictateExperienceProps) {\n const appTheme = useAppTheme();\n const theme = useMemo(() => dictateTheme(hostDark ?? appTheme.isDark), [hostDark, appTheme.isDark]);\n const insets = useSafeInsets();\n const store = useMemo(() => injectedStore ?? createDictationStore(), [injectedStore]);\n const recorder = useDictationRecorder();\n const [screen, setScreen] = useState<Screen>({ kind: 'home' });\n const [records, setRecords] = useState<DictationRecord[]>([]);\n const [stages, setStages] = useState<Record<string, DictateStage>>({});\n const [starting, setStarting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [micDenied, setMicDenied] = useState(false);\n const [showIdentifiers, setShowIdentifiers] = useState(false);\n const [suggestStates, setSuggestStates] = useState<Record<string, SuggestState>>({});\n\n useEffect(() => {\n void readShowIdentifiers().then(setShowIdentifiers);\n }, []);\n const toggleIdentifiers = useCallback(() => {\n setShowIdentifiers((show) => {\n writeShowIdentifiers(!show);\n return !show;\n });\n }, []);\n\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const agent = useMemo<DictateDeps>(\n () =>\n deps ?? {\n exec: (connector, action, params, options) => {\n const { current } = tokenRef;\n if (!current) return Promise.reject(new Error('Network request failed: not signed in yet'));\n return createRemoteConnectorExec(current, indexUrl)(connector, action, params, options);\n },\n upload: (uri) => {\n const { current } = tokenRef;\n if (!current) return Promise.reject(new Error('Network request failed: not signed in yet'));\n return createRemoteUploadMedia(current, indexUrl)(uri);\n },\n },\n [deps, indexUrl],\n );\n\n const refresh = useCallback(async () => setRecords(await store.list()), [store]);\n\n // A note being written when the app last closed never finished: offer it again.\n useEffect(() => {\n void (async () => {\n for (const r of await store.list()) {\n if (r.status === 'writing') await store.update(r.id, { status: r.audioUri ? 'saved' : 'failed' });\n }\n await refresh();\n })();\n }, [store, refresh]);\n\n const write = useCallback(\n async (record: DictationRecord) => {\n if (!record.audioUri) return;\n setStages((s) => ({ ...s, [record.id]: 'uploading' }));\n await store.update(record.id, { status: 'writing', error: undefined });\n await refresh();\n try {\n const written = await writeNoteFromRecording(record.audioUri, agent, {}, (stage) =>\n setStages((s) => ({ ...s, [record.id]: stage })),\n );\n await store.update(record.id, {\n status: 'ready',\n note: written.note,\n transcript: written.transcript,\n redaction: written.redaction,\n model: written.model,\n error: undefined,\n });\n await store.dropAudio(record.id);\n } catch (err) {\n await store.update(record.id, { status: 'failed', error: dictateErrorText(err) });\n } finally {\n setStages(({ [record.id]: _done, ...rest }) => rest);\n await refresh();\n }\n },\n [agent, store, refresh],\n );\n\n const suggest = useCallback(\n async (record: DictationRecord) => {\n if (!record.redaction) return;\n setSuggestStates((s) => ({ ...s, [record.id]: { status: 'loading' } }));\n try {\n const { items, identifiers } = await suggestFromNote(record.redaction, agent);\n await store.update(record.id, {\n suggestions: { items, identifiers, createdAt: new Date().toISOString() },\n });\n await refresh();\n setSuggestStates((s) => ({ ...s, [record.id]: { status: 'idle' } }));\n } catch (err) {\n setSuggestStates((s) => ({ ...s, [record.id]: { status: 'error', message: suggestErrorText(err) } }));\n }\n },\n [agent, store, refresh],\n );\n\n const start = useCallback(async () => {\n setError(null);\n setStarting(true);\n try {\n await recorder.start();\n setMicDenied(false);\n setScreen({ kind: 'record' });\n } catch (err) {\n // A declined microphone is not an error to retry: it is a setting, so offer the way there.\n if (err instanceof MicrophoneDeniedError) setMicDenied(true);\n else setError('The microphone could not start. Try again.');\n } finally {\n setStarting(false);\n }\n }, [recorder]);\n\n const done = useCallback(async () => {\n const out = await recorder.finish();\n if (!out) {\n setScreen({ kind: 'home' });\n setError('The recording could not be saved. Try again.');\n return;\n }\n const record = await store.keepRecording(out.uri, out.durationMs);\n await refresh();\n setScreen({ kind: 'note', id: record.id });\n void write(record);\n }, [recorder, store, refresh, write]);\n\n const discard = useCallback(() => {\n Alert.alert('Discard this dictation?', 'The recording is deleted and no note is written.', [\n { text: 'Keep dictating', style: 'cancel' },\n {\n text: 'Discard',\n style: 'destructive',\n onPress: () => {\n void (async () => {\n const out = await recorder.finish();\n if (out) await store.discardRecording(out.uri);\n setScreen({ kind: 'home' });\n })();\n },\n },\n ]);\n }, [recorder, store]);\n\n const back = useCallback(() => {\n if (screen.kind === 'record') {\n if (recorder.phase === 'recording') recorder.pause();\n Alert.alert('Stop dictating?', 'Write the note from what you have recorded, or discard it.', [\n { text: 'Keep dictating', style: 'cancel' },\n {\n text: 'Write the note',\n onPress: () => {\n void done();\n },\n },\n {\n text: 'Discard',\n style: 'destructive',\n onPress: () => {\n void (async () => {\n const out = await recorder.finish();\n if (out) await store.discardRecording(out.uri);\n setScreen({ kind: 'home' });\n })();\n },\n },\n ]);\n return true;\n }\n if (screen.kind === 'note') {\n setScreen({ kind: 'home' });\n return true;\n }\n onExit();\n return true;\n }, [screen.kind, recorder, done, store, onExit]);\n\n useEffect(() => {\n const sub = BackHandler.addEventListener('hardwareBackPress', back);\n return () => sub.remove();\n }, [back]);\n\n const open = screen.kind === 'note' ? (records.find((r) => r.id === screen.id) ?? null) : null;\n\n // What is on screen goes: hidden identifiers stay hidden (\"[Name 1]\"), shown ones go as dictated.\n const share = useCallback(() => {\n if (!open?.note) return;\n const shown = shownNote(open, open.note, showIdentifiers);\n const text = noteToText(shown.note, shown.transcript, shown.suggestions);\n void Share.share({ title: labelText(shown.note.title), message: labelText(text) });\n }, [open, showIdentifiers]);\n\n const remove = useCallback(() => {\n if (!open) return;\n const removed = open.note\n ? 'The note and transcript are removed from this phone.'\n : 'The recording is removed from this phone.';\n Alert.alert('Delete this dictation?', removed, [\n { text: 'Cancel', style: 'cancel' },\n {\n text: 'Delete',\n style: 'destructive',\n onPress: () => {\n void (async () => {\n await store.remove(open.id);\n await refresh();\n setScreen({ kind: 'home' });\n })();\n },\n },\n ]);\n }, [open, store, refresh]);\n\n const title = screen.kind === 'note' ? 'Note' : 'Dictate';\n\n // The note, the live recording, or the home list - whichever screen is open.\n let body: React.ReactNode;\n if (screen.kind === 'note' && open) {\n body = (\n <NoteView\n record={open}\n stage={stages[open.id] ?? null}\n theme={theme}\n bottomInset={insets.bottom}\n onRetry={() => {\n void write(open);\n }}\n showIdentifiers={showIdentifiers}\n onToggleIdentifiers={toggleIdentifiers}\n suggestState={suggestStates[open.id] ?? { status: 'idle' }}\n onSuggest={() => {\n void suggest(open);\n }}\n />\n );\n } else if (screen.kind === 'record' && recorder.phase !== 'idle') {\n body = (\n <View style={styles.recordBody}>\n <RecordingPanel\n theme={theme}\n phase={recorder.phase}\n pauseReason={recorder.pauseReason}\n elapsedMs={recorder.elapsedMs}\n level={recorder.level}\n noInput={recorder.noInput}\n onPause={recorder.pause}\n onResume={recorder.resume}\n onDone={() => {\n void done();\n }}\n onDiscard={discard}\n />\n </View>\n );\n } else {\n body = (\n <ScrollView contentContainerStyle={[styles.home, { paddingBottom: 24 + insets.bottom }]}>\n <Text style={[styles.lead, { color: theme.ink }]}>Dictate a visit</Text>\n <Text style={[styles.leadDetail, { color: theme.muted }]}>\n Speak your note. Yantra writes it up as a SOAP note and lists what to check before you use it.\n </Text>\n <StartPanel\n theme={theme}\n starting={starting}\n recordsInBackground={recorder.recordsInBackground}\n onStart={() => {\n void start();\n }}\n />\n {error ? <Text style={[styles.error, { color: theme.crit }]}>{error}</Text> : null}\n {micDenied ? (\n <View style={[styles.banner, { backgroundColor: theme.recSoft }]}>\n <MaterialCommunityIcons name=\"microphone-off\" size={18} color={theme.crit} />\n <Text style={[styles.bannerText, { color: theme.crit }]}>\n Microphone access is off for Yantra.\n </Text>\n <Tap\n onPress={() => {\n void Linking.openSettings();\n }}\n accessibilityRole=\"button\"\n style={[styles.bannerAction, { backgroundColor: theme.card, borderColor: theme.border }]}\n pressedStyle={DIM}\n >\n <Text style={[styles.bannerActionText, { color: theme.ink }]}>Open Settings</Text>\n </Tap>\n </View>\n ) : null}\n {sessionError ? (\n <Tap\n onPress={onRetrySession}\n accessibilityRole=\"button\"\n style={[styles.banner, { backgroundColor: theme.recSoft }]}\n pressedStyle={DIM}\n >\n <Text style={[styles.bannerText, { color: theme.crit }]}>\n Not connected, so notes cannot be written yet. Tap to retry.\n </Text>\n </Tap>\n ) : null}\n <View\n style={[\n styles.banner,\n {\n backgroundColor: theme.warnSoft,\n borderColor: theme.warnBorder,\n borderWidth: StyleSheet.hairlineWidth,\n },\n ]}\n >\n <MaterialCommunityIcons name=\"shield-alert-outline\" size={18} color={theme.warnInk} />\n <Text style={[styles.bannerText, { color: theme.warnInk }]}>{PREVIEW_NOTICE}</Text>\n </View>\n <Text style={[styles.sectionLabel, { color: theme.muted }]}>RECENT</Text>\n <DictationList records={records} theme={theme} onOpen={(r) => setScreen({ kind: 'note', id: r.id })} />\n </ScrollView>\n );\n }\n\n return (\n <View style={[styles.screen, { backgroundColor: theme.screen, paddingTop: insets.top }]}>\n <View style={[styles.header, { borderBottomColor: theme.border }]}>\n <Tap\n onPress={back}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Back\"\n style={styles.headerButton}\n pressedStyle={DIM}\n >\n <MaterialCommunityIcons name=\"chevron-left\" size={28} color={theme.ink} />\n </Tap>\n <Text style={[styles.headerTitle, { color: theme.ink }]}>{title}</Text>\n <View style={styles.headerActions}>\n {open?.note ? (\n <Tap\n onPress={share}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Share the note\"\n style={styles.headerButton}\n pressedStyle={DIM}\n >\n <MaterialCommunityIcons name=\"export-variant\" size={22} color={theme.ink} />\n </Tap>\n ) : null}\n {/* Any dictation can go, note or not; just not while its note is being written. */}\n {open && !stages[open.id] ? (\n <Tap\n onPress={remove}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Delete\"\n style={styles.headerButton}\n pressedStyle={DIM}\n >\n <MaterialCommunityIcons name=\"trash-can-outline\" size={22} color={theme.ink} />\n </Tap>\n ) : null}\n </View>\n </View>\n\n {body}\n </View>\n );\n}\n\nexport default DictateExperience;\n\nconst styles = StyleSheet.create({\n screen: { flex: 1 },\n header: {\n height: 52,\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 4,\n borderBottomWidth: StyleSheet.hairlineWidth,\n },\n headerButton: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' },\n headerTitle: { flex: 1, fontSize: 17, fontWeight: '600' },\n headerActions: { flexDirection: 'row', alignItems: 'center' },\n home: { paddingHorizontal: 16, paddingTop: 20 },\n lead: { fontSize: 24, fontWeight: '700' },\n leadDetail: { marginTop: 6, fontSize: 15, lineHeight: 21 },\n error: { textAlign: 'center', fontSize: 13, marginBottom: 8 },\n banner: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: 10,\n borderRadius: 12,\n paddingHorizontal: 12,\n paddingVertical: 10,\n marginBottom: 12,\n },\n bannerText: { flex: 1, fontSize: 13, lineHeight: 18 },\n bannerAction: {\n paddingHorizontal: 12,\n paddingVertical: 7,\n borderRadius: 999,\n borderWidth: StyleSheet.hairlineWidth,\n },\n bannerActionText: { fontSize: 13, fontWeight: '600' },\n sectionLabel: { marginTop: 12, marginBottom: 8, fontSize: 12, fontWeight: '700', letterSpacing: 0.8 },\n recordBody: { flex: 1, justifyContent: 'center', paddingHorizontal: 16 },\n});\n"],"names":["_b","_a","_c"],"mappings":"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"}