# 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 |
| :-- | :-- |
|
| Parse and evaluate DPM-XL validation rules |
|
| Read, write and check xBRL-CSV and XBRL-XML reports **(this package)** |
|
| 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/.csv datapoint,factValue,
```
Nil values are written as `#nil`, empty strings as `#empty` and a leading
`#` as `##`. The root defaults to the target's file name without `.zip`,
or, for a stream, to
`default_package_root(report)`,
`_`. With `taxonomy_packages`, the module's
tables, columns and prefixes are used instead; see
[convert between formats](../guides/convert-formats.md#xbrl-xml-to-xbrl-csv). Output is deterministic: sorted templates,
source fact order, fixed ZIP timestamps. Facts without a template or
datapoint, unsafe template or root names, and the template names
`parameters` and `FilingIndicators` raise `ReportError` before anything is
written.
## Written XBRL-XML
`write_xbrl_xml` writes `link:schemaRef`, the
filing indicators, contexts deduplicated by dimensions (`c1`, `c2`, ...,
each with the entity and an instant at `reference_date`, dimensions in the
scenario), units (`u1`, ...) and facts. A fact with a unit is numeric and
gets `decimals` (`INF` when unset) unless it is nil; a fact with decimals
but no unit gets `xbrli:pure`. A fact repeated with the same concept,
dimensions, unit, value and decimals is written once. The `schemaRef` is
the module's XSD entry point, mapped from a JSON one through the taxonomy
packages or the EBA's naming. Only the namespaces the instance uses are
declared, on the root element.
Facts without a concept, undeclared or invalid prefixes, invalid local
names, characters XML cannot represent, a missing entity or reference date,
and a typed dimension without a typed domain raise `ReportError`. Because
the reader does not keep typed-domain element names, typed dimensions need
their domain element: from `taxonomy_packages`, or as
`typed_domains={"eba_dim:qINC": "eba_typ:qCC"}`, with a prefix the report
declares or in Clark notation
(`{http://www.eba.europa.eu/xbrl/crr/dict/typ}qCC`), whose namespace is then
declared; only the domains of dimensions that facts use are written.
Carriage returns in values are kept. The writer holds the instance text in
memory before writing it.
Reading a written report reproduces its facts in both formats; the test
suite covers these round trips.
[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 checked
The readers record the [EBA Filing Rules][eba-filing-rules] (version 5.8,
February 2026) that a report breaks and that the file alone can show, in
`Report.filing`. `RULE_TITLES` names each
rule; [filing rules and coverage](../concepts/filing-rules.md) explains the
approach.
## Both formats
| Rule | Checks |
| --- | --- |
| 1.5, 2.2, 2.3 | One absolute taxonomy entry point. |
| 2.8, 3.6 | The reporting subject: scheme (LEI or reporting-subject), suffix such as `.CON` or `.IND` from 31 December 2022 (unless the module is a `_con` or `_ind` module), upper case, and LEI check digits (ISO 17442). |
| File naming | The file name against `ReportSubject_Country_FrameworkCodeModuleVersion_Module_ReferenceDate_CreationTimestamp`, including the reporting subject, module and reference date. Warnings. |
## XBRL-XML
| Rule | Checks |
| --- | --- |
| 1.4, 1.13 | UTF-8, and no standalone declaration. |
| 1.6, 1.6.1 | Filing indicators present, unique, and in a context without segment or scenario. |
| 1.14, 1.15, 2.1, 2.4, 2.25 | No `xsi:schemaLocation`, XInclude, `xml:base`, `link:linkbaseRef` or footnotes. |
| 2.11, 2.13, 2.14, 2.15 | No forever or duration periods, segments, or non-dimensional scenario content. |
| 2.9, 2.10, 2.13 | One entity and one reference date, as plain dates. |
| 2.16, 2.16.1 | No duplicate facts, and none differing only by unit. |
| 2.17, 2.18 | `decimals` rather than `precision`, and present on numeric facts. |
| 2.19 | No nil or empty facts. |
| 2.7, 2.21, 2.22 | No unused or repeated contexts and units. |
| 3.1 | One reporting currency (a), and currency-of-denomination facts in the currency their CUS or CUA member names (c). Rule (c) binds only facts under (b), in contexts with CCA `eba_CA:x1` or qAEA `eba_qCA:qx2000`, so a CUS or CUA member in other contexts is not compared with the unit. |
| 3.2 (a) | `xbrli:pure` for other numeric facts. |
| 3.4, 3.9, 3.10 | Namespace prefixes declared once, on the document element, and used. |
## xBRL-CSV
| Rule | Checks |
| --- | --- |
| Package | `META-INF/reportPackage.json`, `parameters.csv` with `refPeriod`, and `FilingIndicators.csv`. |
| File naming | The root folder named as the ZIP. |
| 1.5 | No `documentInfo.taxonomy`. |
| 2.10 | A plain-date `refPeriod`. |
| 1.6.1 | Unique filing indicators. |
| xBRL-CSV 6 | No table file for a template indicated as not reported. |
| 2.19, xBRL-CSV 5 | No special values in fact or key columns: `#nil`, `#empty` and any other value starting with `#`. |
| xBRL-CSV 1 | No decimals suffix, such as `78920030d-4`. |
| 3.11 | Key values without leading or trailing spaces. |
| xBRL-CSV 4 | A value in the key columns of each reported fact: without metadata every key column but `unit`, which only some data points use; with metadata, the columns each data point refers to. |
| xBRL-CSV 2 | With resolved metadata, every column the table's metadata defines in its header. |
| Package | No files besides `META-INF/reportPackage.json` and, in `reports`, `report.json`, `parameters.csv`, `FilingIndicators.csv` and the tables (`unexpectedFile`, a warning: the filing rules describe the layout without a MUST). |
`read_xbrl_csv_frames` checks
special fact values only; the decimals suffix, spaces around key values,
special key values and empty key values are left to the caller.
## With taxonomy packages
Given `taxonomy_packages`, both readers also check the rules that need the
taxonomy, and only then:
| Rule | Checks |
| --- | --- |
| 1.5 | The entry point is one the packages' `taxonomyPackage.xml` lists (an XSD, or the JSON beside it), and the reference date is within the module's `FromReferenceDate` and `toReferenceDate`. The EBA's placeholder 9999-12-31 counts as no date. |
| 1.6, 1.6.3 | A filing indicator, true or false, for each template of the module, and only the module's codes. |
| 2.18 | Decimals of at least -4 for monetary facts (-6 in the FP, ESG, Pillar 3 and REM_DBM modules), at least 4 for percentages and exactly 0 for integers, or INF, by the metric's schema type (`accuracyRequirements`). The -3 that applied to submissions before April 2025 is not checked: the file does not show when it is sent. |
| 3.2 (b) | Percentages, by the metric's `percentItemType`, as decimal fractions: a value above 50 is a warning, since only the value suggests a percentage multiplied by 100. |
| 3.5 | XML only: each namespace prefix as the namespace's schema in the packages binds it, or as the XBRL specifications use it (`xbrli`, `link`, `xlink`, `xbrldi`, `xbrldt`, `xsi`, `iso4217`, `find`). |
| xBRL-CSV 2 | Header columns, as the packages resolve the metadata. |
| Package | Only the module's table files in `reports`. |
## What each mode checks
| Mode | Rules |
| --- | --- |
| `read_xbrl_xml` | 1.4, 1.5, 1.6, 1.6.1, 1.13 to 1.15, 2.1 to 2.4, 2.7 to 2.11, 2.13 to 2.19, 2.21, 2.22, 2.25, 3.1, 3.2 (a), 3.4, 3.6, 3.9, 3.10, file naming |
| `read_xbrl_csv` | 1.5, 1.6, 1.6.1, 2.2, 2.3, 2.8, 2.10, 2.19, 3.6, 3.11 (keys), xBRL-CSV 1, 2 (with metadata), 4, 5, 6, package, file naming |
| `read_xbrl_csv_frames` | As `read_xbrl_csv`, but of the values only special fact values (2.19, xBRL-CSV 5) |
| Any, with `taxonomy_packages` | Adds 1.5 (published entry point, reference dates), 1.6 (each template), 1.6.3, 2.18, 3.2 (b), and 3.5 for XML; the frames reader, which builds no facts, adds neither 2.18 nor 3.2 |
`checked_rules` gives the same, exactly,
as `(rule, code)` pairs for one mode, so that callers who check rules
themselves can skip the ones the reader already reports:
```python
from dpmxbrl.filing import checked_rules
xml = checked_rules("xbrl-xml", taxonomy=True) - checked_rules("xbrl-xml")
frames = checked_rules("xbrl-csv", taxonomy=True, columnar=True)
print(sorted(xml))
print(("2.18", "accuracyRequirements") in frames)
```
```text
[('1.6.3', 'invalidFilingIndicatorValue'), ('2.18', 'accuracyRequirements'), ('3.2', 'useDecimalFractions'), ('3.5', 'notRecommendedNamespacePrefix')]
False
```
## Left to callers
The other rules that need the taxonomy are left to the caller, which can build
`FilingFinding`s and merge them with
`FilingLog`; see
[add your own filing findings](../guides/add-filing-findings.md):
| Rule | Needs |
| --- | --- |
| 1.7.1 | Facts outside the reported templates. |
| 3.11 | Fact values without leading or trailing spaces, for string data points only (`SPACES`). |
| (none) | `baseCurrency` when monetary facts use it. |
## Not checked
Units against the XBRL unit registry (2.23), generating-software
information (2.26) and comments (2.5).
[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
# Known gaps
- No taxonomy validation, schema lookups, or calculation and formula
checks. Without a taxonomy, an explicit member and a typed value that
looks like a declared qname cannot be told apart in xBRL-CSV metadata.
For the EBA validation rules on report values, see [dpm-xl].
- xBRL-CSV: no `rowIdColumn`, `links`, footnotes, `@start`/`@end` period
semantics, per-fact `period`/`entity`/`language` values (they are
ignored), or multiple reports per package. Taxonomy packages must be
ZIPs, or one ZIP of them as the EBA publishes a release (one level deep,
read into memory).
- EBA-convention CSV facts carry no concept, dimensions, unit or decimals,
and XML facts no datapoint or template, so converting between formats
needs the module's taxonomy packages. XML to xBRL-CSV places facts in
tables of the EBA layout only (one property-group column, one fact
column), and a `decimals*` parameter takes the lowest decimals of its
facts. Key columns follow the EBA's header-code order, which matches
the EBA's own packages for most but not all tables.
- Without taxonomy packages, `write_xbrl_xml` names the `.xsd` entry
point of a `.json` one by the EBA's naming convention, with an
`EntryPointWarning`.
- Empty-string facts do not survive an xBRL-CSV round trip (`#empty` is
skipped when read), and `Fact.decimals`, concepts and dimensions are not
written to EBA-convention CSV.
- XML: tuples, footnotes, `precision`, segment versus scenario placement,
forever periods and typed-domain element names are not kept. XML output
puts all dimensions in the scenario and uses one entity and instant. The
XML writer holds the instance text in memory before writing it.
- Fact locations in XML use the fact id rather than a line number.
- Reading collects up to 100 structural problems of xBRL-CSV tables (row
lengths, quoting, headers) in `ReportError.problems`; other problems,
such as a broken `report.json` or parameters file, stop it at the first.
The frames reader stops at the first problem.
- The 2.18 check does not apply the -3 minimum for monetary facts sent
before April 2025, and 3.2 (b) flags only percentages above 50.
[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
# Python API
The public API is the names in the `__all__` of three modules. Everything
else, including modules whose names start with an underscore, is internal.
| Module | Contents |
| --- | --- |
| [`dpmxbrl`](dpmxbrl.md) | Readers, writers, format detection, and the model: `Report`, `Fact`, `FilingFinding`, `Limits`, `ReportError`. |
| [`dpmxbrl.filing`](filing.md) | The filing-rule catalogue, `FilingLog`, the header checks and the rule tuples for your own findings. |
| [`dpmxbrl.frames`](frames.md) | Columnar reading into polars frames (`frames` extra). |
The model is defined in `dpmxbrl.model`, and the readers and writers in
`dpmxbrl.reader`, `dpmxbrl.csv_reader`, `dpmxbrl.xml_reader` and
`dpmxbrl.writer`; import them from `dpmxbrl`. The CLI is documented in
[CLI](../cli.md).
## Thread safety
All readers and writers may be called from several threads at once: they
keep no shared state, and their results (`Report`, `Fact`,
`FilingFinding`, `ReportFrames`) are immutable. While a reader builds facts,
it pauses Python's cyclic garbage collector for the whole process and
restores it when the last concurrent read ends. `FilingLog` is not
synchronized; use one per thread. Details are in
[the security model](../../concepts/security.md#thread-safety).
[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
# `dpmxbrl`
The top-level package: readers, writers and the fact model. Everything on
this page is in `dpmxbrl.__all__` and covered by the
[stability policy](../../project/stability.md). The model classes are
defined in `dpmxbrl.model` and re-exported here; import them from
`dpmxbrl`.
All functions are safe to call from several threads at once: each call
works on its own input and returns immutable results. See
[thread safety](../../concepts/security.md#thread-safety).
## Reading
## `dpmxbrl.read_report`
```python
def read_report(source: str | os.PathLike[str] | bytes, *, name: str | None = None, metadata_resolver: MetadataResolver | None = None, taxonomy_packages: str | os.PathLike[str] | Sequence[str | os.PathLike[str]] = (), limits: Limits = Limits()) -> Report
```
Read a report, detecting xBRL-CSV or XBRL-XML from the input.
The format is detected as by :func:`detect_format`; the input is then
read by :func:`read_xbrl_csv` or :func:`read_xbrl_xml`.
Args:
source: Bytes, a file path, an xBRL-CSV package directory, or the
path of a package's ``report.json``.
name: Label used in messages and fact locations, such as the file
name of uploaded bytes; the EBA file-naming rule checks it.
metadata_resolver: xBRL-CSV ``extends`` URL resolver; unused for XML.
taxonomy_packages: Local taxonomy package ZIPs, one path or a
sequence: they resolve xBRL-CSV metadata and add the filing
rules that need the taxonomy.
limits: Resource limits for untrusted input.
Returns:
Report: The parsed report.
Raises:
ReportError: If the input cannot be read safely.
Examples:
>>> from dpmxbrl import read_report
>>> report = read_report("instance.xbrl")
>>> report.format, report.reference_date.isoformat(), len(report.facts)
('xbrl-xml', '2025-12-31', 3)
>>> [(finding.rule, finding.code) for finding in report.filing]
[('File naming', 'invalidFileName')]
## `dpmxbrl.read_xbrl_csv`
```python
def read_xbrl_csv(source: str | os.PathLike[str] | bytes, *, name: str | None = None, metadata_resolver: MetadataResolver | None = None, taxonomy_packages: str | os.PathLike[str] | Sequence[str | os.PathLike[str]] = (), limits: Limits = Limits()) -> Report
```
Read an xBRL-CSV report package from a ZIP or a directory.
Args:
source: ZIP bytes, a ZIP path, a package directory, or ``report.json``.
name: Label used in messages instead of the source path.
metadata_resolver: Returns the bytes for an absolute ``extends`` URL,
or ``None`` when unknown. It is consulted before taxonomy packages.
taxonomy_packages: Local taxonomy package ZIPs, one path or a
sequence, whose ``META-INF/catalog.xml`` rewrites resolve
``extends`` URLs.
limits: Resource limits for untrusted input.
Returns:
Report: Report with ``format == "xbrl-csv"``.
Raises:
ReportError: If the package is invalid, unsafe or over a limit.
Examples:
>>> from dpmxbrl import read_xbrl_csv
>>> report = read_xbrl_csv("described.zip", taxonomy_packages=["taxonomy.zip"])
>>> fact = report.facts[0]
>>> fact.datapoint, fact.concept, fact.unit, fact.decimals
('dp84817', 'eba_met:mi53', 'iso4217:EUR', -3)
## `dpmxbrl.read_xbrl_xml`
```python
def read_xbrl_xml(source: str | os.PathLike[str] | bytes, *, name: str | None = None, taxonomy_packages: str | os.PathLike[str] | Sequence[str | os.PathLike[str]] = (), limits: Limits = Limits()) -> Report
```
Read one XBRL 2.1 XML instance.
Args:
source: Instance bytes or a path to the instance document.
name: Label used in messages and fact locations.
taxonomy_packages: Local taxonomy package ZIPs, one path or a
sequence; with them the filing rules that need the taxonomy are
checked too.
limits: Resource limits for untrusted input.
Returns:
Report: Report with ``format == "xbrl-xml"``.
Raises:
ReportError: If the instance is malformed, unsafe or over a limit.
Examples:
>>> from dpmxbrl import read_xbrl_xml
>>> fact = read_xbrl_xml("instance.xbrl").facts[2]
>>> fact.concept, dict(fact.dimensions), dict(fact.typed_dimensions)
('eba_met:qNCO', {'eba_dim:qNCO': 'eba_qCO:qx2000'}, {'eba_dim:qINC': 'CP0001'})
## `dpmxbrl.detect_format`
```python
def detect_format(source: str | os.PathLike[str] | bytes, *, name: str | None = None) -> ReportFormat
```
Detect whether input is an xBRL-CSV package or an XBRL-XML instance.
ZIP archives, directories and ``.json`` metadata paths are xBRL-CSV;
anything else is read as XBRL-XML. A ZIP is recognized by its content,
whatever its suffix.
Args:
source: Bytes, a file path, or an xBRL-CSV package directory.
name: Label used in messages instead of the path, such as the file
name of uploaded bytes.
Returns:
ReportFormat: ``"xbrl-csv"`` or ``"xbrl-xml"``.
Raises:
ReportError: If a path does not exist or cannot be read, or if a
``.zip`` file or one starting like a ZIP archive cannot be read as
one (it is incomplete or damaged; reading it as XML would only
report malformed XML).
Examples:
>>> from dpmxbrl import detect_format
>>> detect_format("report.zip"), detect_format("instance.xbrl")
('xbrl-csv', 'xbrl-xml')
## Writing
## `dpmxbrl.write_xbrl_csv`
```python
def write_xbrl_csv(report: Report, target: str | Path | IO[bytes], *, root: str | None = None, taxonomy_packages: str | os.PathLike[str] | Sequence[str | os.PathLike[str]] = (), strict: bool = True, limits: Limits = Limits()) -> SkippedFacts
```
Write an EBA xBRL-CSV report package ZIP.
The package holds ``META-INF/reportPackage.json``, ``reports/report.json``,
``parameters.csv``, ``FilingIndicators.csv`` and one
``datapoint,factValue,`` table per template. Output is
deterministic: templates are sorted, facts keep their order, and ZIP
timestamps are fixed.
With ``taxonomy_packages``, the module's JSON entry point (the entry
point with ``.json`` for ``.xsd``) gives the tables: each fact with a
concept, such as one read from XML, goes in every table row whose data
point has its concept and dimensions, with the module's table files,
columns and prefixes. ``report.json`` extends the JSON entry point, and
``baseCurrency`` and the ``decimals*`` parameters come from the facts.
Facts without a concept keep their template and datapoint.
Args:
report: Report to write; without taxonomy packages every fact needs a
template and datapoint.
target: Destination path or writable binary stream.
root: Top-level folder name; defaults to the target's file name
without ``.zip``, as the EBA filing rules ask, or to
``default_package_root`` for a stream.
taxonomy_packages: Local taxonomy package ZIPs, one path or a
sequence, holding the module's xBRL-CSV metadata, used to place
facts by concept.
strict: With taxonomy packages, whether a fact that cannot be placed
exactly raises ``ReportError``; when ``False`` it is left out and
returned.
limits: Resource limits for reading the taxonomy packages.
Returns:
SkippedFacts: The facts left out, each a :class:`SkippedFact` with
the reason and its code; always empty when ``strict`` is true.
Warns:
DecimalsWarning: If a ``decimals*`` parameter is lower than the
decimals of some of its facts, naming them.
Raises:
ReportError: If a fact lacks a template or datapoint (without
taxonomy packages) or cannot be placed (with them, in strict
mode), the module metadata is missing, or a template or root name
is not a safe file name.
Examples:
>>> from dpmxbrl import read_report, write_xbrl_csv
>>> report = read_report("report.zip")
>>> write_xbrl_csv(report, "copy.zip") # root folder "copy"
()
>>> read_report("copy.zip").facts == report.facts
True
## `dpmxbrl.write_xbrl_xml`
```python
def write_xbrl_xml(report: Report, target: str | Path | IO[bytes], *, typed_domains: Mapping[str, str] | None = None, taxonomy_packages: str | os.PathLike[str] | Sequence[str | os.PathLike[str]] = (), limits: Limits = Limits()) -> None
```
Write an XBRL 2.1 XML instance.
Contexts are deduplicated per dimension combination (ids ``c1``, ``c2``
in first-use order) and use the report entity and an instant at the
reference date; dimensions are written in the scenario. Facts with a unit
are numeric and get ``decimals`` (``INF`` when unset) unless nil; a fact
with decimals but no unit, as xBRL-CSV metadata gives pure facts, gets
``xbrli:pure``. A fact repeated with the same concept, dimensions, unit,
value and decimals, as in several xBRL-CSV tables, is written once.
The ``schemaRef`` names the module's XSD entry point: a JSON entry point,
as xBRL-CSV reports extend, is mapped to the XSD that the taxonomy
packages list for it, or else named by the EBA's convention.
Args:
report: Report to write; every fact needs a concept.
target: Destination path or writable binary stream.
typed_domains: Typed dimension qname to the qname of its typed domain
element, with a prefix the report declares or in Clark notation
(``{namespace}local``, declared under a new prefix if needed);
required for each typed dimension used by a fact unless the
taxonomy packages give it.
taxonomy_packages: Local taxonomy package ZIPs, one path or a
sequence, whose dimension schemas give the typed domain
(``xbrldt:typedDomainRef``) of each typed dimension not in
``typed_domains``, declared with the prefix its schema uses.
limits: Resource limits for reading the taxonomy packages.
Raises:
ReportError: If a fact lacks a concept, the entity or reference date
is missing, a prefix is undeclared or invalid, a qname has an
invalid local name, a typed domain is unknown, or a value holds
a character XML cannot represent.
Warns:
EntryPointWarning: If the XSD entry point for a JSON one comes from
the EBA's naming convention rather than the taxonomy packages.
Examples:
>>> from dpmxbrl import read_report, write_xbrl_xml
>>> report = read_report("described.zip", taxonomy_packages=["taxonomy.zip"])
>>> write_xbrl_xml(report, "described.xbrl", taxonomy_packages=["taxonomy.zip"])
>>> instance = read_report("described.xbrl")
>>> len(instance.facts), instance.entry_point.rsplit("/", 1)[1]
(6, 'corep_of.xsd')
## `dpmxbrl.default_package_root`
```python
def default_package_root(report: Report) -> str
```
Return the default top-level folder name of a written report package.
Args:
report: Report whose entity and reference date name the folder.
Returns:
str: ``_`` made path-safe, or ``report``.
Examples:
>>> from dpmxbrl import default_package_root, read_report
>>> default_package_root(read_report("report.zip"))
'SYNTHETICDPMXL000002.CON_2025-12-31'
## `dpmxbrl.SkippedFacts`
```python
SkippedFacts = tuple[SkippedFact, ...]
```
The facts that ``write_xbrl_csv(..., strict=False)`` left out.
## `dpmxbrl.SkippedFact`
```python
class SkippedFact(NamedTuple)
```
A fact that ``write_xbrl_csv(..., strict=False)`` left out.
Attributes:
fact: The fact.
reason: Why, as a phrase such as ``its unit iso4217:USD differs from
baseCurrency iso4217:EUR, which the other facts use``; it may be
reworded.
code: Why, as a stable code: ``undeclaredPrefix`` (a prefix of its
concept or dimensions is not declared), ``noDataPoint`` (no data
point of the module has its concept and dimensions),
``unitMismatch`` (its unit does not fit the data point) or
``currencyMismatch`` (its unit differs from the ``baseCurrency``
the other facts use).
## `dpmxbrl.EntryPointWarning`
```python
class EntryPointWarning(UserWarning)
```
``write_xbrl_xml`` named an XSD entry point it could not confirm.
## `dpmxbrl.DecimalsWarning`
```python
class DecimalsWarning(UserWarning)
```
``write_xbrl_csv`` lowered the decimals some facts state.
An EBA xBRL-CSV package gives decimals per data type, in ``decimals*``
parameters; each takes the lowest decimals of its facts, so a fact that
had more now states less accuracy than in the report.
## Model
## `dpmxbrl.Report`
```python
class Report(format: ReportFormat, entry_point: str | None, entity_scheme: str | None, entity_id: str | None, reference_date: date | None, base_currency: str | None, parameters: Mapping[str, str], decimals_parameters: Mapping[str, int | None], filing_indicators: Mapping[str, bool], facts: tuple[Fact, ...], namespaces: Mapping[str, str], warnings: tuple[str, ...] = (), filing: tuple[FilingFinding, ...] = ())
```
One regulatory report read from xBRL-CSV or XBRL-XML.
Attributes:
format: Serialization the report was read from.
entry_point: Taxonomy entry point (``extends[0]`` or ``schemaRef``).
entity_scheme: Entity identifier scheme URI, or the CSV prefix when
no namespace declares it.
entity_id: Entity identifier without scheme.
reference_date: Reference date (CSV ``refPeriod``; XML instant/end).
base_currency: Base currency unit such as ``iso4217:EUR``.
parameters: xBRL-CSV report parameters; empty for XML.
decimals_parameters: xBRL-CSV ``decimals*`` parameters; ``None`` for
``#none``/``INF``.
filing_indicators: Template code to whether it was reported.
facts: Reported facts in source order.
namespaces: Prefix to namespace URI.
warnings: Non-fatal findings in deterministic order.
filing: EBA filing rules the report breaks that the file alone
shows (see :mod:`dpmxbrl.filing`), in rule order.
## `dpmxbrl.Fact`
```python
class Fact(value: str | None, concept: str | None = None, datapoint: str | None = None, template: str | None = None, dimensions: Mapping[str, str] = dict(), typed_dimensions: Mapping[str, str] = dict(), keys: Mapping[str, str] = dict(), unit: str | None = None, decimals: int | None = None, location: str = '')
```
One reported fact independent of its serialization.
Attributes:
value: Lexical value as written; ``None`` when the fact is nil.
concept: Prefixed concept qname such as ``eba_met:qABJ``. xBRL-CSV
facts carry it only when table metadata was resolved.
datapoint: xBRL-CSV datapoint code such as ``dp84817``.
template: xBRL-CSV table template identifier; ``None`` for XML.
dimensions: Explicit dimension qname to member qname.
typed_dimensions: Typed dimension qname to typed value text.
keys: xBRL-CSV non-fact column name to cell value, in header order.
unit: Unit such as ``iso4217:EUR`` or ``a/b`` for a divide unit.
decimals: Decimals accuracy; ``None`` means INF or unspecified.
location: Source location, ``reports/x.csv:3`` (the 1-based
physical line) or ``file.xbrl#id`` (``#factN`` for the Nth fact
without an id).
The mappings of facts that are read are read-only and may be shared
between facts; copy one with ``dict()`` to change it.
Examples:
>>> from dpmxbrl import read_report
>>> fact = read_report("report.zip").facts_for_template("C_27.00")[0]
>>> fact.datapoint, fact.value
('dp5486584', 'Northwind Holdings')
>>> dict(fact.keys)
{'qINC': 'CP0001', 'qNCO': 'eba_qCO:qx2000'}
>>> fact.location
'reports/c_27.00.csv:2'
## `dpmxbrl.FilingFinding`
```python
class FilingFinding(rule: str, code: str, severity: Severity, message: str, count: int = 1, examples: tuple[str, ...] = ())
```
One EBA filing rule a report breaks, with how often and where.
Attributes:
rule: The rule's number, a key of :data:`dpmxbrl.filing.RULE_TITLES`.
code: The rule's tag in the EBA document, such as ``nilUsed``.
severity: ``error`` when the rule says MUST, ``warning`` for SHOULD.
message: What is wrong and what the rule requires, as a sentence.
count: How many times the report breaks it.
examples: Up to five locations, such as ``report.xbrl#f12``.
``rule`` and ``code`` are stable across releases; ``message`` may be
reworded in a minor release.
## `dpmxbrl.Limits`
```python
class Limits(max_zip_entries: int = 10000, max_total_size: int = 2 * 1024 ** 3, max_file_size: int = 512 * 1024 ** 2, max_facts: int = 20000000, max_extends_depth: int = 32, max_taxonomy_entries: int = 100000)
```
Resource limits applied while reading untrusted report input.
Attributes:
max_zip_entries: Maximum number of entries in a report package ZIP.
max_total_size: Maximum total uncompressed size of a report package.
max_file_size: Maximum uncompressed size of one file, including an
XBRL-XML instance document.
max_facts: Maximum number of facts in one report.
max_extends_depth: Maximum nesting depth of xBRL-CSV ``extends``.
max_taxonomy_entries: Maximum number of entries in one taxonomy
package ZIP; the EBA's reporting frameworks package has about
17,400. Files read from taxonomy packages also count towards
``max_total_size``.
Examples:
>>> from dataclasses import replace
>>> from dpmxbrl import Limits, ReportError, read_report
>>> try:
... read_report("report.zip", limits=replace(Limits(), max_facts=10))
... except ReportError as error:
... print(error)
reports/c_28.00.csv:2: report exceeds the limit of 10 facts
## `dpmxbrl.ReportError`
```python
class ReportError(message: str, *, problems: Sequence[str] = ()) # bases: ValueError
```
Raised when a report cannot be read or written safely.
Every unreadable, unsafe or over-limit input raises it, with a message
that names the file and, where known, the line or entry.
Attributes:
problems: Every problem found, one message each; an error about a
single problem holds its own message.
Examples:
>>> from dpmxbrl import ReportError, read_report
>>> try:
... read_report("missing.zip")
... except ReportError as error:
... print(error)
missing.zip: no such file or directory
## `dpmxbrl.ReportFormat`
```python
ReportFormat = Literal['xbrl-csv', 'xbrl-xml']
```
## `dpmxbrl.Severity`
```python
Severity = Literal['error', 'warning']
```
``error`` for a "MUST" filing rule (the report is rejected), ``warning``
for a "SHOULD" rule.
## `dpmxbrl.local_name`
```python
def local_name(qname: str) -> str
```
Return the local part of a prefixed or Clark-notation qname.
Args:
qname: A qname such as ``eba_met:mi53`` or ``{namespace}mi53``.
Returns:
str: Text after the last ``}`` or the first ``:``; a name without
either is returned as is.
Examples:
>>> from dpmxbrl import local_name
>>> local_name("eba_met:mi53"), local_name("{http://example.com/met}mi53")
('mi53', 'mi53')
## `dpmxbrl.filing_indicator_code`
```python
def filing_indicator_code(template: str) -> str
```
Return the filing indicator code of an xBRL-CSV table template.
Single-letter parts are dropped: ``c_07.00.a`` is filed as ``c_07.00``,
``f_18.00.a.dp`` as ``f_18.00.dp``. Filing indicators compare
case-insensitively.
Args:
template: Table template id, such as a table file's stem.
Returns:
str: The template id without single-letter parts.
Examples:
>>> from dpmxbrl import filing_indicator_code
>>> filing_indicator_code("c_07.00.a"), filing_indicator_code("F_18.00.a.dp")
('c_07.00', 'F_18.00.dp')
## Version
## `dpmxbrl.__version__`
```python
__version__ = version('dpm-xbrl')
```
[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
# `dpmxbrl.filing`
The EBA filing rules: the rule catalogue, the log that collects findings,
the checks the readers run, and the rule tuples that callers use to add
their own findings. Everything on this page is in `dpmxbrl.filing.__all__`.
See [check EBA filing rules](../../guides/check-filing-rules.md),
[add your own filing findings](../../guides/add-filing-findings.md) and the
[list of rules checked](../filing-rules.md).
EBA filing rules: checks on how a report is written, before its values.
The EBA Filing Rules (version 5.8, 25 February 2026) constrain the report
file itself: one entity and reference date, filing indicators, decimals,
units, file naming, and constructs that must not appear. A report that
breaks a "MUST" rule is rejected however good its values are; "SHOULD"
rules are recommendations.
The readers record a :class:`FilingFinding` for each rule a report breaks
that can be judged from the file alone. Rules that need the taxonomy, such
as which templates a module has or a data point's data type, are left to
callers, which can build findings with the same classes.
## `dpmxbrl.filing.DECIMALS_SUFFIX`
```python
DECIMALS_SUFFIX: Rule = ('xBRL-CSV 1', 'decimalsSuffixUsed', 'error', 'Fact values must not use the decimals suffix (such as 78920030d-4); decimals belong in parameters.csv.')
```
## `dpmxbrl.filing.EMPTY_VALUE`
```python
EMPTY_VALUE: Rule = ('2.19', 'emptyUsed', 'error', 'Facts must not be reported as #empty; such values were skipped.')
```
## `dpmxbrl.filing.KEY_SPACES`
```python
KEY_SPACES: Rule = ('3.11', 'leadingOrTrailingSpacesInText', 'warning', 'Key values should not start or end with spaces or line breaks.')
```
## `dpmxbrl.filing.MISSING_KEY_VALUE`
```python
MISSING_KEY_VALUE: Rule = ('xBRL-CSV 4', 'missingKeyValue', 'error', 'Key columns must hold a value for each reported fact.')
```
## `dpmxbrl.filing.NIL_VALUE`
```python
NIL_VALUE: Rule = ('2.19', 'nilUsed', 'error', 'Facts must not be reported as #nil; leave out a fact that is not reported.')
```
## `dpmxbrl.filing.RULE_TITLES`
```python
RULE_TITLES: dict[str, str] = {'1.4': 'Character encoding', '1.5': 'Taxonomy entry point', '1.6': 'Filing indicators', '1.6.1': 'One filing indicator per template', '1.6.3': 'Filing indicator codes', '1.7': 'Templates indicated as reported', '1.7.1': 'Facts outside the reported templates', '1.9': 'Valid XBRL', '1.13': 'Standalone declaration', '1.14': 'Schema location attributes', '1.15': 'XInclude', '2.1': 'xml:base', '2.2': 'Absolute entry point URL', '2.3': 'One taxonomy reference', '2.4': 'Linkbase references', '2.7': 'Unused or duplicated contexts', '2.8': 'Reporting subject identification', '2.9': 'One reporting subject', '2.10': 'Reference date format', '2.11': 'xbrli:forever', '2.13': 'Period consistency', '2.14': 'xbrli:segment', '2.15': 'Scenario content', '2.16': 'Duplicate facts', '2.16.1': 'Facts differing only by unit', '2.17': 'Precision attribute', '2.18': 'Decimals', '2.19': 'Nil and empty values', '2.21': 'Duplicated units', '2.22': 'Unused units', '2.25': 'Footnotes', '3.1': 'Currency of monetary facts', '3.2': 'Non-monetary units', '3.4': 'Unused namespace prefixes', '3.5': 'Canonical namespace prefixes', '3.6': 'Reporting subject', '3.9': 'Namespace declarations', '3.10': 'One prefix per namespace', '3.11': 'Leading or trailing spaces', 'Package': 'xBRL-CSV report package', 'File naming': 'File naming', 'xBRL-CSV 1': 'Decimals suffix', 'xBRL-CSV 2': 'Header row', 'xBRL-CSV 4': 'Key column values', 'xBRL-CSV 5': 'Special values', 'xBRL-CSV 6': 'Tables of templates not reported'}
```
Each rule's number in the EBA Filing Rules and a short title.
Numbered rules come from sections 1 to 3. ``Package`` and ``File naming``
are the requirements in "File naming structure for remittance to the EBA";
``xBRL-CSV n`` are the numbered "xBRL-CSV reports extra rules".
## `dpmxbrl.filing.SPACES`
```python
SPACES: Rule = ('3.11', 'leadingOrTrailingSpacesInText', 'warning', 'Fact values should not start or end with spaces or line breaks.')
```
Rule 3.11 for fact values, which applies to string data points only.
The readers do not know data types, so they leave this check to callers
that do; they check key values (:data:`KEY_SPACES`) themselves.
## `dpmxbrl.filing.SPECIAL_KEY_VALUE`
```python
SPECIAL_KEY_VALUE: Rule = ('xBRL-CSV 5', 'specialKeyValueUsed', 'error', 'Key columns must not use special values starting with #.')
```
## `dpmxbrl.filing.SPECIAL_VALUE`
```python
SPECIAL_VALUE: Rule = ('xBRL-CSV 5', 'specialValueUsed', 'error', 'Fact values must not use special values starting with # (#none, #empty, #nil or others).')
```
## `dpmxbrl.filing.FilingFinding`
```python
class FilingFinding(rule: str, code: str, severity: Severity, message: str, count: int = 1, examples: tuple[str, ...] = ())
```
One EBA filing rule a report breaks, with how often and where.
Attributes:
rule: The rule's number, a key of :data:`dpmxbrl.filing.RULE_TITLES`.
code: The rule's tag in the EBA document, such as ``nilUsed``.
severity: ``error`` when the rule says MUST, ``warning`` for SHOULD.
message: What is wrong and what the rule requires, as a sentence.
count: How many times the report breaks it.
examples: Up to five locations, such as ``report.xbrl#f12``.
``rule`` and ``code`` are stable across releases; ``message`` may be
reworded in a minor release.
## `dpmxbrl.filing.FilingLog`
```python
class FilingLog()
```
Collect findings, counting repeats of the same finding.
A finding is identified by its rule, code and message. The log keeps a
count and up to five distinct locations for each, and returns them in
rule order. It is not synchronized; use one log per thread.
Examples:
>>> import dataclasses
>>> from dpmxbrl import read_report
>>> from dpmxbrl.filing import FilingLog
>>> report = read_report("report.zip")
>>> log = FilingLog()
>>> log.extend(report.filing)
>>> rule = (
... "1.6",
... "missingFilingIndicator",
... "error",
... "Each template needs one.",
... )
>>> log.add(*rule, "C_30.00")
>>> report = dataclasses.replace(report, filing=log.result())
>>> [(finding.rule, finding.examples) for finding in report.filing]
[('1.6', ('C_30.00',)), ('File naming', ('report.zip',))]
### `dpmxbrl.filing.FilingLog.add`
```python
def add(self, rule: str, code: str, severity: Severity, message: str, location: str | None = None, *, count: int = 1) -> None
```
Record ``count`` occurrences of a finding, keeping a few locations.
A location already kept for the finding is not kept again. Callers
usually unpack a :data:`Rule` tuple: ``log.add(*NIL_VALUE, where)``.
Args:
rule: Rule number, a key of :data:`RULE_TITLES`.
code: The rule's tag in the EBA document.
severity: ``error`` for a MUST rule, ``warning`` for SHOULD.
message: What is wrong, as a sentence.
location: Where, such as ``reports/c_01.00.csv:3``; optional.
count: Occurrences to add; zero or less records nothing.
### `dpmxbrl.filing.FilingLog.extend`
```python
def extend(self, findings: Iterable[FilingFinding]) -> None
```
Record findings made elsewhere, keeping each location once.
Args:
findings: Findings such as a report's ``filing``; counts add up.
### `dpmxbrl.filing.FilingLog.result`
```python
def result(self) -> tuple[FilingFinding, ...]
```
Return the findings in rule order.
Returns:
tuple[FilingFinding, ...]: One finding per rule, tag and message.
## `dpmxbrl.filing.Rule`
```python
Rule = tuple[str, str, Severity, str]
```
A finding's rule number, tag, severity and message, for :meth:`FilingLog.add`.
## `dpmxbrl.filing.Severity`
```python
Severity = Literal['error', 'warning']
```
``error`` for a "MUST" filing rule (the report is rejected), ``warning``
for a "SHOULD" rule.
## `dpmxbrl.filing.check_entry_point`
```python
def check_entry_point(entry_points: list[str], log: FilingLog, *, where: str, xml: bool) -> None
```
Check that one absolute entry point URL is referenced (1.5, 2.2, 2.3).
Args:
entry_points: Every entry point the report references.
log: Log the findings are added to.
where: Location for the findings, such as the file name.
xml: Whether the report is XBRL-XML, which chooses the tags and
the element named in the messages.
## `dpmxbrl.filing.check_file_name`
```python
def check_file_name(name: str, report: Report, log: FilingLog) -> None
```
Check a report file's name against the EBA naming structure.
The structure, ``ReportSubject_Country_FrameworkCodeModuleVersion_
Module_ReferenceDate_CreationTimestamp``, is the one competent
authorities use when they send reports to the EBA; reporters confirm
their own with their supervisor, so mismatches are warnings.
Args:
name: File name or path; a ``.zip``, ``.xbrl`` or ``.xml`` suffix
is ignored.
report: Report whose entity, entry point and reference date the
name must match.
log: Log the findings are added to.
Examples:
>>> from dpmxbrl import read_report
>>> from dpmxbrl.filing import FilingLog, check_file_name
>>> log = FilingLog()
>>> name = (
... "SYNTHETICDPMXL000002.CON_FR_COREP030300_COREPLE_"
... "2025-12-31_20260115093000000.zip"
... )
>>> check_file_name(name, read_report("report.zip"), log)
>>> log.result()
()
## `dpmxbrl.filing.check_subject`
```python
def check_subject(report: Report, log: FilingLog) -> None
```
Check the reporting subject's scheme and identifier (rules 2.8 and 3.6).
The EBA prescribes two schemes: the LEI scheme for a bare LEI, and the
reporting-subject scheme for an identifier with a suffix such as
``.CON``. Reports from 31 December 2022 onwards must use a suffix
unless their module name ends in ``_con`` or ``_ind``. A suffix the
EBA does not list is a warning: other codes are agreed with the EBA.
Args:
report: Report whose entity scheme, identifier, entry point and
reference date are checked.
log: Log the findings are added to.
## `dpmxbrl.filing.checked_rules`
```python
def checked_rules(format: ReportFormat, *, taxonomy: bool = False, columnar: bool = False) -> frozenset[tuple[str, str]]
```
Return the findings a reader can record, as ``(rule, code)`` pairs.
Callers that check rules themselves use it to avoid reporting a rule
twice. The set is exact for each mode: the rules the taxonomy adds are
there only with ``taxonomy``, canonical prefixes (3.5) only for XML, and
the columnar reader, which builds no facts, records neither 2.18 nor
3.2 (b).
Args:
format: ``xbrl-xml`` for :func:`~dpmxbrl.read_xbrl_xml`, ``xbrl-csv``
for :func:`~dpmxbrl.read_xbrl_csv`.
taxonomy: Whether the reader is given taxonomy packages.
columnar: For ``xbrl-csv``, whether the report is read with
:func:`~dpmxbrl.frames.read_xbrl_csv_frames`.
Returns:
frozenset[tuple[str, str]]: The ``(rule, code)`` pairs.
Raises:
ValueError: If ``format`` is not a report format, or ``columnar`` is
asked for XML.
Examples:
>>> from dpmxbrl.filing import checked_rules
>>> added = checked_rules("xbrl-csv", taxonomy=True) - checked_rules("xbrl-csv")
>>> for rule, code in sorted(added):
... print(rule, code)
1.6.3 invalidFilingIndicatorValue
2.18 accuracyRequirements
3.2 useDecimalFractions
## `dpmxbrl.filing.sort_findings`
```python
def sort_findings(findings: Iterable[FilingFinding]) -> tuple[FilingFinding, ...]
```
Order findings by rule number, then the annex requirements.
Numbered rules come first in numeric order (``2.9`` before ``2.10``),
then ``xBRL-CSV n``, then ``Package`` and ``File naming``.
Args:
findings: Findings in any order.
Returns:
tuple[FilingFinding, ...]: The findings in document order.
`FilingFinding` and `Severity` are re-exported here from
[`dpmxbrl`](dpmxbrl.md#dpmxbrl.FilingFinding).
[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
# `dpmxbrl.frames`
Columnar reading of large xBRL-CSV packages into polars frames. Needs the
`frames` extra (`pip install "dpm-xbrl[frames]"`). Everything on this page
is in `dpmxbrl.frames.__all__`.
See [read large reports as frames](../../guides/large-reports.md).
Columnar xBRL-CSV reading: one polars data frame per table.
:func:`read_xbrl_csv` builds one :class:`~dpmxbrl.model.Fact` per row, which
is convenient but costs several microseconds per fact in Python. Reports with
millions of open-table rows need columnar access instead:
:func:`read_xbrl_csv_frames` applies the same package, ``report.json``,
parameter and filing-indicator checks, then reads each EBA-convention table
straight into a polars frame of strings (``datapoint``, ``factValue`` and the
key columns), applying the same special-value and duplicate rules.
Requires the optional ``frames`` extra (``polars``). Tables described by
resolved xBRL-CSV metadata are not supported here; use :func:`read_xbrl_csv`.
Filing-rule findings on values are limited to special values (``#nil``,
``#empty`` and others). Checks that :func:`read_xbrl_csv` makes on every value
(the decimals suffix, spaces around key values, special key values) cost a
second scan of millions of strings, so they are left to callers that already
split values by data type and key column.
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, so they differ when a file has blank
lines or values spanning lines.
Rows with no fact (an empty value, ``#empty`` or ``#none``) and exact
duplicate rows are removed, so a frame's row index is not the row's place in
its CSV file. :attr:`ReportFrames.dropped_rows` lists the removed rows by
template, which maps a frame row back to its data row when a caller needs
the file location.
## `dpmxbrl.frames.MetadataTablesError`
```python
class MetadataTablesError(ReportError)
```
The package has tables described by xBRL-CSV metadata.
Columnar reading supports the EBA ``datapoint``/``factValue`` convention
only; read such a package with :func:`dpmxbrl.read_xbrl_csv`.
## `dpmxbrl.frames.ReportFrames`
```python
class ReportFrames(report: Report, tables: Mapping[str, pl.DataFrame], dropped_rows: Mapping[str, tuple[int, ...]] = dict())
```
Report metadata plus one string frame per table template.
Each frame has a categorical ``datapoint`` column, a ``factValue``
column of strings (null for ``#nil``) and the table's key columns,
categorical, in header order. Rows keep file order, without the rows
listed in :attr:`dropped_rows`.
### `dpmxbrl.frames.ReportFrames.report`
```python
report: Report
```
The report with ``facts == ()``; use :attr:`tables` for the facts.
### `dpmxbrl.frames.ReportFrames.tables`
```python
tables: Mapping[str, pl.DataFrame]
```
Template ID to a frame with ``datapoint``, ``factValue`` (strings) and
key columns; ``datapoint`` and key columns are categorical.
### `dpmxbrl.frames.ReportFrames.dropped_rows`
```python
dropped_rows: Mapping[str, tuple[int, ...]] = field(default_factory=dict)
```
Template ID to the sorted 0-based data-row indices of the rows removed
from its frame: rows without a fact (an empty value, ``#empty`` or
``#none``) and exact duplicates of an earlier row. Indices count data
rows as read, before any row is removed, and continue across the files
of a template in file order, as the frame does; data row ``i`` of a file
is on line ``i + 2`` when no value spans lines. Only templates with
removed rows appear.
### `dpmxbrl.frames.ReportFrames.fact_count`
```python
fact_count: int
```
Total number of fact rows across tables.
Returns:
int: The sum of the frames' heights.
## `dpmxbrl.frames.read_xbrl_csv_frames`
```python
def read_xbrl_csv_frames(source: str | os.PathLike[str] | bytes, *, name: str | None = None, metadata_resolver: MetadataResolver | None = None, taxonomy_packages: str | os.PathLike[str] | Sequence[str | os.PathLike[str]] = (), limits: Limits = Limits(), on_metadata: Callable[[Report], None] | None = None, on_table: Callable[[str, pl.DataFrame, tuple[int, ...]], None] | None = None) -> ReportFrames
```
Read an xBRL-CSV report package into columnar table frames.
Args:
source: Package bytes, path, or directory.
name: Label used in messages.
metadata_resolver: Returns the bytes for an absolute ``extends`` URL.
taxonomy_packages: Taxonomy packages for offline metadata, one path
or a sequence.
limits: Resource limits for untrusted input.
on_metadata: Called with the report (entry point, reference date,
parameters and filing indicators, no facts) before the tables
are read, so callers can start dependent work that overlaps the
table parsing. Exceptions it raises abort the read.
on_table: Called on the calling thread with each template ID, its
frame and its removed rows (as in :attr:`ReportFrames.tables` and
:attr:`ReportFrames.dropped_rows`) as soon as the template's last
file is read, in package order, so callers can process tables
while later ones are still being read. Exceptions it raises abort
the read.
Returns:
ReportFrames: Report metadata and the table frames.
Raises:
MetadataTablesError: If the package has metadata-described tables.
ReportError: If the package is invalid, unsafe or over a limit.
Examples:
>>> from dpmxbrl.frames import read_xbrl_csv_frames
>>> frames = read_xbrl_csv_frames("report.zip")
>>> frames.fact_count, sorted(frames.tables)
(14, ['c_00.01', 'c_26.00', 'c_27.00', 'c_28.00'])
>>> frames.tables["c_28.00"].columns
['datapoint', 'factValue', 'qINC']
[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
# Using dpm-xbrl with AI agents
This page is for coding agents that write code *using* dpm-xbrl, and for
people who brief them. It gives the essential API, the contracts to rely on
and the mistakes to avoid. Agents working *on* the dpm-xbrl repository
should read `AGENTS.md` in its root instead.
Machine-readable copies of these docs:
- [`llms.txt`](llms.txt): an index of every page with a one-line
description ([llmstxt.org](https://llmstxt.org) format).
- [`llms-full.txt`](llms-full.txt): every page in one Markdown file,
including the API reference.
- Each page as Markdown: replace the trailing `/` of a page URL with
`.md`, for example
`https://luukhopman.github.io/dpm-xbrl/guides/read-a-report.md`
(section index pages are `.../guides/index.md`).
## Essentials
```python
from dpmxbrl import Limits, ReportError, read_report
try:
# A path, a directory, a report.json, an .xbrl file, or bytes.
report = read_report("report.zip", limits=Limits())
except ReportError as error: # the only exception for bad input
raise SystemExit(f"cannot read: {error}")
report.format # "xbrl-csv" or "xbrl-xml"
report.entity_id # "SYNTHETICDPMXL000002.CON"
report.reference_date # datetime.date(2025, 12, 31)
report.filing_indicators # {"C_26.00": True, ...}
report.parameters # {"refPeriod": "2025-12-31", ...}; xBRL-CSV only
report.facts # tuple[Fact, ...] in file order
report.warnings # tuple[str, ...]: how the file was read
report.filing # tuple[FilingFinding, ...]: EBA filing rules broken
for fact in report.facts_for_template("C_28.00"):
fact.datapoint, fact.keys, fact.value, fact.location
```
| Task | Call |
| --- | --- |
| Read any report | `dpmxbrl.read_report(source, name=None, metadata_resolver=None, taxonomy_packages=(), limits=Limits())` |
| Read one format | `read_xbrl_csv(...)`, `read_xbrl_xml(source, name=None, taxonomy_packages=(), limits=Limits())` |
| Detect the format | `detect_format(source, name=None) -> "xbrl-csv" \| "xbrl-xml"` |
| Millions of facts | `dpmxbrl.frames.read_xbrl_csv_frames(source, ...) -> ReportFrames` (`frames` extra) |
| Write | `write_xbrl_csv(report, target, root=None, taxonomy_packages=(), strict=True) -> SkippedFacts`, `write_xbrl_xml(report, target, typed_domains=None, taxonomy_packages=())` |
| Convert | Read, then write with `taxonomy_packages`, or `dpm-xbrl convert IN OUT --taxonomy-package ZIP` |
| Avoid duplicate findings | `dpmxbrl.filing.checked_rules(format, taxonomy=..., columnar=...)`: the `(rule, code)` pairs a reader checks |
| Gate on filing rules | `[f for f in report.filing if f.severity == "error"]`, or `dpm-xbrl inspect PATH --fail-on error` (exit 3) |
| Add findings | `dpmxbrl.filing.FilingLog`: `.extend(report.filing)`, `.add(*rule_tuple, location)`, `.result()` |
| Change a report | `dataclasses.replace(report, filing=..., entry_point=...)` |
## Contracts
- Every unreadable, unsafe or over-limit input raises `ReportError`, a
`ValueError`. Anything else is a bug.
- `Report`, `Fact`, `FilingFinding` and `Limits` are frozen dataclasses.
`Fact.dimensions`, `typed_dimensions` and `keys` are read-only dicts that
facts may share; `dict(fact.keys)` makes a mutable copy.
- `Fact.value` is the lexical string from the file, or `None` for nil.
Nothing is converted to numbers or dates.
- Match findings on `FilingFinding.rule` and `.code`, which are stable;
never on `.message`, which may be reworded.
- `inspect --json` output carries `schema_version` (1); it changes only
with a major version.
- Nothing is fetched over the network.
- Readers and writers are thread-safe; results are immutable.
## Pitfalls
- **Facts of EBA-convention CSV have no concept.** Without
`taxonomy_packages`, `Fact.concept`, `dimensions`, `unit` and `decimals`
are empty; use `datapoint`, `keys` and `report.decimals_parameters`.
The warning "could not be resolved offline" is expected, not an error.
- **XML facts have no datapoint or template.** `facts_for_template` returns
nothing for XML reports; identify XML facts by `concept` and `dimensions`.
- **Template ids are not filing indicators.** A file `c_07.00.a.csv` has
`Fact.template == "c_07.00.a"`, filed as `C_07.00`;
`facts_for_template` matches either, ignoring case, and
`filing_indicator_code` maps one to the other. See
[table codes and filing indicators](concepts/filing-indicators.md).
- **Warnings are not filing findings.** Gate on `report.filing`, not on
`report.warnings`.
- **Conversion needs the taxonomy packages.** Without them, EBA-convention
CSV cannot be written as XML, and XML cannot be written as CSV. See
[convert between formats](guides/convert-formats.md).
- **Taxonomy checks need the packages.** 1.5 (published entry point and
reference dates), 1.6 (each template), 1.6.3, 2.18, 3.2 (b) and 3.5 are
checked only with `taxonomy_packages`.
- **Bytes need a name.** Pass `name=` with the real file name when reading
bytes, or the file-naming rule checks ``.
- **Large reports.** `read_report` costs about 5 µs and 0.7 KB per fact.
Above a few million facts use `read_xbrl_csv_frames`, and raise `Limits`
for trusted packages over 512 MiB per file.
- **Columnar reading checks less.** It checks special fact values only, and
raises `MetadataTablesError` for metadata-described tables; fall back to
`read_report`.
- **The garbage collector is paused process-wide** while facts are built.
Do not rely on cyclic collection in other threads during a read.
## Where to look next
- [Getting started](getting-started.md) for a guided first use.
- [Python API](reference/api/index.md) for every signature.
- [CLI](reference/cli.md) and [JSON output](reference/json-output.md).
- [Filing rules checked](reference/filing-rules.md) and
[known gaps](reference/known-gaps.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
# Changelog
All notable changes to this project are documented here. The project follows
[semantic versioning](https://semver.org).
## 1.0.0 - 2026-10-24
### Added
- Python 3.15 support, tested in CI on 3.11 to 3.15 with and without extras.
On 3.15 the `fast` extra installs without `isal`, which has no 3.15
wheels yet, and ZIP entries are inflated with zlib.
- A documentation site at https://luukhopman.github.io/dpm-xbrl/, with
`llms.txt` and `llms-full.txt` for AI agents.
- `dpm-xbrl inspect --fail-on error|warning` exits with status 3 when the
report breaks a filing rule of that severity (2 stays the usage-error
status); `--version`, and examples in `--help`. A limit error names the
option that raises it.
- `dpmxbrl.__version__`; `inspect --json` output carries `schema_version`.
- `inspect --columnar` counts large xBRL-CSV packages with polars, and both
commands take `--max-facts`, `--max-file-size`, `--max-total-size` and
`--max-zip-entries`.
- `dpmxbrl.frames.MetadataTablesError`, a `ReportError` raised when a package
needs `read_xbrl_csv`, so callers need not match the message.
- `write_xbrl_xml` and `convert --typed-domain` accept a typed domain element
in Clark notation, `{namespace}local`, and declare its namespace.
- `write_xbrl_csv(..., taxonomy_packages=...)` and `convert --taxonomy-package`
convert XBRL-XML to xBRL-CSV: facts go in the module's table rows by concept
and dimensions. Facts that cannot be placed raise `ReportError`, or are left
out and returned with `strict=False` (`convert --skip-unmapped`).
- `write_xbrl_xml(..., taxonomy_packages=...)` takes typed domains from the
dimension schemas, so `convert` needs no `--typed-domain` for EBA packages.
- Both writers take `limits=`, for reading the taxonomy packages.
- `taxonomy_packages` may be one path, a `str` or `os.PathLike`, as well as
a sequence of them.
- `EntryPointWarning`, which `write_xbrl_xml` gives when it names an XSD
entry point by the EBA's naming rather than from the taxonomy packages.
- `read_xbrl_xml` takes `taxonomy_packages`, and both readers check with them
the entry point and the module's reference dates (1.5), filing indicator
codes (1.6, 1.6.3), percentages above 50 (3.2) and, for XML, canonical
namespace prefixes (3.5).
- xBRL-CSV packages with files outside the EBA layout break the `Package`
rule (`unexpectedFile`, a warning).
- `ReportError.problems` lists every problem; reading xBRL-CSV tables
collects up to 100 row-length, quoting and header problems before failing.
- With taxonomy packages, decimals are checked against the accuracy
requirements per data type (2.18).
- xBRL-CSV tables must hold every column their metadata defines (rule 2)
and a value in each key column of a reported fact (rule 4);
`dpmxbrl.filing.MISSING_KEY_VALUE` is the rule 4 tuple for callers.
- Taxonomy packages may be one ZIP holding the packages, as the EBA
publishes them.
- `dpmxbrl.filing.checked_rules(format, *, taxonomy, columnar)` gives the
findings a reader can record in each mode, so callers can avoid
reporting a rule twice.
- `SkippedFacts`, the type `write_xbrl_csv` returns: a tuple of
`SkippedFact(fact, reason, code)`, with stable codes.
- `DecimalsWarning` names the facts whose decimals a converted package's
`decimals*` parameter lowers.
- `Limits.max_taxonomy_entries` bounds the entries of a taxonomy package
ZIP, and files read from taxonomy packages count towards
`Limits.max_total_size`.
- `filing_indicator_code` maps a table template to its filing indicator.
- `detect_format(..., name=...)` names the input in its errors.
- The public API is documented: every name in the `__all__` of `dpmxbrl`,
`dpmxbrl.filing` and `dpmxbrl.frames`, and how table codes map to filing
indicators.
### Changed
- **Breaking:** `write_xbrl_csv` to a path names the package's root folder
as the ZIP file, without `.zip`, as the EBA filing rules ask;
`default_package_root` still names it for streams.
- **Breaking:** the mappings of a report that is read are read-only
`dict`s: the `dimensions`, `typed_dimensions` and `keys` of its facts,
which facts may share, and its `parameters`, `decimals_parameters`,
`filing_indicators` and `namespaces`. Assigning to one raises
`TypeError`, so copy it with `dict()` to change it. They pickle, copy and
serialize as plain dicts.
- `inspect` no longer prints a `(none)` per-template row for XML reports,
whose facts have no template.
- `detect_format` is annotated to return `ReportFormat`.
- `Report.facts_for_template` also matches a table by its filing indicator,
so `C_07.00` selects the facts of `c_07.00.a` and `c_07.00.b`.
- `write_xbrl_xml` declares only the prefixes the instance uses, writes a
fact repeated in several xBRL-CSV tables once, and gives numeric facts
without a unit `xbrli:pure`.
### Fixed
- Unused contexts and units (2.7, 2.22) are listed in document order.
- `write_xbrl_xml` references the module's `.xsd` entry point when the
report extends its `.json` one: the one the taxonomy packages list, or by
the EBA's naming with an `EntryPointWarning`.
- The error for a damaged ZIP uses the `name=` given to `read_report`.
- `write_xbrl_csv` declares the Report Packages 1.0 type
`https://xbrl.org/report-package/2023` that EBA packages use; the
`/2023/xbrl-csv` it wrote before is not a defined type, and other tools
reject it. The reader accepts `/2023` and `/2023/xbr`, and reads the old
value with a warning.
- A DOCTYPE in a UTF-16 instance without a byte order mark is rejected, and
such an instance breaks filing rule 1.4.
- Every unreadable input raises `ReportError`: unknown or multi-byte XML
encodings, unreadable files, ZIP entries with an unsupported compression
method or version, damaged report and taxonomy package archives, JSON
nested too deeply and a malformed `propertiesFrom`. URLs that cannot be
parsed are unresolved rather than raising `ValueError`, and
`ReportError` subclasses pickle with their `problems`.
- A `refPeriod` must be a `YYYY-MM-DD` or `YYYYMMDD` date: week dates and
mixed or non-ASCII digits are rejected. An XML period must start with a
`YYYY-MM-DD` date, as `xs:date` requires.
- Directory packages reject linked directories that leave the package, and
re-check each file when it is opened.
- xBRL-CSV drops exact copies of a fact whose key already had a conflicting
value; XML finds duplicate facts that share a fact id or have none.
- `write_xbrl_xml` rejects invalid qnames, namespace prefixes and characters
XML cannot represent, and keeps carriage returns.
- `read_xbrl_csv_frames` rejects repeated and empty table columns, and an
unreadable header raises `ReportError`.
- Caller-built filing findings with any `xBRL-CSV` rule name can be sorted.
### Performance
- Reading 7.3 million CSV facts takes 33 s and 5.0 GB instead of 55 s and
7.4 GB: the cyclic garbage collector is paused (process-wide) while facts
are built, and facts share empty mappings, datapoint codes and XML context
dimensions.
## 0.1.1 - 2026-10-23
### Performance
- XML elements that declare no namespace share their parent's scope.
## 0.1.0 - 2026-10-23
First release on PyPI.
[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
# Benchmarks
dpm-xbrl compared with the Python packages used for the same job:
eba-xbridge, Arelle, py-xbrl and brel-xbrl. Their scope differs, so the
capabilities come first; the timings cover only tasks several tools do.
## Capabilities
| | dpm-xbrl | eba-xbridge | Arelle | py-xbrl | brel-xbrl |
| --- | --- | --- | --- | --- | --- |
| Licence | MIT | Apache-2.0 | Apache-2.0 | GPL-3.0 | Apache-2.0 |
| Runtime dependencies | none | lxml, numpy, pandas | 15 | 2 | 17 |
| Reads EBA XBRL-XML | yes | yes | with the taxonomy | no | no |
| Reads EBA xBRL-CSV | yes | no | with the taxonomy | no | no |
| Works without the taxonomy | yes | yes | no | no | no |
| Millions of facts in seconds | yes (`frames`) | no | no | no | no |
| Converts XML → xBRL-CSV | yes, EBA layout | yes, EBA layout | generic OIM (plugin) | no | no |
| Converts xBRL-CSV → XML | yes | no | generic OIM (plugin) | no | no |
| EBA filing rules, with rule numbers | yes, while reading | yes | older plugin | no | no |
| CLI with JSON and CI exit status | yes | JSON | yes | no | no |
| *Outside dpm-xbrl's scope:* | | | | | |
| Taxonomy validation (schemas, formulas) | no | no | yes | no | no |
| EBA validation rules on values | via [dpm-xl](https://luukhopman.github.io/dpm-xl/) | no | yes, slowly | no | no |
| Other filings (SEC, ESEF) | no | no | yes | yes | yes |
py-xbrl cannot find the DPM 2.0 taxonomy, and brel-xbrl downloads it and
runs out of memory, so neither reads EBA reports today.
## Timings
Measured on 2026-10-24: AMD Ryzen 7 4700U with Radeon Graphics, 8 cores, 15 GB, Linux 7.0.0-38-generic; load average when measured 2.4, 2.7, 3.0. dpm-xbrl at commit `088ceef`.
| Tool | Version | Python |
| --- | --- | --- |
| Arelle | 2.47.2 | 3.12.3 |
| dpm-xbrl | 1.0.0 | 3.12.3 |
| eba-xbridge | 2.2.1 | 3.12.3 |
| Task | dpm-xbrl | eba-xbridge | Arelle |
| --- | --- | --- | --- |
| Import the package | 0.09 s, 24 MB | 0.32 s, 71 MB | 0.46 s, 63 MB |
| Read `EBA-COREP_OF.xbrl` (22.9 MB, 21,314 facts) | 1.27 s, 82 MB | 3.56 s, 461 MB | 92.07 s, 6,053 MB (1 run) |
| Read `EBA-FINREP9.xbrl` (13.3 MB, 13,384 facts) | 0.81 s, 61 MB | 2.06 s, 302 MB | 68.00 s, 4,396 MB (1 run) |
| Read `EBA-COREP_OF.csv.zip` (25,677 facts) | 0.23 s, 37 MB | cannot read xBRL-CSV | 115.25 s, 6,147 MB (1 run) |
| Read `EBA-COREP_OF.csv.zip` with the taxonomy's metadata resolved | 1.27 s, 161 MB | cannot read xBRL-CSV | n/a |
| Read `EBA-FINREP9.csv.zip` (14,148 facts) | 0.16 s, 30 MB | cannot read xBRL-CSV | not measured |
| EBA filing-rule check, `EBA-COREP_OF.xbrl` | 1.28 s, 82 MB | 18.29 s, 1,124 MB | not measured |
| EBA filing-rule check, `EBA-COREP_OF.csv.zip` | 0.22 s, 37 MB | 1.51 s, 153 MB | not measured |
| Read COREP_LE, 7.3 million facts (`frames` for dpm-xbrl) | 0.90 s, 941 MB | 23.07 s, 2,261 MB | not attempted |
| Read COREP_LE, 7.3 million facts, one object per fact | 44.58 s, 4,831 MB (1 run) | see the row above | not attempted |
| Read FINREP9, 7.3 million facts (`frames`) | 1.50 s, 1,213 MB | not measured | not attempted |
| Read COREP_LE, 21.9 million facts in one 587 MB table (`frames`) | 2.15 s, 2,195 MB | not measured | not attempted |
| Convert `EBA-COREP_OF.xbrl` to xBRL-CSV | 2.47 s, 141 MB | 41.69 s, 499 MB | n/a |
| Convert `EBA-FINREP9.xbrl` to xBRL-CSV | 1.47 s, 91 MB | 27.67 s, 324 MB | n/a |
- Times include starting Python. Arelle runs load the EBA 4.2 taxonomy packages as part of reading, and count the filing indicators as facts.
- dpm-xbrl checks the filing rules while it reads, so its filing-rule rows are read times; eba-xbridge runs `validate(eba=True)`.
- eba-xbridge cannot read xBRL-CSV; its time on the 7.3-million-fact package is its `validate(eba=True)`.
- dpm-xbrl converts with the EBA 4.2 taxonomy packages (`write_xbrl_csv(..., taxonomy_packages=...)`), including opening them.
Sample reports read (109 files: the EBA samples and three synthetic packages):
- dpm-xbrl: 109/109
- eba-xbridge: loaded 52/52 XML, validated 109/109
- dpm-xbrl and eba-xbridge read the same facts from every EBA XML sample;
Arelle also counts filing indicators as facts.
- Arelle needs the taxonomy to load any report, which makes it slow and
large here; in return it validates against the taxonomy.
- Converted packages match the EBA's own xBRL-CSV samples table for table
on all 53 official sample pairs.
## Reproduce
Set up `benchmarks/.venvs` as in
[`benchmarks/README.md`](https://github.com/luukhopman/dpm-xbrl/blob/main/benchmarks/README.md),
then run `uv run python benchmarks/run.py --samples SAMPLES --taxonomy
TAXONOMY` and `python benchmarks/render.py`.
[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
# Stability and versioning
dpm-xbrl follows [semantic versioning][semver]. A major version may break
the public API; minor and patch versions do not. The
[changelog](changelog.md) lists every change, with breaking changes marked.
## The public API
- The names in the `__all__` of `dpmxbrl`, `dpmxbrl.filing` (including
`FilingLog` and `Rule`, with which callers add their own findings, and
`RULE_TITLES`) and `dpmxbrl.frames` (including the `on_metadata` and
`on_table` callbacks).
- The fields of `Report`, `Fact`, `FilingFinding` and `Limits`, and the
attributes of `ReportFrames`.
- The `ReportError` contract: every unreadable or unsafe input raises
`ReportError` (or its subclass `MetadataTablesError`), a `ValueError`.
- The CLI: its commands, options, exit statuses and the `--json` layout,
versioned by `schema_version`.
- Filing rule numbers (`FilingFinding.rule`) and tags (`FilingFinding.code`).
Modules whose names start with an underscore are internal, as are names
not in an `__all__`. `dpmxbrl.__version__` gives the installed version.
## What may change in a minor release
- New functions, parameters with defaults, fields with defaults, CLI
options, JSON fields and filing checks. A new filing check can add
findings to a report that had none, so a pipeline with `--fail-on` may
start failing on a report it accepted before; read the changelog before
upgrading such a pipeline.
- The wording of warnings, filing-finding messages and error messages.
Match on rule numbers and tags, never on messages.
- Performance characteristics.
## Python versions
dpm-xbrl supports Python 3.11 to 3.15. Support for a Python version is
dropped only in a minor release, after its end of life.
## Dependencies
The core package uses only the standard library and will stay so. The
optional extras (`fast` for isal, `frames` for polars) pin a major version
range.
[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 policy
dpm-xbrl reads files from outside the trust boundary, so security bugs in
it matter. [The security model](../concepts/security.md) describes what it
protects against.
## Supported versions
Security fixes are released for the latest minor version of the latest
major version.
## Reporting a vulnerability
Please report vulnerabilities privately, not in a public issue: use
[GitHub's private vulnerability reporting](https://github.com/luukhopman/dpm-xbrl/security/advisories/new)
for the repository, or e-mail github@luukhopman.nl. Include the dpm-xbrl
and Python versions, and, if you can, a small input that shows the problem.
The fix is released as a patch version and described in the changelog.
## What counts
- Reading a report raises anything other than `ReportError`, hangs, or uses
memory or time far beyond what its size and the `Limits` allow.
- Reading a report reads or writes files outside the input, makes a network
request, or resolves an XML entity.
- A written report differs from the `Report` it was written from in a way
that changes its meaning.
[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
# Contributing
Bug reports, questions and pull requests are welcome on
[GitHub](https://github.com/luukhopman/dpm-xbrl/issues). For a larger
change, open an issue first to agree on the approach.
## Set up
dpm-xbrl uses [uv](https://docs.astral.sh/uv/):
```text
git clone https://github.com/luukhopman/dpm-xbrl
cd dpm-xbrl
uv sync --all-extras
```
## Check a change
```text
uv run pytest # tests, including every docs example
uv run ruff check
uv run ruff format --check
uv run ty check
uv run python scripts/build_docs.py # strict docs build into site/
```
`uv run zensical serve` previews the documentation while you edit it.
## Conventions
- Every behaviour has a test. Fixtures are built in code by
`tests/support.py`; keep them small.
- Public functions and classes have Google-style docstrings; they are the
API reference.
- The core package has no runtime dependencies. Optional ones go in an
extra.
- Keep `__all__`, the docs and the [changelog](changelog.md) in step with
the code. Examples in the docs are run by `tests/test_docs.py`, so show
real output.
- The sample reports in `docs/samples` are generated by
`tests/docs_samples.py`; regenerate them with
`uv run python -m tests.docs_samples` after changing it.
- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/),
such as `fix(csv): reject an empty table header`.
`AGENTS.md` in the repository holds the same information for coding
agents.
[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
# Licence
dpm-xbrl is released under the MIT licence:
```text
MIT License
Copyright (c) 2026 Luuk Hopman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
dpm-xbrl is an independent project, not affiliated with or endorsed by the
European Banking Authority. "EBA" and the names of EBA reporting frameworks
are used only to describe the reports dpm-xbrl reads.
[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