mindee 1.0.6 → 1.1.0
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/.nyc_output/f57ba3bc-1777-4ecc-81e1-230327c2a401.json +1 -0
- package/.nyc_output/processinfo/f57ba3bc-1777-4ecc-81e1-230327c2a401.json +1 -0
- package/.nyc_output/processinfo/index.json +1 -0
- package/CHANGELOG.md +37 -2
- package/LICENSE +202 -0
- package/README.md +1 -1
- package/coverage/coverage.json +1 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +79 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +171 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +170 -0
- package/coverage/lcov.info +1595 -0
- package/coverage.lcov +1595 -0
- package/lib/api/financialDocument.js +5 -5
- package/lib/api/invoice.js +3 -3
- package/lib/api/object.js +2 -2
- package/lib/api/receipt.js +3 -3
- package/lib/api/request.js +12 -6
- package/lib/api/response.js +8 -3
- package/lib/documents/document.js +5 -1
- package/lib/documents/fields/field.js +1 -1
- package/lib/documents/financialDocument.js +36 -28
- package/lib/documents/invoice.js +49 -31
- package/lib/documents/receipt.js +40 -25
- package/lib/inputs.js +78 -24
- package/mindee/api/financialDocument.js +47 -0
- package/mindee/api/index.js +5 -0
- package/mindee/api/invoice.js +53 -0
- package/mindee/api/object.js +82 -0
- package/mindee/api/receipt.js +39 -0
- package/mindee/api/request.js +70 -0
- package/mindee/api/response.js +65 -0
- package/mindee/documents/document.js +69 -0
- package/mindee/documents/fields/amount.js +25 -0
- package/mindee/documents/fields/date.js +29 -0
- package/mindee/documents/fields/field.js +89 -0
- package/mindee/documents/fields/index.js +7 -0
- package/mindee/documents/fields/locale.js +30 -0
- package/mindee/documents/fields/orientation.js +24 -0
- package/mindee/documents/fields/paymentDetails.js +52 -0
- package/mindee/documents/fields/tax.js +47 -0
- package/mindee/documents/financialDocument.js +228 -0
- package/mindee/documents/index.js +5 -0
- package/mindee/documents/invoice.js +411 -0
- package/mindee/documents/receipt.js +287 -0
- package/mindee/errors/handler.js +16 -0
- package/mindee/index.js +36 -0
- package/mindee/inputs.js +156 -0
- package/mindee/logger.js +33 -0
- package/package.json +7 -3
- package/LICENSE.md +0 -674
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
const Document = require("./document");
|
|
2
|
+
const Field = require("./fields").field;
|
|
3
|
+
const Date = require("./fields").date;
|
|
4
|
+
const Amount = require("./fields").amount;
|
|
5
|
+
const Locale = require("./fields").locale;
|
|
6
|
+
const Orientation = require("./fields").orientation;
|
|
7
|
+
const PaymentDetails = require("./fields").paymentDetails;
|
|
8
|
+
const Tax = require("./fields").tax;
|
|
9
|
+
|
|
10
|
+
class Invoice extends Document {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} apiPrediction - Json parsed prediction from HTTP response
|
|
13
|
+
* @param {Input} input - Input object
|
|
14
|
+
* @param {Object} locale - locale value for creating Invoice object from scratch
|
|
15
|
+
* @param {Object} totalIncl - total tax included value for creating Invoice object from scratch
|
|
16
|
+
* @param {Object} totalExcl - total tax excluded value for creating Invoice object from scratch
|
|
17
|
+
* @param {Object} invoiceDate - invoice date value for creating Invoice object from scratch
|
|
18
|
+
* @param {Object} invoiceNumber - invoice number value for creating Invoice object from scratch
|
|
19
|
+
* @param {Object} taxes - taxes value for creating Invoice object from scratch
|
|
20
|
+
* @param {Object} supplier - supplier value for creating Invoice object from scratch
|
|
21
|
+
* @param {Object} paymentDetails - payment details value for creating Invoice object from scratch
|
|
22
|
+
* @param {Object} companyNumber - company number value for creating Invoice object from scratch
|
|
23
|
+
* @param {Object} vatNumber - vat number value for creating Invoice object from scratch
|
|
24
|
+
* @param {Object} orientation - orientation value for creating Invoice object from scratch
|
|
25
|
+
* @param {Object} totalTax - total tax value for creating Invoice object from scratch
|
|
26
|
+
* @param {Number} pageNumber - pageNumber for multi pages pdf input
|
|
27
|
+
* @param {String} level - specify whether object is built from "page" level or "document" level prediction
|
|
28
|
+
*/
|
|
29
|
+
constructor({
|
|
30
|
+
apiPrediction = undefined,
|
|
31
|
+
inputFile = undefined,
|
|
32
|
+
locale = undefined,
|
|
33
|
+
totalIncl = undefined,
|
|
34
|
+
totalExcl = undefined,
|
|
35
|
+
invoiceDate = undefined,
|
|
36
|
+
invoiceNumber = undefined,
|
|
37
|
+
dueDate = undefined,
|
|
38
|
+
taxes = undefined,
|
|
39
|
+
supplier = undefined,
|
|
40
|
+
paymentDetails = undefined,
|
|
41
|
+
companyNumber = undefined,
|
|
42
|
+
vatNumber = undefined,
|
|
43
|
+
orientation = undefined,
|
|
44
|
+
totalTax = undefined,
|
|
45
|
+
pageNumber = 0,
|
|
46
|
+
level = "page",
|
|
47
|
+
}) {
|
|
48
|
+
super(inputFile);
|
|
49
|
+
this.level = level;
|
|
50
|
+
this.constructPrediction = function (item) {
|
|
51
|
+
return { prediction: { value: item }, valueKey: "value", pageNumber };
|
|
52
|
+
};
|
|
53
|
+
if (apiPrediction === undefined) {
|
|
54
|
+
this.#initFromScratch({
|
|
55
|
+
locale,
|
|
56
|
+
totalIncl,
|
|
57
|
+
totalExcl,
|
|
58
|
+
invoiceDate,
|
|
59
|
+
invoiceNumber,
|
|
60
|
+
dueDate,
|
|
61
|
+
taxes,
|
|
62
|
+
supplier,
|
|
63
|
+
paymentDetails,
|
|
64
|
+
companyNumber,
|
|
65
|
+
vatNumber,
|
|
66
|
+
orientation,
|
|
67
|
+
pageNumber,
|
|
68
|
+
totalTax,
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
this.#initFromApiPrediction(apiPrediction, pageNumber);
|
|
72
|
+
}
|
|
73
|
+
this.#checklist();
|
|
74
|
+
this.#reconstruct();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#initFromScratch({
|
|
78
|
+
locale,
|
|
79
|
+
totalIncl,
|
|
80
|
+
totalExcl,
|
|
81
|
+
totalTax,
|
|
82
|
+
invoiceDate,
|
|
83
|
+
invoiceNumber,
|
|
84
|
+
dueDate,
|
|
85
|
+
taxes,
|
|
86
|
+
supplier,
|
|
87
|
+
paymentDetails,
|
|
88
|
+
companyNumber,
|
|
89
|
+
vatNumber,
|
|
90
|
+
orientation,
|
|
91
|
+
pageNumber,
|
|
92
|
+
}) {
|
|
93
|
+
this.locale = new Locale(this.constructPrediction(locale));
|
|
94
|
+
this.totalIncl = new Amount(this.constructPrediction(totalIncl));
|
|
95
|
+
this.totalExcl = new Amount(this.constructPrediction(totalExcl));
|
|
96
|
+
this.totalTax = new Amount(this.constructPrediction(totalTax));
|
|
97
|
+
this.date = new Date(this.constructPrediction(invoiceDate));
|
|
98
|
+
this.invoiceDate = new Date(this.constructPrediction(invoiceDate));
|
|
99
|
+
this.dueDate = new Date(this.constructPrediction(dueDate));
|
|
100
|
+
this.supplier = new Field(this.constructPrediction(supplier));
|
|
101
|
+
this.invoiceNumber = new Field(this.constructPrediction(invoiceNumber));
|
|
102
|
+
this.paymentDetails = new Field(this.constructPrediction(paymentDetails));
|
|
103
|
+
this.companyNumber = new Field(this.constructPrediction(companyNumber));
|
|
104
|
+
this.vatNumber = new Field(this.constructPrediction(vatNumber));
|
|
105
|
+
if (taxes !== undefined) {
|
|
106
|
+
this.taxes = [];
|
|
107
|
+
for (const t of taxes) {
|
|
108
|
+
this.taxes.push(
|
|
109
|
+
new Tax({
|
|
110
|
+
prediction: { value: t[0], rate: t[1] },
|
|
111
|
+
pageNumber,
|
|
112
|
+
valueKey: "value",
|
|
113
|
+
rateKey: "rate",
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (this.level === "page") {
|
|
119
|
+
this.orientation = new Orientation(this.constructPrediction(orientation));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#initFromApiPrediction(apiPrediction, pageNumber) {
|
|
124
|
+
this.words = [];
|
|
125
|
+
this.locale = new Locale({ prediction: apiPrediction.locale, pageNumber });
|
|
126
|
+
this.totalIncl = new Amount({
|
|
127
|
+
prediction: apiPrediction.total_incl,
|
|
128
|
+
valueKey: "value",
|
|
129
|
+
pageNumber,
|
|
130
|
+
});
|
|
131
|
+
this.totalTax = new Amount({
|
|
132
|
+
prediction: { value: undefined, probability: 0.0 },
|
|
133
|
+
valueKey: "value",
|
|
134
|
+
pageNumber,
|
|
135
|
+
});
|
|
136
|
+
this.totalExcl = new Amount({
|
|
137
|
+
prediction: apiPrediction.total_excl,
|
|
138
|
+
valueKey: "value",
|
|
139
|
+
pageNumber,
|
|
140
|
+
});
|
|
141
|
+
this.date = new Date({
|
|
142
|
+
prediction: apiPrediction.date,
|
|
143
|
+
valueKey: "value",
|
|
144
|
+
pageNumber,
|
|
145
|
+
});
|
|
146
|
+
this.invoiceDate = new Date({
|
|
147
|
+
prediction: apiPrediction.date,
|
|
148
|
+
valueKey: "value",
|
|
149
|
+
pageNumber,
|
|
150
|
+
});
|
|
151
|
+
this.taxes = apiPrediction.taxes.map(function (taxPrediction) {
|
|
152
|
+
return new Tax({
|
|
153
|
+
prediction: taxPrediction,
|
|
154
|
+
pageNumber,
|
|
155
|
+
valueKey: "value",
|
|
156
|
+
rateKey: "rate",
|
|
157
|
+
codeKey: "code",
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
this.companyNumber = apiPrediction.company_registration.map(function (
|
|
161
|
+
companyNumber
|
|
162
|
+
) {
|
|
163
|
+
return new Field({
|
|
164
|
+
prediction: companyNumber,
|
|
165
|
+
pageNumber,
|
|
166
|
+
extraFields: ["type"],
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
this.dueDate = new Date({
|
|
170
|
+
prediction: apiPrediction.due_date,
|
|
171
|
+
valueKey: "value",
|
|
172
|
+
pageNumber,
|
|
173
|
+
});
|
|
174
|
+
this.invoiceNumber = new Field({
|
|
175
|
+
prediction: apiPrediction.invoice_number,
|
|
176
|
+
pageNumber,
|
|
177
|
+
});
|
|
178
|
+
this.supplier = new Field({
|
|
179
|
+
prediction: apiPrediction.supplier,
|
|
180
|
+
pageNumber,
|
|
181
|
+
});
|
|
182
|
+
this.paymentDetails = apiPrediction.payment_details.map(function (
|
|
183
|
+
paymentDetail
|
|
184
|
+
) {
|
|
185
|
+
return new PaymentDetails({ prediction: paymentDetail, pageNumber });
|
|
186
|
+
});
|
|
187
|
+
if (this.level === "page") {
|
|
188
|
+
this.orientation = new Orientation({
|
|
189
|
+
prediction: apiPrediction.orientation,
|
|
190
|
+
pageNumber,
|
|
191
|
+
});
|
|
192
|
+
} else {
|
|
193
|
+
this.orientation = new Orientation(
|
|
194
|
+
this.constructPrediction({
|
|
195
|
+
prediction: {
|
|
196
|
+
value: undefined,
|
|
197
|
+
probability: 0.0,
|
|
198
|
+
degrees: undefined,
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
// document.inference.ocr
|
|
204
|
+
if ("mvision" in apiPrediction) this.words = apiPrediction.mvision;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
toString() {
|
|
208
|
+
return `
|
|
209
|
+
-----Invoice data-----
|
|
210
|
+
Filename: ${this.filename}
|
|
211
|
+
Invoice number: ${this.invoiceNumber.value}
|
|
212
|
+
Total amount including taxes: ${this.totalIncl.value}
|
|
213
|
+
Total amount excluding taxes: ${this.totalExcl.value}
|
|
214
|
+
Invoice Date: ${this.invoiceDate.value}
|
|
215
|
+
Supplier name: ${this.supplier.value}
|
|
216
|
+
Taxes: ${this.taxes.map((tax) => tax.toString()).join(" - ")}
|
|
217
|
+
Total taxes: ${this.totalTax.value}
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#checklist() {
|
|
222
|
+
this.checklist = {
|
|
223
|
+
taxesMatchTotalIncl: this.#taxesMatchTotalIncl(),
|
|
224
|
+
taxesMatchTotalExcl: this.#taxesMatchTotalExcl(),
|
|
225
|
+
taxesPlusTotalExclMatchTotalIncl: this.#taxesPlusTotalExclMatchTotalIncl(),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#reconstruct() {
|
|
230
|
+
this.#reconstructTotalTax();
|
|
231
|
+
this.#reconstructTotalExcl();
|
|
232
|
+
this.#reconstructTotalIncl();
|
|
233
|
+
this.#reconstructTotalTaxFromTotals();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#taxesMatchTotalIncl() {
|
|
237
|
+
// Check taxes and total include exist
|
|
238
|
+
if (this.taxes.length === 0 || this.totalIncl.value === undefined)
|
|
239
|
+
return false;
|
|
240
|
+
|
|
241
|
+
// Reconstruct totalIncl from taxes
|
|
242
|
+
let totalVat = 0;
|
|
243
|
+
let reconstructedTotal = 0;
|
|
244
|
+
this.taxes.forEach((tax) => {
|
|
245
|
+
if (tax.value === undefined || !tax.rate) return false;
|
|
246
|
+
totalVat += tax.value;
|
|
247
|
+
reconstructedTotal += tax.value + (100 * tax.value) / tax.rate;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Sanity check
|
|
251
|
+
if (totalVat <= 0) return false;
|
|
252
|
+
|
|
253
|
+
// Crate epsilon
|
|
254
|
+
const eps = 1 / (100 * totalVat);
|
|
255
|
+
|
|
256
|
+
if (
|
|
257
|
+
this.totalIncl.value * (1 - eps) - 0.02 <= reconstructedTotal &&
|
|
258
|
+
reconstructedTotal <= this.totalIncl.value * (1 + eps) + 0.02
|
|
259
|
+
) {
|
|
260
|
+
this.taxes = this.taxes.map((tax) => ({ ...tax, probability: 1.0 }));
|
|
261
|
+
this.totalTax.probability = 1.0;
|
|
262
|
+
this.totalIncl.probability = 1.0;
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
*
|
|
270
|
+
*/
|
|
271
|
+
#taxesMatchTotalExcl() {
|
|
272
|
+
// Check taxes and total amount exist
|
|
273
|
+
if (this.taxes.length === 0 || this.totalExcl.value == null) return false;
|
|
274
|
+
|
|
275
|
+
// Reconstruct total_incl from taxes
|
|
276
|
+
let totalVat = 0;
|
|
277
|
+
let reconstructedTotal = 0;
|
|
278
|
+
this.taxes.forEach((tax) => {
|
|
279
|
+
if (tax.value == null || !tax.rate) return false;
|
|
280
|
+
totalVat += tax.value;
|
|
281
|
+
reconstructedTotal += (100 * tax.value) / tax.rate;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Sanity check
|
|
285
|
+
if (totalVat <= 0) return false;
|
|
286
|
+
|
|
287
|
+
// Crate epsilon
|
|
288
|
+
const eps = 1 / (100 * totalVat);
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
this.totalExcl.value * (1 - eps) - 0.02 <= reconstructedTotal &&
|
|
292
|
+
reconstructedTotal <= this.totalExcl.value * (1 + eps) + 0.02
|
|
293
|
+
) {
|
|
294
|
+
this.taxes = this.taxes.map((tax) => ({ ...tax, probability: 1.0 }));
|
|
295
|
+
this.totalTax.probability = 1.0;
|
|
296
|
+
this.totalExcl.probability = 1.0;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#taxesPlusTotalExclMatchTotalIncl() {
|
|
303
|
+
if (
|
|
304
|
+
this.totalExcl.value === undefined ||
|
|
305
|
+
this.taxes.length == 0 ||
|
|
306
|
+
this.totalIncl === undefined
|
|
307
|
+
)
|
|
308
|
+
return false;
|
|
309
|
+
let totalVat = 0;
|
|
310
|
+
this.taxes.forEach((tax) => (totalVat += tax.value));
|
|
311
|
+
const reconstructedTotal = totalVat + this.totalExcl.value;
|
|
312
|
+
|
|
313
|
+
if (totalVat <= 0) return false;
|
|
314
|
+
|
|
315
|
+
if (
|
|
316
|
+
this.totalIncl.value - 0.01 <= reconstructedTotal &&
|
|
317
|
+
reconstructedTotal <= this.totalIncl.value + 0.01
|
|
318
|
+
) {
|
|
319
|
+
this.taxes = this.taxes.map((tax) => ({ ...tax, probability: 1.0 }));
|
|
320
|
+
this.totalTax.probability = 1.0;
|
|
321
|
+
this.totalIncl.probability = 1.0;
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#reconstructTotalTax() {
|
|
328
|
+
if (this.taxes.length > 0) {
|
|
329
|
+
const totalTax = {
|
|
330
|
+
value: this.taxes.reduce((acc, tax) => {
|
|
331
|
+
return tax.value !== undefined ? acc + tax.value : acc;
|
|
332
|
+
}, 0),
|
|
333
|
+
probability: Field.arrayProbability(this.taxes),
|
|
334
|
+
};
|
|
335
|
+
if (totalTax.value > 0)
|
|
336
|
+
this.totalTax = new Amount({
|
|
337
|
+
prediction: totalTax,
|
|
338
|
+
valueKey: "value",
|
|
339
|
+
reconstructed: true,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#reconstructTotalTaxFromTotals() {
|
|
345
|
+
if (
|
|
346
|
+
this.totalTax.value === undefined &&
|
|
347
|
+
this.totalIncl.value > 0 &&
|
|
348
|
+
this.totalExcl.value > 0 &&
|
|
349
|
+
this.totalExcl.value <= this.totalIncl.value
|
|
350
|
+
) {
|
|
351
|
+
const totalTax = {
|
|
352
|
+
value: this.totalIncl.value - this.totalExcl.value,
|
|
353
|
+
probability: this.totalIncl.probability * this.totalExcl.probability,
|
|
354
|
+
};
|
|
355
|
+
if (totalTax.value > 0)
|
|
356
|
+
this.totalTax = new Amount({
|
|
357
|
+
prediction: totalTax,
|
|
358
|
+
valueKey: "value",
|
|
359
|
+
reconstructed: true,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#reconstructTotalExcl() {
|
|
365
|
+
if (
|
|
366
|
+
this.taxes.length &&
|
|
367
|
+
this.totalIncl.value != null &&
|
|
368
|
+
this.totalExcl.value === undefined
|
|
369
|
+
) {
|
|
370
|
+
const totalExcl = {
|
|
371
|
+
value:
|
|
372
|
+
this.totalIncl.value -
|
|
373
|
+
this.taxes.reduce((acc, tax) => {
|
|
374
|
+
return tax.value !== undefined ? acc + tax.value : acc;
|
|
375
|
+
}, 0),
|
|
376
|
+
probability:
|
|
377
|
+
Field.arrayProbability(this.taxes) * this.totalIncl.probability,
|
|
378
|
+
};
|
|
379
|
+
this.totalExcl = new Amount({
|
|
380
|
+
prediction: totalExcl,
|
|
381
|
+
valueKey: "value",
|
|
382
|
+
reconstructed: true,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
#reconstructTotalIncl() {
|
|
388
|
+
if (
|
|
389
|
+
this.taxes.length &&
|
|
390
|
+
this.totalExcl.value != null &&
|
|
391
|
+
this.totalIncl.value === undefined
|
|
392
|
+
) {
|
|
393
|
+
const totalIncl = {
|
|
394
|
+
value:
|
|
395
|
+
this.totalExcl.value +
|
|
396
|
+
this.taxes.reduce((acc, tax) => {
|
|
397
|
+
return tax.value ? acc + tax.value : acc;
|
|
398
|
+
}, 0.0),
|
|
399
|
+
probability:
|
|
400
|
+
Field.arrayProbability(this.taxes) * this.totalExcl.probability,
|
|
401
|
+
};
|
|
402
|
+
this.totalIncl = new Amount({
|
|
403
|
+
prediction: totalIncl,
|
|
404
|
+
valueKey: "value",
|
|
405
|
+
reconstructed: true,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
module.exports = Invoice;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
const Document = require("./document");
|
|
2
|
+
const Field = require("./fields").field;
|
|
3
|
+
const Date = require("./fields").date;
|
|
4
|
+
const Amount = require("./fields").amount;
|
|
5
|
+
const Locale = require("./fields").locale;
|
|
6
|
+
const Orientation = require("./fields").orientation;
|
|
7
|
+
const Tax = require("./fields").tax;
|
|
8
|
+
const fs = require("fs").promises;
|
|
9
|
+
|
|
10
|
+
class Receipt extends Document {
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} apiPrediction - Json parsed prediction from HTTP response
|
|
13
|
+
* @param {Input} input - Input object
|
|
14
|
+
* @param {Integer} pageNumber - Page number for multi pages pdf input
|
|
15
|
+
* @param {Object} locale - locale value for creating Receipt object from scratch
|
|
16
|
+
* @param {Object} totalIncl - total tax Included value for creating Receipt object from scratch
|
|
17
|
+
* @param {Object} date - date value for creating Receipt object from scratch
|
|
18
|
+
* @param {Object} category - category value for creating Receipt object from scratch
|
|
19
|
+
* @param {Object} merchantName - merchant name value for creating Receipt object from scratch
|
|
20
|
+
* @param {Object} time - time value for creating Receipt object from scratch
|
|
21
|
+
* @param {Object} taxes - taxes value for creating Receipt object from scratch
|
|
22
|
+
* @param {Object} orientation - orientation value for creating Receipt object from scratch
|
|
23
|
+
* @param {Object} totalTax - total taxes value for creating Receipt object from scratch
|
|
24
|
+
* @param {Object} totalExcl - total taxes excluded value for creating Receipt object from scratch
|
|
25
|
+
* @param {String} level - specify whether object is built from "page" level or "document" level prediction
|
|
26
|
+
*/
|
|
27
|
+
constructor({
|
|
28
|
+
apiPrediction = undefined,
|
|
29
|
+
inputFile = undefined,
|
|
30
|
+
locale = undefined,
|
|
31
|
+
totalIncl = undefined,
|
|
32
|
+
date = undefined,
|
|
33
|
+
category = undefined,
|
|
34
|
+
merchantName = undefined,
|
|
35
|
+
time = undefined,
|
|
36
|
+
taxes = undefined,
|
|
37
|
+
orientation = undefined,
|
|
38
|
+
totalTax = undefined,
|
|
39
|
+
totalExcl = undefined,
|
|
40
|
+
pageNumber = 0,
|
|
41
|
+
level = "page",
|
|
42
|
+
}) {
|
|
43
|
+
super(inputFile);
|
|
44
|
+
this.level = level;
|
|
45
|
+
this.constructPrediction = function (item) {
|
|
46
|
+
return { prediction: { value: item }, valueKey: "value", pageNumber };
|
|
47
|
+
};
|
|
48
|
+
if (apiPrediction === undefined) {
|
|
49
|
+
this.#initFromScratch({
|
|
50
|
+
locale,
|
|
51
|
+
totalExcl,
|
|
52
|
+
totalIncl,
|
|
53
|
+
date,
|
|
54
|
+
category,
|
|
55
|
+
merchantName,
|
|
56
|
+
time,
|
|
57
|
+
taxes,
|
|
58
|
+
orientation,
|
|
59
|
+
totalTax,
|
|
60
|
+
pageNumber,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
this.#initFromApiPrediction(apiPrediction, pageNumber);
|
|
64
|
+
}
|
|
65
|
+
this.#checklist();
|
|
66
|
+
this.#reconstruct();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#initFromScratch({
|
|
70
|
+
locale,
|
|
71
|
+
totalExcl,
|
|
72
|
+
totalIncl,
|
|
73
|
+
date,
|
|
74
|
+
category,
|
|
75
|
+
merchantName,
|
|
76
|
+
time,
|
|
77
|
+
taxes,
|
|
78
|
+
orientation,
|
|
79
|
+
totalTax,
|
|
80
|
+
pageNumber,
|
|
81
|
+
}) {
|
|
82
|
+
this.locale = new Locale(this.constructPrediction(locale));
|
|
83
|
+
this.totalIncl = new Amount(this.constructPrediction(totalIncl));
|
|
84
|
+
this.date = new Date(this.constructPrediction(date));
|
|
85
|
+
this.category = new Field(this.constructPrediction(category));
|
|
86
|
+
this.merchantName = new Field(this.constructPrediction(merchantName));
|
|
87
|
+
this.time = new Field(this.constructPrediction(time));
|
|
88
|
+
if (taxes !== undefined) {
|
|
89
|
+
this.taxes = [];
|
|
90
|
+
for (const t of taxes) {
|
|
91
|
+
this.taxes.push(
|
|
92
|
+
new Tax({
|
|
93
|
+
prediction: { value: t[0], rate: t[1] },
|
|
94
|
+
pageNumber,
|
|
95
|
+
valueKey: "value",
|
|
96
|
+
rateKey: "rate",
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.orientation = new Orientation(this.constructPrediction(orientation));
|
|
102
|
+
this.totalTax = new Amount(this.constructPrediction(totalTax));
|
|
103
|
+
this.totalExcl = new Amount(this.constructPrediction(totalExcl));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
Set the object attributes with api prediction values
|
|
108
|
+
@param apiPrediction: Raw prediction from HTTP response
|
|
109
|
+
@param pageNumber: Page number for multi pages pdf input
|
|
110
|
+
*/
|
|
111
|
+
#initFromApiPrediction(apiPrediction, pageNumber) {
|
|
112
|
+
this.words = [];
|
|
113
|
+
this.locale = new Locale({ prediction: apiPrediction.locale, pageNumber });
|
|
114
|
+
this.totalIncl = new Amount({
|
|
115
|
+
prediction: apiPrediction.total_incl,
|
|
116
|
+
valueKey: "value",
|
|
117
|
+
pageNumber,
|
|
118
|
+
});
|
|
119
|
+
this.date = new Date({
|
|
120
|
+
prediction: apiPrediction.date,
|
|
121
|
+
valueKey: "value",
|
|
122
|
+
pageNumber,
|
|
123
|
+
});
|
|
124
|
+
this.category = new Field({
|
|
125
|
+
prediction: apiPrediction.category,
|
|
126
|
+
pageNumber,
|
|
127
|
+
});
|
|
128
|
+
this.merchantName = new Field({
|
|
129
|
+
prediction: apiPrediction.supplier,
|
|
130
|
+
valueKey: "value",
|
|
131
|
+
pageNumber,
|
|
132
|
+
});
|
|
133
|
+
this.time = new Field({
|
|
134
|
+
prediction: apiPrediction.time,
|
|
135
|
+
valueKey: "value",
|
|
136
|
+
pageNumber,
|
|
137
|
+
});
|
|
138
|
+
this.taxes = apiPrediction.taxes.map(
|
|
139
|
+
(taxPrediction) =>
|
|
140
|
+
new Tax({
|
|
141
|
+
prediction: taxPrediction,
|
|
142
|
+
pageNumber,
|
|
143
|
+
valueKey: "value",
|
|
144
|
+
rateKey: "rate",
|
|
145
|
+
codeKey: "code",
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
this.totalTax = new Amount({
|
|
149
|
+
prediction: { value: undefined, probability: 0 },
|
|
150
|
+
valueKey: "value",
|
|
151
|
+
pageNumber,
|
|
152
|
+
});
|
|
153
|
+
this.totalExcl = new Amount({
|
|
154
|
+
prediction: { value: undefined, probability: 0 },
|
|
155
|
+
valueKey: "value",
|
|
156
|
+
pageNumber,
|
|
157
|
+
});
|
|
158
|
+
if (this.level === "page") {
|
|
159
|
+
this.orientation = new Orientation({
|
|
160
|
+
prediction: apiPrediction.orientation,
|
|
161
|
+
pageNumber,
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
this.orientation = new Orientation(
|
|
165
|
+
this.constructPrediction({
|
|
166
|
+
prediction: {
|
|
167
|
+
value: undefined,
|
|
168
|
+
probability: 0.0,
|
|
169
|
+
degrees: undefined,
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if ("mvision" in apiPrediction) this.words = apiPrediction.mvision;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
toString() {
|
|
178
|
+
return `
|
|
179
|
+
-----Receipt data-----
|
|
180
|
+
Filename: ${this.filename}
|
|
181
|
+
Total amount: ${this.totalIncl.value}
|
|
182
|
+
Date: ${this.date.value}
|
|
183
|
+
Category: ${this.category.value}
|
|
184
|
+
Time: ${this.time.value}
|
|
185
|
+
Merchant name: ${this.merchantName.value}
|
|
186
|
+
Taxes: ${this.taxes.map((tax) => tax.toString()).join(" - ")}
|
|
187
|
+
Total taxes: ${this.totalTax.value}
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static async load(path) {
|
|
192
|
+
const file = fs.readFile(path);
|
|
193
|
+
const args = JSON.parse(file);
|
|
194
|
+
return new Receipt({ reconsctruted: true, ...args });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Call all check methods
|
|
199
|
+
*/
|
|
200
|
+
#checklist() {
|
|
201
|
+
this.checklist = { taxesMatchTotalIncl: this.#taxesMatchTotal() };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
#taxesMatchTotal() {
|
|
205
|
+
// Check taxes and total amount exist
|
|
206
|
+
|
|
207
|
+
if (this.taxes.length === 0 || this.totalIncl.value == null) return false;
|
|
208
|
+
|
|
209
|
+
// Reconstruct total_incl from taxes
|
|
210
|
+
let totalVat = 0;
|
|
211
|
+
let reconstructedTotal = 0;
|
|
212
|
+
this.taxes.forEach((tax) => {
|
|
213
|
+
if (tax.value == null || !tax.rate) return false;
|
|
214
|
+
totalVat += tax.value;
|
|
215
|
+
reconstructedTotal += tax.value + (100 * tax.value) / tax.rate;
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Sanity check
|
|
219
|
+
if (totalVat <= 0) return false;
|
|
220
|
+
|
|
221
|
+
// Crate epsilon
|
|
222
|
+
const eps = 1 / (100 * totalVat);
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
this.totalIncl.value * (1 - eps) - 0.02 <= reconstructedTotal &&
|
|
226
|
+
reconstructedTotal <= this.totalIncl.value * (1 + eps) + 0.02
|
|
227
|
+
) {
|
|
228
|
+
this.taxes = this.taxes.map((tax) => ({ ...tax, probability: 1.0 }));
|
|
229
|
+
this.totalTax.probability = 1.0;
|
|
230
|
+
this.totalIncl.probability = 1.0;
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Call all fields that need to be reconstructed
|
|
238
|
+
*/
|
|
239
|
+
#reconstruct() {
|
|
240
|
+
this.#reconstructTotalExclFromTCCAndTaxes();
|
|
241
|
+
this.#reconstructTotalTax();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Set this.totalExcl with Amount object
|
|
246
|
+
* The totalExcl Amount value is the difference between totalIncl and sum of taxes
|
|
247
|
+
* The totalExcl Amount probability is the product of this.taxes probabilities multiplied by totalIncl probability
|
|
248
|
+
*/
|
|
249
|
+
#reconstructTotalExclFromTCCAndTaxes() {
|
|
250
|
+
if (this.taxes.length && this.totalIncl.value != null) {
|
|
251
|
+
const totalExcl = {
|
|
252
|
+
value: this.totalIncl.value - Field.arraySum(this.taxes),
|
|
253
|
+
probability:
|
|
254
|
+
Field.arrayProbability(this.taxes) * this.totalIncl.probability,
|
|
255
|
+
};
|
|
256
|
+
this.totalExcl = new Amount({
|
|
257
|
+
prediction: totalExcl,
|
|
258
|
+
valueKey: "value",
|
|
259
|
+
reconstructed: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Set this.totalTax with Amount object
|
|
266
|
+
* The totalTax Amount value is the sum of all this.taxes value
|
|
267
|
+
* The totalTax Amount probability is the product of this.taxes probabilities
|
|
268
|
+
*/
|
|
269
|
+
#reconstructTotalTax() {
|
|
270
|
+
if (this.taxes.length && this.totalTax.value == null) {
|
|
271
|
+
const totalTax = {
|
|
272
|
+
value: this.taxes
|
|
273
|
+
.map((tax) => tax.value || 0)
|
|
274
|
+
.reduce((a, b) => a + b, 0),
|
|
275
|
+
probability: Field.arrayProbability(this.taxes),
|
|
276
|
+
};
|
|
277
|
+
if (totalTax.value > 0)
|
|
278
|
+
this.totalTax = new Amount({
|
|
279
|
+
prediction: totalTax,
|
|
280
|
+
valueKey: "value",
|
|
281
|
+
reconstructed: true,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = Receipt;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const logger = require("../logger");
|
|
2
|
+
|
|
3
|
+
class ErrorHandler {
|
|
4
|
+
constructor(throwOnError = true) {
|
|
5
|
+
this.throwOnError = throwOnError;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
throw(error, force = true) {
|
|
9
|
+
if (this.throwOnError || force) throw error;
|
|
10
|
+
else logger.error(error.message);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const errorHandler = new ErrorHandler();
|
|
15
|
+
|
|
16
|
+
module.exports = errorHandler;
|