@timeax/digital-service-engine 0.2.6 → 0.2.8
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 +1327 -866
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +13 -1
- package/dist/core/index.d.ts +13 -1
- package/dist/core/index.js +1325 -866
- package/dist/core/index.js.map +1 -1
- package/dist/react/index.cjs +579 -379
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +579 -379
- package/dist/react/index.js.map +1 -1
- package/dist/workspace/index.cjs +588 -392
- package/dist/workspace/index.cjs.map +1 -1
- package/dist/workspace/index.js +588 -392
- package/dist/workspace/index.js.map +1 -1
- package/package.json +1 -1
package/dist/workspace/index.js
CHANGED
|
@@ -4078,13 +4078,7 @@ function resolveRootTags(tags) {
|
|
|
4078
4078
|
const roots = tags.filter((t) => !t.bind_id);
|
|
4079
4079
|
return roots.length ? roots : tags.slice(0, 1);
|
|
4080
4080
|
}
|
|
4081
|
-
function
|
|
4082
|
-
var _a, _b, _c, _d, _e, _f;
|
|
4083
|
-
const inc = (_a = v.props.includes_for_buttons) != null ? _a : {};
|
|
4084
|
-
const exc = (_b = v.props.excludes_for_buttons) != null ? _b : {};
|
|
4085
|
-
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;
|
|
4086
|
-
}
|
|
4087
|
-
function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectful) {
|
|
4081
|
+
function collectSelectableTriggersInContext(v, tagId, selectedKeys, effectfulKeys) {
|
|
4088
4082
|
var _a;
|
|
4089
4083
|
const visible = visibleFieldsUnder(v.props, tagId, {
|
|
4090
4084
|
selectedKeys
|
|
@@ -4093,11 +4087,11 @@ function collectSelectableTriggersInContext(v, tagId, selectedKeys, onlyEffectfu
|
|
|
4093
4087
|
for (const f of visible) {
|
|
4094
4088
|
if (f.button === true) {
|
|
4095
4089
|
const t = f.id;
|
|
4096
|
-
if (
|
|
4090
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
4097
4091
|
}
|
|
4098
4092
|
for (const o of (_a = f.options) != null ? _a : []) {
|
|
4099
|
-
const t =
|
|
4100
|
-
if (
|
|
4093
|
+
const t = o.id;
|
|
4094
|
+
if (effectfulKeys.has(t)) triggers.push(t);
|
|
4101
4095
|
}
|
|
4102
4096
|
}
|
|
4103
4097
|
triggers.sort();
|
|
@@ -4197,20 +4191,38 @@ function dedupeErrorsInPlace(v, startIndex) {
|
|
|
4197
4191
|
v.errors.splice(startIndex, v.errors.length - startIndex, ...kept);
|
|
4198
4192
|
}
|
|
4199
4193
|
function validateVisibility(v, options = {}) {
|
|
4200
|
-
var _a, _b, _c;
|
|
4194
|
+
var _a, _b, _c, _d, _e;
|
|
4195
|
+
v.simulatedVisibilityContexts = [];
|
|
4201
4196
|
const simulate = options.simulate === true;
|
|
4202
4197
|
if (!simulate) {
|
|
4203
4198
|
runVisibilityRulesOnce(v);
|
|
4199
|
+
for (const tag of v.tags) {
|
|
4200
|
+
v.simulatedVisibilityContexts.push({
|
|
4201
|
+
tagId: tag.id,
|
|
4202
|
+
selectedKeys: Array.from(v.selectedKeys),
|
|
4203
|
+
visibleFieldIds: v.fieldsVisibleUnder(tag.id).map((f) => f.id)
|
|
4204
|
+
});
|
|
4205
|
+
}
|
|
4204
4206
|
return;
|
|
4205
4207
|
}
|
|
4206
4208
|
const maxStates = Math.max(1, (_a = options.maxStates) != null ? _a : 500);
|
|
4207
4209
|
const maxDepth = Math.max(0, (_b = options.maxDepth) != null ? _b : 6);
|
|
4208
4210
|
const onlyEffectful = options.onlyEffectfulTriggers !== false;
|
|
4211
|
+
const effectfulKeys = /* @__PURE__ */ new Set();
|
|
4212
|
+
if (onlyEffectful) {
|
|
4213
|
+
for (const key of Object.keys((_c = v.props.includes_for_buttons) != null ? _c : {})) {
|
|
4214
|
+
effectfulKeys.add(key);
|
|
4215
|
+
}
|
|
4216
|
+
for (const key of Object.keys((_d = v.props.excludes_for_buttons) != null ? _d : {})) {
|
|
4217
|
+
effectfulKeys.add(key);
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4209
4220
|
const roots = resolveRootTags(v.tags);
|
|
4210
4221
|
const rootTags = options.simulateAllRoots ? roots : roots.slice(0, 1);
|
|
4211
|
-
const originalSelected = new Set((
|
|
4222
|
+
const originalSelected = new Set((_e = v.selectedKeys) != null ? _e : []);
|
|
4212
4223
|
const errorsStart = v.errors.length;
|
|
4213
4224
|
const visited = /* @__PURE__ */ new Set();
|
|
4225
|
+
const seenContexts = /* @__PURE__ */ new Set();
|
|
4214
4226
|
const stack = [];
|
|
4215
4227
|
for (const rt of rootTags) {
|
|
4216
4228
|
stack.push({
|
|
@@ -4223,10 +4235,27 @@ function validateVisibility(v, options = {}) {
|
|
|
4223
4235
|
while (stack.length) {
|
|
4224
4236
|
if (validatedStates >= maxStates) break;
|
|
4225
4237
|
const state = stack.pop();
|
|
4226
|
-
const sig = stableKeyOfSelection(state.selected)
|
|
4238
|
+
const sig = `${state.rootTagId}::${stableKeyOfSelection(state.selected)}`;
|
|
4227
4239
|
if (visited.has(sig)) continue;
|
|
4228
4240
|
visited.add(sig);
|
|
4229
4241
|
v.selectedKeys = state.selected;
|
|
4242
|
+
const visibleNow = visibleFieldsUnder(v.props, state.rootTagId, {
|
|
4243
|
+
selectedKeys: state.selected
|
|
4244
|
+
}).map((f) => f.id);
|
|
4245
|
+
const context = {
|
|
4246
|
+
tagId: state.rootTagId,
|
|
4247
|
+
selectedKeys: Array.from(state.selected),
|
|
4248
|
+
visibleFieldIds: visibleNow
|
|
4249
|
+
};
|
|
4250
|
+
const contextKey = [
|
|
4251
|
+
context.tagId,
|
|
4252
|
+
[...context.selectedKeys].sort().join("|"),
|
|
4253
|
+
[...context.visibleFieldIds].sort().join("|")
|
|
4254
|
+
].join("::");
|
|
4255
|
+
if (!seenContexts.has(contextKey)) {
|
|
4256
|
+
seenContexts.add(contextKey);
|
|
4257
|
+
v.simulatedVisibilityContexts.push(context);
|
|
4258
|
+
}
|
|
4230
4259
|
validatedStates++;
|
|
4231
4260
|
runVisibilityRulesOnce(v);
|
|
4232
4261
|
if (state.depth >= maxDepth) continue;
|
|
@@ -4234,7 +4263,7 @@ function validateVisibility(v, options = {}) {
|
|
|
4234
4263
|
v,
|
|
4235
4264
|
state.rootTagId,
|
|
4236
4265
|
state.selected,
|
|
4237
|
-
|
|
4266
|
+
effectfulKeys
|
|
4238
4267
|
);
|
|
4239
4268
|
for (let i = triggers.length - 1; i >= 0; i--) {
|
|
4240
4269
|
const trig = triggers[i];
|
|
@@ -4476,8 +4505,8 @@ function validateOptionMaps(v) {
|
|
|
4476
4505
|
};
|
|
4477
4506
|
}
|
|
4478
4507
|
if (ref.kind === "field") {
|
|
4479
|
-
const
|
|
4480
|
-
if (!
|
|
4508
|
+
const isButton = ref.node.button === true;
|
|
4509
|
+
if (!isButton)
|
|
4481
4510
|
return { ok: false, nodeId: ref.id, affected: [ref.id] };
|
|
4482
4511
|
return { ok: true, nodeId: ref.id, affected: [ref.id] };
|
|
4483
4512
|
}
|
|
@@ -4658,10 +4687,19 @@ function validateOrderKinds(v) {
|
|
|
4658
4687
|
}
|
|
4659
4688
|
|
|
4660
4689
|
// src/core/validate/steps/service-vs-input.ts
|
|
4690
|
+
function hasButtonTriggerMap(v, fieldId) {
|
|
4691
|
+
var _a, _b;
|
|
4692
|
+
const includes = (_a = v.props.includes_for_buttons) == null ? void 0 : _a[fieldId];
|
|
4693
|
+
const excludes = (_b = v.props.excludes_for_buttons) == null ? void 0 : _b[fieldId];
|
|
4694
|
+
return Array.isArray(includes) && includes.length > 0 || Array.isArray(excludes) && excludes.length > 0;
|
|
4695
|
+
}
|
|
4661
4696
|
function validateServiceVsUserInput(v) {
|
|
4662
4697
|
for (const f of v.fields) {
|
|
4663
4698
|
const anySvc = hasAnyServiceOption(f);
|
|
4664
4699
|
const hasName = !!(f.name && f.name.trim());
|
|
4700
|
+
const isButton = f.button === true;
|
|
4701
|
+
const hasFieldService = f.service_id !== void 0 && f.service_id !== null;
|
|
4702
|
+
const hasTriggerMap = isButton && hasButtonTriggerMap(v, f.id);
|
|
4665
4703
|
if (f.type === "custom" && anySvc) {
|
|
4666
4704
|
v.errors.push({
|
|
4667
4705
|
code: "user_input_field_has_service_option",
|
|
@@ -4672,14 +4710,15 @@ function validateServiceVsUserInput(v) {
|
|
|
4672
4710
|
});
|
|
4673
4711
|
}
|
|
4674
4712
|
if (!hasName) {
|
|
4675
|
-
if (
|
|
4676
|
-
|
|
4677
|
-
code: "service_field_missing_service_id",
|
|
4678
|
-
severity: "error",
|
|
4679
|
-
message: `Service-backed field "${f.id}" has no "name" and must provide at least one option with a service_id.`,
|
|
4680
|
-
nodeId: f.id
|
|
4681
|
-
});
|
|
4713
|
+
if (hasFieldService || anySvc || hasTriggerMap) {
|
|
4714
|
+
continue;
|
|
4682
4715
|
}
|
|
4716
|
+
v.errors.push({
|
|
4717
|
+
code: "service_field_missing_service_id",
|
|
4718
|
+
severity: "error",
|
|
4719
|
+
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.`,
|
|
4720
|
+
nodeId: f.id
|
|
4721
|
+
});
|
|
4683
4722
|
} else {
|
|
4684
4723
|
if (anySvc) {
|
|
4685
4724
|
v.errors.push({
|
|
@@ -4855,15 +4894,6 @@ function passesRatePolicy(policy, primaryRate, candidateRate) {
|
|
|
4855
4894
|
return candidateRate <= primaryRate * (1 - rp.pct / 100);
|
|
4856
4895
|
}
|
|
4857
4896
|
}
|
|
4858
|
-
function rateOk(svcMap, candidate, primary, policy) {
|
|
4859
|
-
const cand = getServiceCapability(svcMap, candidate);
|
|
4860
|
-
const prim = getServiceCapability(svcMap, primary);
|
|
4861
|
-
if (!cand || !prim) return false;
|
|
4862
|
-
const cRate = toFiniteNumber(cand.rate);
|
|
4863
|
-
const pRate = toFiniteNumber(prim.rate);
|
|
4864
|
-
if (!Number.isFinite(cRate) || !Number.isFinite(pRate)) return false;
|
|
4865
|
-
return passesRatePolicy(policy.ratePolicy, pRate, cRate);
|
|
4866
|
-
}
|
|
4867
4897
|
function getServiceCapabilityEntry(svcMap, candidate) {
|
|
4868
4898
|
if (candidate === void 0 || candidate === null) return void 0;
|
|
4869
4899
|
const direct = svcMap[candidate];
|
|
@@ -4991,6 +5021,324 @@ function validateRates(v) {
|
|
|
4991
5021
|
}
|
|
4992
5022
|
}
|
|
4993
5023
|
|
|
5024
|
+
// src/core/rate-coherence.ts
|
|
5025
|
+
function buildTriggerEffectMap(props) {
|
|
5026
|
+
var _a, _b;
|
|
5027
|
+
const map = /* @__PURE__ */ new Map();
|
|
5028
|
+
const ensure = (key) => {
|
|
5029
|
+
let item = map.get(key);
|
|
5030
|
+
if (!item) {
|
|
5031
|
+
item = { includes: /* @__PURE__ */ new Set(), excludes: /* @__PURE__ */ new Set() };
|
|
5032
|
+
map.set(key, item);
|
|
5033
|
+
}
|
|
5034
|
+
return item;
|
|
5035
|
+
};
|
|
5036
|
+
for (const [key, ids] of Object.entries((_a = props.includes_for_buttons) != null ? _a : {})) {
|
|
5037
|
+
const item = ensure(key);
|
|
5038
|
+
for (const id of ids != null ? ids : []) item.includes.add(id);
|
|
5039
|
+
}
|
|
5040
|
+
for (const [key, ids] of Object.entries((_b = props.excludes_for_buttons) != null ? _b : {})) {
|
|
5041
|
+
const item = ensure(key);
|
|
5042
|
+
for (const id of ids != null ? ids : []) item.excludes.add(id);
|
|
5043
|
+
}
|
|
5044
|
+
return map;
|
|
5045
|
+
}
|
|
5046
|
+
function isRefExcludedBySelectedKeys(ref, selectedKeys, effectMap) {
|
|
5047
|
+
for (const key of selectedKeys) {
|
|
5048
|
+
const effects = effectMap.get(key);
|
|
5049
|
+
if (!effects) continue;
|
|
5050
|
+
if (ref.fieldId && effects.excludes.has(ref.fieldId) || effects.excludes.has(ref.nodeId)) {
|
|
5051
|
+
return true;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
return false;
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
// src/core/validate/steps/rate-coherence.ts
|
|
5058
|
+
function normalizeRole(role, fallback) {
|
|
5059
|
+
return role === "base" || role === "utility" ? role : fallback;
|
|
5060
|
+
}
|
|
5061
|
+
function uniqueStrings(values) {
|
|
5062
|
+
const out = /* @__PURE__ */ new Set();
|
|
5063
|
+
for (const value of values) {
|
|
5064
|
+
if (!value) continue;
|
|
5065
|
+
out.add(value);
|
|
5066
|
+
}
|
|
5067
|
+
return Array.from(out);
|
|
5068
|
+
}
|
|
5069
|
+
function getRate(serviceMap, serviceId) {
|
|
5070
|
+
const cap = getServiceCapability(serviceMap, serviceId);
|
|
5071
|
+
const rate = cap == null ? void 0 : cap.rate;
|
|
5072
|
+
if (typeof rate !== "number" || !Number.isFinite(rate)) return void 0;
|
|
5073
|
+
return rate;
|
|
5074
|
+
}
|
|
5075
|
+
function collectContextRefs(tag, visibleFields, serviceMap) {
|
|
5076
|
+
var _a, _b, _c, _d, _e;
|
|
5077
|
+
const serviceRefs = [];
|
|
5078
|
+
let tagDefault;
|
|
5079
|
+
if (tag.service_id !== void 0 && tag.service_id !== null) {
|
|
5080
|
+
const tagRate = getRate(serviceMap, tag.service_id);
|
|
5081
|
+
if (tagRate != null) {
|
|
5082
|
+
tagDefault = {
|
|
5083
|
+
key: tag.id,
|
|
5084
|
+
nodeId: tag.id,
|
|
5085
|
+
nodeKind: "tag",
|
|
5086
|
+
serviceId: tag.service_id,
|
|
5087
|
+
rate: tagRate,
|
|
5088
|
+
label: (_a = tag.label) != null ? _a : tag.id,
|
|
5089
|
+
pricingRole: "base"
|
|
5090
|
+
};
|
|
5091
|
+
}
|
|
5092
|
+
}
|
|
5093
|
+
for (const field of visibleFields) {
|
|
5094
|
+
const fieldRole = normalizeRole(field.pricing_role, "base");
|
|
5095
|
+
if (field.service_id !== void 0 && field.service_id !== null) {
|
|
5096
|
+
const rate = getRate(serviceMap, field.service_id);
|
|
5097
|
+
if (rate != null) {
|
|
5098
|
+
serviceRefs.push({
|
|
5099
|
+
key: field.id,
|
|
5100
|
+
nodeId: field.id,
|
|
5101
|
+
fieldId: field.id,
|
|
5102
|
+
nodeKind: "button",
|
|
5103
|
+
serviceId: field.service_id,
|
|
5104
|
+
rate,
|
|
5105
|
+
label: (_b = field.label) != null ? _b : field.id,
|
|
5106
|
+
pricingRole: fieldRole
|
|
5107
|
+
});
|
|
5108
|
+
}
|
|
5109
|
+
}
|
|
5110
|
+
for (const option of (_c = field.options) != null ? _c : []) {
|
|
5111
|
+
if (option.service_id === void 0 || option.service_id === null) continue;
|
|
5112
|
+
const rate = getRate(serviceMap, option.service_id);
|
|
5113
|
+
if (rate == null) continue;
|
|
5114
|
+
serviceRefs.push({
|
|
5115
|
+
key: option.id,
|
|
5116
|
+
nodeId: option.id,
|
|
5117
|
+
fieldId: field.id,
|
|
5118
|
+
nodeKind: "option",
|
|
5119
|
+
serviceId: option.service_id,
|
|
5120
|
+
rate,
|
|
5121
|
+
label: (_d = option.label) != null ? _d : option.id,
|
|
5122
|
+
pricingRole: normalizeRole((_e = option.pricing_role) != null ? _e : field.pricing_role, "base")
|
|
5123
|
+
});
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
return { tagDefault, serviceRefs };
|
|
5127
|
+
}
|
|
5128
|
+
function pickHighestRatePrimary(refs) {
|
|
5129
|
+
return refs.reduce((best, cur) => {
|
|
5130
|
+
if (!best) return cur;
|
|
5131
|
+
if (cur.rate > best.rate) return cur;
|
|
5132
|
+
if (cur.rate < best.rate) return best;
|
|
5133
|
+
return cur.nodeId < best.nodeId ? cur : best;
|
|
5134
|
+
}, void 0);
|
|
5135
|
+
}
|
|
5136
|
+
function validateRateCoherenceForVisibleContext(params) {
|
|
5137
|
+
const { v, tagId, selectedKeys, visibleFieldIds, effectMap, seen } = params;
|
|
5138
|
+
const tag = v.tagById.get(tagId);
|
|
5139
|
+
if (!tag) return;
|
|
5140
|
+
const visibleFields = visibleFieldIds.map((id) => v.fieldById.get(id)).filter(Boolean);
|
|
5141
|
+
const { tagDefault, serviceRefs: allServiceRefs } = collectContextRefs(
|
|
5142
|
+
tag,
|
|
5143
|
+
visibleFields,
|
|
5144
|
+
v.serviceMap
|
|
5145
|
+
);
|
|
5146
|
+
const baseRefs = allServiceRefs.filter((ref) => ref.pricingRole === "base");
|
|
5147
|
+
if (baseRefs.length === 0 && !tagDefault) return;
|
|
5148
|
+
const ratePolicy = normalizeRatePolicy(v.options.ratePolicy);
|
|
5149
|
+
const visibleInvalidFieldIds = visibleFieldIds.filter(
|
|
5150
|
+
(fieldId) => v.invalidRateFieldIds.has(fieldId)
|
|
5151
|
+
);
|
|
5152
|
+
for (const fieldId of visibleInvalidFieldIds) {
|
|
5153
|
+
const internalKey = [
|
|
5154
|
+
"rate-coherence-internal",
|
|
5155
|
+
tagId,
|
|
5156
|
+
[...selectedKeys].sort().join("|"),
|
|
5157
|
+
fieldId
|
|
5158
|
+
].join("::");
|
|
5159
|
+
if (seen.has(internalKey)) continue;
|
|
5160
|
+
seen.add(internalKey);
|
|
5161
|
+
v.errors.push({
|
|
5162
|
+
code: "rate_coherence_violation",
|
|
5163
|
+
severity: "error",
|
|
5164
|
+
nodeId: fieldId,
|
|
5165
|
+
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
5166
|
+
details: {
|
|
5167
|
+
kind: "internal_field",
|
|
5168
|
+
tagId,
|
|
5169
|
+
selectedKeys: [...selectedKeys],
|
|
5170
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
5171
|
+
fieldId,
|
|
5172
|
+
invalidFieldIds: [fieldId],
|
|
5173
|
+
affectedIds: uniqueStrings([tagId, ...selectedKeys, fieldId])
|
|
5174
|
+
}
|
|
5175
|
+
});
|
|
5176
|
+
}
|
|
5177
|
+
const selectedSet = new Set(selectedKeys);
|
|
5178
|
+
const selectedServiceRefs = baseRefs.filter((ref) => selectedSet.has(ref.key));
|
|
5179
|
+
if (baseRefs.length === 0) return;
|
|
5180
|
+
for (let i = 0; i < baseRefs.length; i++) {
|
|
5181
|
+
for (let j = i + 1; j < baseRefs.length; j++) {
|
|
5182
|
+
const left = baseRefs[i];
|
|
5183
|
+
const right = baseRefs[j];
|
|
5184
|
+
const hypotheticalKeys = [...selectedKeys, left.key, right.key];
|
|
5185
|
+
const survivingRefs = baseRefs.filter(
|
|
5186
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
5187
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
5188
|
+
hypotheticalKeys,
|
|
5189
|
+
effectMap
|
|
5190
|
+
)
|
|
5191
|
+
);
|
|
5192
|
+
const survivingSet = new Set(survivingRefs.map((ref) => ref.nodeId));
|
|
5193
|
+
if (!survivingSet.has(left.nodeId) || !survivingSet.has(right.nodeId)) {
|
|
5194
|
+
continue;
|
|
5195
|
+
}
|
|
5196
|
+
if (survivingRefs.length <= 1) continue;
|
|
5197
|
+
const survivingSelected = survivingRefs.filter(
|
|
5198
|
+
(ref) => selectedSet.has(ref.key)
|
|
5199
|
+
);
|
|
5200
|
+
const tagIsCompeting = survivingSelected.length === 0;
|
|
5201
|
+
const primary = pickHighestRatePrimary(survivingRefs);
|
|
5202
|
+
if (!primary) continue;
|
|
5203
|
+
const comparePool = survivingRefs.filter((ref) => ref.nodeId !== primary.nodeId);
|
|
5204
|
+
for (const candidate of comparePool) {
|
|
5205
|
+
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) continue;
|
|
5206
|
+
const issueKey = [
|
|
5207
|
+
"rate-coherence-context",
|
|
5208
|
+
tagId,
|
|
5209
|
+
[...selectedKeys].sort().join("|"),
|
|
5210
|
+
[...survivingRefs.map((r) => r.nodeId).sort()].join("|"),
|
|
5211
|
+
primary.nodeId,
|
|
5212
|
+
candidate.nodeId,
|
|
5213
|
+
ratePolicy.kind,
|
|
5214
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
5215
|
+
].join("::");
|
|
5216
|
+
if (seen.has(issueKey)) continue;
|
|
5217
|
+
seen.add(issueKey);
|
|
5218
|
+
v.errors.push({
|
|
5219
|
+
code: "rate_coherence_violation",
|
|
5220
|
+
severity: "error",
|
|
5221
|
+
nodeId: candidate.nodeId,
|
|
5222
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
5223
|
+
details: {
|
|
5224
|
+
kind: "selected_context",
|
|
5225
|
+
tagId,
|
|
5226
|
+
selectedKeys: [...selectedKeys],
|
|
5227
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
5228
|
+
primary: {
|
|
5229
|
+
nodeId: primary.nodeId,
|
|
5230
|
+
fieldId: primary.fieldId,
|
|
5231
|
+
service_id: primary.serviceId,
|
|
5232
|
+
serviceId: primary.serviceId,
|
|
5233
|
+
rate: primary.rate
|
|
5234
|
+
},
|
|
5235
|
+
candidate: {
|
|
5236
|
+
nodeId: candidate.nodeId,
|
|
5237
|
+
fieldId: candidate.fieldId,
|
|
5238
|
+
service_id: candidate.serviceId,
|
|
5239
|
+
serviceId: candidate.serviceId,
|
|
5240
|
+
rate: candidate.rate
|
|
5241
|
+
},
|
|
5242
|
+
policy: ratePolicy.kind,
|
|
5243
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
5244
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
5245
|
+
affectedIds: uniqueStrings([
|
|
5246
|
+
tagId,
|
|
5247
|
+
...selectedKeys,
|
|
5248
|
+
primary.nodeId,
|
|
5249
|
+
primary.fieldId,
|
|
5250
|
+
candidate.nodeId,
|
|
5251
|
+
candidate.fieldId,
|
|
5252
|
+
tagIsCompeting ? tagDefault == null ? void 0 : tagDefault.nodeId : void 0
|
|
5253
|
+
]),
|
|
5254
|
+
affectedServiceIds: uniqueStrings([
|
|
5255
|
+
String(primary.serviceId),
|
|
5256
|
+
String(candidate.serviceId)
|
|
5257
|
+
])
|
|
5258
|
+
}
|
|
5259
|
+
});
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
if (selectedServiceRefs.length === 0 && tagDefault && baseRefs.length > 0) {
|
|
5264
|
+
const survivingByDefault = baseRefs.filter(
|
|
5265
|
+
(ref) => !isRefExcludedBySelectedKeys(
|
|
5266
|
+
{ fieldId: ref.fieldId, nodeId: ref.nodeId },
|
|
5267
|
+
selectedKeys,
|
|
5268
|
+
effectMap
|
|
5269
|
+
)
|
|
5270
|
+
);
|
|
5271
|
+
for (const candidate of survivingByDefault) {
|
|
5272
|
+
if (passesRatePolicy(ratePolicy, tagDefault.rate, candidate.rate)) continue;
|
|
5273
|
+
const issueKey = [
|
|
5274
|
+
"rate-coherence-default",
|
|
5275
|
+
tagId,
|
|
5276
|
+
[...selectedKeys].sort().join("|"),
|
|
5277
|
+
tagDefault.nodeId,
|
|
5278
|
+
candidate.nodeId,
|
|
5279
|
+
ratePolicy.kind,
|
|
5280
|
+
"pct" in ratePolicy ? String(ratePolicy.pct) : ""
|
|
5281
|
+
].join("::");
|
|
5282
|
+
if (seen.has(issueKey)) continue;
|
|
5283
|
+
seen.add(issueKey);
|
|
5284
|
+
v.errors.push({
|
|
5285
|
+
code: "rate_coherence_violation",
|
|
5286
|
+
severity: "error",
|
|
5287
|
+
nodeId: candidate.nodeId,
|
|
5288
|
+
message: "Visible service context contains incompatible base service rates.",
|
|
5289
|
+
details: {
|
|
5290
|
+
kind: "selected_context",
|
|
5291
|
+
tagId,
|
|
5292
|
+
selectedKeys: [...selectedKeys],
|
|
5293
|
+
visibleFieldIds: [...visibleFieldIds],
|
|
5294
|
+
primary: {
|
|
5295
|
+
nodeId: tagDefault.nodeId,
|
|
5296
|
+
service_id: tagDefault.serviceId,
|
|
5297
|
+
serviceId: tagDefault.serviceId,
|
|
5298
|
+
rate: tagDefault.rate
|
|
5299
|
+
},
|
|
5300
|
+
candidate: {
|
|
5301
|
+
nodeId: candidate.nodeId,
|
|
5302
|
+
fieldId: candidate.fieldId,
|
|
5303
|
+
service_id: candidate.serviceId,
|
|
5304
|
+
serviceId: candidate.serviceId,
|
|
5305
|
+
rate: candidate.rate
|
|
5306
|
+
},
|
|
5307
|
+
policy: ratePolicy.kind,
|
|
5308
|
+
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
5309
|
+
invalidFieldIds: visibleInvalidFieldIds,
|
|
5310
|
+
affectedIds: uniqueStrings([
|
|
5311
|
+
tagId,
|
|
5312
|
+
...selectedKeys,
|
|
5313
|
+
tagDefault.nodeId,
|
|
5314
|
+
candidate.nodeId,
|
|
5315
|
+
candidate.fieldId
|
|
5316
|
+
]),
|
|
5317
|
+
affectedServiceIds: uniqueStrings([
|
|
5318
|
+
String(tagDefault.serviceId),
|
|
5319
|
+
String(candidate.serviceId)
|
|
5320
|
+
])
|
|
5321
|
+
}
|
|
5322
|
+
});
|
|
5323
|
+
}
|
|
5324
|
+
}
|
|
5325
|
+
}
|
|
5326
|
+
function validateRateCoherence(v) {
|
|
5327
|
+
if (Object.keys(v.serviceMap).length === 0 || v.tags.length === 0) return;
|
|
5328
|
+
const effectMap = buildTriggerEffectMap(v.props);
|
|
5329
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5330
|
+
for (const context of v.simulatedVisibilityContexts) {
|
|
5331
|
+
validateRateCoherenceForVisibleContext({
|
|
5332
|
+
v,
|
|
5333
|
+
tagId: context.tagId,
|
|
5334
|
+
selectedKeys: context.selectedKeys,
|
|
5335
|
+
visibleFieldIds: context.visibleFieldIds,
|
|
5336
|
+
effectMap,
|
|
5337
|
+
seen
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
|
|
4994
5342
|
// src/core/validate/steps/constraints.ts
|
|
4995
5343
|
function constraintKeysInChain(v, tagId) {
|
|
4996
5344
|
const keys = [];
|
|
@@ -5747,6 +6095,84 @@ function mergeValidatorOptions(defaults = {}, overrides = {}) {
|
|
|
5747
6095
|
};
|
|
5748
6096
|
}
|
|
5749
6097
|
|
|
6098
|
+
// src/core/validate/index.ts
|
|
6099
|
+
function readVisibilitySimOpts(ctx) {
|
|
6100
|
+
const c = ctx;
|
|
6101
|
+
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
6102
|
+
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
6103
|
+
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
6104
|
+
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
6105
|
+
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
6106
|
+
return {
|
|
6107
|
+
simulate,
|
|
6108
|
+
maxStates,
|
|
6109
|
+
maxDepth,
|
|
6110
|
+
simulateAllRoots,
|
|
6111
|
+
onlyEffectfulTriggers
|
|
6112
|
+
};
|
|
6113
|
+
}
|
|
6114
|
+
function validate(props, ctx = {}) {
|
|
6115
|
+
var _a, _b, _c;
|
|
6116
|
+
const options = mergeValidatorOptions({}, ctx);
|
|
6117
|
+
const fallbackSettings = resolveFallbackSettings(options);
|
|
6118
|
+
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
6119
|
+
const errors = [];
|
|
6120
|
+
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
6121
|
+
const selectedKeys = new Set(
|
|
6122
|
+
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
6123
|
+
);
|
|
6124
|
+
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
6125
|
+
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
6126
|
+
const tagById = /* @__PURE__ */ new Map();
|
|
6127
|
+
const fieldById = /* @__PURE__ */ new Map();
|
|
6128
|
+
for (const t of tags) tagById.set(t.id, t);
|
|
6129
|
+
for (const f of fields) fieldById.set(f.id, f);
|
|
6130
|
+
const v = {
|
|
6131
|
+
props,
|
|
6132
|
+
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
6133
|
+
options: {
|
|
6134
|
+
...options,
|
|
6135
|
+
ratePolicy,
|
|
6136
|
+
fallbackSettings
|
|
6137
|
+
},
|
|
6138
|
+
errors,
|
|
6139
|
+
serviceMap,
|
|
6140
|
+
selectedKeys,
|
|
6141
|
+
tags,
|
|
6142
|
+
fields,
|
|
6143
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
6144
|
+
tagById,
|
|
6145
|
+
fieldById,
|
|
6146
|
+
fieldsVisibleUnder: (_tagId) => [],
|
|
6147
|
+
simulatedVisibilityContexts: []
|
|
6148
|
+
};
|
|
6149
|
+
validateStructure(v);
|
|
6150
|
+
validateIdentity(v);
|
|
6151
|
+
validateOptionMaps(v);
|
|
6152
|
+
validateOrderKinds(v);
|
|
6153
|
+
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
6154
|
+
const visSim = readVisibilitySimOpts(options);
|
|
6155
|
+
validateVisibility(v, visSim);
|
|
6156
|
+
applyPolicies(
|
|
6157
|
+
v.errors,
|
|
6158
|
+
v.props,
|
|
6159
|
+
v.serviceMap,
|
|
6160
|
+
v.options.policies,
|
|
6161
|
+
v.fieldsVisibleUnder,
|
|
6162
|
+
v.tags
|
|
6163
|
+
);
|
|
6164
|
+
validateServiceVsUserInput(v);
|
|
6165
|
+
validateUtilityMarkers(v);
|
|
6166
|
+
validateRates(v);
|
|
6167
|
+
validateRateCoherence(v);
|
|
6168
|
+
validateConstraints(v);
|
|
6169
|
+
validateCustomFields(v);
|
|
6170
|
+
validateGlobalUtilityGuard(v);
|
|
6171
|
+
validateUnboundFields(v);
|
|
6172
|
+
validateFallbacks(v);
|
|
6173
|
+
return v.errors;
|
|
6174
|
+
}
|
|
6175
|
+
|
|
5750
6176
|
// src/core/builder.ts
|
|
5751
6177
|
import { cloneDeep as cloneDeep2 } from "lodash-es";
|
|
5752
6178
|
function createBuilder(opts = {}) {
|
|
@@ -6029,344 +6455,6 @@ function toStringSet(v) {
|
|
|
6029
6455
|
return new Set(v.map(String));
|
|
6030
6456
|
}
|
|
6031
6457
|
|
|
6032
|
-
// src/core/rate-coherence.ts
|
|
6033
|
-
function validateRateCoherenceDeep(params) {
|
|
6034
|
-
var _a, _b, _c;
|
|
6035
|
-
const { builder, services, tagId } = params;
|
|
6036
|
-
const ratePolicy = normalizeRatePolicy(params.ratePolicy);
|
|
6037
|
-
const props = builder.getProps();
|
|
6038
|
-
const invalidFieldIds = new Set((_a = params.invalidFieldIds) != null ? _a : []);
|
|
6039
|
-
const fields = (_b = props.fields) != null ? _b : [];
|
|
6040
|
-
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
6041
|
-
const tagById = new Map(((_c = props.filters) != null ? _c : []).map((t) => [t.id, t]));
|
|
6042
|
-
const tag = tagById.get(tagId);
|
|
6043
|
-
const baselineFieldIds = builder.visibleFields(tagId, []);
|
|
6044
|
-
const baselineFields = baselineFieldIds.map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
6045
|
-
const anchors = collectAnchors(baselineFields);
|
|
6046
|
-
const diagnostics = [];
|
|
6047
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6048
|
-
for (const anchor of anchors) {
|
|
6049
|
-
const selectedKeys = anchor.kind === "option" ? [`${anchor.fieldId}::${anchor.id}`] : [anchor.fieldId];
|
|
6050
|
-
const visibleFields = builder.visibleFields(tagId, selectedKeys).map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
6051
|
-
const visibleInvalidFieldIds = visibleFields.map((field) => field.id).filter((fieldId) => invalidFieldIds.has(fieldId));
|
|
6052
|
-
for (const fieldId of visibleInvalidFieldIds) {
|
|
6053
|
-
const key = `internal|${tagId}|${fieldId}`;
|
|
6054
|
-
if (seen.has(key)) continue;
|
|
6055
|
-
seen.add(key);
|
|
6056
|
-
diagnostics.push({
|
|
6057
|
-
kind: "internal_field",
|
|
6058
|
-
scope: "visible_group",
|
|
6059
|
-
tagId,
|
|
6060
|
-
fieldId,
|
|
6061
|
-
nodeId: fieldId,
|
|
6062
|
-
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
6063
|
-
simulationAnchor: {
|
|
6064
|
-
kind: anchor.kind,
|
|
6065
|
-
id: anchor.id,
|
|
6066
|
-
fieldId: anchor.fieldId,
|
|
6067
|
-
label: anchor.label
|
|
6068
|
-
},
|
|
6069
|
-
invalidFieldIds: [fieldId]
|
|
6070
|
-
});
|
|
6071
|
-
}
|
|
6072
|
-
const references = visibleFields.flatMap(
|
|
6073
|
-
(field) => collectFieldReferences(field, services)
|
|
6074
|
-
);
|
|
6075
|
-
if (references.length <= 1) continue;
|
|
6076
|
-
const primary = references.reduce((best, current) => {
|
|
6077
|
-
if (current.rate !== best.rate) {
|
|
6078
|
-
return current.rate > best.rate ? current : best;
|
|
6079
|
-
}
|
|
6080
|
-
const bestKey = `${best.fieldId}|${best.nodeId}`;
|
|
6081
|
-
const currentKey = `${current.fieldId}|${current.nodeId}`;
|
|
6082
|
-
return currentKey < bestKey ? current : best;
|
|
6083
|
-
});
|
|
6084
|
-
for (const candidate of references) {
|
|
6085
|
-
if (candidate.nodeId === primary.nodeId) continue;
|
|
6086
|
-
if (candidate.fieldId === primary.fieldId) continue;
|
|
6087
|
-
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) {
|
|
6088
|
-
continue;
|
|
6089
|
-
}
|
|
6090
|
-
const key = contextualKey(tagId, primary, candidate, ratePolicy);
|
|
6091
|
-
if (seen.has(key)) continue;
|
|
6092
|
-
seen.add(key);
|
|
6093
|
-
diagnostics.push({
|
|
6094
|
-
kind: "contextual",
|
|
6095
|
-
scope: "visible_group",
|
|
6096
|
-
tagId,
|
|
6097
|
-
nodeId: candidate.nodeId,
|
|
6098
|
-
primary: toDiagnosticRef(primary),
|
|
6099
|
-
offender: toDiagnosticRef(candidate),
|
|
6100
|
-
policy: ratePolicy.kind,
|
|
6101
|
-
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
6102
|
-
message: explainRateMismatch(
|
|
6103
|
-
ratePolicy,
|
|
6104
|
-
primary,
|
|
6105
|
-
candidate,
|
|
6106
|
-
describeLabel(tag)
|
|
6107
|
-
),
|
|
6108
|
-
simulationAnchor: {
|
|
6109
|
-
kind: anchor.kind,
|
|
6110
|
-
id: anchor.id,
|
|
6111
|
-
fieldId: anchor.fieldId,
|
|
6112
|
-
label: anchor.label
|
|
6113
|
-
},
|
|
6114
|
-
invalidFieldIds: visibleInvalidFieldIds
|
|
6115
|
-
});
|
|
6116
|
-
}
|
|
6117
|
-
}
|
|
6118
|
-
return diagnostics;
|
|
6119
|
-
}
|
|
6120
|
-
function collectAnchors(fields) {
|
|
6121
|
-
var _a, _b;
|
|
6122
|
-
const anchors = [];
|
|
6123
|
-
for (const field of fields) {
|
|
6124
|
-
if (!isButton(field)) continue;
|
|
6125
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
6126
|
-
for (const option of field.options) {
|
|
6127
|
-
anchors.push({
|
|
6128
|
-
kind: "option",
|
|
6129
|
-
id: option.id,
|
|
6130
|
-
fieldId: field.id,
|
|
6131
|
-
label: (_a = option.label) != null ? _a : option.id
|
|
6132
|
-
});
|
|
6133
|
-
}
|
|
6134
|
-
continue;
|
|
6135
|
-
}
|
|
6136
|
-
anchors.push({
|
|
6137
|
-
kind: "field",
|
|
6138
|
-
id: field.id,
|
|
6139
|
-
fieldId: field.id,
|
|
6140
|
-
label: (_b = field.label) != null ? _b : field.id
|
|
6141
|
-
});
|
|
6142
|
-
}
|
|
6143
|
-
return anchors;
|
|
6144
|
-
}
|
|
6145
|
-
function collectFieldReferences(field, services) {
|
|
6146
|
-
var _a;
|
|
6147
|
-
const members = collectBaseMembers(field, services);
|
|
6148
|
-
if (members.length === 0) return [];
|
|
6149
|
-
if (isMultiField(field)) {
|
|
6150
|
-
const averageRate = members.reduce((sum, member) => sum + member.rate, 0) / members.length;
|
|
6151
|
-
return [
|
|
6152
|
-
{
|
|
6153
|
-
refKind: "multi",
|
|
6154
|
-
nodeId: field.id,
|
|
6155
|
-
fieldId: field.id,
|
|
6156
|
-
label: (_a = field.label) != null ? _a : field.id,
|
|
6157
|
-
rate: averageRate,
|
|
6158
|
-
members
|
|
6159
|
-
}
|
|
6160
|
-
];
|
|
6161
|
-
}
|
|
6162
|
-
return members.map((member) => ({
|
|
6163
|
-
refKind: "single",
|
|
6164
|
-
nodeId: member.id,
|
|
6165
|
-
fieldId: field.id,
|
|
6166
|
-
label: member.label,
|
|
6167
|
-
rate: member.rate,
|
|
6168
|
-
service_id: member.service_id,
|
|
6169
|
-
members: [member]
|
|
6170
|
-
}));
|
|
6171
|
-
}
|
|
6172
|
-
function collectBaseMembers(field, services) {
|
|
6173
|
-
var _a, _b, _c;
|
|
6174
|
-
const members = [];
|
|
6175
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
6176
|
-
for (const option of field.options) {
|
|
6177
|
-
const role2 = normalizeRole((_a = option.pricing_role) != null ? _a : field.pricing_role, "base");
|
|
6178
|
-
if (role2 !== "base") continue;
|
|
6179
|
-
if (option.service_id === void 0 || option.service_id === null) {
|
|
6180
|
-
continue;
|
|
6181
|
-
}
|
|
6182
|
-
const cap2 = getServiceCapability(services, option.service_id);
|
|
6183
|
-
if (!cap2 || typeof cap2.rate !== "number" || !Number.isFinite(cap2.rate)) {
|
|
6184
|
-
continue;
|
|
6185
|
-
}
|
|
6186
|
-
members.push({
|
|
6187
|
-
kind: "option",
|
|
6188
|
-
id: option.id,
|
|
6189
|
-
fieldId: field.id,
|
|
6190
|
-
label: (_b = option.label) != null ? _b : option.id,
|
|
6191
|
-
service_id: option.service_id,
|
|
6192
|
-
rate: cap2.rate
|
|
6193
|
-
});
|
|
6194
|
-
}
|
|
6195
|
-
return members;
|
|
6196
|
-
}
|
|
6197
|
-
const role = normalizeRole(field.pricing_role, "base");
|
|
6198
|
-
if (role !== "base") return members;
|
|
6199
|
-
if (field.service_id === void 0 || field.service_id === null) return members;
|
|
6200
|
-
const cap = getServiceCapability(services, field.service_id);
|
|
6201
|
-
if (!cap || typeof cap.rate !== "number" || !Number.isFinite(cap.rate)) {
|
|
6202
|
-
return members;
|
|
6203
|
-
}
|
|
6204
|
-
members.push({
|
|
6205
|
-
kind: "field",
|
|
6206
|
-
id: field.id,
|
|
6207
|
-
fieldId: field.id,
|
|
6208
|
-
label: (_c = field.label) != null ? _c : field.id,
|
|
6209
|
-
service_id: field.service_id,
|
|
6210
|
-
rate: cap.rate
|
|
6211
|
-
});
|
|
6212
|
-
return members;
|
|
6213
|
-
}
|
|
6214
|
-
function isButton(field) {
|
|
6215
|
-
if (field.button === true) return true;
|
|
6216
|
-
return Array.isArray(field.options) && field.options.length > 0;
|
|
6217
|
-
}
|
|
6218
|
-
function normalizeRole(role, fallback) {
|
|
6219
|
-
return role === "base" || role === "utility" ? role : fallback;
|
|
6220
|
-
}
|
|
6221
|
-
function toDiagnosticRef(reference) {
|
|
6222
|
-
return {
|
|
6223
|
-
nodeId: reference.nodeId,
|
|
6224
|
-
fieldId: reference.fieldId,
|
|
6225
|
-
label: reference.label,
|
|
6226
|
-
refKind: reference.refKind,
|
|
6227
|
-
service_id: reference.service_id,
|
|
6228
|
-
rate: reference.rate
|
|
6229
|
-
};
|
|
6230
|
-
}
|
|
6231
|
-
function contextualKey(tagId, primary, candidate, ratePolicy) {
|
|
6232
|
-
const pctKey = "pct" in ratePolicy ? `:${ratePolicy.pct}` : "";
|
|
6233
|
-
return [
|
|
6234
|
-
"contextual",
|
|
6235
|
-
tagId,
|
|
6236
|
-
primary.fieldId,
|
|
6237
|
-
primary.nodeId,
|
|
6238
|
-
candidate.fieldId,
|
|
6239
|
-
candidate.nodeId,
|
|
6240
|
-
`${ratePolicy.kind}${pctKey}`
|
|
6241
|
-
].join("|");
|
|
6242
|
-
}
|
|
6243
|
-
function describeLabel(tag) {
|
|
6244
|
-
var _a, _b;
|
|
6245
|
-
return (_b = (_a = tag == null ? void 0 : tag.label) != null ? _a : tag == null ? void 0 : tag.id) != null ? _b : "tag";
|
|
6246
|
-
}
|
|
6247
|
-
function explainRateMismatch(policy, primary, candidate, where) {
|
|
6248
|
-
var _a, _b;
|
|
6249
|
-
const primaryLabel = `${(_a = primary.label) != null ? _a : primary.nodeId} (${primary.rate})`;
|
|
6250
|
-
const candidateLabel = `${(_b = candidate.label) != null ? _b : candidate.nodeId} (${candidate.rate})`;
|
|
6251
|
-
switch (policy.kind) {
|
|
6252
|
-
case "eq_primary":
|
|
6253
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must exactly match ${primaryLabel}.`;
|
|
6254
|
-
case "lte_primary":
|
|
6255
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must stay within ${policy.pct}% below and never above ${primaryLabel}.`;
|
|
6256
|
-
case "within_pct":
|
|
6257
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be within ${policy.pct}% of ${primaryLabel}.`;
|
|
6258
|
-
case "at_least_pct_lower":
|
|
6259
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be at least ${policy.pct}% lower than ${primaryLabel}.`;
|
|
6260
|
-
}
|
|
6261
|
-
}
|
|
6262
|
-
|
|
6263
|
-
// src/core/validate/index.ts
|
|
6264
|
-
function readVisibilitySimOpts(ctx) {
|
|
6265
|
-
const c = ctx;
|
|
6266
|
-
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
6267
|
-
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
6268
|
-
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
6269
|
-
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
6270
|
-
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
6271
|
-
return {
|
|
6272
|
-
simulate,
|
|
6273
|
-
maxStates,
|
|
6274
|
-
maxDepth,
|
|
6275
|
-
simulateAllRoots,
|
|
6276
|
-
onlyEffectfulTriggers
|
|
6277
|
-
};
|
|
6278
|
-
}
|
|
6279
|
-
function validate(props, ctx = {}) {
|
|
6280
|
-
var _a, _b, _c;
|
|
6281
|
-
const options = mergeValidatorOptions({}, ctx);
|
|
6282
|
-
const fallbackSettings = resolveFallbackSettings(options);
|
|
6283
|
-
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
6284
|
-
const errors = [];
|
|
6285
|
-
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
6286
|
-
const selectedKeys = new Set(
|
|
6287
|
-
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
6288
|
-
);
|
|
6289
|
-
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
6290
|
-
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
6291
|
-
const tagById = /* @__PURE__ */ new Map();
|
|
6292
|
-
const fieldById = /* @__PURE__ */ new Map();
|
|
6293
|
-
for (const t of tags) tagById.set(t.id, t);
|
|
6294
|
-
for (const f of fields) fieldById.set(f.id, f);
|
|
6295
|
-
const v = {
|
|
6296
|
-
props,
|
|
6297
|
-
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
6298
|
-
options: {
|
|
6299
|
-
...options,
|
|
6300
|
-
ratePolicy,
|
|
6301
|
-
fallbackSettings
|
|
6302
|
-
},
|
|
6303
|
-
errors,
|
|
6304
|
-
serviceMap,
|
|
6305
|
-
selectedKeys,
|
|
6306
|
-
tags,
|
|
6307
|
-
fields,
|
|
6308
|
-
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
6309
|
-
tagById,
|
|
6310
|
-
fieldById,
|
|
6311
|
-
fieldsVisibleUnder: (_tagId) => []
|
|
6312
|
-
};
|
|
6313
|
-
validateStructure(v);
|
|
6314
|
-
validateIdentity(v);
|
|
6315
|
-
validateOptionMaps(v);
|
|
6316
|
-
validateOrderKinds(v);
|
|
6317
|
-
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
6318
|
-
const visSim = readVisibilitySimOpts(options);
|
|
6319
|
-
validateVisibility(v, visSim);
|
|
6320
|
-
applyPolicies(
|
|
6321
|
-
v.errors,
|
|
6322
|
-
v.props,
|
|
6323
|
-
v.serviceMap,
|
|
6324
|
-
v.options.policies,
|
|
6325
|
-
v.fieldsVisibleUnder,
|
|
6326
|
-
v.tags
|
|
6327
|
-
);
|
|
6328
|
-
validateServiceVsUserInput(v);
|
|
6329
|
-
validateUtilityMarkers(v);
|
|
6330
|
-
validateRates(v);
|
|
6331
|
-
if (Object.keys(serviceMap).length > 0 && tags.length > 0) {
|
|
6332
|
-
const builder = createBuilder({ serviceMap });
|
|
6333
|
-
builder.load(props);
|
|
6334
|
-
for (const tag of tags) {
|
|
6335
|
-
const diags = validateRateCoherenceDeep({
|
|
6336
|
-
builder,
|
|
6337
|
-
services: serviceMap,
|
|
6338
|
-
tagId: tag.id,
|
|
6339
|
-
ratePolicy,
|
|
6340
|
-
invalidFieldIds: v.invalidRateFieldIds
|
|
6341
|
-
});
|
|
6342
|
-
for (const diag of diags) {
|
|
6343
|
-
if (diag.kind !== "contextual") continue;
|
|
6344
|
-
errors.push({
|
|
6345
|
-
code: "rate_coherence_violation",
|
|
6346
|
-
severity: "error",
|
|
6347
|
-
message: diag.message,
|
|
6348
|
-
nodeId: diag.nodeId,
|
|
6349
|
-
details: {
|
|
6350
|
-
tagId: diag.tagId,
|
|
6351
|
-
simulationAnchor: diag.simulationAnchor,
|
|
6352
|
-
primary: diag.primary,
|
|
6353
|
-
offender: diag.offender,
|
|
6354
|
-
policy: diag.policy,
|
|
6355
|
-
policyPct: diag.policyPct,
|
|
6356
|
-
invalidFieldIds: diag.invalidFieldIds
|
|
6357
|
-
}
|
|
6358
|
-
});
|
|
6359
|
-
}
|
|
6360
|
-
}
|
|
6361
|
-
}
|
|
6362
|
-
validateConstraints(v);
|
|
6363
|
-
validateCustomFields(v);
|
|
6364
|
-
validateGlobalUtilityGuard(v);
|
|
6365
|
-
validateUnboundFields(v);
|
|
6366
|
-
validateFallbacks(v);
|
|
6367
|
-
return v.errors;
|
|
6368
|
-
}
|
|
6369
|
-
|
|
6370
6458
|
// src/core/fallback.ts
|
|
6371
6459
|
var DEFAULT_SETTINGS = {
|
|
6372
6460
|
requireConstraintFit: true,
|
|
@@ -6788,9 +6876,9 @@ function createNodeIndex(builder) {
|
|
|
6788
6876
|
if (cached) return cached;
|
|
6789
6877
|
const raw = fieldById.get(id);
|
|
6790
6878
|
if (!raw) return void 0;
|
|
6791
|
-
const
|
|
6792
|
-
const includes =
|
|
6793
|
-
const excludes =
|
|
6879
|
+
const isButton = raw.button === true;
|
|
6880
|
+
const includes = isButton ? Object.freeze(new Set((_b2 = (_a2 = props.includes_for_buttons) == null ? void 0 : _a2[id]) != null ? _b2 : [])) : emptySet;
|
|
6881
|
+
const excludes = isButton ? Object.freeze(new Set((_d2 = (_c2 = props.excludes_for_buttons) == null ? void 0 : _c2[id]) != null ? _d2 : [])) : emptySet;
|
|
6794
6882
|
const bindIds = () => {
|
|
6795
6883
|
const cachedBind = fieldBindIdsCache.get(id);
|
|
6796
6884
|
if (cachedBind) return cachedBind;
|
|
@@ -6827,7 +6915,7 @@ function createNodeIndex(builder) {
|
|
|
6827
6915
|
);
|
|
6828
6916
|
},
|
|
6829
6917
|
getDescendants(tagId) {
|
|
6830
|
-
return resolveDescendants(id, includes, tagId, !
|
|
6918
|
+
return resolveDescendants(id, includes, tagId, !isButton);
|
|
6831
6919
|
}
|
|
6832
6920
|
};
|
|
6833
6921
|
fieldNodeCache.set(id, node);
|
|
@@ -6911,22 +6999,15 @@ function createNodeIndex(builder) {
|
|
|
6911
6999
|
|
|
6912
7000
|
// src/core/service-filter.ts
|
|
6913
7001
|
function filterServicesForVisibleGroup(input, deps) {
|
|
6914
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
7002
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
6915
7003
|
const svcMap = (_c = (_b = (_a = deps.builder).getServiceMap) == null ? void 0 : _b.call(_a)) != null ? _c : {};
|
|
6916
7004
|
const builderOptions = (_e = (_d = deps.builder).getOptions) == null ? void 0 : _e.call(_d);
|
|
6917
7005
|
const { context } = input;
|
|
6918
7006
|
const usedSet = new Set(context.usedServiceIds.map(String));
|
|
6919
|
-
const primary = context.usedServiceIds[0];
|
|
6920
7007
|
const explicitFallbackSettings = (_f = context.fallbackSettings) != null ? _f : context.fallback;
|
|
6921
7008
|
const resolvedRatePolicy = normalizeRatePolicy(
|
|
6922
7009
|
(_h = (_g = context.ratePolicy) != null ? _g : explicitFallbackSettings == null ? void 0 : explicitFallbackSettings.ratePolicy) != null ? _h : builderOptions == null ? void 0 : builderOptions.ratePolicy
|
|
6923
7010
|
);
|
|
6924
|
-
const fallbackSettingsSource = explicitFallbackSettings != null ? explicitFallbackSettings : builderOptions == null ? void 0 : builderOptions.fallbackSettings;
|
|
6925
|
-
const fb = {
|
|
6926
|
-
...DEFAULT_FALLBACK_SETTINGS,
|
|
6927
|
-
...fallbackSettingsSource != null ? fallbackSettingsSource : {},
|
|
6928
|
-
ratePolicy: resolvedRatePolicy
|
|
6929
|
-
};
|
|
6930
7011
|
const policySource = (_j = (_i = context.policies) != null ? _i : builderOptions == null ? void 0 : builderOptions.policies) != null ? _j : [];
|
|
6931
7012
|
const visibleServiceIds = context.selectedButtons === void 0 ? void 0 : collectVisibleServiceIds(
|
|
6932
7013
|
deps.builder,
|
|
@@ -6954,7 +7035,15 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
6954
7035
|
cap.id,
|
|
6955
7036
|
(_k = context.effectiveConstraints) != null ? _k : {}
|
|
6956
7037
|
);
|
|
6957
|
-
const passesRate2 =
|
|
7038
|
+
const passesRate2 = candidatePassesRateCoherence(
|
|
7039
|
+
deps.builder,
|
|
7040
|
+
svcMap,
|
|
7041
|
+
context.tagId,
|
|
7042
|
+
(_l = context.selectedButtons) != null ? _l : [],
|
|
7043
|
+
context.usedServiceIds,
|
|
7044
|
+
id,
|
|
7045
|
+
resolvedRatePolicy
|
|
7046
|
+
);
|
|
6958
7047
|
const polRes = evaluatePoliciesRaw(
|
|
6959
7048
|
policySource,
|
|
6960
7049
|
[...context.usedServiceIds, id],
|
|
@@ -7073,7 +7162,9 @@ function collectVisibleServiceIds(builder, tagId, selectedButtons) {
|
|
|
7073
7162
|
const fields = (_b = props.fields) != null ? _b : [];
|
|
7074
7163
|
const tag = tags.find((t) => t.id === tagId);
|
|
7075
7164
|
if ((tag == null ? void 0 : tag.service_id) != null) out.add(String(tag.service_id));
|
|
7076
|
-
const visibleFieldIds = new Set(
|
|
7165
|
+
const visibleFieldIds = new Set(
|
|
7166
|
+
builder.visibleFields(tagId, selectedButtons)
|
|
7167
|
+
);
|
|
7077
7168
|
for (const field of fields) {
|
|
7078
7169
|
if (!visibleFieldIds.has(field.id)) continue;
|
|
7079
7170
|
if (field.service_id != null) {
|
|
@@ -7096,8 +7187,7 @@ function matchesRuleFilter(cap, rule, tagId) {
|
|
|
7096
7187
|
if (!cap) return false;
|
|
7097
7188
|
const f = rule.filter;
|
|
7098
7189
|
if (!f) return true;
|
|
7099
|
-
|
|
7100
|
-
return true;
|
|
7190
|
+
return !(f.tag_id && !toStrSet(f.tag_id).has(String(tagId)));
|
|
7101
7191
|
}
|
|
7102
7192
|
function toStrSet(v) {
|
|
7103
7193
|
const arr = Array.isArray(v) ? v : [v];
|
|
@@ -7105,6 +7195,107 @@ function toStrSet(v) {
|
|
|
7105
7195
|
for (const x of arr) s.add(String(x));
|
|
7106
7196
|
return s;
|
|
7107
7197
|
}
|
|
7198
|
+
function candidatePassesRateCoherence(builder, serviceMap, tagId, selectedKeys, usedServiceIds, candidateId, ratePolicy) {
|
|
7199
|
+
var _a, _b, _c, _d;
|
|
7200
|
+
if (usedServiceIds.length === 0) return true;
|
|
7201
|
+
const props = builder.getProps();
|
|
7202
|
+
const baseFields = (_a = props.fields) != null ? _a : [];
|
|
7203
|
+
const candidateFieldId = syntheticServiceFieldId("candidate", candidateId, 0);
|
|
7204
|
+
const syntheticFields = [
|
|
7205
|
+
...usedServiceIds.map((serviceId, index) => ({
|
|
7206
|
+
id: syntheticServiceFieldId("used", serviceId, index),
|
|
7207
|
+
label: `Used service ${String(serviceId)}`,
|
|
7208
|
+
type: "custom",
|
|
7209
|
+
button: true,
|
|
7210
|
+
service_id: serviceId,
|
|
7211
|
+
pricing_role: "base"
|
|
7212
|
+
})),
|
|
7213
|
+
{
|
|
7214
|
+
id: candidateFieldId,
|
|
7215
|
+
label: `Candidate ${String(candidateId)}`,
|
|
7216
|
+
type: "custom",
|
|
7217
|
+
button: true,
|
|
7218
|
+
service_id: candidateId,
|
|
7219
|
+
pricing_role: "base"
|
|
7220
|
+
}
|
|
7221
|
+
];
|
|
7222
|
+
const fields = [...baseFields, ...syntheticFields];
|
|
7223
|
+
const visibleFieldIds = [
|
|
7224
|
+
...builder.visibleFields(tagId, selectedKeys),
|
|
7225
|
+
...syntheticFields.map((field) => field.id)
|
|
7226
|
+
];
|
|
7227
|
+
const anchoredFilters = ((_b = props.filters) != null ? _b : []).map(
|
|
7228
|
+
(tag) => tag.id === tagId && usedServiceIds[0] != null ? { ...tag, service_id: usedServiceIds[0] } : tag
|
|
7229
|
+
);
|
|
7230
|
+
const validationProps = {
|
|
7231
|
+
...props,
|
|
7232
|
+
filters: anchoredFilters,
|
|
7233
|
+
fields
|
|
7234
|
+
};
|
|
7235
|
+
const errors = [];
|
|
7236
|
+
const tags = (_c = validationProps.filters) != null ? _c : [];
|
|
7237
|
+
const fieldById = new Map(fields.map((field) => [field.id, field]));
|
|
7238
|
+
const tagById = new Map(tags.map((tag) => [tag.id, tag]));
|
|
7239
|
+
const v = {
|
|
7240
|
+
props: validationProps,
|
|
7241
|
+
nodeMap: buildNodeMap(validationProps),
|
|
7242
|
+
options: {
|
|
7243
|
+
...(_d = builder.getOptions) == null ? void 0 : _d.call(builder),
|
|
7244
|
+
serviceMap,
|
|
7245
|
+
ratePolicy
|
|
7246
|
+
},
|
|
7247
|
+
errors,
|
|
7248
|
+
serviceMap,
|
|
7249
|
+
selectedKeys: new Set(selectedKeys),
|
|
7250
|
+
tags,
|
|
7251
|
+
fields,
|
|
7252
|
+
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
7253
|
+
tagById,
|
|
7254
|
+
fieldById,
|
|
7255
|
+
fieldsVisibleUnder: () => [],
|
|
7256
|
+
simulatedVisibilityContexts: []
|
|
7257
|
+
};
|
|
7258
|
+
validateRateCoherenceForVisibleContext({
|
|
7259
|
+
v,
|
|
7260
|
+
tagId,
|
|
7261
|
+
selectedKeys,
|
|
7262
|
+
visibleFieldIds,
|
|
7263
|
+
effectMap: buildTriggerEffectMap(validationProps),
|
|
7264
|
+
seen: /* @__PURE__ */ new Set()
|
|
7265
|
+
});
|
|
7266
|
+
return !errors.some(
|
|
7267
|
+
(error) => rateIssueAffectsCandidate(
|
|
7268
|
+
error,
|
|
7269
|
+
candidateId,
|
|
7270
|
+
candidateFieldId,
|
|
7271
|
+
usedServiceIds[0]
|
|
7272
|
+
)
|
|
7273
|
+
);
|
|
7274
|
+
}
|
|
7275
|
+
function syntheticServiceFieldId(kind, serviceId, index) {
|
|
7276
|
+
return `__service_filter_${kind}__:${index}:${String(serviceId)}`;
|
|
7277
|
+
}
|
|
7278
|
+
function rateIssueAffectsCandidate(error, candidateId, candidateFieldId, primaryAnchorId) {
|
|
7279
|
+
var _a, _b, _c, _d;
|
|
7280
|
+
if (error.code !== "rate_coherence_violation") return false;
|
|
7281
|
+
const candidateKey = String(candidateId);
|
|
7282
|
+
const details = (_a = error.details) != null ? _a : {};
|
|
7283
|
+
const anchorKey = primaryAnchorId == null ? void 0 : String(primaryAnchorId);
|
|
7284
|
+
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;
|
|
7285
|
+
if (primaryMatchesAnchor && ((_d = details.affectedServiceIds) == null ? void 0 : _d.some(
|
|
7286
|
+
(serviceId) => String(serviceId) === candidateKey
|
|
7287
|
+
))) {
|
|
7288
|
+
return true;
|
|
7289
|
+
}
|
|
7290
|
+
if (primaryMatchesAnchor && String(error.nodeId) === candidateFieldId) {
|
|
7291
|
+
return true;
|
|
7292
|
+
}
|
|
7293
|
+
return [details.primary, details.candidate].some((ref) => {
|
|
7294
|
+
if (!ref) return false;
|
|
7295
|
+
if (!primaryMatchesAnchor) return false;
|
|
7296
|
+
return String(ref.serviceId) === candidateKey || String(ref.service_id) === candidateKey || String(ref.fieldId) === candidateFieldId || String(ref.nodeId) === candidateFieldId;
|
|
7297
|
+
});
|
|
7298
|
+
}
|
|
7108
7299
|
|
|
7109
7300
|
// src/react/canvas/editor/editor-ids.ts
|
|
7110
7301
|
function uniqueId(ctx, base) {
|
|
@@ -7734,7 +7925,7 @@ function setService(ctx, id, input) {
|
|
|
7734
7925
|
);
|
|
7735
7926
|
}
|
|
7736
7927
|
const isOptionBased = Array.isArray(f.options) && f.options.length > 0;
|
|
7737
|
-
const
|
|
7928
|
+
const isButton = !!f.button;
|
|
7738
7929
|
if (nextRole) {
|
|
7739
7930
|
f.pricing_role = nextRole;
|
|
7740
7931
|
}
|
|
@@ -7750,7 +7941,7 @@ function setService(ctx, id, input) {
|
|
|
7750
7941
|
if ("service_id" in f) delete f.service_id;
|
|
7751
7942
|
return;
|
|
7752
7943
|
}
|
|
7753
|
-
if (!
|
|
7944
|
+
if (!isButton) {
|
|
7754
7945
|
if (hasSidKey) {
|
|
7755
7946
|
ctx.api.emit("error", {
|
|
7756
7947
|
message: "Only button fields (without options) can have a service_id.",
|
|
@@ -10932,18 +11123,23 @@ function useErrors(opts = {}) {
|
|
|
10932
11123
|
setValidating(true);
|
|
10933
11124
|
schedule(
|
|
10934
11125
|
() => {
|
|
10935
|
-
var _a2, _b2, _c2, _d2, _e2;
|
|
11126
|
+
var _a2, _b2, _c2, _d2, _e2, _f, _g;
|
|
10936
11127
|
if (token !== runTokenRef.current) return;
|
|
10937
11128
|
try {
|
|
10938
11129
|
const props = api.editor.getProps();
|
|
10939
|
-
const
|
|
11130
|
+
const builderOptions = (_c2 = (_b2 = (_a2 = api.builder).getOptions) == null ? void 0 : _b2.call(_a2)) != null ? _c2 : {};
|
|
11131
|
+
const res = validate(props, {
|
|
11132
|
+
...builderOptions,
|
|
11133
|
+
simulateVisibility: (_d2 = builderOptions.simulateVisibility) != null ? _d2 : true,
|
|
11134
|
+
visibilityOnlyEffectfulTriggers: (_e2 = builderOptions.visibilityOnlyEffectfulTriggers) != null ? _e2 : true
|
|
11135
|
+
});
|
|
10940
11136
|
if (token !== runTokenRef.current) return;
|
|
10941
11137
|
setValidation(toValidationRows(res != null ? res : []));
|
|
10942
11138
|
} catch (err) {
|
|
10943
11139
|
if (token !== runTokenRef.current) return;
|
|
10944
11140
|
pushLog({
|
|
10945
|
-
message: (
|
|
10946
|
-
code: (
|
|
11141
|
+
message: (_f = err == null ? void 0 : err.message) != null ? _f : "validate() threw",
|
|
11142
|
+
code: (_g = err == null ? void 0 : err.code) != null ? _g : "validate_throw",
|
|
10947
11143
|
meta: err
|
|
10948
11144
|
});
|
|
10949
11145
|
setValidation([]);
|