feat: implement Haptika PPG and Watch V1

This commit is contained in:
Sacha
2026-07-31 16:19:42 +02:00
parent 6b7d4e0728
commit b9930b7378
249 changed files with 46475 additions and 122 deletions
@@ -0,0 +1,63 @@
import {buildMetricInsights} from '../insights/metricInsights';
import type {StoredMeasurement} from '../storage/storageTypes';
import {makeProcessingResult} from './syntheticMeasurements';
const stored = (index: number, bpm: number, rmssd: number): StoredMeasurement => ({
schemaVersion: 1,
algorithmVersion: 'test',
acquisition: {cameraPosition: 'front', torchUsed: false},
id: `stored-${index}`,
measuredAt: new Date(Date.UTC(2026, 6, index + 1, 8)).toISOString(),
atRest: true,
baselineEligible: true,
cameraResult: makeProcessingResult({
bpm: {...makeProcessingResult().bpm, bpm},
prv: {...makeProcessingResult().prv, estimatedRmssdMs: rmssd},
}),
potentialStress: {
index: null,
status: 'baselineBuilding',
confidence: 0,
bpmContribution: null,
prvContribution: null,
signalQuality: 0.9,
baselineMeasurementCount: 0,
healthKitUsedForScore: false,
limitations: [],
},
healthKitContext: null,
selfReport: null,
});
describe('buildMetricInsights', () => {
it('uses real historical measurements and creates personal ranges after five values', () => {
const history = [
stored(0, 70, 40),
stored(1, 71, 41),
stored(2, 69, 39),
stored(3, 72, 42),
stored(4, 70, 40),
];
const insights = buildMetricInsights(history[4]!.cameraResult, history);
const bpm = insights.find(insight => insight.key === 'bpm');
const rmssd = insights.find(insight => insight.key === 'rmssd');
expect(bpm?.trend).toHaveLength(5);
expect(bpm?.referenceRange).not.toBeNull();
expect(bpm?.status).toBe('Dans votre repère habituel');
expect(rmssd?.trend.map(point => point.value)).toEqual([40, 41, 39, 42, 40]);
});
it('never publishes PRV insights when the current PRV is unreliable', () => {
const current = makeProcessingResult({
prv: {...makeProcessingResult().prv, reliable: false},
});
const insights = buildMetricInsights(current, []);
expect(insights.find(insight => insight.key === 'bpm')?.value).toBe(72);
expect(insights.find(insight => insight.key === 'rmssd')?.value).toBeNull();
expect(insights.find(insight => insight.key === 'rmssd')?.status).toBe(
'Signal insuffisant',
);
});
});