Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Priyanka amarnani related party mapping processor[5xx] #3259

Open
wants to merge 13 commits into
base: 5.x.x
Choose a base branch
from
Open
58 changes: 5 additions & 53 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,16 @@
# _Infrastructure - Dependency Update_

_What is being released?_

This release updates the rune dependencies to version `11.24.2`. This update includes support for visualising the `Choice Type` elements in the Rosetta User Interface.

_Review directions_

The changes can be reviewed in PR: [#3255](https://github.com/finos/common-domain-model/pull/3255)

# _Mapping Update - InterestRateForwardDebtPriceMappingProcessor updated to handle 'Percentage' quoteUnits_
# _Mapping Update - Related party role mapper_

_Background_

The price of bond forwards is captured as a monetary value whereas it should be a decimal/percentage. Even if the value in FpML was 'Percentage', the CDM representation value did not accurately represent this, causing misinterpretations.
The Party Role mapping issue involved the incorrect transfer of FpML's relatedParty structure into CDM, particularly in cases where multiple relatedParty elements exist within the same partyTradeInformation block. The mapping process was only capturing the first relatedParty role found, which led to incorrect associations between party references and roles. Furthermore, if the role of the first relatedParty was not found in PartyRoleEnum, another role was incorrectly assigned, causing mismatches and inaccuracies in the data mapping.

_What is being released?_

- An update to the **InterestRateForwardDebtPriceMappingProcessor** code to fix the described issue. This change, would correct the interpretation by dividing the current monetary value by 100, when the *quoteUnits* corresponds to the XML value '*Percentage*'.
- The **bond-fwd-generic-ex01.xml** and **bond-fwd-generic-ex02.xml** samples have been updated as the files were using the value 'Percent' but the correct value according to the enum should be 'Percentage'


_Review directions_

In Rosetta, select the Textual Browser and inspect each of the changes identified above.

The changes can be reviewed in PR: [#3244](https://github.com/finos/common-domain-model/pull/3244)

# _CDM Model - CapacityUnit Enum_

_Background_
In has been seen that in the ExternalUnitOfMeasure1Code from the 2Q2024 ISO External CodeSets v1, the unity Joule is supported in the Enum. However, in CDM this is not the case, as it does not appear anywhere in the CapacityUnitEnum. Therefore, the Joule unit of measure will be added to the CapacityUnitEnum for completeness and to align with 2Q2024 ISO External CodeSets v1, for versions 5 and 6 of CDM.

_What is being released?_

- Updated `CapacityUnitEnum` in cdm.base.math

_Enumerations_

- Updated `CapacityUnitEnum` by adding 'J' to support Joule unit

The changes can be reviewed in PR: [#3197](https://github.com/finos/common-domain-model/pull/3197)

# _Infrastructure - Dependency Update_

_What is being released?_

This release updates the rune dependencies.

Version updates include:
- DSL 9.22.0: handle null for `min` and `max` operations. For further details see DSL release notes: https://github.com/finos/rune-dsl/releases/tag/9.22.0
- FpML Coding Scheme `11.25.1`: support for latest version (v2.20).
- We are introducing a new RelatedPartyRoleMappingProcessor that addresses the limitations of the previous implementation. This processor evaluates all relatedParty elements within a partyTradeInformation block instead of just mapping the first one. It ensures that each relatedParty is independently assessed, verifies its role against the PartyRoleEnum list, and assigns the correct role and reference accordingly. Additionally, if a role is not found in PartyRoleEnum, the processor omits that reference rather than assigning an incorrect role to the relatedParty.

_Review directions_

In Rosetta, select the Textual Browser and inspect changes due to the FpML code scheme update:
- `FloatingRateIndexEnum` has values added:
- `EUR_EuroSTR_ICE_Swap_Rate`
- `IDR_INDONIA`
- `IDR_INDONIA_OIS_Compound`
- `PHP_ORR`
- `USD_SOFR_ICE_Swap_Rate_Spreads`
In Rosetta, select the Textual Browser and inspect each of the changes identified above.

The changes can be reviewed in PR: [#3260](https://github.com/finos/common-domain-model/pull/3260)
Changes can be reviewed in PR: #3259
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package cdm.event.common.processor;

import cdm.base.staticdata.party.PartyRole;
import cdm.base.staticdata.party.PartyRoleEnum;
import cdm.base.staticdata.party.metafields.ReferenceWithMetaParty;
import cdm.event.common.Trade;
import com.regnosys.rosetta.common.translation.Mapping;
import com.regnosys.rosetta.common.translation.MappingContext;
import com.regnosys.rosetta.common.translation.MappingProcessor;
import com.regnosys.rosetta.common.translation.Path;
import com.rosetta.model.lib.RosettaModelObjectBuilder;
import com.rosetta.model.lib.path.RosettaPath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import static com.regnosys.rosetta.common.translation.MappingProcessorUtils.subPath;
import static com.regnosys.rosetta.common.translation.MappingProcessorUtils.updateMappingSuccess;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;

/**
* Created by Tradeheader, SL
*
* @author gopazoTH
* @since 18/09/2024
*/

@SuppressWarnings("unused")
public class RelatedPartyRoleMappingProcessor extends MappingProcessor {

private static final Logger LOGGER = LoggerFactory.getLogger(RelatedPartyRoleMappingProcessor.class);

public RelatedPartyRoleMappingProcessor(RosettaPath modelPath, List<Path> synonymPaths, MappingContext context) {
super(modelPath, synonymPaths, context);
}

@Override
public void map(Path synonymPath, List<? extends RosettaModelObjectBuilder> builder, RosettaModelObjectBuilder parent) {

Collection<PartyRoleMapping> partyRoleMappings = getMappings().stream()
// find all partyTradeInformation.relatedParty mappings
.filter(m -> synonymPath.getParent().nameStartMatches(m.getXmlPath()))
// group by relatedParty path index
.collect(Collectors.groupingBy(this::getKey, collectingAndThen(toList(), this::toPartyRoleMapping)))
.values();

partyRoleMappings.forEach(m -> {
Trade.TradeBuilder tradeBuilder = (Trade.TradeBuilder) parent;

// Check if m.role is non-null before calling fromDisplayName
if (m != null && m.role != null) {
try {
// Normalize the XML value to match the enum constant (convert to uppercase and replace spaces/underscores)
PartyRoleEnum roleEnum = PartyRoleEnum.fromDisplayName(m.role);

// Create the PartyRoleBuilder and add the necessary values
PartyRole.PartyRoleBuilder partyRoleBuilder = PartyRole.builder()
.setRole(roleEnum);

// Check if m.partyReference is non-null before setting it
if (m.partyReference != null) {
partyRoleBuilder.setPartyReference(ReferenceWithMetaParty.builder()
.setExternalReference(m.partyReference));
}

// Check if m.ownershipPartyReference is non-null before setting it
if (m.ownershipPartyReference != null) {
partyRoleBuilder.setOwnershipPartyReference(ReferenceWithMetaParty.builder()
.setExternalReference(m.ownershipPartyReference));
}

// Add the PartyRole to the trade
tradeBuilder.addPartyRole(partyRoleBuilder);

// Update mappings if they are non-null
if (m.partyReferenceMapping != null && m.partyReferenceModelPath != null) {
updateMappingSuccess(m.partyReferenceMapping, m.partyReferenceModelPath);
}
if (m.roleMapping != null && m.roleModelPath != null) {
updateMappingSuccess(m.roleMapping, m.roleModelPath);
}
}
catch (IllegalArgumentException e) {
// If the value is not a valid enum constant, do nothing and skip this iteration
LOGGER.warn("Invalid PartyRoleEnum: " + m.role, e);

}
}


});
}

/**
* Group by path index, e.g. collect all mappings under relatedParty(index)
* (nonpublicExecutionReport.trade.tradeHeader.partyTradeInformation(0).relatedParty(10))
*/
private String getKey(Mapping m) {
int partyTradeInformationIndex = getPathIndex(m, "partyTradeInformation");
int relatedPartyIndex = getPathIndex(m, "relatedParty");
return partyTradeInformationIndex + "-" + relatedPartyIndex;
}

private int getPathIndex(Mapping m, String elementName) {
return subPath(elementName, m.getXmlPath())
.map(Path::getLastElement)
.map(Path.PathElement::forceGetIndex)
.orElse(-1);
}

private PartyRoleMapping toPartyRoleMapping(List<Mapping> mappings) {
try {
if (!mappings.isEmpty()) {
// Get the xmlPath from the first element in the mappings list
String xmlPath = mappings.get(0).getXmlPath().toString();

// Extract the index value from partyTradeInformation(x) in the xmlPath
Matcher matcher = Pattern.compile(".*partyTradeInformation\\((\\d+)\\).*").matcher(xmlPath);

if (matcher.find()) {
String index = matcher.group(1); // This captures the index (x) from partyTradeInformation(x)

// Dynamically build the pattern using the extracted index
Pattern dynamicPattern = Pattern.compile(".*partyTradeInformation\\(" + index + "\\)\\.partyReference.*");

// Apply the dynamic pattern to filter mappings
Mapping ownershipPartyReferenceMapping = getMapping(
getMappings().stream()
.filter(m -> m.getXmlValue() != null && dynamicPattern.matcher(m.getXmlPath().toString()).matches())
.collect(Collectors.toList()),
Path.parse("partyReference.href")
);

// Proceed with your existing mapping logic
RosettaPath ownershipPartyReferenceModelPath = getModelPath().newSubPath("ownershipPartyReference").newSubPath("externalReference");
Mapping partyReferenceMapping = getMapping(mappings.stream().filter(m -> m.getXmlValue() != null).collect(Collectors.toList()), Path.parse("partyReference.href"));
RosettaPath partyReferenceModelPath = getModelPath().newSubPath("partyReference").newSubPath("externalReference");
Mapping roleMapping = getMapping(mappings.stream().filter(m -> m.getXmlValue() != null).collect(Collectors.toList()), Path.parse("role"));
RosettaPath roleModelPath = getModelPath().newSubPath("role");

// Build and return the PartyRoleMapping if the mappings are valid
if (partyReferenceMapping != null && partyReferenceMapping.getXmlValue() != null
&& roleMapping != null && roleMapping.getXmlValue() != null) { return new PartyRoleMapping(
String.valueOf(ownershipPartyReferenceMapping.getXmlValue()),
ownershipPartyReferenceMapping,
ownershipPartyReferenceModelPath,
String.valueOf(partyReferenceMapping.getXmlValue()),
partyReferenceMapping,
partyReferenceModelPath,
String.valueOf(roleMapping.getXmlValue()),
roleMapping,
roleModelPath
);
}
}
}
} catch (Exception e) {
LOGGER.error("Failed to build party role mapping from mappings {}", mappings, e);
}
return null;
}


private Mapping getMapping(List<Mapping> mappings, Path endsWith) {
return mappings.stream().filter(m -> m.getXmlPath().endsWith(endsWith)).findFirst().orElse(null);
}

private static class PartyRoleMapping {

private final String ownershipPartyReference;
private final Mapping ownershipPartyReferenceMapping;
private final RosettaPath ownershipPartyReferenceModelPath;
private final String partyReference;
private final Mapping partyReferenceMapping;
private final RosettaPath partyReferenceModelPath;
private final String role;
private final Mapping roleMapping;
private final RosettaPath roleModelPath;

public PartyRoleMapping(String ownershipPartyReference, Mapping ownershipPartyReferenceMapping, RosettaPath ownershipPartyReferenceModelPath,String partyReference, Mapping partyReferenceMapping, RosettaPath partyReferenceModelPath, String role, Mapping roleMapping, RosettaPath roleModelPath) {
this.ownershipPartyReference = ownershipPartyReference;
this.ownershipPartyReferenceMapping = ownershipPartyReferenceMapping;
this.ownershipPartyReferenceModelPath = ownershipPartyReferenceModelPath;
this.partyReference = partyReference;
this.partyReferenceMapping = partyReferenceMapping;
this.partyReferenceModelPath = partyReferenceModelPath;
this.role = role;
this.roleMapping = roleMapping;
this.roleModelPath = roleModelPath;
}
}
}
Loading
Loading