Skip to content

Commit 3ccd28e

Browse files
committed
Unified preferred representation API + rename settings prefixes
- HDSModel-Preferred: getPreferredInput/getPreferredDisplay unified API for both variation items (kg/lb) and converter items (mood/mucus) - Rename settings: converter-auto- → preferred-display-, converter-default- → preferred-input- - Standalone getPreferredInput/getPreferredDisplay functions - Variation resolution: per-item override → unitSystem fallback → default - 12 new preferred API tests, 318 total passing
1 parent 796e9a3 commit 3ccd28e

8 files changed

Lines changed: 354 additions & 42 deletions

File tree

tests/HDSSettings.test.js

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -209,95 +209,95 @@ describe('[HDSD] HDSSettings dynamic settings', function () {
209209
});
210210

211211
it('[HDSD1] _testInject and get work for dynamic keys', () => {
212-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'billings');
213-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), 'billings');
212+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'billings');
213+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), 'billings');
214214
assert.strictEqual(HDSSettings.isHooked, true);
215215
});
216216

217217
it('[HDSD2] _testClear removes dynamic key', () => {
218-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'mira');
219-
HDSSettings._testClear('converter-auto-wellbeing-mood');
220-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), undefined);
218+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'mira');
219+
HDSSettings._testClear('preferred-display-wellbeing-mood');
220+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), undefined);
221221
});
222222

223223
it('[HDSD3] getDynamic returns all settings with prefix', () => {
224-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'billings');
225-
HDSSettings._testInject('converter-auto-body-vulva-mucus-inspect', 'appleHealth');
226-
const all = HDSSettings.getDynamic('converter-auto-');
224+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'billings');
225+
HDSSettings._testInject('preferred-display-body-vulva-mucus-inspect', 'appleHealth');
226+
const all = HDSSettings.getDynamic('preferred-display-');
227227
assert.strictEqual(all['wellbeing-mood'], 'billings');
228228
assert.strictEqual(all['body-vulva-mucus-inspect'], 'appleHealth');
229229
});
230230

231231
it('[HDSD4] unhook clears dynamic settings', () => {
232-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'billings');
232+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'billings');
233233
HDSSettings.unhook();
234-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), undefined);
235-
assert.deepStrictEqual(HDSSettings.getDynamic('converter-auto-'), {});
234+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), undefined);
235+
assert.deepStrictEqual(HDSSettings.getDynamic('preferred-display-'), {});
236236
});
237237

238238
it('[HDSD5] load reads dynamic settings from server events', async () => {
239239
const conn = createMockConnection({
240240
'events.get': () => ({
241241
events: [
242-
{ id: 'ev-ac1', type: 'settings/converter-auto', content: { itemKey: 'wellbeing-mood', method: 'billings' } },
243-
{ id: 'ev-ac2', type: 'settings/converter-auto', content: { itemKey: 'body-vulva-mucus-inspect', method: 'appleHealth' } },
242+
{ id: 'ev-ac1', type: 'settings/preferred-display', content: { itemKey: 'wellbeing-mood', value: 'billings' } },
243+
{ id: 'ev-ac2', type: 'settings/preferred-display', content: { itemKey: 'body-vulva-mucus-inspect', value: 'appleHealth' } },
244244
{ id: 'ev-t', type: 'settings/theme', content: 'dark' },
245245
]
246246
})
247247
});
248248
await HDSSettings.hookToConnection(conn, 'test-stream');
249-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), 'billings');
250-
assert.strictEqual(HDSSettings.get('converter-auto-body-vulva-mucus-inspect'), 'appleHealth');
249+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), 'billings');
250+
assert.strictEqual(HDSSettings.get('preferred-display-body-vulva-mucus-inspect'), 'appleHealth');
251251
assert.strictEqual(HDSSettings.get('theme'), 'dark');
252252
});
253253

254254
it('[HDSD6] setDynamic creates new event', async () => {
255255
const conn = createMockConnection();
256256
await HDSSettings.hookToConnection(conn, 'test-stream');
257257

258-
await HDSSettings.setDynamic('converter-auto-wellbeing-mood', 'billings');
259-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), 'billings');
258+
await HDSSettings.setDynamic('preferred-display-wellbeing-mood', 'billings');
259+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), 'billings');
260260

