Adding a new jurisdiction
Once the reporting rules for a regime exist in the model, three further changes are needed to run that regime across the DRRDRR Digital Regulatory Reporting. An industry‑developed, machine‑executable interpretation of regulatory rules that produces consistent, transparent and fully traceable reporting outputs from standardised CDM data. test packs. They live in three different places, and all three are required: the first alone produces reports in which every jurisdiction-specific field is empty.
This page walks through all three using ESMAESMA European Securities and Markets Authority – EU‑level regulator responsible for securities markets, including EMIR reporting. MiFIRMiFIR Markets in Financial Instruments Regulation – alongside MiFID II, it forms the EU’s core rulebook for how financial markets operate. RTS 22 as the worked example. For the modelling work that comes first — defining the bodybody The organisation or authority that issues the regulatory or technical document the model is based on e.g. a regulator (CFTC, ESMA) or a standard‑setting organisation (CPMI–IOSCO)., corpuscorpus The regulatory document that a reporting rule is based on., report type and reporting rules — see Create a new regime.
The pipeline you are adding to
A DRRDRR Digital Regulatory Reporting. An industry‑developed, machine‑executable interpretation of regulatory rules that produces consistent, transparent and fully traceable reporting outputs from standardised CDM data. pipeline is a chain of transforms. Each stage's output is the next stage's input, and each is a generated function you can call directly.
| Stage | Transform type | Function (MiFIRMiFIR Markets in Financial Instruments Regulation – alongside MiFID II, it forms the EU’s core rulebook for how financial markets operate. RTS 22) | Output type |
|---|---|---|---|
| Translate | TRANSLATE | Ingest_FpmlRecordKeepingToReportableEvent | ReportableEvent |
| Enrich | ENRICH | Enrich_TransactionReportInstructionTestPackDefault | TransactionReportInstruction |
| Report | REPORT | ESMAMIFIRRTS22ReportFunction | MIFIRTransactionReportRTS22 |
| Projection | PROJECTION | Project_EsmaMifirTradeReportToUnavistaCSV | MIFIRUnavistaCSVDocument |
The input at the head of the chain is an FpMLFpML Financial Products Markup Language record-keeping XMLXML Extensible Markup Language. Text-based format used to store and transport data in a structured way that both humans and machines can read. sample under rosetta-source/src/main/resources/cdm-sample-files/. The directory names there — rates, credit, commodity, equity, fx, etd, events, exotic, custom-scenarios, delegated-reporting, cftc-event-scenarios, pre-enrich — are the test pack IDs.
Summary of the three steps
| Step | What it does | Where |
|---|---|---|
| 1 | Says which test packs run against your report and projection functions | DrrTestPackCreator (Java) |
| 2 | Puts the jurisdiction's data into every FpMLFpML Financial Products Markup Language sample on disk | TestPackModifierMain (Java) |
| 3 | Maps that FpMLFpML Financial Products Markup Language data into CDMCDM Common Domain Model. A standardised, machine-readable and machine-executable blueprint for how financial products are traded and managed across the transaction lifecycle. It is represented as a domain model and distributed in open source. ReportableInformation | ingest-fpml-recordkeeping-reportableinfo-func.rosetta (Rune) |
Steps 2 and 3 are a pair. Step 2 without step 3 puts data in the FpMLFpML Financial Products Markup Language that nothing reads; step 3 without step 2 writes mapping logic that never finds a source node.
Step 1 — Wire the pipeline to test packs
File: tests/src/test/java/com/regnosys/drr/testpack/DrrTestPackCreator.java
Add one method and call it from generatePipelines():
private void generatePipelines() throws IOException {
...
writeEsmaMifirRts22TradeConfig();
...
}
private void writeEsmaMifirRts22TradeConfig() {
writeTradeConfig(
TRADE_COMMON_TEST_PACKS.or(startsWith("delegated-reporting", "etd", "commodity", "rates")),
ESMAMIFIRRTS22ReportFunction.class,
Project_EsmaMifirTradeReportToUnavistaCSV.class);
}
The three arguments are:
- The test pack filter.
TRADE_COMMON_TEST_PACKSis a shared constant coveringcredit,custom-scenarios,equity,eventsandfx;.or(startsWith(...))adds the ones specific to this regime. This is where you decide the scope — if a regulation only applies to rates and credit, name only those and you get expectations only for those. - The report function.
- The projection function, or
nullwhere the regime has no projection. RTS 22 projects its report to Unavista CSVCSV Comma-Separated Values. Simple file format used to store tabular data (like spreadsheets or databases) in plain text.; RTS 1 has no projection, so the same call passesnullin that position:
// with a projection
private void writeEsmaMifirRts22TradeConfig() {
writeTradeConfig(
TRADE_COMMON_TEST_PACKS.or(startsWith("delegated-reporting", "etd", "commodity", "rates")),
ESMAMIFIRRTS22ReportFunction.class,
Project_EsmaMifirTradeReportToUnavistaCSV.class);
}
// without one
private void writeEsmaMifirRts1TradeConfig() {
writeTradeConfig(
TRADE_COMMON_TEST_PACKS.or(startsWith("delegated-reporting", "etd", "commodity", "rates")),
ESMAMIFIRRTS1ReportFunction.class,
null);
}
The difference shows up in what gets generated. Naming a projection produces a projection pipeline, a projection test pack config per test pack, and an expected output file per sample, on top of the report ones. Passing null produces the report side only — RTS 22 has twelve projection configs on master, RTS 1 has none.
Two things that are easy to miss:
writeTradeConfig writes two pipelines per call — the default chain, and a pre-enrich chain using Enrich_TransactionReportInstructionTestPackPreEnrich against the pre-enrich test pack. You do not add a second call for it.
A projection with a non-JSONJSON JavaScript Object Notation. Text-based, language-independent format with key-value pairs (eg Name: Dave). output format must be registered in addXMLAndSchemaMap():
ImmutableMap<Class<?>, PipelineModel.Serialisation.Format> csvTypeToFormatMap =
ImmutableMap.<Class<?>, PipelineModel.Serialisation.Format>builder()
.put(MIFIRUnavistaCSVDocument.class, PipelineModel.Serialisation.Format.CSV_LABELLED)
.build();
Running it regenerates, under rosetta-source/src/main/resources/, one pipeline config per report and projection, one test pack config per pipeline and test pack combination (listing every sample with its input path, output path and assertions), and one output file per sample:
mvn -pl tests -am clean install -Pupdate-expectations -DskipTests
The generated files are committed. The model must compile before this runs.
Step 2 — Put the jurisdiction's data into every sample
Entry point: tests/src/test/java/com/regnosys/drr/dataquality/TestPackModifierMain.java
This is a separate main, run by hand, not part of DrrTestPackCreator. It walks every FpMLFpML Financial Products Markup Language sample and inserts the nodes the new jurisdiction needs — typically a reportingRegime block. Samples are modified in place, and the modified samples are committed; they are then permanently part of the test data.
To add a jurisdiction, add one class under dataquality/modifiers/. There is no registry to update: ClasspathScanningTestPackModifierFactory finds modifiers by scanning the package, so two conventions are load-bearing — the class name must end in Modifier, and it must have a public constructor taking a single ModifierContext. A class that misses either is skipped with a log line.
PartyTradeInformationModifier is the one to copy. It checks whether a regime is already present and inserts it if not:
Node emirReportingRegimeNode = getReportingRegimeNode(xml, partyTradeInformationNode, "EMIR");
if (emirReportingRegimeNode == null) {
LOGGER.info("Missing partyTradeInformation.reportingRegime.name=EMIR for {}", id);
addReportingRegime(xml, partyTradeInformationNode, createEmirReportingRegime(xml));
}
and builds the block it inserts:
private static Node createEmirReportingRegime(XmlDom xml) {
Node reportingRegime = xml.createNode("reportingRegime");
reportingRegime.appendChild(xml.createNode("name", "EMIR"));
reportingRegime.appendChild(createSupervisorRegistration(xml, "ESMA"));
reportingRegime.appendChild(xml.createNode("reportingRole", "ReportingParty"));
reportingRegime.appendChild(xml.createNode("reportingPurpose", "PrimaryEconomicTerms"));
reportingRegime.appendChild(xml.createNode("mandatorilyClearable", "false"));
reportingRegime.appendChild(xml.createNode("exceedsClearingThreshold", "true"));
reportingRegime.appendChild(xml.createNode("entityClassification",
Map.of("entityClassificationScheme",
"http://www.fpml.org/coding-scheme/esma-entity-classification"),
"Financial"));
return reportingRegime;
}
The insertion point matters. FpMLFpML Financial Products Markup Language is sequence-ordered, so addReportingRegime inserts before the first of endUserException, nonStandardTerms, largeSizeTrade, executionType, executionVenueType or confirmationMethod rather than appending — an element appended in the wrong position fails schema validation.
For each sample the runner calls isApplicable, then a raw-string pass and a DOM pass, writing the file only if the content changed:
boolean applicable = modifier.isApplicable(xmlFile, xmlContent, xmlDom);
if (applicable) {
String modified = modifier.modify(xmlFile, xmlContent); // raw-string pass
writeFile(...);
modifier.modify(xmlFile, xmlDom); // DOM pass
writeFile(...);
}
Most modifiers extend BaseModifier — which is applicable to every sample and does nothing — and override only the DOM pass. The XmlDom helper providesIDE Integrated Development Environment. A software application that brings together all the essential tools a developer needs to write, test and debug code in one unified workspace. get and getList by XPath, createNode with attributes and text, and addFirst, addAfter, addLast and getOrCreate for placement. To repair existing nodes rather than insert new ones, see ReportingRegimeEsmaEmirModifier.
Run it from the repository root — the sample path is resolved relative to the working directory. Pass dryRun to see what would change without writing:
java -cp <tests test classpath> com.regnosys.drr.dataquality.TestPackModifierMain dryRun
Per-file failures are caught and logged rather than thrown, so check the log output: a modifier that throws on every sample looks much like one that did nothing.
Step 3 — Map the new FpML block into CDM
File: rosetta-source/src/main/rosetta/ingest-fpml-recordkeeping-reportableinfo-func.rosetta
Step 2 puts a reportingRegime block in the FpMLFpML Financial Products Markup Language. This step turns it into ReportableInformation -> jurisdictionInformation in CDMCDM Common Domain Model. A standardised, machine-readable and machine-executable blueprint for how financial products are traded and managed across the transaction lifecycle. It is represented as a domain model and distributed in open source.. Without it the block sits in the XMLXML Extensible Markup Language. Text-based format used to store and transport data in a structured way that both humans and machines can read. and nothing downstream reads it.
In MapPartyTradeInformationListToReportableJurisdictionInformation, add an alias for the regime:
alias emirEsma:
BuildReportableJurisdictionInformation(
EMIR,
ESMA,
fpmlPartyTradeInformationList,
fpmlPartyList,
fpmlRequestMessageHeader
)
and add it to the set at the end of the function. This second half is easy to forget, because omitting it still compiles and simply produces nothing:
set reportableJurisdictionInformation:
[emirEsma, ukEmirFca, jfsaJfsa, csaCsa, hkmaHkma, cftcCftc, secSec, asicAsic, masMas]
BuildReportableJurisdictionInformation matches on regime name and supervisory bodybody The organisation or authority that issues the regulatory or technical document the model is based on e.g. a regulator (CFTC, ESMA) or a standard‑setting organisation (CPMI–IOSCO)., so the two enum values here must agree with what your step 2 modifier writes into name and supervisorRegistration -> supervisoryBody.
Also check that RegimeNameEnum in regulation-common-enum.rosetta has a value for the regime, and that MapRegimeNameEnum handles it — that function only needs a case where the FpMLFpML Financial Products Markup Language string differs from the enum name, as anything else falls through to to-enum RegimeNameEnum. Where a regime needs per-party fields as well, follow MapReportingRegimeToEMIRPartyInformation and its call site in MapJurisdictionPartyInformation.
Why this step is what makes the fields appear
Reporting rules read jurisdiction data back out through a single function:
func GetTransactionInformationForRegime:
set transactionInformation:
transaction -> reportableInformation -> jurisdictionInformation
then filter
regimeName = regime
and (supervisoryBody = supervisoryBodyIn or supervisoryBodyIn is absent)
then only-element
The RTS 22 rules call it as GetTransactionInformationForRegime(item, MiFIR, ESMA) for waiverIndicator, shortSellingIndicator, otcPostTradeIndicator, isRiskReduced, isSecuritiesFinancing and noPrice. If no jurisdictionInformation entry carries regimeName = MiFIR, the filter matches nothing and all of those fields are absent from the report.
Verifying the result
A partly-wired jurisdiction does not fail loudly. Reports still generate, and fields sourced from the trade itself still populate — so a spot check on one output can look healthy while every regime-specific field is empty.
Check explicitly instead. First, that step 2 reached the samples:
grep -rl "MIFIR" rosetta-source/src/main/resources/cdm-sample-files/ | wc -l
Then, that step 3 carried it through into CDMCDM Common Domain Model. A standardised, machine-readable and machine-executable blueprint for how financial products are traded and managed across the transaction lifecycle. It is represented as a domain model and distributed in open source.. The enrich outputs are the point where jurisdictionInformation first becomes visible, and each entry names its regime:
"regimeName" : {
"value" : "EMIR"
},
"supervisoryBody" : {
"value" : "ESMA"
},
So counting the enrich outputs that carry your regime tells you whether both steps landed:
grep -rl '"value" : "MiFIR"' rosetta-source/src/main/resources/enrich/output/ | wc -l
An established regime returns a count in the low hundreds — one per sample. A regime that returns zero here has not been mapped, and every field that routes through GetTransactionInformationForRegime will be absent from its reports.
To debug a single sample end to end without the test pack machinery, use the example test at examples/src/test/java/org/isda/drr/example/reporting/transaction/MIFIRRTS22TradeWithUnavistaCsvProjectionTest.java.
Optional: reviewing the change in Rosetta
Everything above works from a clone and a terminal. If you would rather see the result in a UI — or want a reviewer to look at it without checking the branch out — you can open the change in RosettaRosetta REGnosys’s proprietary platform used as an execution engine for DRR. as a contribution and inspect the test packs there before it is merged.
This is useful for reviewing rather than for building: the generated reports are shown per sample, so you can see what your new regime actually produces against real trades, and hand a reviewer a link instead of a set of grep commands. It changes nothing about the steps above, and is not a substitute for regenerating expectations locally and committing them.
See the Rosetta Workspace Contribution Guide for how contributions work, and DRR data journey using Rosetta for a walkthrough of the workspace itself.
Checklist
- Add the report and projection functions and reporting rules to the model.
- Add
RegimeNameEnumandSupervisoryBodyEnumvalues if the regime is new. - Step 1 — add a
writeXxxTradeConfig()method toDrrTestPackCreatorand call it fromgeneratePipelines(). Choose the test pack filter deliberately, and register any non-JSONJSON JavaScript Object Notation. Text-based, language-independent format with key-value pairs (eg Name: Dave). projection format inaddXMLAndSchemaMap(). - Step 2 — add a
*Modifierclass that inserts the regime block. RunTestPackModifierMainfrom the repository root, first withdryRun, then for real, and commit the changed samples as their own commit. - Step 3 — add the alias in
MapPartyTradeInformationListToReportableJurisdictionInformationand add it to thesetlist. Extend the party-information mapping if the regime needs it. - Rebuild the model, regenerate expectations with
-Pupdate-expectations, and commit the regenerated configs, outputs and assertion counts. - Verify as above.