# Fri, 3 Jul 2026 11:20:33 GMT
---
version: 44
data:




- type: AttributeGenerator
  attributes:
    displayName: ubsBooleanGenerator
    description: |
          Generates true or false based on the configured probability.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * Boolean generator
         */
        
        def probabilityTrue
        def seedVal
        def random
        
        void init() {
            probabilityTrue = parseProbability(parameters.trueProbability)
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            return random.nextDouble() < probabilityTrue
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.BOOLEAN) {
                return 0
            }
        
            int baseScore = 10  // Base score for BOOLEAN type
            int nameBonus = getFieldNameBonus(attribute.name, ["flag", "enabled", "disabled", "active", "inactive", "is", "has", "valid", "invalid"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 80  // 90 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 30  // 40 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 5  // 15 total
                }
            }
        
            return 0  // No match
        }
        
        private double parseProbability(def value) {
            if (value == null) {
                return 0.5d
            }
        
            double probability
            if (value instanceof Number) {
                probability = value.doubleValue()
            } else if (value instanceof String) {
                probability = Double.parseDouble(value)
            } else {
                throw new IllegalArgumentException("Unsupported type for parameter 'trueProbability': ${value.getClass().name}")
            }
        
            if (probability > 1d && probability <= 100d) {
                probability = probability / 100d
            }
        
            if (probability < 0d || probability > 1d) {
                throw new IllegalArgumentException("Parameter 'trueProbability' must be within [0, 1] or [0, 100].")
            }
        
            return probability
        }
        
        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}")
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsCategoryGenerator
    description: |
          Generates a value from a predefined value list with optional weights.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * Category generator
         */
        
        def values
        def weights
        def totalWeight
        def seedVal
        def random
        
        void init() {
            values = parseValues(parameters.values)
            if (values.isEmpty()) {
                throw new IllegalArgumentException("Parameter 'values' must contain at least one value.")
            }
        
            weights = parseWeights(parameters.weighting, values.size())
            totalWeight = weights.sum()
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            double point = random.nextDouble() * totalWeight
            double cumulative = 0d
            for (int i = 0; i < values.size(); i++) {
                cumulative += weights[i]
                if (point < cumulative) {
                    return values[i]
                }
            }
            return values[values.size() - 1]
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 1  // Base score for STRING type
            int nameBonus = getFieldNameBonus(attribute.name, ["category", "status", "type", "segment", "level", "kind"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 85  // 86 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40  // 41 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 15  // 16 total
                }
            }
        
            return 0  // No match
        }
        
        private List<String> parseValues(def value) {
            if (value == null) {
                return ["A", "B", "C"]
            }
        
            return value.toString().split(",")
                    .collect { it.trim() }
                    .findAll { !it.isEmpty() }
        }
        
        private List<Double> parseWeights(def value, int expectedSize) {
            if (value == null || value.toString().trim().isEmpty()) {
                return (0..<expectedSize).collect { 1d }
            }
        
            List<Double> parsed = value.toString().split(",")
                    .collect { it.trim() }
                    .findAll { !it.isEmpty() }
                    .collect { Double.parseDouble(it) }
        
            if (parsed.size() != expectedSize) {
                throw new IllegalArgumentException("Parameter 'weighting' must contain the same number of entries as 'values'.")
            }
        
            if (parsed.any { it <= 0d }) {
                throw new IllegalArgumentException("All values in parameter 'weighting' must be greater than 0.")
            }
        
            return parsed
        }
        
        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}")
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsDateGenerator
    description: |
          Generates a date or timestamp between start and end date.
          The output format is configurable via the format parameter.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * Date generator
         */
        
        def startInstant
        def endInstant
        def formatter
        def seedVal
        def random
        
        void init() {
            startInstant = parseInstant(parameters.startDate, "startDate")
            endInstant = parseInstant(parameters.endDate, "endDate")
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        
            if (startInstant == null && endInstant == null) {
                endInstant = java.time.Instant.now()
                startInstant = endInstant.minus(java.time.Duration.ofDays(365))
            } else if (startInstant == null) {
                startInstant = endInstant.minus(java.time.Duration.ofDays(365))
            } else if (endInstant == null) {
                endInstant = startInstant.plus(java.time.Duration.ofDays(365))
            }
        
            if (endInstant.toEpochMilli() < startInstant.toEpochMilli()) {
                throw new IllegalArgumentException("Parameter 'endDate' must be greater than or equal to 'startDate'.")
            }
        
            String outputFormat = parameters.format != null ? parameters.format.toString() : "yyyy-MM-dd"
            formatter = java.time.format.DateTimeFormatter.ofPattern(outputFormat).withZone(java.time.ZoneOffset.UTC)
        }
        
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            long min = startInstant.toEpochMilli()
            long max = endInstant.toEpochMilli()
            long randomMillis = min == max ? min : min + random.nextLong((max - min) + 1)
            return formatter.format(java.time.Instant.ofEpochMilli(randomMillis))
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null) {
                return 0
            }
        
            if (attribute.type != FieldDefinition.FieldType.DATE && attribute.type != FieldDefinition.FieldType.TIMESTAMP) {
                return 0
            }
        
            int baseScore = 10  // Base score for DATE/TIMESTAMP type
            int nameBonus = getFieldNameBonus(attribute.name, ["date", "time", "timestamp", "created", "modified", "updated"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 80  // 90 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 30  // 40 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 5  // 15 total
                }
            }
        
            return 0  // No match
        }
        
        private java.time.Instant parseInstant(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return java.time.Instant.ofEpochMilli(value.longValue())
            }
        
            String text = value.toString().trim()
            try {
                return java.time.Instant.parse(text)
            } catch (Exception ignored) {
            }
        
            try {
                return java.time.LocalDateTime.parse(text).toInstant(java.time.ZoneOffset.UTC)
            } catch (Exception ignored) {
            }
        
            try {
                return java.time.LocalDate.parse(text).atStartOfDay().toInstant(java.time.ZoneOffset.UTC)
            } catch (Exception ignored) {
            }
        
            throw new IllegalArgumentException("Unsupported value for parameter '${parameterName}': ${text}")
        }
        
        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}")
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsNumberGenerator
    description: |
          Generates a random integer value within the configured range.
          The lower bound is configured via minValue and the upper bound via maxValue.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * *****************************************************************************
         * numberGenerator
         * *****************************************************************************
         *
         * Attribute Generator that produces a random integer within the configured
         * min/max range.
         *
         * The following variables are available in this script:
         * =====================================================
         * parameters          : Parameter map configured on the attribute generator.
         * *****************************************************************************
         */
        
        def minVal
        def maxVal
        def seedVal
        
        def defaultMinValue = 0
        def defaultMaxValue = 999999
        
        def rand
        
        void init() {
            minVal = parseInteger(parameters.minValue, "minValue")
            maxVal = parseInteger(parameters.maxValue, "maxValue")
            seedVal = parseLong(parameters.seed, "seed")
        
            minVal = minVal == null ? defaultMinValue : minVal
            maxVal = maxVal == null ? defaultMaxValue : maxVal
        
            rand = seedVal == null ? new Random() : new Random(seedVal)
        
            if (maxVal < minVal) {
                throw new IllegalArgumentException("Parameter 'maxValue' must be greater than or equal to 'minValue'.")
            }
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            if (minVal == maxVal) {
                return minVal
            }
            return rand.nextInt((maxVal - minVal) + 1) + minVal
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.NUMBER) {
                return 0
            }
        
            int baseScore = 5  // Base score for NUMBER type
            int nameBonus = getFieldNameBonus(attribute.name, ["number", "count", "quantity", "value", "amount", "id"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 85  // 90 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||  // CamelCase: "itemNumber" or "userCount"
                        lowerName.startsWith(lowerKeyword) ||  // CamelCase: "numberFormat"
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40  // 45 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 15  // 20 total
                }
            }
        
            return 0  // No match
        }
        
        private Integer parseInteger(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.intValue()
            }
            if (value instanceof String) {
                return Integer.parseInt(value)
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        private Long parseLong(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.longValue()
            }
            if (value instanceof String) {
                return Long.parseLong(value)
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsPhoneNumberGenerator
    description: |
          Generates a synthetic phone number for a country and output format.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * Phone number generator
         */
        
        def country
        def formatType
        def countryPrefix
        def subscriberLength
        def seedVal
        def random
        
        def prefixByCountry = [
                DE: "49",
                CH: "41",
                AT: "43",
                US: "1"
        ]
        
        def lengthByCountry = [
                DE: 10,
                CH: 9,
                AT: 10,
                US: 10
        ]
        
        void init() {
            def configuredCountry = parameters.country
            country = configuredCountry != null ? configuredCountry.toString().toUpperCase() : "DE"
            formatType = parameters.format != null ? parameters.format.toString().toUpperCase() : "E164"
        
            if (country.trim().isEmpty()) {
                throw new IllegalArgumentException("Parameter 'country' must not be empty.")
            }
            if (!prefixByCountry.containsKey(country)) {
                throw new IllegalArgumentException("Unsupported value for parameter 'country': " + country)
            }
            if (!("E164".equals(formatType) || "NATIONAL".equals(formatType))) {
                throw new IllegalArgumentException("Unsupported value for parameter 'format': " + formatType)
            }
        
            countryPrefix = prefixByCountry[country]
            subscriberLength = lengthByCountry[country]
            seedVal = parseLong(parameters.seed, "seed")
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            String number = randomDigits(subscriberLength)
            if ("E164".equals(formatType)) {
                return "+" + countryPrefix + number
            }
            return "0" + number
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5  // Base score for STRING type
            int nameBonus = getFieldNameBonus(attribute.name, ["phone", "telephone", "mobile", "number", "tel"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 85  // 90 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||  // CamelCase: "phoneNumber" or "userPhone"
                        lowerName.startsWith(lowerKeyword) ||  // CamelCase: "phoneCustomer"
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40  // 45 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 15  // 20 total
                }
            }
        
            return 0  // No match
        }
        
        private String randomDigits(int length) {
            StringBuilder builder = new StringBuilder(length)
            for (int i = 0; i < length; i++) {
                builder.append(random.nextInt(10))
            }
            return builder.toString()
        }
        
        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}")
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsTextGenerator
    description: |
          Generates a random text value.
          The generated text can be configured via length, character set, prefix and suffix.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * *****************************************************************************
         * textGenerator
         * *****************************************************************************
         *
         * Attribute Generator that produces a random text value.
         * The generated text is composed of a configurable number of random characters
         * and optional prefix / suffix values.
         *
         * The following variables are available in this script:
         * =====================================================
         * parameters          : Parameter map configured on the attribute generator.
         * *****************************************************************************
         */
        
        def textLength
        def characterSet
        def prefix
        def suffix
        def seedVal
        def random
        
        def defaultLength = 12
        
        void init() {
            textLength = parseInteger(parameters.length, "length")
            if (textLength != null && textLength < 0) {
                throw new IllegalArgumentException("Parameter 'length' must be greater than or equal to 0.")
            }
        
            def configuredCharacterSet = parameters.characterSet
            characterSet = configuredCharacterSet != null ? configuredCharacterSet.toString() : defaultCharacterSet()
            if (characterSet.isEmpty()) {
                throw new IllegalArgumentException("Parameter 'characterSet' must not be empty.")
            }
        
            def configuredPrefix = parameters.prefix
            prefix = configuredPrefix != null ? configuredPrefix.toString() : ""
            suffix = parameters.suffix != null ? parameters.suffix.toString() : ""
            seedVal = parseLong(parameters.seed, "seed")
        
            random = seedVal == null ? new Random() : new Random(seedVal)
        }
        
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            Integer effectiveLength = textLength != null ? textLength : fieldDefinition.getLength()
            effectiveLength = effectiveLength == null ? defaultLength : effectiveLength
            if (effectiveLength < 0) {
                throw new IllegalArgumentException("Generated length must be greater than or equal to 0.")
            }
        
            StringBuilder builder = new StringBuilder(prefix)
            for (int i = 0; i < effectiveLength; i++) {
                int index = random.nextInt(characterSet.length())
                builder.append(characterSet.charAt(index))
            }
            builder.append(suffix)
            return builder.toString()
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 10  // Base score for STRING type
            int nameBonus = getFieldNameBonus(attribute.name, ["text", "description", "comment", "remarks", "note", "content"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 80  // 90 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 30  // 40 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 5  // 15 total
                }
            }
        
            return 0  // No match
        }
        
        private Integer parseInteger(def value, String parameterName) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.intValue()
            }
            if (value instanceof String) {
                return Integer.parseInt(value)
            }
            throw new IllegalArgumentException("Unsupported type for parameter '${parameterName}': ${value.getClass().name}")
        }
        
        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}")
        }
        
        private String defaultCharacterSet() {
            return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ

- type: AttributeGenerator
  attributes:
    displayName: ubsUuidGenerator
    description: |
          Generates a unique UUID. Optional version currently supports v4.
    scriptLanguage: "GROOVY"
    code: |
        /*
         * UUID generator
         */
        
        def version
        
        void init() {
            version = parseVersion(parameters.version)
        }
        
        def generateAttribute(EntityInstance entityInstance, FieldDefinition fieldDefinition) {
            if (version == null || version == 4) {
                return java.util.UUID.randomUUID().toString()
            }
            throw new IllegalArgumentException("Unsupported UUID version: " + version + ". Currently only version 4 is supported.")
        }
        
        int getMatchScore(FieldDefinition attribute, EntityGeneratorDefinition entityGeneratorDefinition, Map parameters) {
            if (attribute == null || attribute.type != FieldDefinition.FieldType.STRING) {
                return 0
            }
        
            int baseScore = 5  // Base score for STRING type
            int nameBonus = getFieldNameBonus(attribute.name, ["uuid", "id", "identifier", "guid"])
        
            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()
        
                // Exact match
                if (lowerName.equals(lowerKeyword)) {
                    return 85  // 90 total
                }
        
                // Strong match: prefix/suffix with underscore or CamelCase boundaries
                if (lowerName.startsWith(lowerKeyword + "_") ||
                        lowerName.endsWith("_" + lowerKeyword) ||
                        lowerName.endsWith(lowerKeyword) ||
                        lowerName.startsWith(lowerKeyword) ||
                        lowerName.contains("_" + lowerKeyword + "_")) {
                    return 40  // 45 total
                }
        
                // Weak match: contains the keyword anywhere
                if (lowerName.contains(lowerKeyword)) {
                    return 15  // 20 total
                }
            }
        
            return 0  // No match
        }
        
        private Integer parseVersion(def value) {
            if (value == null) {
                return null
            }
            if (value instanceof Number) {
                return value.intValue()
            }
            if (value instanceof String) {
                return Integer.parseInt(value)
            }
            throw new IllegalArgumentException("Unsupported type for parameter 'version': ${value.getClass().name}")
        }
    mappingTableContainers: []
    mappingTableAccessMode: READ
