@fin.cx/opendata 4.7.0 → 4.8.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/.smartconfig.json +6 -2
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/financialstatements/amounts.d.ts +58 -0
- package/dist_ts/financialstatements/amounts.js +130 -0
- package/dist_ts/financialstatements/classes.financialstatementerror.d.ts +24 -0
- package/dist_ts/financialstatements/classes.financialstatementerror.js +10 -0
- package/dist_ts/financialstatements/constants.d.ts +16 -0
- package/dist_ts/financialstatements/constants.js +20 -0
- package/dist_ts/financialstatements/dates.d.ts +16 -0
- package/dist_ts/financialstatements/dates.js +72 -0
- package/dist_ts/financialstatements/html.d.ts +21 -0
- package/dist_ts/financialstatements/html.js +92 -0
- package/dist_ts/financialstatements/index.d.ts +6 -0
- package/dist_ts/financialstatements/index.js +11 -0
- package/dist_ts/financialstatements/interfaces.d.ts +258 -0
- package/dist_ts/financialstatements/interfaces.js +2 -0
- package/dist_ts/financialstatements/layout.d.ts +20 -0
- package/dist_ts/financialstatements/layout.html.d.ts +8 -0
- package/dist_ts/financialstatements/layout.html.js +125 -0
- package/dist_ts/financialstatements/layout.js +2 -0
- package/dist_ts/financialstatements/layout.pdf.d.ts +6 -0
- package/dist_ts/financialstatements/layout.pdf.js +229 -0
- package/dist_ts/financialstatements/mediatypes.d.ts +3 -0
- package/dist_ts/financialstatements/mediatypes.js +11 -0
- package/dist_ts/financialstatements/outline.d.ts +22 -0
- package/dist_ts/financialstatements/outline.js +26 -0
- package/dist_ts/financialstatements/parse.esef.d.ts +13 -0
- package/dist_ts/financialstatements/parse.esef.js +349 -0
- package/dist_ts/financialstatements/parse.hgb.d.ts +9 -0
- package/dist_ts/financialstatements/parse.hgb.js +1014 -0
- package/dist_ts/financialstatements/parse.statement.d.ts +8 -0
- package/dist_ts/financialstatements/parse.statement.js +25 -0
- package/dist_ts/financialstatements/plugins.d.ts +10 -0
- package/dist_ts/financialstatements/plugins.js +15 -0
- package/dist_ts/financialstatements/read.d.ts +10 -0
- package/dist_ts/financialstatements/read.js +113 -0
- package/package.json +14 -4
- package/readme.md +82 -4
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/financialstatements/amounts.ts +181 -0
- package/ts/financialstatements/classes.financialstatementerror.ts +35 -0
- package/ts/financialstatements/constants.ts +24 -0
- package/ts/financialstatements/dates.ts +89 -0
- package/ts/financialstatements/html.ts +107 -0
- package/ts/financialstatements/index.ts +17 -0
- package/ts/financialstatements/interfaces.ts +268 -0
- package/ts/financialstatements/layout.html.ts +130 -0
- package/ts/financialstatements/layout.pdf.ts +268 -0
- package/ts/financialstatements/layout.ts +16 -0
- package/ts/financialstatements/mediatypes.ts +14 -0
- package/ts/financialstatements/outline.ts +44 -0
- package/ts/financialstatements/parse.esef.ts +394 -0
- package/ts/financialstatements/parse.hgb.ts +1235 -0
- package/ts/financialstatements/parse.statement.ts +26 -0
- package/ts/financialstatements/plugins.ts +29 -0
- package/ts/financialstatements/read.ts +135 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { FinancialStatementError } from './classes.financialstatementerror.js';
|
|
2
|
+
import { maxStatementBytes } from './constants.js';
|
|
3
|
+
import { decodeHtml } from './html.js';
|
|
4
|
+
import { htmlLayout } from './layout.html.js';
|
|
5
|
+
import { pdfLayout } from './layout.pdf.js';
|
|
6
|
+
import { statementMediaTypeOf } from './mediatypes.js';
|
|
7
|
+
import { readHgbStatement } from './parse.hgb.js';
|
|
8
|
+
import type { IHgbStatement } from './interfaces.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reads a statement under the HGB from the HTML or the PDF a register shows: the balance sheet
|
|
12
|
+
* (§ 266 HGB) and, where one is published, the income statement (§ 275 HGB). Everything it returns
|
|
13
|
+
* was printed and adds up; where the current year's balance sheet cannot be read with certainty it
|
|
14
|
+
* throws `unparseable` rather than guess. A PDF is read from its text layer only.
|
|
15
|
+
*/
|
|
16
|
+
export const parseStatementDocument = async (bytesArg: Uint8Array, mediaTypeArg: string): Promise<IHgbStatement> => {
|
|
17
|
+
const mediaType = statementMediaTypeOf(mediaTypeArg);
|
|
18
|
+
if (mediaType !== 'text/html' && mediaType !== 'application/xhtml+xml' && mediaType !== 'application/pdf') {
|
|
19
|
+
throw new FinancialStatementError('unsupported_media_type', `A statement in "${mediaTypeArg}" is not read here; HTML and PDF are.`);
|
|
20
|
+
}
|
|
21
|
+
if (bytesArg.byteLength > maxStatementBytes) {
|
|
22
|
+
throw new FinancialStatementError('too_large', `The document has ${bytesArg.byteLength} bytes, more than the ${maxStatementBytes} read here.`);
|
|
23
|
+
}
|
|
24
|
+
const blocks = mediaType === 'application/pdf' ? await pdfLayout(bytesArg) : htmlLayout(decodeHtml(bytesArg));
|
|
25
|
+
return readHgbStatement(blocks);
|
|
26
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// This entry point stands alone: it imports nothing from the rest of the package and needs no storage.
|
|
2
|
+
|
|
3
|
+
// node native scope
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
path,
|
|
9
|
+
createRequire,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// @push.rocks scope
|
|
13
|
+
import * as smartarchiveZip from '@push.rocks/smartarchive/web/zip';
|
|
14
|
+
import * as smartxmlXpath from '@push.rocks/smartxml/xpath';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
smartarchiveZip,
|
|
18
|
+
smartxmlXpath,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// third party scope
|
|
22
|
+
import * as parse5 from 'parse5';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
parse5,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** PDF.js for Node.js (its legacy build), loaded when the first PDF is read */
|
|
29
|
+
export const loadPdfjs = () => import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { FinancialStatementError } from './classes.financialstatementerror.js';
|
|
2
|
+
import { maxStatementBytes, unternehmensregisterNotice } from './constants.js';
|
|
3
|
+
import { statementMediaTypeOf } from './mediatypes.js';
|
|
4
|
+
import { parseEsefPackage, parseEsefReport } from './parse.esef.js';
|
|
5
|
+
import { parseStatementDocument } from './parse.statement.js';
|
|
6
|
+
import type {
|
|
7
|
+
IFiling,
|
|
8
|
+
IFinancialStatement,
|
|
9
|
+
IReadFinancialStatementOptions,
|
|
10
|
+
IStatementParseFailure,
|
|
11
|
+
TFilingKind,
|
|
12
|
+
TFilingSource,
|
|
13
|
+
TParsedStatement,
|
|
14
|
+
TSizeClass,
|
|
15
|
+
} from './interfaces.js';
|
|
16
|
+
|
|
17
|
+
const filingSources: TFilingSource[] = ['unternehmensregister', 'bundesanzeiger', 'issuer', 'unknown'];
|
|
18
|
+
const filingKinds: TFilingKind[] = ['annual_accounts', 'consolidated_accounts', 'deposited_balance_sheet', 'annual_financial_report'];
|
|
19
|
+
const sizeClasses: TSizeClass[] = ['micro', 'small', 'medium', 'large'];
|
|
20
|
+
const parseFailureCodes = new Set<string>(['invalid_package', 'unreadable', 'no_text', 'unparseable', 'too_large']);
|
|
21
|
+
|
|
22
|
+
/** the inline XBRL namespace, which marks an HTML document as an ESEF report */
|
|
23
|
+
const inlineXbrlNamespace = 'http://www.xbrl.org/2013/inlineXBRL';
|
|
24
|
+
|
|
25
|
+
const invalid = (messageArg: string): never => {
|
|
26
|
+
throw new FinancialStatementError('invalid_input', messageArg);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const isIsoDate = (valueArg: string): boolean => {
|
|
30
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(valueArg)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const date = new Date(`${valueArg}T00:00:00Z`);
|
|
34
|
+
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === valueArg;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const filingOf = (inputArg: IReadFinancialStatementOptions['filing']): IFiling => {
|
|
38
|
+
if (!filingSources.includes(inputArg?.source)) {
|
|
39
|
+
invalid(`The filing's source "${inputArg?.source}" is not one of ${filingSources.join(', ')}.`);
|
|
40
|
+
}
|
|
41
|
+
if (!filingKinds.includes(inputArg.kind)) {
|
|
42
|
+
invalid(`The filing's kind "${inputArg.kind}" is not one of ${filingKinds.join(', ')}.`);
|
|
43
|
+
}
|
|
44
|
+
if (inputArg.sizeClass != null && !sizeClasses.includes(inputArg.sizeClass)) {
|
|
45
|
+
invalid(`The filing's size class "${inputArg.sizeClass}" is not one of ${sizeClasses.join(', ')}.`);
|
|
46
|
+
}
|
|
47
|
+
for (const field of ['fiscalYearStart', 'fiscalYearEnd', 'publishedOn'] as const) {
|
|
48
|
+
const value = inputArg[field];
|
|
49
|
+
if (value != null && (typeof value !== 'string' || !isIsoDate(value))) {
|
|
50
|
+
invalid(`The filing's ${field} "${value}" is not an ISO date (YYYY-MM-DD).`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (inputArg.source === 'unternehmensregister' && inputArg.publishedOn == null) {
|
|
54
|
+
invalid('A filing from the Unternehmensregister needs publishedOn, the day it was posted or deposited, for its origin note.');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
source: inputArg.source,
|
|
58
|
+
kind: inputArg.kind,
|
|
59
|
+
companyName: inputArg.companyName ?? null,
|
|
60
|
+
registerReference: inputArg.registerReference ?? null,
|
|
61
|
+
lei: inputArg.lei ?? null,
|
|
62
|
+
fiscalYearStart: inputArg.fiscalYearStart ?? null,
|
|
63
|
+
fiscalYearEnd: inputArg.fiscalYearEnd ?? null,
|
|
64
|
+
publishedOn: inputArg.publishedOn ?? null,
|
|
65
|
+
title: inputArg.title ?? null,
|
|
66
|
+
sizeClass: inputArg.sizeClass ?? null,
|
|
67
|
+
documentId: inputArg.documentId ?? null,
|
|
68
|
+
sourceUrl: inputArg.sourceUrl ?? null,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const sha256Of = async (bytesArg: Uint8Array<ArrayBuffer>): Promise<string> => {
|
|
73
|
+
const digest = new Uint8Array(await globalThis.crypto.subtle.digest('SHA-256', bytesArg));
|
|
74
|
+
return [...digest].map((byteArg) => byteArg.toString(16).padStart(2, '0')).join('');
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Keeps a published statement as a caller stores it: the filing as the caller states it, the
|
|
79
|
+
* document byte for byte with its SHA-256, and what could be read from it: an ESEF report by
|
|
80
|
+
* `parseEsefPackage` or `parseEsefReport`, any other HTML or PDF by `parseStatementDocument`. A
|
|
81
|
+
* document that cannot be read with certainty is kept with `parsed` null and `parseFailure` saying
|
|
82
|
+
* why. Throws `invalid_input`, `unsupported_media_type` and, for a document larger than
|
|
83
|
+
* `maxStatementBytes`, `too_large`, as nothing would be kept.
|
|
84
|
+
*/
|
|
85
|
+
export const readFinancialStatement = async (optionsArg: IReadFinancialStatementOptions): Promise<IFinancialStatement> => {
|
|
86
|
+
if (!(optionsArg?.bytes instanceof Uint8Array)) {
|
|
87
|
+
invalid('bytes must be a Uint8Array.');
|
|
88
|
+
}
|
|
89
|
+
const filing = filingOf(optionsArg.filing);
|
|
90
|
+
if (optionsArg.bytes.byteLength > maxStatementBytes) {
|
|
91
|
+
throw new FinancialStatementError(
|
|
92
|
+
'too_large',
|
|
93
|
+
`The document has ${optionsArg.bytes.byteLength} bytes, more than the ${maxStatementBytes} read here.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const mediaType = statementMediaTypeOf(String(optionsArg.mediaType));
|
|
97
|
+
if (!mediaType) {
|
|
98
|
+
throw new FinancialStatementError(
|
|
99
|
+
'unsupported_media_type',
|
|
100
|
+
`A statement in "${optionsArg.mediaType}" is not read here; HTML, PDF, XHTML and ZIP packages are.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const bytes = new Uint8Array(optionsArg.bytes);
|
|
104
|
+
const sha256 = await sha256Of(bytes);
|
|
105
|
+
let parsed: TParsedStatement | null = null;
|
|
106
|
+
let parseFailure: IStatementParseFailure | null = null;
|
|
107
|
+
try {
|
|
108
|
+
if (mediaType === 'application/zip') {
|
|
109
|
+
parsed = await parseEsefPackage(bytes);
|
|
110
|
+
} else if (
|
|
111
|
+
mediaType === 'application/xhtml+xml' ||
|
|
112
|
+
(mediaType === 'text/html' && new TextDecoder('windows-1252').decode(bytes.subarray(0, 65536)).includes(inlineXbrlNamespace))
|
|
113
|
+
) {
|
|
114
|
+
parsed = parseEsefReport(bytes);
|
|
115
|
+
} else {
|
|
116
|
+
parsed = await parseStatementDocument(bytes, mediaType);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (!(error instanceof FinancialStatementError) || !parseFailureCodes.has(error.code)) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
parseFailure = { code: error.code as IStatementParseFailure['code'], message: error.message };
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
filing,
|
|
126
|
+
raw: { bytes, mediaType, sha256 },
|
|
127
|
+
parsed,
|
|
128
|
+
parseFailure,
|
|
129
|
+
obtainedAt: (optionsArg.now ?? Date.now)(),
|
|
130
|
+
notice:
|
|
131
|
+
filing.source === 'unternehmensregister'
|
|
132
|
+
? unternehmensregisterNotice(filing.publishedOn as string, filing.kind === 'deposited_balance_sheet')
|
|
133
|
+
: null,
|
|
134
|
+
};
|
|
135
|
+
};
|