Skip to content

Commit 61f9ff0

Browse files
committed
v1.0.5 — base-unit convenience fields across all output methods
toRawDict(), toDict(), pretty() and describe() now expose canonical base-unit fields so callers never need to handle unit conversion: weight mg/g/kg → measure_gr / measure_available_gr volume ml/cl/L/m³ → measure_ml / measure_available_ml size mm/cm/m → measure_mm / measure_available_mm area m² → measure_mm2 / measure_available_mm2 Already-canonical units (g / ml / mm) produce no redundant field. pretty() shows "(= 750 g)" inline; describe() appends "— 750 g available, 1000 g total" to the Quantity sentence. README and llms.txt updated accordingly.
1 parent c93fe3c commit 61f9ff0

4 files changed

Lines changed: 52 additions & 7 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1212
- **Volume** (ml / cl / L / m³ → millilitres): `measure_ml` + `measure_available_ml`
1313
- **Size** (mm / cm / m → millimetres): `measure_mm` + `measure_available_mm`
1414
- **Area** (m² → square millimetres): `measure_mm2` + `measure_available_mm2`
15-
- `toRawDict()` now includes `measure_gr` / `measure_available_gr` (or `_ml` for liquids)
16-
immediately after `measure_available`. Developers can read a single field in grams (or ml)
17-
without caring about the unit stored on the chip.
15+
- `toRawDict()` now includes the convenience fields immediately after `measure_available`.
16+
Developers read a single field in grams (or ml/mm/mm²) without caring about `id_unit`.
1817
- `toDict()` `.measure` block now includes the same convenience fields alongside `initial`,
1918
`available`, `unit`, and `percent`.
19+
- `pretty()` Quantity section appends `(= 750 g)` hint on Initial and Available lines when
20+
the stored unit is not already the canonical base unit (g / ml / mm).
21+
- `describe()` Quantity sentence appends `— 750 g available, 1000 g total` when applicable.
2022

2123
## [1.0.4] — 2026-05-22
2224

