@sohqureshi/tokenwise 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/dist/chunk-BN3L7TUV.js +241 -0
- package/dist/{chunk-QHCUNOUH.js → chunk-FBJ3DHS6.js} +1 -1
- package/dist/cli.cjs +136 -0
- package/dist/cli.js +2 -2
- package/dist/core/natural.cjs +136 -0
- package/dist/core/natural.js +1 -1
- package/dist/index.cjs +136 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-D4CFTFM3.js +0 -105
package/README.md
CHANGED
|
@@ -147,9 +147,25 @@ toNatural({
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
-
//
|
|
150
|
+
// Carlos Rivera has policy HLT-2048. The claim is under review for 64,000.
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
`toNatural()` uses semantic templates when the field relationships are
|
|
154
|
+
unambiguous (for example, a policy holder, policy number, and claim). Unknown
|
|
155
|
+
JSON shapes use a conservative key-value fallback so the formatter does not
|
|
156
|
+
invent relationships or facts. For dynamic entity-shaped objects, it can
|
|
157
|
+
recognize common subject fields such as `name`, `title`, `label`, `displayName`,
|
|
158
|
+
`entityName`, `fullName`, `userName`, `customerName`, `ownerName`,
|
|
159
|
+
`accountName`, `companyName`, `organizationName`, `teamName`, `projectName`,
|
|
160
|
+
`productName`, `serviceName`, `resourceName`, `deviceName`, `patientName`,
|
|
161
|
+
`taskName`, and `eventName`. These can be paired with state fields such as
|
|
162
|
+
`status`, `state`, `condition`, `stage`, `role`, `type`, `category`, `priority`,
|
|
163
|
+
`phase`, `mode`, `availability`, `outcome`, `result`, `health`, `progress`,
|
|
164
|
+
`visibility`, `access`, `membership`, `sentiment`, and `severity`.
|
|
165
|
+
Matching is case-insensitive and also supports keys that contain these
|
|
166
|
+
concepts, such as `patientName`, `orderStatus`, `currentPhase`, or
|
|
167
|
+
`incidentSeverity`.
|
|
168
|
+
|
|
153
169
|
### `toTOON()`
|
|
154
170
|
|
|
155
171
|
Converts JSON into a compact TOON-like text format. Arrays of objects become table-style rows.
|
|
@@ -220,14 +236,27 @@ used by `tiktoken`.
|
|
|
220
236
|
|
|
221
237
|
## Release Notes
|
|
222
238
|
|
|
239
|
+
### v1.0.10 — 2026-09-13
|
|
240
|
+
|
|
241
|
+
- Add conservative contains-based semantic matching for dynamic subject and state keys, including fields such as `patientName`, `orderStatus`, and `incidentSeverity`.
|
|
242
|
+
- Expand `toNatural()` regression coverage for dynamic entities, nested objects, arrays, metadata filtering, and alternate subject fields.
|
|
243
|
+
- Update the browser demo to use the v1.0.10 CDN package.
|
|
244
|
+
|
|
223
245
|
### v1.0.8 — 2026-09-06
|
|
224
246
|
|
|
225
247
|
- Expose exact tokenizer metadata when available (model + encoding), and fall back to a clear, model-aware estimator in browser demos.
|
|
226
248
|
- Demo updated to show selected model and expected encoding when the exact tokenizer (tiktoken) is not available in-browser.
|
|
227
249
|
- Updated analyze() to surface tokenizer encoding in analysis output so the demo shows "Estimator: model=gpt-4 expected_encoding=cl100k_base" even when using the heuristic fallback.
|
|
228
|
-
- Misc: build artifacts updated
|
|
250
|
+
- Misc: build artifacts updated.
|
|
251
|
+
|
|
252
|
+
### v1.0.9 — 2026-09-13
|
|
229
253
|
|
|
230
|
-
|
|
254
|
+
- Use `tiktoken` model-aware token counts by default in `estimateTokens()` and `analyze()`.
|
|
255
|
+
- Keep the four-characters-per-token estimate available with `exact: false`.
|
|
256
|
+
- Correctly identify `o200k_base` for modern OpenAI models such as `gpt-4o`, `gpt-4.1`, `o1`, and `o3`.
|
|
257
|
+
- Update the browser demo to use the v1.0.9 CDN package.
|
|
258
|
+
- Make demo analysis actionable by showing signed token and character changes, the selected transformation, and an explanation of whether the result is cheaper or larger.
|
|
259
|
+
- Rename the demo's input-only Analyze view to Inspect, showing input size and structure without applying a transformation.
|
|
231
260
|
|
|
232
261
|
---
|
|
233
262
|
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/core/natural.ts
|
|
2
|
+
function toNatural(data, depth = 0) {
|
|
3
|
+
if (data === null || data === void 0) return "nothing";
|
|
4
|
+
if (typeof data === "string") return data;
|
|
5
|
+
if (typeof data === "number") return String(data);
|
|
6
|
+
if (typeof data === "boolean") return data ? "yes" : "no";
|
|
7
|
+
if (Array.isArray(data)) {
|
|
8
|
+
if (data.length === 0) return "empty list";
|
|
9
|
+
if (data.every(isPlainObject)) {
|
|
10
|
+
return data.map((item, index) => `${index + 1}. ${stripTrailingPeriod(toNatural(item, depth + 1))}.`).join(" ");
|
|
11
|
+
}
|
|
12
|
+
const items = data.map((item) => toNatural(item, depth + 1));
|
|
13
|
+
return joinNaturalList(items);
|
|
14
|
+
}
|
|
15
|
+
if (typeof data === "object") {
|
|
16
|
+
return buildContextualStory(data, depth);
|
|
17
|
+
}
|
|
18
|
+
return String(data);
|
|
19
|
+
}
|
|
20
|
+
function buildContextualStory(obj, depth = 0) {
|
|
21
|
+
const semanticStory = buildSemanticStory(obj);
|
|
22
|
+
if (semanticStory) return semanticStory;
|
|
23
|
+
const nestedEntity = Object.values(obj).find((value) => {
|
|
24
|
+
return isPlainObject(value) && buildSemanticStory(value) !== null;
|
|
25
|
+
});
|
|
26
|
+
if (nestedEntity) {
|
|
27
|
+
const nestedStory = buildSemanticStory(nestedEntity);
|
|
28
|
+
if (nestedStory) return nestedStory;
|
|
29
|
+
}
|
|
30
|
+
let name = obj.name || obj.userName || obj.user;
|
|
31
|
+
if (typeof name === "object" && name !== null && name.name) {
|
|
32
|
+
name = name.name;
|
|
33
|
+
}
|
|
34
|
+
const entries = Object.entries(obj).filter(([key]) => {
|
|
35
|
+
return !["id", "timestamp", "apiKey"].includes(key);
|
|
36
|
+
});
|
|
37
|
+
if (entries.length === 0) return "";
|
|
38
|
+
let story = name && typeof name === "string" ? `User ${name}` : "";
|
|
39
|
+
const clauses = entries.map(([key, value]) => {
|
|
40
|
+
if (key === "user" && name && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
41
|
+
const userProps = Object.entries(value).filter(([k]) => !["id", "name", "timestamp", "apiKey"].includes(k)).map(([k, v]) => formatPropertyClause(k, v, depth + 1)).filter((c) => c !== null && c !== "").join(", ");
|
|
42
|
+
return userProps ? `(${userProps})` : null;
|
|
43
|
+
}
|
|
44
|
+
return formatPropertyClause(key, value, depth);
|
|
45
|
+
}).filter((c) => c !== null && c !== "");
|
|
46
|
+
if (clauses.length === 0) return story;
|
|
47
|
+
if (story) {
|
|
48
|
+
story += " " + clauses.join(", ");
|
|
49
|
+
} else {
|
|
50
|
+
story = clauses.join(", ");
|
|
51
|
+
}
|
|
52
|
+
return story + ".";
|
|
53
|
+
}
|
|
54
|
+
function buildSemanticStory(obj) {
|
|
55
|
+
if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
|
|
56
|
+
const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
|
|
57
|
+
const details = [
|
|
58
|
+
typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
|
|
59
|
+
typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
|
|
60
|
+
].filter((value) => value !== null);
|
|
61
|
+
const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
|
|
62
|
+
if (isPlainObject(obj.claim)) {
|
|
63
|
+
const claim = describeClaim(obj.claim);
|
|
64
|
+
if (claim) sentences.push(claim);
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
|
|
67
|
+
sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
|
|
68
|
+
}
|
|
69
|
+
return sentences.join(" ");
|
|
70
|
+
}
|
|
71
|
+
const subject = findSubject(obj);
|
|
72
|
+
const stateEntry = findSemanticEntry(obj, [
|
|
73
|
+
"status",
|
|
74
|
+
"state",
|
|
75
|
+
"condition",
|
|
76
|
+
"stage",
|
|
77
|
+
"role",
|
|
78
|
+
"type",
|
|
79
|
+
"category",
|
|
80
|
+
"classification",
|
|
81
|
+
"priority",
|
|
82
|
+
"phase",
|
|
83
|
+
"mode",
|
|
84
|
+
"availability",
|
|
85
|
+
"outcome",
|
|
86
|
+
"result",
|
|
87
|
+
"health",
|
|
88
|
+
"progress",
|
|
89
|
+
"visibility",
|
|
90
|
+
"access",
|
|
91
|
+
"membership",
|
|
92
|
+
"sentiment",
|
|
93
|
+
"severity"
|
|
94
|
+
]);
|
|
95
|
+
if (subject && stateEntry && typeof stateEntry[1] === "string") {
|
|
96
|
+
const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
|
|
97
|
+
return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function findSubject(obj) {
|
|
102
|
+
const subjectKeys = [
|
|
103
|
+
"name",
|
|
104
|
+
"title",
|
|
105
|
+
"label",
|
|
106
|
+
"displayName",
|
|
107
|
+
"entityName",
|
|
108
|
+
"fullName",
|
|
109
|
+
"userName",
|
|
110
|
+
"username",
|
|
111
|
+
"personName",
|
|
112
|
+
"customerName",
|
|
113
|
+
"clientName",
|
|
114
|
+
"ownerName",
|
|
115
|
+
"accountName",
|
|
116
|
+
"companyName",
|
|
117
|
+
"organizationName",
|
|
118
|
+
"teamName",
|
|
119
|
+
"departmentName",
|
|
120
|
+
"projectName",
|
|
121
|
+
"productName",
|
|
122
|
+
"serviceName",
|
|
123
|
+
"resourceName",
|
|
124
|
+
"fileName",
|
|
125
|
+
"deviceName",
|
|
126
|
+
"hostName",
|
|
127
|
+
"applicationName",
|
|
128
|
+
"appName",
|
|
129
|
+
"taskName",
|
|
130
|
+
"eventName",
|
|
131
|
+
"itemName",
|
|
132
|
+
"orderName",
|
|
133
|
+
"patientName",
|
|
134
|
+
"employeeName"
|
|
135
|
+
];
|
|
136
|
+
const entries = Object.entries(obj);
|
|
137
|
+
for (const key of subjectKeys) {
|
|
138
|
+
if (typeof obj[key] === "string" && obj[key].trim()) {
|
|
139
|
+
return { key, value: obj[key].trim() };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const nameEntry = entries.find(([key, value]) => {
|
|
143
|
+
return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
|
|
144
|
+
});
|
|
145
|
+
if (nameEntry) {
|
|
146
|
+
return { key: nameEntry[0], value: nameEntry[1].trim() };
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function findSemanticEntry(obj, keys) {
|
|
151
|
+
const entries = Object.entries(obj);
|
|
152
|
+
const exactEntry = entries.find(([key]) => keys.includes(key));
|
|
153
|
+
if (exactEntry) return exactEntry;
|
|
154
|
+
return entries.find(([key]) => {
|
|
155
|
+
const normalizedKey = key.toLowerCase();
|
|
156
|
+
return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
|
|
157
|
+
}) ?? null;
|
|
158
|
+
}
|
|
159
|
+
function shouldDescribeSemantically(key, value) {
|
|
160
|
+
return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
|
|
161
|
+
}
|
|
162
|
+
function formatSemanticValue(value) {
|
|
163
|
+
if (typeof value === "number") return formatNumber(value);
|
|
164
|
+
if (typeof value === "boolean") return value ? "enabled" : "disabled";
|
|
165
|
+
return String(value);
|
|
166
|
+
}
|
|
167
|
+
function describeClaim(claim) {
|
|
168
|
+
const parts = [];
|
|
169
|
+
if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
|
|
170
|
+
if (typeof claim.requestedAmount === "number") {
|
|
171
|
+
parts.push(`for ${formatNumber(claim.requestedAmount)}`);
|
|
172
|
+
}
|
|
173
|
+
if (typeof claim.claimNumber === "string") {
|
|
174
|
+
parts.push(`(reference ${claim.claimNumber})`);
|
|
175
|
+
}
|
|
176
|
+
return parts.length ? `${parts.join(" ")}.` : null;
|
|
177
|
+
}
|
|
178
|
+
function formatNumber(value) {
|
|
179
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
180
|
+
}
|
|
181
|
+
function formatPropertyClause(key, value, depth) {
|
|
182
|
+
const naturalKey = camelToWords(key);
|
|
183
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
184
|
+
if (["internal", "metadata", "debug"].includes(key)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const nested = Object.entries(value).filter(([k]) => !["id", "timestamp", "apiKey", "createdAt", "updatedAt"].includes(k)).map(([k, v]) => {
|
|
188
|
+
if (typeof v === "object" && v !== null) {
|
|
189
|
+
return formatPropertyClause(k, v, depth + 1);
|
|
190
|
+
}
|
|
191
|
+
const propValue = toNatural(v, depth + 1);
|
|
192
|
+
if (propValue === "yes" || propValue === "no") {
|
|
193
|
+
return `${camelToWords(k)} ${propValue === "yes" ? "enabled" : "disabled"}`;
|
|
194
|
+
}
|
|
195
|
+
return `${camelToWords(k)} ${propValue}`;
|
|
196
|
+
}).filter((c) => c && c.trim()).join(", ");
|
|
197
|
+
if (!nested) return null;
|
|
198
|
+
return `${naturalKey}: ${nested}`;
|
|
199
|
+
}
|
|
200
|
+
if (Array.isArray(value)) {
|
|
201
|
+
if (value.length === 0) return null;
|
|
202
|
+
if (["skills", "hobbies", "interests", "tags", "languages", "items"].includes(key)) {
|
|
203
|
+
const items2 = value.map((item) => {
|
|
204
|
+
const str = String(item);
|
|
205
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
206
|
+
});
|
|
207
|
+
return `Having ${joinNaturalList(items2)}`;
|
|
208
|
+
}
|
|
209
|
+
const items = joinNaturalList(value.map((item) => toNatural(item, depth + 1)));
|
|
210
|
+
return `${naturalKey}: ${items}`;
|
|
211
|
+
}
|
|
212
|
+
const naturalValue = toNatural(value, depth + 1);
|
|
213
|
+
if (naturalValue === "yes") return `${naturalKey} enabled`;
|
|
214
|
+
if (naturalValue === "no") return `${naturalKey} disabled`;
|
|
215
|
+
if (key === "theme") return `prefers the ${naturalValue} ${key}`;
|
|
216
|
+
if (key === "age") return `age: ${naturalValue}`;
|
|
217
|
+
if (key === "email") return `email: ${naturalValue}`;
|
|
218
|
+
if (key === "city") return `address: ${naturalValue}`;
|
|
219
|
+
if (key === "debug" && naturalValue === "yes") return `debug enabled`;
|
|
220
|
+
return `${naturalKey}: ${naturalValue}`;
|
|
221
|
+
}
|
|
222
|
+
function camelToWords(str) {
|
|
223
|
+
return str.replace(/([A-Z])/g, " $1").toLowerCase().trim();
|
|
224
|
+
}
|
|
225
|
+
function isPlainObject(value) {
|
|
226
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
227
|
+
}
|
|
228
|
+
function joinNaturalList(items) {
|
|
229
|
+
if (items.length === 0) return "";
|
|
230
|
+
if (items.length === 1) return items[0];
|
|
231
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
232
|
+
const lastItem = items[items.length - 1];
|
|
233
|
+
return `${items.slice(0, -1).join(", ")} and ${lastItem}`;
|
|
234
|
+
}
|
|
235
|
+
function stripTrailingPeriod(value) {
|
|
236
|
+
return value.replace(/\.+$/, "");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export {
|
|
240
|
+
toNatural
|
|
241
|
+
};
|
package/dist/cli.cjs
CHANGED
|
@@ -285,6 +285,15 @@ function toNatural(data, depth = 0) {
|
|
|
285
285
|
return String(data);
|
|
286
286
|
}
|
|
287
287
|
function buildContextualStory(obj, depth = 0) {
|
|
288
|
+
const semanticStory = buildSemanticStory(obj);
|
|
289
|
+
if (semanticStory) return semanticStory;
|
|
290
|
+
const nestedEntity = Object.values(obj).find((value) => {
|
|
291
|
+
return isPlainObject2(value) && buildSemanticStory(value) !== null;
|
|
292
|
+
});
|
|
293
|
+
if (nestedEntity) {
|
|
294
|
+
const nestedStory = buildSemanticStory(nestedEntity);
|
|
295
|
+
if (nestedStory) return nestedStory;
|
|
296
|
+
}
|
|
288
297
|
let name = obj.name || obj.userName || obj.user;
|
|
289
298
|
if (typeof name === "object" && name !== null && name.name) {
|
|
290
299
|
name = name.name;
|
|
@@ -309,6 +318,133 @@ function buildContextualStory(obj, depth = 0) {
|
|
|
309
318
|
}
|
|
310
319
|
return story + ".";
|
|
311
320
|
}
|
|
321
|
+
function buildSemanticStory(obj) {
|
|
322
|
+
if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
|
|
323
|
+
const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
|
|
324
|
+
const details = [
|
|
325
|
+
typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
|
|
326
|
+
typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
|
|
327
|
+
].filter((value) => value !== null);
|
|
328
|
+
const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
|
|
329
|
+
if (isPlainObject2(obj.claim)) {
|
|
330
|
+
const claim = describeClaim(obj.claim);
|
|
331
|
+
if (claim) sentences.push(claim);
|
|
332
|
+
}
|
|
333
|
+
if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
|
|
334
|
+
sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
|
|
335
|
+
}
|
|
336
|
+
return sentences.join(" ");
|
|
337
|
+
}
|
|
338
|
+
const subject = findSubject(obj);
|
|
339
|
+
const stateEntry = findSemanticEntry(obj, [
|
|
340
|
+
"status",
|
|
341
|
+
"state",
|
|
342
|
+
"condition",
|
|
343
|
+
"stage",
|
|
344
|
+
"role",
|
|
345
|
+
"type",
|
|
346
|
+
"category",
|
|
347
|
+
"classification",
|
|
348
|
+
"priority",
|
|
349
|
+
"phase",
|
|
350
|
+
"mode",
|
|
351
|
+
"availability",
|
|
352
|
+
"outcome",
|
|
353
|
+
"result",
|
|
354
|
+
"health",
|
|
355
|
+
"progress",
|
|
356
|
+
"visibility",
|
|
357
|
+
"access",
|
|
358
|
+
"membership",
|
|
359
|
+
"sentiment",
|
|
360
|
+
"severity"
|
|
361
|
+
]);
|
|
362
|
+
if (subject && stateEntry && typeof stateEntry[1] === "string") {
|
|
363
|
+
const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
|
|
364
|
+
return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function findSubject(obj) {
|
|
369
|
+
const subjectKeys = [
|
|
370
|
+
"name",
|
|
371
|
+
"title",
|
|
372
|
+
"label",
|
|
373
|
+
"displayName",
|
|
374
|
+
"entityName",
|
|
375
|
+
"fullName",
|
|
376
|
+
"userName",
|
|
377
|
+
"username",
|
|
378
|
+
"personName",
|
|
379
|
+
"customerName",
|
|
380
|
+
"clientName",
|
|
381
|
+
"ownerName",
|
|
382
|
+
"accountName",
|
|
383
|
+
"companyName",
|
|
384
|
+
"organizationName",
|
|
385
|
+
"teamName",
|
|
386
|
+
"departmentName",
|
|
387
|
+
"projectName",
|
|
388
|
+
"productName",
|
|
389
|
+
"serviceName",
|
|
390
|
+
"resourceName",
|
|
391
|
+
"fileName",
|
|
392
|
+
"deviceName",
|
|
393
|
+
"hostName",
|
|
394
|
+
"applicationName",
|
|
395
|
+
"appName",
|
|
396
|
+
"taskName",
|
|
397
|
+
"eventName",
|
|
398
|
+
"itemName",
|
|
399
|
+
"orderName",
|
|
400
|
+
"patientName",
|
|
401
|
+
"employeeName"
|
|
402
|
+
];
|
|
403
|
+
const entries = Object.entries(obj);
|
|
404
|
+
for (const key of subjectKeys) {
|
|
405
|
+
if (typeof obj[key] === "string" && obj[key].trim()) {
|
|
406
|
+
return { key, value: obj[key].trim() };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const nameEntry = entries.find(([key, value]) => {
|
|
410
|
+
return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
|
|
411
|
+
});
|
|
412
|
+
if (nameEntry) {
|
|
413
|
+
return { key: nameEntry[0], value: nameEntry[1].trim() };
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
function findSemanticEntry(obj, keys) {
|
|
418
|
+
const entries = Object.entries(obj);
|
|
419
|
+
const exactEntry = entries.find(([key]) => keys.includes(key));
|
|
420
|
+
if (exactEntry) return exactEntry;
|
|
421
|
+
return entries.find(([key]) => {
|
|
422
|
+
const normalizedKey = key.toLowerCase();
|
|
423
|
+
return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
|
|
424
|
+
}) ?? null;
|
|
425
|
+
}
|
|
426
|
+
function shouldDescribeSemantically(key, value) {
|
|
427
|
+
return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
|
|
428
|
+
}
|
|
429
|
+
function formatSemanticValue(value) {
|
|
430
|
+
if (typeof value === "number") return formatNumber(value);
|
|
431
|
+
if (typeof value === "boolean") return value ? "enabled" : "disabled";
|
|
432
|
+
return String(value);
|
|
433
|
+
}
|
|
434
|
+
function describeClaim(claim) {
|
|
435
|
+
const parts = [];
|
|
436
|
+
if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
|
|
437
|
+
if (typeof claim.requestedAmount === "number") {
|
|
438
|
+
parts.push(`for ${formatNumber(claim.requestedAmount)}`);
|
|
439
|
+
}
|
|
440
|
+
if (typeof claim.claimNumber === "string") {
|
|
441
|
+
parts.push(`(reference ${claim.claimNumber})`);
|
|
442
|
+
}
|
|
443
|
+
return parts.length ? `${parts.join(" ")}.` : null;
|
|
444
|
+
}
|
|
445
|
+
function formatNumber(value) {
|
|
446
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
447
|
+
}
|
|
312
448
|
function formatPropertyClause(key, value, depth) {
|
|
313
449
|
const naturalKey = camelToWords(key);
|
|
314
450
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AIChain
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FBJ3DHS6.js";
|
|
5
5
|
import "./chunk-2TDVL6BJ.js";
|
|
6
6
|
import "./chunk-6VRLDRJK.js";
|
|
7
7
|
import "./chunk-XE36GLJP.js";
|
|
8
8
|
import "./chunk-L7BC62MT.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-BN3L7TUV.js";
|
|
10
10
|
import "./chunk-ZD536GZF.js";
|
|
11
11
|
import "./chunk-L3HY6AWL.js";
|
|
12
12
|
|
package/dist/core/natural.cjs
CHANGED
|
@@ -42,6 +42,15 @@ function toNatural(data, depth = 0) {
|
|
|
42
42
|
return String(data);
|
|
43
43
|
}
|
|
44
44
|
function buildContextualStory(obj, depth = 0) {
|
|
45
|
+
const semanticStory = buildSemanticStory(obj);
|
|
46
|
+
if (semanticStory) return semanticStory;
|
|
47
|
+
const nestedEntity = Object.values(obj).find((value) => {
|
|
48
|
+
return isPlainObject(value) && buildSemanticStory(value) !== null;
|
|
49
|
+
});
|
|
50
|
+
if (nestedEntity) {
|
|
51
|
+
const nestedStory = buildSemanticStory(nestedEntity);
|
|
52
|
+
if (nestedStory) return nestedStory;
|
|
53
|
+
}
|
|
45
54
|
let name = obj.name || obj.userName || obj.user;
|
|
46
55
|
if (typeof name === "object" && name !== null && name.name) {
|
|
47
56
|
name = name.name;
|
|
@@ -66,6 +75,133 @@ function buildContextualStory(obj, depth = 0) {
|
|
|
66
75
|
}
|
|
67
76
|
return story + ".";
|
|
68
77
|
}
|
|
78
|
+
function buildSemanticStory(obj) {
|
|
79
|
+
if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
|
|
80
|
+
const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
|
|
81
|
+
const details = [
|
|
82
|
+
typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
|
|
83
|
+
typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
|
|
84
|
+
].filter((value) => value !== null);
|
|
85
|
+
const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
|
|
86
|
+
if (isPlainObject(obj.claim)) {
|
|
87
|
+
const claim = describeClaim(obj.claim);
|
|
88
|
+
if (claim) sentences.push(claim);
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
|
|
91
|
+
sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
|
|
92
|
+
}
|
|
93
|
+
return sentences.join(" ");
|
|
94
|
+
}
|
|
95
|
+
const subject = findSubject(obj);
|
|
96
|
+
const stateEntry = findSemanticEntry(obj, [
|
|
97
|
+
"status",
|
|
98
|
+
"state",
|
|
99
|
+
"condition",
|
|
100
|
+
"stage",
|
|
101
|
+
"role",
|
|
102
|
+
"type",
|
|
103
|
+
"category",
|
|
104
|
+
"classification",
|
|
105
|
+
"priority",
|
|
106
|
+
"phase",
|
|
107
|
+
"mode",
|
|
108
|
+
"availability",
|
|
109
|
+
"outcome",
|
|
110
|
+
"result",
|
|
111
|
+
"health",
|
|
112
|
+
"progress",
|
|
113
|
+
"visibility",
|
|
114
|
+
"access",
|
|
115
|
+
"membership",
|
|
116
|
+
"sentiment",
|
|
117
|
+
"severity"
|
|
118
|
+
]);
|
|
119
|
+
if (subject && stateEntry && typeof stateEntry[1] === "string") {
|
|
120
|
+
const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
|
|
121
|
+
return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function findSubject(obj) {
|
|
126
|
+
const subjectKeys = [
|
|
127
|
+
"name",
|
|
128
|
+
"title",
|
|
129
|
+
"label",
|
|
130
|
+
"displayName",
|
|
131
|
+
"entityName",
|
|
132
|
+
"fullName",
|
|
133
|
+
"userName",
|
|
134
|
+
"username",
|
|
135
|
+
"personName",
|
|
136
|
+
"customerName",
|
|
137
|
+
"clientName",
|
|
138
|
+
"ownerName",
|
|
139
|
+
"accountName",
|
|
140
|
+
"companyName",
|
|
141
|
+
"organizationName",
|
|
142
|
+
"teamName",
|
|
143
|
+
"departmentName",
|
|
144
|
+
"projectName",
|
|
145
|
+
"productName",
|
|
146
|
+
"serviceName",
|
|
147
|
+
"resourceName",
|
|
148
|
+
"fileName",
|
|
149
|
+
"deviceName",
|
|
150
|
+
"hostName",
|
|
151
|
+
"applicationName",
|
|
152
|
+
"appName",
|
|
153
|
+
"taskName",
|
|
154
|
+
"eventName",
|
|
155
|
+
"itemName",
|
|
156
|
+
"orderName",
|
|
157
|
+
"patientName",
|
|
158
|
+
"employeeName"
|
|
159
|
+
];
|
|
160
|
+
const entries = Object.entries(obj);
|
|
161
|
+
for (const key of subjectKeys) {
|
|
162
|
+
if (typeof obj[key] === "string" && obj[key].trim()) {
|
|
163
|
+
return { key, value: obj[key].trim() };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const nameEntry = entries.find(([key, value]) => {
|
|
167
|
+
return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
|
|
168
|
+
});
|
|
169
|
+
if (nameEntry) {
|
|
170
|
+
return { key: nameEntry[0], value: nameEntry[1].trim() };
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function findSemanticEntry(obj, keys) {
|
|
175
|
+
const entries = Object.entries(obj);
|
|
176
|
+
const exactEntry = entries.find(([key]) => keys.includes(key));
|
|
177
|
+
if (exactEntry) return exactEntry;
|
|
178
|
+
return entries.find(([key]) => {
|
|
179
|
+
const normalizedKey = key.toLowerCase();
|
|
180
|
+
return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
|
|
181
|
+
}) ?? null;
|
|
182
|
+
}
|
|
183
|
+
function shouldDescribeSemantically(key, value) {
|
|
184
|
+
return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
|
|
185
|
+
}
|
|
186
|
+
function formatSemanticValue(value) {
|
|
187
|
+
if (typeof value === "number") return formatNumber(value);
|
|
188
|
+
if (typeof value === "boolean") return value ? "enabled" : "disabled";
|
|
189
|
+
return String(value);
|
|
190
|
+
}
|
|
191
|
+
function describeClaim(claim) {
|
|
192
|
+
const parts = [];
|
|
193
|
+
if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
|
|
194
|
+
if (typeof claim.requestedAmount === "number") {
|
|
195
|
+
parts.push(`for ${formatNumber(claim.requestedAmount)}`);
|
|
196
|
+
}
|
|
197
|
+
if (typeof claim.claimNumber === "string") {
|
|
198
|
+
parts.push(`(reference ${claim.claimNumber})`);
|
|
199
|
+
}
|
|
200
|
+
return parts.length ? `${parts.join(" ")}.` : null;
|
|
201
|
+
}
|
|
202
|
+
function formatNumber(value) {
|
|
203
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
204
|
+
}
|
|
69
205
|
function formatPropertyClause(key, value, depth) {
|
|
70
206
|
const naturalKey = camelToWords(key);
|
|
71
207
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
package/dist/core/natural.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -294,6 +294,15 @@ function toNatural(data, depth = 0) {
|
|
|
294
294
|
return String(data);
|
|
295
295
|
}
|
|
296
296
|
function buildContextualStory(obj, depth = 0) {
|
|
297
|
+
const semanticStory = buildSemanticStory(obj);
|
|
298
|
+
if (semanticStory) return semanticStory;
|
|
299
|
+
const nestedEntity = Object.values(obj).find((value) => {
|
|
300
|
+
return isPlainObject2(value) && buildSemanticStory(value) !== null;
|
|
301
|
+
});
|
|
302
|
+
if (nestedEntity) {
|
|
303
|
+
const nestedStory = buildSemanticStory(nestedEntity);
|
|
304
|
+
if (nestedStory) return nestedStory;
|
|
305
|
+
}
|
|
297
306
|
let name = obj.name || obj.userName || obj.user;
|
|
298
307
|
if (typeof name === "object" && name !== null && name.name) {
|
|
299
308
|
name = name.name;
|
|
@@ -318,6 +327,133 @@ function buildContextualStory(obj, depth = 0) {
|
|
|
318
327
|
}
|
|
319
328
|
return story + ".";
|
|
320
329
|
}
|
|
330
|
+
function buildSemanticStory(obj) {
|
|
331
|
+
if (typeof obj.holderName === "string" && typeof obj.policyNumber === "string") {
|
|
332
|
+
const subject2 = `${obj.holderName} has policy ${obj.policyNumber}`;
|
|
333
|
+
const details = [
|
|
334
|
+
typeof obj.planType === "string" ? `a ${obj.planType} plan` : null,
|
|
335
|
+
typeof obj.premiumAmount === "number" ? `with a premium of ${formatNumber(obj.premiumAmount)}` : null
|
|
336
|
+
].filter((value) => value !== null);
|
|
337
|
+
const sentences = [`${subject2}${details.length ? `, ${details.join(", ")}` : ""}.`];
|
|
338
|
+
if (isPlainObject2(obj.claim)) {
|
|
339
|
+
const claim = describeClaim(obj.claim);
|
|
340
|
+
if (claim) sentences.push(claim);
|
|
341
|
+
}
|
|
342
|
+
if (Array.isArray(obj.dependents) && obj.dependents.length > 0) {
|
|
343
|
+
sentences.push(`${obj.holderName}'s dependents are ${joinNaturalList(obj.dependents.map(String))}.`);
|
|
344
|
+
}
|
|
345
|
+
return sentences.join(" ");
|
|
346
|
+
}
|
|
347
|
+
const subject = findSubject(obj);
|
|
348
|
+
const stateEntry = findSemanticEntry(obj, [
|
|
349
|
+
"status",
|
|
350
|
+
"state",
|
|
351
|
+
"condition",
|
|
352
|
+
"stage",
|
|
353
|
+
"role",
|
|
354
|
+
"type",
|
|
355
|
+
"category",
|
|
356
|
+
"classification",
|
|
357
|
+
"priority",
|
|
358
|
+
"phase",
|
|
359
|
+
"mode",
|
|
360
|
+
"availability",
|
|
361
|
+
"outcome",
|
|
362
|
+
"result",
|
|
363
|
+
"health",
|
|
364
|
+
"progress",
|
|
365
|
+
"visibility",
|
|
366
|
+
"access",
|
|
367
|
+
"membership",
|
|
368
|
+
"sentiment",
|
|
369
|
+
"severity"
|
|
370
|
+
]);
|
|
371
|
+
if (subject && stateEntry && typeof stateEntry[1] === "string") {
|
|
372
|
+
const details = Object.entries(obj).filter(([key, value]) => key !== stateEntry[0] && key !== subject.key && shouldDescribeSemantically(key, value)).map(([key, value]) => `${camelToWords(key)} ${formatSemanticValue(value)}`);
|
|
373
|
+
return `${subject.value} is ${stateEntry[1]}${details.length ? ` and has ${details.join(", ")}` : ""}.`;
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
function findSubject(obj) {
|
|
378
|
+
const subjectKeys = [
|
|
379
|
+
"name",
|
|
380
|
+
"title",
|
|
381
|
+
"label",
|
|
382
|
+
"displayName",
|
|
383
|
+
"entityName",
|
|
384
|
+
"fullName",
|
|
385
|
+
"userName",
|
|
386
|
+
"username",
|
|
387
|
+
"personName",
|
|
388
|
+
"customerName",
|
|
389
|
+
"clientName",
|
|
390
|
+
"ownerName",
|
|
391
|
+
"accountName",
|
|
392
|
+
"companyName",
|
|
393
|
+
"organizationName",
|
|
394
|
+
"teamName",
|
|
395
|
+
"departmentName",
|
|
396
|
+
"projectName",
|
|
397
|
+
"productName",
|
|
398
|
+
"serviceName",
|
|
399
|
+
"resourceName",
|
|
400
|
+
"fileName",
|
|
401
|
+
"deviceName",
|
|
402
|
+
"hostName",
|
|
403
|
+
"applicationName",
|
|
404
|
+
"appName",
|
|
405
|
+
"taskName",
|
|
406
|
+
"eventName",
|
|
407
|
+
"itemName",
|
|
408
|
+
"orderName",
|
|
409
|
+
"patientName",
|
|
410
|
+
"employeeName"
|
|
411
|
+
];
|
|
412
|
+
const entries = Object.entries(obj);
|
|
413
|
+
for (const key of subjectKeys) {
|
|
414
|
+
if (typeof obj[key] === "string" && obj[key].trim()) {
|
|
415
|
+
return { key, value: obj[key].trim() };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const nameEntry = entries.find(([key, value]) => {
|
|
419
|
+
return key.toLowerCase().includes("name") && typeof value === "string" && value.trim().length > 0;
|
|
420
|
+
});
|
|
421
|
+
if (nameEntry) {
|
|
422
|
+
return { key: nameEntry[0], value: nameEntry[1].trim() };
|
|
423
|
+
}
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
function findSemanticEntry(obj, keys) {
|
|
427
|
+
const entries = Object.entries(obj);
|
|
428
|
+
const exactEntry = entries.find(([key]) => keys.includes(key));
|
|
429
|
+
if (exactEntry) return exactEntry;
|
|
430
|
+
return entries.find(([key]) => {
|
|
431
|
+
const normalizedKey = key.toLowerCase();
|
|
432
|
+
return keys.some((semanticKey) => normalizedKey.includes(semanticKey.toLowerCase()));
|
|
433
|
+
}) ?? null;
|
|
434
|
+
}
|
|
435
|
+
function shouldDescribeSemantically(key, value) {
|
|
436
|
+
return (typeof value === "string" || typeof value === "number" || typeof value === "boolean") && !["id", "timestamp", "createdAt", "updatedAt", "apiKey", "debug"].includes(key);
|
|
437
|
+
}
|
|
438
|
+
function formatSemanticValue(value) {
|
|
439
|
+
if (typeof value === "number") return formatNumber(value);
|
|
440
|
+
if (typeof value === "boolean") return value ? "enabled" : "disabled";
|
|
441
|
+
return String(value);
|
|
442
|
+
}
|
|
443
|
+
function describeClaim(claim) {
|
|
444
|
+
const parts = [];
|
|
445
|
+
if (typeof claim.status === "string") parts.push(`The claim is ${claim.status}`);
|
|
446
|
+
if (typeof claim.requestedAmount === "number") {
|
|
447
|
+
parts.push(`for ${formatNumber(claim.requestedAmount)}`);
|
|
448
|
+
}
|
|
449
|
+
if (typeof claim.claimNumber === "string") {
|
|
450
|
+
parts.push(`(reference ${claim.claimNumber})`);
|
|
451
|
+
}
|
|
452
|
+
return parts.length ? `${parts.join(" ")}.` : null;
|
|
453
|
+
}
|
|
454
|
+
function formatNumber(value) {
|
|
455
|
+
return new Intl.NumberFormat("en-US").format(value);
|
|
456
|
+
}
|
|
321
457
|
function formatPropertyClause(key, value, depth) {
|
|
322
458
|
const naturalKey = camelToWords(key);
|
|
323
459
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AIChain
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-FBJ3DHS6.js";
|
|
4
4
|
import {
|
|
5
5
|
analyze
|
|
6
6
|
} from "./chunk-2TDVL6BJ.js";
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./chunk-L7BC62MT.js";
|
|
16
16
|
import {
|
|
17
17
|
toNatural
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-BN3L7TUV.js";
|
|
19
19
|
import {
|
|
20
20
|
prune
|
|
21
21
|
} from "./chunk-ZD536GZF.js";
|
package/package.json
CHANGED
package/dist/chunk-D4CFTFM3.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
// src/core/natural.ts
|
|
2
|
-
function toNatural(data, depth = 0) {
|
|
3
|
-
if (data === null || data === void 0) return "nothing";
|
|
4
|
-
if (typeof data === "string") return data;
|
|
5
|
-
if (typeof data === "number") return String(data);
|
|
6
|
-
if (typeof data === "boolean") return data ? "yes" : "no";
|
|
7
|
-
if (Array.isArray(data)) {
|
|
8
|
-
if (data.length === 0) return "empty list";
|
|
9
|
-
if (data.every(isPlainObject)) {
|
|
10
|
-
return data.map((item, index) => `${index + 1}. ${stripTrailingPeriod(toNatural(item, depth + 1))}.`).join(" ");
|
|
11
|
-
}
|
|
12
|
-
const items = data.map((item) => toNatural(item, depth + 1));
|
|
13
|
-
return joinNaturalList(items);
|
|
14
|
-
}
|
|
15
|
-
if (typeof data === "object") {
|
|
16
|
-
return buildContextualStory(data, depth);
|
|
17
|
-
}
|
|
18
|
-
return String(data);
|
|
19
|
-
}
|
|
20
|
-
function buildContextualStory(obj, depth = 0) {
|
|
21
|
-
let name = obj.name || obj.userName || obj.user;
|
|
22
|
-
if (typeof name === "object" && name !== null && name.name) {
|
|
23
|
-
name = name.name;
|
|
24
|
-
}
|
|
25
|
-
const entries = Object.entries(obj).filter(([key]) => {
|
|
26
|
-
return !["id", "timestamp", "apiKey"].includes(key);
|
|
27
|
-
});
|
|
28
|
-
if (entries.length === 0) return "";
|
|
29
|
-
let story = name && typeof name === "string" ? `User ${name}` : "";
|
|
30
|
-
const clauses = entries.map(([key, value]) => {
|
|
31
|
-
if (key === "user" && name && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
32
|
-
const userProps = Object.entries(value).filter(([k]) => !["id", "name", "timestamp", "apiKey"].includes(k)).map(([k, v]) => formatPropertyClause(k, v, depth + 1)).filter((c) => c !== null && c !== "").join(", ");
|
|
33
|
-
return userProps ? `(${userProps})` : null;
|
|
34
|
-
}
|
|
35
|
-
return formatPropertyClause(key, value, depth);
|
|
36
|
-
}).filter((c) => c !== null && c !== "");
|
|
37
|
-
if (clauses.length === 0) return story;
|
|
38
|
-
if (story) {
|
|
39
|
-
story += " " + clauses.join(", ");
|
|
40
|
-
} else {
|
|
41
|
-
story = clauses.join(", ");
|
|
42
|
-
}
|
|
43
|
-
return story + ".";
|
|
44
|
-
}
|
|
45
|
-
function formatPropertyClause(key, value, depth) {
|
|
46
|
-
const naturalKey = camelToWords(key);
|
|
47
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
48
|
-
if (["internal", "metadata", "debug"].includes(key)) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
const nested = Object.entries(value).filter(([k]) => !["id", "timestamp", "apiKey", "createdAt", "updatedAt"].includes(k)).map(([k, v]) => {
|
|
52
|
-
if (typeof v === "object" && v !== null) {
|
|
53
|
-
return formatPropertyClause(k, v, depth + 1);
|
|
54
|
-
}
|
|
55
|
-
const propValue = toNatural(v, depth + 1);
|
|
56
|
-
if (propValue === "yes" || propValue === "no") {
|
|
57
|
-
return `${camelToWords(k)} ${propValue === "yes" ? "enabled" : "disabled"}`;
|
|
58
|
-
}
|
|
59
|
-
return `${camelToWords(k)} ${propValue}`;
|
|
60
|
-
}).filter((c) => c && c.trim()).join(", ");
|
|
61
|
-
if (!nested) return null;
|
|
62
|
-
return `${naturalKey}: ${nested}`;
|
|
63
|
-
}
|
|
64
|
-
if (Array.isArray(value)) {
|
|
65
|
-
if (value.length === 0) return null;
|
|
66
|
-
if (["skills", "hobbies", "interests", "tags", "languages", "items"].includes(key)) {
|
|
67
|
-
const items2 = value.map((item) => {
|
|
68
|
-
const str = String(item);
|
|
69
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
70
|
-
});
|
|
71
|
-
return `Having ${joinNaturalList(items2)}`;
|
|
72
|
-
}
|
|
73
|
-
const items = joinNaturalList(value.map((item) => toNatural(item, depth + 1)));
|
|
74
|
-
return `${naturalKey}: ${items}`;
|
|
75
|
-
}
|
|
76
|
-
const naturalValue = toNatural(value, depth + 1);
|
|
77
|
-
if (naturalValue === "yes") return `${naturalKey} enabled`;
|
|
78
|
-
if (naturalValue === "no") return `${naturalKey} disabled`;
|
|
79
|
-
if (key === "theme") return `prefers the ${naturalValue} ${key}`;
|
|
80
|
-
if (key === "age") return `age: ${naturalValue}`;
|
|
81
|
-
if (key === "email") return `email: ${naturalValue}`;
|
|
82
|
-
if (key === "city") return `address: ${naturalValue}`;
|
|
83
|
-
if (key === "debug" && naturalValue === "yes") return `debug enabled`;
|
|
84
|
-
return `${naturalKey}: ${naturalValue}`;
|
|
85
|
-
}
|
|
86
|
-
function camelToWords(str) {
|
|
87
|
-
return str.replace(/([A-Z])/g, " $1").toLowerCase().trim();
|
|
88
|
-
}
|
|
89
|
-
function isPlainObject(value) {
|
|
90
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
91
|
-
}
|
|
92
|
-
function joinNaturalList(items) {
|
|
93
|
-
if (items.length === 0) return "";
|
|
94
|
-
if (items.length === 1) return items[0];
|
|
95
|
-
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
96
|
-
const lastItem = items[items.length - 1];
|
|
97
|
-
return `${items.slice(0, -1).join(", ")} and ${lastItem}`;
|
|
98
|
-
}
|
|
99
|
-
function stripTrailingPeriod(value) {
|
|
100
|
-
return value.replace(/\.+$/, "");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export {
|
|
104
|
-
toNatural
|
|
105
|
-
};
|