@timeax/digital-service-engine 0.2.7 → 0.2.9
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/dist/core/index.cjs +1688 -1218
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +21 -1
- package/dist/core/index.d.ts +21 -1
- package/dist/core/index.js +1686 -1218
- package/dist/core/index.js.map +1 -1
- package/dist/react/index.cjs +586 -376
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +8 -0
- package/dist/react/index.d.ts +8 -0
- package/dist/react/index.js +586 -376
- package/dist/react/index.js.map +1 -1
- package/dist/workspace/index.cjs +595 -389
- package/dist/workspace/index.cjs.map +1 -1
- package/dist/workspace/index.d.cts +8 -0
- package/dist/workspace/index.d.ts +8 -0
- package/dist/workspace/index.js +595 -389
- package/dist/workspace/index.js.map +1 -1
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -1260,13 +1260,7 @@ function resolveRootTags(tags) {
|
|
|
1260
1260
|
const roots = tags.filter((t) => !t.bind_id);
|
|
1261
1261
|
return roots.length ? roots : tags.slice(0, 1);
|
|
1262
1262
|
}
|
|
1263
|
-
function
|
|
1264
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1265
|
-
const inc = (_a = v.props.includes_for_buttons) != null ? _a : {};
|
|
1266
|
-
const exc = (_b = v.props.excludes_for_buttons) != null ? _b : {};
|
|
1267
|
-
return ((_d = (_c = inc[trigger]) == null ? void 0 : _c.length) != null ? _d : 0) > 0 || ((_f = (_e = exc[trigger]) == null ? void 0 : _e.length) != null ? _f : 0) > 0;
|
|
1268
|
-
}
|
|
1269
|
-
function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectful) {
|
|
1263
|
+
function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
|
|
1270
1264
|
var _a;
|
|
1271
1265
|
const visible = visibleFieldsUnder(v.props, tagId, {
|
|
1272
1266
|
selectedKeys
|
|
@@ -1275,11 +1269,11 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectfu
|
|
|
1275
1269
|
for (const f of visible) {
|
|
1276
1270
|
if (f.button === true) {
|
|
1277
1271
|
const t = f.id;
|
|
1278
|
-
if (
|
|
1272
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
1279
1273
|
}
|
|
1280
1274
|
for (const o of (_a = f.options) != null ? _a : []) {
|
|
1281
|
-
const t =
|
|
1282
|
-
if (
|
|
1275
|
+
const t = o.id;
|
|
1276
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
1283
1277
|
}
|
|
1284
1278
|
}
|
|
1285
1279
|
triggers.sort();
|
|
@@ -1379,20 +1373,38 @@ function dedupeErrorsInPlace(v, startIndex) {
|
|
|
1379
1373
|
v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
|
|
1380
1374
|
}
|
|
1381
1375
|
function validateVisibility(v, options = {}) {
|
|
1382
|
-
var _a, _b, _c;
|
|
1376
|
+
var _a, _b, _c, _d, _e;
|
|
1377
|
+
v.simulatedVisibilityContexts = [];
|
|
1383
1378
|
const simulate = options.simulate === true;
|
|
1384
1379
|
if (!simulate) {
|
|
1385
1380
|
runVisibilityRulesOnce(v);
|
|
1381
|
+
for (const tag of v.tags) {
|
|
1382
|
+
v.simulatedVisibilityContexts.push({
|
|
1383
|
+
tagId: tag.id,
|
|
1384
|
+
selectedKeys: Array.from(v.selectedKeys),
|
|
1385
|
+
visibleFieldIds: v.fieldsVisibleUnder(tag.id).map((f) => f.id)
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1386
1388
|
return;
|
|
1387
1389
|
}
|
|
1388
1390
|
const maxStates = Math.max(1, (_a = options.maxStates) != null ? _a : 500);
|
|
1389
1391
|
const maxDepth = Math.max(0, (_b = options.maxDepth) != null ? _b : 6);
|
|
1390
1392
|
const onlyEffectful = options.onlyEffectfulTriggers !== false;
|
|
1393
|
+
const effectfulKeys = /* @__PURE__ */ new Set();
|
|
1394
|
+
if (onlyEffectful) {
|
|
1395
|
+
for (const key of Object.keys((_c = v.props.includes_for_buttons) != null ? _c : {})) {
|
|
1396
|
+
effectfulKeys.add(key);
|
|
1397
|
+
}
|
|
1398
|
+
for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
|
|
1399
|
+
effectfulKeys.add(key);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1391
1402
|
const roots = resolveRootTags(v.tags);
|
|
1392
1403
|
const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
|
|
1393
|
-
const originalSelected = new Set((
|
|
1404
|
+
const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
|
|
1394
1405
|
const errorsStart = v.errors.length;
|
|
1395
1406
|
const visited = /* @__PURE__ */ new Set();
|
|
1407
|
+
const seenContexts = /* @__PURE__ */ new Set();
|
|
1396
1408
|
const stack = [];
|
|
1397
1409
|
for (const rt of rootTags) {
|
|
1398
1410
|
stack.push({
|
|
@@ -1405,10 +1417,27 @@ function validateVisibility(v, options = {}) {
|
|
|
1405
1417
|
while (stack.length) {
|
|
1406
1418
|
if (validatedStates >= maxStates) break;
|
|
1407
1419
|
const state = stack.pop();
|
|
1408
|
-
const sig = stableKeyOfSelection(state.selected)
|
|
1420
|
+
const sig = `${state.rootTagId}::${stableKeyOfSelection(state.selected)}`;
|
|
1409
1421
|
if (visited.has(sig)) continue;
|
|
1410
1422
|
visited.add(sig);
|
|
1411
1423
|
v.selectedKeys = state.selected;
|
|
1424
|
+
const visibleNow = visibleFieldsUnder(v.props, state.rootTagId, {
|
|
1425
|
+
selectedKeys: state.selected
|
|
1426
|
+
}).map((f) => f.id);
|
|
1427
|
+
const context = {
|
|
1428
|
+
tagId: state.rootTagId,
|
|
1429
|
+
selectedKeys: Array.from(state.selected),
|
|
1430
|
+
visibleFieldIds: visibleNow
|
|
1431
|
+
};
|
|
1432
|
+
const contextKey = [
|
|
1433
|
+
context.tagId,
|
|
1434
|
+
[...context.selectedKeys].sort().join("|"),
|
|
1435
|
+
[...context.visibleFieldIds].sort().join("|")
|
|
1436
|
+
].join("::");
|
|
1437
|
+
if (!seenContexts.has(contextKey)) {
|
|
1438
|
+
seenContexts.add(contextKey);
|
|
1439
|
+
v.simulatedVisibilityContexts.push(context);
|
|
1440
|
+
}
|
|
1412
1441
|
validatedStates++;
|
|
1413
1442
|
runVisibilityRulesOnce(v);
|
|
1414
1443
|
if (state.depth >= maxDepth) continue;
|
|
@@ -1416,7 +1445,7 @@ function validateVisibility(v, options = {}) {
|
|
|
1416
1445
|
v,
|
|
1417
1446
|
state.rootTagId,
|
|
1418
1447
|
state.selected,
|
|
1419
|
-
|
|
1448
|
+
effectfulKeys
|
|
1420
1449
|
);
|
|
1421
1450
|
for (let i = triggers.length - 1; i >= 0; i--) {
|
|
1422
1451
|
const trig = triggers[i];
|
|
@@ -1658,8 +1687,8 @@ function validateOptionMaps(v) {
|
|
|
1658
1687
|
};
|
|
1659
1688
|
}
|
|
1660
1689
|
if (ref.kind === "field") {
|
|
1661
|
-
const
|
|
1662
|
-
if (!
|
|
1690
|
+
const isButton = ref.node.button === true;
|
|
1691
|
+
if (!isButton)
|
|
1663
1692
|
return { ok: false, nodeId: ref.id, affected: [ref.id] };
|
|
1664
1693
|
return { ok: true, nodeId: ref.id, affected: [ref.id] };
|
|
1665
1694
|
}
|
|
@@ -1850,9 +1879,9 @@ function validateServiceVsUserInput(v) {
|
|
|
1850
1879
|
for (const f of v.fields) {
|
|
1851
1880
|
const anySvc = hasAnyServiceOption(f);
|
|
1852
1881
|
const hasName = !!(f.name && f.name.trim());
|
|
1853
|
-
const
|
|
1882
|
+
const isButton = f.button === true;
|
|
1854
1883
|
const hasFieldService = f.service_id !== void 0 && f.service_id !== null;
|
|
1855
|
-
const hasTriggerMap =
|
|
1884
|
+
const hasTriggerMap = isButton && hasButtonTriggerMap(v, f.id);
|
|
1856
1885
|
if (f.type === "custom" && anySvc) {
|
|
1857
1886
|
v.errors.push({
|
|
1858
1887
|
code: "user_input_field_has_service_option",
|
|
@@ -1869,7 +1898,7 @@ function validateServiceVsUserInput(v) {
|
|
|
1869
1898
|
v.errors.push({
|
|
1870
1899
|
code: "service_field_missing_service_id",
|
|
1871
1900
|
severity: "error",
|
|
1872
|
-
message:
|
|
1901
|
+
message: isButton ? `Button field "${f.id}" has no "name", no "service_id", and no includes/excludes trigger map. Add a name, attach a service_id, or configure includes_for_buttons/excludes_for_buttons.` : `Service-backed field "${f.id}" has no "name" and must provide at least one option with a service_id.`,
|
|
1873
1902
|
nodeId: f.id
|
|
1874
1903
|
});
|
|
1875
1904
|
} else {
|
|
@@ -2188,6 +2217,324 @@ function validateRates(v) {
|
|
|
2188
2217
|
}
|
|
2189
2218
|
}
|
|
2190
2219
|
|
|
2220
|
+
// src/core/rate-coherence.ts
|
|
2221
|
+
function buildTriggerEffectMap(props) {
|
|
2222
|
+
var _a, _b;
|
|
2223
|
+
const map = /* @__PURE__ */ new Map();
|
|
2224
|
+
const ensure = (key) => {
|
|
2225
|
+
let item = map.get(key);
|
|
2226
|
+
if (!item) {
|
|
2227
|
+
item = { includes: /* @__PURE__ */ new Set(), excludes: /* @__PURE__ */ new Set() };
|
|
2228
|
+
map.set(key, item);
|
|
2229
|
+
}
|
|
2230
|
+
return item;
|
|
2231
|
+
};
|
|
2232
|
+
for (const [key, ids] of Object.entries((_a = props.includes_for_buttons) != null ? _a : {})) {
|
|
2233
|
+
const item = ensure(key);
|
|
2234
|
+
for (const id of ids != null ? ids : []) item.includes.add(id);
|
|
2235
|
+
}
|
|
2236
|
+
for (const [key, ids] of Object.entries((_b = props.excludes_for_buttons) != null ? _b : {})) {
|
|
2237
|
+
const item = ensure(key);
|
|
2238
|
+
for (const id of ids != null ? ids : []) item.excludes.add(id);
|
|
2239
|
+
}
|
|
2240
|
+
return map;
|
|
2241
|
+
}
|
|
2242
|
+
function isRefExcludedBySelectedKeys(ref, selectedKeys, effectMap) {
|
|
2243
|
+
for (const key of selectedKeys) {
|
|
2244
|
+
const effects = effectMap.get(key);
|
|
2245
|
+
if (!effects) continue;
|
|
2246
|
+
if (ref.fieldId && effects.excludes.has(ref.fieldId) || effects.excludes.has(ref.nodeId)) {
|
|
2247
|
+
return true;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
return false;
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/core/validate/steps/rate-coherence.ts
|
|
2254
|
+
function normalizeRole(role, fallback) {
|
|
2255
|
+
return role === "base" || role === "utility" ? role : fallback;
|
|
2256
|
+
}
|
|
2257
|
+
function uniqueStrings(values) {
|
|
2258
|
+
const out = /* @__PURE__ */ new Set();
|
|
2259
|
+
for (const value of values) {
|
|
2260
|
+
if (!value) continue;
|
|
2261
|
+
out.add(value);
|
|
2262
|
+
}
|
|
2263
|
+
return Array.from(out);
|
|
2264
|
+
}
|
|
2265
|
+
function getRate(serviceMap, serviceId) {
|
|
2266
|
+
const cap = getServiceCapability(serviceMap, serviceId);
|
|
2267
|
+
const rate = cap == null ? void 0 : cap.rate;
|
|
2268
|
+
if (typeof rate !== "number" || !Number.isFinite(rate)) return void 0;
|
|
2269
|
+
return rate;
|
|
2270
|
+
}
|
|
2271
|
+
function collectContextRefs(tag, visibleFields, serviceMap) {
|
|
2272
|
+
var _a, _b, _c, _d, _e;
|
|
2273
|
+
const serviceRefs = [];
|
|
2274
|
+
let tagDefault;
|
|
2275
|
+
if (tag.service_id !== void 0 && tag.service_id !== null) {
|
|
2276
|
+
const tagRate = getRate(serviceMap, tag.service_id);
|
|
2277
|
+
if (tagRate != null) {
|
|
2278
|
+
tagDefault = {
|
|
2279
|
+
key: tag.id,
|
|
2280
|
+
nodeId: tag.id,
|
|
2281
|
+
nodeKind: "tag",
|
|
2282
|
+
serviceId: tag.service_id,
|
|
2283
|
+
rate: tagRate,
|
|
2284
|
+
label: (_a = tag.label) != null ? _a : tag.id,
|
|
2285
|
+
pricingRole: "base"
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
for (const field of visibleFields) {
|
|
2290
|
+
const fieldRole = normalizeRole(field.pricing_role, "base");
|
|
2291
|
+
if (field.service_id !== void 0 && field.service_id !== null) {
|
|
2292
|
+
const rate = getRate(serviceMap, field.service_id);
|
|
2293
|
+
if (rate != null) {
|
|
2294
|
+
serviceRefs.push({
|
|
2295
|
+
key: field.id,
|
|
2296
|
+
nodeId: field.id,
|
|
2297
|
+
fieldId: field.id,
|
|
2298
|
+
nodeKind: "button",
|
|
2299
|
+
serviceId: field.service_id,
|
|
2300
|
+
rate,
|
|
2301
|
+
label: (_b = field.label) != null ? _b : field.id,
|
|
2302
|
+
pricingRole: fieldRole
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
for (const option of (_c = field.options) != null ? _c : []) {
|
|
2307
|
+
if (option.service_id === void 0 || option.service_id === null) continue;
|
|
2308
|
+
const rate = getRate(serviceMap, option.service_id);
|
|
2309
|
+
if (rate == null) continue;
|
|
2310
|
+
serviceRefs.push({
|
|
2311
|
+
key: option.id,
|
|
2312
|
+
nodeId: option.id,
|
|
2313
|
+
fieldId: field.id,
|
|
2314
|
+
nodeKind: "option",
|
|
2315
|
+
serviceId: option.service_id,
|
|
2316
|
+
rate,
|
|
2317
|
+
label: (_d = option.label) != null ? _d : option.id,
|
|
2318
|
+
pricingRole: normalizeRole((_e = option.pricing_role) != null ? _e : field.pricing_role, "base")
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return { tagDefault, serviceRefs };
|
|
2323
|
+
}
|
|
2324
|
+
function pickHighestRatePrimary(refs) {
|
|
2325
|
+
return refs.reduce((best, cur) => {
|
|
2326
|
+
if (!best) return cur;
|
|
2327
|
+
if (cur.rate > best.rate) return cur;
|
|
2328
|
+
if (cur.rate < best.rate) return best;
|
|
2329
|
+
return cur.nodeId < best.nodeId ? cur : best;
|
|
2330
|
+
}, void 0);
|
|
2331
|
+
}
|
|
2332
|
+
function validateRateCoherenceForVisibleContext(params) {
|
|
2333
|
+
const { v, tagId, selectedKeys, visibleFieldIds, effectMap, seen } = params;
|
|
2334
|
+
const tag = v.tagById.get(tagId);
|
|
2335
|
+
if (!tag) return;
|
|
2336
|
+
const visibleFields = visibleFieldIds.map((id) => v.fieldById.get(id)).filter(Boolean);
|
|
2337
|
+
const { tagDefault, serviceRefs: allServiceRefs } = collectContextRefs(
|
|
2338
|
+
tag,
|
|
2339
|
+
visibleFields,
|
|
2340
|
+
v.serviceMap
|
|
2341
|
+
);
|
|
2342
|
+
const baseRefs = allServiceRefs.filter((ref) => ref.pricingRole === "base");
|
|
2343
|
+
if (baseRefs.length === 0 && !tagDefault) return;
|
|
2344
|
+
const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
|
|
2345
|
+
const visibleInvalidFieldIds = visibleFieldIds.filter(
|
|
2346
|
+
(fieldId) => v.invalidRateFieldIds.has(fieldId)
|
|
2347
|
+
);
|
|
2348
|
+
for (const fieldId of visibleInvalidFieldIds) {
|
|
2349
|
+
const internalKey = [
|
|
2350
|
+
"rate-coherence-internal",
|
|
2351
|
+
tagId,
|
|
2352
|
+
[...selectedKeys].sort().join("|"),
|
|
2353
|
+
fieldId
|
|
2354
|
+
].join("::");
|
|
2355
|
+
if (seen.has(internalKey)) continue;
|
|
2356
|
+
seen.add(internalKey);
|
|
2357
|
+
v.errors.push({
|
|
2358
|
+
code: "rate_coherence_violation",
|
|
2359
|
+
severity: "error",
|
|
2360
|
+
nodeId: fieldId,
|
|
2361
|
+
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
2362
|
+
details: {
|
|
2363
|
+
kind: "internal_field",
|
|
2364
|
+
tagId,
|
|
2365
|
+
selectedKeys: [...selectedKeys],
|
|
2366
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2367
|
+
fieldId,
|
|
2368
|
+
invalidFieldIds: [fieldId],
|
|
2369
|
+
affectedIds: uniqueStrings([tagId, ...selectedKeys, fieldId])
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
const selectedSet = new Set(selectedKeys);
|
|
2374
|
+
const selectedServiceRefs = baseRefs.filter((ref) => selectedSet.has(ref.key));
|
|
2375
|
+
if (baseRefs.length === 0) return;
|
|
2376
|
+
for (let i = 0; i < baseRefs.length; i++) {
|
|
2377
|
+
for (let j = i + 1; j < baseRefs.length; j++) {
|
|
2378
|
+
const left = baseRefs[i];
|
|
2379
|
+
const right = baseRefs[j];
|
|
2380
|
+
const hypotheticalKeys = [...selectedKeys, left.key, right.key];
|
|
2381
|
+
const survivingRefs = baseRefs.filter(
|
|
2382
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
2383
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
2384
|
+
hypotheticalKeys,
|
|
2385
|
+
effectMap
|
|
2386
|
+
)
|
|
2387
|
+
);
|
|
2388
|
+
const survivingSet = new Set(survivingRefs.map((ref) => ref.nodeId));
|
|
2389
|
+
if (!survivingSet.has(left.nodeId) || !survivingSet.has(right.nodeId)) {
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
if (survivingRefs.length <= 1) continue;
|
|
2393
|
+
const survivingSelected = survivingRefs.filter(
|
|
2394
|
+
(ref) => selectedSet.has(ref.key)
|
|
2395
|
+
);
|
|
2396
|
+
const tagIsCompeting = survivingSelected.length === 0;
|
|
2397
|
+
const primary = pickHighestRatePrimary(survivingRefs);
|
|
2398
|
+
if (!primary) continue;
|
|
2399
|
+
const comparePool = survivingRefs.filter((ref) => ref.nodeId !== primary.nodeId);
|
|
2400
|
+
for (const candidate of comparePool) {
|
|
2401
|
+
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) continue;
|
|
2402
|
+
const issueKey = [
|
|
2403
|
+
"rate-coherence-context",
|
|
2404
|
+
tagId,
|
|
2405
|
+
[...selectedKeys].sort().join("|"),
|
|
2406
|
+
[...survivingRefs.map((r) => r.nodeId).sort()].join("|"),
|
|
2407
|
+
primary.nodeId,
|
|
2408
|
+
candidate.nodeId,
|
|
2409
|
+
ratePolicy.kind,
|
|
2410
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
2411
|
+
].join("::");
|
|
2412
|
+
if (seen.has(issueKey)) continue;
|
|
2413
|
+
seen.add(issueKey);
|
|
2414
|
+
v.errors.push({
|
|
2415
|
+
code: "rate_coherence_violation",
|
|
2416
|
+
severity: "error",
|
|
2417
|
+
nodeId: candidate.nodeId,
|
|
2418
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
2419
|
+
details: {
|
|
2420
|
+
kind: "selected_context",
|
|
2421
|
+
tagId,
|
|
2422
|
+
selectedKeys: [...selectedKeys],
|
|
2423
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2424
|
+
primary: {
|
|
2425
|
+
nodeId: primary.nodeId,
|
|
2426
|
+
fieldId: primary.fieldId,
|
|
2427
|
+
service_id: primary.serviceId,
|
|
2428
|
+
serviceId: primary.serviceId,
|
|
2429
|
+
rate: primary.rate
|
|
2430
|
+
},
|
|
2431
|
+
candidate: {
|
|
2432
|
+
nodeId: candidate.nodeId,
|
|
2433
|
+
fieldId: candidate.fieldId,
|
|
2434
|
+
service_id: candidate.serviceId,
|
|
2435
|
+
serviceId: candidate.serviceId,
|
|
2436
|
+
rate: candidate.rate
|
|
2437
|
+
},
|
|
2438
|
+
policy: ratePolicy.kind,
|
|
2439
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2440
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
2441
|
+
affectedIds: uniqueStrings([
|
|
2442
|
+
tagId,
|
|
2443
|
+
...selectedKeys,
|
|
2444
|
+
primary.nodeId,
|
|
2445
|
+
primary.fieldId,
|
|
2446
|
+
candidate.nodeId,
|
|
2447
|
+
candidate.fieldId,
|
|
2448
|
+
tagIsCompeting ? tagDefault == null ? void 0 : tagDefault.nodeId : void 0
|
|
2449
|
+
]),
|
|
2450
|
+
affectedServiceIds: uniqueStrings([
|
|
2451
|
+
String(primary.serviceId),
|
|
2452
|
+
String(candidate.serviceId)
|
|
2453
|
+
])
|
|
2454
|
+
}
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
if (selectedServiceRefs.length === 0 && tagDefault && baseRefs.length > 0) {
|
|
2460
|
+
const survivingByDefault = baseRefs.filter(
|
|
2461
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
2462
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
2463
|
+
selectedKeys,
|
|
2464
|
+
effectMap
|
|
2465
|
+
)
|
|
2466
|
+
);
|
|
2467
|
+
for (const candidate of survivingByDefault) {
|
|
2468
|
+
if (passesRatePolicy(ratePolicy, tagDefault.rate, candidate.rate)) continue;
|
|
2469
|
+
const issueKey = [
|
|
2470
|
+
"rate-coherence-default",
|
|
2471
|
+
tagId,
|
|
2472
|
+
[...selectedKeys].sort().join("|"),
|
|
2473
|
+
tagDefault.nodeId,
|
|
2474
|
+
candidate.nodeId,
|
|
2475
|
+
ratePolicy.kind,
|
|
2476
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
2477
|
+
].join("::");
|
|
2478
|
+
if (seen.has(issueKey)) continue;
|
|
2479
|
+
seen.add(issueKey);
|
|
2480
|
+
v.errors.push({
|
|
2481
|
+
code: "rate_coherence_violation",
|
|
2482
|
+
severity: "error",
|
|
2483
|
+
nodeId: candidate.nodeId,
|
|
2484
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
2485
|
+
details: {
|
|
2486
|
+
kind: "selected_context",
|
|
2487
|
+
tagId,
|
|
2488
|
+
selectedKeys: [...selectedKeys],
|
|
2489
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2490
|
+
primary: {
|
|
2491
|
+
nodeId: tagDefault.nodeId,
|
|
2492
|
+
service_id: tagDefault.serviceId,
|
|
2493
|
+
serviceId: tagDefault.serviceId,
|
|
2494
|
+
rate: tagDefault.rate
|
|
2495
|
+
},
|
|
2496
|
+
candidate: {
|
|
2497
|
+
nodeId: candidate.nodeId,
|
|
2498
|
+
fieldId: candidate.fieldId,
|
|
2499
|
+
service_id: candidate.serviceId,
|
|
2500
|
+
serviceId: candidate.serviceId,
|
|
2501
|
+
rate: candidate.rate
|
|
2502
|
+
},
|
|
2503
|
+
policy: ratePolicy.kind,
|
|
2504
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2505
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
2506
|
+
affectedIds: uniqueStrings([
|
|
2507
|
+
tagId,
|
|
2508
|
+
...selectedKeys,
|
|
2509
|
+
tagDefault.nodeId,
|
|
2510
|
+
candidate.nodeId,
|
|
2511
|
+
candidate.fieldId
|
|
2512
|
+
]),
|
|
2513
|
+
affectedServiceIds: uniqueStrings([
|
|
2514
|
+
String(tagDefault.serviceId),
|
|
2515
|
+
String(candidate.serviceId)
|
|
2516
|
+
])
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
function validateRateCoherence(v) {
|
|
2523
|
+
if (Object.keys(v.serviceMap).length === 0 || v.tags.length === 0) return;
|
|
2524
|
+
const effectMap = buildTriggerEffectMap(v.props);
|
|
2525
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2526
|
+
for (const context of v.simulatedVisibilityContexts) {
|
|
2527
|
+
validateRateCoherenceForVisibleContext({
|
|
2528
|
+
v,
|
|
2529
|
+
tagId: context.tagId,
|
|
2530
|
+
selectedKeys: context.selectedKeys,
|
|
2531
|
+
visibleFieldIds: context.visibleFieldIds,
|
|
2532
|
+
effectMap,
|
|
2533
|
+
seen
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2191
2538
|
// src/core/validate/steps/constraints.ts
|
|
2192
2539
|
function constraintKeysInChain(v, tagId) {
|
|
2193
2540
|
const keys = [];
|
|
@@ -2944,6 +3291,84 @@ function mergeValidatorOptions(defaults = {}, overrides = {}) {
|
|
|
2944
3291
|
};
|
|
2945
3292
|
}
|
|
2946
3293
|
|
|
3294
|
+
// src/core/validate/index.ts
|
|
3295
|
+
function readVisibilitySimOpts(ctx) {
|
|
3296
|
+
const c = ctx;
|
|
3297
|
+
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
3298
|
+
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
3299
|
+
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
3300
|
+
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
3301
|
+
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
3302
|
+
return {
|
|
3303
|
+
simulate,
|
|
3304
|
+
maxStates,
|
|
3305
|
+
maxDepth,
|
|
3306
|
+
simulateAllRoots,
|
|
3307
|
+
onlyEffectfulTriggers
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3310
|
+
function validate(props, ctx = {}) {
|
|
3311
|
+
var _a, _b, _c;
|
|
3312
|
+
const options = mergeValidatorOptions({}, ctx);
|
|
3313
|
+
const fallbackSettings = resolveFallbackSettings(options);
|
|
3314
|
+
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
3315
|
+
const errors = [];
|
|
3316
|
+
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
3317
|
+
const selectedKeys = new Set(
|
|
3318
|
+
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
3319
|
+
);
|
|
3320
|
+
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
3321
|
+
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
3322
|
+
const tagById = /* @__PURE__ */ new Map();
|
|
3323
|
+
const fieldById = /* @__PURE__ */ new Map();
|
|
3324
|
+
for (const t of tags) tagById.set(t.id, t);
|
|
3325
|
+
for (const f of fields) fieldById.set(f.id, f);
|
|
3326
|
+
const v = {
|
|
3327
|
+
props,
|
|
3328
|
+
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
3329
|
+
options: {
|
|
3330
|
+
...options,
|
|
3331
|
+
ratePolicy,
|
|
3332
|
+
fallbackSettings
|
|
3333
|
+
},
|
|
3334
|
+
errors,
|
|
3335
|
+
serviceMap,
|
|
3336
|
+
selectedKeys,
|
|
3337
|
+
tags,
|
|
3338
|
+
fields,
|
|
3339
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
3340
|
+
tagById,
|
|
3341
|
+
fieldById,
|
|
3342
|
+
fieldsVisibleUnder: (_tagId) => [],
|
|
3343
|
+
simulatedVisibilityContexts: []
|
|
3344
|
+
};
|
|
3345
|
+
validateStructure(v);
|
|
3346
|
+
validateIdentity(v);
|
|
3347
|
+
validateOptionMaps(v);
|
|
3348
|
+
validateOrderKinds(v);
|
|
3349
|
+
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
3350
|
+
const visSim = readVisibilitySimOpts(options);
|
|
3351
|
+
validateVisibility(v, visSim);
|
|
3352
|
+
applyPolicies(
|
|
3353
|
+
v.errors,
|
|
3354
|
+
v.props,
|
|
3355
|
+
v.serviceMap,
|
|
3356
|
+
v.options.policies,
|
|
3357
|
+
v.fieldsVisibleUnder,
|
|
3358
|
+
v.tags
|
|
3359
|
+
);
|
|
3360
|
+
validateServiceVsUserInput(v);
|
|
3361
|
+
validateUtilityMarkers(v);
|
|
3362
|
+
validateRates(v);
|
|
3363
|
+
validateRateCoherence(v);
|
|
3364
|
+
validateConstraints(v);
|
|
3365
|
+
validateCustomFields(v);
|
|
3366
|
+
validateGlobalUtilityGuard(v);
|
|
3367
|
+
validateUnboundFields(v);
|
|
3368
|
+
validateFallbacks(v);
|
|
3369
|
+
return v.errors;
|
|
3370
|
+
}
|
|
3371
|
+
|
|
2947
3372
|
// src/core/builder.ts
|
|
2948
3373
|
import { cloneDeep as cloneDeep2 } from "lodash-es";
|
|
2949
3374
|
function createBuilder(opts = {}) {
|
|
@@ -3226,344 +3651,6 @@ function toStringSet(v) {
|
|
|
3226
3651
|
return new Set(v.map(String));
|
|
3227
3652
|
}
|
|
3228
3653
|
|
|
3229
|
-
// src/core/rate-coherence.ts
|
|
3230
|
-
function validateRateCoherenceDeep(params) {
|
|
3231
|
-
var _a, _b, _c;
|
|
3232
|
-
const { builder, services, tagId } = params;
|
|
3233
|
-
const ratePolicy = normalizeRatePolicy(params.ratePolicy);
|
|
3234
|
-
const props = builder.getProps();
|
|
3235
|
-
const invalidFieldIds = new Set((_a = params.invalidFieldIds) != null ? _a : []);
|
|
3236
|
-
const fields = (_b = props.fields) != null ? _b : [];
|
|
3237
|
-
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
3238
|
-
const tagById = new Map(((_c = props.filters) != null ? _c : []).map((t) => [t.id, t]));
|
|
3239
|
-
const tag = tagById.get(tagId);
|
|
3240
|
-
const baselineFieldIds = builder.visibleFields(tagId, []);
|
|
3241
|
-
const baselineFields = baselineFieldIds.map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
3242
|
-
const anchors = collectAnchors(baselineFields);
|
|
3243
|
-
const diagnostics = [];
|
|
3244
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3245
|
-
for (const anchor of anchors) {
|
|
3246
|
-
const selectedKeys = anchor.kind === "option" ? [`${anchor.fieldId}::${anchor.id}`] : [anchor.fieldId];
|
|
3247
|
-
const visibleFields = builder.visibleFields(tagId, selectedKeys).map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
3248
|
-
const visibleInvalidFieldIds = visibleFields.map((field) => field.id).filter((fieldId) => invalidFieldIds.has(fieldId));
|
|
3249
|
-
for (const fieldId of visibleInvalidFieldIds) {
|
|
3250
|
-
const key = `internal|${tagId}|${fieldId}`;
|
|
3251
|
-
if (seen.has(key)) continue;
|
|
3252
|
-
seen.add(key);
|
|
3253
|
-
diagnostics.push({
|
|
3254
|
-
kind: "internal_field",
|
|
3255
|
-
scope: "visible_group",
|
|
3256
|
-
tagId,
|
|
3257
|
-
fieldId,
|
|
3258
|
-
nodeId: fieldId,
|
|
3259
|
-
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
3260
|
-
simulationAnchor: {
|
|
3261
|
-
kind: anchor.kind,
|
|
3262
|
-
id: anchor.id,
|
|
3263
|
-
fieldId: anchor.fieldId,
|
|
3264
|
-
label: anchor.label
|
|
3265
|
-
},
|
|
3266
|
-
invalidFieldIds: [fieldId]
|
|
3267
|
-
});
|
|
3268
|
-
}
|
|
3269
|
-
const references = visibleFields.flatMap(
|
|
3270
|
-
(field) => collectFieldReferences(field, services)
|
|
3271
|
-
);
|
|
3272
|
-
if (references.length <= 1) continue;
|
|
3273
|
-
const primary = references.reduce((best, current) => {
|
|
3274
|
-
if (current.rate !== best.rate) {
|
|
3275
|
-
return current.rate > best.rate ? current : best;
|
|
3276
|
-
}
|
|
3277
|
-
const bestKey = `${best.fieldId}|${best.nodeId}`;
|
|
3278
|
-
const currentKey = `${current.fieldId}|${current.nodeId}`;
|
|
3279
|
-
return currentKey < bestKey ? current : best;
|
|
3280
|
-
});
|
|
3281
|
-
for (const candidate of references) {
|
|
3282
|
-
if (candidate.nodeId === primary.nodeId) continue;
|
|
3283
|
-
if (candidate.fieldId === primary.fieldId) continue;
|
|
3284
|
-
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) {
|
|
3285
|
-
continue;
|
|
3286
|
-
}
|
|
3287
|
-
const key = contextualKey(tagId, primary, candidate, ratePolicy);
|
|
3288
|
-
if (seen.has(key)) continue;
|
|
3289
|
-
seen.add(key);
|
|
3290
|
-
diagnostics.push({
|
|
3291
|
-
kind: "contextual",
|
|
3292
|
-
scope: "visible_group",
|
|
3293
|
-
tagId,
|
|
3294
|
-
nodeId: candidate.nodeId,
|
|
3295
|
-
primary: toDiagnosticRef(primary),
|
|
3296
|
-
offender: toDiagnosticRef(candidate),
|
|
3297
|
-
policy: ratePolicy.kind,
|
|
3298
|
-
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
3299
|
-
message: explainRateMismatch(
|
|
3300
|
-
ratePolicy,
|
|
3301
|
-
primary,
|
|
3302
|
-
candidate,
|
|
3303
|
-
describeLabel(tag)
|
|
3304
|
-
),
|
|
3305
|
-
simulationAnchor: {
|
|
3306
|
-
kind: anchor.kind,
|
|
3307
|
-
id: anchor.id,
|
|
3308
|
-
fieldId: anchor.fieldId,
|
|
3309
|
-
label: anchor.label
|
|
3310
|
-
},
|
|
3311
|
-
invalidFieldIds: visibleInvalidFieldIds
|
|
3312
|
-
});
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
return diagnostics;
|
|
3316
|
-
}
|
|
3317
|
-
function collectAnchors(fields) {
|
|
3318
|
-
var _a, _b;
|
|
3319
|
-
const anchors = [];
|
|
3320
|
-
for (const field of fields) {
|
|
3321
|
-
if (!isButton(field)) continue;
|
|
3322
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
3323
|
-
for (const option of field.options) {
|
|
3324
|
-
anchors.push({
|
|
3325
|
-
kind: "option",
|
|
3326
|
-
id: option.id,
|
|
3327
|
-
fieldId: field.id,
|
|
3328
|
-
label: (_a = option.label) != null ? _a : option.id
|
|
3329
|
-
});
|
|
3330
|
-
}
|
|
3331
|
-
continue;
|
|
3332
|
-
}
|
|
3333
|
-
anchors.push({
|
|
3334
|
-
kind: "field",
|
|
3335
|
-
id: field.id,
|
|
3336
|
-
fieldId: field.id,
|
|
3337
|
-
label: (_b = field.label) != null ? _b : field.id
|
|
3338
|
-
});
|
|
3339
|
-
}
|
|
3340
|
-
return anchors;
|
|
3341
|
-
}
|
|
3342
|
-
function collectFieldReferences(field, services) {
|
|
3343
|
-
var _a;
|
|
3344
|
-
const members = collectBaseMembers(field, services);
|
|
3345
|
-
if (members.length === 0) return [];
|
|
3346
|
-
if (isMultiField(field)) {
|
|
3347
|
-
const averageRate = members.reduce((sum, member) => sum + member.rate, 0) / members.length;
|
|
3348
|
-
return [
|
|
3349
|
-
{
|
|
3350
|
-
refKind: "multi",
|
|
3351
|
-
nodeId: field.id,
|
|
3352
|
-
fieldId: field.id,
|
|
3353
|
-
label: (_a = field.label) != null ? _a : field.id,
|
|
3354
|
-
rate: averageRate,
|
|
3355
|
-
members
|
|
3356
|
-
}
|
|
3357
|
-
];
|
|
3358
|
-
}
|
|
3359
|
-
return members.map((member) => ({
|
|
3360
|
-
refKind: "single",
|
|
3361
|
-
nodeId: member.id,
|
|
3362
|
-
fieldId: field.id,
|
|
3363
|
-
label: member.label,
|
|
3364
|
-
rate: member.rate,
|
|
3365
|
-
service_id: member.service_id,
|
|
3366
|
-
members: [member]
|
|
3367
|
-
}));
|
|
3368
|
-
}
|
|
3369
|
-
function collectBaseMembers(field, services) {
|
|
3370
|
-
var _a, _b, _c;
|
|
3371
|
-
const members = [];
|
|
3372
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
3373
|
-
for (const option of field.options) {
|
|
3374
|
-
const role2 = normalizeRole((_a = option.pricing_role) != null ? _a : field.pricing_role, "base");
|
|
3375
|
-
if (role2 !== "base") continue;
|
|
3376
|
-
if (option.service_id === void 0 || option.service_id === null) {
|
|
3377
|
-
continue;
|
|
3378
|
-
}
|
|
3379
|
-
const cap2 = getServiceCapability(services, option.service_id);
|
|
3380
|
-
if (!cap2 || typeof cap2.rate !== "number" || !Number.isFinite(cap2.rate)) {
|
|
3381
|
-
continue;
|
|
3382
|
-
}
|
|
3383
|
-
members.push({
|
|
3384
|
-
kind: "option",
|
|
3385
|
-
id: option.id,
|
|
3386
|
-
fieldId: field.id,
|
|
3387
|
-
label: (_b = option.label) != null ? _b : option.id,
|
|
3388
|
-
service_id: option.service_id,
|
|
3389
|
-
rate: cap2.rate
|
|
3390
|
-
});
|
|
3391
|
-
}
|
|
3392
|
-
return members;
|
|
3393
|
-
}
|
|
3394
|
-
const role = normalizeRole(field.pricing_role, "base");
|
|
3395
|
-
if (role !== "base") return members;
|
|
3396
|
-
if (field.service_id === void 0 || field.service_id === null) return members;
|
|
3397
|
-
const cap = getServiceCapability(services, field.service_id);
|
|
3398
|
-
if (!cap || typeof cap.rate !== "number" || !Number.isFinite(cap.rate)) {
|
|
3399
|
-
return members;
|
|
3400
|
-
}
|
|
3401
|
-
members.push({
|
|
3402
|
-
kind: "field",
|
|
3403
|
-
id: field.id,
|
|
3404
|
-
fieldId: field.id,
|
|
3405
|
-
label: (_c = field.label) != null ? _c : field.id,
|
|
3406
|
-
service_id: field.service_id,
|
|
3407
|
-
rate: cap.rate
|
|
3408
|
-
});
|
|
3409
|
-
return members;
|
|
3410
|
-
}
|
|
3411
|
-
function isButton(field) {
|
|
3412
|
-
if (field.button === true) return true;
|
|
3413
|
-
return Array.isArray(field.options) && field.options.length > 0;
|
|
3414
|
-
}
|
|
3415
|
-
function normalizeRole(role, fallback) {
|
|
3416
|
-
return role === "base" || role === "utility" ? role : fallback;
|
|
3417
|
-
}
|
|
3418
|
-
function toDiagnosticRef(reference) {
|
|
3419
|
-
return {
|
|
3420
|
-
nodeId: reference.nodeId,
|
|
3421
|
-
fieldId: reference.fieldId,
|
|
3422
|
-
label: reference.label,
|
|
3423
|
-
refKind: reference.refKind,
|
|
3424
|
-
service_id: reference.service_id,
|
|
3425
|
-
rate: reference.rate
|
|
3426
|
-
};
|
|
3427
|
-
}
|
|
3428
|
-
function contextualKey(tagId, primary, candidate, ratePolicy) {
|
|
3429
|
-
const pctKey = "pct" in ratePolicy ? `:${ratePolicy.pct}` : "";
|
|
3430
|
-
return [
|
|
3431
|
-
"contextual",
|
|
3432
|
-
tagId,
|
|
3433
|
-
primary.fieldId,
|
|
3434
|
-
primary.nodeId,
|
|
3435
|
-
candidate.fieldId,
|
|
3436
|
-
candidate.nodeId,
|
|
3437
|
-
`${ratePolicy.kind}${pctKey}`
|
|
3438
|
-
].join("|");
|
|
3439
|
-
}
|
|
3440
|
-
function describeLabel(tag) {
|
|
3441
|
-
var _a, _b;
|
|
3442
|
-
return (_b = (_a = tag == null ? void 0 : tag.label) != null ? _a : tag == null ? void 0 : tag.id) != null ? _b : "tag";
|
|
3443
|
-
}
|
|
3444
|
-
function explainRateMismatch(policy, primary, candidate, where) {
|
|
3445
|
-
var _a, _b;
|
|
3446
|
-
const primaryLabel = `${(_a = primary.label) != null ? _a : primary.nodeId} (${primary.rate})`;
|
|
3447
|
-
const candidateLabel = `${(_b = candidate.label) != null ? _b : candidate.nodeId} (${candidate.rate})`;
|
|
3448
|
-
switch (policy.kind) {
|
|
3449
|
-
case "eq_primary":
|
|
3450
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must exactly match ${primaryLabel}.`;
|
|
3451
|
-
case "lte_primary":
|
|
3452
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must stay within ${policy.pct}% below and never above ${primaryLabel}.`;
|
|
3453
|
-
case "within_pct":
|
|
3454
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be within ${policy.pct}% of ${primaryLabel}.`;
|
|
3455
|
-
case "at_least_pct_lower":
|
|
3456
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be at least ${policy.pct}% lower than ${primaryLabel}.`;
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3459
|
-
|
|
3460
|
-
// src/core/validate/index.ts
|
|
3461
|
-
function readVisibilitySimOpts(ctx) {
|
|
3462
|
-
const c = ctx;
|
|
3463
|
-
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
3464
|
-
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
3465
|
-
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
3466
|
-
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
3467
|
-
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
3468
|
-
return {
|
|
3469
|
-
simulate,
|
|
3470
|
-
maxStates,
|
|
3471
|
-
maxDepth,
|
|
3472
|
-
simulateAllRoots,
|
|
3473
|
-
onlyEffectfulTriggers
|
|
3474
|
-
};
|
|
3475
|
-
}
|
|
3476
|
-
function validate(props, ctx = {}) {
|
|
3477
|
-
var _a, _b, _c;
|
|
3478
|
-
const options = mergeValidatorOptions({}, ctx);
|
|
3479
|
-
const fallbackSettings = resolveFallbackSettings(options);
|
|
3480
|
-
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
3481
|
-
const errors = [];
|
|
3482
|
-
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
3483
|
-
const selectedKeys = new Set(
|
|
3484
|
-
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
3485
|
-
);
|
|
3486
|
-
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
3487
|
-
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
3488
|
-
const tagById = /* @__PURE__ */ new Map();
|
|
3489
|
-
const fieldById = /* @__PURE__ */ new Map();
|
|
3490
|
-
for (const t of tags) tagById.set(t.id, t);
|
|
3491
|
-
for (const f of fields) fieldById.set(f.id, f);
|
|
3492
|
-
const v = {
|
|
3493
|
-
props,
|
|
3494
|
-
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
3495
|
-
options: {
|
|
3496
|
-
...options,
|
|
3497
|
-
ratePolicy,
|
|
3498
|
-
fallbackSettings
|
|
3499
|
-
},
|
|
3500
|
-
errors,
|
|
3501
|
-
serviceMap,
|
|
3502
|
-
selectedKeys,
|
|
3503
|
-
tags,
|
|
3504
|
-
fields,
|
|
3505
|
-
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
3506
|
-
tagById,
|
|
3507
|
-
fieldById,
|
|
3508
|
-
fieldsVisibleUnder: (_tagId) => []
|
|
3509
|
-
};
|
|
3510
|
-
validateStructure(v);
|
|
3511
|
-
validateIdentity(v);
|
|
3512
|
-
validateOptionMaps(v);
|
|
3513
|
-
validateOrderKinds(v);
|
|
3514
|
-
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
3515
|
-
const visSim = readVisibilitySimOpts(options);
|
|
3516
|
-
validateVisibility(v, visSim);
|
|
3517
|
-
applyPolicies(
|
|
3518
|
-
v.errors,
|
|
3519
|
-
v.props,
|
|
3520
|
-
v.serviceMap,
|
|
3521
|
-
v.options.policies,
|
|
3522
|
-
v.fieldsVisibleUnder,
|
|
3523
|
-
v.tags
|
|
3524
|
-
);
|
|
3525
|
-
validateServiceVsUserInput(v);
|
|
3526
|
-
validateUtilityMarkers(v);
|
|
3527
|
-
validateRates(v);
|
|
3528
|
-
if (Object.keys(serviceMap).length > 0 && tags.length > 0) {
|
|
3529
|
-
const builder = createBuilder({ serviceMap });
|
|
3530
|
-
builder.load(props);
|
|
3531
|
-
for (const tag of tags) {
|
|
3532
|
-
const diags = validateRateCoherenceDeep({
|
|
3533
|
-
builder,
|
|
3534
|
-
services: serviceMap,
|
|
3535
|
-
tagId: tag.id,
|
|
3536
|
-
ratePolicy,
|
|
3537
|
-
invalidFieldIds: v.invalidRateFieldIds
|
|
3538
|
-
});
|
|
3539
|
-
for (const diag of diags) {
|
|
3540
|
-
if (diag.kind !== "contextual") continue;
|
|
3541
|
-
errors.push({
|
|
3542
|
-
code: "rate_coherence_violation",
|
|
3543
|
-
severity: "error",
|
|
3544
|
-
message: diag.message,
|
|
3545
|
-
nodeId: diag.nodeId,
|
|
3546
|
-
details: {
|
|
3547
|
-
tagId: diag.tagId,
|
|
3548
|
-
simulationAnchor: diag.simulationAnchor,
|
|
3549
|
-
primary: diag.primary,
|
|
3550
|
-
offender: diag.offender,
|
|
3551
|
-
policy: diag.policy,
|
|
3552
|
-
policyPct: diag.policyPct,
|
|
3553
|
-
invalidFieldIds: diag.invalidFieldIds
|
|
3554
|
-
}
|
|
3555
|
-
});
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
validateConstraints(v);
|
|
3560
|
-
validateCustomFields(v);
|
|
3561
|
-
validateGlobalUtilityGuard(v);
|
|
3562
|
-
validateUnboundFields(v);
|
|
3563
|
-
validateFallbacks(v);
|
|
3564
|
-
return v.errors;
|
|
3565
|
-
}
|
|
3566
|
-
|
|
3567
3654
|
// src/core/fallback.ts
|
|
3568
3655
|
var DEFAULT_SETTINGS = {
|
|
3569
3656
|
requireConstraintFit: true,
|
|
@@ -4142,9 +4229,9 @@ function createNodeIndex(builder) {
|
|
|
4142
4229
|
if (cached) return cached;
|
|
4143
4230
|
const raw = fieldById.get(id);
|
|
4144
4231
|
if (!raw) return void 0;
|
|
4145
|
-
const
|
|
4146
|
-
const includes =
|
|
4147
|
-
const excludes =
|
|
4232
|
+
const isButton = raw.button === true;
|
|
4233
|
+
const includes = isButton ? Object.freeze(new Set((_b2 = (_a2 = props.includes_for_buttons) == null ? void 0 : _a2[id]) != null ? _b2 : [])) : emptySet;
|
|
4234
|
+
const excludes = isButton ? Object.freeze(new Set((_d2 = (_c2 = props.excludes_for_buttons) == null ? void 0 : _c2[id]) != null ? _d2 : [])) : emptySet;
|
|
4148
4235
|
const bindIds = () => {
|
|
4149
4236
|
const cachedBind = fieldBindIdsCache.get(id);
|
|
4150
4237
|
if (cachedBind) return cachedBind;
|
|
@@ -4181,7 +4268,7 @@ function createNodeIndex(builder) {
|
|
|
4181
4268
|
);
|
|
4182
4269
|
},
|
|
4183
4270
|
getDescendants(tagId) {
|
|
4184
|
-
return resolveDescendants(id, includes, tagId, !
|
|
4271
|
+
return resolveDescendants(id, includes, tagId, !isButton);
|
|
4185
4272
|
}
|
|
4186
4273
|
};
|
|
4187
4274
|
fieldNodeCache.set(id, node);
|
|
@@ -4518,23 +4605,20 @@ function compilePolicies(raw) {
|
|
|
4518
4605
|
|
|
4519
4606
|
// src/core/service-filter.ts
|
|
4520
4607
|
function filterServicesForVisibleGroup(input, deps) {
|
|
4521
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
4608
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
4522
4609
|
const svcMap = (_c = (_b = (_a = deps.builder).getServiceMap) == null ? void 0 : _b.call(_a)) != null ? _c : {};
|
|
4523
4610
|
const builderOptions = (_e = (_d = deps.builder).getOptions) == null ? void 0 : _e.call(_d);
|
|
4524
4611
|
const { context } = input;
|
|
4525
4612
|
const usedSet = new Set(context.usedServiceIds.map(String));
|
|
4526
|
-
const primary = context.usedServiceIds[0];
|
|
4527
4613
|
const explicitFallbackSettings = (_f = context.fallbackSettings) != null ? _f : context.fallback;
|
|
4528
4614
|
const resolvedRatePolicy = normalizeRatePolicy(
|
|
4529
4615
|
(_h = (_g = context.ratePolicy) != null ? _g : explicitFallbackSettings == null ? void 0 : explicitFallbackSettings.ratePolicy) != null ? _h : builderOptions == null ? void 0 : builderOptions.ratePolicy
|
|
4530
4616
|
);
|
|
4531
|
-
const fallbackSettingsSource = explicitFallbackSettings != null ? explicitFallbackSettings : builderOptions == null ? void 0 : builderOptions.fallbackSettings;
|
|
4532
|
-
const fb = {
|
|
4533
|
-
...DEFAULT_FALLBACK_SETTINGS,
|
|
4534
|
-
...fallbackSettingsSource != null ? fallbackSettingsSource : {},
|
|
4535
|
-
ratePolicy: resolvedRatePolicy
|
|
4536
|
-
};
|
|
4537
4617
|
const policySource = (_j = (_i = context.policies) != null ? _i : builderOptions == null ? void 0 : builderOptions.policies) != null ? _j : [];
|
|
4618
|
+
const resolvedCustomPrimaryRate = resolveCustomPrimaryRate(
|
|
4619
|
+
context.rateContext,
|
|
4620
|
+
svcMap
|
|
4621
|
+
);
|
|
4538
4622
|
const visibleServiceIds = context.selectedButtons === void 0 ? void 0 : collectVisibleServiceIds(
|
|
4539
4623
|
deps.builder,
|
|
4540
4624
|
context.tagId,
|
|
@@ -4561,7 +4645,19 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
4561
4645
|
cap.id,
|
|
4562
4646
|
(_k = context.effectiveConstraints) != null ? _k : {}
|
|
4563
4647
|
);
|
|
4564
|
-
const passesRate2 =
|
|
4648
|
+
const passesRate2 = resolvedCustomPrimaryRate != null ? passesRatePolicy(
|
|
4649
|
+
resolvedRatePolicy,
|
|
4650
|
+
resolvedCustomPrimaryRate,
|
|
4651
|
+
toFiniteNumber(cap.rate)
|
|
4652
|
+
) : candidatePassesRateCoherence(
|
|
4653
|
+
deps.builder,
|
|
4654
|
+
svcMap,
|
|
4655
|
+
context.tagId,
|
|
4656
|
+
(_l = context.selectedButtons) != null ? _l : [],
|
|
4657
|
+
context.usedServiceIds,
|
|
4658
|
+
id,
|
|
4659
|
+
resolvedRatePolicy
|
|
4660
|
+
);
|
|
4565
4661
|
const polRes = evaluatePoliciesRaw(
|
|
4566
4662
|
policySource,
|
|
4567
4663
|
[...context.usedServiceIds, id],
|
|
@@ -4593,6 +4689,17 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
4593
4689
|
diagnostics: lastDiagnostics && lastDiagnostics.length ? lastDiagnostics : void 0
|
|
4594
4690
|
};
|
|
4595
4691
|
}
|
|
4692
|
+
function resolveCustomPrimaryRate(rateContext, serviceMap) {
|
|
4693
|
+
if (!rateContext || rateContext.mode !== "custom_primary_rate") {
|
|
4694
|
+
return void 0;
|
|
4695
|
+
}
|
|
4696
|
+
if (rateContext.source === "manual") {
|
|
4697
|
+
return toFiniteNumber(rateContext.primaryRate);
|
|
4698
|
+
}
|
|
4699
|
+
if (rateContext.primaryServiceId == null) return void 0;
|
|
4700
|
+
const cap = getServiceCapability(serviceMap, rateContext.primaryServiceId);
|
|
4701
|
+
return toFiniteNumber(cap == null ? void 0 : cap.rate);
|
|
4702
|
+
}
|
|
4596
4703
|
function evaluatePoliciesRaw(raw, serviceIds, svcMap, tagId, visibleServiceIds) {
|
|
4597
4704
|
const compiled = compilePolicies(raw);
|
|
4598
4705
|
const evaluated = evaluateServicePolicies(
|
|
@@ -4680,7 +4787,9 @@ function collectVisibleServiceIds(builder, tagId, selectedButtons) {
|
|
|
4680
4787
|
const fields = (_b = props.fields) != null ? _b : [];
|
|
4681
4788
|
const tag = tags.find((t) => t.id === tagId);
|
|
4682
4789
|
if ((tag == null ? void 0 : tag.service_id) != null) out.add(String(tag.service_id));
|
|
4683
|
-
const visibleFieldIds = new Set(
|
|
4790
|
+
const visibleFieldIds = new Set(
|
|
4791
|
+
builder.visibleFields(tagId, selectedButtons)
|
|
4792
|
+
);
|
|
4684
4793
|
for (const field of fields) {
|
|
4685
4794
|
if (!visibleFieldIds.has(field.id)) continue;
|
|
4686
4795
|
if (field.service_id != null) {
|
|
@@ -4703,8 +4812,7 @@ function matchesRuleFilter(cap, rule, tagId) {
|
|
|
4703
4812
|
if (!cap) return false;
|
|
4704
4813
|
const f = rule.filter;
|
|
4705
4814
|
if (!f) return true;
|
|
4706
|
-
|
|
4707
|
-
return true;
|
|
4815
|
+
return !(f.tag_id && !toStrSet(f.tag_id).has(String(tagId)));
|
|
4708
4816
|
}
|
|
4709
4817
|
function toStrSet(v) {
|
|
4710
4818
|
const arr = Array.isArray(v) ? v : [v];
|
|
@@ -4712,6 +4820,107 @@ function toStrSet(v) {
|
|
|
4712
4820
|
for (const x of arr) s.add(String(x));
|
|
4713
4821
|
return s;
|
|
4714
4822
|
}
|
|
4823
|
+
function candidatePassesRateCoherence(builder, serviceMap, tagId, selectedKeys, usedServiceIds, candidateId, ratePolicy) {
|
|
4824
|
+
var _a, _b, _c, _d;
|
|
4825
|
+
if (usedServiceIds.length === 0) return true;
|
|
4826
|
+
const props = builder.getProps();
|
|
4827
|
+
const baseFields = (_a = props.fields) != null ? _a : [];
|
|
4828
|
+
const candidateFieldId = syntheticServiceFieldId("candidate", candidateId, 0);
|
|
4829
|
+
const syntheticFields = [
|
|
4830
|
+
...usedServiceIds.map((serviceId, index) => ({
|
|
4831
|
+
id: syntheticServiceFieldId("used", serviceId, index),
|
|
4832
|
+
label: `Used service ${String(serviceId)}`,
|
|
4833
|
+
type: "custom",
|
|
4834
|
+
button: true,
|
|
4835
|
+
service_id: serviceId,
|
|
4836
|
+
pricing_role: "base"
|
|
4837
|
+
})),
|
|
4838
|
+
{
|
|
4839
|
+
id: candidateFieldId,
|
|
4840
|
+
label: `Candidate ${String(candidateId)}`,
|
|
4841
|
+
type: "custom",
|
|
4842
|
+
button: true,
|
|
4843
|
+
service_id: candidateId,
|
|
4844
|
+
pricing_role: "base"
|
|
4845
|
+
}
|
|
4846
|
+
];
|
|
4847
|
+
const fields = [...baseFields, ...syntheticFields];
|
|
4848
|
+
const visibleFieldIds = [
|
|
4849
|
+
...builder.visibleFields(tagId, selectedKeys),
|
|
4850
|
+
...syntheticFields.map((field) => field.id)
|
|
4851
|
+
];
|
|
4852
|
+
const anchoredFilters = ((_b = props.filters) != null ? _b : []).map(
|
|
4853
|
+
(tag) => tag.id === tagId && usedServiceIds[0] != null ? { ...tag, service_id: usedServiceIds[0] } : tag
|
|
4854
|
+
);
|
|
4855
|
+
const validationProps = {
|
|
4856
|
+
...props,
|
|
4857
|
+
filters: anchoredFilters,
|
|
4858
|
+
fields
|
|
4859
|
+
};
|
|
4860
|
+
const errors = [];
|
|
4861
|
+
const tags = (_c = validationProps.filters) != null ? _c : [];
|
|
4862
|
+
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
|
4863
|
+
const tagById = new Map(tags.map((tag) => [tag.id, tag]));
|
|
4864
|
+
const v = {
|
|
4865
|
+
props: validationProps,
|
|
4866
|
+
nodeMap: buildNodeMap(validationProps),
|
|
4867
|
+
options: {
|
|
4868
|
+
...(_d = builder.getOptions) == null ? void 0 : _d.call(builder),
|
|
4869
|
+
serviceMap,
|
|
4870
|
+
ratePolicy
|
|
4871
|
+
},
|
|
4872
|
+
errors,
|
|
4873
|
+
serviceMap,
|
|
4874
|
+
selectedKeys: new Set(selectedKeys),
|
|
4875
|
+
tags,
|
|
4876
|
+
fields,
|
|
4877
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
4878
|
+
tagById,
|
|
4879
|
+
fieldById,
|
|
4880
|
+
fieldsVisibleUnder: () => [],
|
|
4881
|
+
simulatedVisibilityContexts: []
|
|
4882
|
+
};
|
|
4883
|
+
validateRateCoherenceForVisibleContext({
|
|
4884
|
+
v,
|
|
4885
|
+
tagId,
|
|
4886
|
+
selectedKeys,
|
|
4887
|
+
visibleFieldIds,
|
|
4888
|
+
effectMap: buildTriggerEffectMap(validationProps),
|
|
4889
|
+
seen: /* @__PURE__ */ new Set()
|
|
4890
|
+
});
|
|
4891
|
+
return !errors.some(
|
|
4892
|
+
(error) => rateIssueAffectsCandidate(
|
|
4893
|
+
error,
|
|
4894
|
+
candidateId,
|
|
4895
|
+
candidateFieldId,
|
|
4896
|
+
usedServiceIds[0]
|
|
4897
|
+
)
|
|
4898
|
+
);
|
|
4899
|
+
}
|
|
4900
|
+
function syntheticServiceFieldId(kind, serviceId, index) {
|
|
4901
|
+
return `__service_filter_${kind}__:${index}:${String(serviceId)}`;
|
|
4902
|
+
}
|
|
4903
|
+
function rateIssueAffectsCandidate(error, candidateId, candidateFieldId, primaryAnchorId) {
|
|
4904
|
+
var _a, _b, _c, _d;
|
|
4905
|
+
if (error.code !== "rate_coherence_violation") return false;
|
|
4906
|
+
const candidateKey = String(candidateId);
|
|
4907
|
+
const details = (_a = error.details) != null ? _a : {};
|
|
4908
|
+
const anchorKey = primaryAnchorId == null ? void 0 : String(primaryAnchorId);
|
|
4909
|
+
const primaryMatchesAnchor = anchorKey == null || String((_b = details.primary) == null ? void 0 : _b.serviceId) === anchorKey || String((_c = details.primary) == null ? void 0 : _c.service_id) === anchorKey;
|
|
4910
|
+
if (primaryMatchesAnchor && ((_d = details.affectedServiceIds) == null ? void 0 : _d.some(
|
|
4911
|
+
(serviceId) => String(serviceId) === candidateKey
|
|
4912
|
+
))) {
|
|
4913
|
+
return true;
|
|
4914
|
+
}
|
|
4915
|
+
if (primaryMatchesAnchor && String(error.nodeId) === candidateFieldId) {
|
|
4916
|
+
return true;
|
|
4917
|
+
}
|
|
4918
|
+
return [details.primary, details.candidate].some((ref) => {
|
|
4919
|
+
if (!ref) return false;
|
|
4920
|
+
if (!primaryMatchesAnchor) return false;
|
|
4921
|
+
return String(ref.serviceId) === candidateKey || String(ref.service_id) === candidateKey || String(ref.fieldId) === candidateFieldId || String(ref.nodeId) === candidateFieldId;
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4715
4924
|
|
|
4716
4925
|
// src/utils/prune-fallbacks.ts
|
|
4717
4926
|
function pruneInvalidNodeFallbacks(props, services, settings) {
|
|
@@ -6442,7 +6651,7 @@ function setService(ctx, id, input) {
|
|
|
6442
6651
|
);
|
|
6443
6652
|
}
|
|
6444
6653
|
const isOptionBased2 = Array.isArray(f.options) && f.options.length > 0;
|
|
6445
|
-
const
|
|
6654
|
+
const isButton = !!f.button;
|
|
6446
6655
|
if (nextRole) {
|
|
6447
6656
|
f.pricing_role = nextRole;
|
|
6448
6657
|
}
|
|
@@ -6458,7 +6667,7 @@ function setService(ctx, id, input) {
|
|
|
6458
6667
|
if ("service_id" in f) delete f.service_id;
|
|
6459
6668
|
return;
|
|
6460
6669
|
}
|
|
6461
|
-
if (!
|
|
6670
|
+
if (!isButton) {
|
|
6462
6671
|
if (hasSidKey) {
|
|
6463
6672
|
ctx.api.emit("error", {
|
|
6464
6673
|
message: "Only button fields (without options) can have a service_id.",
|
|
@@ -7482,7 +7691,8 @@ function filterServicesForVisibleGroup2(ctx, candidates, input) {
|
|
|
7482
7691
|
policies: input.policies,
|
|
7483
7692
|
ratePolicy: input.ratePolicy,
|
|
7484
7693
|
fallbackSettings: input.fallbackSettings,
|
|
7485
|
-
fallback: input.fallback
|
|
7694
|
+
fallback: input.fallback,
|
|
7695
|
+
rateContext: input.rateContext
|
|
7486
7696
|
}
|
|
7487
7697
|
};
|
|
7488
7698
|
const result = filterServicesForVisibleGroup(coreInput, {
|