@timeax/digital-service-engine 0.2.7 → 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 +1669 -1218
- 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 +1667 -1218
- package/dist/core/index.js.map +1 -1
- package/dist/react/index.cjs +565 -375
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +565 -375
- package/dist/react/index.js.map +1 -1
- package/dist/workspace/index.cjs +574 -388
- package/dist/workspace/index.cjs.map +1 -1
- package/dist/workspace/index.js +574 -388
- 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
|
}
|
|
@@ -4668,9 +4697,9 @@ function validateServiceVsUserInput(v) {
|
|
|
4668
4697
|
for (const f of v.fields) {
|
|
4669
4698
|
const anySvc = hasAnyServiceOption(f);
|
|
4670
4699
|
const hasName = !!(f.name && f.name.trim());
|
|
4671
|
-
const
|
|
4700
|
+
const isButton = f.button === true;
|
|
4672
4701
|
const hasFieldService = f.service_id !== void 0 && f.service_id !== null;
|
|
4673
|
-
const hasTriggerMap =
|
|
4702
|
+
const hasTriggerMap = isButton && hasButtonTriggerMap(v, f.id);
|
|
4674
4703
|
if (f.type === "custom" && anySvc) {
|
|
4675
4704
|
v.errors.push({
|
|
4676
4705
|
code: "user_input_field_has_service_option",
|
|
@@ -4687,7 +4716,7 @@ function validateServiceVsUserInput(v) {
|
|
|
4687
4716
|
v.errors.push({
|
|
4688
4717
|
code: "service_field_missing_service_id",
|
|
4689
4718
|
severity: "error",
|
|
4690
|
-
message:
|
|
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.`,
|
|
4691
4720
|
nodeId: f.id
|
|
4692
4721
|
});
|
|
4693
4722
|
} else {
|
|
@@ -4865,15 +4894,6 @@ function passesRatePolicy(policy, primaryRate, candidateRate) {
|
|
|
4865
4894
|
return candidateRate <= primaryRate * (1 - rp.pct / 100);
|
|
4866
4895
|
}
|
|
4867
4896
|
}
|
|
4868
|
-
function rateOk(svcMap, candidate, primary, policy) {
|
|
4869
|
-
const cand = getServiceCapability(svcMap, candidate);
|
|
4870
|
-
const prim = getServiceCapability(svcMap, primary);
|
|
4871
|
-
if (!cand || !prim) return false;
|
|
4872
|
-
const cRate = toFiniteNumber(cand.rate);
|
|
4873
|
-
const pRate = toFiniteNumber(prim.rate);
|
|
4874
|
-
if (!Number.isFinite(cRate) || !Number.isFinite(pRate)) return false;
|
|
4875
|
-
return passesRatePolicy(policy.ratePolicy, pRate, cRate);
|
|
4876
|
-
}
|
|
4877
4897
|
function getServiceCapabilityEntry(svcMap, candidate) {
|
|
4878
4898
|
if (candidate === void 0 || candidate === null) return void 0;
|
|
4879
4899
|
const direct = svcMap[candidate];
|
|
@@ -5001,6 +5021,324 @@ function validateRates(v) {
|
|
|
5001
5021
|
}
|
|
5002
5022
|
}
|
|
5003
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
|
+
|
|
5004
5342
|
// src/core/validate/steps/constraints.ts
|
|
5005
5343
|
function constraintKeysInChain(v, tagId) {
|
|
5006
5344
|
const keys = [];
|
|
@@ -5757,6 +6095,84 @@ function mergeValidatorOptions(defaults = {}, overrides = {}) {
|
|
|
5757
6095
|
};
|
|
5758
6096
|
}
|
|
5759
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
|
+
|
|
5760
6176
|
// src/core/builder.ts
|
|
5761
6177
|
import { cloneDeep as cloneDeep2 } from "lodash-es";
|
|
5762
6178
|
function createBuilder(opts = {}) {
|
|
@@ -6039,344 +6455,6 @@ function toStringSet(v) {
|
|
|
6039
6455
|
return new Set(v.map(String));
|
|
6040
6456
|
}
|
|
6041
6457
|
|
|
6042
|
-
// src/core/rate-coherence.ts
|
|
6043
|
-
function validateRateCoherenceDeep(params) {
|
|
6044
|
-
var _a, _b, _c;
|
|
6045
|
-
const { builder, services, tagId } = params;
|
|
6046
|
-
const ratePolicy = normalizeRatePolicy(params.ratePolicy);
|
|
6047
|
-
const props = builder.getProps();
|
|
6048
|
-
const invalidFieldIds = new Set((_a = params.invalidFieldIds) != null ? _a : []);
|
|
6049
|
-
const fields = (_b = props.fields) != null ? _b : [];
|
|
6050
|
-
const fieldById = new Map(fields.map((f) => [f.id, f]));
|
|
6051
|
-
const tagById = new Map(((_c = props.filters) != null ? _c : []).map((t) => [t.id, t]));
|
|
6052
|
-
const tag = tagById.get(tagId);
|
|
6053
|
-
const baselineFieldIds = builder.visibleFields(tagId, []);
|
|
6054
|
-
const baselineFields = baselineFieldIds.map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
6055
|
-
const anchors = collectAnchors(baselineFields);
|
|
6056
|
-
const diagnostics = [];
|
|
6057
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6058
|
-
for (const anchor of anchors) {
|
|
6059
|
-
const selectedKeys = anchor.kind === "option" ? [`${anchor.fieldId}::${anchor.id}`] : [anchor.fieldId];
|
|
6060
|
-
const visibleFields = builder.visibleFields(tagId, selectedKeys).map((fid) => fieldById.get(fid)).filter(Boolean);
|
|
6061
|
-
const visibleInvalidFieldIds = visibleFields.map((field) => field.id).filter((fieldId) => invalidFieldIds.has(fieldId));
|
|
6062
|
-
for (const fieldId of visibleInvalidFieldIds) {
|
|
6063
|
-
const key = `internal|${tagId}|${fieldId}`;
|
|
6064
|
-
if (seen.has(key)) continue;
|
|
6065
|
-
seen.add(key);
|
|
6066
|
-
diagnostics.push({
|
|
6067
|
-
kind: "internal_field",
|
|
6068
|
-
scope: "visible_group",
|
|
6069
|
-
tagId,
|
|
6070
|
-
fieldId,
|
|
6071
|
-
nodeId: fieldId,
|
|
6072
|
-
message: `Field "${fieldId}" is internally invalid under rate policy "${ratePolicy.kind}".`,
|
|
6073
|
-
simulationAnchor: {
|
|
6074
|
-
kind: anchor.kind,
|
|
6075
|
-
id: anchor.id,
|
|
6076
|
-
fieldId: anchor.fieldId,
|
|
6077
|
-
label: anchor.label
|
|
6078
|
-
},
|
|
6079
|
-
invalidFieldIds: [fieldId]
|
|
6080
|
-
});
|
|
6081
|
-
}
|
|
6082
|
-
const references = visibleFields.flatMap(
|
|
6083
|
-
(field) => collectFieldReferences(field, services)
|
|
6084
|
-
);
|
|
6085
|
-
if (references.length <= 1) continue;
|
|
6086
|
-
const primary = references.reduce((best, current) => {
|
|
6087
|
-
if (current.rate !== best.rate) {
|
|
6088
|
-
return current.rate > best.rate ? current : best;
|
|
6089
|
-
}
|
|
6090
|
-
const bestKey = `${best.fieldId}|${best.nodeId}`;
|
|
6091
|
-
const currentKey = `${current.fieldId}|${current.nodeId}`;
|
|
6092
|
-
return currentKey < bestKey ? current : best;
|
|
6093
|
-
});
|
|
6094
|
-
for (const candidate of references) {
|
|
6095
|
-
if (candidate.nodeId === primary.nodeId) continue;
|
|
6096
|
-
if (candidate.fieldId === primary.fieldId) continue;
|
|
6097
|
-
if (passesRatePolicy(ratePolicy, primary.rate, candidate.rate)) {
|
|
6098
|
-
continue;
|
|
6099
|
-
}
|
|
6100
|
-
const key = contextualKey(tagId, primary, candidate, ratePolicy);
|
|
6101
|
-
if (seen.has(key)) continue;
|
|
6102
|
-
seen.add(key);
|
|
6103
|
-
diagnostics.push({
|
|
6104
|
-
kind: "contextual",
|
|
6105
|
-
scope: "visible_group",
|
|
6106
|
-
tagId,
|
|
6107
|
-
nodeId: candidate.nodeId,
|
|
6108
|
-
primary: toDiagnosticRef(primary),
|
|
6109
|
-
offender: toDiagnosticRef(candidate),
|
|
6110
|
-
policy: ratePolicy.kind,
|
|
6111
|
-
policyPct: "pct" in ratePolicy ? ratePolicy.pct : void 0,
|
|
6112
|
-
message: explainRateMismatch(
|
|
6113
|
-
ratePolicy,
|
|
6114
|
-
primary,
|
|
6115
|
-
candidate,
|
|
6116
|
-
describeLabel(tag)
|
|
6117
|
-
),
|
|
6118
|
-
simulationAnchor: {
|
|
6119
|
-
kind: anchor.kind,
|
|
6120
|
-
id: anchor.id,
|
|
6121
|
-
fieldId: anchor.fieldId,
|
|
6122
|
-
label: anchor.label
|
|
6123
|
-
},
|
|
6124
|
-
invalidFieldIds: visibleInvalidFieldIds
|
|
6125
|
-
});
|
|
6126
|
-
}
|
|
6127
|
-
}
|
|
6128
|
-
return diagnostics;
|
|
6129
|
-
}
|
|
6130
|
-
function collectAnchors(fields) {
|
|
6131
|
-
var _a, _b;
|
|
6132
|
-
const anchors = [];
|
|
6133
|
-
for (const field of fields) {
|
|
6134
|
-
if (!isButton(field)) continue;
|
|
6135
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
6136
|
-
for (const option of field.options) {
|
|
6137
|
-
anchors.push({
|
|
6138
|
-
kind: "option",
|
|
6139
|
-
id: option.id,
|
|
6140
|
-
fieldId: field.id,
|
|
6141
|
-
label: (_a = option.label) != null ? _a : option.id
|
|
6142
|
-
});
|
|
6143
|
-
}
|
|
6144
|
-
continue;
|
|
6145
|
-
}
|
|
6146
|
-
anchors.push({
|
|
6147
|
-
kind: "field",
|
|
6148
|
-
id: field.id,
|
|
6149
|
-
fieldId: field.id,
|
|
6150
|
-
label: (_b = field.label) != null ? _b : field.id
|
|
6151
|
-
});
|
|
6152
|
-
}
|
|
6153
|
-
return anchors;
|
|
6154
|
-
}
|
|
6155
|
-
function collectFieldReferences(field, services) {
|
|
6156
|
-
var _a;
|
|
6157
|
-
const members = collectBaseMembers(field, services);
|
|
6158
|
-
if (members.length === 0) return [];
|
|
6159
|
-
if (isMultiField(field)) {
|
|
6160
|
-
const averageRate = members.reduce((sum, member) => sum + member.rate, 0) / members.length;
|
|
6161
|
-
return [
|
|
6162
|
-
{
|
|
6163
|
-
refKind: "multi",
|
|
6164
|
-
nodeId: field.id,
|
|
6165
|
-
fieldId: field.id,
|
|
6166
|
-
label: (_a = field.label) != null ? _a : field.id,
|
|
6167
|
-
rate: averageRate,
|
|
6168
|
-
members
|
|
6169
|
-
}
|
|
6170
|
-
];
|
|
6171
|
-
}
|
|
6172
|
-
return members.map((member) => ({
|
|
6173
|
-
refKind: "single",
|
|
6174
|
-
nodeId: member.id,
|
|
6175
|
-
fieldId: field.id,
|
|
6176
|
-
label: member.label,
|
|
6177
|
-
rate: member.rate,
|
|
6178
|
-
service_id: member.service_id,
|
|
6179
|
-
members: [member]
|
|
6180
|
-
}));
|
|
6181
|
-
}
|
|
6182
|
-
function collectBaseMembers(field, services) {
|
|
6183
|
-
var _a, _b, _c;
|
|
6184
|
-
const members = [];
|
|
6185
|
-
if (Array.isArray(field.options) && field.options.length > 0) {
|
|
6186
|
-
for (const option of field.options) {
|
|
6187
|
-
const role2 = normalizeRole((_a = option.pricing_role) != null ? _a : field.pricing_role, "base");
|
|
6188
|
-
if (role2 !== "base") continue;
|
|
6189
|
-
if (option.service_id === void 0 || option.service_id === null) {
|
|
6190
|
-
continue;
|
|
6191
|
-
}
|
|
6192
|
-
const cap2 = getServiceCapability(services, option.service_id);
|
|
6193
|
-
if (!cap2 || typeof cap2.rate !== "number" || !Number.isFinite(cap2.rate)) {
|
|
6194
|
-
continue;
|
|
6195
|
-
}
|
|
6196
|
-
members.push({
|
|
6197
|
-
kind: "option",
|
|
6198
|
-
id: option.id,
|
|
6199
|
-
fieldId: field.id,
|
|
6200
|
-
label: (_b = option.label) != null ? _b : option.id,
|
|
6201
|
-
service_id: option.service_id,
|
|
6202
|
-
rate: cap2.rate
|
|
6203
|
-
});
|
|
6204
|
-
}
|
|
6205
|
-
return members;
|
|
6206
|
-
}
|
|
6207
|
-
const role = normalizeRole(field.pricing_role, "base");
|
|
6208
|
-
if (role !== "base") return members;
|
|
6209
|
-
if (field.service_id === void 0 || field.service_id === null) return members;
|
|
6210
|
-
const cap = getServiceCapability(services, field.service_id);
|
|
6211
|
-
if (!cap || typeof cap.rate !== "number" || !Number.isFinite(cap.rate)) {
|
|
6212
|
-
return members;
|
|
6213
|
-
}
|
|
6214
|
-
members.push({
|
|
6215
|
-
kind: "field",
|
|
6216
|
-
id: field.id,
|
|
6217
|
-
fieldId: field.id,
|
|
6218
|
-
label: (_c = field.label) != null ? _c : field.id,
|
|
6219
|
-
service_id: field.service_id,
|
|
6220
|
-
rate: cap.rate
|
|
6221
|
-
});
|
|
6222
|
-
return members;
|
|
6223
|
-
}
|
|
6224
|
-
function isButton(field) {
|
|
6225
|
-
if (field.button === true) return true;
|
|
6226
|
-
return Array.isArray(field.options) && field.options.length > 0;
|
|
6227
|
-
}
|
|
6228
|
-
function normalizeRole(role, fallback) {
|
|
6229
|
-
return role === "base" || role === "utility" ? role : fallback;
|
|
6230
|
-
}
|
|
6231
|
-
function toDiagnosticRef(reference) {
|
|
6232
|
-
return {
|
|
6233
|
-
nodeId: reference.nodeId,
|
|
6234
|
-
fieldId: reference.fieldId,
|
|
6235
|
-
label: reference.label,
|
|
6236
|
-
refKind: reference.refKind,
|
|
6237
|
-
service_id: reference.service_id,
|
|
6238
|
-
rate: reference.rate
|
|
6239
|
-
};
|
|
6240
|
-
}
|
|
6241
|
-
function contextualKey(tagId, primary, candidate, ratePolicy) {
|
|
6242
|
-
const pctKey = "pct" in ratePolicy ? `:${ratePolicy.pct}` : "";
|
|
6243
|
-
return [
|
|
6244
|
-
"contextual",
|
|
6245
|
-
tagId,
|
|
6246
|
-
primary.fieldId,
|
|
6247
|
-
primary.nodeId,
|
|
6248
|
-
candidate.fieldId,
|
|
6249
|
-
candidate.nodeId,
|
|
6250
|
-
`${ratePolicy.kind}${pctKey}`
|
|
6251
|
-
].join("|");
|
|
6252
|
-
}
|
|
6253
|
-
function describeLabel(tag) {
|
|
6254
|
-
var _a, _b;
|
|
6255
|
-
return (_b = (_a = tag == null ? void 0 : tag.label) != null ? _a : tag == null ? void 0 : tag.id) != null ? _b : "tag";
|
|
6256
|
-
}
|
|
6257
|
-
function explainRateMismatch(policy, primary, candidate, where) {
|
|
6258
|
-
var _a, _b;
|
|
6259
|
-
const primaryLabel = `${(_a = primary.label) != null ? _a : primary.nodeId} (${primary.rate})`;
|
|
6260
|
-
const candidateLabel = `${(_b = candidate.label) != null ? _b : candidate.nodeId} (${candidate.rate})`;
|
|
6261
|
-
switch (policy.kind) {
|
|
6262
|
-
case "eq_primary":
|
|
6263
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must exactly match ${primaryLabel}.`;
|
|
6264
|
-
case "lte_primary":
|
|
6265
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must stay within ${policy.pct}% below and never above ${primaryLabel}.`;
|
|
6266
|
-
case "within_pct":
|
|
6267
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be within ${policy.pct}% of ${primaryLabel}.`;
|
|
6268
|
-
case "at_least_pct_lower":
|
|
6269
|
-
return `Rate coherence failed (${where}): ${candidateLabel} must be at least ${policy.pct}% lower than ${primaryLabel}.`;
|
|
6270
|
-
}
|
|
6271
|
-
}
|
|
6272
|
-
|
|
6273
|
-
// src/core/validate/index.ts
|
|
6274
|
-
function readVisibilitySimOpts(ctx) {
|
|
6275
|
-
const c = ctx;
|
|
6276
|
-
const simulate = c.simulateVisibility === true || c.visibilitySimulate === true || c.simulate === true;
|
|
6277
|
-
const maxStates = typeof c.maxVisibilityStates === "number" ? c.maxVisibilityStates : typeof c.visibilityMaxStates === "number" ? c.visibilityMaxStates : typeof c.maxStates === "number" ? c.maxStates : void 0;
|
|
6278
|
-
const maxDepth = typeof c.maxVisibilityDepth === "number" ? c.maxVisibilityDepth : typeof c.visibilityMaxDepth === "number" ? c.visibilityMaxDepth : typeof c.maxDepth === "number" ? c.maxDepth : void 0;
|
|
6279
|
-
const simulateAllRoots = c.simulateAllRoots === true || c.visibilitySimulateAllRoots === true;
|
|
6280
|
-
const onlyEffectfulTriggers = c.onlyEffectfulTriggers === false ? false : c.visibilityOnlyEffectfulTriggers !== false;
|
|
6281
|
-
return {
|
|
6282
|
-
simulate,
|
|
6283
|
-
maxStates,
|
|
6284
|
-
maxDepth,
|
|
6285
|
-
simulateAllRoots,
|
|
6286
|
-
onlyEffectfulTriggers
|
|
6287
|
-
};
|
|
6288
|
-
}
|
|
6289
|
-
function validate(props, ctx = {}) {
|
|
6290
|
-
var _a, _b, _c;
|
|
6291
|
-
const options = mergeValidatorOptions({}, ctx);
|
|
6292
|
-
const fallbackSettings = resolveFallbackSettings(options);
|
|
6293
|
-
const ratePolicy = resolveGlobalRatePolicy(options);
|
|
6294
|
-
const errors = [];
|
|
6295
|
-
const serviceMap = (_a = options.serviceMap) != null ? _a : {};
|
|
6296
|
-
const selectedKeys = new Set(
|
|
6297
|
-
(_b = options.selectedOptionKeys) != null ? _b : []
|
|
6298
|
-
);
|
|
6299
|
-
const tags = Array.isArray(props.filters) ? props.filters : [];
|
|
6300
|
-
const fields = Array.isArray(props.fields) ? props.fields : [];
|
|
6301
|
-
const tagById = /* @__PURE__ */ new Map();
|
|
6302
|
-
const fieldById = /* @__PURE__ */ new Map();
|
|
6303
|
-
for (const t of tags) tagById.set(t.id, t);
|
|
6304
|
-
for (const f of fields) fieldById.set(f.id, f);
|
|
6305
|
-
const v = {
|
|
6306
|
-
props,
|
|
6307
|
-
nodeMap: (_c = options.nodeMap) != null ? _c : buildNodeMap(props),
|
|
6308
|
-
options: {
|
|
6309
|
-
...options,
|
|
6310
|
-
ratePolicy,
|
|
6311
|
-
fallbackSettings
|
|
6312
|
-
},
|
|
6313
|
-
errors,
|
|
6314
|
-
serviceMap,
|
|
6315
|
-
selectedKeys,
|
|
6316
|
-
tags,
|
|
6317
|
-
fields,
|
|
6318
|
-
invalidRateFieldIds: /* @__PURE__ */ new Set(),
|
|
6319
|
-
tagById,
|
|
6320
|
-
fieldById,
|
|
6321
|
-
fieldsVisibleUnder: (_tagId) => []
|
|
6322
|
-
};
|
|
6323
|
-
validateStructure(v);
|
|
6324
|
-
validateIdentity(v);
|
|
6325
|
-
validateOptionMaps(v);
|
|
6326
|
-
validateOrderKinds(v);
|
|
6327
|
-
v.fieldsVisibleUnder = createFieldsVisibleUnder(v);
|
|
6328
|
-
const visSim = readVisibilitySimOpts(options);
|
|
6329
|
-
validateVisibility(v, visSim);
|
|
6330
|
-
applyPolicies(
|
|
6331
|
-
v.errors,
|
|
6332
|
-
v.props,
|
|
6333
|
-
v.serviceMap,
|
|
6334
|
-
v.options.policies,
|
|
6335
|
-
v.fieldsVisibleUnder,
|
|
6336
|
-
v.tags
|
|
6337
|
-
);
|
|
6338
|
-
validateServiceVsUserInput(v);
|
|
6339
|
-
validateUtilityMarkers(v);
|
|
6340
|
-
validateRates(v);
|
|
6341
|
-
if (Object.keys(serviceMap).length > 0 && tags.length > 0) {
|
|
6342
|
-
const builder = createBuilder({ serviceMap });
|
|
6343
|
-
builder.load(props);
|
|
6344
|
-
for (const tag of tags) {
|
|
6345
|
-
const diags = validateRateCoherenceDeep({
|
|
6346
|
-
builder,
|
|
6347
|
-
services: serviceMap,
|
|
6348
|
-
tagId: tag.id,
|
|
6349
|
-
ratePolicy,
|
|
6350
|
-
invalidFieldIds: v.invalidRateFieldIds
|
|
6351
|
-
});
|
|
6352
|
-
for (const diag of diags) {
|
|
6353
|
-
if (diag.kind !== "contextual") continue;
|
|
6354
|
-
errors.push({
|
|
6355
|
-
code: "rate_coherence_violation",
|
|
6356
|
-
severity: "error",
|
|
6357
|
-
message: diag.message,
|
|
6358
|
-
nodeId: diag.nodeId,
|
|
6359
|
-
details: {
|
|
6360
|
-
tagId: diag.tagId,
|
|
6361
|
-
simulationAnchor: diag.simulationAnchor,
|
|
6362
|
-
primary: diag.primary,
|
|
6363
|
-
offender: diag.offender,
|
|
6364
|
-
policy: diag.policy,
|
|
6365
|
-
policyPct: diag.policyPct,
|
|
6366
|
-
invalidFieldIds: diag.invalidFieldIds
|
|
6367
|
-
}
|
|
6368
|
-
});
|
|
6369
|
-
}
|
|
6370
|
-
}
|
|
6371
|
-
}
|
|
6372
|
-
validateConstraints(v);
|
|
6373
|
-
validateCustomFields(v);
|
|
6374
|
-
validateGlobalUtilityGuard(v);
|
|
6375
|
-
validateUnboundFields(v);
|
|
6376
|
-
validateFallbacks(v);
|
|
6377
|
-
return v.errors;
|
|
6378
|
-
}
|
|
6379
|
-
|
|
6380
6458
|
// src/core/fallback.ts
|
|
6381
6459
|
var DEFAULT_SETTINGS = {
|
|
6382
6460
|
requireConstraintFit: true,
|
|
@@ -6798,9 +6876,9 @@ function createNodeIndex(builder) {
|
|
|
6798
6876
|
if (cached) return cached;
|
|
6799
6877
|
const raw = fieldById.get(id);
|
|
6800
6878
|
if (!raw) return void 0;
|
|
6801
|
-
const
|
|
6802
|
-
const includes =
|
|
6803
|
-
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;
|
|
6804
6882
|
const bindIds = () => {
|
|
6805
6883
|
const cachedBind = fieldBindIdsCache.get(id);
|
|
6806
6884
|
if (cachedBind) return cachedBind;
|
|
@@ -6837,7 +6915,7 @@ function createNodeIndex(builder) {
|
|
|
6837
6915
|
);
|
|
6838
6916
|
},
|
|
6839
6917
|
getDescendants(tagId) {
|
|
6840
|
-
return resolveDescendants(id, includes, tagId, !
|
|
6918
|
+
return resolveDescendants(id, includes, tagId, !isButton);
|
|
6841
6919
|
}
|
|
6842
6920
|
};
|
|
6843
6921
|
fieldNodeCache.set(id, node);
|
|
@@ -6921,22 +6999,15 @@ function createNodeIndex(builder) {
|
|
|
6921
6999
|
|
|
6922
7000
|
// src/core/service-filter.ts
|
|
6923
7001
|
function filterServicesForVisibleGroup(input, deps) {
|
|
6924
|
-
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;
|
|
6925
7003
|
const svcMap = (_c = (_b = (_a = deps.builder).getServiceMap) == null ? void 0 : _b.call(_a)) != null ? _c : {};
|
|
6926
7004
|
const builderOptions = (_e = (_d = deps.builder).getOptions) == null ? void 0 : _e.call(_d);
|
|
6927
7005
|
const { context } = input;
|
|
6928
7006
|
const usedSet = new Set(context.usedServiceIds.map(String));
|
|
6929
|
-
const primary = context.usedServiceIds[0];
|
|
6930
7007
|
const explicitFallbackSettings = (_f = context.fallbackSettings) != null ? _f : context.fallback;
|
|
6931
7008
|
const resolvedRatePolicy = normalizeRatePolicy(
|
|
6932
7009
|
(_h = (_g = context.ratePolicy) != null ? _g : explicitFallbackSettings == null ? void 0 : explicitFallbackSettings.ratePolicy) != null ? _h : builderOptions == null ? void 0 : builderOptions.ratePolicy
|
|
6933
7010
|
);
|
|
6934
|
-
const fallbackSettingsSource = explicitFallbackSettings != null ? explicitFallbackSettings : builderOptions == null ? void 0 : builderOptions.fallbackSettings;
|
|
6935
|
-
const fb = {
|
|
6936
|
-
...DEFAULT_FALLBACK_SETTINGS,
|
|
6937
|
-
...fallbackSettingsSource != null ? fallbackSettingsSource : {},
|
|
6938
|
-
ratePolicy: resolvedRatePolicy
|
|
6939
|
-
};
|
|
6940
7011
|
const policySource = (_j = (_i = context.policies) != null ? _i : builderOptions == null ? void 0 : builderOptions.policies) != null ? _j : [];
|
|
6941
7012
|
const visibleServiceIds = context.selectedButtons === void 0 ? void 0 : collectVisibleServiceIds(
|
|
6942
7013
|
deps.builder,
|
|
@@ -6964,7 +7035,15 @@ function filterServicesForVisibleGroup(input, deps) {
|
|
|
6964
7035
|
cap.id,
|
|
6965
7036
|
(_k = context.effectiveConstraints) != null ? _k : {}
|
|
6966
7037
|
);
|
|
6967
|
-
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
|
+
);
|
|
6968
7047
|
const polRes = evaluatePoliciesRaw(
|
|
6969
7048
|
policySource,
|
|
6970
7049
|
[...context.usedServiceIds, id],
|
|
@@ -7083,7 +7162,9 @@ function collectVisibleServiceIds(builder, tagId, selectedButtons) {
|
|
|
7083
7162
|
const fields = (_b = props.fields) != null ? _b : [];
|
|
7084
7163
|
const tag = tags.find((t) => t.id === tagId);
|
|
7085
7164
|
if ((tag == null ? void 0 : tag.service_id) != null) out.add(String(tag.service_id));
|
|
7086
|
-
const visibleFieldIds = new Set(
|
|
7165
|
+
const visibleFieldIds = new Set(
|
|
7166
|
+
builder.visibleFields(tagId, selectedButtons)
|
|
7167
|
+
);
|
|
7087
7168
|
for (const field of fields) {
|
|
7088
7169
|
if (!visibleFieldIds.has(field.id)) continue;
|
|
7089
7170
|
if (field.service_id != null) {
|
|
@@ -7106,8 +7187,7 @@ function matchesRuleFilter(cap, rule, tagId) {
|
|
|
7106
7187
|
if (!cap) return false;
|
|
7107
7188
|
const f = rule.filter;
|
|
7108
7189
|
if (!f) return true;
|
|
7109
|
-
|
|
7110
|
-
return true;
|
|
7190
|
+
return !(f.tag_id && !toStrSet(f.tag_id).has(String(tagId)));
|
|
7111
7191
|
}
|
|
7112
7192
|
function toStrSet(v) {
|
|
7113
7193
|
const arr = Array.isArray(v) ? v : [v];
|
|
@@ -7115,6 +7195,107 @@ function toStrSet(v) {
|
|
|
7115
7195
|
for (const x of arr) s.add(String(x));
|
|
7116
7196
|
return s;
|
|
7117
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
|
+
}
|
|
7118
7299
|
|
|
7119
7300
|
// src/react/canvas/editor/editor-ids.ts
|
|
7120
7301
|
function uniqueId(ctx, base) {
|
|
@@ -7744,7 +7925,7 @@ function setService(ctx, id, input) {
|
|
|
7744
7925
|
);
|
|
7745
7926
|
}
|
|
7746
7927
|
const isOptionBased = Array.isArray(f.options) && f.options.length > 0;
|
|
7747
|
-
const
|
|
7928
|
+
const isButton = !!f.button;
|
|
7748
7929
|
if (nextRole) {
|
|
7749
7930
|
f.pricing_role = nextRole;
|
|
7750
7931
|
}
|
|
@@ -7760,7 +7941,7 @@ function setService(ctx, id, input) {
|
|
|
7760
7941
|
if ("service_id" in f) delete f.service_id;
|
|
7761
7942
|
return;
|
|
7762
7943
|
}
|
|
7763
|
-
if (!
|
|
7944
|
+
if (!isButton) {
|
|
7764
7945
|
if (hasSidKey) {
|
|
7765
7946
|
ctx.api.emit("error", {
|
|
7766
7947
|
message: "Only button fields (without options) can have a service_id.",
|
|
@@ -10942,18 +11123,23 @@ function useErrors(opts = {}) {
|
|
|
10942
11123
|
setValidating(true);
|
|
10943
11124
|
schedule(
|
|
10944
11125
|
() => {
|
|
10945
|
-
var _a2, _b2, _c2, _d2, _e2;
|
|
11126
|
+
var _a2, _b2, _c2, _d2, _e2, _f, _g;
|
|
10946
11127
|
if (token !== runTokenRef.current) return;
|
|
10947
11128
|
try {
|
|
10948
11129
|
const props = api.editor.getProps();
|
|
10949
|
-
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
|
+
});
|
|
10950
11136
|
if (token !== runTokenRef.current) return;
|
|
10951
11137
|
setValidation(toValidationRows(res != null ? res : []));
|
|
10952
11138
|
} catch (err) {
|
|
10953
11139
|
if (token !== runTokenRef.current) return;
|
|
10954
11140
|
pushLog({
|
|
10955
|
-
message: (
|
|
10956
|
-
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",
|
|
10957
11143
|
meta: err
|
|
10958
11144
|
});
|
|
10959
11145
|
setValidation([]);
|