‎README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,13 @@ capability container) are never part of the user data payload.
261261
```js
262262
// Read
263263
tag.pretty(db, sigResult) // → string human-readable summary
264+
// Quantity section shows "(= 750 g)" hint when unit ≠ base
265+
tag.describe(db) // → string LLM-friendly paragraph
266+
// Quantity sentence includes "— 750 g available, 1000 g total"
264267
tag.toDict(db) // → object JSON-serializable, all labels resolved
268+
// .measure includes measure_gr/ml/mm/mm2 + measure_available_*
265269
tag.toRawDict() // → object raw protocol fields, no resolution
270+
// includes measure_gr/ml/mm/mm2 + measure_available_* (base-unit)
266271
tag.toBytes(includeSignature = false) // → Buffer re-serialize to chip bytes
267272
tag.validate() // → string[] sanity check — list of warnings
268273
tag.verify(db) // → SignatureResult

‎llms.txt‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,21 @@ TigerTag.fromCloudDoc(doc, db?)
203203

204204
tag.toBytes(includeSignature=false) → Buffer(80 or 144)
205205
tag.toDict(db?) → resolved object (IDs → labels + metadata)
206-
tag.toRawDict() → raw protocol fields
206+
.measure block always includes canonical base-unit fields:
207+
measure_gr/measure_available_gr (weight), measure_ml/measure_available_ml
208+
(volume), measure_mm/measure_available_mm (size),
209+
measure_mm2/measure_available_mm2 (area).
210+
Already-canonical units (g/ml/mm) produce no duplicate field.
211+
tag.toRawDict() → raw protocol fields (flat snake_case)
212+
same base-unit convenience fields as toDict().measure (top-level).
207213
tag.patch({ ...fields }) → new TigerTag (immutable update, camelCase keys)
208214
tag.patchFromRawDict(raw) → new TigerTag (immutable update, snake_case keys from toRawDict())
209215
tag.validate() → string[] warnings
210216
tag.verify(db?) → SignatureResult
211217
tag.pretty(db?, sigResult?) → human-readable string
218+
Quantity section: "1 kg (= 1000 g)" / "0.75 kg (= 750 g) (75% remaining)"
212219
tag.describe(db?) → LLM-friendly natural language paragraph
220+
Quantity sentence: "0.75 kg remaining out of 1 kg initial (75% — 750 g available, 1000 g total)"
213221

214222
tag.rawApi(timeout=5000) → Promise<object|null>
215223
tag.diffApi(apiData?, db?) → Promise<ApiDiff[]>

‎src/tag.js‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,24 @@ class TigerTag {
947947
*
948948
* @private
949949
*/
950+
/**
951+
* Returns a parenthetical base-unit hint string for a single value,
952+
* e.g. " (= 750 g)" or " (= 500 ml)".
953+
* Returns "" when idUnit is already the canonical base unit (g / ml / mm)
954+
* or unsupported (no conversion available).
955+
* @private
956+
*/
957+
static _toBaseUnitStr(value, idUnit) {
958+
const BASE_IDS = new Set([21, 48, 112]); // g, ml, mm are already base
959+
if (BASE_IDS.has(idUnit)) return '';
960+
const f = TigerTag._baseUnitFields(value, value, idUnit);
961+
if (f.measure_gr !== undefined) return ` (= ${f.measure_gr} g)`;
962+
if (f.measure_ml !== undefined) return ` (= ${f.measure_ml} ml)`;
963+
if (f.measure_mm !== undefined) return ` (= ${f.measure_mm} mm)`;
964+
if (f.measure_mm2 !== undefined) return ` (= ${f.measure_mm2} mm²)`;
965+
return '';
966+
}
967+
950968
static _baseUnitFields(measure, measureAvailable, idUnit) {
951969
// Weight: mg(10) g(21) kg(35) → grams
952970
const WEIGHT_TO_G = { 10: 0.001, 21: 1, 35: 1000 };
@@ -1384,10 +1402,22 @@ class TigerTag {
13841402
}
13851403

13861404
if (this.measure > 0) {
1405+
const BASE_UNIT_IDS = new Set([21, 48, 112]); // g, ml, mm — already canonical
1406+
const bf = BASE_UNIT_IDS.has(this.idUnit)
1407+
? {}
1408+
: TigerTag._baseUnitFields(this.measure, this.measureAvailable, this.idUnit);
1409+
const buKey = Object.keys(bf).find(k => k.startsWith('measure_available_'));
1410+
const buUnit = buKey ? buKey.replace('measure_available_', '') : null;
1411+
// buUnit is 'gr'→'g', 'ml', 'mm', 'mm2'→'mm²'
1412+
const buLabel = buUnit ? buUnit.replace('gr', 'g').replace('mm2', 'mm²') : null;
1413+
const buNote = buLabel
1414+
? ` — ${bf['measure_available_' + buUnit]} ${buLabel} available, `
1415+
+ `${bf['measure_' + buUnit]} ${buLabel} total`
1416+
: '';
13871417
parts.push(
13881418
`Quantity: ${this.measureAvailable} ${unit} remaining`
13891419
+ ` out of ${this.measure} ${unit} initial`
1390-
+ (stock != null ? ` (${stock}%).` : '.'),
1420+
+ (stock != null ? ` (${stock}%${buNote}).` : (buNote ? `${buNote}.` : '.')),
13911421
);
13921422
}
13931423

@@ -1475,8 +1505,8 @@ class TigerTag {
14751505
+ `│ Drying ${this.dryTemp}°C / ${this.dryTime}h${recNote('dryTemp', 'dryTime', ' h')}\n`
14761506
+ `├─ Quantity ────────────────────────────────────────────\n`
14771507
+ `│ Unit ${ul}\n`
1478-
+ `│ Initial ${this.measure} ${ul}\n`
1479-
+ `│ Available ${this.measureAvailable} ${ul}`
1508+
+ `│ Initial ${this.measure} ${ul}${TigerTag._toBaseUnitStr(this.measure, this.idUnit)}\n`
1509+
+ `│ Available ${this.measureAvailable} ${ul}${TigerTag._toBaseUnitStr(this.measureAvailable, this.idUnit)}`
14801510
+ (stock != null ? ` (${stock}% remaining)\n` : '\n')
14811511
+ `├─ Traceability ────────────────────────────────────────\n`
14821512
+ `│ Manufactured ${this.manufacturingDate.toISOString().replace('T', ' ').slice(0, 16)} UTC\n`

0 commit comments

Comments
 (0)