261261
const createCall = conn.apiCalls.find(c =>
262-
c.method === 'events.create' && c.params.type === 'settings/converter-auto'
262+
c.method === 'events.create' && c.params.type === 'settings/preferred-display'
263263
);
264264
assert.ok(createCall, 'Should have called events.create');
265265
assert.strictEqual(createCall.params.content.itemKey, 'wellbeing-mood');
266-
assert.strictEqual(createCall.params.content.method, 'billings');
266+
assert.strictEqual(createCall.params.content.value, 'billings');
267267
});
268268

269269
it('[HDSD7] setDynamic updates existing event', async () => {
270270
const conn = createMockConnection({
271271
'events.get': () => ({
272272
events: [
273-
{ id: 'ev-ac-mood', type: 'settings/converter-auto', content: { itemKey: 'wellbeing-mood', method: 'billings' } }
273+
{ id: 'ev-ac-mood', type: 'settings/preferred-display', content: { itemKey: 'wellbeing-mood', value: 'billings' } }
274274
]
275275
})
276276
});
277277
await HDSSettings.hookToConnection(conn, 'test-stream');
278-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), 'billings');
278+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), 'billings');
279279

280-
await HDSSettings.setDynamic('converter-auto-wellbeing-mood', 'mira');
281-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), 'mira');
280+
await HDSSettings.setDynamic('preferred-display-wellbeing-mood', 'mira');
281+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), 'mira');
282282

283283
const updateCall = conn.apiCalls.find(c => c.method === 'events.update');
284284
assert.ok(updateCall, 'Should have called events.update');
285285
assert.strictEqual(updateCall.params.id, 'ev-ac-mood');
286-
assert.strictEqual(updateCall.params.update.content.method, 'mira');
286+
assert.strictEqual(updateCall.params.update.content.value, 'mira');
287287
});
288288

