# dpm-xbrl dpm-xbrl reads and writes EBA-style regulatory XBRL reports in both serializations, xBRL-CSV report packages and XBRL 2.1 XML instances, through one neutral fact model. While it reads, it records the [EBA Filing Rules][eba-filing-rules] that the report breaks and that the file alone can show. dpm-xbrl is an independent project, not affiliated with or endorsed by the European Banking Authority. It targets reports built on the data point model (DPM) taxonomies of the European Banking Authority and is tested on EBA reports. It uses only the standard library and needs Python 3.11 or newer. ## Who it is for - Teams that receive, produce or archive EBA reports (COREP, FINREP, large exposures, resolution, remuneration and the other DPM frameworks) and need the facts in Python. - Pipelines that must stop a report the EBA would reject before it is sent. - Rule engines, such as [dpm-xl], that evaluate validation rules on the facts of a report. ## In 30 seconds ```python from dpmxbrl import read_report report = read_report("report.zip") # or a directory, report.json, .xbrl, or bytes print(report.entity_id, report.reference_date, len(report.facts)) for fact in report.facts_for_template("C_28.00"): print(fact.datapoint, dict(fact.keys), fact.value) ``` ```text SYNTHETICDPMXL000002.CON 2025-12-31 14 dp5488013 {'qINC': 'CP0001'} 67129000 dp5488014 {'qINC': 'CP0001'} 60309000 dp5488013 {'qINC': 'CP0002'} 41250000 dp5488014 {'qINC': 'CP0002'} 38900000 ``` The same report from the command line, with the filing rules it breaks: ```console $ dpm-xbrl inspect report.zip Format: xbrl-csv Entry point: http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json Entity: SYNTHETICDPMXL000002.CON (https://eurofiling.info/eu/rs) Reference date: 2025-12-31 Base currency: iso4217:EUR Filing indicators: 4 reported of 5 Facts: 14 c_00.01 2 c_26.00 4 c_27.00 4 c_28.00 4 Warnings: 1 - reports/report.json: 1 extended metadata document could not be resolved offline (http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json); tables without resolved metadata use the EBA datapoint/factValue convention EBA filing rules broken: 0 must be fixed, 1 recommended - [warning] File naming The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. ``` The sample report is [`report.zip`](samples/report.zip). Every example in these pages runs against the files in [`docs/samples`](https://github.com/luukhopman/dpm-xbrl/tree/main/docs/samples), and the test suite checks that each prints what is shown. ## Install ```text pip install dpm-xbrl # standard library only pip install "dpm-xbrl[fast]" # isal: inflates ZIP entries about twice as fast pip install "dpm-xbrl[frames]" # polars: columnar reading of large reports ``` The `fast` extra changes speed only; the bytes read are the same either way. ## Key features - **One fact model for both formats.** `Report` and `Fact` are frozen dataclasses; see [the fact model](concepts/fact-model.md). - **EBA filing rules.** Each report carries the filing rules it breaks, with the rule number, the EBA tag, a severity and locations; see [check EBA filing rules](guides/check-filing-rules.md). - **Millions of facts.** The `frames` extra reads a 7-million-fact package into polars frames in about a second; see [read large reports as frames](guides/large-reports.md). - **Writing.** Deterministic xBRL-CSV packages and XBRL-XML instances that read back as written; see [convert between formats](guides/convert-formats.md). - **Offline.** Nothing is fetched over the network; xBRL-CSV metadata resolves through local taxonomy packages; see [resolve metadata offline](guides/taxonomy-packages.md). - **Safe on untrusted input.** Resource limits, ZIP and XML hardening, and a single exception type; see [the security model](concepts/security.md). ## Toolkit **Part of the dpm toolkit**, three Python packages that work together: | Package | What it does | | :-- | :-- | | dpm-xl | Parse and evaluate DPM-XL validation rules | | dpm-xbrl | Read, write and check xBRL-CSV and XBRL-XML reports **(this package)** | | dpm-synth | Generate test reports that satisfy DPM-XL rules | The [toolkit tour](https://luukhopman.github.io/dpm-xl/guides/toolkit-tour/) takes one report through all three: generated with dpm-synth, read with dpm-xbrl, then evaluated and explained with dpm-xl. ## Where next - New here: the [getting started](getting-started.md) tutorial. - A task to do: the [how-to guides](guides/index.md). - How it works: the [concepts](concepts/index.md). - Exact behaviour: the [reference](reference/index.md), including the [API](reference/api/dpmxbrl.md) and the [command line](reference/cli.md). - Working with an AI agent: [dpm-xbrl for AI agents](agents.md), [`llms.txt`](llms.txt) and [`llms-full.txt`](llms-full.txt). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Getting started This tutorial takes you from installing dpm-xbrl to reading a report, looking at its facts and finding the EBA filing rules it breaks. It takes about ten minutes. ## Install dpm-xbrl needs Python 3.11 or newer and has no required dependencies. ```text pip install dpm-xbrl ``` or, in a project managed with [uv](https://docs.astral.sh/uv/): ```text uv add dpm-xbrl ``` Check the installed version: ```console $ dpm-xbrl --version dpm-xbrl 1.0.0 ``` ## Get a sample report Download two small sample reports into an empty folder: - [`report.zip`](samples/report.zip): a COREP large-exposures report as an xBRL-CSV report package, written in the EBA's `datapoint`/`factValue` convention. - [`needs_fixes.zip`](samples/needs_fixes.zip): the same report with mistakes the EBA would reject. An xBRL-CSV report package is a ZIP holding a few JSON and CSV files: ```python from zipfile import ZipFile print(*ZipFile("report.zip").namelist(), sep="\n") ``` ```text report/META-INF/reportPackage.json report/reports/report.json report/reports/parameters.csv report/reports/FilingIndicators.csv report/reports/c_00.01.csv report/reports/c_26.00.csv report/reports/c_27.00.csv report/reports/c_28.00.csv ``` `parameters.csv` names the reporting entity and the reference date, `FilingIndicators.csv` says which templates are reported, and each other CSV file is one table of facts. ## Inspect it from the command line ```console $ dpm-xbrl inspect report.zip Format: xbrl-csv Entry point: http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json Entity: SYNTHETICDPMXL000002.CON (https://eurofiling.info/eu/rs) Reference date: 2025-12-31 Base currency: iso4217:EUR Filing indicators: 4 reported of 5 Facts: 14 c_00.01 2 c_26.00 4 c_27.00 4 c_28.00 4 Warnings: 1 - reports/report.json: 1 extended metadata document could not be resolved offline (http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json); tables without resolved metadata use the EBA datapoint/factValue convention EBA filing rules broken: 0 must be fixed, 1 recommended - [warning] File naming The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. ``` Two things to notice: - The warning says the taxonomy's metadata was not available offline, so the tables were read in the EBA `datapoint`/`factValue` convention. That is normal and enough for most uses; see [EBA convention and resolved metadata](concepts/metadata.md). - The only filing rule broken is a recommendation: EBA report files are named `ReportSubject_Country_..._CreationTimestamp`, and `report.zip` is not. ## Read it in Python ```python from dpmxbrl import read_report report = read_report("report.zip") print(report.format, report.entity_id, report.reference_date) print(report.filing_indicators) ``` ```text xbrl-csv SYNTHETICDPMXL000002.CON 2025-12-31 {'C_00.01': True, 'C_26.00': True, 'C_27.00': True, 'C_28.00': True, 'C_29.00': False} ``` `read_report` detects the format, so the same call reads an XBRL-XML instance. It returns a `Report`, a frozen dataclass with the report's header and a tuple of facts. ## Look at the facts Each `Fact` carries its value as written and where it came from: ```python fact = report.facts[2] print(fact.datapoint, fact.value, fact.template, fact.location) ``` ```text dp457468 56058000 c_26.00 reports/c_26.00.csv:2 ``` Open tables have key columns, which identify the row, for example the counterparty of a large exposure: ```python for fact in report.facts_for_template("C_27.00"): print(fact.datapoint, fact.keys["qINC"], fact.value) ``` ```text dp5486584 CP0001 Northwind Holdings dp5485645 CP0001 eba_NC:DE dp5486584 CP0002 Contoso Group dp5485645 CP0002 eba_NC:FR ``` `facts_for_template` takes the code of a filing indicator (`C_27.00`) and compares it case-insensitively with the table template (`c_27.00`). Values are strings exactly as written. The decimals that apply to a numeric value are report parameters in this convention: ```python print(report.decimals_parameters) ``` ```text {'decimalsInteger': 0, 'decimalsMonetary': -3, 'decimalsPercentage': 4} ``` ## Find the filing rules a report breaks `needs_fixes.zip` contains four mistakes. Each broken rule is a `FilingFinding` in `Report.filing`: ```python broken = read_report("needs_fixes.zip") for finding in broken.filing: print( f"{finding.severity:7} {finding.rule:11} {finding.code:31} {finding.examples}" ) ``` ```text error 2.19 nilUsed ('reports/c_26.00.csv:3',) warning 3.11 leadingOrTrailingSpacesInText ('reports/c_27.00.csv:2', 'reports/c_27.00.csv:3') error xBRL-CSV 1 decimalsSuffixUsed ('reports/c_26.00.csv:2',) error xBRL-CSV 6 tableForNegativeFilingIndicator ('reports/c_29.00.csv',) warning File naming invalidFileName ('needs_fixes.zip',) ``` An `error` is a rule the EBA states with MUST: the report would be rejected. A `warning` is a SHOULD. The command line can stop a pipeline on them with `--fail-on`, which exits with status 3: ```console $ dpm-xbrl inspect needs_fixes.zip --fail-on error Format: xbrl-csv Entry point: http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json Entity: SYNTHETICDPMXL000002.CON (https://eurofiling.info/eu/rs) Reference date: 2025-12-31 Base currency: iso4217:EUR Filing indicators: 4 reported of 5 Facts: 13 c_00.01 2 c_26.00 4 c_27.00 2 c_28.00 4 c_29.00 1 Warnings: 1 - reports/report.json: 1 extended metadata document could not be resolved offline (http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json); tables without resolved metadata use the EBA datapoint/factValue convention EBA filing rules broken: 3 must be fixed, 2 recommended - [error] 2.19 Facts must not be reported as #nil; leave out a fact that is not reported. - [warning] 3.11 Key values should not start or end with spaces or line breaks. (2 times) - [error] xBRL-CSV 1 Fact values must not use the decimals suffix (such as 78920030d-4); decimals belong in parameters.csv. - [error] xBRL-CSV 6 The package must not contain the table file of a template whose filing indicator is false. - [warning] File naming The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. $ echo $? 3 ``` ## Next steps - [Read a report](guides/read-a-report.md) from bytes, a directory or an XML instance. - [Check EBA filing rules](guides/check-filing-rules.md) and gate a CI pipeline. - [Read large reports as frames](guides/large-reports.md) when a report has millions of facts. - [Validate a report with dpm-xl](guides/validate-with-dpm-xl.md): evaluate the EBA validation rules on the facts. - Learn [the fact model](concepts/fact-model.md). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # How-to guides Recipes for specific tasks. Each assumes you have dpm-xbrl installed and know what a report is; the [getting started](../getting-started.md) tutorial covers the basics. Every example runs against the files in [`docs/samples`](../samples/report.zip). | Guide | Use it to | | --- | --- | | [Read a report](read-a-report.md) | Read paths, directories, bytes and XML instances; handle errors and warnings. | | [Read large reports as frames](large-reports.md) | Read packages with millions of facts into polars frames in seconds. | | [Check EBA filing rules](check-filing-rules.md) | List the filing rules a report breaks and stop a CI pipeline with `--fail-on`. | | [Add your own filing findings](add-filing-findings.md) | Record taxonomy-aware findings next to the ones the readers make. | | [Convert between formats](convert-formats.md) | Write xBRL-CSV packages and XBRL-XML instances. | | [Resolve metadata offline](taxonomy-packages.md) | Read concepts, dimensions and units from local taxonomy packages. | | [Handle untrusted uploads](untrusted-input.md) | Accept reports from users safely, with limits and one error type. | | [Validate a report with dpm-xl](validate-with-dpm-xl.md) | Evaluate DPM-XL validation rules on the facts of a report. | [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Read a report `read_report` reads both formats and returns a `Report`. This guide shows the inputs it accepts, how to label input that has no file name, and how to handle failures. ## From a path ```python from dpmxbrl import read_report package = read_report("report.zip") # xBRL-CSV report package instance = read_report("instance.xbrl") # XBRL-XML instance print(package.format, len(package.facts)) print(instance.format, len(instance.facts)) ``` ```text xbrl-csv 14 xbrl-xml 3 ``` A ZIP (by content, whatever its suffix), a directory and a `.json` path are read as xBRL-CSV; anything else as XBRL-XML. `detect_format` tells you which without reading the report: ```python from dpmxbrl import detect_format print(detect_format("report.zip"), detect_format("instance.xbrl")) ``` ```text xbrl-csv xbrl-xml ``` An unpacked package works too: pass its directory, or the path of its `report.json`. ```python from zipfile import ZipFile ZipFile("report.zip").extractall("unpacked") print(len(read_report("unpacked/report").facts)) print(len(read_report("unpacked/report/reports/report.json").facts)) ``` ```text 14 14 ``` ## From bytes Uploads and message queues hand over bytes. Pass them directly, with `name=` to label messages and locations the way the file would be: ```python from pathlib import Path data = Path("report.zip").read_bytes() report = read_report(data, name="SYNTHETICDPMXL000002.CON_upload.zip") print(report.filing[0].examples) ``` ```text ('SYNTHETICDPMXL000002.CON_upload.zip',) ``` The name matters for the EBA file-naming rule, which checks the file name. Without `name=`, ZIP bytes are labelled `` and XML bytes `instance.xbrl`. ## Use the facts Facts are frozen dataclasses. Their mappings (`dimensions`, `typed_dimensions`, `keys`) are read-only and may be shared between facts; copy one with `dict()` to change it. ```python fact = instance.facts[2] print(fact.concept, fact.value) print(fact.dimensions, fact.typed_dimensions) labels = dict(fact.dimensions) labels["note"] = "copied" ``` ```text eba_met:qNCO Northwind Holdings {'eba_dim:qNCO': 'eba_qCO:qx2000'} {'eba_dim:qINC': 'CP0001'} ``` Facts and reports pickle, copy and convert to plain data: ```python import dataclasses import json print(json.dumps(dataclasses.asdict(package.facts[4]))) ``` ```text {"value": "86911000", "concept": null, "datapoint": "dp32026", "template": "c_26.00", "dimensions": {}, "typed_dimensions": {}, "keys": {}, "unit": null, "decimals": null, "location": "reports/c_26.00.csv:4"} ``` To group facts by template, use `Report.facts_for_template` with the filing-indicator code. It compares codes case-insensitively; see [table codes and filing indicators](../concepts/filing-indicators.md) for how table files map to filing indicators. ## Handle failures Every input that cannot be read raises `ReportError`, a `ValueError`, naming the file and, where known, the line: ```python from dpmxbrl import ReportError for source in ("missing.zip", b" # Read large reports as frames `read_report` builds one Python object per fact, which takes about 5 µs and 0.7 KB per fact: a report with 7 million open-table facts reads in 30 to 50 seconds. The `frames` extra reads EBA-convention tables straight into [polars] frames of strings instead, the same report in about a second. ```text pip install "dpm-xbrl[frames]" ``` ## Read a package into frames `read_xbrl_csv_frames` returns a `ReportFrames`: the report header with no facts, plus one frame per table template. ```python from dpmxbrl.frames import read_xbrl_csv_frames frames = read_xbrl_csv_frames("report.zip") print(frames.report.reference_date, frames.report.facts, frames.fact_count) print(frames.tables["c_27.00"]) ``` ```text 2025-12-31 () 14 shape: (4, 4) ┌───────────┬────────────────────┬────────┬────────────────┐ │ datapoint ┆ factValue ┆ qINC ┆ qNCO │ │ --- ┆ --- ┆ --- ┆ --- │ │ cat ┆ str ┆ cat ┆ cat │ ╞═══════════╪════════════════════╪════════╪════════════════╡ │ dp5486584 ┆ Northwind Holdings ┆ CP0001 ┆ eba_qCO:qx2000 │ │ dp5485645 ┆ eba_NC:DE ┆ CP0001 ┆ eba_qCO:qx2000 │ │ dp5486584 ┆ Contoso Group ┆ CP0002 ┆ eba_qCO:qx2000 │ │ dp5485645 ┆ eba_NC:FR ┆ CP0002 ┆ eba_qCO:qx2000 │ └───────────┴────────────────────┴────────┴────────────────┘ ``` Each frame has a `datapoint` column, a `factValue` column of strings (null for a `#nil` value) and the table's key columns; `datapoint` and the key columns are categorical. Keys are the frame's template IDs, the file stems such as `c_27.00`. Cast values once you know their data type, then work in polars: ```python import polars as pl exposures = frames.tables["c_28.00"].with_columns(pl.col("factValue").cast(pl.Int64)) print(exposures.group_by("qINC").agg(pl.col("factValue").sum()).sort("qINC")) ``` ```text shape: (2, 2) ┌────────┬───────────┐ │ qINC ┆ factValue │ │ --- ┆ --- │ │ cat ┆ i64 │ ╞════════╪═══════════╡ │ CP0001 ┆ 127438000 │ │ CP0002 ┆ 80150000 │ └────────┴───────────┘ ``` ## Map frame rows back to file rows Rows with no fact (an empty value, `#empty` or `#none`) and exact duplicate rows are removed, so a frame row's index is not its place in the CSV file. `ReportFrames.dropped_rows` lists the removed data rows per template: ```python import io from zipfile import ZipFile # Add an empty value and a duplicate row to c_28.00. buffer = io.BytesIO() with ZipFile("report.zip") as source, ZipFile(buffer, "w") as target: for name in source.namelist(): data = source.read(name) if name.endswith("c_28.00.csv"): data += b"dp5488015,,CP0001\r\ndp5488013,67129000,CP0001\r\n" target.writestr(name, data) edited = read_xbrl_csv_frames(buffer.getvalue(), name="report.zip") print(edited.tables["c_28.00"].height, edited.dropped_rows) ``` ```text 4 {'c_28.00': (4, 5)} ``` Indices are 0-based data rows as read, before any row is removed. Data row `i` of a file is on line `i + 2` when no value spans lines, so frame row `n` is data row `n` plus the number of dropped indices at or below it. ## Process tables as they arrive `on_metadata` receives the report header before any table is read, and `on_table` receives each template's frame as soon as its last file is read, on the calling thread and in package order. Use them to start dependent work, such as loading rules for the module, while later tables are still being parsed: ```python def on_metadata(report): print("header:", report.entity_id, sorted(report.filing_indicators)) def on_table(template, frame, dropped): print("table:", template, frame.height, dropped) frames = read_xbrl_csv_frames("report.zip", on_metadata=on_metadata, on_table=on_table) ``` ```text header: SYNTHETICDPMXL000002.CON ['C_00.01', 'C_26.00', 'C_27.00', 'C_28.00', 'C_29.00'] table: c_00.01 2 () table: c_26.00 4 () table: c_27.00 4 () table: c_28.00 4 () ``` An exception raised by a callback aborts the read. ## What is checked, and what is left to you Columnar reading applies the same package, metadata, parameter, filing-indicator, header, special-value and duplicate checks as `read_report`. Checks that need a second pass over every string are left to the caller, which usually splits values by data type anyway: - the decimals suffix (xBRL-CSV rule 1), - spaces around key values (3.11), - special values in key columns (xBRL-CSV rule 5). `needs_fixes.zip` breaks the first two. `read_report` finds five broken rules in it; the frames reader finds three, without the decimals suffix and the key spaces: ```python broken = read_xbrl_csv_frames("needs_fixes.zip") for finding in broken.report.filing: print(finding.rule, finding.code) ``` ```text 2.19 nilUsed xBRL-CSV 6 tableForNegativeFilingIndicator File naming invalidFileName ``` Record the checks you make with the rule tuples in [`dpmxbrl.filing`](../reference/api/filing.md); see [add your own filing findings](add-filing-findings.md#checks-the-columnar-reader-leaves-to-you). Table files are parsed by polars, which differs from the `csv` module: a row with fewer cells than the header is padded with empty cells instead of rejected, lines must end in LF or CRLF, and line numbers in findings count data rows rather than physical lines. ## Tables described by metadata Columnar reading supports the EBA `datapoint`/`factValue` convention only. A package whose tables are described by resolved xBRL-CSV metadata raises `MetadataTablesError`, a `ReportError`; fall back to `read_report`: ```python from dpmxbrl import read_report from dpmxbrl.frames import MetadataTablesError try: frames = read_xbrl_csv_frames("described.zip", taxonomy_packages=["taxonomy.zip"]) except MetadataTablesError: report = read_report("described.zip", taxonomy_packages=["taxonomy.zip"]) print("read", len(report.facts), "facts with read_report") ``` ```text read 6 facts with read_report ``` ## From the command line `dpm-xbrl inspect --columnar` counts facts with the frames reader and says so on standard error: ```console $ dpm-xbrl inspect report.zip --columnar note: --columnar checks special fact values only; key values and decimals suffixes are not checked Format: xbrl-csv Entry point: http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json Entity: SYNTHETICDPMXL000002.CON (https://eurofiling.info/eu/rs) Reference date: 2025-12-31 Base currency: iso4217:EUR Filing indicators: 4 reported of 5 Facts: 14 c_00.01 2 c_26.00 4 c_27.00 4 c_28.00 4 Warnings: 1 - reports/report.json: 1 extended metadata document could not be resolved offline (http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json); tables without resolved metadata use the EBA datapoint/factValue convention EBA filing rules broken: 0 must be fixed, 1 recommended - [warning] File naming The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. ``` On a 21.9-million-fact package (one 587 MB table) it takes 2 seconds and 2.3 GB instead of minutes; such a package also needs higher [limits](untrusted-input.md#raise-the-limits-for-large-trusted-reports). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Check EBA filing rules The [EBA Filing Rules][eba-filing-rules] constrain how a report file is written: one entity and reference date, filing indicators, decimals, units, naming, and constructs that must not appear. A report that breaks a MUST rule is rejected however good its values are. Both readers record the rules a report breaks, as far as the file alone can show, in `Report.filing`. This guide shows how to read the findings and how to stop a pipeline on them. What is and is not checked is listed in [filing rules checked](../reference/filing-rules.md). ## List the broken rules ```python from dpmxbrl import read_report from dpmxbrl.filing import RULE_TITLES report = read_report("needs_fixes.zip") for finding in report.filing: title = RULE_TITLES[finding.rule] print( f"[{finding.severity}] {finding.rule} {title} ({finding.code}), {finding.count}x" ) print(f" {finding.message}") print(f" at {', '.join(finding.examples)}") ``` ```text [error] 2.19 Nil and empty values (nilUsed), 1x Facts must not be reported as #nil; leave out a fact that is not reported. at reports/c_26.00.csv:3 [warning] 3.11 Leading or trailing spaces (leadingOrTrailingSpacesInText), 2x Key values should not start or end with spaces or line breaks. at reports/c_27.00.csv:2, reports/c_27.00.csv:3 [error] xBRL-CSV 1 Decimals suffix (decimalsSuffixUsed), 1x Fact values must not use the decimals suffix (such as 78920030d-4); decimals belong in parameters.csv. at reports/c_26.00.csv:2 [error] xBRL-CSV 6 Tables of templates not reported (tableForNegativeFilingIndicator), 1x The package must not contain the table file of a template whose filing indicator is false. at reports/c_29.00.csv [warning] File naming File naming (invalidFileName), 1x The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. at needs_fixes.zip ``` Each `FilingFinding` has: - `rule`: the rule's number in the EBA document (`2.19`), or `xBRL-CSV n` for the extra xBRL-CSV rules, or `Package` and `File naming` for the remittance requirements. Rule numbers are stable across releases. - `code`: the rule's tag in the EBA document, such as `nilUsed`. Also stable. - `severity`: `error` where the rule says MUST, `warning` where it says SHOULD. - `message`: what is wrong, as a sentence. Messages may be reworded in minor releases, so match on `rule` and `code`, never on the message. - `count` and up to five `examples` of locations. Findings come in rule order, and one finding is kept per rule, code and message. ## Decide whether to send a report ```python must_fix = [finding for finding in report.filing if finding.severity == "error"] print(f"{len(must_fix)} rules must be fixed before sending") ``` ```text 3 rules must be fixed before sending ``` File-naming findings are always warnings, because each supervisor confirms its own naming. ## Gate a CI pipeline with `--fail-on` `dpm-xbrl inspect --fail-on error` exits with status 3 when the report breaks a MUST rule; `--fail-on warning` also fails on SHOULD rules. Status 1 means the report could not be read at all, and 2 is a usage error. ```python import subprocess for path, level in [ ("report.zip", "error"), ("report.zip", "warning"), ("needs_fixes.zip", "error"), ]: command = ["dpm-xbrl", "inspect", path, "--fail-on", level] status = subprocess.run(command, capture_output=True, check=False).returncode print(f"{path} --fail-on {level}: exit status {status}") ``` ```text report.zip --fail-on error: exit status 0 report.zip --fail-on warning: exit status 3 needs_fixes.zip --fail-on error: exit status 3 ``` `report.zip` breaks only the file-naming recommendation, so it passes `--fail-on error` and fails `--fail-on warning`. A GitHub Actions step that checks every report in a folder: ```yaml - name: Check EBA filing rules run: | for report in reports/*.zip; do uvx dpm-xbrl inspect "$report" --fail-on error done ``` To keep the details for a dashboard or an artefact, add `--json`; the exit status is the same. The layout is in [JSON output](../reference/json-output.md). ```python import json command = ["dpm-xbrl", "inspect", "needs_fixes.zip", "--json", "--fail-on", "error"] result = subprocess.run(command, capture_output=True, text=True, check=False) summary = json.loads(result.stdout) print(result.returncode, [rule["code"] for rule in summary["filing_rules"]]) ``` ```text 3 ['nilUsed', 'leadingOrTrailingSpacesInText', 'decimalsSuffixUsed', 'tableForNegativeFilingIndicator', 'invalidFileName'] ``` ## Check a file name before sending The file-naming check compares the name with the report's own entity, module and reference date. Read bytes with the final name to check it: ```python from pathlib import Path name = ( "SYNTHETICDPMXL000002.CON_FR_COREP030300_COREPLE_2025-12-31_20260115093000000.zip" ) renamed = read_report(Path("report.zip").read_bytes(), name=name) print([finding.code for finding in renamed.filing]) ``` ```text ['invalidRootFolder'] ``` The name is right; what remains is that the package's root folder must be named as the file, without `.zip`. Rename the folder inside the ZIP, or write the package again with `write_xbrl_csv(report, target)`, which names the folder as the file. ## Rules that need the taxonomy Some rules need the taxonomy: which entry points the EBA publishes and the reference dates of each module (1.5), which templates a module has (1.6, 1.6.3), and a data point's data type (2.18, 3.2 (b)). Pass the [taxonomy packages](taxonomy-packages.md) and the readers check these too, and only then: ```python import io from zipfile import ZipFile buffer = io.BytesIO() with ZipFile("described.zip") as source, ZipFile(buffer, "w") as target: for name in source.namelist(): data = source.read(name) if name.endswith("parameters.csv"): data = data.replace(b"decimalsMonetary,-3", b"decimalsMonetary,-6") if name.endswith("FilingIndicators.csv"): data += b"C_99.00,false\r\n" target.writestr(name, data) data = buffer.getvalue() for taxonomy_packages in ((), ["taxonomy.zip"]): report = read_report( data, name="described.zip", taxonomy_packages=taxonomy_packages ) print([(finding.rule, finding.code) for finding in report.filing]) ``` ```text [('File naming', 'invalidFileName')] [('1.6.3', 'invalidFilingIndicatorValue'), ('2.18', 'accuracyRequirements'), ('File naming', 'invalidFileName')] ``` The [rules each mode checks](../reference/filing-rules.md#what-each-mode-checks) lists them, and `checked_rules` gives them as `(rule, code)` pairs. Facts outside the reported templates (1.7.1) and spaces around string values (3.11) are left to you; [add your own filing findings](add-filing-findings.md) shows how to record them in the same form. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Add your own filing findings The readers check the filing rules that the report file alone can show, and, given taxonomy packages, those that need the module's templates and data types. Other rules, and checks from your own sources, are left to you. This guide shows how to record your findings in the same form, so that one list holds them all; check `checked_rules` so as not to repeat one the reader made. ## Record findings with `FilingLog` `FilingLog` collects findings, counts repeats and keeps up to five locations per finding. A finding is identified by a `Rule` tuple: the rule number, the EBA tag, the severity and the message. Rule 1.6 requires a filing indicator for every template of the module. The readers check it with taxonomy packages; without them, or with a list of templates from elsewhere, such as the DPM database, the check is yours: ```python import dataclasses from dpmxbrl import read_report from dpmxbrl.filing import FilingLog, Rule # The module's templates, from your copy of the taxonomy. MODULE_TEMPLATES = {"C_00.01", "C_26.00", "C_27.00", "C_28.00", "C_29.00", "C_30.00"} MISSING_INDICATOR: Rule = ( "1.6", "missingFilingIndicator", "error", "Each template of the module must have a filing indicator.", ) report = read_report("report.zip") log = FilingLog() log.extend(report.filing) # keep what the reader found for template in sorted(MODULE_TEMPLATES - set(report.filing_indicators)): log.add(*MISSING_INDICATOR, template) report = dataclasses.replace(report, filing=log.result()) for finding in report.filing: print(finding.severity, finding.rule, finding.code, finding.examples) ``` ```text error 1.6 missingFilingIndicator ('C_30.00',) warning File naming invalidFileName ('report.zip',) ``` `log.result()` returns the findings sorted in rule order, the same order the readers use. `sort_findings` sorts any other iterable of findings the same way. Use the rule number from `RULE_TITLES` and the tag from the [EBA Filing Rules][eba-filing-rules]; the tag above is illustrative. Severity is `error` for a MUST rule and `warning` for a SHOULD rule. `FilingLog.add` takes an optional location and a `count`, for when you count occurrences without keeping each location: ```python log = FilingLog() log.add(*MISSING_INDICATOR, "C_30.00") log.add(*MISSING_INDICATOR, "C_30.00") # the same location is kept once log.add(*MISSING_INDICATOR, count=3) (finding,) = log.result() print(finding.count, finding.examples) ``` ```text 5 ('C_30.00',) ``` ## Spaces around string values Rule 3.11 forbids leading and trailing spaces in string values, but only for string data points, so the readers check key values and leave fact values to you. `SPACES` is the rule tuple: ```python from dpmxbrl.filing import SPACES STRING_DATAPOINTS = {"dp5486584"} # from the taxonomy's data types log = FilingLog() for fact in read_report("report.zip").facts: if fact.datapoint in STRING_DATAPOINTS and fact.value != (fact.value or "").strip(): log.add(*SPACES, fact.location) print(log.result()) ``` ```text () ``` ## Checks the columnar reader leaves to you `read_xbrl_csv_frames` checks special fact values only. The decimals suffix, spaces around key values and special key values cost a second pass over millions of strings, so it leaves them to callers, which can make them in polars and record them with the same messages as `read_report`: `DECIMALS_SUFFIX`, `KEY_SPACES` and `SPECIAL_KEY_VALUE`. ```python import polars as pl from dpmxbrl.filing import DECIMALS_SUFFIX, KEY_SPACES, SPECIAL_KEY_VALUE from dpmxbrl.frames import read_xbrl_csv_frames SUFFIX = r"^[-+]?[0-9.]+(?:[eE][-+]?[0-9]+)?d(?:[-+]?[0-9]+|INF)$" def location(template, row, dropped): """Return the file line of a frame row, skipping the removed rows.""" for removed in dropped: if removed <= row: row += 1 return f"reports/{template}.csv:{row + 2}" frames = read_xbrl_csv_frames("needs_fixes.zip") log = FilingLog() log.extend(frames.report.filing) for template, frame in frames.tables.items(): dropped = frames.dropped_rows.get(template, ()) rows = frame.with_row_index("row") for row in rows.filter(pl.col("factValue").str.contains(SUFFIX))["row"]: log.add(*DECIMALS_SUFFIX, location(template, row, dropped)) for key in frame.columns[2:]: text = pl.col(key).cast(pl.String) for row in rows.filter(text != text.str.strip_chars())["row"]: log.add(*KEY_SPACES, location(template, row, dropped)) for row in rows.filter(text.str.starts_with("#"))["row"]: log.add(*SPECIAL_KEY_VALUE, location(template, row, dropped)) for finding in log.result(): print(finding.rule, finding.code, finding.examples) ``` ```text 2.19 nilUsed ('reports/c_26.00.csv:3',) 3.11 leadingOrTrailingSpacesInText ('reports/c_27.00.csv:2', 'reports/c_27.00.csv:3') xBRL-CSV 1 decimalsSuffixUsed ('reports/c_26.00.csv:2',) xBRL-CSV 6 tableForNegativeFilingIndicator ('reports/c_29.00.csv',) File naming invalidFileName ('needs_fixes.zip',) ``` The result matches what `read_report` finds in the same package. The line arithmetic assumes one file per template and no values spanning lines. ## Reuse the readers' checks The checks the readers run on the report header are public, so you can run them on a report you built yourself: `check_subject` (rules 2.8 and 3.6), `check_file_name` and `check_entry_point` (1.5, 2.2, 2.3). ```python from dpmxbrl.filing import check_entry_point, check_file_name, check_subject report = read_report("report.zip") log = FilingLog() check_subject(dataclasses.replace(report, entity_id="syntheticdpmxl000002.CON"), log) check_file_name( "SYNTHETICDPMXL000002.CON_FR_COREP030300_COREPLE_2025-12-31.zip", report, log ) check_entry_point(["corep_le.json"], log, where="report.json", xml=False) for finding in log.result(): print(finding.rule, finding.code, "|", finding.message) ``` ```text 2.2 inappropriateTaxonomyRef | The entry point corep_le.json must be the full published URL. 3.6 reportingSubjectNotUppercase | The reporting subject syntheticdpmxl000002.CON must be written in upper case. File naming invalidFileName | The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. ``` [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Convert between formats `write_xbrl_csv` writes an xBRL-CSV report package and `write_xbrl_xml` an XBRL 2.1 XML instance. Both take a `Report`, so a conversion is a read followed by a write. This guide shows what each conversion needs. ## What can be converted The two formats identify facts differently: xBRL-CSV in the EBA convention by template, datapoint and key columns; XBRL-XML by concept, dimensions and unit. The taxonomy packages of the module connect the two: | From | To xBRL-CSV | To XBRL-XML | | --- | --- | --- | | xBRL-CSV, EBA convention (no metadata) | Yes | Read it with [taxonomy packages](taxonomy-packages.md) | | xBRL-CSV with resolved metadata | Yes | Yes | | XBRL-XML | With taxonomy packages | Yes | Without them, facts that lack a needed field raise `ReportError` before anything is written: ```python from dpmxbrl import ReportError, read_report, write_xbrl_csv, write_xbrl_xml try: write_xbrl_xml(read_report("report.zip"), "report.xbrl") except ReportError as error: print(error) try: write_xbrl_csv(read_report("instance.xbrl"), "instance.zip") except ReportError as error: print(error) ``` ```text reports/c_00.01.csv:2: fact has no concept and cannot be written as XBRL-XML instance.xbrl#f1: fact has no template and cannot be written as xBRL-CSV; taxonomy packages place facts by concept ``` ## xBRL-CSV to XBRL-XML Read the package with its taxonomy packages, so that each fact gets its concept, dimensions and unit, then write XML with the same packages: ```python report = read_report("described.zip", taxonomy_packages=["taxonomy.zip"]) write_xbrl_xml(report, "described.xbrl", taxonomy_packages=["taxonomy.zip"]) instance = read_report("described.xbrl") print(len(instance.facts), instance.entry_point) print(instance.facts[3].concept, dict(instance.facts[3].typed_dimensions)) print([(finding.rule, finding.count) for finding in instance.filing]) ``` ```text 6 http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/mod/corep_of.xsd eba_met:qNCO {'eba_dim:qINC': 'cp1'} [('File naming', 1)] ``` The packages give what the xBRL-CSV report does not hold: - **The `schemaRef`.** An xBRL-CSV report extends the module's `.json` entry point; XML references its `.xsd`, the one the packages' `taxonomyPackage.xml` lists for it. Without packages, or when they do not list it, the EBA's naming gives it (`corep_of.json` beside `corep_of.xsd`) and the writer warns with `EntryPointWarning`. - **Typed domains.** The reader keeps typed dimension values but not the element that wraps them in XML; the dimension's `xbrldt:typedDomainRef` names it, declared with the prefix its schema uses. Without packages, pass `typed_domains`, such as `{"eba_dim:qINC": "{http://www.eba.europa.eu/xbrl/crr/dict/typ}CC"}`, with a prefix the report declares or in Clark notation. Contexts are written once per combination of dimensions (`c1`, `c2`, ...), each with the report entity and an instant at the reference date, and all dimensions in the scenario; units are `u1`, `u2`, .... A fact with a unit is numeric and gets `decimals` (`INF` when unset) unless it is nil; a fact with decimals but no unit, as xBRL-CSV metadata gives pure facts, gets `xbrli:pure`. A fact that the package repeats in several tables is written once, and only the prefixes the instance uses are declared (rule 3.4). The same conversion from the command line: ```console $ dpm-xbrl convert described.zip described.xbrl --taxonomy-package taxonomy.zip ``` ## XBRL-XML to xBRL-CSV Pass the taxonomy packages to `write_xbrl_csv`. The module's JSON entry point, the `schemaRef` with `.json` for `.xsd`, and the table documents it extends give the layout: each fact goes in every table row whose data point has its concept and dimensions, as the EBA's own packages do when a cell appears in several templates. ```python write_xbrl_csv(instance, "described-again.zip", taxonomy_packages=["taxonomy.zip"]) again = read_report("described-again.zip") print(again.entry_point) print(sorted(again.parameters.items())) for fact in again.facts_for_template("c_27.00"): print(fact.datapoint, dict(fact.keys), fact.value) ``` ```text http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/mod/corep_of.json [('baseCurrency', 'iso4217:EUR'), ('decimalsMonetary', '-3'), ('entityID', 'rs:SYNTHETICDPMXL000002.CON'), ('refPeriod', '2024-12-31')] dp100 {'c0010': 'cp1', 'c0020': 'eba_qCO:qx2000'} ABC dp101 {'c0010': 'cp1', 'c0020': 'eba_qCO:qx2000'} 12.5 dp100 {'c0010': 'cp2', 'c0020': 'eba_qCO:qx2001'} DEF ``` - Tables, columns and prefixes are the module's: key columns come in the EBA's header-code order, take the members, typed values and units that the data point leaves open, and enumeration values take the module's prefixes. `report.json` declares no namespaces unless a fact needs one the module lacks. - `baseCurrency` is the report's base currency, and each `decimals*` parameter the lowest decimals of its facts, so that no value claims more accuracy than its source gave it. The facts that had more decimals are named in a `DecimalsWarning`. - Read back with the packages, the package gives the facts of the instance again; the tests check this on the EBA's official samples. A fact that no data point holds, or whose unit differs from `baseCurrency` or from its data point's, cannot be written exactly. By default it raises `ReportError`, whose `problems` list every such fact; with `strict=False` it is left out and returned in `SkippedFacts`, as a `SkippedFact` with the reason and a stable code: `undeclaredPrefix`, `noDataPoint`, `unitMismatch` or `currencyMismatch`. ```python from dataclasses import replace from dpmxbrl import Fact stray = Fact("5", concept="eba_met:mi53", unit="iso4217:EUR", location="x#f9") odd = replace(instance, facts=(*instance.facts, stray)) try: write_xbrl_csv(odd, "odd.zip", taxonomy_packages=["taxonomy.zip"]) except ReportError as error: print(error.problems) skipped = write_xbrl_csv( odd, "odd.zip", taxonomy_packages=["taxonomy.zip"], strict=False ) for item in skipped: print(item.fact.location, item.code, item.reason) write_xbrl_xml(odd, "odd.xbrl", taxonomy_packages=["taxonomy.zip"]) ``` ```text ('x#f9: no data point of corep_of.json has concept eba_met:mi53 with these dimensions',) x#f9 noDataPoint no data point of corep_of.json has concept eba_met:mi53 with these dimensions ``` From the command line, `--skip-unmapped` leaves such facts out and lists them on standard error: ```console $ dpm-xbrl convert odd.xbrl odd.zip --taxonomy-package taxonomy.zip error: 1 fact cannot be written as xBRL-CSV: odd.xbrl#fact7: no data point of corep_of.json has concept eba_met:mi53 with these dimensions $ echo $? 1 $ dpm-xbrl convert odd.xbrl odd.zip --taxonomy-package taxonomy.zip --skip-unmapped warning: 1 fact left out: odd.xbrl#fact7: no data point of corep_of.json has concept eba_met:mi53 with these dimensions (noDataPoint) ``` For EBA reports, pass the release ZIP the EBA publishes, or its dictionary and reporting-frameworks packages. Converting the 21,314-fact `EBA-COREP_OF.xbrl` sample takes about 3 seconds; see the [benchmarks](../project/benchmarks.md). ## XBRL-XML to XBRL-XML, xBRL-CSV to xBRL-CSV Writing the same format again normalizes a report: contexts and units are deduplicated and renumbered, or a package is rewritten in the EBA layout. Reading a written report reproduces its facts: ```python original = read_report("report.zip") write_xbrl_csv(original, "rewritten.zip") print(read_report("rewritten.zip").facts == original.facts) ``` ```text True ``` The package is deterministic: templates are sorted, facts keep their order and ZIP timestamps are fixed, so the same report always gives the same bytes. The top-level folder is the ZIP's file name without `.zip`, as EBA file naming asks, or, when writing to a stream, `default_package_root(report)`, `_`: ```python from dpmxbrl import default_package_root print(default_package_root(original)) ``` ```text SYNTHETICDPMXL000002.CON_2025-12-31 ``` Pass `root=` to name it otherwise; see [check EBA filing rules](check-filing-rules.md#check-a-file-name-before-sending) for the file name itself. From the command line, the output suffix chooses the format: `.zip` writes xBRL-CSV, `.xbrl` or `.xml` writes XBRL-XML. Reader warnings go to standard error. ```console $ dpm-xbrl convert report.zip copy.zip warning: reports/report.json: 1 extended metadata document could not be resolved offline (http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.json); tables without resolved metadata use the EBA datapoint/factValue convention ``` ## Write to a stream Both writers accept a writable binary stream as well as a path: ```python import io buffer = io.BytesIO() write_xbrl_csv(original, buffer) print(read_report(buffer.getvalue()).entity_id) ``` ```text SYNTHETICDPMXL000002.CON ``` ## What is not kept See [known gaps](../reference/known-gaps.md) for the details. In short: empty-string facts do not survive an xBRL-CSV round trip, `Fact.decimals`, concepts and dimensions are not written to EBA-convention CSV, and XML tuples, footnotes, `precision` and segment placement are not kept. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Resolve metadata offline An xBRL-CSV report extends a taxonomy entry point, such as `http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/mod/corep_of.json`, whose table templates describe what each column and datapoint means. When dpm-xbrl can read that metadata, each fact gets its concept, dimensions, unit and decimals. dpm-xbrl never fetches anything over the network, so you hand it the metadata: as taxonomy package ZIPs, or through a resolver function. Without metadata, tables are read in the EBA `datapoint`/`factValue` convention, which is enough to work with datapoint codes; see [EBA convention and resolved metadata](../concepts/metadata.md) for the difference. ## With taxonomy packages Pass the taxonomy package ZIPs the EBA publishes for the framework release. dpm-xbrl reads each package's `META-INF/catalog.xml` and maps the entry point URL to the file inside the package: ```python from dpmxbrl import read_report plain = read_report("described.zip") resolved = read_report("described.zip", taxonomy_packages=["taxonomy.zip"]) print(plain.facts[0]) print(resolved.facts[0]) ``` ```text Fact(value='1000', concept=None, datapoint='dp84817', template='c_01.00', dimensions={}, typed_dimensions={}, keys={}, unit=None, decimals=None, location='reports/c_01.00.csv:2') Fact(value='1000', concept='eba_met:mi53', datapoint='dp84817', template='c_01.00', dimensions={'eba_dim:BAS': 'eba_BA:x6'}, typed_dimensions={}, keys={}, unit='iso4217:EUR', decimals=-3, location='reports/c_01.00.csv:2') ``` Facts of open tables also get their typed dimensions from the key columns, while `Fact.keys` keeps the columns as written: ```python fact = resolved.facts[4] print(fact.concept, dict(fact.dimensions), dict(fact.typed_dimensions)) print(dict(fact.keys), fact.unit, fact.decimals) ``` ```text eba_met:mi1 {'eba_dim:qCO': 'eba_qCO:qx2000'} {'eba_dim:qINC': 'cp1'} {'c0010': 'cp1', 'c0020': 'eba_qCO:qx2000'} iso4217:EUR -3 ``` The sample [`taxonomy.zip`](../samples/taxonomy.zip) is a minimal package for the examples. For real reports, pass every ZIP of the EBA taxonomy release (for 4.2, the dictionary and reporting-frameworks packages); the order does not matter. The same works from the command line with `--taxonomy-package`, once per ZIP: ```console $ dpm-xbrl inspect described.zip --taxonomy-package taxonomy.zip Format: xbrl-csv Entry point: http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/mod/corep_of.json Entity: SYNTHETICDPMXL000002.CON (https://eurofiling.info/eu/rs) Reference date: 2024-12-31 Base currency: iso4217:EUR Filing indicators: 2 reported of 2 Facts: 6 c_01.00 3 c_27.00 3 Warnings: 0 EBA filing rules broken: 0 must be fixed, 1 recommended - [warning] File naming The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. ``` Without the package, the same command prints the warning that the entry point could not be resolved offline. ### The release ZIP as the EBA publishes it The EBA publishes a taxonomy release as one ZIP holding the packages, such as `taxo_package_4.2_hotfix.zip`. Pass that file as it is: when a ZIP has no `META-INF/catalog.xml`, the ZIPs at its top level are read as the packages, within `Limits.max_file_size` each and `Limits.max_total_size` together. Other files in it, such as release notes, are ignored. ```python from zipfile import ZipFile with ZipFile("release.zip", "w") as release: release.write("taxonomy.zip", "EBA_XBRL_4.0_Reporting_Frameworks.zip") release.writestr("Release Notes.pdf", b"%PDF-1.7") report = read_report("described.zip", taxonomy_packages=["release.zip"]) print(report.facts[0].concept) ``` ```text eba_met:mi53 ``` ## What else the packages give Besides the metadata, dpm-xbrl takes from the packages: - the published entry points (`taxonomyPackage.xml`) and each module's reference dates and templates, for the filing rules that need the taxonomy: 1.5, 1.6, 1.6.3, 2.18, 3.2 (b) and 3.5. Both readers check them only when given taxonomy packages; see [which rules each mode checks](../reference/filing-rules.md#what-each-mode-checks); - the tables of the module, to [convert XBRL-XML to xBRL-CSV](convert-formats.md#xbrl-xml-to-xbrl-csv); - typed domain elements and the module's XSD entry point, to [convert xBRL-CSV to XBRL-XML](convert-formats.md#xbrl-csv-to-xbrl-xml). Reading an XML instance with `taxonomy_packages` adds the checks only: XML needs no metadata to read. ## With a resolver function If the taxonomy files live elsewhere, in a database, a cache directory or an unpacked taxonomy, pass `metadata_resolver`: a function from an absolute URL to the document's bytes, or `None` when it does not know the URL. It is asked before any taxonomy package. ```python from zipfile import ZipFile # Index the taxonomy's files by URL, as a cache of your own would. with ZipFile("taxonomy.zip") as package: documents = { "http://" + name.split("/", 1)[1]: package.read(name) for name in package.namelist() if "/www.eba.europa.eu/" in name } def resolve(url): print("resolve", url) return documents.get(url) report = read_report("described.zip", metadata_resolver=resolve) print(report.facts[0].concept) ``` ```text resolve http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/mod/corep_of.json resolve http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/tab/c_01.00/c_01.00.json resolve http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.0/tab/c_27.00/c_27.00.json eba_met:mi53 ``` The module's `extends` are followed recursively, relative URLs against the document that names them. The chain is cycle-safe and limited to `Limits.max_extends_depth` levels. ## What is resolved Metadata documents merge `namespaces`, `tableTemplates`, `tables`, `parameters`, `dimensions`, `decimals` and `parameterURL`, with the extending document taking precedence. The supported subset of xBRL-CSV table templates is listed in [data formats](../reference/data-formats.md#resolved-metadata). A table whose template cannot be resolved falls back to the EBA convention; an unresolved `extends` produces one warning. Resolving metadata costs time: the EBA COREP own-funds sample reads several times slower with its metadata than without; see the [benchmarks](../project/benchmarks.md). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Handle untrusted uploads Reports often come from outside: an upload form, a supervisor's portal, an e-mail attachment. dpm-xbrl is built to read them safely. This guide shows the settings that matter; [the security model](../concepts/security.md) explains what is protected and why. ## Read bytes, catch `ReportError` Read the upload's bytes directly, with `name=` set to the uploaded file name, and catch `ReportError`. Every problem with the input raises it, so one handler covers malformed files, unsafe archives and inputs over a limit: ```python from dpmxbrl import Limits, ReportError, read_report def accept(data: bytes, filename: str) -> str: try: report = read_report(data, name=filename, limits=Limits(max_facts=1_000_000)) except ReportError as error: return f"rejected: {error}" errors = [finding for finding in report.filing if finding.severity == "error"] return f"accepted: {len(report.facts)} facts, {len(errors)} filing errors" with open("report.zip", "rb") as upload: print(accept(upload.read(), "report.zip")) print(accept(b"PK\x03\x04 truncated", "broken.zip")) print( accept( b']>&e;', "evil.xbrl", ) ) ``` ```text accepted: 14 facts, 0 filing errors rejected: broken.zip is not a readable ZIP archive: it may be incomplete or damaged rejected: evil.xbrl: DOCTYPE and ENTITY declarations are not allowed ``` Messages name the file and, where known, the line, and are safe to show to the person who uploaded it. They contain no stack traces and no paths other than the names inside the upload. ## Set the limits `Limits` caps the resources one read may use. The defaults suit reports from regular reporting; tighten them for a public upload form: | Field | Default | Caps | | --- | --- | --- | | `max_zip_entries` | 10,000 | entries in a report package ZIP | | `max_total_size` | 2 GiB | total uncompressed size of a package | | `max_file_size` | 512 MiB | uncompressed size of one file, including an XML instance and files read from taxonomy packages | | `max_facts` | 20,000,000 | facts in one report | | `max_extends_depth` | 32 | nesting of xBRL-CSV `extends` | | `max_taxonomy_entries` | 100,000 | entries in one taxonomy package ZIP (the EBA's reporting frameworks package has about 17,400) | Each limit raises `ReportError` when exceeded, before the data is inflated or the facts are built: ```python for limits in ( Limits(max_facts=10), Limits(max_zip_entries=5), Limits(max_file_size=100), Limits(max_total_size=500), ): try: read_report("report.zip", limits=limits) except ReportError as error: print(error) ``` ```text reports/c_28.00.csv:2: report exceeds the limit of 10 facts report.zip: 8 ZIP entries exceed the limit of 5 report.zip: entry 'report/reports/report.json' exceeds the maximum file size of 100 bytes report.zip: uncompressed size exceeds the limit of 500 bytes ``` `Limits` is a frozen dataclass; derive one from the defaults with `dataclasses.replace(Limits(), max_facts=1_000_000)`. ## Raise the limits for large trusted reports The largest EBA packages hold tens of millions of facts in files over 512 MiB. For reports you trust, raise the limits; from the command line, `--max-facts`, `--max-file-size`, `--max-total-size` and `--max-zip-entries` do the same, and a limit error names the option: ```console $ dpm-xbrl inspect report.zip --max-facts 10 error: reports/c_28.00.csv:2: report exceeds the limit of 10 facts hint: --max-facts raises the limit $ echo $? 1 ``` A 21.9-million-fact package with one 587 MB table needs `--max-file-size 1000000000 --max-total-size 4000000000 --max-facts 30000000`. ## What else is rejected Report packages are rejected for entry names that are absolute or contain `..`, duplicate entries and encrypted entries; package directories for links that leave the directory. XML instances are scanned before parsing, and any DOCTYPE, and with it any entity declaration, is rejected. Nothing is ever fetched over the network. ```python import io from zipfile import ZipFile buffer = io.BytesIO() with ZipFile(buffer, "w") as archive: archive.writestr("../evil/reports/report.json", "{}") try: read_report(buffer.getvalue(), name="upload.zip") except ReportError as error: print(error) ``` ```text upload.zip: unsafe parent-relative entry '../evil/reports/report.json' ``` ## Run reads in a separate process Limits bound one read, not the whole process. For a public service, run reads in a worker process with an operating-system memory limit and a timeout as well, so one hostile upload cannot slow other requests. Readers pause the cyclic garbage collector for the whole process while they build facts, which is another reason to keep them out of a latency-sensitive server process. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Validate a report with dpm-xl dpm-xbrl reads a report and checks how the file is written. Whether the *values* are right, for example that a total equals the sum of its parts, is decided by the EBA validation rules, written in DPM-XL. The sibling package [dpm-xl] parses and evaluates those rules. The two packages are independent: neither depends on the other. You connect them with a small `DataProvider` that answers dpm-xl's queries for table, row and column cells from the facts that dpm-xbrl reads. The mapping from a datapoint code to its table, row and column comes from the DPM release of the taxonomy. The dpm-xl documentation has the full, tested walk-through: [from an XBRL report to explained failures][dpm-xl-validate]. It reads a report with `read_report`, binds its facts to a provider through an explicit datapoint mapping, evaluates rules with dpm-xl's columnar engine, falls back to the reference engine where the columnar engine does not support a rule, and explains each failure. ## What dpm-xbrl gives the provider - `Report.facts`, each with `datapoint`, `template`, `keys` and `value` (see [the fact model](../concepts/fact-model.md)). For large reports, [read the tables as frames](large-reports.md) instead and hand the frames to dpm-xl's columnar engine. - `Report.filing_indicators`, keyed by template code, for dpm-xl's `table_reported`. Map a table file to its filing indicator as described in [table codes and filing indicators](../concepts/filing-indicators.md). - `Report.parameters` (such as `refPeriod` and `baseCurrency`) and `Report.decimals_parameters`, for dpm-xl's `parameter`. ```python from dpmxbrl import read_report report = read_report("report.zip") print(report.parameters["refPeriod"], report.filing_indicators["C_26.00"]) print([(fact.datapoint, fact.value) for fact in report.facts_for_template("C_26.00")]) ``` ```text 2025-12-31 True [('dp457468', '56058000'), ('dp32024', '92592000'), ('dp32026', '86911000'), ('dp32025', '0.71')] ``` Values are strings as written; convert numbers with the decimals parameter that applies to their data type before evaluating rules. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Concepts These pages explain how dpm-xbrl sees a report and why it behaves as it does. They are background reading; for tasks, see the [how-to guides](../guides/index.md), and for exact behaviour, the [reference](../reference/index.md). | Page | Explains | | --- | --- | | [The fact model](fact-model.md) | `Report` and `Fact`, what each field holds in each format, and memory and speed. | | [xBRL-CSV and XBRL-XML](formats.md) | The two serializations of the same report and how dpm-xbrl reads each. | | [EBA convention and resolved metadata](metadata.md) | The two ways an xBRL-CSV table is read, and when you need a taxonomy. | | [Table codes and filing indicators](filing-indicators.md) | How a table file such as `c_07.00.a.csv` maps to the filing indicator `C_07.00`. | | [Filing rules and coverage](filing-rules.md) | What the EBA filing rules are, which ones dpm-xbrl checks and why some are left to you. | | [Security model](security.md) | What dpm-xbrl protects against when it reads untrusted input, and its thread-safety guarantees. | [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # The fact model dpm-xbrl reads both serializations into the same two frozen, slotted dataclasses, defined in `dpmxbrl.model` and exported from `dpmxbrl`: `Report` for the report as a whole and `Fact` for each reported value. The model is neutral: it keeps what the file says, as written, and does not interpret values against a taxonomy. ## Report A `Report` holds the report header and its facts: | Field | xBRL-CSV | XBRL-XML | | --- | --- | --- | | `format` | `"xbrl-csv"` | `"xbrl-xml"` | | `entry_point` | first `documentInfo.extends` URL | `link:schemaRef` | | `entity_scheme`, `entity_id` | from `entityID` such as `rs:5299...CON`; the prefix is looked up in `namespaces` | the context identifier's scheme and value, the most common across facts | | `reference_date` | `refPeriod` | the instant, or a duration's end date, most common across facts | | `base_currency` | `baseCurrency` | the most common single-measure `iso4217` unit | | `parameters` | every report parameter, as strings | empty | | `decimals_parameters` | parameters named `decimals*`, as integers; `None` for `#none` and `INF` | empty | | `filing_indicators` | `FilingIndicators.csv`, code to reported | `find:filingIndicator`, `find:filed` defaulting to true | | `facts` | one per table row value, in file order | one per fact element, in document order | | `namespaces` | prefix to URI from the metadata | the instance's declarations | | `warnings` | how the file was read, deterministic order | same | | `filing` | [EBA filing rules broken](filing-rules.md), in rule order | same | ## Fact | Field | xBRL-CSV, EBA convention | xBRL-CSV, resolved metadata | XBRL-XML | | --- | --- | --- | --- | | `value` | the cell as written; `None` for `#nil` | same | the element text; `None` for `xsi:nil="true"` | | `concept` | `None` | concept qname | element qname | | `datapoint` | `datapoint` column, such as `dp84817` | property-group key | `None` | | `template` | file stem, such as `c_27.00` | template id | `None` | | `dimensions` | empty | explicit dimension to member | `xbrldi:explicitMember` | | `typed_dimensions` | empty | typed dimension to value | `xbrldi:typedMember` text, stripped | | `keys` | other columns, in header order | key columns | empty | | `unit` | `None` | unit | `iso4217:EUR`, `xbrli:pure`, `num/denom`, `a*b` | | `decimals` | `None`; use `Report.decimals_parameters` | from metadata | `decimals`; `None` for `INF` | | `location` | `reports/c_27.00.csv:3`, the physical line | same | `instance.xbrl#f12`, the fact id, or `#factN` for the Nth fact without one | Values are always the lexical text from the file. dpm-xbrl does not parse numbers or dates in fact values, because their data type lives in the taxonomy; parse them where you know the type. ```python from dpmxbrl import read_report instance = read_report("instance.xbrl") for fact in instance.facts: print(fact.concept, repr(fact.value), fact.unit, fact.decimals, fact.location) ``` ```text eba_met:mi53 '56058000' iso4217:EUR -3 instance.xbrl#f1 eba_met:pi1 '0.71' xbrli:pure 4 instance.xbrl#f2 eba_met:qNCO 'Northwind Holdings' None None instance.xbrl#f3 ``` ## Read-only mappings The mappings of a report that is read are read-only `dict`s: a fact's `dimensions`, `typed_dimensions` and `keys`, which facts may share, and the report's `parameters`, `decimals_parameters`, `filing_indicators` and `namespaces`. Assigning to one raises `TypeError`; copy it with `dict()` to change it. They pickle, copy and serialize as plain dicts. ```python fact = instance.facts[2] try: fact.dimensions["eba_dim:qNCO"] = "eba_qCO:qx2001" except TypeError as error: print(error) ``` ```text report and fact mappings are read-only; copy one with dict() ``` ## Qnames Qnames use prefixes declared in `Report.namespaces`. XML keeps the instance's own prefixes; a namespace declared again deeper in the document under another prefix is rendered with the first prefix bound to it. `local_name` returns the part after the prefix, for prefixed and Clark-notation names: ```python from dpmxbrl import local_name print( local_name("eba_met:mi53"), local_name("{http://www.eba.europa.eu/xbrl/crr/dict/met}mi53"), ) ``` ```text mi53 mi53 ``` ## Memory and speed `read_report` builds one `Fact` per value, about 5 µs and 0.7 KB per fact. A report with 7 million open-table facts reads in 30 to 50 seconds, depending on the machine. To keep that low, facts share empty mappings, datapoint codes and the dimensions of an XML context, and while the readers build facts they pause Python's cyclic garbage collector for the whole process (facts form no reference cycles) and restore it afterwards. Nested and concurrent reads re-enable it only when the last one ends, and only if it was enabled before. For millions of facts, read the tables as polars frames instead; see [read large reports as frames](../guides/large-reports.md). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # xBRL-CSV and XBRL-XML An EBA report can be written in two serializations of the same XBRL model: xBRL-CSV, the format of the EBA's current frameworks, and XBRL-XML, the older form, still found in earlier submissions and archives. ## xBRL-CSV report packages A report package is a ZIP with one top-level folder: ```text SYNTHETICDPMXL000002.CON_FR_COREP030300_COREPLE_2025-12-31_20260115093000000/ META-INF/reportPackage.json the package type reports/report.json documentInfo: extends the taxonomy entry point reports/parameters.csv entityID, refPeriod, baseCurrency, decimals* reports/FilingIndicators.csv templateID, reported reports/c_26.00.csv one CSV file per table reports/c_27.00.csv ``` Each table file has one row per fact (EBA's convention: `datapoint`, `factValue`, then key columns for open tables). The meaning of each datapoint, its concept and dimensions, lives in the taxonomy's metadata that `report.json` extends; see [EBA convention and resolved metadata](metadata.md). The format is defined by the [xBRL-CSV 1.0][xbrl-csv-spec] and [Report Packages 1.0][report-packages] specifications. dpm-xbrl streams table rows with the `csv` module (`utf-8-sig`), straight from the ZIP, so a package is never unpacked to disk. The exact package rules are in [data formats](../reference/data-formats.md#xbrl-csv-report-packages). ## XBRL-XML instances An instance is one XML document under the [XBRL 2.1][xbrl-21] specification: a `link:schemaRef` to the entry point, filing indicators, contexts (entity, period and dimensions), units, and one element per fact that refers to a context and a unit. ```xml 56058000 ``` dpm-xbrl scans the prolog, rejects any DOCTYPE, then streams the document with `iterparse`, clearing each top-level element once it is processed. The exact rules are in [data formats](../reference/data-formats.md#xbrl-xml-instances). ## The same fact in both A fact in an xBRL-CSV table is identified by its template, datapoint and key values; in XML, by its concept, dimensions and unit. The taxonomy maps one to the other. Without it, a CSV fact has no concept and an XML fact has no datapoint, which is why [conversion](../guides/convert-formats.md) between the formats needs resolved metadata. | | xBRL-CSV | XBRL-XML | | --- | --- | --- | | Identifies a fact by | template, datapoint, key columns | concept, dimensions, unit | | Decimals | report parameters per data type (`decimalsMonetary`) | `decimals` attribute on each fact | | Filing indicators | `FilingIndicators.csv` | `find:fIndicators` | | Size | compact; millions of facts per table | verbose; a context per dimension combination | | `Fact.location` | `reports/c_27.00.csv:3` | `instance.xbrl#f12` | | Reads without the taxonomy | yes, in the EBA convention | yes | [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # EBA convention and resolved metadata An xBRL-CSV table is a CSV file; what its columns mean is defined in JSON metadata, the *table templates*, that the report extends from the taxonomy. dpm-xbrl reads a table in one of two ways, depending on whether it can read that metadata. ## The EBA convention, without metadata EBA tables follow one layout: a `datapoint` column, a `factValue` column, and, for open tables, key columns such as `qINC` that identify the row. dpm-xbrl reads that layout without the taxonomy: - every row with a non-empty `factValue` is one fact; - `datapoint` becomes `Fact.datapoint`, every other column a key in `Fact.keys`; - `#nil` gives a nil fact, `#empty` and `#none` are skipped, `##` escapes a leading `#`, and other `#` values are kept as written; each case but `##` is recorded as a broken [filing rule](filing-rules.md); - `Fact.concept`, `dimensions`, `unit` and `decimals` stay empty; the decimals that apply are the report's `decimals*` parameters. This is what you get by default, and it is all that rule engines keyed by datapoint, such as [dpm-xl], need. Reading also warns that the extended metadata could not be resolved offline. Exact duplicate rows are reported once; conflicting duplicates (same template, datapoint and keys, different value) are all kept. Repeated findings are aggregated into one warning with a count and the first location. ## Resolved metadata With [taxonomy packages or a resolver](../guides/taxonomy-packages.md), dpm-xbrl follows `report.json` and its `extends` chain and reads each table through its template, as the xBRL-CSV specification defines. Each fact then gets its concept, explicit and typed dimensions, unit and decimals. `Fact.datapoint` is the property-group key (EBA's `dp...` codes) and `Fact.keys` keeps the key columns. You need resolved metadata to: - [convert](../guides/convert-formats.md) a report to XBRL-XML; - work with concepts and dimensions rather than datapoint codes; - read tables that do not follow the EBA layout. The supported subset of the specification is listed in [data formats](../reference/data-formats.md#resolved-metadata). ## Mixing the two The choice is per table. A table whose template cannot be resolved falls back to the EBA convention, so a package can have some tables of each. [Columnar reading](../guides/large-reports.md) supports the EBA convention only, and raises `MetadataTablesError` when a table needs metadata. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Table codes and filing indicators A report says which templates it reports through its *filing indicators*, codes such as `C_07.00` with a reported flag. Its xBRL-CSV tables are named by *table codes*, the file stems such as `c_07.00.a`. One template can be split over several tables, so the two codes differ. ## The mapping dpm-xbrl maps a table code to its filing indicator in two steps: 1. **Single-letter parts are dropped.** A part is the text between dots; a part of one letter (any case) is a table's letter within its template. 2. **Codes are compared case-insensitively.** | Table file | Template id | Filing indicator | | --- | --- | --- | | `c_01.00.csv` | `c_01.00` | `C_01.00` | | `c_07.00.a.csv`, `c_07.00.b.csv` | `c_07.00.a`, `c_07.00.b` | `C_07.00` | | `f_18.00.a.dp.csv` | `f_18.00.a.dp` | `F_18.00.dp` | | `c_43.00.c.csv` | `c_43.00.c` | `C_43.00` | The template id is the file stem, unless the metadata's `tables` maps the file URL to another template. dpm-xbrl uses this mapping for xBRL-CSV rule 6: a package must not contain the table file of a template whose filing indicator is false. ## Where the mapping applies, and where it does not `Fact.template` is always the template id, `c_07.00.a`, never the filing indicator. `Report.facts_for_template` compares case-insensitively and matches a table by its template id or by its filing indicator, so `C_07.00` selects the facts of every lettered table of the template and `c_07.00.a` those of one table. `filing_indicator_code` gives a table's filing indicator: ```python import io from zipfile import ZipFile from dpmxbrl import filing_indicator_code, read_report # report.zip plus a template split into two tables, and one filed as not reported. buffer = io.BytesIO() with ZipFile("report.zip") as source, ZipFile(buffer, "w") as target: for name in source.namelist(): data = source.read(name) if name.endswith("FilingIndicators.csv"): data += b"C_07.00,true\r\nF_18.00.dp,false\r\n" target.writestr(name, data) target.writestr( "report/reports/c_07.00.a.csv", "datapoint,factValue\r\ndp1001,500\r\n" ) target.writestr( "report/reports/c_07.00.b.csv", "datapoint,factValue\r\ndp1002,700\r\n" ) target.writestr( "report/reports/f_18.00.a.dp.csv", "datapoint,factValue\r\ndp1003,1\r\n" ) report = read_report(buffer.getvalue(), name="report.zip") print( len(report.facts_for_template("C_07.00")), len(report.facts_for_template("C_07.00.A")), ) ``` ```text 2 1 ``` To group every fact by filing indicator: ```python by_indicator = {} for fact in report.facts: code = filing_indicator_code(fact.template).casefold() by_indicator.setdefault(code, []).append(fact) reported = {code.casefold(): filed for code, filed in report.filing_indicators.items()} for code, facts in sorted(by_indicator.items()): print(code, len(facts), reported.get(code)) ``` ```text c_00.01 2 True c_07.00 2 True c_26.00 4 True c_27.00 4 True c_28.00 4 True f_18.00.dp 1 False ``` The table filed as not reported breaks rule 6: ```python print( [ (finding.rule, finding.examples) for finding in report.filing if finding.rule == "xBRL-CSV 6" ] ) ``` ```text [('xBRL-CSV 6', ('reports/f_18.00.a.dp.csv',))] ``` The frames reader keys `ReportFrames.tables` by template id too, so the same recipe, with `filing_indicator_code`, groups frames. ## XML reports XBRL-XML facts carry no template: the filing indicators say which templates are reported, but which template a fact belongs to is only known from the taxonomy. `facts_for_template` returns nothing for XML reports. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Filing rules and coverage The [EBA Filing Rules][eba-filing-rules] (version 5.8, February 2026) constrain how a report file is written, before anyone looks at its values: one taxonomy entry point, one entity and reference date, filing indicators, decimals, units, naming, and XML constructs that must not appear. They are separate from the EBA *validation rules*, written in DPM-XL, which check the values; for those, see [dpm-xl]. ## What dpm-xbrl checks Both readers record every rule that the file alone can show to be broken, in the same pass that reads the facts, so checking costs nothing extra. Each broken rule becomes one `FilingFinding` per rule, tag and message, with a count and up to five locations, in `Report.filing`. The full list is in [filing rules checked](../reference/filing-rules.md). Severity follows the rule text: `error` where the rule says MUST (the EBA rejects the report) and `warning` where it says SHOULD. File-naming findings are warnings, because each supervisor confirms its own naming structure with its reporters. ## With the taxonomy Some rules need the taxonomy or a data point's data type, which the file does not carry. Given [taxonomy packages](../guides/taxonomy-packages.md), the readers check these too, and only then: a published entry point valid for the reference date (1.5), a filing indicator for every template of the module and only valid codes (1.6, 1.6.3), minimum decimals per data type (2.18), percentages as decimal fractions (3.2 (b)) and, for XML, canonical namespace prefixes (3.5). [What each mode checks](../reference/filing-rules.md#what-each-mode-checks) has the details. ## What is left to you The readers leave some rules to callers, which can record them with the same classes; see [add your own filing findings](../guides/add-filing-findings.md): - facts outside the reported templates (1.7.1); - leading or trailing spaces in string fact values (3.11); - `baseCurrency` when monetary facts use it; - without taxonomy packages, the rules of the previous section. `checked_rules` gives what each mode checks, so that your own findings need not repeat them. [Columnar reading](../guides/large-reports.md) checks special fact values only, and leaves the decimals suffix, key spaces, special key values and empty key values to the caller as well. ## What is not checked Units against the XBRL unit registry (2.23), generating-software information (2.26) and comments (2.5). ## Warnings are not filing findings `Report.warnings` describes how the file was read: an unresolved metadata document, a draft document type, aggregated duplicates. `Report.filing` lists the rules the report breaks. Pipelines gate on `filing`; warnings explain surprises. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Security model dpm-xbrl assumes that a report file may be hostile: crafted to exhaust memory, escape a directory, or make the parser read local files. It protects the process reading it as follows. To use these protections, see [handle untrusted uploads](../guides/untrusted-input.md). ## One error type Every input that cannot be read safely raises `ReportError`, a `ValueError`. Other exceptions from reading are bugs. Messages name the file and, where known, the line or entry, and never include data from outside the input. ## Resource limits `Limits` bounds a read: entries in a ZIP, total and per-file uncompressed size, facts, and the depth of xBRL-CSV `extends`. Sizes are checked against the ZIP's directory before anything is inflated, and inflation never produces more than an entry declares, so an entry that understates its size is rejected rather than inflated without bound. The file-size limit also applies to XML instances and to files read from taxonomy packages, whose reads also count towards the total-size limit; a taxonomy package ZIP may hold up to `max_taxonomy_entries` entries. ## ZIP packages and directories - Entry names that are absolute or contain `..` are rejected, as are duplicate and encrypted entries and unsupported compression methods. - Entries are read straight from the ZIP; nothing is extracted to disk. - A package directory is rejected if a link leaves it, and each file is checked again when it is opened. - An entry with damaged compressed data raises `ReportError` naming it, as does any archive or entry `zipfile` cannot read, in report and taxonomy packages alike. ## XML The prolog is scanned before parsing, including for UTF-16 without a byte order mark, and any DOCTYPE, and with it any ENTITY declaration, is rejected. External entities are therefore never resolved, and entity expansion attacks cannot start. Parsing streams with `iterparse`. XML from taxonomy packages (catalogs, `taxonomyPackage.xml` and schemas) gets the same prolog scan, with one difference: a DOCTYPE that only names an external DTD, as the EBA's catalogs name the OASIS catalog DTD, is allowed, because the DTD is never loaded. A DOCTYPE with an internal subset, where ENTITY declarations live, any other declaration and an unknown encoding raise `ReportError`. ## JSON Duplicate keys are rejected rather than silently resolved, and documents nested too deeply raise `ReportError` instead of `RecursionError`. ## No network Nothing is fetched over the network. Metadata URLs resolve only through a `metadata_resolver` you supply and the catalogs of local taxonomy packages. ## Thread safety - Readers and writers keep no shared state: each call reads its own input and returns new objects, so threads may call them concurrently. - `Report`, `Fact`, `FilingFinding` and `Limits` are frozen, and the mappings of read facts are read-only, so results can be shared between threads without copying. - While a reader builds facts it pauses the cyclic garbage collector for the whole process. Concurrent reads share the pause, and the collector is re-enabled when the last one ends, if it was enabled before the first. Other threads run without cyclic collection meanwhile. - `FilingLog` is not synchronized; use one per thread. - The frames reader calls `on_metadata` and `on_table` on the calling thread. ## Reporting a vulnerability See the [security policy](../project/security.md). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Reference Exact, complete descriptions. For explanations, see the [concepts](../concepts/index.md); for recipes, the [how-to guides](../guides/index.md). | Page | Describes | | --- | --- | | [CLI](cli.md) | Every `dpm-xbrl` command and option, and the exit statuses. | | [JSON output](json-output.md) | The layout of `dpm-xbrl inspect --json`, with `schema_version`. | | [Data formats](data-formats.md) | What is read from xBRL-CSV packages and XBRL-XML instances, and what is written. | | [Filing rules checked](filing-rules.md) | Every EBA filing rule the readers check, by format. | | [Known gaps](known-gaps.md) | What dpm-xbrl does not do. | | [Python API](api/index.md) | Every public name, generated from the docstrings. | [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # CLI Installing dpm-xbrl installs the `dpm-xbrl` command. It has two subcommands: `inspect` summarizes a report and the filing rules it breaks, `convert` writes a report in the other format. `python -m dpmxbrl.cli` is not provided; use `dpm-xbrl`, or `uvx dpm-xbrl` without installing. ```console $ dpm-xbrl --help usage: dpm-xbrl [-h] [--version] {inspect,convert} ... Inspect and convert EBA xBRL-CSV and XBRL-XML reports. positional arguments: {inspect,convert} inspect summarize a report and the EBA filing rules it breaks convert convert a report (.zip -> xBRL-CSV, .xbrl/.xml -> XML) options: -h, --help show this help message and exit --version show program's version number and exit examples: dpm-xbrl inspect report.zip dpm-xbrl inspect instance.xbrl --json dpm-xbrl inspect report.zip --fail-on error dpm-xbrl convert report.zip instance.xbrl --taxonomy-package taxonomy.zip dpm-xbrl convert instance.xbrl report.zip --taxonomy-package taxonomy.zip ``` ## Exit statuses | Status | Meaning | | --- | --- | | 0 | Success. | | 1 | The report cannot be read or written: `error: ...` on standard error, with a `hint:` naming the option when a limit is hit. | | 2 | Usage error (unknown option, missing argument). | | 3 | `inspect --fail-on` found a broken filing rule of that severity. | ## `dpm-xbrl inspect` ```console $ dpm-xbrl inspect --help usage: dpm-xbrl inspect [-h] [--json] [--columnar] [--fail-on {error,warning}] [--max-file-size N] [--max-total-size N] [--max-zip-entries N] [--max-facts N] [--taxonomy-package ZIP] path positional arguments: path report package (.zip), directory or XML instance options: -h, --help show this help message and exit --json print JSON --columnar count xBRL-CSV facts with polars: seconds instead of minutes for millions of facts, checking special fact values only (frames extra) --fail-on {error,warning} exit with status 3 when the report breaks a filing rule of this severity or worse --taxonomy-package ZIP local taxonomy package: resolves xBRL-CSV metadata, adds the checks that need the taxonomy, and places XML facts in tables or gives typed domains when converting limits: resource limits for untrusted input (defaults in the documentation) --max-file-size N largest uncompressed file, in bytes --max-total-size N largest uncompressed package, in bytes --max-zip-entries N most entries in a package --max-facts N most facts in a report ``` `inspect` reads the report with `read_report` (format detected as in `detect_format`) and prints the format, entry point, entity, reference date, base currency, filing indicators (reported of total), the fact count, the count per template (xBRL-CSV only), the reader warnings and the EBA filing rules broken, errors counted as "must be fixed" and warnings as "recommended": ```console $ dpm-xbrl inspect instance.xbrl Format: xbrl-xml Entry point: http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.xsd Entity: SYNTHETICDPMXL000002.CON (https://eurofiling.info/eu/rs) Reference date: 2025-12-31 Base currency: iso4217:EUR Filing indicators: 2 reported of 3 Facts: 3 Warnings: 0 EBA filing rules broken: 0 must be fixed, 1 recommended - [warning] File naming The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA. ``` A finding that occurs more than once ends in `(N times)`. | Option | Effect | | --- | --- | | `--json` | Print the summary as JSON; see [JSON output](json-output.md). | | `--fail-on error` | Exit with status 3 if any `error` finding is present. | | `--fail-on warning` | Exit with status 3 if any finding is present. | | `--columnar` | Count xBRL-CSV facts with the `frames` extra (polars) instead of building every fact. Prints a note on standard error; checks special fact values only. Falls back to a full read, with a note, for tables described by metadata; ignored with a note for XML. Fails with status 1 without the extra. | | `--taxonomy-package ZIP` | Resolve xBRL-CSV metadata from a local taxonomy package, and check the [filing rules that need the taxonomy](filing-rules.md#what-each-mode-checks). Repeat for several; a ZIP of packages, as the EBA publishes a release, also works. | | `--max-file-size N` | Raise or lower `Limits.max_file_size` (default 536870912). | | `--max-total-size N` | `Limits.max_total_size` (default 2147483648). | | `--max-zip-entries N` | `Limits.max_zip_entries` (default 10000). | | `--max-facts N` | `Limits.max_facts` (default 20000000). | Limit values are positive whole numbers; `_` separators are accepted, as in `--max-facts 30_000_000`. ## `dpm-xbrl convert` ```console $ dpm-xbrl convert --help usage: dpm-xbrl convert [-h] [--typed-domain DIMENSION=ELEMENT] [--root ROOT] [--skip-unmapped] [--max-file-size N] [--max-total-size N] [--max-zip-entries N] [--max-facts N] [--taxonomy-package ZIP] input output positional arguments: input report to read output file to write options: -h, --help show this help message and exit --typed-domain DIMENSION=ELEMENT typed domain element for a typed dimension (XML output) --root ROOT top-level folder name (xBRL-CSV output) --skip-unmapped leave out, and list, facts the taxonomy packages cannot place in a table instead of failing (xBRL-CSV output) --taxonomy-package ZIP local taxonomy package: resolves xBRL-CSV metadata, adds the checks that need the taxonomy, and places XML facts in tables or gives typed domains when converting limits: resource limits for untrusted input (defaults in the documentation) --max-file-size N largest uncompressed file, in bytes --max-total-size N largest uncompressed package, in bytes --max-zip-entries N most entries in a package --max-facts N most facts in a report ``` `convert` reads `input` as `inspect` does, prints each reader warning on standard error as `warning: ...`, and writes `output` in the format its suffix names: `.zip` writes an xBRL-CSV package with `write_xbrl_csv`, `.xbrl` or `.xml` an XBRL-XML instance with `write_xbrl_xml`. Any other suffix is an error: ```console $ dpm-xbrl convert report.zip report.txt error: report.txt: output must end in .zip, .xbrl or .xml $ echo $? 1 ``` | Option | Effect | | --- | --- | | `--taxonomy-package ZIP` | As for `inspect`; also places XML facts in the module's tables for xBRL-CSV output, and gives typed domains and the XSD `schemaRef` for XML output. | | `--typed-domain DIMENSION=ELEMENT` | Typed domain element of a typed dimension, for XML output without taxonomy packages, such as `eba_dim:qINC=eba_typ:qCC` or with the element in Clark notation, `eba_dim:qINC={http://www.eba.europa.eu/xbrl/crr/dict/typ}qCC`. Repeat for each typed dimension the facts use; it overrides the taxonomy's. | | `--root NAME` | Top-level folder of the xBRL-CSV package; defaults to the output's file name without `.zip`, as EBA file naming asks. | | `--skip-unmapped` | With taxonomy packages and xBRL-CSV output, leave out facts that cannot be placed exactly and list them on standard error, instead of failing with status 1. | | `--max-*` | As for `inspect`. | See [convert between formats](../guides/convert-formats.md) for which conversions are possible. [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # JSON output `dpm-xbrl inspect --json` prints one JSON object (UTF-8, two-space indentation, non-ASCII characters as is). The exit status is the same as without `--json`, including `--fail-on`. ```console $ dpm-xbrl inspect instance.xbrl --json { "schema_version": 1, "format": "xbrl-xml", "entry_point": "http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/4.2/mod/corep_le.xsd", "entity_scheme": "https://eurofiling.info/eu/rs", "entity_id": "SYNTHETICDPMXL000002.CON", "reference_date": "2025-12-31", "base_currency": "iso4217:EUR", "filing_indicators": { "reported": 2, "total": 3, "templates": { "C_26.00": true, "C_27.00": true, "C_29.00": false } }, "fact_count": 3, "facts_per_template": { "(none)": 3 }, "warnings": [], "filing_rules": [ { "rule": "File naming", "title": "File naming", "code": "invalidFileName", "severity": "warning", "message": "The file name does not follow ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp, the structure for reports sent to the EBA.", "count": 1, "examples": [ "instance.xbrl" ] } ] } ``` ## Fields | Field | Type | Meaning | | --- | --- | --- | | `schema_version` | integer | Version of this layout, currently `1`. | | `format` | string | `"xbrl-csv"` or `"xbrl-xml"`. | | `entry_point` | string or null | `Report.entry_point`. | | `entity_scheme` | string or null | `Report.entity_scheme`. | | `entity_id` | string or null | `Report.entity_id`. | | `reference_date` | string or null | `Report.reference_date` as `YYYY-MM-DD`. | | `base_currency` | string or null | `Report.base_currency`, such as `iso4217:EUR`. | | `filing_indicators.reported` | integer | Templates indicated as reported. | | `filing_indicators.total` | integer | All filing indicators. | | `filing_indicators.templates` | object | Template code to reported (boolean), in report order. | | `fact_count` | integer | Facts in the report (with `--columnar`, frame rows). | | `facts_per_template` | object | Template id to fact count, sorted by template. XML facts have no template and count under `"(none)"`. | | `warnings` | array of strings | `Report.warnings`. | | `filing_rules` | array of objects | `Report.filing`, in rule order. | | `filing_rules[].rule` | string | Rule number, such as `"2.19"`, `"xBRL-CSV 1"` or `"File naming"`. | | `filing_rules[].title` | string | Short title from `RULE_TITLES`. | | `filing_rules[].code` | string | The rule's tag in the EBA document, such as `"nilUsed"`. | | `filing_rules[].severity` | string | `"error"` (MUST) or `"warning"` (SHOULD). | | `filing_rules[].message` | string | What is wrong, as a sentence. | | `filing_rules[].count` | integer | How often the report breaks it. | | `filing_rules[].examples` | array of strings | Up to five locations. | ## Compatibility `schema_version` changes only with a new major version of dpm-xbrl. Within a major version, fields are not removed or renamed and their types do not change; new fields may be added, so ignore fields you do not know. Warning texts and finding messages may be reworded in minor releases; `rule` and `code` may not. See [stability and versioning](../project/stability.md). [dpm-xl]: https://luukhopman.github.io/dpm-xl/ [dpm-xl-validate]: https://luukhopman.github.io/dpm-xl/guides/end-to-end/ [eba-filing-rules]: https://www.eba.europa.eu/sites/default/files/2026-02/10436139-46ae-4a7d-880f-c399ba6de669/EBA%20Filing%20Rules%20v5.8_2026_02_25.pdf [xbrl-csv-spec]: https://www.xbrl.org/Specification/xbrl-csv/REC-2021-10-13/xbrl-csv-REC-2021-10-13.html [report-packages]: https://www.xbrl.org/Specification/report-package/REC-2023-04-19/report-package-REC-2023-04-19.html [xbrl-21]: https://www.xbrl.org/Specification/XBRL-2.1/REC-2003-12-31/XBRL-2.1-REC-2003-12-31+corrected-errata-2013-02-20.html [semver]: https://semver.org [polars]: https://pola.rs # Data formats This page lists exactly what the readers accept and what the writers produce. `read_report` treats ZIP input (bytes or a path), a directory and a `.json` path as xBRL-CSV and everything else as XBRL-XML. Every failure raises `ReportError` naming the file and, where known, the line. ## xBRL-CSV report packages ### Package - A ZIP or directory with exactly one `reports/*.json` report (normally `reports/report.json`), optionally below one top-level folder, plus an optional `META-INF/reportPackage.json` whose `documentType` must be `https://xbrl.org/report-package/2023` or `.../2023/xbr`. `.../2023/xbrl-csv`, which dpm-xbrl 0.1 wrote, is read with a warning. A directory may also hold `report.json` directly. - `documentInfo.documentType` must be `https://xbrl.org/2021/xbrl-csv`; the 2019 to 2021 PWD, CR and PR draft URIs are accepted with a warning. Duplicate JSON keys are rejected. ### Parameters - `parameters.csv` (or the metadata's `parameterURL`) must have the header `name,value`. - `entityID`, such as `lei:5299...`, becomes `Report.entity_id`; its prefix is looked up in `namespaces` for `entity_scheme`, else kept as the scheme. When the entry point cannot be read offline, the EBA prefixes `lei` (`http://standards.iso.org/iso/17442`) and `rs` (`https://eurofiling.info/eu/rs`) apply. - `refPeriod` must be a `YYYY-MM-DD` date. A basic-format `YYYYMMDD` date, or one with a time or time zone, is read and breaks filing rule 2.10; week dates and mixed or non-ASCII digits are rejected. - Parameters named `decimals*` populate `Report.decimals_parameters`; `#none` and `INF` map to `None`. - Inline JSON `parameters` are merged first; the CSV file overrides them. ### Filing indicators `FilingIndicators.csv` must have the header `templateID,reported`, with `reported` one of `true`, `false`, `1`, `0`. See [table codes and filing indicators](../concepts/filing-indicators.md) for how tables map to them. ### Tables Every other `*.csv` under the reports folder is a table. Its template id is the file stem, unless the metadata `tables` maps the file URL to a template. Rows are streamed with the `csv` module (`utf-8-sig`), including from ZIPs; every row must have as many cells as the header, and the header must not repeat or leave empty a column name. **EBA convention (no metadata).** Tables need `datapoint` and `factValue` columns; every other column is a key column kept in `Fact.keys`. Each row with a non-empty `factValue` is one fact. `#nil` gives a nil fact, `#empty` and `#none` are skipped, `##` escapes a leading `#`, and other `#` values are kept as written; each case but `##` is recorded in `Report.filing`. `Fact.decimals` stays `None`; consumers pick the `decimals_parameters` entry for the fact's data type. Exact duplicate rows are reported once; conflicting duplicates (same template, datapoint and keys, different value) are all kept, and exact copies of a fact whose key already had a conflicting value are dropped. Repeated findings are aggregated into one warning with a count and the first location. ### Resolved metadata `report.json` and its `extends` chain are followed recursively (cycle-safe, diamond-safe, depth-limited by `Limits.max_extends_depth`), merging `namespaces`, `tableTemplates`, `tables`, `parameters`, `dimensions`, `decimals` and `parameterURL`, with the extending document taking precedence. Relative URLs resolve inside the package or against the extending document's URL. Absolute URLs are resolved by `metadata_resolver(url)` first and then through the `META-INF/catalog.xml` `rewriteURI` and `rewriteSystem` prefixes of the given taxonomy package ZIPs. Nothing is fetched over the network. Table `url`s and `parameterURL` resolve against the report's own JSON. For a table whose template is resolved, the supported subset is: - template `columns`; - fact columns, with `dimensions` or `propertiesFrom`; - property-group columns (`propertyGroups`, such as EBA's `datapoint` column keyed by `dp...`); - other columns as key columns; - report, template, column and property-group `dimensions` and `decimals`, in increasing precedence; - `$column` and `$parameter` references (row cell, then table parameters, then report parameters), `$$` escapes and `#none`; - the core dimensions `concept`, `unit`, `period`, `entity` and `language` (`period`, `entity` and `language` values are ignored); - taxonomy-defined dimensions. A value that is a qname with a declared prefix is an explicit member; any other value is a typed value. `Fact.datapoint` is the property-group key and `Fact.keys` keeps the key columns. A table whose template cannot be resolved falls back to the EBA convention, and unresolved `extends` produce one warning. ## XBRL-XML instances - The prolog is scanned before parsing, and any DOCTYPE (and with it any ENTITY declaration) is rejected; external entities are never resolved. UTF-8, UTF-16 and single-byte encodings such as ISO-8859-1 are read (anything but UTF-8 breaks rule 1.4); unknown or other multi-byte encodings raise `ReportError`. Parsing streams with `iterparse` and clears each top-level element once processed. - `link:schemaRef` gives `entry_point`. - Contexts provide the entity identifier and scheme, the reference date (an instant, or the end date of a duration; it must start with a `YYYY-MM-DD` date) and `xbrldi:explicitMember`/`xbrldi:typedMember` from the segment or scenario. A typed member's value is the stripped text of its single child element; the child element name is not kept. - Units with one or more measures, or a divide, become strings: `iso4217:EUR`, `a*b` for a product, `num/denom` for a divide. - Facts are top-level elements outside the `xbrli`, `link` and `find` namespaces with a `contextRef`. `xsi:nil="true"` gives `value=None`; `decimals="INF"` gives `None`. `precision` is ignored, and tuples and elements without `contextRef` are skipped, each with a warning. - `find:fIndicators/find:filingIndicator` provide filing indicators; `find:filed` defaults to true. - The report entity and reference date are the most common across facts, and `base_currency` is the most common single-measure `iso4217` unit. More than one entity or period adds a warning, because the EBA requires one. - Qnames use the instance's own prefixes; a namespace declared again deeper in the document under another prefix is rendered with the first prefix bound to it. ## Written xBRL-CSV `write_xbrl_csv` writes a ZIP report package in the EBA convention: ```text /META-INF/reportPackage.json documentType https://xbrl.org/report-package/2023 /reports/report.json extends entry_point, declares namespaces /reports/parameters.csv entityID, refPeriod, baseCurrency, decimals*, then other parameters /reports/FilingIndicators.csv templateID,reported, sorted /reports/