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


- type: ModificationMethod
  attributes:
    displayName: Allowance number
    description: |
          Builds an allowance number (Zulagennummer) based on the following input parameters:
          +
          * number of the insurer (place 1-2)
          * date of birth of the insured person (place 3-8)
          * family name of the insured person (place 9)
          * gender, 00-49 male, 50-99 female (place 10-11)
          * check digit (place 12)
          
          +
          The allowance number is equal to the German social security number and is e.g. important to determine
          the pension income of an employee, to manage health insurance, as well as other issues concerning
          social security. The number is normally assigned to an employee via the first paycheck. Some employees
          like public officials, judges and soldiers have to request their allowance number manually. The allowance
          number is used to determine the increment of the German Riester pension (German subsidized private pension).
    scriptLanguage: "GROOVY"
    legacyScript: false
    inputType: "COLUMN"
    mappingTableContainers: []
    mappingTableAccessMode: READ
    code: |
        /*
         * *****************************************************************************
         * The following variables are available in this script:
         * =====================================================
         * ctx             : A context object that offers a number of methods.
         *                   The method indexOf() can be used to get the index of a
         *                   specific column within the data.
         * data       : The data array. This array contains the contents of all
         *                   columns of the current row of the table that is being
         *                   masked by this method.
         *
         * Additionally for column based modification methods the following two
         * variables are available:
         * columnName      : The name of the column that matched the pattern.
         * columnIndex     : The position of the column that matched the pattern within
         *                   the data array. This value is zero-based.
         * *****************************************************************************
         */
        
        import de.ubs.xdm3.batch.modification.ModificationException
        import de.ubs.xdm3.batch.modification.HashUtils
        
        factors = [2, 1, 2, 5, 7, 1, 2, 1, 2, 1, 2, 1]
        alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
        
        cyrillicInitialMap = [
                'А':'A','Б':'B','В':'V','Г':'G','Д':'D','Е':'E','Ё':'E',
                'Ж':'Z','З':'Z','И':'I','Й':'J','К':'K','Л':'L','М':'M',
                'Н':'N','О':'O','П':'P','Р':'R','С':'S','Т':'T','У':'U',
                'Ф':'F','Х':'H','Ц':'C','Ч':'C','Ш':'S','Щ':'S',
                'Ы':'Y','Э':'E','Ю':'Y','Я':'Y'
        ]
        
        charNormalizationMap = [
                'À':'A','Á':'A','Â':'A','Ã':'A','Ä':'A','Å':'A',
                'à':'A','á':'A','â':'A','ã':'A','ä':'A','å':'A',
                'Æ':'A','æ':'A',
                'Ç':'C','ç':'C',
                'È':'E','É':'E','Ê':'E','Ë':'E',
                'è':'E','é':'E','ê':'E','ë':'E',
                'Ì':'I','Í':'I','Î':'I','Ï':'I',
                'ì':'I','í':'I','î':'I','ï':'I',
                'Ñ':'N','ñ':'N',
                'Ò':'O','Ó':'O','Ô':'O','Õ':'O','Ö':'O','Ø':'O',
                'ò':'O','ó':'O','ô':'O','õ':'O','ö':'O','ø':'O',
                'Ù':'U','Ú':'U','Û':'U','Ü':'U',
                'ù':'U','ú':'U','û':'U','ü':'U',
                'Ý':'Y','ý':'Y','ÿ':'Y'
        ]
        
        def init() {}
        
        def apply() {
            def gender = modificationMethod.gender.toString().trim()
            def lastName = modificationMethod.lastName.toString().trim()
            def birthDate = modificationMethod.birthDate.toString().trim()
            def areaNumber = modificationMethod.areaNumber.toString().trim()
        
            if (birthDate?.length() < 6) {
                birthDate = '0' + birthDate
            }
            if (birthDate?.length() >= 10 && birthDate[4] == '-' && birthDate[7] == '-') {
                def year = birthDate[2..3]
                def month = birthDate[5..6]
                def day = birthDate[8..9]
                birthDate = "${day}${month}${year}"
            }
        
            def firstChar = lastName[0].toString()
        
            if (charNormalizationMap.containsKey(firstChar)) {
                firstChar = charNormalizationMap[firstChar]
            }
        
            def upperChar = firstChar.toUpperCase()
            if (cyrillicInitialMap.containsKey(upperChar)) {
                upperChar = cyrillicInitialMap[upperChar]
            }
        
            firstChar = upperChar
        
            if (!(firstChar in alphabet)) {
                firstChar = 'X'
            }
        
            def firstCharCode = alphabet.indexOf(firstChar) + 1
            if (firstCharCode == 0) {
                throw new ModificationException("First character ${firstChar} not found in alphabet")
            }
        
            if (gender in ['F', 'W', 'M']) {
                def hash = HashUtils.hashInt(HashUtils.hashStringLikeInt(areaNumber + birthDate + firstChar))
                def newGender = hash % 50
                if (gender in ['F', 'W']) {
                    newGender += 50
                }
                gender = newGender.toString().padLeft(2, '0')
            }
            def insurancePolicyNumber = areaNumber + birthDate + firstCharCode.toString().padLeft(2, '0') + gender
            def digits = insurancePolicyNumber.findAll { it.isNumber() }.collect { it.toInteger() }
            def checkSum = digits
                    .withIndex()
                    .collect { digit, i ->
                        def factor = i < factors.size() ? factors[i] : 1  // Fallback falls Index zu lang
                        return digit * factor
                    }
                    .collectMany { it.toString().split('').toList() }
                    .collect { it.toInteger() }
                    .sum() % 10
        
            data[columnIndex] = "${areaNumber}${birthDate}${firstChar}${gender}${checkSum}"
            return true
        }
        
        def close() {}
    parameters:
    - displayName: Area Number
      description: |
          Number of the insurer
      variableName: areaNumber
      dataType: STRING
    - displayName: Birth Date
      description: |
          Birth date in the format ddMMYY or YYYY-MM-dd or as Date
      variableName: birthDate
      dataType: STRING
    - displayName: Last Name
      description: |
          Last name at the time of the application
      variableName: lastName
      dataType: STRING
    - displayName: Gender
      description: |
          00-49 for male, 50-99 for female
      variableName: gender
      dataType: STRING