289289
it('[HDSD8] setDynamic with null deletes setting', async () => {
290290
const conn = createMockConnection({
291291
'events.get': () => ({
292292
events: [
293-
{ id: 'ev-ac-mood', type: 'settings/converter-auto', content: { itemKey: 'wellbeing-mood', method: 'billings' } }
293+
{ id: 'ev-ac-mood', type: 'settings/preferred-display', content: { itemKey: 'wellbeing-mood', value: 'billings' } }
294294
]
295295
})
296296
});
297297
await HDSSettings.hookToConnection(conn, 'test-stream');
298298

299-
await HDSSettings.setDynamic('converter-auto-wellbeing-mood', null);
300-
assert.strictEqual(HDSSettings.get('converter-auto-wellbeing-mood'), undefined);
299+
await HDSSettings.setDynamic('preferred-display-wellbeing-mood', null);
300+
assert.strictEqual(HDSSettings.get('preferred-display-wellbeing-mood'), undefined);
301301

302302
const deleteCall = conn.apiCalls.find(c => c.method === 'events.delete');
303303
assert.ok(deleteCall, 'Should have called events.delete');
@@ -315,7 +315,7 @@ describe('[HDSD] HDSSettings dynamic settings', function () {
315315

316316
it('[HDSD10] setDynamic throws when not hooked', async () => {
317317
await assert.rejects(
318-
() => HDSSettings.setDynamic('converter-auto-wellbeing-mood', 'billings'),
318+
() => HDSSettings.setDynamic('preferred-display-wellbeing-mood', 'billings'),
319319
/hookToApplication|hookToConnection/
320320
);
321321
});

tests/eventToShortText.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -419,14 +419,14 @@ describe('[ESTX] eventToShortText', () => {
419419
});
420420

421421
it('[EST22b] autoConvert same method — shows sourceData + method name (no conversion)', async () => {
422-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'mira');
422+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'mira');
423423
const event = await model.converters.convertMethodToEvent('mood', 'mira', 'Happy');
424424
event.time = Date.now() / 1000;
425425
assert.equal(eventToShortText(event), 'Happy (Mira)');
426426
});
427427

428428
it('[EST22c] autoConvert mood mira→hds — shows stop labels with target <- source', async () => {
429-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'hds');
429+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'hds');
430430
const event = await model.converters.convertMethodToEvent('mood', 'mira', 'Happy');
431431
event.time = Date.now() / 1000;
432432
const result = eventToShortText(event);
@@ -436,15 +436,15 @@ describe('[ESTX] eventToShortText', () => {
436436
});
437437

438438
it('[EST22d] autoConvert cervical fluid mira→appleHealth — localized label + perfect match', async () => {
439-
HDSSettings._testInject('converter-auto-body-vulva-mucus-inspect', 'appleHealth');
439+
HDSSettings._testInject('preferred-display-body-vulva-mucus-inspect', 'appleHealth');
440440
const event = await model.converters.convertMethodToEvent('cervical-fluid', 'mira', 'Creamy');
441441
event.time = Date.now() / 1000;
442442
// "creamy" value should resolve to "Creamy" label from appleHealth method
443443
assert.equal(eventToShortText(event), 'Creamy (Apple Health <- Mira)');
444444
});
445445

446446
it('[EST22e] autoConvert cervical fluid mira→billings — localized label + partial match', async () => {
447-
HDSSettings._testInject('converter-auto-body-vulva-mucus-inspect', 'billings');
447+
HDSSettings._testInject('preferred-display-body-vulva-mucus-inspect', 'billings');
448448
const event = await model.converters.convertMethodToEvent('cervical-fluid', 'mira', 'Watery');
449449
event.time = Date.now() / 1000;
450450
const result = eventToShortText(event);
@@ -455,7 +455,7 @@ describe('[ESTX] eventToShortText', () => {
455455
});
456456

457457
it('[EST22f] autoConvert raw vector (no source) — shows result + target name', async () => {
458-
HDSSettings._testInject('converter-auto-wellbeing-mood', 'mira');
458+
HDSSettings._testInject('preferred-display-wellbeing-mood', 'mira');
459459
const event = {
460460
content: { vectors: { valence: 0.8, arousal: 0.2, dominance: 0.7, socialOrientation: 0.5, temporalFocus: 0.3 } },
461461
streamIds: ['wellbeing-mood'],
@@ -468,7 +468,7 @@ describe('[ESTX] eventToShortText', () => {
468468
});
469469

470470
it('[EST22g] all cervical fluid mira→appleHealth labels are capitalized', async () => {
471-
HDSSettings._testInject('converter-auto-body-vulva-mucus-inspect', 'appleHealth');
471+
HDSSettings._testInject('preferred-display-body-vulva-mucus-inspect', 'appleHealth');
472472
const pairs = [
473473
['No discharge', 'Dry'], ['Dry', 'Dry'], ['Sticky', 'Sticky'],
474474
['Creamy', 'Creamy'], ['Watery', 'Watery'], ['Raw Egg White', 'Egg White'],

tests/preferred.test.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { assert } from './test-utils/deps-node.js';
2+
import { HDSSettings } from '../ts/index.ts';
3+
import { getModel } from '../ts/HDSModel/HDSModelInitAndSingleton.ts';
4+
5+
const modelURL = 'https://model.datasafe.dev/pack.json';
6+
7+
describe('[PREF] HDSModel.preferred', function () {
8+
let model;
9+
10+
before(async () => {
11+
model = getModel();
12+
await model.load(modelURL);
13+
await model.converters.ensureEngine('mood');
14+
await model.converters.ensureEngine('cervical-fluid');
15+
});
16+
17+
afterEach(() => {
18+
HDSSettings.unhook();
19+
});
20+
21+
// ─── Variation items ─────────────────────────────────────────
22+
23+
describe('[PREF-V] variation items', () => {
24+
it('[PREF-V1] body-weight returns variation config with default (metric)', () => {
25+
HDSSettings._testInject('unitSystem', 'metric');
26+
const pref = model.preferred.getPreferredInput('body-weight');
27+
assert.equal(pref.type, 'variation');
28+
assert.equal(pref.eventType, 'mass/kg');
29+
assert.ok(pref.symbol, 'Should have symbol');
30+
});
31+
32+
it('[PREF-V2] body-weight returns imperial when unitSystem=imperial', () => {
33+
HDSSettings._testInject('unitSystem', 'imperial');
34+
const pref = model.preferred.getPreferredInput('body-weight');
35+
assert.equal(pref.type, 'variation');
36+
assert.equal(pref.eventType, 'mass/lb');
37+
});
38+
39+
it('[PREF-V3] per-item override takes precedence over unitSystem', () => {
40+
HDSSettings._testInject('unitSystem', 'metric');
41+
HDSSettings._testInject('preferred-input-body-weight', 'mass/lb');
42+
const pref = model.preferred.getPreferredInput('body-weight');
43+
assert.equal(pref.eventType, 'mass/lb');
44+
});
45+
46+
it('[PREF-V4] body-height respects per-item override (Canada: metric but ft)', () => {
47+
HDSSettings._testInject('unitSystem', 'metric');
48+
HDSSettings._testInject('preferred-input-body-height', 'length/ft');
49+
const weightPref = model.preferred.getPreferredInput('body-weight');
50+
const heightPref = model.preferred.getPreferredInput('body-height');
51+
assert.equal(weightPref.eventType, 'mass/kg', 'weight follows unitSystem');
52+
assert.equal(heightPref.eventType, 'length/ft', 'height uses per-item override');
53+
});
54+
55+
it('[PREF-V5] display and input can differ', () => {
56+
HDSSettings._testInject('unitSystem', 'metric');
57+
HDSSettings._testInject('preferred-display-body-weight', 'mass/lb');
58+
const input = model.preferred.getPreferredInput('body-weight');
59+
const display = model.preferred.getPreferredDisplay('body-weight');
60+
assert.equal(input.eventType, 'mass/kg', 'input follows unitSystem');
61+
assert.equal(display.eventType, 'mass/lb', 'display uses per-item override');
62+
});
63+
64+
it('[PREF-V6] without settings returns first option as default', () => {
65+
const pref = model.preferred.getPreferredInput('body-weight');
66+
assert.equal(pref.type, 'variation');
67+
assert.ok(pref.eventType, 'Should have an eventType');
68+
});
69+
});
70+
71+
// ─── Converter items ─────────────────────────────────────────
72+
73+
describe('[PREF-C] converter items', () => {
74+
it('[PREF-C1] mood with no preference returns method=null', () => {
75+
const pref = model.preferred.getPreferredInput('wellbeing-mood');
76+
assert.equal(pref.type, 'converter');
77+
assert.equal(pref.method, null);
78+
assert.ok(pref.engine, 'Should have engine');
79+
});
80+
81+
it('[PREF-C2] mood with preferred-input returns method + name', () => {
82+
HDSSettings._testInject('preferred-input-wellbeing-mood', 'mira');
83+
const pref = model.preferred.getPreferredInput('wellbeing-mood');
84+
assert.equal(pref.type, 'converter');
85+
assert.equal(pref.method, 'mira');
86+
assert.equal(pref.methodName, 'Mira');
87+
});
88+
89+
it('[PREF-C3] cervical-fluid preferred-display returns method', () => {
90+
HDSSettings._testInject('preferred-display-body-vulva-mucus-inspect', 'appleHealth');
91+
const pref = model.preferred.getPreferredDisplay('body-vulva-mucus-inspect');
92+
assert.equal(pref.type, 'converter');
93+
assert.equal(pref.method, 'appleHealth');
94+
assert.equal(pref.methodName, 'Apple Health');
95+
});
96+
97+
it('[PREF-C4] input and display can differ', () => {
98+
HDSSettings._testInject('preferred-input-body-vulva-mucus-inspect', 'chartneo');
99+
HDSSettings._testInject('preferred-display-body-vulva-mucus-inspect', 'billings');
100+
const input = model.preferred.getPreferredInput('body-vulva-mucus-inspect');
101+
const display = model.preferred.getPreferredDisplay('body-vulva-mucus-inspect');
102+
assert.equal(input.method, 'chartneo');
103+
assert.equal(display.method, 'billings');
104+
});
105+
});
106+
107+
// ─── Default items (no variation, no converter) ──────────────
108+
109+
describe('[PREF-D] default items', () => {
110+
it('[PREF-D1] simple checkbox returns default', () => {
111+
const pref = model.preferred.getPreferredInput('activity-yoga');
112+
assert.equal(pref.type, 'default');
113+
});
114+
115+
it('[PREF-D2] unknown item returns default', () => {
116+
const pref = model.preferred.getPreferredInput('nonexistent-item');
117+
assert.equal(pref.type, 'default');
118+
});
119+
});
120+
});

0 commit comments

Comments
 (0)