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,94 @@
import type {NativePPGProcessingResult} from 'react-native-haptika-ppg';
import {mapNativeProcessingResult} from '../measurement/measurementCoordinator';
const nativeResult = (): NativePPGProcessingResult => ({
valid: false,
invalidReason: 'Signal exploitable insuffisant',
bpm: {
bpm: 72,
temporalBpm: Number.NaN,
frequencyBpm: 72.4,
methodAgreementBpm: 0.4,
validPeakCount: 19.7,
rejectedPeakCount: 8.2,
confidence: 0.51,
selectedChannel: 'red-inverted',
},
prv: {
estimatedRmssdMs: Number.POSITIVE_INFINITY,
estimatedSdnnMs: 31,
meanIpiMs: 833,
medianIpiMs: 829,
pnn50Percent: 7,
rangeIpiMs: 118,
modeIpiMs: 825,
amo50Percent: 42,
coefficientOfVariation: 0.05,
validIntervalCount: 18.8,
rejectedIntervalCount: 7.4,
rejectedIntervalRatio: 0.28,
reliable: false,
},
quality: {
score: 0.43,
level: 'invalid',
usableDurationSeconds: 21.5,
droppedFrameRatio: 0.08,
saturationRatio: 0.12,
rejectedIntervalRatio: 0.28,
signalToNoiseRatio: 3.2,
motionArtifactRatio: 0.31,
methodAgreementScore: 0.6,
timestampJitterRatio: 0.04,
longestGapSeconds: 0.38,
issues: ['motionArtifacts', 'futureNativeDiagnostic'],
},
startedAtMonotonicSeconds: 100,
endedAtMonotonicSeconds: 160,
rawSampleCount: 1_642.4,
resampledSampleCount: 5_501.7,
});
describe('measurementCoordinator diagnostics', () => {
test('preserves acquisition diagnostics used to explain an invalid result', () => {
const result = mapNativeProcessingResult(nativeResult());
expect(result).toMatchObject({
valid: false,
invalidReason: 'Signal exploitable insuffisant',
rawSampleCount: 1_642,
resampledSampleCount: 5_502,
quality: {
usableDurationSeconds: 21.5,
droppedFrameRatio: 0.08,
saturationRatio: 0.12,
rejectedIntervalRatio: 0.28,
signalToNoiseRatio: 3.2,
motionArtifactRatio: 0.31,
timestampJitterRatio: 0.04,
longestGapSeconds: 0.38,
},
bpm: {
selectedChannel: 'red-inverted',
validPeakCount: 20,
rejectedPeakCount: 8,
},
prv: {
validIntervalCount: 19,
rejectedIntervalCount: 7,
},
});
});
test('normalizes non-finite values and unknown native issues safely', () => {
const result = mapNativeProcessingResult(nativeResult());
expect(result.bpm.temporalBpm).toBeNull();
expect(result.prv.estimatedRmssdMs).toBeNull();
expect(result.quality.issues).toEqual([
'motionArtifacts',
'nonFiniteProcessingResult',
]);
});
});