@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.cjs
CHANGED
|
@@ -1324,13 +1324,7 @@ function resolveRootTags(tags) {
|
|
|
1324
1324
|
const roots = tags.filter((t) => !t.bind_id);
|
|
1325
1325
|
return roots.length ? roots : tags.slice(0, 1);
|
|
1326
1326
|
}
|
|
1327
|
-
function
|
|
1328
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1329
|
-
const inc = (_a = v.props.includes_for_buttons) != null ? _a : {};
|
|
1330
|
-
const exc = (_b = v.props.excludes_for_buttons) != null ? _b : {};
|
|
1331
|
-
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;
|
|
1332
|
-
}
|
|
1333
|
-
function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectful) {
|
|
1327
|
+
function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
|
|
1334
1328
|
var _a;
|
|
1335
1329
|
const visible = visibleFieldsUnder(v.props, tagId, {
|
|
1336
1330
|
selectedKeys
|
|
@@ -1339,11 +1333,11 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectfu
|
|
|
1339
1333
|
for (const f of visible) {
|
|
1340
1334
|
if (f.button === true) {
|
|
1341
1335
|
const t = f.id;
|
|
1342
|
-
if (
|
|
1336
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
1343
1337
|
}
|
|
1344
1338
|
for (const o of (_a = f.options) != null ? _a : []) {
|
|
1345
|
-
const t =
|
|
1346
|
-
if (
|
|
1339
|
+
const t = o.id;
|
|
1340
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
1347
1341
|
}
|
|
1348
1342
|
}
|
|
1349
1343
|
triggers.sort();
|
|
@@ -1443,20 +1437,38 @@ function dedupeErrorsInPlace(v, startIndex) {
|
|
|
1443
1437
|
v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
|
|
1444
1438
|
}
|
|
1445
1439
|
function validateVisibility(v, options = {}) {
|
|
1446
|
-
var _a, _b, _c;
|
|
1440
|
+
var _a, _b, _c, _d, _e;
|
|
1441
|
+
v.simulatedVisibilityContexts = [];
|
|
1447
1442
|
const simulate = options.simulate === true;
|
|
1448
1443
|
if (!simulate) {
|
|
1449
1444
|
runVisibilityRulesOnce(v);
|
|
1445
|
+
for (const tag of v.tags) {
|
|
1446
|
+
v.simulatedVisibilityContexts.push({
|
|
1447
|
+
tagId: tag.id,
|
|
1448
|
+
selectedKeys: Array.from(v.selectedKeys),
|
|
1449
|
+
visibleFieldIds: v.fieldsVisibleUnder(tag.id).map((f) => f.id)
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1450
1452
|
return;
|
|
1451
1453
|
}
|
|
1452
1454
|
const maxStates = Math.max(1, (_a = options.maxStates) != null ? _a : 500);
|
|
1453
1455
|
const maxDepth = Math.max(0, (_b = options.maxDepth) != null ? _b : 6);
|
|
1454
1456
|
const onlyEffectful = options.onlyEffectfulTriggers !== false;
|
|
1457
|
+
const effectfulKeys = /* @__PURE__ */ new Set();
|
|
1458
|
+
if (onlyEffectful) {
|
|
1459
|
+
for (const key of Object.keys((_c = v.props.includes_for_buttons) != null ? _c : {})) {
|
|
1460
|
+
effectfulKeys.add(key);
|
|
1461
|
+
}
|
|
1462
|
+
for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
|
|
1463
|
+
effectfulKeys.add(key);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1455
1466
|
const roots = resolveRootTags(v.tags);
|
|
1456
1467
|
const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
|
|
1457
|
-
const originalSelected = new Set((
|
|
1468
|
+
const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
|
|
1458
1469
|
const errorsStart = v.errors.length;
|
|
1459
1470
|
const visited = /* @__PURE__ */ new Set();
|
|
1471
|
+
const seenContexts = /* @__PURE__ */ new Set();
|
|
1460
1472
|
const stack = [];
|
|
1461
1473
|
for (const rt of rootTags) {
|
|
1462
1474
|
stack.push({
|
|
@@ -1469,10 +1481,27 @@ function validateVisibility(v, options = {}) {
|
|
|
1469
1481
|
while (stack.length) {
|
|
1470
1482
|
if (validatedStates >= maxStates) break;
|
|
1471
1483
|
const state = stack.pop();
|
|
1472
|
-
const sig = stableKeyOfSelection(state.selected)
|
|
1484
|
+
const sig = `${state.rootTagId}::${stableKeyOfSelection(state.selected)}`;
|
|
1473
1485
|
if (visited.has(sig)) continue;
|
|
1474
1486
|
visited.add(sig);
|
|
1475
1487
|
v.selectedKeys = state.selected;
|
|
1488
|
+
const visibleNow = visibleFieldsUnder(v.props, state.rootTagId, {
|
|
1489
|
+
selectedKeys: state.selected
|
|
1490
|
+
}).map((f) => f.id);
|
|
1491
|
+
const context = {
|
|
1492
|
+
tagId: state.rootTagId,
|
|
1493
|
+
selectedKeys: Array.from(state.selected),
|
|
1494
|
+
visibleFieldIds: visibleNow
|
|
1495
|
+
};
|
|
1496
|
+
const contextKey = [
|
|
1497
|
+
context.tagId,
|
|
1498
|
+
[...context.selectedKeys].sort().join("|"),
|
|
1499
|
+
[...context.visibleFieldIds].sort().join("|")
|
|
1500
|
+
].join("::");
|
|
1501
|
+
if (!seenContexts.has(contextKey)) {
|
|
1502
|
+
seenContexts.add(contextKey);
|
|
1503
|
+
v.simulatedVisibilityContexts.push(context);
|
|
1504
|
+
}
|
|
1476
1505
|
validatedStates++;
|
|
1477
1506
|
runVisibilityRulesOnce(v);
|
|
1478
1507
|
if (state.depth >= maxDepth) continue;
|
|
@@ -1480,7 +1509,7 @@ function validateVisibility(v, options = {}) {
|
|
|
1480
1509
|
v,
|
|
1481
1510
|
state.rootTagId,
|
|
1482
1511
|
state.selected,
|
|
1483
|
-
|
|
1512
|
+
effectfulKeys
|
|
1484
1513
|
);
|
|
1485
1514
|
for (let i = triggers.length - 1; i >= 0; i--) {
|
|
1486
1515
|
const trig = triggers[i];
|
|
@@ -1722,8 +1751,8 @@ function validateOptionMaps(v) {
|
|
|
1722
1751
|
};
|
|
1723
1752
|
}
|
|
1724
1753
|
if (ref.kind === "field") {
|
|
1725
|
-
const
|
|
1726
|
-
if (!
|
|
1754
|
+
const isButton = ref.node.button === true;
|
|
1755
|
+
if (!isButton)
|
|
1727
1756
|
return { ok: false, nodeId: ref.id, affected: [ref.id] };
|
|
1728
1757
|
return { ok: true, nodeId: ref.id, affected: [ref.id] };
|
|
1729
1758
|
}
|
|
@@ -1914,9 +1943,9 @@ function validateServiceVsUserInput(v) {
|
|
|
1914
1943
|
for (const f of v.fields) {
|
|
1915
1944
|
const anySvc = hasAnyServiceOption(f);
|
|
1916
1945
|
const hasName = !!(f.name && f.name.trim());
|
|
1917
|
-
const
|
|
1946
|
+
const isButton = f.button === true;
|
|
1918
1947
|
const hasFieldService = f.service_id !== void 0 && f.service_id !== null;
|
|
1919
|
-
const hasTriggerMap =
|
|
1948
|
+
const hasTriggerMap = isButton && hasButtonTriggerMap(v, f.id);
|
|
1920
1949
|
if (f.type === "custom" && anySvc) {
|
|
1921
1950
|
v.errors.push({
|
|
1922
1951
|
code: "user_input_field_has_service_option",
|
|
@@ -1933,7 +1962,7 @@ function validateServiceVsUserInput(v) {
|
|
|
1933
1962
|
v.errors.push({
|
|
1934
1963
|
code: "service_field_missing_service_id",
|
|
1935
1964
|
severity: "error",
|
|
1936
|
-
message:
|
|
1965
|
+
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.`,
|
|
1937
1966
|
nodeId: f.id
|
|
1938
1967
|
});
|
|
1939
1968
|
} else {
|
|
@@ -2252,6 +2281,324 @@ function validateRates(v) {
|
|
|
2252
2281
|
}
|
|
2253
2282
|
}
|
|
2254
2283
|
|
|
2284
|
+
// src/core/rate-coherence.ts
|
|
2285
|
+
function buildTriggerEffectMap(props) {
|
|
2286
|
+
var _a, _b;
|
|
2287
|
+
const map = /* @__PURE__ */ new Map();
|
|
2288
|
+
const ensure = (key) => {
|
|
2289
|
+
let item = map.get(key);
|
|
2290
|
+
if (!item) {
|
|
2291
|
+
item = { includes: /* @__PURE__ */ new Set(), excludes: /* @__PURE__ */ new Set() };
|
|
2292
|
+
map.set(key, item);
|
|
2293
|
+
}
|
|
2294
|
+
return item;
|
|
2295
|
+
};
|
|
2296
|
+
for (const [key, ids] of Object.entries((_a = props.includes_for_buttons) != null ? _a : {})) {
|
|
2297
|
+
const item = ensure(key);
|
|
2298
|
+
for (const id of ids != null ? ids : []) item.includes.add(id);
|
|
2299
|
+
}
|
|
2300
|
+
for (const [key, ids] of Object.entries((_b = props.excludes_for_buttons) != null ? _b : {})) {
|
|
2301
|
+
const item = ensure(key);
|
|
2302
|
+
for (const id of ids != null ? ids : []) item.excludes.add(id);
|
|
2303
|
+
}
|
|
2304
|
+
return map;
|
|
2305
|
+
}
|
|
2306
|
+
function isRefExcludedBySelectedKeys(ref, selectedKeys, effectMap) {
|
|
2307
|
+
for (const key of selectedKeys) {
|
|
2308
|
+
const effects = effectMap.get(key);
|
|
2309
|
+
if (!effects) continue;
|
|
2310
|
+
if (ref.fieldId && effects.excludes.has(ref.fieldId) || effects.excludes.has(ref.nodeId)) {
|
|
2311
|
+
return true;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
return false;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
// src/core/validate/steps/rate-coherence.ts
|
|
2318
|
+
function normalizeRole(role, fallback) {
|
|
2319
|
+
return role === "base" || role === "utility" ? role : fallback;
|
|
2320
|
+
}
|
|
2321
|
+
function uniqueStrings(values) {
|
|
2322
|
+
const out = /* @__PURE__ */ new Set();
|
|
2323
|
+
for (const value of values) {
|
|
2324
|
+
if (!value) continue;
|
|
2325
|
+
out.add(value);
|
|
2326
|
+
}
|
|
2327
|
+
return Array.from(out);
|
|
2328
|
+
}
|
|
2329
|
+
function getRate(serviceMap, serviceId) {
|
|
2330
|
+
const cap = getServiceCapability(serviceMap, serviceId);
|
|
2331
|
+
const rate = cap == null ? void 0 : cap.rate;
|
|
2332
|
+
if (typeof rate !== "number" || !Number.isFinite(rate)) return void 0;
|
|
2333
|
+
return rate;
|
|
2334
|
+
}
|
|
2335
|
+
function collectContextRefs(tag, visibleFields, serviceMap) {
|
|
2336
|
+
var _a, _b, _c, _d, _e;
|
|
2337
|
+
const serviceRefs = [];
|
|
2338
|
+
let tagDefault;
|
|
2339
|
+
if (tag.service_id !== void 0 && tag.service_id !== null) {
|
|
2340
|
+
const tagRate = getRate(serviceMap, tag.service_id);
|
|
2341
|
+
if (tagRate != null) {
|
|
2342
|
+
tagDefault = {
|
|
2343
|
+
key: tag.id,
|
|
2344
|
+
nodeId: tag.id,
|
|
2345
|
+
nodeKind: "tag",
|
|
2346
|
+
serviceId: tag.service_id,
|
|
2347
|
+
rate: tagRate,
|
|
2348
|
+
label: (_a = tag.label) != null ? _a : tag.id,
|
|
2349
|
+
pricingRole: "base"
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
for (const field of visibleFields) {
|
|
2354
|
+
const fieldRole = normalizeRole(field.pricing_role, "base");
|
|
2355
|
+
if (field.service_id !== void 0 && field.service_id !== null) {
|
|
2356
|
+
const rate = getRate(serviceMap, field.service_id);
|
|
2357
|
+
if (rate != null) {
|
|
2358
|
+
serviceRefs.push({
|
|
2359
|
+
key: field.id,
|
|
2360
|
+
nodeId: field.id,
|
|
2361
|
+
fieldId: field.id,
|
|
2362
|
+
nodeKind: "button",
|
|
2363
|
+
serviceId: field.service_id,
|
|
2364
|
+
rate,
|
|
2365
|
+
label: (_b = field.label) != null ? _b : field.id,
|
|
2366
|
+
pricingRole: fieldRole
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
for (const option of (_c = field.options) != null ? _c : []) {
|
|
2371
|
+
if (option.service_id === void 0 || option.service_id === null) continue;
|
|
2372
|
+
const rate = getRate(serviceMap, option.service_id);
|
|
2373
|
+
if (rate == null) continue;
|
|
2374
|
+
serviceRefs.push({
|
|
2375
|
+
key: option.id,
|
|
2376
|
+
nodeId: option.id,
|
|
2377
|
+
fieldId: field.id,
|
|
2378
|
+
nodeKind: "option",
|
|
2379
|
+
serviceId: option.service_id,
|
|
2380
|
+
rate,
|
|
2381
|
+
label: (_d = option.label) != null ? _d : option.id,
|
|
2382
|
+
pricingRole: normalizeRole((_e = option.pricing_role) != null ? _e : field.pricing_role, "base")
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
return { tagDefault, serviceRefs };
|
|
2387
|
+
}
|
|
2388
|
+
function pickHighestRatePrimary(refs) {
|
|
2389
|
+
return refs.reduce((best, cur) => {
|
|
2390
|
+
if (!best) return cur;
|
|
2391
|
+
if (cur.rate > best.rate) return cur;
|
|
2392
|
+
if (cur.rate < best.rate) return best;
|
|
2393
|
+
return cur.nodeId < best.nodeId ? cur : best;
|
|
2394
|
+
}, void 0);
|
|
2395
|
+
}
|
|
2396
|
+
function validateRateCoherenceForVisibleContext(params) {
|
|
2397
|
+
const { v, tagId, selectedKeys, visibleFieldIds, effectMap, seen } = params;
|
|
2398
|
+
const tag = v.tagById.get(tagId);
|
|
2399
|
+
if (!tag) return;
|
|
2400
|
+
const visibleFields = visibleFieldIds.map((id) => v.fieldById.get(id)).filter(Boolean);
|
|
2401
|
+
const { tagDefault, serviceRefs: allServiceRefs } = collectContextRefs(
|
|
2402
|
+
tag,
|
|
2403
|
+
visibleFields,
|
|
2404
|
+
v.serviceMap
|
|
2405
|
+
);
|
|
2406
|
+
const baseRefs = allServiceRefs.filter((ref) => ref.pricingRole === "base");
|
|
2407
|
+
if (baseRefs.length === 0 && !tagDefault) return;
|
|
2408
|
+
const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
|
|
2409
|
+
const visibleInvalidFieldIds = visibleFieldIds.filter(
|
|
2410
|
+
(fieldId) => v.invalidRateFieldIds.has(fieldId)
|
|
2411
|
+
);
|
|
2412
|
+
for (const fieldId of visibleInvalidFieldIds) {
|
|
2413
|
+
const internalKey = [
|
|
2414
|
+
"rate-coherence-internal",
|
|
2415
|
+
tagId,
|
|
2416
|
+
[...selectedKeys].sort().join("|"),
|
|
2417
|
+
fieldId
|
|
2418
|
+
].join("::");
|
|
2419
|
+
if (seen.has(internalKey)) continue;
|
|
2420
|
+
seen.add(internalKey);
|
|
2421
|
+
v.errors.push({
|
|
2422
|
+
code: "rate_coherence_violation",
|
|
2423
|
+
severity: "error",
|
|
2424
|
+
nodeId: fieldId,
|
|
2425
|
+
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
2426
|
+
details: {
|
|
2427
|
+
kind: "internal_field",
|
|
2428
|
+
tagId,
|
|
2429
|
+
selectedKeys: [...selectedKeys],
|
|
2430
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2431
|
+
fieldId,
|
|
2432
|
+
invalidFieldIds: [fieldId],
|
|
2433
|
+
affectedIds: uniqueStrings([tagId, ...selectedKeys, fieldId])
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
const selectedSet = new Set(selectedKeys);
|
|
2438
|
+
const selectedServiceRefs = baseRefs.filter((ref) => selectedSet.has(ref.key));
|
|
2439
|
+
if (baseRefs.length === 0) return;
|
|
2440
|
+
for (let i = 0; i < baseRefs.length; i++) {
|
|
2441
|
+
for (let j = i + 1; j < baseRefs.length; j++) {
|
|
2442
|
+
const left = baseRefs[i];
|
|
2443
|
+
const right = baseRefs[j];
|
|
2444
|
+
const hypotheticalKeys = [...selectedKeys, left.key, right.key];
|
|
2445
|
+
const survivingRefs = baseRefs.filter(
|
|
2446
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
2447
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
2448
|
+
hypotheticalKeys,
|
|
2449
|
+
effectMap
|
|
2450
|
+
)
|
|
2451
|
+
);
|
|
2452
|
+
const survivingSet = new Set(survivingRefs.map((ref) => ref.nodeId));
|
|
2453
|
+
if (!survivingSet.has(left.nodeId) || !survivingSet.has(right.nodeId)) {
|
|
2454
|
+
continue;
|
|
2455
|
+
}
|
|
2456
|
+
if (survivingRefs.length <= 1) continue;
|
|
2457
|
+
const survivingSelected = survivingRefs.filter(
|
|
2458
|
+
(ref) => selectedSet.has(ref.key)
|
|
2459
|
+
);
|
|
2460
|
+
const tagIsCompeting = survivingSelected.length === 0;
|
|
2461
|
+
const primary = pickHighestRatePrimary(survivingRefs);
|
|
2462
|
+
if (!primary) continue;
|
|
2463
|
+
const comparePool = survivingRefs.filter((ref) => ref.nodeId !== primary.nodeId);
|
|
2464
|
+
for (const candidate of comparePool) {
|
|
2465
|
+
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) continue;
|
|
2466
|
+
const issueKey = [
|
|
2467
|
+
"rate-coherence-context",
|
|
2468
|
+
tagId,
|
|
2469
|
+
[...selectedKeys].sort().join("|"),
|
|
2470
|
+
[...survivingRefs.map((r) => r.nodeId).sort()].join("|"),
|
|
2471
|
+
primary.nodeId,
|
|
2472
|
+
candidate.nodeId,
|
|
2473
|
+
ratePolicy.kind,
|
|
2474
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
2475
|
+
].join("::");
|
|
2476
|
+
if (seen.has(issueKey)) continue;
|
|
2477
|
+
seen.add(issueKey);
|
|
2478
|
+
v.errors.push({
|
|
2479
|
+
code: "rate_coherence_violation",
|
|
2480
|
+
severity: "error",
|
|
2481
|
+
nodeId: candidate.nodeId,
|
|
2482
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
2483
|
+
details: {
|
|
2484
|
+
kind: "selected_context",
|
|
2485
|
+
tagId,
|
|
2486
|
+
selectedKeys: [...selectedKeys],
|
|
2487
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2488
|
+
primary: {
|
|
2489
|
+
nodeId: primary.nodeId,
|
|
2490
|
+
fieldId: primary.fieldId,
|
|
2491
|
+
service_id: primary.serviceId,
|
|
2492
|
+
serviceId: primary.serviceId,
|
|
2493
|
+
rate: primary.rate
|
|
2494
|
+
},
|
|
2495
|
+
candidate: {
|
|
2496
|
+
nodeId: candidate.nodeId,
|
|
2497
|
+
fieldId: candidate.fieldId,
|
|
2498
|
+
service_id: candidate.serviceId,
|
|
2499
|
+
serviceId: candidate.serviceId,
|
|
2500
|
+
rate: candidate.rate
|
|
2501
|
+
},
|
|
2502
|
+
policy: ratePolicy.kind,
|
|
2503
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2504
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
2505
|
+
affectedIds: uniqueStrings([
|
|
2506
|
+
tagId,
|
|
2507
|
+
...selectedKeys,
|
|
2508
|
+
primary.nodeId,
|
|
2509
|
+
primary.fieldId,
|
|
2510
|
+
candidate.nodeId,
|
|
2511
|
+
candidate.fieldId,
|
|
2512
|
+
tagIsCompeting ? tagDefault == null ? void 0 : tagDefault.nodeId : void 0
|
|
2513
|
+
]),
|
|
2514
|
+
affectedServiceIds: uniqueStrings([
|
|
2515
|
+
String(primary.serviceId),
|
|
2516
|
+
String(candidate.serviceId)
|
|
2517
|
+
])
|
|
2518
|
+
}
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
if (selectedServiceRefs.length === 0 && tagDefault && baseRefs.length > 0) {
|
|
2524
|
+
const survivingByDefault = baseRefs.filter(
|
|
2525
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
2526
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
2527
|
+
selectedKeys,
|
|
2528
|
+
effectMap
|
|
2529
|
+
)
|
|
2530
|
+
);
|
|
2531
|
+
for (const candidate of survivingByDefault) {
|
|
2532
|
+
if (passesRatePolicy(ratePolicy, tagDefault.rate, candidate.rate)) continue;
|
|
2533
|
+
const issueKey = [
|
|
2534
|
+
"rate-coherence-default",
|
|
2535
|
+
tagId,
|
|
2536
|
+
[...selectedKeys].sort().join("|"),
|
|
2537
|
+
tagDefault.nodeId,
|
|
2538
|
+
candidate.nodeId,
|
|
2539
|
+
ratePolicy.kind,
|
|
2540
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
2541
|
+
].join("::");
|
|
2542
|
+
if (seen.has(issueKey)) continue;
|
|
2543
|
+
seen.add(issueKey);
|
|
2544
|
+
v.errors.push({
|
|
2545
|
+
code: "rate_coherence_violation",
|
|
2546
|
+
severity: "error",
|
|
2547
|
+
nodeId: candidate.nodeId,
|
|
2548
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
2549
|
+
details: {
|
|
2550
|
+
kind: "selected_context",
|
|
2551
|
+
tagId,
|
|
2552
|
+
selectedKeys: [...selectedKeys],
|
|
2553
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
2554
|
+
primary: {
|
|
2555
|
+
nodeId: tagDefault.nodeId,
|
|
2556
|
+
service_id: tagDefault.serviceId,
|
|
2557
|
+
serviceId: tagDefault.serviceId,
|
|
2558
|
+
rate: tagDefault.rate
|
|
2559
|
+
},
|
|
2560
|
+
candidate: {
|
|
2561
|
+
nodeId: candidate.nodeId,
|
|
2562
|
+
fieldId: candidate.fieldId,
|
|
2563
|
+
service_id: candidate.serviceId,
|
|
2564
|
+
serviceId: candidate.serviceId,
|
|
2565
|
+
rate: candidate.rate
|
|
2566
|
+
},
|
|
2567
|
+
policy: ratePolicy.kind,
|
|
2568
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
2569
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
2570
|
+
affectedIds: uniqueStrings([
|
|
2571
|
+
tagId,
|
|
2572
|
+
...selectedKeys,
|
|
2573
|
+
tagDefault.nodeId,
|
|
2574
|
+
candidate.nodeId,
|
|
2575
|
+
candidate.fieldId
|
|
2576
|
+
]),
|
|
2577
|
+
affectedServiceIds: uniqueStrings([
|
|
2578
|
+
String(tagDefault.serviceId),
|
|
2579
|
+
String(candidate.serviceId)
|
|
2580
|
+
])
|
|
2581
|
+
}
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
function validateRateCoherence(v) {
|
|
2587
|
+
if (Object.keys(v.serviceMap).length === 0 || v.tags.length === 0) return;
|
|
2588
|
+
const effectMap = buildTriggerEffectMap(v.props);
|
|
2589
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2590
|
+
for (const context of v.simulatedVisibilityContexts) {
|
|
2591
|
+
validateRateCoherenceForVisibleContext({
|
|
2592
|
+
v,
|
|
2593
|
+
tagId: context.tagId,
|
|
2594
|
+
selectedKeys: context.selectedKeys,
|
|
2595
|
+
visibleFieldIds: context.visibleFieldIds,
|
|
2596
|
+
effectMap,
|
|
2597
|
+
seen
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2255
2602
|
// src/core/validate/steps/constraints.ts
|
|
2256
2603
|
function constraintKeysInChain(v, tagId) {
|
|
2257
2604
|
const keys = [];
|
|
@@ -3008,6 +3355,84 @@ function mergeValidatorOptions(defaults = {}, overrides = {}) {
|
|
|
3008
3355
|
};
|
|
3009
3356
|
}
|
|
3010
3357
|
|
|
3358
|
+
// src/core/validate/index.ts
|
|
3359
|
+
function readVisibilitySimOpts(ctx) {
|
|
3360
|
+
const c = ctx;
|
|
3361
|
+
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
3362
|
+
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
3363
|
+
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
3364
|
+
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
3365
|
+
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
3366
|
+
return {
|
|
3367
|
+
simulate,
|
|
3368
|
+
maxStates,
|
|
3369
|
+
maxDepth,
|
|
3370
|
+
simulateAllRoots,
|
|
3371
|
+
onlyEffectfulTriggers
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
function validate(props, ctx = {}) {
|
|
3375
|
+
var _a, _b, _c;
|
|
3376
|
+
const options = mergeValidatorOptions({}, ctx);
|
|
3377
|
+
const fallbackSettings = resolveFallbackSettings(options);
|
|
3378
|
+
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
3379
|
+
const errors = [];
|
|
3380
|
+
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
3381
|
+
const selectedKeys = new Set(
|
|
3382
|
+
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
3383
|
+
);
|
|
3384
|
+
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
3385
|
+
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
3386
|
+
const tagById = /* @__PURE__ */ new Map();
|
|
3387
|
+
const fieldById = /* @__PURE__ */ new Map();
|
|
3388
|
+
for (const t of tags) tagById.set(t.id, t);
|
|
3389
|
+
for (const f of fields) fieldById.set(f.id, f);
|
|
3390
|
+
const v = {
|
|
3391
|
+
props,
|
|
3392
|
+
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
3393
|
+
options: {
|
|
3394
|
+
...options,
|
|
3395
|
+
ratePolicy,
|
|
3396
|
+
fallbackSettings
|
|
3397
|
+
},
|
|
3398
|
+
errors,
|
|
3399
|
+
serviceMap,
|
|
3400
|
+
selectedKeys,
|
|
3401
|
+
tags,
|
|
3402
|
+
fields,
|
|
3403
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
3404
|
+
tagById,
|
|
3405
|
+
fieldById,
|
|
3406
|
+
fieldsVisibleUnder: (_tagId) => [],
|
|
3407
|
+
simulatedVisibilityContexts: []
|
|
3408
|
+
};
|
|
3409
|
+
validateStructure(v);
|
|
3410
|
+
validateIdentity(v);
|
|
3411
|
+
validateOptionMaps(v);
|
|
3412
|
+
validateOrderKinds(v);
|
|
3413
|
+
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
3414
|
+
const visSim = readVisibilitySimOpts(options);
|
|
3415
|
+
validateVisibility(v, visSim);
|
|
3416
|
+
applyPolicies(
|
|
3417
|
+
v.errors,
|
|
3418
|
+
v.props,
|
|
3419
|
+
v.serviceMap,
|
|
3420
|
+
v.options.policies,
|
|
3421
|
+
v.fieldsVisibleUnder,
|
|
3422
|
+
v.tags
|
|
3423
|
+
);
|
|
3424
|
+
validateServiceVsUserInput(v);
|
|
3425
|
+
validateUtilityMarkers(v);
|
|
3426
|
+
validateRates(v);
|
|
3427
|
+
validateRateCoherence(v);
|
|
3428
|
+
validateConstraints(v);
|
|
3429
|
+
validateCustomFields(v);
|
|
3430
|
+
validateGlobalUtilityGuard(v);
|
|
3431
|
+
validateUnboundFields(v);
|
|
3432
|
+
validateFallbacks(v);
|
|
3433
|
+
return v.errors;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3011
3436
|
// src/core/builder.ts
|
|
3012
3437
|
var import_lodash_es2 = require("lodash-es");
|
|
3013
3438
|
function createBuilder(opts = {}) {
|
|
@@ -3290,344 +3715,6 @@ function toStringSet(v) {
|
|
|
3290
3715
|
return new Set(v.map(String));
|
|
3291
3716
|
}
|
|
3292
3717
|
|
|
3293
|
-
// src/core/rate-coherence.ts
|
|
3294
|
-
function validateRateCoherenceDeep(params) {
|
|
3295
|
-
var _a, _b, _c;
|
|
3296
|
-
const { builder, services, tagId } = params;
|
|
3297
|
-
const ratePolicy = normalizeRatePolicy(params.ratePolicy);
|
|
3298
|
-
const props = builder.getProps();
|
|
3299
|
-
const invalidFieldIds = new Set((_a = params.invalidFieldIds) != null ? _a : []);
|
|
3300
|
-
const fields = (_b = props.fields) != null ? _b : [];
|
|
3301
|
-
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
3302
|
-
const tagById = new Map(((_c = props.filters) != null ? _c : []).map((t) => [t.id, t]));
|
|
3303
|
-
const tag = tagById.get(tagId);
|
|
3304
|
-
const baselineFieldIds = builder.visibleFields(tagId, []);
|
|
3305
|
-
const baselineFields = baselineFieldIds.map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
3306
|
-
const anchors = collectAnchors(baselineFields);
|
|
3307
|
-
const diagnostics = [];
|
|
3308
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3309
|
-
for (const anchor of anchors) {
|
|
3310
|
-
const selectedKeys = anchor.kind === "option" ? [`${anchor.fieldId}::${anchor.id}`] : [anchor.fieldId];
|
|
3311
|
-
const visibleFields = builder.visibleFields(tagId, selectedKeys).map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
3312
|
-
const visibleInvalidFieldIds = visibleFields.map((field) => field.id).filter((fieldId) => invalidFieldIds.has(fieldId));
|
|
3313
|
-
for (const fieldId of visibleInvalidFieldIds) {
|
|
3314
|
-
const key = `internal|${tagId}|${fieldId}`;
|
|
3315
|
-
if (seen.has(key)) continue;
|
|
3316
|
-
seen.add(key);
|
|
3317
|
-
diagnostics.push({
|
|
3318
|
-
kind: "internal_field",
|
|
3319
|
-
scope: "visible_group",
|
|
3320
|
-
tagId,
|
|
3321
|
-
fieldId,
|
|
3322
|
-
nodeId: fieldId,
|
|
3323
|
-
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
3324
|
-
simulationAnchor: {
|
|
3325
|
-
kind: anchor.kind,
|
|
3326
|
-
id: anchor.id,
|
|
3327
|
-
fieldId: anchor.fieldId,
|
|
3328
|
-
label: anchor.label
|
|
3329
|
-
},
|
|
3330
|
-
invalidFieldIds: [fieldId]
|
|
3331
|
-
});
|
|
3332
|
-
}
|
|
3333
|
-
const references = visibleFields.flatMap(
|
|
3334
|
-
(field) => collectFieldReferences(field, services)
|
|
3335
|
-
);
|
|
3336
|
-
if (references.length <= 1) continue;
|
|
3337
|
-
const primary = references.reduce((best, current) => {
|
|
3338
|
-
if (current.rate !== best.rate) {
|
|
3339
|
-
return current.rate > best.rate ? current : best;
|
|
3340
|
-
}
|
|
3341
|
-
const bestKey = `${best.fieldId}|${best.nodeId}`;
|
|
3342
|
-
const currentKey = `${current.fieldId}|${current.nodeId}`;
|
|
3343
|
-
return currentKey < bestKey ? current : best;
|
|
3344
|
-
});
|
|
3345
|
-
for (const candidate of references) {
|
|
3346
|
-
if (candidate.nodeId === primary.nodeId) continue;
|
|
3347
|
-
if (candidate.fieldId === primary.fieldId) continue;
|
|
3348
|
-
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) {
|
|
3349
|
-
continue;
|
|
3350
|
-
}
|
|
3351
|
-
const key = contextualKey(tagId, primary, candidate, ratePolicy);
|
|
3352
|
-
if (seen.has(key)) continue;
|
|
3353
|
-
seen.add(key);
|
|
3354
|
-
diagnostics.push({
|
|
3355
|
-
kind: "contextual",
|
|
3356
|
-
scope: "visible_group",
|
|
3357
|
-
tagId,
|
|
3358
|
-
nodeId: candidate.nodeId,
|
|
3359
|
-
primary: toDiagnosticRef(primary),
|
|
3360
|
-
offender: toDiagnosticRef(candidate),
|
|
3361
|
-
policy: ratePolicy.kind,
|
|
3362
|
-
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
3363
|
-
message: explainRateMismatch(
|
|
3364
|
-
ratePolicy,
|
|
3365
|
-
primary,
|
|
3366
|
-
candidate,
|
|
3367
|
-
describeLabel(tag)
|
|
3368
|
-
),
|
|
3369
|
-
simulationAnchor: {
|
|
3370
|
-
kind: anchor.kind,
|
|
3371
|
-
id: anchor.id,
|
|
3372
|
-
fieldId: anchor.fieldId,
|
|
3373
|
-
label: anchor.label
|
|
3374
|
-
},
|
|
3375
|
-
invalidFieldIds: visibleInvalidFieldIds
|
|
3376
|
-
});
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
return diagnostics;
|
|
3380
|
-
}
|
|
3381
|
-
function collectAnchors(fields) {
|
|
3382
|
-
var _a, _b;
|
|
3383
|
-
const anchors = [];
|
|
3384
|
-
for (const field of fields) {
|
|
3385
|
-
if (!isButton(field)) continue;
|
|
3386
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
3387
|
-
for (const option of field.options) {
|
|
3388
|
-
anchors.push({
|
|
3389
|
-
kind: "option",
|
|
3390
|
-
id: option.id,
|
|
3391
|
-
fieldId: field.id,
|
|
3392
|
-
label: (_a = option.label) != null ? _a : option.id
|
|
3393
|
-
});
|
|
3394
|
-
}
|
|
3395
|
-
continue;
|
|
3396
|
-
}
|
|
3397
|
-
anchors.push({
|
|
3398
|
-
kind: "field",
|
|
3399
|
-
id: field.id,
|
|
3400
|
-
fieldId: field.id,
|
|
3401
|
-
label: (_b = field.label) != null ? _b : field.id
|
|
3402
|
-
});
|
|
3403
|
-
}
|
|
3404
|
-
return anchors;
|
|
3405
|
-
}
|
|
3406
|
-
function collectFieldReferences(field, services) {
|
|
3407
|
-
var _a;
|
|
3408
|
-
const members = collectBaseMembers(field, services);
|
|
3409
|
-
if (members.length === 0) return [];
|
|
3410
|
-
if (isMultiField(field)) {
|
|
3411
|
-
const averageRate = members.reduce((sum, member) => sum + member.rate, 0) / members.length;
|
|
3412
|
-
return [
|
|
3413
|
-
{
|
|
3414
|
-
refKind: "multi",
|
|
3415
|
-
nodeId: field.id,
|
|
3416
|
-
fieldId: field.id,
|
|
3417
|
-
label: (_a = field.label) != null ? _a : field.id,
|
|
3418
|
-
rate: averageRate,
|
|
3419
|
-
members
|
|
3420
|
-
}
|
|
3421
|
-
];
|
|
3422
|
-
}
|
|
3423
|
-
return members.map((member) => ({
|
|
3424
|
-
refKind: "single",
|
|
3425
|
-
nodeId: member.id,
|
|
3426
|
-
fieldId: field.id,
|
|
3427
|
-
label: member.label,
|
|
3428
|
-
rate: member.rate,
|
|
3429
|
-
service_id: member.service_id,
|
|
3430
|
-
members: [member]
|
|
3431
|
-
}));
|
|
3432
|
-
}
|
|
3433
|
-
function collectBaseMembers(field, services) {
|
|
3434
|
-
var _a, _b, _c;
|
|
3435
|
-
const members = [];
|
|
3436
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
3437
|
-
for (const option of field.options) {
|
|
3438
|
-
const role2 = normalizeRole((_a = option.pricing_role) != null ? _a : field.pricing_role, "base");
|
|
3439
|
-
if (role2 !== "base") continue;
|
|
3440
|
-
if (option.service_id === void 0 || option.service_id === null) {
|
|
3441
|
-
continue;
|
|
3442
|
-
}
|
|
3443
|
-
const cap2 = getServiceCapability(services, option.service_id);
|
|
3444
|
-
if (!cap2 || typeof cap2.rate !== "number" || !Number.isFinite(cap2.rate)) {
|
|
3445
|
-
continue;
|
|
3446
|
-
}
|
|
3447
|
-
members.push({
|
|
3448
|
-
kind: "option",
|
|
3449
|
-
id: option.id,
|
|
3450
|
-
fieldId: field.id,
|
|
3451
|
-
label: (_b = option.label) != null ? _b : option.id,
|
|
3452
|
-
service_id: option.service_id,
|
|
3453
|
-
rate: cap2.rate
|
|
3454
|
-
});
|
|
3455
|
-
}
|
|
3456
|
-
return members;
|
|
3457
|
-
}
|
|
3458
|
-
const role = normalizeRole(field.pricing_role, "base");
|
|
3459
|
-
if (role !== "base") return members;
|
|
3460
|
-
if (field.service_id === void 0 || field.service_id === null) return members;
|
|
3461
|
-
const cap = getServiceCapability(services, field.service_id);
|
|
3462
|
-
if (!cap || typeof cap.rate !== "number" || !Number.isFinite(cap.rate)) {
|
|
3463
|
-
return members;
|
|
3464
|
-
}
|
|
3465
|
-
members.push({
|
|
3466
|
-
kind: "field",
|
|
3467
|
-
id: field.id,
|
|
3468
|
-
fieldId: field.id,
|
|
3469
|
-
label: (_c = field.label) != null ? _c : field.id,
|
|
3470
|
-
service_id: field.service_id,
|
|
3471
|
-
rate: cap.rate
|
|
3472
|
-
});
|
|
3473
|
-
return members;
|
|
3474
|
-
}
|
|
3475
|
-
function isButton(field) {
|
|
3476
|
-
if (field.button === true) return true;
|
|
3477
|
-
return Array.isArray(field.options) && field.options.length > 0;
|
|
3478
|
-
}
|
|
3479
|
-
function normalizeRole(role, fallback) {
|
|
3480
|
-
return role === "base" || role === "utility" ? role : fallback;
|
|
3481
|
-
}
|
|
3482
|
-
function toDiagnosticRef(reference) {
|
|
3483
|
-
return {
|
|
3484
|
-
nodeId: reference.nodeId,
|
|
3485
|
-
fieldId: reference.fieldId,
|
|
3486
|
-
label: reference.label,
|
|
3487
|
-
refKind: reference.refKind,
|
|
3488
|
-
service_id: reference.service_id,
|
|
3489
|
-
rate: reference.rate
|
|
3490
|
-
};
|
|
3491
|
-
}
|
|
3492
|
-
function contextualKey(tagId, primary, candidate, ratePolicy) {
|
|
3493
|
-
const pctKey = "pct" in ratePolicy ? `:${ratePolicy.pct}` : "";
|
|
3494
|
-
return [
|
|
3495
|
-
"contextual",
|
|
3496
|
-
tagId,
|
|
3497
|
-
primary.fieldId,
|
|
3498
|
-
primary.nodeId,
|
|
3499
|
-
candidate.fieldId,
|
|
3500
|
-
candidate.nodeId,
|
|
3501
|
-
`${ratePolicy.kind}${pctKey}`
|
|
3502
|
-
].join("|");
|
|
3503
|
-
}
|
|
3504
|
-
function describeLabel(tag) {
|
|
3505
|
-
var _a, _b;
|
|
3506
|
-
return (_b = (_a = tag == null ? void 0 : tag.label) != null ? _a : tag == null ? void 0 : tag.id) != null ? _b : "tag";
|
|
3507
|
-
}
|
|
3508
|
-
function explainRateMismatch(policy, primary, candidate, where) {
|
|
3509
|
-
var _a, _b;
|
|
3510
|
-
const primaryLabel = `${(_a = primary.label) != null ? _a : primary.nodeId} (${primary.rate})`;
|
|
3511
|
-
const candidateLabel = `${(_b = candidate.label) != null ? _b : candidate.nodeId} (${candidate.rate})`;
|
|
3512
|
-
switch (policy.kind) {
|
|
3513
|
-
case "eq_primary":
|
|
3514
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must exactly match ${primaryLabel}.`;
|
|
3515
|
-
case "lte_primary":
|
|
3516
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must stay within ${policy.pct}% below and never above ${primaryLabel}.`;
|
|
3517
|
-
case "within_pct":
|
|
3518
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be within ${policy.pct}% of ${primaryLabel}.`;
|
|
3519
|
-
case "at_least_pct_lower":
|
|
3520
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be at least ${policy.pct}% lower than ${primaryLabel}.`;
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3524
|
-
// src/core/validate/index.ts
|
|
3525
|
-
function readVisibilitySimOpts(ctx) {
|
|
3526
|
-
const c = ctx;
|
|
3527
|
-
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
3528
|
-
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
3529
|
-
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
3530
|
-
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
3531
|
-
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
3532
|
-
return {
|
|
3533
|
-
simulate,
|
|
3534
|
-
maxStates,
|
|
3535
|
-
maxDepth,
|
|
3536
|
-
simulateAllRoots,
|
|
3537
|
-
onlyEffectfulTriggers
|
|
3538
|
-
};
|
|
3539
|
-
}
|
|
3540
|
-
function validate(props, ctx = {}) {
|
|
3541
|
-
var _a, _b, _c;
|
|
3542
|
-
const options = mergeValidatorOptions({}, ctx);
|
|
3543
|
-
const fallbackSettings = resolveFallbackSettings(options);
|
|
3544
|
-
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
3545
|
-
const errors = [];
|
|
3546
|
-
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
3547
|
-
const selectedKeys = new Set(
|
|
3548
|
-
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
3549
|
-
);
|
|
3550
|
-
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
3551
|
-
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
3552
|
-
const tagById = /* @__PURE__ */ new Map();
|
|
3553
|
-
const fieldById = /* @__PURE__ */ new Map();
|
|
3554
|
-
for (const t of tags) tagById.set(t.id, t);
|
|
3555
|
-
for (const f of fields) fieldById.set(f.id, f);
|
|
3556
|
-
const v = {
|
|
3557
|
-
props,
|
|
3558
|
-
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
3559
|
-
options: {
|
|
3560
|
-
...options,
|
|
3561
|
-
ratePolicy,
|
|
3562
|
-
fallbackSettings
|
|
3563
|
-
},
|
|
3564
|
-
errors,
|
|
3565
|
-
serviceMap,
|
|
3566
|
-
selectedKeys,
|
|
3567
|
-
tags,
|
|
3568
|
-
fields,
|
|
3569
|
-
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
3570
|
-
tagById,
|
|
3571
|
-
fieldById,
|
|
3572
|
-
fieldsVisibleUnder: (_tagId) => []
|
|
3573
|
-
};
|
|
3574
|
-
validateStructure(v);
|
|
3575
|
-
validateIdentity(v);
|
|
3576
|
-
validateOptionMaps(v);
|
|
3577
|
-
validateOrderKinds(v);
|
|
3578
|
-
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
3579
|
-
const visSim = readVisibilitySimOpts(options);
|
|
3580
|
-
validateVisibility(v, visSim);
|
|
3581
|
-
applyPolicies(
|
|
3582
|
-
v.errors,
|
|
3583
|
-
v.props,
|
|
3584
|
-
v.serviceMap,
|
|
3585
|
-
v.options.policies,
|
|
3586
|
-
v.fieldsVisibleUnder,
|
|
3587
|
-
v.tags
|
|
3588
|
-
);
|
|
3589
|
-
validateServiceVsUserInput(v);
|
|
3590
|
-
validateUtilityMarkers(v);
|
|
3591
|
-
validateRates(v);
|
|
3592
|
-
if (Object.keys(serviceMap).length > 0 && tags.length > 0) {
|
|
3593
|
-
const builder = createBuilder({ serviceMap });
|
|
3594
|
-
builder.load(props);
|
|
3595
|
-
for (const tag of tags) {
|
|
3596
|
-
const diags = validateRateCoherenceDeep({
|
|
3597
|
-
builder,
|
|
3598
|
-
services: serviceMap,
|
|
3599
|
-
tagId: tag.id,
|
|
3600
|
-
ratePolicy,
|
|
3601
|
-
invalidFieldIds: v.invalidRateFieldIds
|
|
3602
|
-
});
|
|
3603
|
-
for (const diag of diags) {
|
|
3604
|
-
if (diag.kind !== "contextual") continue;
|
|
3605
|
-
errors.push({
|
|
3606
|
-
code: "rate_coherence_violation",
|
|
3607
|
-
severity: "error",
|
|
3608
|
-
message: diag.message,
|
|
3609
|
-
nodeId: diag.nodeId,
|
|
3610
|
-
details: {
|
|
3611
|
-
tagId: diag.tagId,
|
|
3612
|
-
simulationAnchor: diag.simulationAnchor,
|
|
3613
|
-
primary: diag.primary,
|
|
3614
|
-
offender: diag.offender,
|
|
3615
|
-
policy: diag.policy,
|
|
3616
|
-
policyPct: diag.policyPct,
|
|
3617
|
-
invalidFieldIds: diag.invalidFieldIds
|
|
3618
|
-
}
|
|
3619
|
-
});
|
|
3620
|
-
}
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
validateConstraints(v);
|
|
3624
|
-
validateCustomFields(v);
|
|
3625
|
-
validateGlobalUtilityGuard(v);
|
|
3626
|
-
validateUnboundFields(v);
|
|
3627
|
-
validateFallbacks(v);
|
|
3628
|
-
return v.errors;
|
|
3629
|
-
}
|
|
3630
|
-
|
|
3631
3718
|
// src/core/fallback.ts
|
|
3632
3719
|
var DEFAULT_SETTINGS = {
|
|
3633
3720
|
requireConstraintFit: true,
|
|
@@ -4206,9 +4293,9 @@ function createNodeIndex(builder) {
|
|
|
4206
4293
|
if (cached) return cached;
|
|
4207
4294
|
const raw = fieldById.get(id);
|
|
4208
4295
|
if (!raw) return void 0;
|
|
4209
|
-
const
|
|
4210
|
-
const includes =
|
|
4211
|
-
const excludes =
|
|
4296
|
+
const isButton = raw.button === true;
|
|
4297
|
+
const includes = isButton ? Object.freeze(new Set((_b2 = (_a2 = props.includes_for_buttons) == null ? void 0 : _a2[id]) != null ? _b2 : [])) : emptySet;
|
|
4298
|
+
const excludes = isButton ? Object.freeze(new Set((_d2 = (_c2 = props.excludes_for_buttons) == null ? void 0 : _c2[id]) != null ? _d2 : [])) : emptySet;
|
|
4212
4299
|
const bindIds = () => {
|
|
4213
4300
|
const cachedBind = fieldBindIdsCache.get(id);
|
|
4214
4301
|
if (cachedBind) return cachedBind;
|
|
@@ -4245,7 +4332,7 @@ function createNodeIndex(builder) {
|
|
|
4245
4332
|
);
|
|
4246
4333
|
},
|
|
4247
4334
|
getDescendants(tagId) {
|
|
4248
|
-
return resolveDescendants(id, includes, tagId, !
|
|
4335
|
+
return resolveDescendants(id, includes, tagId, !isButton);
|
|
4249
4336
|
}
|
|
4250
4337
|
};
|
|
4251
4338
|
fieldNodeCache.set(id, node);
|
|
@@ -4582,23 +4669,20 @@ function compilePolicies(raw) {
|
|
|
4582
4669
|
|
|
4583
4670
|
// src/core/service-filter.ts
|
|
4584
4671
|
function filterServicesForVisibleGroup(input, deps) {
|
|
4585
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
4672
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
4586
4673
|
const svcMap = (_c = (_b = (_a = deps.builder).getServiceMap) == null ? void 0 : _b.call(_a)) != null ? _c : {};
|
|
4587
4674
|
const builderOptions = (_e = (_d = deps.builder).getOptions) == null ? void 0 : _e.call(_d);
|
|
4588
4675
|
const { context } = input;
|
|
4589
4676
|
const usedSet = new Set(context.usedServiceIds.map(String));
|
|
4590
|
-
const primary = context.usedServiceIds[0];
|
|
4591
4677
|
const explicitFallbackSettings = (_f = context.fallbackSettings) != null ? _f : context.fallback;
|
|
4592
4678
|
const resolvedRatePolicy = normalizeRatePolicy(
|
|
4593
4679
|
(_h = (_g = context.ratePolicy) != null ? _g : explicitFallbackSettings == null ? void 0 : explicitFallbackSettings.ratePolicy) != null ? _h : builderOptions == null ? void 0 : builderOptions.ratePolicy
|
|
4594
4680
|
);
|
|
4595
|
-
const fallbackSettingsSource = explicitFallbackSettings != null ? explicitFallbackSettings : builderOptions == null ? void 0 : builderOptions.fallbackSettings;
|
|
4596
|
-
const fb = {
|
|
4597
|
-
...DEFAULT_FALLBACK_SETTINGS,
|
|
4598
|
-
...fallbackSettingsSource != null ? fallbackSettingsSource : {},
|
|
4599
|
-
ratePolicy: resolvedRatePolicy
|
|
4600
|
-
};
|
|
4601
4681
|
const policySource = (_j = (_i = context.policies) != null ? _i : builderOptions == null ? void 0 : builderOptions.policies) != null ? _j : [];
|
|
4682
|
+
const resolvedCustomPrimaryRate = resolveCustomPrimaryRate(
|
|
4683
|
+
context.rateContext,
|
|
4684
|
+
svcMap
|
|
4685
|
+
);
|
|
4602
4686
|
const visibleServiceIds = context.selectedButtons === void 0 ? void 0 : collectVisibleServiceIds(
|
|
4603
4687
|
deps.builder,
|
|
4604
4688
|
context.tagId,
|
|
@@ -4625,7 +4709,19 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
4625
4709
|
cap.id,
|
|
4626
4710
|
(_k = context.effectiveConstraints) != null ? _k : {}
|
|
4627
4711
|
);
|
|
4628
|
-
const passesRate2 =
|
|
4712
|
+
const passesRate2 = resolvedCustomPrimaryRate != null ? passesRatePolicy(
|
|
4713
|
+
resolvedRatePolicy,
|
|
4714
|
+
resolvedCustomPrimaryRate,
|
|
4715
|
+
toFiniteNumber(cap.rate)
|
|
4716
|
+
) : candidatePassesRateCoherence(
|
|
4717
|
+
deps.builder,
|
|
4718
|
+
svcMap,
|
|
4719
|
+
context.tagId,
|
|
4720
|
+
(_l = context.selectedButtons) != null ? _l : [],
|
|
4721
|
+
context.usedServiceIds,
|
|
4722
|
+
id,
|
|
4723
|
+
resolvedRatePolicy
|
|
4724
|
+
);
|
|
4629
4725
|
const polRes = evaluatePoliciesRaw(
|
|
4630
4726
|
policySource,
|
|
4631
4727
|
[...context.usedServiceIds, id],
|
|
@@ -4657,6 +4753,17 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
4657
4753
|
diagnostics: lastDiagnostics && lastDiagnostics.length ? lastDiagnostics : void 0
|
|
4658
4754
|
};
|
|
4659
4755
|
}
|
|
4756
|
+
function resolveCustomPrimaryRate(rateContext, serviceMap) {
|
|
4757
|
+
if (!rateContext || rateContext.mode !== "custom_primary_rate") {
|
|
4758
|
+
return void 0;
|
|
4759
|
+
}
|
|
4760
|
+
if (rateContext.source === "manual") {
|
|
4761
|
+
return toFiniteNumber(rateContext.primaryRate);
|
|
4762
|
+
}
|
|
4763
|
+
if (rateContext.primaryServiceId == null) return void 0;
|
|
4764
|
+
const cap = getServiceCapability(serviceMap, rateContext.primaryServiceId);
|
|
4765
|
+
return toFiniteNumber(cap == null ? void 0 : cap.rate);
|
|
4766
|
+
}
|
|
4660
4767
|
function evaluatePoliciesRaw(raw, serviceIds, svcMap, tagId, visibleServiceIds) {
|
|
4661
4768
|
const compiled = compilePolicies(raw);
|
|
4662
4769
|
const evaluated = evaluateServicePolicies(
|
|
@@ -4744,7 +4851,9 @@ function collectVisibleServiceIds(builder, tagId, selectedButtons) {
|
|
|
4744
4851
|
const fields = (_b = props.fields) != null ? _b : [];
|
|
4745
4852
|
const tag = tags.find((t) => t.id === tagId);
|
|
4746
4853
|
if ((tag == null ? void 0 : tag.service_id) != null) out.add(String(tag.service_id));
|
|
4747
|
-
const visibleFieldIds = new Set(
|
|
4854
|
+
const visibleFieldIds = new Set(
|
|
4855
|
+
builder.visibleFields(tagId, selectedButtons)
|
|
4856
|
+
);
|
|
4748
4857
|
for (const field of fields) {
|
|
4749
4858
|
if (!visibleFieldIds.has(field.id)) continue;
|
|
4750
4859
|
if (field.service_id != null) {
|
|
@@ -4767,8 +4876,7 @@ function matchesRuleFilter(cap, rule, tagId) {
|
|
|
4767
4876
|
if (!cap) return false;
|
|
4768
4877
|
const f = rule.filter;
|
|
4769
4878
|
if (!f) return true;
|
|
4770
|
-
|
|
4771
|
-
return true;
|
|
4879
|
+
return !(f.tag_id && !toStrSet(f.tag_id).has(String(tagId)));
|
|
4772
4880
|
}
|
|
4773
4881
|
function toStrSet(v) {
|
|
4774
4882
|
const arr = Array.isArray(v) ? v : [v];
|
|
@@ -4776,6 +4884,107 @@ function toStrSet(v) {
|
|
|
4776
4884
|
for (const x of arr) s.add(String(x));
|
|
4777
4885
|
return s;
|
|
4778
4886
|
}
|
|
4887
|
+
function candidatePassesRateCoherence(builder, serviceMap, tagId, selectedKeys, usedServiceIds, candidateId, ratePolicy) {
|
|
4888
|
+
var _a, _b, _c, _d;
|
|
4889
|
+
if (usedServiceIds.length === 0) return true;
|
|
4890
|
+
const props = builder.getProps();
|
|
4891
|
+
const baseFields = (_a = props.fields) != null ? _a : [];
|
|
4892
|
+
const candidateFieldId = syntheticServiceFieldId("candidate", candidateId, 0);
|
|
4893
|
+
const syntheticFields = [
|
|
4894
|
+
...usedServiceIds.map((serviceId, index) => ({
|
|
4895
|
+
id: syntheticServiceFieldId("used", serviceId, index),
|
|
4896
|
+
label: `Used service ${String(serviceId)}`,
|
|
4897
|
+
type: "custom",
|
|
4898
|
+
button: true,
|
|
4899
|
+
service_id: serviceId,
|
|
4900
|
+
pricing_role: "base"
|
|
4901
|
+
})),
|
|
4902
|
+
{
|
|
4903
|
+
id: candidateFieldId,
|
|
4904
|
+
label: `Candidate ${String(candidateId)}`,
|
|
4905
|
+
type: "custom",
|
|
4906
|
+
button: true,
|
|
4907
|
+
service_id: candidateId,
|
|
4908
|
+
pricing_role: "base"
|
|
4909
|
+
}
|
|
4910
|
+
];
|
|
4911
|
+
const fields = [...baseFields, ...syntheticFields];
|
|
4912
|
+
const visibleFieldIds = [
|
|
4913
|
+
...builder.visibleFields(tagId, selectedKeys),
|
|
4914
|
+
...syntheticFields.map((field) => field.id)
|
|
4915
|
+
];
|
|
4916
|
+
const anchoredFilters = ((_b = props.filters) != null ? _b : []).map(
|
|
4917
|
+
(tag) => tag.id === tagId && usedServiceIds[0] != null ? { ...tag, service_id: usedServiceIds[0] } : tag
|
|
4918
|
+
);
|
|
4919
|
+
const validationProps = {
|
|
4920
|
+
...props,
|
|
4921
|
+
filters: anchoredFilters,
|
|
4922
|
+
fields
|
|
4923
|
+
};
|
|
4924
|
+
const errors = [];
|
|
4925
|
+
const tags = (_c = validationProps.filters) != null ? _c : [];
|
|
4926
|
+
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
|
4927
|
+
const tagById = new Map(tags.map((tag) => [tag.id, tag]));
|
|
4928
|
+
const v = {
|
|
4929
|
+
props: validationProps,
|
|
4930
|
+
nodeMap: buildNodeMap(validationProps),
|
|
4931
|
+
options: {
|
|
4932
|
+
...(_d = builder.getOptions) == null ? void 0 : _d.call(builder),
|
|
4933
|
+
serviceMap,
|
|
4934
|
+
ratePolicy
|
|
4935
|
+
},
|
|
4936
|
+
errors,
|
|
4937
|
+
serviceMap,
|
|
4938
|
+
selectedKeys: new Set(selectedKeys),
|
|
4939
|
+
tags,
|
|
4940
|
+
fields,
|
|
4941
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
4942
|
+
tagById,
|
|
4943
|
+
fieldById,
|
|
4944
|
+
fieldsVisibleUnder: () => [],
|
|
4945
|
+
simulatedVisibilityContexts: []
|
|
4946
|
+
};
|
|
4947
|
+
validateRateCoherenceForVisibleContext({
|
|
4948
|
+
v,
|
|
4949
|
+
tagId,
|
|
4950
|
+
selectedKeys,
|
|
4951
|
+
visibleFieldIds,
|
|
4952
|
+
effectMap: buildTriggerEffectMap(validationProps),
|
|
4953
|
+
seen: /* @__PURE__ */ new Set()
|
|
4954
|
+
});
|
|
4955
|
+
return !errors.some(
|
|
4956
|
+
(error) => rateIssueAffectsCandidate(
|
|
4957
|
+
error,
|
|
4958
|
+
candidateId,
|
|
4959
|
+
candidateFieldId,
|
|
4960
|
+
usedServiceIds[0]
|
|
4961
|
+
)
|
|
4962
|
+
);
|
|
4963
|
+
}
|
|
4964
|
+
function syntheticServiceFieldId(kind, serviceId, index) {
|
|
4965
|
+
return `__service_filter_${kind}__:${index}:${String(serviceId)}`;
|
|
4966
|
+
}
|
|
4967
|
+
function rateIssueAffectsCandidate(error, candidateId, candidateFieldId, primaryAnchorId) {
|
|
4968
|
+
var _a, _b, _c, _d;
|
|
4969
|
+
if (error.code !== "rate_coherence_violation") return false;
|
|
4970
|
+
const candidateKey = String(candidateId);
|
|
4971
|
+
const details = (_a = error.details) != null ? _a : {};
|
|
4972
|
+
const anchorKey = primaryAnchorId == null ? void 0 : String(primaryAnchorId);
|
|
4973
|
+
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;
|
|
4974
|
+
if (primaryMatchesAnchor && ((_d = details.affectedServiceIds) == null ? void 0 : _d.some(
|
|
4975
|
+
(serviceId) => String(serviceId) === candidateKey
|
|
4976
|
+
))) {
|
|
4977
|
+
return true;
|
|
4978
|
+
}
|
|
4979
|
+
if (primaryMatchesAnchor && String(error.nodeId) === candidateFieldId) {
|
|
4980
|
+
return true;
|
|
4981
|
+
}
|
|
4982
|
+
return [details.primary, details.candidate].some((ref) => {
|
|
4983
|
+
if (!ref) return false;
|
|
4984
|
+
if (!primaryMatchesAnchor) return false;
|
|
4985
|
+
return String(ref.serviceId) === candidateKey || String(ref.service_id) === candidateKey || String(ref.fieldId) === candidateFieldId || String(ref.nodeId) === candidateFieldId;
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4779
4988
|
|
|
4780
4989
|
// src/utils/prune-fallbacks.ts
|
|
4781
4990
|
function pruneInvalidNodeFallbacks(props, services, settings) {
|
|
@@ -6506,7 +6715,7 @@ function setService(ctx, id, input) {
|
|
|
6506
6715
|
);
|
|
6507
6716
|
}
|
|
6508
6717
|
const isOptionBased2 = Array.isArray(f.options) && f.options.length > 0;
|
|
6509
|
-
const
|
|
6718
|
+
const isButton = !!f.button;
|
|
6510
6719
|
if (nextRole) {
|
|
6511
6720
|
f.pricing_role = nextRole;
|
|
6512
6721
|
}
|
|
@@ -6522,7 +6731,7 @@ function setService(ctx, id, input) {
|
|
|
6522
6731
|
if ("service_id" in f) delete f.service_id;
|
|
6523
6732
|
return;
|
|
6524
6733
|
}
|
|
6525
|
-
if (!
|
|
6734
|
+
if (!isButton) {
|
|
6526
6735
|
if (hasSidKey) {
|
|
6527
6736
|
ctx.api.emit("error", {
|
|
6528
6737
|
message: "Only button fields (without options) can have a service_id.",
|
|
@@ -7546,7 +7755,8 @@ function filterServicesForVisibleGroup2(ctx, candidates, input) {
|
|
|
7546
7755
|
policies: input.policies,
|
|
7547
7756
|
ratePolicy: input.ratePolicy,
|
|
7548
7757
|
fallbackSettings: input.fallbackSettings,
|
|
7549
|
-
fallback: input.fallback
|
|
7758
|
+
fallback: input.fallback,
|
|
7759
|
+
rateContext: input.rateContext
|
|
7550
7760
|
}
|
|
7551
7761
|
};
|
|
7552
7762
|
const result = filterServicesForVisibleGroup(coreInput, {
|