ixbrl-viewer 1.4.13__py3-none-any.whl → 1.4.15__py3-none-any.whl
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.
Potentially problematic release.
This version of ixbrl-viewer might be problematic. Click here for more details.
- iXBRLViewerPlugin/__init__.py +27 -26
- iXBRLViewerPlugin/_version.py +2 -2
- iXBRLViewerPlugin/constants.py +2 -1
- iXBRLViewerPlugin/iXBRLViewer.py +17 -8
- iXBRLViewerPlugin/ui.py +5 -4
- iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
- iXBRLViewerPlugin/viewer/src/html/inspector.html +27 -0
- iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +18 -1
- iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +18 -1
- iXBRLViewerPlugin/viewer/src/icons/calculator.svg +13 -0
- iXBRLViewerPlugin/viewer/src/icons/circle-cross.svg +11 -0
- iXBRLViewerPlugin/viewer/src/icons/circle-tick.svg +11 -0
- iXBRLViewerPlugin/viewer/src/icons/dimension.svg +1 -5
- iXBRLViewerPlugin/viewer/src/icons/member.svg +2 -5
- iXBRLViewerPlugin/viewer/src/icons/multi-tag.svg +10 -0
- iXBRLViewerPlugin/viewer/src/js/accordian.js +2 -2
- iXBRLViewerPlugin/viewer/src/js/calculation.js +202 -0
- iXBRLViewerPlugin/viewer/src/js/calculation.test.js +306 -0
- iXBRLViewerPlugin/viewer/src/js/calculationInspector.js +190 -0
- iXBRLViewerPlugin/viewer/src/js/concept.js +7 -1
- iXBRLViewerPlugin/viewer/src/js/fact.js +59 -5
- iXBRLViewerPlugin/viewer/src/js/factset.js +54 -5
- iXBRLViewerPlugin/viewer/src/js/factset.test.js +71 -5
- iXBRLViewerPlugin/viewer/src/js/inspector.js +85 -30
- iXBRLViewerPlugin/viewer/src/js/interval.js +70 -0
- iXBRLViewerPlugin/viewer/src/js/interval.test.js +153 -0
- iXBRLViewerPlugin/viewer/src/js/test-utils.js +19 -0
- iXBRLViewerPlugin/viewer/src/js/viewer.js +6 -8
- iXBRLViewerPlugin/viewer/src/less/accordian.less +2 -2
- iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +83 -0
- iXBRLViewerPlugin/viewer/src/less/colours.less +2 -0
- iXBRLViewerPlugin/viewer/src/less/common.less +3 -3
- iXBRLViewerPlugin/viewer/src/less/dialog.less +8 -4
- iXBRLViewerPlugin/viewer/src/less/inspector.less +69 -25
- iXBRLViewerPlugin/viewer/src/less/validation-report.less +1 -2
- iXBRLViewerPlugin/viewer/src/less/viewer.less +12 -12
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/METADATA +3 -3
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/RECORD +43 -34
- iXBRLViewerPlugin/viewer/src/js/calculations.js +0 -111
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/LICENSE +0 -0
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/NOTICE +0 -0
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/WHEEL +0 -0
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/entry_points.txt +0 -0
- {ixbrl_viewer-1.4.13.dist-info → ixbrl_viewer-1.4.15.dist-info}/top_level.txt +0 -0
|
@@ -2,16 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
import { Fact } from "./fact.js";
|
|
4
4
|
import { Footnote } from "./footnote.js";
|
|
5
|
+
import { Interval } from './interval.js';
|
|
5
6
|
|
|
6
7
|
export class FactSet {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
constructor (items) {
|
|
10
|
+
this.itemMap = new Map();
|
|
11
|
+
for (const item of items ?? []) {
|
|
12
|
+
this.add(item)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add(item) {
|
|
17
|
+
this.itemMap.set(item.vuid, item);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
items() {
|
|
21
|
+
return Array.from(this.itemMap.values());
|
|
9
22
|
}
|
|
10
23
|
|
|
11
24
|
/* Returns the union of dimensions present on facts in the set */
|
|
12
25
|
_allDimensions() {
|
|
13
26
|
const dims = {};
|
|
14
|
-
const facts = this.
|
|
27
|
+
const facts = this.items().filter((item) => item instanceof Fact);
|
|
15
28
|
for (const fact of facts) {
|
|
16
29
|
const dd = Object.keys(fact.dimensions());
|
|
17
30
|
for (var j = 0; j < dd.length; j++) {
|
|
@@ -30,7 +43,7 @@ export class FactSet {
|
|
|
30
43
|
*/
|
|
31
44
|
minimallyUniqueLabel(fact) {
|
|
32
45
|
if (!this._minimallyUniqueLabels) {
|
|
33
|
-
var facts = this.
|
|
46
|
+
var facts = this.items().filter((item) => item instanceof Fact);
|
|
34
47
|
var allLabels = {};
|
|
35
48
|
var allAspects = ["c", "p"].concat(this._allDimensions());
|
|
36
49
|
/* Assemble a map of arrays of all aspect labels for all facts, in a
|
|
@@ -86,7 +99,7 @@ export class FactSet {
|
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
|
|
89
|
-
this.
|
|
102
|
+
this.items().filter((item) => item instanceof Footnote).forEach((fn) => {
|
|
90
103
|
uniqueLabels[fn.vuid] = [fn.title];
|
|
91
104
|
});
|
|
92
105
|
|
|
@@ -94,4 +107,40 @@ export class FactSet {
|
|
|
94
107
|
}
|
|
95
108
|
return this._minimallyUniqueLabels[fact.vuid].join(", ");
|
|
96
109
|
}
|
|
110
|
+
|
|
111
|
+
isEmpty() {
|
|
112
|
+
return this.itemMap.size == 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* Returns an Interval for the intersection of all values in the set, or
|
|
117
|
+
* undefined if there is no intersection (inconsistent duplicates)
|
|
118
|
+
*/
|
|
119
|
+
valueIntersection() {
|
|
120
|
+
const duplicates = this.items().map(fact => Interval.fromFact(fact));
|
|
121
|
+
return Interval.intersection(...duplicates);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
completeDuplicates() {
|
|
125
|
+
return this.items().every(f => f.isCompleteDuplicate(this.items()[0]));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
isConsistent() {
|
|
129
|
+
if (this.itemMap.size == 0) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
const duplicates = this.items().map(fact => Interval.fromFact(fact));
|
|
133
|
+
return Interval.intersection(...duplicates) !== undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
size() {
|
|
137
|
+
return this.itemMap.size;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* Return the most precise (highest decimals) value within the set.
|
|
142
|
+
*/
|
|
143
|
+
mostPrecise() {
|
|
144
|
+
return this.items().reduce((a, b) => b.isMorePrecise(a) ? b : a);
|
|
145
|
+
}
|
|
97
146
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// See COPYRIGHT.md for copyright information
|
|
2
2
|
|
|
3
3
|
import { FactSet } from "./factset.js";
|
|
4
|
+
import { Fact } from "./fact.js";
|
|
4
5
|
import { NAMESPACE_ISO4217, viewerUniqueId } from "./util.js";
|
|
5
6
|
import { ReportSet } from "./reportset.js";
|
|
7
|
+
import './test-utils.js';
|
|
6
8
|
|
|
7
9
|
var i = 0;
|
|
8
10
|
|
|
@@ -71,7 +73,7 @@ var testReportData = {
|
|
|
71
73
|
}
|
|
72
74
|
};
|
|
73
75
|
|
|
74
|
-
function
|
|
76
|
+
function testReportSet(facts) {
|
|
75
77
|
// Deep copy of standing data
|
|
76
78
|
const data = JSON.parse(JSON.stringify(testReportData));
|
|
77
79
|
data.facts = facts;
|
|
@@ -90,7 +92,7 @@ function getFact(reportSet, id) {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
describe("Minimally unique labels (non-dimensional)", () => {
|
|
93
|
-
const reportSet =
|
|
95
|
+
const reportSet = testReportSet({
|
|
94
96
|
"f1": testFact({"c": "eg:Concept1", "p": "2018-01-01"}),
|
|
95
97
|
"f2": testFact({"c": "eg:Concept2", "p": "2018-01-01"}),
|
|
96
98
|
"f3": testFact({"c": "eg:Concept2", "p": "2019-01-01"}),
|
|
@@ -133,7 +135,7 @@ describe("Minimally unique labels (non-dimensional)", () => {
|
|
|
133
135
|
});
|
|
134
136
|
|
|
135
137
|
describe("Minimally unique labels (dimensional)", () => {
|
|
136
|
-
const reportSet =
|
|
138
|
+
const reportSet = testReportSet({
|
|
137
139
|
"f1": testFact({"c": "eg:Concept1", "p": "2018-01-01", "eg:Dimension1": "eg:DimensionValue1"}),
|
|
138
140
|
"f2": testFact({"c": "eg:Concept1", "p": "2018-01-01", "eg:Dimension1": "eg:DimensionValue2"}),
|
|
139
141
|
"f3": testFact({"c": "eg:Concept1", "p": "2019-01-01", "eg:Dimension1": "eg:DimensionValue2"}),
|
|
@@ -172,7 +174,7 @@ describe("Minimally unique labels (dimensional)", () => {
|
|
|
172
174
|
});
|
|
173
175
|
|
|
174
176
|
describe("Minimally unique labels (duplicate facts)", () => {
|
|
175
|
-
const reportSet =
|
|
177
|
+
const reportSet = testReportSet({
|
|
176
178
|
"f1": testFact({"c": "eg:Concept1", "p": "2018-01-01", "eg:Dimension1": "eg:DimensionValue1"}),
|
|
177
179
|
"f2": testFact({"c": "eg:Concept1", "p": "2018-01-01", "eg:Dimension1": "eg:DimensionValue1"}),
|
|
178
180
|
});
|
|
@@ -189,7 +191,7 @@ describe("Minimally unique labels (duplicate facts)", () => {
|
|
|
189
191
|
});
|
|
190
192
|
|
|
191
193
|
describe("Minimally unique labels (missing labels)", () => {
|
|
192
|
-
const reportSet =
|
|
194
|
+
const reportSet = testReportSet({
|
|
193
195
|
"f1": testFact({"c": "eg:Concept1", "p": "2018-01-01" }),
|
|
194
196
|
"f2": testFact({"c": "eg:Concept4", "p": "2018-01-01" }),
|
|
195
197
|
});
|
|
@@ -204,3 +206,67 @@ describe("Minimally unique labels (missing labels)", () => {
|
|
|
204
206
|
expect(fs.minimallyUniqueLabel(f2)).toEqual("eg:Concept4");
|
|
205
207
|
});
|
|
206
208
|
});
|
|
209
|
+
|
|
210
|
+
function numericTestFact(value, decimals) {
|
|
211
|
+
var factData = { "d": decimals, "v": value, "a": { "c": "eg:Concept1", "u": "eg:pure" }};
|
|
212
|
+
return factData;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
describe("Consistency", () => {
|
|
216
|
+
const reportSet = testReportSet({
|
|
217
|
+
"f1": numericTestFact(150, -1), // 145-155
|
|
218
|
+
"f2": numericTestFact(200, -2), // 150-250
|
|
219
|
+
"f3": numericTestFact(140, -1), // 135-145
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const f1 = getFact(reportSet, "f1");
|
|
223
|
+
const f2 = getFact(reportSet, "f2");
|
|
224
|
+
const f3 = getFact(reportSet, "f3");
|
|
225
|
+
|
|
226
|
+
test("Inconsistent fact set", () => {
|
|
227
|
+
const factSet = new FactSet([f1, f2, f3]);
|
|
228
|
+
expect(factSet.valueIntersection()).toBeUndefined();
|
|
229
|
+
expect(factSet.isConsistent()).toBeFalsy();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("Consistent fact sets - overlap", () => {
|
|
233
|
+
const factSet = new FactSet([f1, f2]);
|
|
234
|
+
const intersection = factSet.valueIntersection();
|
|
235
|
+
expect(intersection.a).toEqualDecimal(150);
|
|
236
|
+
expect(intersection.b).toEqualDecimal(155);
|
|
237
|
+
expect(factSet.isConsistent()).toBeTruthy();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("Consistent fact sets - bounds coincide", () => {
|
|
241
|
+
const factSet = new FactSet([f1, f3]);
|
|
242
|
+
const intersection = factSet.valueIntersection();
|
|
243
|
+
expect(intersection.a).toEqualDecimal(145);
|
|
244
|
+
expect(intersection.b).toEqualDecimal(145);
|
|
245
|
+
expect(factSet.isConsistent()).toBeTruthy();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe("Most precise", () => {
|
|
251
|
+
const reportSet = testReportSet({
|
|
252
|
+
"f1": numericTestFact(150, -1),
|
|
253
|
+
"f2": numericTestFact(200, 0),
|
|
254
|
+
"f3": numericTestFact(140, 2),
|
|
255
|
+
"f4": numericTestFact(160, undefined)
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const f1 = getFact(reportSet, "f1");
|
|
259
|
+
const f2 = getFact(reportSet, "f2");
|
|
260
|
+
const f3 = getFact(reportSet, "f3");
|
|
261
|
+
const f4 = getFact(reportSet, "f4");
|
|
262
|
+
|
|
263
|
+
test("Most precise - finite decimals", () => {
|
|
264
|
+
const factSet = new FactSet([f1, f2, f3]);
|
|
265
|
+
expect(factSet.mostPrecise()).toEqual(f3);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("Most precise - mixture of finite and infinite", () => {
|
|
269
|
+
const factSet = new FactSet([f1, f2, f3, f4]);
|
|
270
|
+
expect(factSet.mostPrecise()).toEqual(f4);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
// See COPYRIGHT.md for copyright information
|
|
2
2
|
|
|
3
3
|
import $ from 'jquery'
|
|
4
|
+
import i18next from 'i18next';
|
|
5
|
+
import jqueryI18next from 'jquery-i18next';
|
|
4
6
|
import { formatNumber, wrapLabel, truncateLabel, runGenerator, SHOW_FACT, HIGHLIGHT_COLORS, viewerUniqueId } from "./util.js";
|
|
5
7
|
import { ReportSearch } from "./search.js";
|
|
6
|
-
import { Calculation } from "./calculations.js";
|
|
7
8
|
import { IXBRLChart } from './chart.js';
|
|
8
|
-
import i18next from 'i18next';
|
|
9
|
-
import jqueryI18next from 'jquery-i18next';
|
|
10
9
|
import { ViewerOptions } from './viewerOptions.js';
|
|
11
10
|
import { Identifiers } from './identifiers.js';
|
|
12
11
|
import { Menu } from './menu.js';
|
|
@@ -17,6 +16,9 @@ import { Footnote } from './footnote.js';
|
|
|
17
16
|
import { ValidationReportDialog } from './validationreport.js';
|
|
18
17
|
import { TextBlockViewerDialog } from './textblockviewer.js';
|
|
19
18
|
import { MessageBox } from './messagebox.js';
|
|
19
|
+
import { Interval } from './interval.js';
|
|
20
|
+
import { Calculation } from "./calculation.js";
|
|
21
|
+
import { CalculationInspector } from './calculationInspector.js';
|
|
20
22
|
import { ReportSetOutline } from './outline.js';
|
|
21
23
|
import { DIMENSIONS_KEY, DocumentSummary, MEMBERS_KEY, PRIMARY_ITEMS_KEY, TOTAL_KEY } from './summary.js';
|
|
22
24
|
|
|
@@ -27,6 +29,7 @@ export class Inspector {
|
|
|
27
29
|
this._iv = iv;
|
|
28
30
|
this._viewerOptions = new ViewerOptions()
|
|
29
31
|
this._currentItem = null;
|
|
32
|
+
this._useCalc11 = true;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
i18nInit() {
|
|
@@ -215,6 +218,7 @@ export class Inspector {
|
|
|
215
218
|
const iv = this._iv;
|
|
216
219
|
this._toolbarMenu.reset();
|
|
217
220
|
this._toolbarMenu.addCheckboxItem(i18next.t("toolbar.xbrlElements"), (checked) => this.highlightAllTags(checked), "highlight-tags", null, this._iv.options.highlightTagsOnStartup);
|
|
221
|
+
this._toolbarMenu.addCheckboxItem(i18next.t("calculation.calculations11"), (useCalc11) => this.setCalculationMode(useCalc11), "calculation-mode", "select-language", this._useCalc11);
|
|
218
222
|
if (iv.isReviewModeEnabled()) {
|
|
219
223
|
this._toolbarMenu.addCheckboxItem("Untagged Numbers", function (checked) {
|
|
220
224
|
const body = iv.viewer.contents().find("body");
|
|
@@ -262,6 +266,13 @@ export class Inspector {
|
|
|
262
266
|
}
|
|
263
267
|
}
|
|
264
268
|
|
|
269
|
+
setCalculationMode(useCalc11) {
|
|
270
|
+
this._useCalc11 = useCalc11;
|
|
271
|
+
if (this._currentItem instanceof Fact) {
|
|
272
|
+
this.updateCalculation(this._currentItem);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
265
276
|
highlightAllTags(checked) {
|
|
266
277
|
this._viewer.highlightAllTags(checked, this._reportSet.namespaceGroups());
|
|
267
278
|
}
|
|
@@ -464,7 +475,7 @@ export class Inspector {
|
|
|
464
475
|
}
|
|
465
476
|
|
|
466
477
|
updateCalculation(fact, elr) {
|
|
467
|
-
$('.calculations .tree').empty().append(this._calculationHTML(fact
|
|
478
|
+
$('.calculations .tree').empty().append(this._calculationHTML(fact));
|
|
468
479
|
}
|
|
469
480
|
|
|
470
481
|
createSummary() {
|
|
@@ -682,47 +693,91 @@ export class Inspector {
|
|
|
682
693
|
}
|
|
683
694
|
|
|
684
695
|
_calculationHTML(fact, elr) {
|
|
685
|
-
const calc = new Calculation(fact);
|
|
696
|
+
const calc = new Calculation(fact, this._useCalc11);
|
|
686
697
|
if (!calc.hasCalculations()) {
|
|
687
698
|
return "";
|
|
688
699
|
}
|
|
689
700
|
const tableFacts = this._viewer.factsInSameTable(fact);
|
|
690
|
-
|
|
691
|
-
elr = calc.bestELRForFactSet(tableFacts);
|
|
692
|
-
}
|
|
701
|
+
const selectedELR = calc.bestELRForFactSet(tableFacts);
|
|
693
702
|
const report = fact.report;
|
|
694
703
|
const inspector = this;
|
|
695
704
|
const a = new Accordian();
|
|
696
705
|
|
|
697
|
-
for (const
|
|
698
|
-
const label = report.getRoleLabel(
|
|
699
|
-
|
|
700
|
-
const rCalc = calc.resolvedCalculation(e);
|
|
706
|
+
for (const rCalc of calc.resolvedCalculations()) {
|
|
707
|
+
const label = report.getRoleLabel(rCalc.elr);
|
|
701
708
|
const calcBody = $('<div></div>');
|
|
702
|
-
|
|
703
|
-
|
|
709
|
+
const calcTable = $('<table></table>')
|
|
710
|
+
.addClass("calculation-table")
|
|
711
|
+
.appendTo(calcBody);
|
|
712
|
+
|
|
713
|
+
for (const r of rCalc.rows) {
|
|
714
|
+
const itemHTML = $("<tr></tr>")
|
|
704
715
|
.addClass("item")
|
|
705
|
-
.append($("<
|
|
706
|
-
.append($("<
|
|
707
|
-
.
|
|
716
|
+
.append($("<td></td>").addClass("weight").text(r.weightSign + " "))
|
|
717
|
+
.append($("<td></td>").addClass("concept-name").text(r.concept.label()))
|
|
718
|
+
.append($("<td></td>").addClass("value"))
|
|
719
|
+
.appendTo(calcTable);
|
|
708
720
|
|
|
709
|
-
|
|
710
|
-
|
|
721
|
+
if (!r.facts.isEmpty()) {
|
|
722
|
+
itemHTML.addClass("calc-fact-link");
|
|
711
723
|
itemHTML.addClass("calc-fact-link");
|
|
712
|
-
itemHTML.data('ivids',
|
|
713
|
-
itemHTML.click(() =>
|
|
714
|
-
itemHTML.mouseenter(() =>
|
|
715
|
-
itemHTML.mouseleave(() =>
|
|
716
|
-
|
|
724
|
+
itemHTML.data('ivids', r.facts.items().map(f => f.vuid));
|
|
725
|
+
itemHTML.click(() => this.selectItem(r.facts.items[0].vuid));
|
|
726
|
+
itemHTML.mouseenter(() => r.facts.items().forEach(f => this._viewer.linkedHighlightFact(f)));
|
|
727
|
+
itemHTML.mouseleave(() => r.facts.items().forEach(f => this._viewer.clearLinkedHighlightFact(f)));
|
|
728
|
+
r.facts.items().forEach(f => this._viewer.highlightRelatedFact(f));
|
|
729
|
+
itemHTML.find(".value").text(r.facts.mostPrecise().readableValue());
|
|
717
730
|
}
|
|
718
731
|
}
|
|
719
|
-
$("<
|
|
720
|
-
.append($("<
|
|
721
|
-
.append($("<
|
|
732
|
+
$("<tr></tr>").addClass("item").addClass("total")
|
|
733
|
+
.append($("<td></td>").addClass("weight"))
|
|
734
|
+
.append($("<td></td>").addClass("concept-name").text(fact.concept().label()))
|
|
735
|
+
.append($("<td></td>").addClass("value").text(fact.readableValue()))
|
|
736
|
+
.appendTo(calcTable);
|
|
737
|
+
|
|
738
|
+
const calcStatusIcon = $("<span></span>");
|
|
739
|
+
const cardTitle = $("<span></span>")
|
|
740
|
+
.append(calcStatusIcon)
|
|
741
|
+
.append($("<span></span>").text(label));
|
|
742
|
+
const calcStatusText = $("<span></span>");
|
|
743
|
+
const calcDetailsLink = $("<span></span>")
|
|
744
|
+
.addClass("calculation-details-link")
|
|
745
|
+
.attr("title", i18next.t('factDetails.viewCalculationDetails'))
|
|
746
|
+
.text("details")
|
|
747
|
+
.click((e) => {
|
|
748
|
+
const dialog = new CalculationInspector();
|
|
749
|
+
dialog.displayCalculation(rCalc);
|
|
750
|
+
dialog.show();
|
|
751
|
+
e.stopPropagation();
|
|
752
|
+
})
|
|
753
|
+
const calcStatus = $("<p></p>")
|
|
754
|
+
.append(calcStatusText)
|
|
755
|
+
.append($("<span></span>").text(" ("))
|
|
756
|
+
.append(calcDetailsLink)
|
|
757
|
+
.append($("<span></span>").text(")"))
|
|
722
758
|
.appendTo(calcBody);
|
|
759
|
+
if (rCalc.binds()) {
|
|
760
|
+
if (rCalc.isConsistent()) {
|
|
761
|
+
calcStatusIcon
|
|
762
|
+
.addClass("consistent-flag")
|
|
763
|
+
.attr("title", i18next.t('factDetails.calculationIsConsistent'))
|
|
764
|
+
calcStatusText.text(i18next.t('factDetails.calculationIsConsistent'));
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
calcStatusIcon
|
|
768
|
+
.addClass("inconsistent-flag")
|
|
769
|
+
.attr("title", i18next.t('factDetails.calculationIsInconsistent'))
|
|
770
|
+
calcStatusText.text(i18next.t('factDetails.calculationIsInconsistent'));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
else if (rCalc.unchecked()) {
|
|
774
|
+
calcStatusIcon
|
|
775
|
+
.addClass("unchecked-flag")
|
|
776
|
+
.attr("title", i18next.t('factDetails.calculationUnchecked'))
|
|
777
|
+
calcStatusText.text(i18next.t('factDetails.calculationUnchecked'));
|
|
778
|
+
}
|
|
723
779
|
|
|
724
|
-
a.addCard(
|
|
725
|
-
|
|
780
|
+
a.addCard(cardTitle, calcBody, rCalc.elr == selectedELR);
|
|
726
781
|
}
|
|
727
782
|
return a.contents();
|
|
728
783
|
}
|
|
@@ -813,7 +868,7 @@ export class Inspector {
|
|
|
813
868
|
}
|
|
814
869
|
}
|
|
815
870
|
else {
|
|
816
|
-
s = $("<i>").text("n/a").attr("title",
|
|
871
|
+
s = $("<i>").text("n/a").attr("title", i18next.t('factDetails.nonNumericFact'));
|
|
817
872
|
}
|
|
818
873
|
$(".fact-properties tr.change td").html(s);
|
|
819
874
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// See COPYRIGHT.md for copyright information
|
|
2
|
+
|
|
3
|
+
import Decimal from 'decimal.js';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Class for working with numeric intervals, of the form: [a, b]
|
|
7
|
+
*
|
|
8
|
+
* Interval is closed (includes both bounds)
|
|
9
|
+
* a must be <= b
|
|
10
|
+
*/
|
|
11
|
+
export class Interval {
|
|
12
|
+
|
|
13
|
+
constructor(a, b) {
|
|
14
|
+
this.a = typeof a == 'object' ? a : new Decimal(a);
|
|
15
|
+
this.b = typeof b == 'object' ? b : new Decimal(b);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static fromFact(fact) {
|
|
19
|
+
if (!fact.isNumeric()) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const decimals = fact.decimals();
|
|
23
|
+
let width = 0;
|
|
24
|
+
if (decimals !== undefined) {
|
|
25
|
+
const x = new Decimal(10);
|
|
26
|
+
width = x.pow(0-decimals).times(0.5);
|
|
27
|
+
}
|
|
28
|
+
let value;
|
|
29
|
+
const factValue = fact.value();
|
|
30
|
+
try {
|
|
31
|
+
value = new Decimal(factValue);
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (e instanceof Error && /DecimalError/.test(e.message)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
return new Interval(value.minus(width), value.plus(width));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
intersection(other) {
|
|
42
|
+
return Interval.intersection(this, other);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static intersection(...intervals) {
|
|
46
|
+
if (intervals.includes(undefined) || intervals.length == 0) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
const aa = intervals.map(x => x.a);
|
|
50
|
+
const bb = intervals.map(x => x.b);
|
|
51
|
+
const a = Decimal.max(...aa);
|
|
52
|
+
const b = Decimal.min(...bb);
|
|
53
|
+
if (b.lessThan(a)) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return new Interval(a, b);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
plus(other) {
|
|
60
|
+
return new Interval(this.a.plus(other.a), this.b.plus(other.b));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
times(x) {
|
|
64
|
+
return x > 0 ? new Interval(this.a.times(x), this.b.times(x)) : new Interval(this.b.times(x), this.a.times(x));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
midpoint() {
|
|
68
|
+
return Decimal.add(this.a, this.b).div(2);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Copyright 2019 Workiva Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import { Fact } from "./fact.js";
|
|
16
|
+
import { Interval } from "./interval.js";
|
|
17
|
+
import { ReportSet } from "./reportset.js";
|
|
18
|
+
import Decimal from 'decimal.js';
|
|
19
|
+
import { viewerUniqueId } from "./util.js";
|
|
20
|
+
import './test-utils.js';
|
|
21
|
+
|
|
22
|
+
function testReportSet(facts) {
|
|
23
|
+
const reportset = new ReportSet({
|
|
24
|
+
prefixes: {},
|
|
25
|
+
concepts: {},
|
|
26
|
+
facts: facts
|
|
27
|
+
});
|
|
28
|
+
reportset._initialize();
|
|
29
|
+
return reportset;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function testFact(factData) {
|
|
33
|
+
factData.a = factData.a || {};
|
|
34
|
+
factData.a.c = factData.a.c || 'eg:Concept1';
|
|
35
|
+
if (!('u' in factData.a)) {
|
|
36
|
+
factData.a.u = 'eg:pure';
|
|
37
|
+
}
|
|
38
|
+
else if (factData.a.u === undefined) {
|
|
39
|
+
delete factData.a.u;
|
|
40
|
+
}
|
|
41
|
+
return testReportSet({"f1": factData}).getItemById(viewerUniqueId(0, "f1"));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("From facts", () => {
|
|
45
|
+
test("Infinite precision", () => {
|
|
46
|
+
var i = Interval.fromFact(testFact({v: 20}));
|
|
47
|
+
expect(i.a).toEqualDecimal(20);
|
|
48
|
+
expect(i.b).toEqualDecimal(20);
|
|
49
|
+
|
|
50
|
+
i = Interval.fromFact(testFact({v: -20}));
|
|
51
|
+
expect(i.a).toEqualDecimal(-20);
|
|
52
|
+
expect(i.b).toEqualDecimal(-20);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("Finite precision", () => {
|
|
56
|
+
var i = Interval.fromFact(testFact({v: 20, d: 0}));
|
|
57
|
+
expect(i.a).toEqualDecimal(19.5);
|
|
58
|
+
expect(i.b).toEqualDecimal(20.5);
|
|
59
|
+
|
|
60
|
+
i = Interval.fromFact(testFact({v: -20.123, d: 3}));
|
|
61
|
+
expect(i.a).toEqualDecimal(-20.1235);
|
|
62
|
+
expect(i.b).toEqualDecimal(-20.1225);
|
|
63
|
+
|
|
64
|
+
i = Interval.fromFact(testFact({v: -20.123, d: -1}));
|
|
65
|
+
expect(i.a).toEqualDecimal(-25.123);
|
|
66
|
+
expect(i.b).toEqualDecimal(-15.123);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("Non numeric", () => {
|
|
70
|
+
var i = Interval.fromFact(testFact({v: 20, a: {u: undefined}}));
|
|
71
|
+
expect(i).toBeUndefined();
|
|
72
|
+
|
|
73
|
+
// This would be invalid XBRL
|
|
74
|
+
i = Interval.fromFact(testFact({v: "abc" }));
|
|
75
|
+
expect(i).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
describe("Intersect", () => {
|
|
81
|
+
test("Intersecting", () => {
|
|
82
|
+
var x = new Interval(5, 15);
|
|
83
|
+
var y = new Interval(10, 25);
|
|
84
|
+
var ii = x.intersection(y);
|
|
85
|
+
expect(ii.a).toEqualDecimal(10);
|
|
86
|
+
expect(ii.b).toEqualDecimal(15);
|
|
87
|
+
|
|
88
|
+
ii = Interval.intersection(x, y);
|
|
89
|
+
expect(ii.a).toEqualDecimal(10);
|
|
90
|
+
expect(ii.b).toEqualDecimal(15);
|
|
91
|
+
|
|
92
|
+
ii = y.intersection(x);
|
|
93
|
+
expect(ii.a).toEqualDecimal(10);
|
|
94
|
+
expect(ii.b).toEqualDecimal(15);
|
|
95
|
+
|
|
96
|
+
y = new Interval(15, 25);
|
|
97
|
+
ii = x.intersection(y);
|
|
98
|
+
expect(ii.a).toEqualDecimal(15);
|
|
99
|
+
expect(ii.b).toEqualDecimal(15);
|
|
100
|
+
|
|
101
|
+
x = new Interval(10, 20);
|
|
102
|
+
y = new Interval(15, 25);
|
|
103
|
+
var z = new Interval(18, 30);
|
|
104
|
+
ii = Interval.intersection(x, y, z);
|
|
105
|
+
expect(ii.a).toEqualDecimal(18);
|
|
106
|
+
expect(ii.b).toEqualDecimal(20);
|
|
107
|
+
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("No intersection", () => {
|
|
111
|
+
var x = new Interval(5, 15);
|
|
112
|
+
expect(x.intersection(new Interval(20, 25))).toBeUndefined();
|
|
113
|
+
expect(x.intersection(new Interval(1, 4.99))).toBeUndefined();
|
|
114
|
+
|
|
115
|
+
x = new Interval(10, 20);
|
|
116
|
+
var y = new Interval(15, 25);
|
|
117
|
+
var z = new Interval(21, 30);
|
|
118
|
+
expect(Interval.intersection(x, y, z)).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
describe("Arithmetic", () => {
|
|
123
|
+
test("Add", () => {
|
|
124
|
+
var i = new Interval(5, 15);
|
|
125
|
+
var ii = i.plus(new Interval(10, 20));
|
|
126
|
+
expect(ii.a).toEqualDecimal(15);
|
|
127
|
+
expect(ii.b).toEqualDecimal(35);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("Multiply", () => {
|
|
131
|
+
var i = new Interval(5, 15);
|
|
132
|
+
var ii = i.times(2);
|
|
133
|
+
expect(ii.a).toEqualDecimal(10);
|
|
134
|
+
expect(ii.b).toEqualDecimal(30);
|
|
135
|
+
|
|
136
|
+
ii = i.times(-2);
|
|
137
|
+
expect(ii.a).toEqualDecimal(-30);
|
|
138
|
+
expect(ii.b).toEqualDecimal(-10);
|
|
139
|
+
|
|
140
|
+
i = new Interval(-10, -5);
|
|
141
|
+
ii = i.times(2);
|
|
142
|
+
expect(ii.a).toEqualDecimal(-20);
|
|
143
|
+
expect(ii.b).toEqualDecimal(-10);
|
|
144
|
+
|
|
145
|
+
ii = i.times(-2);
|
|
146
|
+
expect(ii.a).toEqualDecimal(10);
|
|
147
|
+
expect(ii.b).toEqualDecimal(20);
|
|
148
|
+
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
|
|
@@ -11,6 +11,25 @@ export function TestInspector() {
|
|
|
11
11
|
|
|
12
12
|
TestInspector.prototype = Object.create(Inspector.prototype);
|
|
13
13
|
|
|
14
|
+
expect.extend({
|
|
15
|
+
toEqualDecimal(received, expected) {
|
|
16
|
+
const options = {
|
|
17
|
+
comment: 'decimal.js equality',
|
|
18
|
+
isNot: this.isNot,
|
|
19
|
+
promise: this.promise,
|
|
20
|
+
};
|
|
21
|
+
const pass = received.equals(expected);
|
|
22
|
+
const message = () =>
|
|
23
|
+
this.utils.matcherHint('toEqualDecimals', undefined, undefined, options) +
|
|
24
|
+
'\n\n' +
|
|
25
|
+
`Expected: ${this.isNot ? '(not) ' : ''}${this.utils.printExpected(new Decimal(expected))}\n` +
|
|
26
|
+
`Received: ${this.utils.printReceived(received)}`;
|
|
27
|
+
|
|
28
|
+
return {actual: received, message, pass};
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
14
33
|
export function createSimpleFact(id, concept, options=null) {
|
|
15
34
|
options = options || {};
|
|
16
35
|
return {
|
|
@@ -271,14 +271,12 @@ export class Viewer {
|
|
|
271
271
|
/* Otherwise, insert a <span> as wrapper */
|
|
272
272
|
if (nodes.length == 0) {
|
|
273
273
|
nodes.push(this._wrapNode(domNode));
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
}
|
|
274
|
+
}
|
|
275
|
+
// Create a list of the wrapper node, and all absolutely positioned descendants.
|
|
276
|
+
for (const e of nodes[0].querySelectorAll("*")) {
|
|
277
|
+
if (getComputedStyle(e).getPropertyValue('position') === "absolute") {
|
|
278
|
+
nodes.push(e);
|
|
279
|
+
}
|
|
282
280
|
}
|
|
283
281
|
for (const [i, n] of nodes.entries()) {
|
|
284
282
|
// getBoundingClientRect blocks on layout, so only do it if we've actually got absolute nodes
|