ixbrl-viewer 1.4.14__py3-none-any.whl → 1.4.16__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.

Files changed (44) hide show
  1. iXBRLViewerPlugin/__init__.py +87 -66
  2. iXBRLViewerPlugin/_version.py +2 -2
  3. iXBRLViewerPlugin/constants.py +6 -0
  4. iXBRLViewerPlugin/iXBRLViewer.py +36 -32
  5. iXBRLViewerPlugin/ui.py +16 -3
  6. iXBRLViewerPlugin/viewer/dist/ixbrlviewer.js +1 -1
  7. iXBRLViewerPlugin/viewer/src/html/inspector.html +27 -0
  8. iXBRLViewerPlugin/viewer/src/i18n/en/translation.json +18 -1
  9. iXBRLViewerPlugin/viewer/src/i18n/es/translation.json +18 -1
  10. iXBRLViewerPlugin/viewer/src/icons/calculator.svg +13 -0
  11. iXBRLViewerPlugin/viewer/src/icons/circle-cross.svg +11 -0
  12. iXBRLViewerPlugin/viewer/src/icons/circle-tick.svg +11 -0
  13. iXBRLViewerPlugin/viewer/src/icons/dimension.svg +1 -5
  14. iXBRLViewerPlugin/viewer/src/icons/member.svg +2 -5
  15. iXBRLViewerPlugin/viewer/src/icons/multi-tag.svg +10 -0
  16. iXBRLViewerPlugin/viewer/src/js/accordian.js +2 -2
  17. iXBRLViewerPlugin/viewer/src/js/calculation.js +202 -0
  18. iXBRLViewerPlugin/viewer/src/js/calculation.test.js +306 -0
  19. iXBRLViewerPlugin/viewer/src/js/calculationInspector.js +190 -0
  20. iXBRLViewerPlugin/viewer/src/js/concept.js +7 -1
  21. iXBRLViewerPlugin/viewer/src/js/fact.js +59 -5
  22. iXBRLViewerPlugin/viewer/src/js/factset.js +54 -5
  23. iXBRLViewerPlugin/viewer/src/js/factset.test.js +71 -5
  24. iXBRLViewerPlugin/viewer/src/js/inspector.js +85 -30
  25. iXBRLViewerPlugin/viewer/src/js/interval.js +70 -0
  26. iXBRLViewerPlugin/viewer/src/js/interval.test.js +153 -0
  27. iXBRLViewerPlugin/viewer/src/js/test-utils.js +19 -0
  28. iXBRLViewerPlugin/viewer/src/less/accordian.less +2 -2
  29. iXBRLViewerPlugin/viewer/src/less/calculation-inspector.less +83 -0
  30. iXBRLViewerPlugin/viewer/src/less/colours.less +2 -0
  31. iXBRLViewerPlugin/viewer/src/less/dialog.less +8 -4
  32. iXBRLViewerPlugin/viewer/src/less/inspector.less +67 -23
  33. iXBRLViewerPlugin/viewer/src/less/validation-report.less +1 -2
  34. iXBRLViewerPlugin/viewer/src/less/viewer.less +1 -1
  35. iXBRLViewerPlugin/xhtmlserialize.py +1 -1
  36. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/METADATA +2 -2
  37. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/RECORD +43 -34
  38. tests/puppeteer/tools/generate.sh +1 -1
  39. iXBRLViewerPlugin/viewer/src/js/calculations.js +0 -111
  40. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/LICENSE +0 -0
  41. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/NOTICE +0 -0
  42. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/WHEEL +0 -0
  43. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/entry_points.txt +0 -0
  44. {ixbrl_viewer-1.4.14.dist-info → ixbrl_viewer-1.4.16.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,306 @@
1
+ // See COPYRIGHT.md for copyright information
2
+
3
+ import Decimal from 'decimal.js';
4
+ import { FactSet } from "./factset.js";
5
+ import { Fact } from "./fact.js";
6
+ import { Interval } from "./interval.js";
7
+ import { NAMESPACE_ISO4217, viewerUniqueId } from "./util.js";
8
+ import { ReportSet } from "./reportset.js";
9
+ import { Calculation } from "./calculation.js";
10
+ import './test-utils.js';
11
+
12
+ const testReportData = {
13
+ "prefixes": {
14
+ "eg": "http://www.example.com",
15
+ "iso4217": NAMESPACE_ISO4217,
16
+ "e": "http://example.com/entity",
17
+ "group": "http://example.com/group",
18
+ },
19
+ "concepts": {
20
+ "eg:Total": {
21
+ "labels": {
22
+ "std": {
23
+ "en": "Total"
24
+ }
25
+ }
26
+ },
27
+ "eg:Item1": {
28
+ "labels": {
29
+ "std": {
30
+ "en": "Item 1"
31
+ }
32
+ }
33
+ },
34
+ "eg:Item2": {
35
+ "labels": {
36
+ "std": {
37
+ "en": "Item 2"
38
+ }
39
+ }
40
+ },
41
+ "eg:Total2": {
42
+ "labels": {
43
+ "std": {
44
+ "en": "Total 2"
45
+ }
46
+ }
47
+ },
48
+ },
49
+ "facts": {
50
+ },
51
+ "rels": {
52
+ "calc": {
53
+ "group": {
54
+ "eg:Total": [
55
+ {"t": "eg:Item1", "w": 1},
56
+ {"t": "eg:Item2", "w": -1}
57
+ ],
58
+ "eg:Total2": [
59
+ {"t": "eg:Item1", "w": 2},
60
+ {"t": "eg:Item2", "w": -2}
61
+ ]
62
+ }
63
+ }
64
+ }
65
+ };
66
+
67
+ function testReportSet(facts) {
68
+ // Deep copy of standing data
69
+ const data = JSON.parse(JSON.stringify(testReportData));
70
+ data.facts = facts;
71
+ const reportset = new ReportSet(data);
72
+ reportset._initialize();
73
+ return reportset;
74
+ }
75
+
76
+ function testFact(aspectData, value, decimals) {
77
+ const factData = { "a": aspectData, "v": value, "d": decimals};
78
+ return factData;
79
+ }
80
+
81
+ function getFact(reportSet, id) {
82
+ return reportSet.getItemById(viewerUniqueId(0, id));
83
+ }
84
+
85
+ describe("Simple consistent calculation", () => {
86
+ const reportSet = testReportSet({
87
+ "f1": testFact({"c": "eg:Total", "u": "iso2417:GBP"}, 10000, -3),
88
+ "f2": testFact({"c": "eg:Item1", "u": "iso2417:GBP"}, 12000, -3),
89
+ "f3": testFact({"c": "eg:Item2", "u": "iso2417:GBP"}, 2000, -3),
90
+ });
91
+
92
+ test("Calc 1.1 total", () => {
93
+ const calc = new Calculation(getFact(reportSet, "f1"), true);
94
+ expect(calc.hasCalculations()).toBe(true);
95
+ const rCalcs = calc.resolvedCalculations();
96
+ expect(rCalcs.length).toBe(1);
97
+ const rCalc = rCalcs[0];
98
+ expect(rCalc.elr).toBe("group");
99
+ expect(rCalc.calculatedTotalInterval()).toEqual(new Interval(9000, 11000));
100
+ expect(Interval.fromFact(rCalc.totalFact)).toEqual(new Interval(9500, 10500));
101
+ expect(rCalc.isConsistent()).toBe(true);
102
+
103
+ });
104
+
105
+ test("Calc contributor", () => {
106
+ const calc11 = new Calculation(getFact(reportSet, "f2"), true);
107
+ expect(calc11.hasCalculations()).toBe(false);
108
+ const calc10 = new Calculation(getFact(reportSet, "f2"), false);
109
+ expect(calc10.hasCalculations()).toBe(false);
110
+ });
111
+
112
+ test("Calc 1.0 total", () => {
113
+ const calc = new Calculation(getFact(reportSet, "f1"), false);
114
+ expect(calc.hasCalculations()).toBe(true);
115
+ const rCalcs = calc.resolvedCalculations();
116
+ expect(rCalcs.length).toBe(1);
117
+ const rCalc = rCalcs[0];
118
+ expect(rCalc.elr).toBe("group");
119
+ expect(rCalc.calculatedTotal()).toEqual(new Decimal(10000));
120
+ expect(rCalc.unchecked()).toBe(false);
121
+ expect(rCalc.isConsistent()).toBe(true);
122
+ });
123
+ });
124
+
125
+ describe("Consistent only under 1.1", () => {
126
+ const reportSet = testReportSet({
127
+ "f1": testFact({"c": "eg:Total", "u": "iso2417:GBP"}, 11000, -3),
128
+ "f2": testFact({"c": "eg:Item1", "u": "iso2417:GBP"}, 12000, -3),
129
+ "f3": testFact({"c": "eg:Item2", "u": "iso2417:GBP"}, 2000, -3),
130
+ });
131
+
132
+ test("Calc 1.1 total", () => {
133
+ const calc = new Calculation(getFact(reportSet, "f1"), true);
134
+ expect(calc.hasCalculations()).toBe(true);
135
+ const rCalcs = calc.resolvedCalculations();
136
+ expect(rCalcs.length).toBe(1);
137
+ const rCalc = rCalcs[0];
138
+ expect(rCalc.elr).toBe("group");
139
+ expect(rCalc.calculatedTotalInterval()).toEqual(new Interval(9000, 11000));
140
+ expect(Interval.fromFact(rCalc.totalFact)).toEqual(new Interval(10500, 11500));
141
+ expect(rCalc.isConsistent()).toBe(true);
142
+
143
+ expect(rCalc.rows[0].concept.name).toBe("eg:Item1");
144
+ expect(rCalc.rows[0].weight).toBe(1);
145
+ expect(rCalc.rows[0].weightSign).toBe("+");
146
+ expect(rCalc.rows[0].facts.size()).toBe(1);
147
+
148
+ expect(rCalc.rows[1].concept.name).toBe("eg:Item2");
149
+ expect(rCalc.rows[1].weight).toBe(-1);
150
+ expect(rCalc.rows[1].weightSign).toBe("-");
151
+ expect(rCalc.rows[1].facts.size()).toBe(1);
152
+
153
+ });
154
+
155
+ test("Calc contributor", () => {
156
+ const calc11 = new Calculation(getFact(reportSet, "f2"), true);
157
+ expect(calc11.hasCalculations()).toBe(false);
158
+ const calc10 = new Calculation(getFact(reportSet, "f2"), false);
159
+ expect(calc10.hasCalculations()).toBe(false);
160
+ });
161
+
162
+ test("Calc 1.0 total", () => {
163
+ const calc = new Calculation(getFact(reportSet, "f1"), false);
164
+ expect(calc.hasCalculations()).toBe(true);
165
+ const rCalcs = calc.resolvedCalculations();
166
+ expect(rCalcs.length).toBe(1);
167
+ const rCalc = rCalcs[0];
168
+ expect(rCalc.elr).toBe("group");
169
+ expect(rCalc.calculatedTotal()).toEqual(new Decimal(10000));
170
+ expect(rCalc.unchecked()).toBe(false);
171
+ expect(rCalc.isConsistent()).toBe(false);
172
+ });
173
+
174
+ });
175
+
176
+ describe("Consistent duplicate contributor", () => {
177
+ const reportSet = testReportSet({
178
+ "f1": testFact({"c": "eg:Total", "u": "iso2417:GBP"}, 10000, -3),
179
+ "f2": testFact({"c": "eg:Item1", "u": "iso2417:GBP"}, 12000, -3),
180
+ "f3": testFact({"c": "eg:Item2", "u": "iso2417:GBP"}, 2000, -3),
181
+ "f4": testFact({"c": "eg:Item2", "u": "iso2417:GBP"}, 1990, -1),
182
+ });
183
+
184
+ test("Calc 1.1 total", () => {
185
+ const calc = new Calculation(getFact(reportSet, "f1"), true);
186
+ expect(calc.hasCalculations()).toBe(true);
187
+ const rCalcs = calc.resolvedCalculations();
188
+ expect(rCalcs.length).toBe(1);
189
+ const rCalc = rCalcs[0];
190
+ expect(rCalc.elr).toBe("group");
191
+ expect(rCalc.calculatedTotalInterval()).toEqual(new Interval(9505, 10515));
192
+ expect(Interval.fromFact(rCalc.totalFact)).toEqual(new Interval(9500, 10500));
193
+ expect(rCalc.isConsistent()).toBe(true);
194
+
195
+ expect(rCalc.rows[0].concept.name).toBe("eg:Item1");
196
+ expect(rCalc.rows[0].weight).toBe(1);
197
+ expect(rCalc.rows[0].weightSign).toBe("+");
198
+ expect(rCalc.rows[0].facts.size()).toBe(1);
199
+
200
+ expect(rCalc.rows[1].concept.name).toBe("eg:Item2");
201
+ expect(rCalc.rows[1].weight).toBe(-1);
202
+ expect(rCalc.rows[1].weightSign).toBe("-");
203
+ expect(rCalc.rows[1].facts.size()).toBe(2);
204
+
205
+ });
206
+
207
+ test("Calc contributor", () => {
208
+ const calc11 = new Calculation(getFact(reportSet, "f2"), true);
209
+ expect(calc11.hasCalculations()).toBe(false);
210
+ const calc10 = new Calculation(getFact(reportSet, "f2"), false);
211
+ expect(calc10.hasCalculations()).toBe(false);
212
+ });
213
+
214
+ test("Calc 1.0 total", () => {
215
+ const calc = new Calculation(getFact(reportSet, "f1"), false);
216
+ expect(calc.hasCalculations()).toBe(true);
217
+ const rCalcs = calc.resolvedCalculations();
218
+ expect(rCalcs.length).toBe(1);
219
+ const rCalc = rCalcs[0];
220
+ expect(rCalc.elr).toBe("group");
221
+ expect(rCalc.calculatedTotal()).toEqual(new Decimal(10000));
222
+ expect(rCalc.unchecked()).toBe(true);
223
+ });
224
+
225
+ });
226
+
227
+
228
+ describe("Single contributor", () => {
229
+ const reportSet = testReportSet({
230
+ "f1": testFact({"c": "eg:Total", "u": "iso2417:GBP"}, 10000, -3),
231
+ "f2": testFact({"c": "eg:Item1", "u": "iso2417:GBP"}, 9990, -1),
232
+ });
233
+
234
+ test("Calc 1.1 total", () => {
235
+ const calc = new Calculation(getFact(reportSet, "f1"), true);
236
+ expect(calc.hasCalculations()).toBe(true);
237
+ const rCalcs = calc.resolvedCalculations();
238
+ expect(rCalcs.length).toBe(1);
239
+ const rCalc = rCalcs[0];
240
+ expect(rCalc.elr).toBe("group");
241
+ expect(rCalc.calculatedTotalInterval()).toEqual(new Interval(9985, 9995));
242
+ expect(Interval.fromFact(rCalc.totalFact)).toEqual(new Interval(9500, 10500));
243
+ expect(rCalc.isConsistent()).toBe(true);
244
+
245
+ });
246
+
247
+ test("Calc 1.0 total", () => {
248
+ const calc = new Calculation(getFact(reportSet, "f1"), false);
249
+ expect(calc.hasCalculations()).toBe(true);
250
+ const rCalcs = calc.resolvedCalculations();
251
+ expect(rCalcs.length).toBe(1);
252
+ const rCalc = rCalcs[0];
253
+ expect(rCalc.elr).toBe("group");
254
+ expect(rCalc.calculatedTotal()).toEqual(new Decimal(9990));
255
+ expect(rCalc.unchecked()).toBe(false);
256
+ expect(rCalc.isConsistent()).toBe(false);
257
+ });
258
+ });
259
+
260
+ describe("Weights", () => {
261
+ const reportSet = testReportSet({
262
+ "f1": testFact({"c": "eg:Total2", "u": "iso2417:GBP"}, 4000, -3),
263
+ "f2": testFact({"c": "eg:Item1", "u": "iso2417:GBP"}, 3000, -3),
264
+ "f3": testFact({"c": "eg:Item2", "u": "iso2417:GBP"}, 1000, -3),
265
+ });
266
+
267
+ test("Calc 1.1 total", () => {
268
+ const calc = new Calculation(getFact(reportSet, "f1"), true);
269
+ expect(calc.hasCalculations()).toBe(true);
270
+ const rCalcs = calc.resolvedCalculations();
271
+ expect(rCalcs.length).toBe(1);
272
+ const rCalc = rCalcs[0];
273
+ expect(rCalc.elr).toBe("group");
274
+ expect(rCalc.calculatedTotalInterval()).toEqual(new Interval(2000, 6000));
275
+ expect(Interval.fromFact(rCalc.totalFact)).toEqual(new Interval(3500, 4500));
276
+ expect(rCalc.isConsistent()).toBe(true);
277
+
278
+ expect(rCalc.rows[0].concept.name).toBe("eg:Item1");
279
+ expect(rCalc.rows[0].weight).toBe(2);
280
+ expect(rCalc.rows[0].weightSign).toBe("2");
281
+
282
+ expect(rCalc.rows[1].concept.name).toBe("eg:Item2");
283
+ expect(rCalc.rows[1].weight).toBe(-2);
284
+ expect(rCalc.rows[1].weightSign).toBe("-2");
285
+
286
+ });
287
+
288
+ test("Calc contributor", () => {
289
+ const calc11 = new Calculation(getFact(reportSet, "f2"), true);
290
+ expect(calc11.hasCalculations()).toBe(false);
291
+ const calc10 = new Calculation(getFact(reportSet, "f2"), false);
292
+ expect(calc10.hasCalculations()).toBe(false);
293
+ });
294
+
295
+ test("Calc 1.0 total", () => {
296
+ const calc = new Calculation(getFact(reportSet, "f1"), false);
297
+ expect(calc.hasCalculations()).toBe(true);
298
+ const rCalcs = calc.resolvedCalculations();
299
+ expect(rCalcs.length).toBe(1);
300
+ const rCalc = rCalcs[0];
301
+ expect(rCalc.elr).toBe("group");
302
+ expect(rCalc.calculatedTotal()).toEqual(new Decimal(4000));
303
+ expect(rCalc.unchecked()).toBe(false);
304
+ expect(rCalc.isConsistent()).toBe(true);
305
+ });
306
+ });
@@ -0,0 +1,190 @@
1
+ // Copyright 2021 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 $ from 'jquery';
16
+ import i18next from 'i18next';
17
+
18
+ import { Dialog } from './dialog.js';
19
+ import { ResolvedCalc11Calculation, ResolvedLegacyCalculation } from './calculation.js';
20
+
21
+ export class CalculationInspector extends Dialog {
22
+ constructor() {
23
+ super(".dialog.calculation-inspector");
24
+ this.addButton("Dismiss", true);
25
+ }
26
+
27
+ duplicateFactIcons(factset) {
28
+ const icons = $("<td></td>").addClass("icons");
29
+ if (factset.size() > 1 && !factset.completeDuplicates()) {
30
+ if (factset.isConsistent()) {
31
+ icons.append(
32
+ $("<span></span>")
33
+ .addClass("duplicate-facts")
34
+ .attr("title", i18next.t('calculation.consistent-duplicate-facts-present', {nfacts: factset.size()}))
35
+ );
36
+ }
37
+ else {
38
+ icons.append(
39
+ $("<span></span>")
40
+ .addClass("duplicate-facts")
41
+ .attr("title", i18next.t('calculation.inconsistent-duplicate-facts-present', {nfacts: factset.size()}))
42
+ );
43
+ }
44
+ }
45
+ return icons;
46
+ }
47
+
48
+ populateCalc11Table(resolvedCalculation, table) {
49
+ table.addClass("calc11");
50
+ const tbody = table.find("tbody");
51
+ let hasIcons = false;
52
+ for (const row of resolvedCalculation.rows) {
53
+ let factText = "";
54
+ let minText = "";
55
+ let maxText = "";
56
+
57
+ if (!row.facts.isEmpty()) {
58
+ let f = row.facts.items()[0];
59
+ const reportedInterval = row.facts.valueIntersection();
60
+ if (reportedInterval === undefined) {
61
+ factText = "n/a";
62
+ minText = "n/a";
63
+ maxText = "n/a";
64
+ }
65
+ else {
66
+ factText = f.readableValue(reportedInterval.midpoint().toNumber());
67
+ const contributionInterval = row.contributionInterval();
68
+ minText = f.readableValue(contributionInterval.a.toNumber());
69
+ maxText = f.readableValue(contributionInterval.b.toNumber());
70
+ }
71
+ }
72
+ const icons = this.duplicateFactIcons(row.facts);
73
+ hasIcons = hasIcons || icons.find("span").length > 0;
74
+ $("<tr></tr>")
75
+ .append($("<td></td>").addClass("weight").text(row.weightSign))
76
+ .append($("<td></td>").text(row.concept.label()).addClass("line-item"))
77
+ .append($("<td></td>").addClass("figure").text(factText))
78
+ .append(icons)
79
+ .append($("<td></td>").addClass("figure").text(minText))
80
+ .append($("<td></td>").addClass("figure").text(maxText))
81
+ .appendTo(tbody);
82
+ }
83
+ const fact = resolvedCalculation.totalFact;
84
+ const cvi = resolvedCalculation.calculatedTotalInterval();
85
+ $("<tr></tr>").addClass("total")
86
+ .append($("<td></td>"))
87
+ .append($("<td></td>").text(`${fact.concept().label()} (${i18next.t('calculation.calculated-total')})`).addClass("line-item"))
88
+ .append($("<td></td>").text(cvi === undefined ? 'n/a' : fact.readableValue(cvi.midpoint().toNumber())).addClass("figure"))
89
+ .append($("<td></td>").addClass("icons"))
90
+ .append($("<td></td>").text(cvi === undefined ? 'n/a' : fact.readableValue(cvi.a.toNumber())).addClass("figure"))
91
+ .append($("<td></td>").text(cvi === undefined ? 'n/a' : fact.readableValue(cvi.b.toNumber())).addClass("figure"))
92
+ .appendTo(tbody);
93
+
94
+ const tvi = resolvedCalculation.totalFactSet.valueIntersection();
95
+ const icons = this.duplicateFactIcons(resolvedCalculation.totalFactSet);
96
+ $("<tr></tr>")
97
+ .append($("<td></td>"))
98
+ .append($("<td></td>").text(`${fact.concept().label()} (${i18next.t('calculation.reported-total')})`).addClass("line-item"))
99
+ .append($("<td></td>").text(tvi === undefined ? 'n/a' : fact.readableValue(tvi.midpoint().toNumber())).addClass("figure"))
100
+ .append(icons)
101
+ .append($("<td></td>").text(tvi === undefined ? 'n/a' : fact.readableValue(tvi.a.toNumber())).addClass("figure"))
102
+ .append($("<td></td>").text(tvi === undefined ? 'n/a' : fact.readableValue(tvi.b.toNumber())).addClass("figure"))
103
+ .appendTo(tbody);
104
+ if (hasIcons) {
105
+ table.addClass("has-icons");
106
+ }
107
+ }
108
+
109
+ populateLegacyCalcTable(resolvedCalculation, table) {
110
+ table.removeClass("calc11");
111
+ const tbody = table.find("tbody");
112
+ let hasIcons = false;
113
+ for (const row of resolvedCalculation.rows) {
114
+ let factText = "";
115
+ let minText = "";
116
+ let maxText = "";
117
+
118
+ if (!row.facts.isEmpty()) {
119
+ let f = row.facts.items()[0];
120
+ const reportedInterval = row.facts.valueIntersection();
121
+ if (reportedInterval === undefined) {
122
+ factText = "Inconsistent duplicates";
123
+ }
124
+ else if (!row.facts.completeDuplicates()) {
125
+ factText = "Duplicate facts";
126
+ }
127
+ else {
128
+ factText = f.readableValue();
129
+ }
130
+ }
131
+ const icons = this.duplicateFactIcons(row.facts);
132
+ hasIcons = hasIcons || icons.find("span").length > 0;
133
+ $("<tr></tr>")
134
+ .append($("<td></td>").addClass("weight").text(row.weightSign))
135
+ .append($("<td></td>").text(row.concept.label()).addClass("line-item"))
136
+ .append($("<td></td>").addClass("figure").text(factText))
137
+ .append(icons)
138
+ .appendTo(tbody);
139
+ }
140
+ const fact = resolvedCalculation.totalFact;
141
+ const ct = resolvedCalculation.calculatedTotal();
142
+ $("<tr></tr>").addClass("total")
143
+ .append($("<td></td>"))
144
+ .append($("<td></td>").text(`${fact.concept().label()} (${i18next.t('calculation.calculated-total')})`).addClass("line-item"))
145
+ .append($("<td></td>").text(fact.readableValue(ct)).addClass("figure"))
146
+ .append($("<td></td>").addClass("icons"))
147
+ .appendTo(tbody);
148
+
149
+ $("<tr></tr>")
150
+ .append($("<td></td>"))
151
+ .append($("<td></td>").text(`${fact.concept().label()} (${i18next.t('calculation.reported-total')})`).addClass("line-item"))
152
+ .append($("<td></td>").text(fact.readableValue()).addClass("figure"))
153
+ .append($("<td></td>").addClass("icons"))
154
+ .appendTo(tbody);
155
+ if (hasIcons) {
156
+ table.addClass("has-icons");
157
+ }
158
+ }
159
+
160
+ displayCalculation(resolvedCalculation) {
161
+ const table = this.node.find("table.calculation-trace");
162
+ const tbody = table.find("tbody");
163
+ // We remove the padding on the icons column (used to indicate
164
+ // duplicate facts) if it is empty to avoid an unsightly gap
165
+ var hasIcons = false;
166
+ tbody.empty();
167
+ table.removeClass("has-icons");
168
+ if (resolvedCalculation instanceof ResolvedCalc11Calculation) {
169
+ this.populateCalc11Table(resolvedCalculation, table);
170
+ }
171
+ else {
172
+ this.populateLegacyCalcTable(resolvedCalculation, table);
173
+ }
174
+
175
+
176
+ const messageCell = table.find("td.status");
177
+ messageCell.removeClass("inconsistent").removeClass("consistent").removeClass("unchecked");
178
+ if (resolvedCalculation.unchecked()) {
179
+ messageCell.addClass("unchecked").find(".message").text("Calculation does not bind");
180
+ }
181
+ else if (resolvedCalculation.isConsistent()) {
182
+ messageCell.addClass("consistent").find(".message").text("Calculation is consistent");
183
+ }
184
+ else {
185
+ messageCell.addClass("inconsistent").find(".message").text("Calculation is inconsistent");
186
+ }
187
+ }
188
+ }
189
+
190
+
@@ -5,6 +5,8 @@ import $ from 'jquery'
5
5
  export class Concept {
6
6
  constructor(report, name) {
7
7
  this._c = report.concepts()[name] || {};
8
+ this.name = name;
9
+ this.report = report;
8
10
  }
9
11
 
10
12
  /*
@@ -48,7 +50,11 @@ export class Concept {
48
50
  }
49
51
 
50
52
  isEnumeration() {
51
- return Boolean(this._c.e);
53
+ return Boolean(this._c && this._c.e);
54
+ }
55
+
56
+ label() {
57
+ return this.report.getLabelOrName(this.name);
52
58
  }
53
59
 
54
60
  isTextBlock() {
@@ -2,10 +2,13 @@
2
2
 
3
3
  import $ from 'jquery'
4
4
  import i18next from "i18next";
5
+ import { isodateToHuman } from "./util.js"
6
+ import { QName } from "./qname.js"
5
7
  import { Aspect } from "./aspect.js";
6
8
  import { Period } from './period.js';
7
9
  import { formatNumber, localId } from "./util.js";
8
10
  import Decimal from "decimal.js";
11
+ import { Interval } from './interval.js';
9
12
 
10
13
  export class Fact {
11
14
 
@@ -42,7 +45,7 @@ export class Fact {
42
45
  return this.report.qname(this.f.a.c);
43
46
  }
44
47
 
45
- period(){
48
+ period() {
46
49
  return new Period(this.f.a.p);
47
50
  }
48
51
 
@@ -63,8 +66,8 @@ export class Fact {
63
66
  return this.f.v;
64
67
  }
65
68
 
66
- readableValue() {
67
- let v = this.f.v;
69
+ readableValue(val) {
70
+ let v = val === undefined ? this.f.v : val;
68
71
  if (this.isInvalidIXValue()) {
69
72
  v = "Invalid value";
70
73
  }
@@ -241,9 +244,11 @@ export class Fact {
241
244
  isNil() {
242
245
  return this.f.v === null;
243
246
  }
247
+
244
248
  isNegative() {
245
249
  return this.isNumeric() && !this.isNil() && this.value() !== undefined && new Decimal(this.value()).isNegative() && !this.isZero();
246
250
  }
251
+
247
252
  isPositive() {
248
253
  return this.isNumeric() && !this.isNil() && this.value() !== undefined && new Decimal(this.value()).isPositive() && !this.isZero();
249
254
  }
@@ -251,6 +256,7 @@ export class Fact {
251
256
  isZero() {
252
257
  return this.isNumeric() && !this.isNil() && this.value() !== undefined && new Decimal(this.value()).isZero();
253
258
  }
259
+
254
260
  isInvalidIXValue() {
255
261
  return this.f.err == 'INVALID_IX_VALUE';
256
262
  }
@@ -344,10 +350,58 @@ export class Fact {
344
350
  return concepts;
345
351
  }
346
352
 
347
- // Facts that are the source of relationships to this fact.
353
+ /*
354
+ * Facts that are the source of relationships to this fact.
355
+ */
348
356
  addLinkedFact(f) {
349
357
  this.linkedFacts.push(f);
350
358
  }
351
- }
352
359
 
360
+ /*
361
+ * Returns the fact's value, rounded according to the value of its decimals
362
+ * property. This is an odd thing to do, as it implies that more figures
363
+ * were reported than the decimals property suggest are accurate, but this
364
+ * is required for Calc 1.0 validation.
365
+ *
366
+ * valueInterval() is a more meaningful.
367
+ */
368
+ roundedValue() {
369
+ Decimal.rounding = Decimal.ROUND_HALF_UP;
370
+ const v = new Decimal(this.value());
371
+ const d = this.decimals();
372
+ if (d === undefined) {
373
+ return v;
374
+ }
375
+ return v.mul(10 ** d).round().mul(10 ** (0-d));
376
+ }
353
377
 
378
+ isCompleteDuplicate(other) {
379
+ return this.value() === other.value() && this.decimals() === other.decimals();
380
+ }
381
+
382
+ /*
383
+ * Facts that are the source of relationships to this fact.
384
+ */
385
+ addLinkedFact(f) {
386
+ this.linkedFacts.push(f);
387
+ }
388
+
389
+ /*
390
+ * Returns an Interval for the fact's value, as implied by its decimals
391
+ * property.
392
+ */
393
+ valueInterval() {
394
+ return Interval.fromFact(this);
395
+ }
396
+
397
+ isMorePrecise(of) {
398
+ // decimals of "undefined" indicates infinite precision
399
+ if (of.decimals() === undefined) {
400
+ return false;
401
+ }
402
+ if (this.decimals() === undefined) {
403
+ return true;
404
+ }
405
+ return this.decimals() > of.decimals();
406
+ }
407
+ }