jupiter-dynamic-forms 1.20.4 → 1.20.5
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/concept-tree.d.ts.map +1 -1
- package/dist/core/dynamic-form.d.ts +4 -1
- package/dist/core/dynamic-form.d.ts.map +1 -1
- package/dist/index.js +33 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -45
- package/dist/index.mjs.map +1 -1
- package/dist/schema/xbrl-types.d.ts +1 -1
- package/dist/schema/xbrl-types.d.ts.map +1 -1
- package/dist/utils/formula-expression-evaluator.d.ts +8 -8
- package/dist/utils/formula-expression-evaluator.d.ts.map +1 -1
- package/dist/utils/formula-precondition-evaluator.d.ts +13 -0
- package/dist/utils/formula-precondition-evaluator.d.ts.map +1 -1
- package/dist/utils/formula-resolution-context.d.ts +39 -1
- package/dist/utils/formula-resolution-context.d.ts.map +1 -1
- package/dist/utils/formula-variable-resolver.d.ts +8 -0
- package/dist/utils/formula-variable-resolver.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2493,6 +2493,10 @@ class Parser {
|
|
|
2493
2493
|
this.next();
|
|
2494
2494
|
return { kind: "number", value: Number(token.value) };
|
|
2495
2495
|
}
|
|
2496
|
+
if (token.type === "STRING") {
|
|
2497
|
+
this.next();
|
|
2498
|
+
return { kind: "string", value: token.value };
|
|
2499
|
+
}
|
|
2496
2500
|
if (token.type === "VARIABLE") {
|
|
2497
2501
|
this.next();
|
|
2498
2502
|
return { kind: "variable", name: token.value };
|
|
@@ -2520,20 +2524,30 @@ class Parser {
|
|
|
2520
2524
|
function lookup(bindings, name) {
|
|
2521
2525
|
return bindings.get(name);
|
|
2522
2526
|
}
|
|
2523
|
-
function evalSum(variable, bindings) {
|
|
2527
|
+
function evalSum(variable, bindings, test) {
|
|
2524
2528
|
const binding = lookup(bindings, variable);
|
|
2525
2529
|
if (binding === void 0)
|
|
2526
2530
|
return 0;
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2531
|
+
const values = Array.isArray(binding) ? binding : [binding];
|
|
2532
|
+
return values.reduce((total, value) => {
|
|
2533
|
+
if (typeof value === "string") {
|
|
2534
|
+
throw new FormulaExpressionError(
|
|
2535
|
+
`Variable "$${variable}" resolved to a non-numeric value ("${value}") and cannot be used in sum(...)`,
|
|
2536
|
+
test,
|
|
2537
|
+
"unsupported-syntax"
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
return total + value;
|
|
2541
|
+
}, 0);
|
|
2530
2542
|
}
|
|
2531
2543
|
function evalValue(node, bindings, test) {
|
|
2532
2544
|
switch (node.kind) {
|
|
2533
2545
|
case "number":
|
|
2534
2546
|
return node.value;
|
|
2547
|
+
case "string":
|
|
2548
|
+
return node.value;
|
|
2535
2549
|
case "sum":
|
|
2536
|
-
return evalSum(node.variable, bindings);
|
|
2550
|
+
return evalSum(node.variable, bindings, test);
|
|
2537
2551
|
case "variable": {
|
|
2538
2552
|
const binding = lookup(bindings, node.name);
|
|
2539
2553
|
if (binding === void 0) {
|
|
@@ -2553,7 +2567,17 @@ function evalValue(node, bindings, test) {
|
|
|
2553
2567
|
return binding;
|
|
2554
2568
|
}
|
|
2555
2569
|
case "additive":
|
|
2556
|
-
return node.terms.reduce((total, { sign, term }) =>
|
|
2570
|
+
return node.terms.reduce((total, { sign, term }) => {
|
|
2571
|
+
const value = evalValue(term, bindings, test);
|
|
2572
|
+
if (typeof value === "string") {
|
|
2573
|
+
throw new FormulaExpressionError(
|
|
2574
|
+
`Non-numeric value ("${value}") cannot be used in arithmetic (+ / -)`,
|
|
2575
|
+
test,
|
|
2576
|
+
"unsupported-syntax"
|
|
2577
|
+
);
|
|
2578
|
+
}
|
|
2579
|
+
return total + sign * value;
|
|
2580
|
+
}, 0);
|
|
2557
2581
|
}
|
|
2558
2582
|
}
|
|
2559
2583
|
function evalBool(node, bindings, test, fallbackUsage) {
|
|
@@ -2582,6 +2606,9 @@ function evalBool(node, bindings, test, fallbackUsage) {
|
|
|
2582
2606
|
case "comparison": {
|
|
2583
2607
|
const left = evalValue(node.left, bindings, test);
|
|
2584
2608
|
const right = evalValue(node.right, bindings, test);
|
|
2609
|
+
if (typeof left === "string" || typeof right === "string") {
|
|
2610
|
+
return String(left) === String(right);
|
|
2611
|
+
}
|
|
2585
2612
|
return Math.abs(left - right) < CALCULATION_TOLERANCE;
|
|
2586
2613
|
}
|
|
2587
2614
|
}
|
|
@@ -2721,7 +2748,8 @@ function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName
|
|
|
2721
2748
|
const info = {
|
|
2722
2749
|
conceptId: concept.id,
|
|
2723
2750
|
qname: concept.name,
|
|
2724
|
-
balance: concept.balance
|
|
2751
|
+
balance: concept.balance,
|
|
2752
|
+
effectivePeriodRole: concept.effectivePeriodRole
|
|
2725
2753
|
};
|
|
2726
2754
|
conceptById.set(concept.id, info);
|
|
2727
2755
|
(conceptByQName.get(concept.name) ?? conceptByQName.set(concept.name, []).get(concept.name)).push(info);
|
|
@@ -2738,10 +2766,13 @@ function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName
|
|
|
2738
2766
|
);
|
|
2739
2767
|
children.forEach((child) => indexConceptTree(child, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex));
|
|
2740
2768
|
}
|
|
2741
|
-
function indexMemberTree(member, memberQNameToId) {
|
|
2769
|
+
function indexMemberTree(member, memberQNameToId, memberChildrenByQName) {
|
|
2742
2770
|
if (!memberQNameToId.has(member.conceptName))
|
|
2743
2771
|
memberQNameToId.set(member.conceptName, member.id);
|
|
2744
|
-
(member.
|
|
2772
|
+
if (!memberChildrenByQName.has(member.conceptName)) {
|
|
2773
|
+
memberChildrenByQName.set(member.conceptName, (member.children || []).map((child) => child.id));
|
|
2774
|
+
}
|
|
2775
|
+
(member.children || []).forEach((child) => indexMemberTree(child, memberQNameToId, memberChildrenByQName));
|
|
2745
2776
|
}
|
|
2746
2777
|
const PRESENTATION_TOTAL_GROUP_ACCESSORS = {
|
|
2747
2778
|
getId: (concept) => concept.id,
|
|
@@ -2749,12 +2780,14 @@ const PRESENTATION_TOTAL_GROUP_ACCESSORS = {
|
|
|
2749
2780
|
isAbstract: (concept) => concept.abstract === true,
|
|
2750
2781
|
getChildren: (concept) => concept.children || []
|
|
2751
2782
|
};
|
|
2752
|
-
function buildFormulaResolutionContext(formData, columns, sections, hypercubeRoles, periodStartDate, periodEndDate) {
|
|
2783
|
+
function buildFormulaResolutionContext(formData, columns, sections, hypercubeRoles, periodStartDate, periodEndDate, typedMemberData) {
|
|
2753
2784
|
const conceptByQName = /* @__PURE__ */ new Map();
|
|
2754
2785
|
const conceptById = /* @__PURE__ */ new Map();
|
|
2755
2786
|
const conceptIdByQNamePerRole = /* @__PURE__ */ new Map();
|
|
2756
2787
|
const childrenByRole = /* @__PURE__ */ new Map();
|
|
2757
2788
|
const totalGroupChildrenByRole = /* @__PURE__ */ new Map();
|
|
2789
|
+
const columnsByRole = /* @__PURE__ */ new Map();
|
|
2790
|
+
const roleURIByConceptId = /* @__PURE__ */ new Map();
|
|
2758
2791
|
sections.forEach((section2) => {
|
|
2759
2792
|
const childrenIndex = /* @__PURE__ */ new Map();
|
|
2760
2793
|
const conceptIdByQName = /* @__PURE__ */ new Map();
|
|
@@ -2763,16 +2796,22 @@ function buildFormulaResolutionContext(formData, columns, sections, hypercubeRol
|
|
|
2763
2796
|
childrenByRole.set(section2.roleURI, childrenIndex);
|
|
2764
2797
|
conceptIdByQNamePerRole.set(section2.roleURI, conceptIdByQName);
|
|
2765
2798
|
totalGroupChildrenByRole.set(section2.roleURI, buildTotalGroupChildrenIndex(section2.concepts, PRESENTATION_TOTAL_GROUP_ACCESSORS));
|
|
2799
|
+
for (const conceptId of childrenIndex.keys()) {
|
|
2800
|
+
roleURIByConceptId.set(conceptId, section2.roleURI);
|
|
2801
|
+
}
|
|
2802
|
+
if (section2.columns)
|
|
2803
|
+
columnsByRole.set(section2.roleURI, section2.columns);
|
|
2766
2804
|
});
|
|
2767
2805
|
const dimensionQNameToId = /* @__PURE__ */ new Map();
|
|
2768
2806
|
const memberQNameToId = /* @__PURE__ */ new Map();
|
|
2807
|
+
const memberChildrenByQName = /* @__PURE__ */ new Map();
|
|
2769
2808
|
(hypercubeRoles || []).forEach((role) => {
|
|
2770
2809
|
role.items.forEach((item) => {
|
|
2771
2810
|
item.dimensions.forEach((dimension) => {
|
|
2772
2811
|
if (!dimensionQNameToId.has(dimension.conceptName)) {
|
|
2773
2812
|
dimensionQNameToId.set(dimension.conceptName, dimension.id);
|
|
2774
2813
|
}
|
|
2775
|
-
(dimension.members || []).forEach((member) => indexMemberTree(member, memberQNameToId));
|
|
2814
|
+
(dimension.members || []).forEach((member) => indexMemberTree(member, memberQNameToId, memberChildrenByQName));
|
|
2776
2815
|
});
|
|
2777
2816
|
});
|
|
2778
2817
|
});
|
|
@@ -2788,7 +2827,11 @@ function buildFormulaResolutionContext(formData, columns, sections, hypercubeRol
|
|
|
2788
2827
|
totalGroupChildrenByRole,
|
|
2789
2828
|
dimensionQNameToId,
|
|
2790
2829
|
memberQNameToId,
|
|
2791
|
-
|
|
2830
|
+
memberChildrenByQName,
|
|
2831
|
+
columnsByRole,
|
|
2832
|
+
roleURIByConceptId,
|
|
2833
|
+
allCandidates: collectAllCandidates(formData),
|
|
2834
|
+
typedMemberData: typedMemberData ?? {}
|
|
2792
2835
|
};
|
|
2793
2836
|
}
|
|
2794
2837
|
function collectDescendantConceptIds(childrenIndex, rootConceptId) {
|
|
@@ -2809,10 +2852,13 @@ function candidateKey(candidate) {
|
|
|
2809
2852
|
return `${candidate.conceptId}::${candidate.columnId}`;
|
|
2810
2853
|
}
|
|
2811
2854
|
function resolveMatchedCandidates(variable, ctx) {
|
|
2812
|
-
return intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, ctx.allCandidates, ctx)));
|
|
2855
|
+
return intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, ctx.allCandidates, ctx, variable.filters)));
|
|
2813
2856
|
}
|
|
2814
|
-
function getColumn(ctx,
|
|
2815
|
-
|
|
2857
|
+
function getColumn(ctx, candidate) {
|
|
2858
|
+
var _a;
|
|
2859
|
+
const roleURI = ctx.roleURIByConceptId.get(candidate.conceptId);
|
|
2860
|
+
const roleColumn = roleURI ? (_a = ctx.columnsByRole.get(roleURI)) == null ? void 0 : _a.find((column2) => column2.id === candidate.columnId) : void 0;
|
|
2861
|
+
return roleColumn ?? ctx.columns.find((column2) => column2.id === candidate.columnId);
|
|
2816
2862
|
}
|
|
2817
2863
|
function columnMatchesExplicitDimension(column2, axisId, memberId) {
|
|
2818
2864
|
if (!(column2 == null ? void 0 : column2.dimensionData))
|
|
@@ -2824,13 +2870,22 @@ function columnMatchesExplicitDimension(column2, axisId, memberId) {
|
|
|
2824
2870
|
(combo) => combo.axisId === axisId && (memberId === void 0 || combo.memberId === memberId)
|
|
2825
2871
|
);
|
|
2826
2872
|
}
|
|
2827
|
-
function
|
|
2873
|
+
function columnDeclaresTypedDimension(column2, axisId) {
|
|
2828
2874
|
if (!(column2 == null ? void 0 : column2.dimensionData))
|
|
2829
2875
|
return false;
|
|
2830
2876
|
if (column2.dimensionData.typedMemberId !== void 0 && column2.dimensionData.axisId === axisId)
|
|
2831
2877
|
return true;
|
|
2832
2878
|
return (column2.dimensionData.typedMembers || []).some((typedMember) => typedMember.axisId === axisId);
|
|
2833
2879
|
}
|
|
2880
|
+
function columnMatchesTypedDimension(ctx, candidate, axisId) {
|
|
2881
|
+
const column2 = getColumn(ctx, candidate);
|
|
2882
|
+
if (!columnDeclaresTypedDimension(column2, axisId))
|
|
2883
|
+
return false;
|
|
2884
|
+
const chosenValues = ctx.typedMemberData[candidate.columnId];
|
|
2885
|
+
if (chosenValues === void 0)
|
|
2886
|
+
return true;
|
|
2887
|
+
return !!chosenValues[axisId];
|
|
2888
|
+
}
|
|
2834
2889
|
function paramTokenName(rawDate) {
|
|
2835
2890
|
if (!rawDate)
|
|
2836
2891
|
return void 0;
|
|
@@ -2875,7 +2930,28 @@ function unionCandidates(lists) {
|
|
|
2875
2930
|
lists.forEach((list) => list.forEach((candidate) => merged.set(candidateKey(candidate), candidate)));
|
|
2876
2931
|
return Array.from(merged.values());
|
|
2877
2932
|
}
|
|
2878
|
-
function
|
|
2933
|
+
function resolveChildMemberAxisFilter(memberQName, axisId, universe, ctx, siblingFilters) {
|
|
2934
|
+
var _a, _b;
|
|
2935
|
+
const childMemberIds = ctx.memberChildrenByQName.get(memberQName) || [];
|
|
2936
|
+
if (childMemberIds.length > 0) {
|
|
2937
|
+
const matches = universe.filter(
|
|
2938
|
+
(candidate) => childMemberIds.some((childId) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, childId))
|
|
2939
|
+
);
|
|
2940
|
+
if (matches.length > 0)
|
|
2941
|
+
return matches;
|
|
2942
|
+
}
|
|
2943
|
+
const conceptRelationFilter = siblingFilters.find(
|
|
2944
|
+
(sibling) => sibling.type === "CONCEPT_RELATION" && sibling.attributes["axis"] === "descendant"
|
|
2945
|
+
);
|
|
2946
|
+
const linkrole = conceptRelationFilter == null ? void 0 : conceptRelationFilter.attributes["linkrole"];
|
|
2947
|
+
const anchorQName = conceptRelationFilter == null ? void 0 : conceptRelationFilter.attributes["qname"];
|
|
2948
|
+
const anchorConceptId = anchorQName && linkrole ? (_a = ctx.conceptIdByQNamePerRole.get(linkrole)) == null ? void 0 : _a.get(anchorQName) : void 0;
|
|
2949
|
+
const directChildren = new Set(anchorConceptId && linkrole ? ((_b = ctx.childrenByRole.get(linkrole)) == null ? void 0 : _b.get(anchorConceptId)) || [] : []);
|
|
2950
|
+
if (directChildren.size === 0)
|
|
2951
|
+
return [];
|
|
2952
|
+
return universe.filter((candidate) => !directChildren.has(candidate.conceptId));
|
|
2953
|
+
}
|
|
2954
|
+
function matchFilter(filter2, universe, ctx, siblingFilters) {
|
|
2879
2955
|
var _a, _b;
|
|
2880
2956
|
switch (filter2.type) {
|
|
2881
2957
|
case "CONCEPT_NAME": {
|
|
@@ -2915,33 +2991,38 @@ function matchFilter(filter2, universe, ctx) {
|
|
|
2915
2991
|
const dimensionQName = filter2.attributes["dimension.qname"];
|
|
2916
2992
|
const memberQName = filter2.attributes["member.qname"];
|
|
2917
2993
|
const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
|
|
2918
|
-
const memberId = memberQName ? ctx.memberQNameToId.get(memberQName) : void 0;
|
|
2919
2994
|
if (!axisId)
|
|
2920
2995
|
return [];
|
|
2921
|
-
|
|
2996
|
+
if (memberQName && filter2.attributes["member.axis"] === "child") {
|
|
2997
|
+
return resolveChildMemberAxisFilter(memberQName, axisId, universe, ctx, siblingFilters);
|
|
2998
|
+
}
|
|
2999
|
+
const memberId = memberQName ? ctx.memberQNameToId.get(memberQName) : void 0;
|
|
3000
|
+
return universe.filter((candidate) => columnMatchesExplicitDimension(getColumn(ctx, candidate), axisId, memberId));
|
|
2922
3001
|
}
|
|
2923
3002
|
case "TYPED_DIMENSION": {
|
|
2924
3003
|
const dimensionQName = filter2.attributes["dimension.qname"];
|
|
2925
3004
|
const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
|
|
2926
3005
|
if (!axisId)
|
|
2927
3006
|
return [];
|
|
2928
|
-
return universe.filter((candidate) => columnMatchesTypedDimension(
|
|
3007
|
+
return universe.filter((candidate) => columnMatchesTypedDimension(ctx, candidate, axisId));
|
|
2929
3008
|
}
|
|
2930
3009
|
case "PERIOD_INSTANT":
|
|
2931
3010
|
case "PERIOD_END": {
|
|
2932
3011
|
const dateAttribute = filter2.attributes["date"];
|
|
2933
|
-
return universe.filter((candidate) => columnMatchesPeriod(getColumn(ctx, candidate
|
|
3012
|
+
return universe.filter((candidate) => columnMatchesPeriod(getColumn(ctx, candidate), dateAttribute, ctx));
|
|
2934
3013
|
}
|
|
2935
3014
|
case "OR_FILTER":
|
|
2936
|
-
return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx)));
|
|
3015
|
+
return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx, siblingFilters)));
|
|
2937
3016
|
case "ASPECT_COVER":
|
|
2938
3017
|
return universe;
|
|
3018
|
+
case "UNKNOWN":
|
|
3019
|
+
return universe;
|
|
2939
3020
|
default:
|
|
2940
3021
|
return [];
|
|
2941
3022
|
}
|
|
2942
3023
|
}
|
|
2943
|
-
function resolveFilter(filter2, universe, ctx) {
|
|
2944
|
-
const matched = matchFilter(filter2, universe, ctx);
|
|
3024
|
+
function resolveFilter(filter2, universe, ctx, siblingFilters) {
|
|
3025
|
+
const matched = matchFilter(filter2, universe, ctx, siblingFilters);
|
|
2945
3026
|
if (!filter2.complement)
|
|
2946
3027
|
return matched;
|
|
2947
3028
|
const matchedKeys = new Set(matched.map(candidateKey));
|
|
@@ -2955,12 +3036,63 @@ function intersectCandidates(sets) {
|
|
|
2955
3036
|
return acc.filter((candidate) => setKeys.has(candidateKey(candidate)));
|
|
2956
3037
|
});
|
|
2957
3038
|
}
|
|
3039
|
+
function resolveRawValue(raw) {
|
|
3040
|
+
if (raw === void 0 || raw === null || raw === "")
|
|
3041
|
+
return void 0;
|
|
3042
|
+
const numeric = parseFloat(String(raw));
|
|
3043
|
+
return Number.isNaN(numeric) ? String(raw) : numeric;
|
|
3044
|
+
}
|
|
3045
|
+
function hasExplicitPeriodFilter(filters) {
|
|
3046
|
+
return filters.some((filter2) => {
|
|
3047
|
+
if (filter2.type === "PERIOD_INSTANT" || filter2.type === "PERIOD_END")
|
|
3048
|
+
return true;
|
|
3049
|
+
if (filter2.type === "OR_FILTER")
|
|
3050
|
+
return hasExplicitPeriodFilter(filter2.children);
|
|
3051
|
+
return false;
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
function narrowToCurrentPeriod(candidates, variable, ctx) {
|
|
3055
|
+
if (candidates.length <= 1 || hasExplicitPeriodFilter(variable.filters))
|
|
3056
|
+
return candidates;
|
|
3057
|
+
const currentPeriodOnly = candidates.filter(
|
|
3058
|
+
(candidate) => columnEffectivePeriod(getColumn(ctx, candidate), ctx).end === ctx.periodEndDate
|
|
3059
|
+
);
|
|
3060
|
+
return currentPeriodOnly.length > 0 ? currentPeriodOnly : candidates;
|
|
3061
|
+
}
|
|
3062
|
+
function narrowByPreferredOccurrence(candidates, ctx) {
|
|
3063
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3064
|
+
candidates.forEach((candidate) => {
|
|
3065
|
+
var _a;
|
|
3066
|
+
const qname = ((_a = ctx.conceptById.get(candidate.conceptId)) == null ? void 0 : _a.qname) ?? candidate.conceptId;
|
|
3067
|
+
const key = `${candidate.columnId}::${qname}`;
|
|
3068
|
+
const group = groups.get(key);
|
|
3069
|
+
if (group)
|
|
3070
|
+
group.push(candidate);
|
|
3071
|
+
else
|
|
3072
|
+
groups.set(key, [candidate]);
|
|
3073
|
+
});
|
|
3074
|
+
const result = [];
|
|
3075
|
+
groups.forEach((group) => {
|
|
3076
|
+
if (group.length <= 1) {
|
|
3077
|
+
result.push(...group);
|
|
3078
|
+
return;
|
|
3079
|
+
}
|
|
3080
|
+
const roles = group.map((candidate) => {
|
|
3081
|
+
var _a;
|
|
3082
|
+
return (_a = ctx.conceptById.get(candidate.conceptId)) == null ? void 0 : _a.effectivePeriodRole;
|
|
3083
|
+
});
|
|
3084
|
+
const endLabelOnly = group.filter((_candidate, index) => roles[index] === "periodEndLabel");
|
|
3085
|
+
const isAmbiguous = new Set(roles).size > 1;
|
|
3086
|
+
result.push(...isAmbiguous && endLabelOnly.length > 0 ? endLabelOnly : group);
|
|
3087
|
+
});
|
|
3088
|
+
return result;
|
|
3089
|
+
}
|
|
2958
3090
|
function resolveFormulaVariable(variable, ctx) {
|
|
2959
|
-
const matched = resolveMatchedCandidates(variable, ctx);
|
|
3091
|
+
const matched = narrowByPreferredOccurrence(narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx), variable, ctx), ctx);
|
|
2960
3092
|
const values = matched.map((candidate) => {
|
|
2961
3093
|
var _a;
|
|
2962
|
-
return
|
|
2963
|
-
}).filter((value) =>
|
|
3094
|
+
return resolveRawValue((_a = ctx.formData[candidate.conceptId]) == null ? void 0 : _a[candidate.columnId]);
|
|
3095
|
+
}).filter((value) => value !== void 0);
|
|
2964
3096
|
if (variable.bindAsSequence) {
|
|
2965
3097
|
if (values.length === 0 && variable.fallbackValue === "()")
|
|
2966
3098
|
return [];
|
|
@@ -2980,6 +3112,15 @@ function resolveFormulaVariable(variable, ctx) {
|
|
|
2980
3112
|
function resolveFormulaVariableMatchCount(variable, ctx) {
|
|
2981
3113
|
return resolveMatchedCandidates(variable, ctx).length;
|
|
2982
3114
|
}
|
|
3115
|
+
function resolveFormulaVariableSetMatchCount(variables, ctx) {
|
|
3116
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3117
|
+
for (const variable of variables) {
|
|
3118
|
+
for (const candidate of resolveMatchedCandidates(variable, ctx)) {
|
|
3119
|
+
merged.set(candidateKey(candidate), candidate);
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
return merged.size;
|
|
3123
|
+
}
|
|
2983
3124
|
function resolveFormulaVariableUsedFallback(variable, ctx) {
|
|
2984
3125
|
const matchCount = resolveFormulaVariableMatchCount(variable, ctx);
|
|
2985
3126
|
if (matchCount > 0)
|
|
@@ -3006,7 +3147,7 @@ function resolveVariableConceptQNames(variable) {
|
|
|
3006
3147
|
}
|
|
3007
3148
|
function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
|
|
3008
3149
|
const universe = ctx.columns.map((column2) => ({ conceptId: targetConceptId, columnId: column2.id }));
|
|
3009
|
-
const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx)));
|
|
3150
|
+
const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx, variable.filters)));
|
|
3010
3151
|
return resolved.map((candidate) => candidate.columnId);
|
|
3011
3152
|
}
|
|
3012
3153
|
function buildAssertionBindings(assertion, ctx) {
|
|
@@ -3029,6 +3170,14 @@ function buildAssertionFallbackUsage(assertion, ctx) {
|
|
|
3029
3170
|
}
|
|
3030
3171
|
return fallbackUsage;
|
|
3031
3172
|
}
|
|
3173
|
+
function checkAllVariablesBound(assertion, bindings) {
|
|
3174
|
+
for (const variable of assertion.variables) {
|
|
3175
|
+
if (bindings.get(variable.name) === void 0) {
|
|
3176
|
+
return { satisfied: false, skipReason: "unresolvable" };
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
return { satisfied: true };
|
|
3180
|
+
}
|
|
3032
3181
|
function evaluateAssertionPreconditions(preconditions, bindings, fallbackUsage = /* @__PURE__ */ new Map()) {
|
|
3033
3182
|
if (preconditions.length === 0)
|
|
3034
3183
|
return { satisfied: true };
|
|
@@ -3099,13 +3248,19 @@ class FormulaValidationService {
|
|
|
3099
3248
|
_evaluateAssertion(assertion, role, ctx, language) {
|
|
3100
3249
|
try {
|
|
3101
3250
|
const bindings = buildAssertionBindings(assertion, ctx);
|
|
3251
|
+
if (assertion.type === "VALUE_ASSERTION") {
|
|
3252
|
+
const bindingCheck = checkAllVariablesBound(assertion, bindings);
|
|
3253
|
+
if (!bindingCheck.satisfied) {
|
|
3254
|
+
return this._skippedResult(assertion, role, bindingCheck.skipReason ?? "unresolvable");
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3102
3257
|
const fallbackUsage = buildAssertionFallbackUsage(assertion, ctx);
|
|
3103
3258
|
const precondition = evaluateAssertionPreconditions(assertion.preconditions, bindings, fallbackUsage);
|
|
3104
3259
|
if (!precondition.satisfied) {
|
|
3105
3260
|
return this._skippedResult(assertion, role, precondition.skipReason ?? "unresolvable");
|
|
3106
3261
|
}
|
|
3107
3262
|
if (assertion.type === "EXISTENCE_ASSERTION") {
|
|
3108
|
-
const matchedFactCount = resolveFormulaVariableMatchCount(assertion.variables[0], ctx);
|
|
3263
|
+
const matchedFactCount = assertion.variables.length === 1 ? resolveFormulaVariableMatchCount(assertion.variables[0], ctx) : resolveFormulaVariableSetMatchCount(assertion.variables, ctx);
|
|
3109
3264
|
const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
|
|
3110
3265
|
return this._evaluatedResult(assertion, role, ctx, satisfied2, language, matchedFactCount);
|
|
3111
3266
|
}
|
|
@@ -6245,15 +6400,33 @@ JupiterConceptTree.styles = css`
|
|
|
6245
6400
|
/* Row-level highlight while a field in this row has focus. Applied to every
|
|
6246
6401
|
cell so the row stays identifiable even after the user scrolls the table
|
|
6247
6402
|
horizontally away from the focused input. */
|
|
6248
|
-
.concept-name-cell.row-focused,
|
|
6249
6403
|
.field-cell.row-focused {
|
|
6250
6404
|
background: var(--jupiter-row-focus-background, rgba(25, 118, 210, 0.14));
|
|
6251
6405
|
}
|
|
6252
6406
|
|
|
6407
|
+
/* .concept-name-cell is position:sticky with left:0, so it renders on top of
|
|
6408
|
+
field-cells that have scrolled underneath it as the user tabs across columns.
|
|
6409
|
+
It must stay fully opaque, or that translucent tint lets the scrolled-under
|
|
6410
|
+
cells' content (inputs, icons) show through and visually overlap with this
|
|
6411
|
+
cell's label/badge. Layer the tint as a background-image over an explicit
|
|
6412
|
+
opaque background-color instead of replacing the background outright. */
|
|
6253
6413
|
.concept-name-cell.row-focused {
|
|
6414
|
+
background-image: linear-gradient(
|
|
6415
|
+
var(--jupiter-row-focus-background, rgba(25, 118, 210, 0.14)),
|
|
6416
|
+
var(--jupiter-row-focus-background, rgba(25, 118, 210, 0.14))
|
|
6417
|
+
);
|
|
6418
|
+
background-color: var(--jupiter-concept-background, #f8f9fa);
|
|
6254
6419
|
box-shadow: inset 3px 0 0 0 var(--jupiter-primary-color, #1976d2);
|
|
6255
6420
|
}
|
|
6256
6421
|
|
|
6422
|
+
.concept-name-cell.abstract.row-focused {
|
|
6423
|
+
background-color: var(--bg-color-1, var(--jupiter-abstract-background, #f0f2f5));
|
|
6424
|
+
}
|
|
6425
|
+
|
|
6426
|
+
.concept-name-cell.leaf.row-focused {
|
|
6427
|
+
background-color: var(--bg-color-2, var(--jupiter-leaf-background, #fff));
|
|
6428
|
+
}
|
|
6429
|
+
|
|
6257
6430
|
.concept-info-btn {
|
|
6258
6431
|
flex-shrink: 0;
|
|
6259
6432
|
width: 22px;
|
|
@@ -11516,7 +11689,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11516
11689
|
// resolution identified, via `columnId` rather than re-deriving it from dimensions.
|
|
11517
11690
|
_handleFormulaConceptClick(event) {
|
|
11518
11691
|
this._showFormulaValidationDialog = false;
|
|
11519
|
-
this.scrollToConcept(event.detail.conceptQName, void 0, void 0, event.detail.columnId);
|
|
11692
|
+
this.scrollToConcept(event.detail.conceptQName, void 0, void 0, event.detail.columnId, event.detail.conceptId);
|
|
11520
11693
|
}
|
|
11521
11694
|
_handleRoleFilterApply(event) {
|
|
11522
11695
|
var _a, _b;
|
|
@@ -12716,7 +12889,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12716
12889
|
var _a2;
|
|
12717
12890
|
return {
|
|
12718
12891
|
roleURI: ((_a2 = section2.metadata) == null ? void 0 : _a2.roleURI) || section2.id,
|
|
12719
|
-
concepts: section2.concepts
|
|
12892
|
+
concepts: section2.concepts,
|
|
12893
|
+
columns: section2.columns
|
|
12720
12894
|
};
|
|
12721
12895
|
});
|
|
12722
12896
|
const ctx = buildFormulaResolutionContext(
|
|
@@ -12725,7 +12899,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12725
12899
|
conceptSections,
|
|
12726
12900
|
(_h = (_g = (_f = this.xbrlInput) == null ? void 0 : _f.hypercubes) == null ? void 0 : _g[0]) == null ? void 0 : _h.roles,
|
|
12727
12901
|
this.periodStartDate,
|
|
12728
|
-
this.periodEndDate
|
|
12902
|
+
this.periodEndDate,
|
|
12903
|
+
this._typedMemberData
|
|
12729
12904
|
);
|
|
12730
12905
|
const scopedXbrlInput = {
|
|
12731
12906
|
...this.xbrlInput,
|
|
@@ -14879,6 +15054,59 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14879
15054
|
}
|
|
14880
15055
|
return null;
|
|
14881
15056
|
}
|
|
15057
|
+
// Same taxonomy concept can be placed at multiple points in a role's presentation tree
|
|
15058
|
+
// with different preferred labels (e.g. a rollforward's periodStartLabel/periodEndLabel
|
|
15059
|
+
// rows) — each such occurrence gets its own ConceptTree row/id. Unlike _findConceptByName,
|
|
15060
|
+
// this collects every matching row (DFS order) so the caller can disambiguate between them
|
|
15061
|
+
// instead of always taking the first one found.
|
|
15062
|
+
_findAllConceptsByName(concepts, name) {
|
|
15063
|
+
var _a;
|
|
15064
|
+
const localName = name.includes(":") ? name.split(":").pop() : name;
|
|
15065
|
+
const matches = [];
|
|
15066
|
+
for (const c2 of concepts) {
|
|
15067
|
+
const cLocal = c2.name.includes(":") ? c2.name.split(":").pop() : c2.name;
|
|
15068
|
+
if (c2.name === name || cLocal === localName)
|
|
15069
|
+
matches.push(c2);
|
|
15070
|
+
if ((_a = c2.children) == null ? void 0 : _a.length) {
|
|
15071
|
+
matches.push(...this._findAllConceptsByName(c2.children, name));
|
|
15072
|
+
}
|
|
15073
|
+
}
|
|
15074
|
+
return matches;
|
|
15075
|
+
}
|
|
15076
|
+
_findConceptById(concepts, id) {
|
|
15077
|
+
var _a;
|
|
15078
|
+
for (const c2 of concepts) {
|
|
15079
|
+
if (c2.id === id)
|
|
15080
|
+
return c2;
|
|
15081
|
+
if ((_a = c2.children) == null ? void 0 : _a.length) {
|
|
15082
|
+
const hit = this._findConceptById(c2.children, id);
|
|
15083
|
+
if (hit)
|
|
15084
|
+
return hit;
|
|
15085
|
+
}
|
|
15086
|
+
}
|
|
15087
|
+
return null;
|
|
15088
|
+
}
|
|
15089
|
+
// Loose value comparison used to disambiguate rows that share a concept name: exact
|
|
15090
|
+
// string match, numeric equality, or numeric equality up to a power-of-ten scale
|
|
15091
|
+
// (the form may store a display value while the caller passes the raw XBRL value).
|
|
15092
|
+
_valueLooselyMatches(fieldValue, targetValue) {
|
|
15093
|
+
const fv = String(fieldValue ?? "");
|
|
15094
|
+
if (fv === targetValue)
|
|
15095
|
+
return true;
|
|
15096
|
+
const fNum = Number(fv);
|
|
15097
|
+
const tNum = Number(targetValue);
|
|
15098
|
+
if (isNaN(fNum) || isNaN(tNum))
|
|
15099
|
+
return false;
|
|
15100
|
+
if (fNum === tNum)
|
|
15101
|
+
return true;
|
|
15102
|
+
if (fNum !== 0 && tNum !== 0) {
|
|
15103
|
+
const ratio = Math.abs(tNum / fNum);
|
|
15104
|
+
const log = Math.log10(ratio);
|
|
15105
|
+
if (Math.abs(log - Math.round(log)) < 1e-4)
|
|
15106
|
+
return true;
|
|
15107
|
+
}
|
|
15108
|
+
return false;
|
|
15109
|
+
}
|
|
14882
15110
|
_findColumnByDimensions(columns, dims) {
|
|
14883
15111
|
var _a;
|
|
14884
15112
|
return (_a = columns.find((col) => {
|
|
@@ -14895,9 +15123,9 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14895
15123
|
return dims.length === 1 && this._normalizeAxisId(col.dimensionData.axisId ?? "") === this._normalizeAxisId(dims[0].axis) && this._normalizeMemberId(col.dimensionData.memberId ?? "") === this._normalizeMemberId(dims[0].member);
|
|
14896
15124
|
})) == null ? void 0 : _a.id;
|
|
14897
15125
|
}
|
|
14898
|
-
async scrollToConcept(conceptName, dimensions, match, columnId) {
|
|
15126
|
+
async scrollToConcept(conceptName, dimensions, match, columnId, exactConceptId) {
|
|
14899
15127
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
14900
|
-
console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)} columnId=${columnId}`);
|
|
15128
|
+
console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)} columnId=${columnId} exactConceptId=${exactConceptId}`);
|
|
14901
15129
|
let targetSection = null;
|
|
14902
15130
|
let targetConcept = null;
|
|
14903
15131
|
const sectionsToSearch = [
|
|
@@ -14908,11 +15136,49 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14908
15136
|
})
|
|
14909
15137
|
];
|
|
14910
15138
|
console.log(`[scrollToConcept] Searching ${sectionsToSearch.length} sections`);
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
15139
|
+
const hasValueMatch = (match == null ? void 0 : match.value) !== void 0 && (match == null ? void 0 : match.value) !== null;
|
|
15140
|
+
const targetValue = hasValueMatch ? String(match.value) : null;
|
|
15141
|
+
if (exactConceptId) {
|
|
15142
|
+
for (const section2 of sectionsToSearch) {
|
|
15143
|
+
const found = this._findConceptById(section2.concepts, exactConceptId);
|
|
15144
|
+
if (found) {
|
|
15145
|
+
console.log(`[scrollToConcept] Section "${section2.id}" matched exactConceptId="${exactConceptId}"`);
|
|
15146
|
+
targetSection = section2;
|
|
15147
|
+
targetConcept = found;
|
|
15148
|
+
break;
|
|
15149
|
+
}
|
|
15150
|
+
}
|
|
15151
|
+
}
|
|
15152
|
+
if (!targetSection || !targetConcept) {
|
|
15153
|
+
for (const section2 of sectionsToSearch) {
|
|
15154
|
+
const candidates = this._findAllConceptsByName(section2.concepts, conceptName);
|
|
15155
|
+
if (!candidates.length)
|
|
15156
|
+
continue;
|
|
15157
|
+
const cols = section2.columns ?? this._columns;
|
|
15158
|
+
const resolvedColumnId = columnId ?? ((dimensions == null ? void 0 : dimensions.length) ? this._findColumnByDimensions(cols, dimensions) : void 0);
|
|
15159
|
+
let picked;
|
|
15160
|
+
if (candidates.length > 1) {
|
|
15161
|
+
picked = candidates.find((c2) => {
|
|
15162
|
+
const rowData = this._formData[c2.id];
|
|
15163
|
+
if (!rowData)
|
|
15164
|
+
return false;
|
|
15165
|
+
if (resolvedColumnId) {
|
|
15166
|
+
const cellValue = rowData[resolvedColumnId];
|
|
15167
|
+
if (cellValue === void 0 || cellValue === null || cellValue === "")
|
|
15168
|
+
return false;
|
|
15169
|
+
return !hasValueMatch || this._valueLooselyMatches(cellValue, targetValue);
|
|
15170
|
+
}
|
|
15171
|
+
if (hasValueMatch) {
|
|
15172
|
+
return Object.values(rowData).some(
|
|
15173
|
+
(v) => v !== void 0 && v !== null && v !== "" && this._valueLooselyMatches(v, targetValue)
|
|
15174
|
+
);
|
|
15175
|
+
}
|
|
15176
|
+
return false;
|
|
15177
|
+
});
|
|
15178
|
+
console.log(`[scrollToConcept] Section "${section2.id}" has ${candidates.length} rows named "${conceptName}" | disambiguated=${(picked == null ? void 0 : picked.id) ?? "none (using first)"}`);
|
|
15179
|
+
}
|
|
15180
|
+
const found = picked ?? candidates[0];
|
|
14914
15181
|
if (columnId) {
|
|
14915
|
-
const cols = section2.columns ?? this._columns;
|
|
14916
15182
|
const hasColumn = cols.some((c2) => c2.id === columnId);
|
|
14917
15183
|
console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | columnId="${columnId}" present=${hasColumn}`);
|
|
14918
15184
|
if (hasColumn) {
|
|
@@ -14924,10 +15190,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14924
15190
|
targetConcept = found;
|
|
14925
15191
|
}
|
|
14926
15192
|
} else if (dimensions == null ? void 0 : dimensions.length) {
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | colMatch=${colId ?? "null"} | cols=${cols.map((c2) => c2.id).join(",")}`);
|
|
14930
|
-
if (colId) {
|
|
15193
|
+
console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | colMatch=${resolvedColumnId ?? "null"} | cols=${cols.map((c2) => c2.id).join(",")}`);
|
|
15194
|
+
if (resolvedColumnId) {
|
|
14931
15195
|
targetSection = section2;
|
|
14932
15196
|
targetConcept = found;
|
|
14933
15197
|
break;
|
|
@@ -14954,8 +15218,6 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14954
15218
|
}
|
|
14955
15219
|
const columns = targetSection.columns ?? this._columns;
|
|
14956
15220
|
let targetColumnId = null;
|
|
14957
|
-
const hasValueMatch = (match == null ? void 0 : match.value) !== void 0 && (match == null ? void 0 : match.value) !== null;
|
|
14958
|
-
const targetValue = hasValueMatch ? String(match.value) : null;
|
|
14959
15221
|
if (columnId) {
|
|
14960
15222
|
targetColumnId = columnId;
|
|
14961
15223
|
} else if (dimensions == null ? void 0 : dimensions.length) {
|