Skip to content

Commit d2e6234

Browse files
committed
feat(eventToShortText): label each composite field, snap to nearest stop
formatComposite now emits 'Field: Value' for each present field (e.g. 'Height: High · Firmness: Soft · Openness: Open') so the tooltip names the dimension. For continuous numeric values that fall between option stops (e.g. Mira imports stored at 0.13, 0.62), snap to the nearest stop's localised label instead of showing the raw decimal — so the text reads 'Low' / 'Medium' / 'High' regardless of the source method's precision. New tests EST19c (off-stop snap) and EST19d (mid-range → Medium).
1 parent 8a11dc7 commit d2e6234

2 files changed

Lines changed: 54 additions & 10 deletions

File tree

tests/eventToShortText.test.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,14 +322,14 @@ describe('[ESTX] eventToShortText', () => {
322322
assert.equal(result, 'Ibuprofen — 400 mg, oral');
323323
});
324324

325-
it('[EST19] cervix-position composite joins selected option labels', () => {
325+
it('[EST19] cervix-position composite labels each field', () => {
326326
const event = {
327327
content: { height: 1.0, firmness: 0.0, openness: 0.5 },
328328
streamIds: ['body-vulva-cervix-position'],
329329
type: 'cervix-position/3d-vectors'
330330
};
331331
const result = eventToShortText(event);
332-
assert.equal(result, 'High · Firm · Medium');
332+
assert.equal(result, 'Height: High · Firmness: Firm · Openness: Medium');
333333
});
334334

335335
it('[EST19b] cervix-position composite skips missing fields', () => {
@@ -339,7 +339,27 @@ describe('[ESTX] eventToShortText', () => {
339339
type: 'cervix-position/3d-vectors'
340340
};
341341
const result = eventToShortText(event);
342-
assert.equal(result, 'Low');
342+
assert.equal(result, 'Height: Low');
343+
});
344+
345+
it('[EST19c] cervix-position composite snaps continuous values to nearest stop', () => {
346+
const event = {
347+
content: { height: 0.13, firmness: 0.07, openness: 0.02 },
348+
streamIds: ['body-vulva-cervix-position'],
349+
type: 'cervix-position/3d-vectors'
350+
};
351+
const result = eventToShortText(event);
352+
assert.equal(result, 'Height: Low · Firmness: Firm · Openness: Closed');
353+
});
354+
355+
it('[EST19d] cervix-position composite snaps mid-range to Medium', () => {
356+
const event = {
357+
content: { height: 0.45, firmness: 0.6 },
358+
streamIds: ['body-vulva-cervix-position'],
359+
type: 'cervix-position/3d-vectors'
360+
};
361+
const result = eventToShortText(event);
362+
assert.equal(result, 'Height: Medium · Firmness: Medium');
343363
});
344364

345365
// ─── Convertible: mood ───────────────────────────────────────────

ts/HDSModel/eventToShortText.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,15 @@ function formatSlider (content: any, itemDef: any): string {
201201

202202
/**
203203
* Format a composite event by walking the itemDef's `composite` block. Only
204-
* applies when every field is a `select` with options — joins the localised
205-
* option labels with " · ". Used e.g. for `body-vulva-cervix-position`
206-
* (`{ height, firmness, openness }` → "High · Soft · Open").
204+
* applies when every field is a `select` with options — emits "Field: Label"
205+
* for each present field, joined by " · ". The field label uses the
206+
* composite entry's localised `label`; the value resolves to the option's
207+
* localised label when a discrete match exists, otherwise falls back to the
208+
* raw value (e.g. continuous imports stored at non-canonical numeric values).
209+
*
210+
* Used e.g. for `body-vulva-cervix-position` so tooltips read
211+
* "Height: High · Firmness: Soft · Openness: Open" — the dimension is named
212+
* even when the imported value falls between option stops.
207213
*
208214
* Falls through to `formatObject` for free-form composites (e.g.
209215
* medication/basic with `name`/`doseValue`/`doseUnit`/`route`) so existing
@@ -222,13 +228,31 @@ function formatComposite (content: any, itemDef: any): string | null {
222228
for (const field of fieldKeys) {
223229
const value = content[field];
224230
if (value === undefined || value === null) continue;
225-
const opt = composite[field].options.find((o: any) => o.value === value);
231+
const fieldDef = composite[field];
232+
const fieldLabel = typeof fieldDef.label === 'string'
233+
? fieldDef.label
234+
: (localizeText(fieldDef.label) || field);
235+
// Prefer exact option match; for continuous numeric values that fall
236+
// between stops (e.g. Mira imports), snap to the nearest option so the
237+
// tooltip shows a meaningful label instead of a raw number.
238+
let opt = fieldDef.options.find((o: any) => o.value === value);
239+
if (!opt && typeof value === 'number') {
240+
let nearest: any = null;
241+
let bestDist = Infinity;
242+
for (const o of fieldDef.options) {
243+
if (typeof o.value !== 'number') continue;
244+
const d = Math.abs(o.value - value);
245+
if (d < bestDist) { bestDist = d; nearest = o; }
246+
}
247+
opt = nearest;
248+
}
249+
let valueText: string;
226250
if (opt?.label) {
227-
const text = typeof opt.label === 'string' ? opt.label : (localizeText(opt.label) || String(value));
228-
parts.push(text);
251+
valueText = typeof opt.label === 'string' ? opt.label : (localizeText(opt.label) || String(value));
229252
} else {
230-
parts.push(String(value));
253+
valueText = String(value);
231254
}
255+
parts.push(`${fieldLabel}: ${valueText}`);
232256
}
233257
return parts.length > 0 ? parts.join(' · ') : formatObject(content);
234258
}

0 commit comments

Comments
 (0)