jupiter-dynamic-forms 1.20.4 → 1.20.6
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 +327 -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,83 @@ 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
|
+
}
|
|
3090
|
+
function narrowToDirectChildOfDescendantAnchor(candidates, variable, ctx) {
|
|
3091
|
+
var _a, _b;
|
|
3092
|
+
if (variable.bindAsSequence)
|
|
3093
|
+
return candidates;
|
|
3094
|
+
if (new Set(candidates.map((candidate) => candidate.conceptId)).size <= 1)
|
|
3095
|
+
return candidates;
|
|
3096
|
+
const relationFilter = variable.filters.find(
|
|
3097
|
+
(filter2) => filter2.type === "CONCEPT_RELATION" && filter2.attributes["axis"] === "descendant"
|
|
3098
|
+
);
|
|
3099
|
+
const anchorQName = relationFilter == null ? void 0 : relationFilter.attributes["qname"];
|
|
3100
|
+
const linkrole = relationFilter == null ? void 0 : relationFilter.attributes["linkrole"];
|
|
3101
|
+
const anchorConceptId = anchorQName && linkrole ? (_a = ctx.conceptIdByQNamePerRole.get(linkrole)) == null ? void 0 : _a.get(anchorQName) : void 0;
|
|
3102
|
+
const directChildren = new Set(anchorConceptId && linkrole ? ((_b = ctx.childrenByRole.get(linkrole)) == null ? void 0 : _b.get(anchorConceptId)) || [] : []);
|
|
3103
|
+
if (directChildren.size === 0)
|
|
3104
|
+
return candidates;
|
|
3105
|
+
return candidates.filter((candidate) => directChildren.has(candidate.conceptId));
|
|
3106
|
+
}
|
|
2958
3107
|
function resolveFormulaVariable(variable, ctx) {
|
|
2959
|
-
const matched =
|
|
3108
|
+
const matched = narrowByPreferredOccurrence(
|
|
3109
|
+
narrowToDirectChildOfDescendantAnchor(narrowToCurrentPeriod(resolveMatchedCandidates(variable, ctx), variable, ctx), variable, ctx),
|
|
3110
|
+
ctx
|
|
3111
|
+
);
|
|
2960
3112
|
const values = matched.map((candidate) => {
|
|
2961
3113
|
var _a;
|
|
2962
|
-
return
|
|
2963
|
-
}).filter((value) =>
|
|
3114
|
+
return resolveRawValue((_a = ctx.formData[candidate.conceptId]) == null ? void 0 : _a[candidate.columnId]);
|
|
3115
|
+
}).filter((value) => value !== void 0);
|
|
2964
3116
|
if (variable.bindAsSequence) {
|
|
2965
3117
|
if (values.length === 0 && variable.fallbackValue === "()")
|
|
2966
3118
|
return [];
|
|
@@ -2980,6 +3132,15 @@ function resolveFormulaVariable(variable, ctx) {
|
|
|
2980
3132
|
function resolveFormulaVariableMatchCount(variable, ctx) {
|
|
2981
3133
|
return resolveMatchedCandidates(variable, ctx).length;
|
|
2982
3134
|
}
|
|
3135
|
+
function resolveFormulaVariableSetMatchCount(variables, ctx) {
|
|
3136
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3137
|
+
for (const variable of variables) {
|
|
3138
|
+
for (const candidate of resolveMatchedCandidates(variable, ctx)) {
|
|
3139
|
+
merged.set(candidateKey(candidate), candidate);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
return merged.size;
|
|
3143
|
+
}
|
|
2983
3144
|
function resolveFormulaVariableUsedFallback(variable, ctx) {
|
|
2984
3145
|
const matchCount = resolveFormulaVariableMatchCount(variable, ctx);
|
|
2985
3146
|
if (matchCount > 0)
|
|
@@ -3006,7 +3167,7 @@ function resolveVariableConceptQNames(variable) {
|
|
|
3006
3167
|
}
|
|
3007
3168
|
function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
|
|
3008
3169
|
const universe = ctx.columns.map((column2) => ({ conceptId: targetConceptId, columnId: column2.id }));
|
|
3009
|
-
const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx)));
|
|
3170
|
+
const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx, variable.filters)));
|
|
3010
3171
|
return resolved.map((candidate) => candidate.columnId);
|
|
3011
3172
|
}
|
|
3012
3173
|
function buildAssertionBindings(assertion, ctx) {
|
|
@@ -3029,6 +3190,14 @@ function buildAssertionFallbackUsage(assertion, ctx) {
|
|
|
3029
3190
|
}
|
|
3030
3191
|
return fallbackUsage;
|
|
3031
3192
|
}
|
|
3193
|
+
function checkAllVariablesBound(assertion, bindings) {
|
|
3194
|
+
for (const variable of assertion.variables) {
|
|
3195
|
+
if (bindings.get(variable.name) === void 0) {
|
|
3196
|
+
return { satisfied: false, skipReason: "unresolvable" };
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
return { satisfied: true };
|
|
3200
|
+
}
|
|
3032
3201
|
function evaluateAssertionPreconditions(preconditions, bindings, fallbackUsage = /* @__PURE__ */ new Map()) {
|
|
3033
3202
|
if (preconditions.length === 0)
|
|
3034
3203
|
return { satisfied: true };
|
|
@@ -3099,13 +3268,19 @@ class FormulaValidationService {
|
|
|
3099
3268
|
_evaluateAssertion(assertion, role, ctx, language) {
|
|
3100
3269
|
try {
|
|
3101
3270
|
const bindings = buildAssertionBindings(assertion, ctx);
|
|
3271
|
+
if (assertion.type === "VALUE_ASSERTION") {
|
|
3272
|
+
const bindingCheck = checkAllVariablesBound(assertion, bindings);
|
|
3273
|
+
if (!bindingCheck.satisfied) {
|
|
3274
|
+
return this._skippedResult(assertion, role, bindingCheck.skipReason ?? "unresolvable");
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3102
3277
|
const fallbackUsage = buildAssertionFallbackUsage(assertion, ctx);
|
|
3103
3278
|
const precondition = evaluateAssertionPreconditions(assertion.preconditions, bindings, fallbackUsage);
|
|
3104
3279
|
if (!precondition.satisfied) {
|
|
3105
3280
|
return this._skippedResult(assertion, role, precondition.skipReason ?? "unresolvable");
|
|
3106
3281
|
}
|
|
3107
3282
|
if (assertion.type === "EXISTENCE_ASSERTION") {
|
|
3108
|
-
const matchedFactCount = resolveFormulaVariableMatchCount(assertion.variables[0], ctx);
|
|
3283
|
+
const matchedFactCount = assertion.variables.length === 1 ? resolveFormulaVariableMatchCount(assertion.variables[0], ctx) : resolveFormulaVariableSetMatchCount(assertion.variables, ctx);
|
|
3109
3284
|
const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
|
|
3110
3285
|
return this._evaluatedResult(assertion, role, ctx, satisfied2, language, matchedFactCount);
|
|
3111
3286
|
}
|
|
@@ -6245,15 +6420,33 @@ JupiterConceptTree.styles = css`
|
|
|
6245
6420
|
/* Row-level highlight while a field in this row has focus. Applied to every
|
|
6246
6421
|
cell so the row stays identifiable even after the user scrolls the table
|
|
6247
6422
|
horizontally away from the focused input. */
|
|
6248
|
-
.concept-name-cell.row-focused,
|
|
6249
6423
|
.field-cell.row-focused {
|
|
6250
6424
|
background: var(--jupiter-row-focus-background, rgba(25, 118, 210, 0.14));
|
|
6251
6425
|
}
|
|
6252
6426
|
|
|
6427
|
+
/* .concept-name-cell is position:sticky with left:0, so it renders on top of
|
|
6428
|
+
field-cells that have scrolled underneath it as the user tabs across columns.
|
|
6429
|
+
It must stay fully opaque, or that translucent tint lets the scrolled-under
|
|
6430
|
+
cells' content (inputs, icons) show through and visually overlap with this
|
|
6431
|
+
cell's label/badge. Layer the tint as a background-image over an explicit
|
|
6432
|
+
opaque background-color instead of replacing the background outright. */
|
|
6253
6433
|
.concept-name-cell.row-focused {
|
|
6434
|
+
background-image: linear-gradient(
|
|
6435
|
+
var(--jupiter-row-focus-background, rgba(25, 118, 210, 0.14)),
|
|
6436
|
+
var(--jupiter-row-focus-background, rgba(25, 118, 210, 0.14))
|
|
6437
|
+
);
|
|
6438
|
+
background-color: var(--jupiter-concept-background, #f8f9fa);
|
|
6254
6439
|
box-shadow: inset 3px 0 0 0 var(--jupiter-primary-color, #1976d2);
|
|
6255
6440
|
}
|
|
6256
6441
|
|
|
6442
|
+
.concept-name-cell.abstract.row-focused {
|
|
6443
|
+
background-color: var(--bg-color-1, var(--jupiter-abstract-background, #f0f2f5));
|
|
6444
|
+
}
|
|
6445
|
+
|
|
6446
|
+
.concept-name-cell.leaf.row-focused {
|
|
6447
|
+
background-color: var(--bg-color-2, var(--jupiter-leaf-background, #fff));
|
|
6448
|
+
}
|
|
6449
|
+
|
|
6257
6450
|
.concept-info-btn {
|
|
6258
6451
|
flex-shrink: 0;
|
|
6259
6452
|
width: 22px;
|
|
@@ -11516,7 +11709,7 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
11516
11709
|
// resolution identified, via `columnId` rather than re-deriving it from dimensions.
|
|
11517
11710
|
_handleFormulaConceptClick(event) {
|
|
11518
11711
|
this._showFormulaValidationDialog = false;
|
|
11519
|
-
this.scrollToConcept(event.detail.conceptQName, void 0, void 0, event.detail.columnId);
|
|
11712
|
+
this.scrollToConcept(event.detail.conceptQName, void 0, void 0, event.detail.columnId, event.detail.conceptId);
|
|
11520
11713
|
}
|
|
11521
11714
|
_handleRoleFilterApply(event) {
|
|
11522
11715
|
var _a, _b;
|
|
@@ -12716,7 +12909,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12716
12909
|
var _a2;
|
|
12717
12910
|
return {
|
|
12718
12911
|
roleURI: ((_a2 = section2.metadata) == null ? void 0 : _a2.roleURI) || section2.id,
|
|
12719
|
-
concepts: section2.concepts
|
|
12912
|
+
concepts: section2.concepts,
|
|
12913
|
+
columns: section2.columns
|
|
12720
12914
|
};
|
|
12721
12915
|
});
|
|
12722
12916
|
const ctx = buildFormulaResolutionContext(
|
|
@@ -12725,7 +12919,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
12725
12919
|
conceptSections,
|
|
12726
12920
|
(_h = (_g = (_f = this.xbrlInput) == null ? void 0 : _f.hypercubes) == null ? void 0 : _g[0]) == null ? void 0 : _h.roles,
|
|
12727
12921
|
this.periodStartDate,
|
|
12728
|
-
this.periodEndDate
|
|
12922
|
+
this.periodEndDate,
|
|
12923
|
+
this._typedMemberData
|
|
12729
12924
|
);
|
|
12730
12925
|
const scopedXbrlInput = {
|
|
12731
12926
|
...this.xbrlInput,
|
|
@@ -14879,6 +15074,59 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14879
15074
|
}
|
|
14880
15075
|
return null;
|
|
14881
15076
|
}
|
|
15077
|
+
// Same taxonomy concept can be placed at multiple points in a role's presentation tree
|
|
15078
|
+
// with different preferred labels (e.g. a rollforward's periodStartLabel/periodEndLabel
|
|
15079
|
+
// rows) — each such occurrence gets its own ConceptTree row/id. Unlike _findConceptByName,
|
|
15080
|
+
// this collects every matching row (DFS order) so the caller can disambiguate between them
|
|
15081
|
+
// instead of always taking the first one found.
|
|
15082
|
+
_findAllConceptsByName(concepts, name) {
|
|
15083
|
+
var _a;
|
|
15084
|
+
const localName = name.includes(":") ? name.split(":").pop() : name;
|
|
15085
|
+
const matches = [];
|
|
15086
|
+
for (const c2 of concepts) {
|
|
15087
|
+
const cLocal = c2.name.includes(":") ? c2.name.split(":").pop() : c2.name;
|
|
15088
|
+
if (c2.name === name || cLocal === localName)
|
|
15089
|
+
matches.push(c2);
|
|
15090
|
+
if ((_a = c2.children) == null ? void 0 : _a.length) {
|
|
15091
|
+
matches.push(...this._findAllConceptsByName(c2.children, name));
|
|
15092
|
+
}
|
|
15093
|
+
}
|
|
15094
|
+
return matches;
|
|
15095
|
+
}
|
|
15096
|
+
_findConceptById(concepts, id) {
|
|
15097
|
+
var _a;
|
|
15098
|
+
for (const c2 of concepts) {
|
|
15099
|
+
if (c2.id === id)
|
|
15100
|
+
return c2;
|
|
15101
|
+
if ((_a = c2.children) == null ? void 0 : _a.length) {
|
|
15102
|
+
const hit = this._findConceptById(c2.children, id);
|
|
15103
|
+
if (hit)
|
|
15104
|
+
return hit;
|
|
15105
|
+
}
|
|
15106
|
+
}
|
|
15107
|
+
return null;
|
|
15108
|
+
}
|
|
15109
|
+
// Loose value comparison used to disambiguate rows that share a concept name: exact
|
|
15110
|
+
// string match, numeric equality, or numeric equality up to a power-of-ten scale
|
|
15111
|
+
// (the form may store a display value while the caller passes the raw XBRL value).
|
|
15112
|
+
_valueLooselyMatches(fieldValue, targetValue) {
|
|
15113
|
+
const fv = String(fieldValue ?? "");
|
|
15114
|
+
if (fv === targetValue)
|
|
15115
|
+
return true;
|
|
15116
|
+
const fNum = Number(fv);
|
|
15117
|
+
const tNum = Number(targetValue);
|
|
15118
|
+
if (isNaN(fNum) || isNaN(tNum))
|
|
15119
|
+
return false;
|
|
15120
|
+
if (fNum === tNum)
|
|
15121
|
+
return true;
|
|
15122
|
+
if (fNum !== 0 && tNum !== 0) {
|
|
15123
|
+
const ratio = Math.abs(tNum / fNum);
|
|
15124
|
+
const log = Math.log10(ratio);
|
|
15125
|
+
if (Math.abs(log - Math.round(log)) < 1e-4)
|
|
15126
|
+
return true;
|
|
15127
|
+
}
|
|
15128
|
+
return false;
|
|
15129
|
+
}
|
|
14882
15130
|
_findColumnByDimensions(columns, dims) {
|
|
14883
15131
|
var _a;
|
|
14884
15132
|
return (_a = columns.find((col) => {
|
|
@@ -14895,9 +15143,9 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14895
15143
|
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
15144
|
})) == null ? void 0 : _a.id;
|
|
14897
15145
|
}
|
|
14898
|
-
async scrollToConcept(conceptName, dimensions, match, columnId) {
|
|
15146
|
+
async scrollToConcept(conceptName, dimensions, match, columnId, exactConceptId) {
|
|
14899
15147
|
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}`);
|
|
15148
|
+
console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)} columnId=${columnId} exactConceptId=${exactConceptId}`);
|
|
14901
15149
|
let targetSection = null;
|
|
14902
15150
|
let targetConcept = null;
|
|
14903
15151
|
const sectionsToSearch = [
|
|
@@ -14908,11 +15156,49 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14908
15156
|
})
|
|
14909
15157
|
];
|
|
14910
15158
|
console.log(`[scrollToConcept] Searching ${sectionsToSearch.length} sections`);
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
15159
|
+
const hasValueMatch = (match == null ? void 0 : match.value) !== void 0 && (match == null ? void 0 : match.value) !== null;
|
|
15160
|
+
const targetValue = hasValueMatch ? String(match.value) : null;
|
|
15161
|
+
if (exactConceptId) {
|
|
15162
|
+
for (const section2 of sectionsToSearch) {
|
|
15163
|
+
const found = this._findConceptById(section2.concepts, exactConceptId);
|
|
15164
|
+
if (found) {
|
|
15165
|
+
console.log(`[scrollToConcept] Section "${section2.id}" matched exactConceptId="${exactConceptId}"`);
|
|
15166
|
+
targetSection = section2;
|
|
15167
|
+
targetConcept = found;
|
|
15168
|
+
break;
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
}
|
|
15172
|
+
if (!targetSection || !targetConcept) {
|
|
15173
|
+
for (const section2 of sectionsToSearch) {
|
|
15174
|
+
const candidates = this._findAllConceptsByName(section2.concepts, conceptName);
|
|
15175
|
+
if (!candidates.length)
|
|
15176
|
+
continue;
|
|
15177
|
+
const cols = section2.columns ?? this._columns;
|
|
15178
|
+
const resolvedColumnId = columnId ?? ((dimensions == null ? void 0 : dimensions.length) ? this._findColumnByDimensions(cols, dimensions) : void 0);
|
|
15179
|
+
let picked;
|
|
15180
|
+
if (candidates.length > 1) {
|
|
15181
|
+
picked = candidates.find((c2) => {
|
|
15182
|
+
const rowData = this._formData[c2.id];
|
|
15183
|
+
if (!rowData)
|
|
15184
|
+
return false;
|
|
15185
|
+
if (resolvedColumnId) {
|
|
15186
|
+
const cellValue = rowData[resolvedColumnId];
|
|
15187
|
+
if (cellValue === void 0 || cellValue === null || cellValue === "")
|
|
15188
|
+
return false;
|
|
15189
|
+
return !hasValueMatch || this._valueLooselyMatches(cellValue, targetValue);
|
|
15190
|
+
}
|
|
15191
|
+
if (hasValueMatch) {
|
|
15192
|
+
return Object.values(rowData).some(
|
|
15193
|
+
(v) => v !== void 0 && v !== null && v !== "" && this._valueLooselyMatches(v, targetValue)
|
|
15194
|
+
);
|
|
15195
|
+
}
|
|
15196
|
+
return false;
|
|
15197
|
+
});
|
|
15198
|
+
console.log(`[scrollToConcept] Section "${section2.id}" has ${candidates.length} rows named "${conceptName}" | disambiguated=${(picked == null ? void 0 : picked.id) ?? "none (using first)"}`);
|
|
15199
|
+
}
|
|
15200
|
+
const found = picked ?? candidates[0];
|
|
14914
15201
|
if (columnId) {
|
|
14915
|
-
const cols = section2.columns ?? this._columns;
|
|
14916
15202
|
const hasColumn = cols.some((c2) => c2.id === columnId);
|
|
14917
15203
|
console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | columnId="${columnId}" present=${hasColumn}`);
|
|
14918
15204
|
if (hasColumn) {
|
|
@@ -14924,10 +15210,8 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14924
15210
|
targetConcept = found;
|
|
14925
15211
|
}
|
|
14926
15212
|
} 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) {
|
|
15213
|
+
console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | colMatch=${resolvedColumnId ?? "null"} | cols=${cols.map((c2) => c2.id).join(",")}`);
|
|
15214
|
+
if (resolvedColumnId) {
|
|
14931
15215
|
targetSection = section2;
|
|
14932
15216
|
targetConcept = found;
|
|
14933
15217
|
break;
|
|
@@ -14954,8 +15238,6 @@ let JupiterDynamicForm = class extends LitElement {
|
|
|
14954
15238
|
}
|
|
14955
15239
|
const columns = targetSection.columns ?? this._columns;
|
|
14956
15240
|
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
15241
|
if (columnId) {
|
|
14960
15242
|
targetColumnId = columnId;
|
|
14961
15243
|
} else if (dimensions == null ? void 0 : dimensions.length) {
|