# Thu, 20 Aug 2026 13:11:52 GMT
---
version: 44
data:


- type: Credential
  attributes:
    displayName: XDM Lookup Table Credential
    description: Credential for XDM Lookup Table
    user: sa
    password: ""


- type: MappingTableContainer
  attributes:
    displayName: XDM Lookup Table
    description: Mapping table to use the xdm lookup tables
    variableName: lookupTable
    credential: XDM Lookup Table Credential


- type: AttributeGenerator
  attributes:
    displayName: ubsBankIdentifierCodeGenerator
    description: |
          Generates a BIC/SWIFT code (ISO 9362) based on the specified seed value.
          The country parameter specifies the country/region of origin ('AT', 'CH', 'DE', 'NL').
          Only a single country value can be specified (e.g. 'DE').
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        def TABLE_NAME = 'BANKS'
        def countMap = [:]
        def totalCount = null
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            countMap = mappingTableUtils.count(TABLE_NAME, "KEY_01")
            totalCount = mappingTableUtils.count(TABLE_NAME)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            def result
            if (countryValue != null) {
                def count = countMap.get(new CompositeKey(countryValue))
                if (count == null || count <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "'.")
                }
                def rowNumber = Math.floorMod(seedHash, count) + 1
                result = mappingTableUtils.get(TABLE_NAME, rowNumber, countryValue)
            } else {
                if (totalCount == null || totalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table " + TABLE_NAME)
                }
                def globalRowNumber = Math.floorMod(seedHash, totalCount) + 1
                result = resolveRowWithoutCountry(globalRowNumber)
            }
            if (result == null) {
                throw new IllegalArgumentException("Failed to retrieve data from table " + TABLE_NAME)
            }
            return result[2]
        }
        
        private def resolveRowWithoutCountry(def globalRowNumber) {
            int offset = 0
            for (entry in countMap.entrySet().sort { e -> e.key.getData()[0].toString() }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(TABLE_NAME, localRowNumber, keyData[0])
                }
                offset += count
            }
            return null
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["bic", "swift", "bankidentifiercode", "bank_identifier_code", "swiftcode", "swift_code"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsBankNameGenerator
    description: |
          Generates a bank name based on the specified seed value.
          The country parameter specifies the country/region of origin ('AT', 'CH', 'DE', 'NL').
          Only a single country value can be specified (e.g. 'DE').
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        def TABLE_NAME = 'BANKS'
        def countMap = [:]
        def totalCount = null
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            countMap = mappingTableUtils.count(TABLE_NAME, "KEY_01")
            totalCount = mappingTableUtils.count(TABLE_NAME)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            def result
            if (countryValue != null) {
                def count = countMap.get(new CompositeKey(countryValue))
                if (count == null || count <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "'.")
                }
                def rowNumber = Math.floorMod(seedHash, count) + 1
                result = mappingTableUtils.get(TABLE_NAME, rowNumber, countryValue)
            } else {
                if (totalCount == null || totalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table " + TABLE_NAME)
                }
                def globalRowNumber = Math.floorMod(seedHash, totalCount) + 1
                result = resolveRowWithoutCountry(globalRowNumber)
            }
            if (result == null) {
                throw new IllegalArgumentException("Failed to retrieve data from table " + TABLE_NAME)
            }
            return result[1]
        }
        
        private def resolveRowWithoutCountry(def globalRowNumber) {
            int offset = 0
            for (entry in countMap.entrySet().sort { e -> e.key.getData()[0].toString() }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(TABLE_NAME, localRowNumber, keyData[0])
                }
                offset += count
            }
            return null
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["bankname", "bank_name", "bank", "institute"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsCreditCardNumberGenerator
    description: |
          Generates a credit card number while retaining a configurable number of leading digits.
          The retain parameter specifies how many leading digits of the seed to keep.
    scriptLanguage: "GROOVY"
    code: |
        import java.math.BigDecimal
        import java.math.BigInteger
        import java.math.RoundingMode
        
        def retainDigits
        
        void init() {
            retainDigits = parseRetain(parameters.retain)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def value = parameters.seed
            value = value == null ? Integer.toUnsignedLong(new Random().nextInt()).toString() : value.toString()
            if (!(value.isNumber()) || value.toBigDecimal() < 0) {
                value = HashUtils.hashInt(HashUtils.hashStringLikeInt(value))
                value = value.toBigDecimal()
                def shift = Math.pow(10, 16 - (Math.floor(Math.log10(value)) + 1))
                value = (value * shift + (HashUtils.hashInt(HashUtils.hashStringLikeInt(value.toString())) % shift))
            } else {
                value = value.toBigDecimal()
            }
        
            value = Math.floor(value)
            if ((Math.floor(Math.log10(Math.floor(value))) + 1) != 16) {
                if (value == 0) {
                    value = 1.toBigDecimal()
                }
                def shift = Math.pow(10, 16 - (Math.floor(Math.log10(value)) + 1))
                value = (value * shift + (HashUtils.hashInt(HashUtils.hashStringLikeInt(value.toString())) % shift))
            }
        
            def hex100000000 = new BigDecimal("2147483648")
            def l = (Math.floor(Math.log10(value.toBigDecimal().doubleValue())) + 1) as int
        
            if (retainDigits >= l) {
                return value.toPlainString()
            }
        
            def a = value.toBigDecimal()
                    .divide(new BigDecimal("4294967296"), 0, RoundingMode.DOWN)
                    .toBigInteger()
                    .and(new BigInteger("2147483647"))
        
            def b = value.toBigDecimal().toBigInteger().and(new BigInteger("2147483647"))
        
            def m = new BigDecimal(HashUtils.hashInt((a ^ b) as int))
            def n = new BigDecimal(HashUtils.hashInt(m as int))
            def s = m.multiply(hex100000000).add(n)
            s = s.remainder(BigDecimal.TEN.pow(l))
        
            if (retainDigits > 0) {
                def v = value.toBigDecimal()
                def left = v.subtract(v.remainder(BigDecimal.TEN.pow(l - retainDigits)))
                def right = s.remainder(BigDecimal.TEN.pow(l - retainDigits))
                s = left.add(right)
            }
        
            return HashUtils.luhn(s as long).toString()
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["creditcard", "credit_card", "cardnumber", "card_number", "ccnumber", "cc_number", "pan"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        def close() {
        }
        
        private static int parseRetain(Object value) {
            if (value == null) {
                return 0
            }
            int retain = Integer.parseInt(value.toString())
            if (retain < 0 || retain > 15) {
                throw new IllegalArgumentException("Parameter 'retain' must be between 0 and 15.")
            }
            return retain
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsInternationalBankAccountNumberGenerator
    description: |
          Generates an IBAN based on the specified seed value.
          The optional country parameter specifies the country/region of origin ('AT', 'CH', 'DE', 'NL').
          If no country is provided, one supported country with available bank data is chosen automatically.
          Only a single country value can be specified (e.g. 'DE').
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        def TABLE_NAME = 'BANKS'
        def SUPPORTED_COUNTRIES = ["DE", "AT", "CH", "NL"]
        def countMap = [:]
        def availableCountries = []
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            countMap = mappingTableUtils.count(TABLE_NAME, "KEY_01")
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
            availableCountries = SUPPORTED_COUNTRIES.findAll { countryCode ->
                def count = countMap.get(new CompositeKey(countryCode))
                count != null && count > 0
            }
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            if (countryValue == null) {
                if (availableCountries.isEmpty()) {
                    throw new IllegalArgumentException("No supported country entries available in table " + TABLE_NAME)
                }
                countryValue = availableCountries.get(random.nextInt(availableCountries.size()))
            }
            def count = countMap.get(new CompositeKey(countryValue))
            if (count == null || count <= 0) {
                throw new IllegalArgumentException("No entries for country ='" + countryValue + "'.")
            }
            def rowNumber = Math.floorMod(seedHash, count) + 1
            def result = mappingTableUtils.get(TABLE_NAME, rowNumber, countryValue)
            if (result == null) {
                throw new IllegalArgumentException("Failed to retrieve data from table " + TABLE_NAME)
            }
            def blz = result[0]
            def knr = Integer.toUnsignedLong(seedHash)
            def blzknr, length, s = ""
        
            switch (countryValue) {
                case "DE":
                    length = 22
                    blzknr = blz.toString().padLeft(8, '0') + knr.toString().padLeft(10, '0')
                    break
                case "AT":
                    length = 20
                    blzknr = blz.toString().padLeft(5, '0') + knr.toString().padLeft(11, '0')
                    break
                case "CH":
                    length = 21
                    blzknr = blz.toString().padLeft(5, '0') + knr.toString().padLeft(12, '0')
                    break
                case "NL":
                    length = 18
                    blzknr = blz.toString().padLeft(4, '0') + knr.toString().padLeft(10, '0')
                    break
                default:
                    throw new IllegalArgumentException("Unsupported country")
            }
            blzknr = blzknr + countryValue + "00"
            for (int i = 0; i < length; i++) {
                def c = blzknr.charAt(i)
                if (c >= '0' && c <= '9') {
                    s += c
                } else {
                    s += ((int) c - 55).toString()
                }
            }
            def t = new java.math.BigDecimal(s).remainder(new java.math.BigDecimal(97))
            def code = 98 - (t % 97)
            return countryValue + code.toString().padLeft(2, '0') + s.substring(0, length - 4)
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["iban", "internationalbankaccountnumber", "international_bank_account_number", "accountnumber", "bankaccount"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsNationalBankIdentifierCodeGenerator
    description: |
          Generates a national bank identifier code (AT/DE: Bankleitzahl, CH: Clearing number) based on the specified seed value.
          The country parameter specifies the country/region of origin ('AT', 'CH', 'DE', 'NL').
          Only a single country value can be specified (e.g. 'DE').
          *This generator requires the lookup tables to be installed.*
    scriptLanguage: "GROOVY"
    code: |
        def TABLE_NAME = 'BANKS'
        def countMap = [:]
        def totalCount = null
        def mappingTableUtils = null
        def seedVal
        def random
        
        void init() {
            mappingTableUtils = new MappingTableUtils(lookupTable)
            countMap = mappingTableUtils.count(TABLE_NAME, "KEY_01")
            totalCount = mappingTableUtils.count(TABLE_NAME)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            def seedHash = random.nextInt()
            def countryValue = parameters.country != null ? parameters.country.toString() : null
            def result
            if (countryValue != null) {
                def count = countMap.get(new CompositeKey(countryValue))
                if (count == null || count <= 0) {
                    throw new IllegalArgumentException("No entries for country ='" + countryValue + "'.")
                }
                def rowNumber = Math.floorMod(seedHash, count) + 1
                result = mappingTableUtils.get(TABLE_NAME, rowNumber, countryValue)
            } else {
                if (totalCount == null || totalCount <= 0) {
                    throw new IllegalArgumentException("No entries available in table " + TABLE_NAME)
                }
                def globalRowNumber = Math.floorMod(seedHash, totalCount) + 1
                result = resolveRowWithoutCountry(globalRowNumber)
            }
            if (result == null) {
                throw new IllegalArgumentException("Failed to retrieve data from table " + TABLE_NAME)
            }
            return result[0]
        }
        
        private def resolveRowWithoutCountry(def globalRowNumber) {
            int offset = 0
            for (entry in countMap.entrySet().sort { e -> e.key.getData()[0].toString() }) {
                def count = entry.value
                if (count == null || count <= 0) {
                    continue
                }
                if (globalRowNumber <= offset + count) {
                    def localRowNumber = globalRowNumber - offset
                    def keyData = entry.key.getData()
                    return mappingTableUtils.get(TABLE_NAME, localRowNumber, keyData[0])
                }
                offset += count
            }
            return null
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5
            int nameBonus = getFieldNameBonus(attribute.name, ["blz", "bankcode", "nationalbankidentifiercode", "national_bank_identifier_code", "routing", "sortcode"])
            return baseScore + nameBonus
        }
        
        private int getFieldNameBonus(String fieldName, List<String> keywords) {
            if (fieldName == null || fieldName.isEmpty()) {
                return 0
            }
        
            String lowerName = fieldName.toLowerCase()
            for (String keyword : keywords) {
                String lowerKeyword = keyword.toLowerCase()
        
                if (lowerName.equals(lowerKeyword)) {
                    return 85
                }
        
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40
                }
        
                if (lowerName.contains(lowerKeyword)) {
                    return 15
                }
            }
        
            return 0
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                try {
                    return Long.parseLong(value)
                } catch (NumberFormatException e) {
                    return (long) value.hashCode()
                }
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        def close() {
            mappingTableUtils.close()
        }
    mappingTableContainers:
      - "XDM Lookup Table"
    mappingTableAccessMode: READ
