{
  "storage": {},
  "autoload": {
    "config": {
      "s3": {
        "AWS_ACCESS_KEY_ID": "::getENV",
        "AWS_SECRET_ACCESS_KEY": "::getENV",
        "AWS_BUCKET_NAME": "::getENV"
      },
      "files": {
        "creditfairadmin": {
          "bureau": {
            "type": "s3",
            "misc": [
              {
                "applicant_type": 1,
                "file": "{identifier}/{identifier}_response_experian_soft_1.json",
                "type": "experian_soft_pull"
              },
              {
                "applicant_type": 2,
                "file": "{identifier}/{identifier}_response_experian_soft_2.json",
                "type": "experian_soft_pull"
              },
              {
                "applicant_type": 1,
                "file": "{identifier}/{identifier}_response_experian.json",
                "type": "experian_hard_pull"
              },
              {
                "applicant_type": 2,
                "file": "{identifier}/{identifier}_response_2_experian.json",
                "type": "experian_hard_pull"
              },
              {
                "file": "{identifier}/{identifier}_response.json",
                "type": "equifax_hard_pull",
                "applicant_type": 1
              },
              {
                "file": "{identifier}/{identifier}_response_2.json",
                "type": "equifax_hard_pull",
                "applicant_type": 2
              }
            ]
          },
          "finbit": {
            "type": "s3",
            "misc": [{
              "file": "{identifier}/{identifier}_finbit.json",
              "type": "finbit"
            }]
          },
          "pirimid": {
            "type": "s3",
            "misc": [
              {
                "file": "consumer/{identifier}/pirimid_analysis_json{pirimid_tracking_id}.json",
                "type": "pirimid_analysis_json"
              }
            ]
        },
        "account_aggregator": {
          "type": "s3",
          "misc": [
            {
              "file": "{identifier}/{account_aggregator_id}_account_aggregator.json",
              "type": "account_aggregator_json"
            }
          ]
          }
        },
        "pre_eligibility": {
          "bureau": {
            "type": "s3",
            "misc": [
              {
                "applicant_type": 1,
                "file": "pre_eligibility/{identifier}/{identifier}_response_experian_soft_1.json",
                "type": "experian_soft_pull"
              },
              {
                "applicant_type": 2,
                "file": "pre_eligibility/{identifier}/{identifier}_response_experian_soft_2.json",
                "type": "experian_soft_pull"
              },
              {
                "applicant_type": 1,
                "file": "pre_eligibility/{identifier}/{identifier}_response_experian.json",
                "type": "experian_hard_pull"
              },
              {
                "applicant_type": 2,
                "file": "pre_eligibility/{identifier}/{identifier}_response_2_experian.json",
                "type": "experian_hard_pull"
              },
              {
                "file": "pre_eligibility/{identifier}/{identifier}_response.json",
                "type": "equifax_hard_pull",
                "applicant_type": 1
              },
              {
                "file": "pre_eligibility/{identifier}/{identifier}_response_2.json",
                "type": "equifax_hard_pull",
                "applicant_type": 2
              }
            ]
          }
        }
      },
      "dataConfig" : {
            "term_loans" : ["Auto Loan", "Housing Loan", "Personal Loan", "Consumer Loan", "Education Loan", "Loan to Professional", "Two-wheeler Loan", "Commercial Vehicle Loan", "Used Car Loan",
                      "Business Loan-Priority Sector-Small Business", "Business Loan - Priority Sector- Agriculture", "Business Loan - Priority Sector- Others", "Business Non-Funded Credit Facility",
                      "Business Non-Funded Credit Facility - Priority", "Staff Loan"],
        "experian_ownership": {
          "Individual": "1",
          "Joint": "2",
          "Joint Account": "2",
          "Authorized User": "3",
          "Authorized user": "3",
          "Guarantor": "7",
          "Deceased": "20"
        },
        "equifax_dpd": {
            "NEW": 0,
            "90+": 90,
            "120+": 120,
            "180+": 180
        },
        "account_status_mappings": {
          "experian": {"date_fields": ["Date_Closed", "Date_of_Last_Payment", "Date_Reported"],
            "value_field": "Account_Status"},
          "equifax": {"date_fields": ["sch:DateClosed", "sch:LastPaymentDate", "sch:DateReported"],
            "value_field": "sch:AccountStatus"}
        },
        "account_status": {"00": "No Suit Filed",
                     "89": "Wilful default",
                     "93": "Suit Filed(Wilful default)",
                     "97": "Suit Filed(Wilful Default) and Written-off",
                     "30": "Restructured",
                     "31": "Restructured Loan (Govt. Mandated)",
                     "32": "Settled",
                     "33": "Post (WO) Settled",
                     "34": "Account Sold",
                     "35": "Written Off and Account Sold",
                     "36": "Account Purchased",
                     "37": "Account Purchased and Written Off",
                     "38": "Account Purchased and Settled",
                     "39": "Account Purchased and Restructured",
                     "40": "Status Cleared",
                     "41": "Restructured Loan",
                     "42": "Restructured Loan (Govt. Mandated)", "43": "Written-off", "44": "Settled",
                     "45": "Post (WO) Settled", "46": "Account Sold", "47": "Written Off and Account Sold",
                     "48": "Account Purchased", "49": "Account Purchased and Written Off",
                     "50": "Account Purchased and Settled", "51": "Account Purchased and Restructured",
                     "52": "Status Cleared", "53": "Suit Filed", "54": "Suit Filed and Written-off",
                     "55": "Suit Filed and Settled", "56": "Suit Filed and Post (WO) Settled",
                     "57": "Suit Filed and Account Sold", "58": "Suit Filed and Written Off and Account Sold",
                     "59": "Suit Filed and Account Purchased", "60": "Suit Filed and Account Purchased and Written Off",
                     "61": "Suit Filed and Account Purchased and Settled",
                     "62": "Suit Filed and Account Purchased and Restructured", "63": "Suit Filed and Status Cleared",
                     "64": "Wilful Default and Restructured Loan",
                     "65": "Wilful Default and Restructured Loan (Govt. Mandated)", "66": "Wilful Default and Settled",
                     "67": "Wilful Default and Post (WO) Settled", "68": "Wilful Default and Account Sold",
                     "69": "Wilful Default and Written Off and Account Sold", "70": "Wilful Default and Account Purchased",
                     "72": "Wilful Default and Account Purchased and Written Off",
                     "73": "Wilful Default and Account Purchased and Settled",
                     "74": "Wilful Default and Account Purchased and Restructured",
                     "75": "Wilful Default and Status Cleared", "76": "Suit filed (Wilful default) and Restructured",
                     "77": "Suit filed (Wilful default) and Restructured Loan (Govt. Mandated)",
                     "79": "Suit filed (Wilful default) and Settled",
                     "81": "Suit filed (Wilful default) and Post (WO) Settled",
                     "85": "Suit filed (Wilful default) and Account Sold",
                     "86": "Suit filed (Wilful default) and Written Off and Account Sold",
                     "87": "Suit filed (Wilful default) and Account Purchased",
                     "88": "Suit filed (Wilful default) and Account Purchased and Written Off",
                     "94": "Suit filed (Wilful default) and Account Purchased and Settled",
                     "90": "Suit filed (Wilful default) and Account Purchased and Restructured",
                     "91": "Suit filed (Wilful default) and Status Cleared", "13": "CLOSED", "14": "CLOSED", "15": "CLOSED",
                     "16": "CLOSED", "17": "CLOSED", "12": "CLOSED", "11": "ACTIVE", "71": "ACTIVE", "78": "ACTIVE", "80": "ACTIVE",
                     "82": "ACTIVE", "83": "ACTIVE", "84": "ACTIVE", "21": "ACTIVE", "22": "ACTIVE", "23": "ACTIVE", "24": "ACTIVE",
                     "25": "ACTIVE", "131": "Restructured due to natural calamity", "130": "Restructured due to COVID-19"},
        "account_type": {
    "1": "Auto Loan",
    "01": "Auto Loan",
    "2": "Housing Loan",
    "02": "Housing Loan",
    "3": "Property Loan",
    "03": "Property Loan",
    "4": "LOAN AGAINST SHARES/SECURITIES",
    "04": "LOAN AGAINST SHARES/SECURITIES",
    "5": "Personal Loan",
    "05": "Personal Loan",
    "6": "Consumer Loan",
    "06": "Consumer Loan",
    "7": "Gold Loan",
    "07": "Gold Loan",
    "8": "EDUCATIONAL LOAN",
    "08": "EDUCATIONAL LOAN",
    "9": "LOAN TO PROFESSIONAL",
    "09": "LOAN TO PROFESSIONAL",
    "10": "Credit Card",
    "11": "LEASING",
    "12": "OVERDRAFT",
    "13": "TWO-WHEELER LOAN",
    "14": "NON-FUNDED CREDIT FACILITY",
    "15": "LOAN AGAINST BANK DEPOSITS",
    "16": "FLEET CARD",
    "17": "Commercial Vehicle Loan",
    "18": "Telco – Wireless",
    "19": "Telco – Broadband",
    "20": "Telco – Landline",
    "23": "GECL Secured",
    "24": "GECL Unsecured",
    "31": "Secured Credit Card",
    "32": "Used Car Loan",
    "33": "Construction Equipment Loan",
    "34": "Tractor Loan",
    "35": "Corporate Credit Card",
    "36": "Kisan Credit Card",
    "37": "Loan on Credit Card",
    "38": "Prime Minister Jaan Dhan Yojana - Overdraft",
    "39": "Mudra Loans – Shishu / Kishor / Tarun",
    "40": "Microfinance – Business Loan",
    "41": "Microfinance – Personal Loan",
    "42": "Microfinance – Housing Loan",
    "43": "Microfinance – Others",
    "44": "Pradhan Mantri Awas Yojana - Credit Link Subsidy Scheme MAY CLSS",
    "45": "P2P Personal Loan",
    "46": "P2P Auto Loan",
    "47": "P2P Education Loan",
    "51": "BUSINESS LOAN – GENERAL",
    "52": "BUSINESS LOAN –PRIORITY SECTOR – SMALL BUSINESS",
    "53": "BUSINESS LOAN –PRIORITY SECTOR – AGRICULTURE",
    "54": "BUSINESS LOAN –PRIORITY SECTOR – OTHERS",
    "55": "BUSINESS NON-FUNDED CREDIT FACILITY – GENERAL",
    "56": "BUSINESS NON-FUNDED CREDIT FACILITY – PRIORITY SECTOR – SMALL BUSINESS",
    "57": "BUSINESS NON-FUNDED CREDIT FACILITY – PRIORITY SECTOR – AGRICULTURE",
    "58": "BUSINESS NON-FUNDED CREDIT FACILITY – PRIORITY SECTOR – OTHERS",
    "59": "BUSINESS LOANS AGAINST BANK DEPOSITS",
    "60": "Staff Loan",
    "61": "Business Loan - Unsecured",
    "00": "Others",
    "50": "Business Loan - Secured",
    "69": "Short Term Personal Loan [Unsecured]",
    "70": "Priority Sector Gold Loan [Secured]",
    "71": "Temporary Overdraft [Unsecured]"
},
        "surrogate_account_mappings" : {
          "Home Loan": {"experian": ["2", "02", "42"], "equifax": ["Housing Loan", "Home Loan", "Microfinance – Housing Loan", "MicroFinance Housing Loan"]},
          "Business Loan": {"experian":["51", "61", "54", "40"], "equifax": ["Business Loan – General", "Business Loan", "Business Loan - Unsecured", "Business Loan –priority Sector – Small Business", "Business Loan - Priority Sector- Small Business", "Business Loan –priority Sector – Others", "Business Loan - Priority Sector- Others", "Microfinance – Business Loan", "Business Loan - Secured", "MicroFinance Business Loan"]},
          "Auto Loan": {"experian":["1", "01", "32", "46"], "equifax": ["Auto Loan", "Used Car Loan", "P2p Auto Loan"]}
        }
      }
    }
  },
  "functions": {
    "lang": [
      {
        "python": [
          {
            "dateDiffInYear": "from datetime import datetime\n\ndef dateDiffInYear(date1, date2):\n    if date1 is not None and date2 is not None:\n        date1 = datetime.strptime(date1, '%Y-%m-%d')\n        date2 = datetime.strptime(date2, '%Y-%m-%d')\n        delta = date2 - date1\n        return int(delta.days / 365)\n    return None",
            "date_diff_in_month()": "from datetime import datetime\n\ndef date_diff_in_month(date1, date2):\n    date1 = datetime.strptime(date1, '%Y-%m-%d')\n    date2 = datetime.strptime(date2, '%Y-%m-%d')\n    delta = date2.year * 12 + date2.month - (date1.year * 12 + date1.month)\n    return int(delta)\n",
            "getCurrentDate()": "from datetime import date\n\ndef getCurrentDate():\n    return date.today().strftime('%Y-%m-%d')\n",
            "check_surrogate_rule": "from datetime import datetime\nimport traceback\nfrom collections import defaultdict\ndef check_surrogate_rule(approved_amount, created_at, bureau_surrogate_rules, accounts, ownership_dict, account_mappings, bureau_type):\n    try:\n        success = False\n        reverse_account_mappings = {val: key for key, val in account_mappings.items()}\n        bureau_surrogate_rules = [item for item in bureau_surrogate_rules.get(\"values\") if item.get(\"status\") == 1]\n        available_sanction_amt_list = []\n        account_type_grouped_accts = defaultdict(dict)\n        current_date = datetime.now()\n        personal_loan_count = 0\n        for acct in accounts:\n            if str(acct.get(\"Account_Type\")) in (\"5\", \"05\") or str(acct.get(\"sch:AccountType\")) in (\"5\", \"05\"):\n                open_date_str = acct.get(\"Open_Date\") if bureau_type == 'experian' else acct.get(\"sch:DateOpened\")\n                date_fmt = '%Y%m%d' if bureau_type == 'experian' else '%Y-%m-%d'\n                if open_date_str:\n                    try:\n                        open_date = datetime.strptime(str(open_date_str), date_fmt)\n                        if calculate_bureau_months(current_date, open_date) <= 12:\n                            personal_loan_count += 1\n                    except Exception:\n                        pass\n        print(personal_loan_count)\n        available_account_types = {account for item in bureau_surrogate_rules for account in item['account_type']}\n        if bureau_type == 'experian':\n            created_at = '' if not created_at else datetime.strptime(created_at, '%Y%m%d')\n            available_account_types = {key for key, value in account_mappings.items() if value in available_account_types}\n        else:\n            created_at = '' if not created_at else datetime.strptime(created_at, '%d-%m-%Y')\n        for distinct_account in available_account_types:\n            account_type_grouped_accts[distinct_account]['accounts'] = [acct for acct in accounts if (str(acct.get(\"Account_Type\")) == distinct_account or str(acct.get(\"sch:AccountType\")) == distinct_account)]\n        for rules in bureau_surrogate_rules:\n            rule_ownership_list = rules.get(\"ownership_type\")\n            rule_open_vintage = rules.get(\"open_vintage\")\n            rule_close_vintage = rules.get(\"close_vintage\")\n            rule_percentage = rules.get(\"limit\")\n            rule_account_type = rules.get(\"account_type\")\n            rule_account_type_count = rules.get(\"account_type_count\")\n            check_accounts = []\n            for selections in rule_account_type:\n                check_accounts.append(int(reverse_account_mappings.get(selections)))\n            relevant_ownerships = [ownership_dict.get(i.capitalize()) for i in rule_ownership_list]\n            for type, accts in account_type_grouped_accts.items():\n                available_sanction_list = []\n                available_sanction_amount = 0\n                for acct in accts.get(\"accounts\", []):\n                    if str(acct.get(\"AccountHoldertypeCode\")) in relevant_ownerships or str(acct.get(\"sch:OwnershipType\")) in rule_ownership_list:\n                        if bureau_type == 'experian':\n                            close_date_key = \"Date_Closed\"\n                            total_payment_grid = len(acct.get(\"CAIS_Account_History\")) if acct.get(\"CAIS_Account_History\") else 0\n                            sanction_amount_key = \"Highest_Credit_or_Original_Loan_Amount\"\n                            close_date = '' if not acct.get(close_date_key) else datetime.strptime(acct.get(close_date_key), '%Y%m%d')\n                        else:\n                            close_date_key = \"sch:DateClosed\"\n                            total_payment_grid = len(acct.get(\"sch:History48Months\").get(\"sch:Month\")) if acct.get(\"sch:History48Months\").get(\"sch:Month\") else 0\n                            sanction_amount_key = \"sch:SanctionAmount\" if acct.get(\"sch:SanctionAmount\") else \"sch:HighCredit\"\n                            close_date = '' if not acct.get(close_date_key) else datetime.strptime(acct.get(close_date_key),'%Y-%m-%d')\n                        acct_close_vintage = calculate_bureau_months(created_at, close_date)\n                        passes_vintage = rule_account_type_count or (total_payment_grid >= rule_open_vintage and acct_close_vintage <= rule_close_vintage)\n                        if passes_vintage:\n                            if acct.get(sanction_amount_key):\n                                available_sanction_list.append(float(acct.get(sanction_amount_key)))\n                if available_sanction_list and int(type) in check_accounts:\n                    if rule_account_type_count:\n                        matched_percentage = None\n                        for count_rule in rule_account_type_count:\n                            if personal_loan_count == count_rule.get(\"personal_count\"):\n                                matched_percentage = count_rule.get(\"percentage_limit\")\n                                break\n                        if matched_percentage is not None:\n                            available_sanction_amount = max(available_sanction_list) * matched_percentage / 100\n                        else:\n                            continue\n                    else:\n                        available_sanction_amount = max(available_sanction_list) * rule_percentage / 100\n                    available_sanction_amt_list.append({\",\".join(rules.get(\"account_type\")): available_sanction_amount})\n                if available_sanction_amount >= 50000:\n                    success = True\n        return [1 if success else 0], available_sanction_amt_list\n    except Exception as ex:\n        print(traceback.print_exc())",
            "calculate_bureau_months": "def calculate_bureau_months(date1, date2):\n    if not date1 or not date2:\n        return 0\n    return (date1 - date2).days // 30",
            "check_close_date": "def check_close_date(bureau_close):\n    return 1 if bureau_close <= 60 else 0",
            "check_amount": "def check_amount(sanctioned_percentage, requested_loan_amount):\n    return 1 if sanctioned_percentage >= requested_loan_amount else 0",
            "dpd_reject_rule": "from datetime import date\nimport operator\nfrom dateutil.relativedelta import relativedelta\nfrom collections import defaultdict\nimport traceback\ndef dpd_reject_rule(rule_data, bureau_data, bureau_type, OwnershipDict, account_type_dict, approval=True):\n    try:\n        ops = {\n            '>': operator.gt,\n            '<': operator.lt,\n            '>=': operator.ge,\n            '<=': operator.le,\n            '==': operator.eq,\n            '!=': operator.ne\n        }\n        historical_key_map = {'experian': {'dpd': 'Days_Past_Due', 'keyword': 'Asset_Classification'},\n                              'equifax': {'dpd': 'sch:PaymentStatus', 'keyword': 'sch:AssetClassificationStatus'}}\n        numeric_map = {'experian': {'dpd': True, 'keyword': False},\n                       'equifax': {'dpd': False, 'keyword': False}}\n        dpd_rules = rule_data.get(\"rules\")\n        ownership = rule_data.get(\"Ownership\") if rule_data.get(\"Ownership\") else rule_data.get(\"ownership\")\n        dpd_ownership_list = [OwnershipDict[i.capitalize()] for i in ownership]\n        dpd_excluded_account_types = rule_data.get(\"account_types\")\n        dpd_order = [i.get(\"value\") for i in dpd_rules]\n        grouped_by_month = defaultdict(list)\n        rule_sources = [\n            (dpd_rules, 'dpd')\n        ]\n        for current_rules_list, source_name in rule_sources:\n            for item in current_rules_list:\n                month_key = item.get(\"months\")\n                if month_key is not None:\n                    remaining_data = {k: v for k, v in item.items() if k != 'months'}\n                    remaining_data['name'] = source_name\n                    grouped_by_month[month_key].append(remaining_data)\n\n        triggered_rules = []\n        rule_definitions = {}\n        all_lookbacks = set()\n        today = date.today()\n        lookback_start_dates = {}\n\n        for months_str, rules_list in grouped_by_month.items():\n            try:\n                months_int = int(months_str) if months_str != '*' else '*'\n                all_lookbacks.add(months_int)\n                if months_int not in lookback_start_dates:\n                    lookback_start_dates[months_int] = today.replace(day=1) - relativedelta(months=months_int - 1) if months_int != '*' else 0\n                for rule in rules_list:\n                    rule_id = (months_int, rule.get('name'), rule.get('operator'), rule.get('value'))\n                    if None not in rule_id and rule_id not in rule_definitions:\n                        try:\n                            req_tradelines = int(rule.get('tradelines', 1))\n                            rule_definitions[rule_id] = (rule, req_tradelines)\n                        except ValueError:\n                            rule_definitions[rule_id] = (rule, 1)\n            except ValueError:\n                continue\n        sorted_lookbacks = sorted([elem for elem in all_lookbacks if elem != '*'])\n        if '*' in all_lookbacks:\n            sorted_lookbacks.append('*')\n        rule_tradeline_match_counts = defaultdict(int)\n        record_details = {}\n        if bureau_data:\n            for account in bureau_data:\n                rule_to_check = []\n                if (str(account.get(\"AccountHoldertypeCode\")) in dpd_ownership_list or\n                        OwnershipDict.get(account.get(\"sch:OwnershipType\")) in dpd_ownership_list):\n                    if dpd_excluded_account_types:\n                        if (account_type_dict.get(str(account.get(\"Account_Type\"))) not in dpd_excluded_account_types and account.get(\"sch:AccountType\") not in dpd_excluded_account_types):\n                            rule_to_check.append('dpd')\n                    else:\n                        rule_to_check.append('dpd')\n                tradeline_name = account.get(\n                    \"Account_Number\") if bureau_type == \"experian\" else f\"{account.get('@seq')}_{account.get('sch:AccountNumber')}\"\n                if bureau_type == \"experian\":\n                    tradeline_records = [account.get(\"CAIS_Account_History\")] if isinstance(\n                        account.get(\"CAIS_Account_History\"), dict) else account.get(\"CAIS_Account_History\")\n                else:\n                    tradeline_records = [account.get(\"sch:History48Months\").get(\"sch:Month\")] \\\n                        if isinstance(account.get(\"sch:History48Months\").get(\"sch:Month\"), dict) else account.get(\n                        \"sch:History48Months\").get(\"sch:Month\")\n                satisfied_rules_per_tradeline = set()\n                if rule_to_check and check_if_open_account(account, bureau_type, approval) == 1:\n                    if tradeline_records:\n                        for record in tradeline_records:\n                            try:\n                                if bureau_type == \"equifax\":\n                                    time_key = record.get(\"@key\")\n                                    record_year = f\"20{time_key.split('-')[-1]}\"\n                                    record_month = time_key.split('-')[0]\n                                else:\n                                    record_year = record.get(\"Year\")\n                                    record_month = record.get(\"Month\")\n                                if not record_year or not record_month: continue\n                                record_date = date(int(record_year), int(record_month), 1)\n                                if record_date > today: continue\n                            except (ValueError, TypeError, KeyError):\n                                continue\n                            for months_lookback in sorted_lookbacks:\n                                start_date = lookback_start_dates[months_lookback] if months_lookback != '*' else '0000-00-00'\n                                if start_date == '0000-00-00' or record_date >= start_date:\n                                    months_str = str(months_lookback)\n                                    if months_str not in grouped_by_month: continue\n                                    filtered_grouped_by_month = [item for item in grouped_by_month[months_str] if\n                                                                 item.get(\"name\") in rule_to_check]\n                                    for rule in filtered_grouped_by_month:\n                                        rule_id = (months_lookback, rule.get('name'), rule.get('operator'),\n                                                   rule.get('value'))\n                                        if rule_id in satisfied_rules_per_tradeline:\n                                            continue\n                                        rule_name = rule.get('name')\n                                        rule_value_str = rule.get('value')\n                                        rule_operator = '>' if rule_name == 'dpd' else '=='\n                                        historical_key = historical_key_map.get(bureau_type).get(rule_name)\n                                        value_is_numeric = numeric_map.get(bureau_type).get(rule_name, False)\n\n                                        if not all([rule_name, rule_value_str, rule_operator,\n                                                    historical_key is not None]): continue\n                                        try:\n                                            compare_value_rule = int(rule_value_str) if value_is_numeric else rule_value_str\n                                        except ValueError:\n                                            continue\n                                        record_value_raw = record.get(historical_key)\n                                        compare_value_record = None\n                                        try:\n                                            if record_value_raw is not None:\n                                                compare_value_record = int(record_value_raw) if value_is_numeric else str(\n                                                    record_value_raw)\n                                        except (ValueError, TypeError):\n                                            if value_is_numeric: continue\n                                        if bureau_type == 'experian':\n                                            condition_result = check_condition(compare_value_record, rule_operator,\n                                                                               compare_value_rule, rule_name,ops, dpd_order)\n                                        else:\n                                            condition_result = check_condition_equifax(compare_value_record, rule_operator,\n                                                                                       compare_value_rule, rule_name,ops,\n                                                                                       dpd_order)\n                                        if condition_result:\n                                            satisfied_rules_per_tradeline.add(rule_id)\n                                            rule_tradeline_match_counts[rule_id] += 1\n                                            if rule_id not in record_details:\n                                                record_details[rule_id] = []\n                                            record_details[rule_id].append(f\"{tradeline_name} - {record}\")\n        sorted_rule_ids = sorted(rule_tradeline_match_counts.keys(), key=lambda x: (float('inf') if x[0] == '*' else x[0], x[1:]))\n        for rule_id in sorted_rule_ids:\n            count = rule_tradeline_match_counts[rule_id]\n            if rule_id in rule_definitions:\n                original_rule, required_tradelines = rule_definitions[rule_id]\n                original_rule['months'] = rule_id[0]\n                print(f\"  Rule {rule_id}: Matched on {count} tradeline(s). Required: {required_tradelines}\")\n                op_func = ops.get('==' if original_rule.get(\"operator\") == '=' else original_rule.get(\"operator\"))\n                if (not approval and op_func(count, required_tradelines)) or (approval and not op_func(count, required_tradelines)):\n                    print(f\"    -> TRIGGERED\")\n                    triggered_rules.append({\n                        \"rule\": original_rule,\n                        \"records\": record_details[rule_id]\n                    })\n        if triggered_rules:\n            return [1], triggered_rules\n        return 0\n    except Exception as ex:\n        traceback.print_exc()",
            "check_if_open_account": "def check_if_open_account(account, type, is_approval):\n    try:\n        open_account = 1\n        if is_approval:\n            return open_account\n        if type == 'experian' and account.get(\"Date_Closed\"):\n            open_account = 0\n        elif type == 'equifax' and not account.get(\"sch:DateOpened\"):\n            open_account = 0\n        return open_account\n    except Exception as ex:\n        return 0",
            "check_condition": "def check_condition(val1, op_str, val2, name, ops, order=()):\n    if name == 'dpd' and order and val1 is not None:\n        for elem in order:\n            if order[-1] != elem and int(elem) == val2:\n                compare_start = int(val2)\n                compare_end = int(val2) + (int(order[order.index(elem) + 1]) - int(val2))\n                if compare_start < val1 <= compare_end:\n                    return True\n            elif order[-1] == elem and int(elem) == val2:\n                if int(val2) < int(val1):\n                    return True\n    else:\n        op_func = ops.get(op_str)\n        if op_func and val1 is not None and val2 is not None:\n            return op_func(val1, val2)\n    return False",
            "check_condition_equifax": "def check_condition_equifax(val1, op_str, val2, name, ops, order=()):\n    EquifaxDPD = {\n        'NEW': 0,\n        '90+': 90,\n        '120+': 120,\n        '180+': 180\n    }\n    if name == 'dpd' and val1:\n        if f\"{val2}+\" == val1 and val2 != order[-1]:\n            return True\n        elif val2 == order[-1]:\n            if int(EquifaxDPD.get(val1, 0)) >= int(val2):\n                return True\n    else:\n        op_func = ops.get(op_str)\n        if op_func and val1 is not None and val2 is not None:\n            return op_func(val1, val2)\n    return False",
            "keyword_reject_rule": "from datetime import date\nfrom dateutil.relativedelta import relativedelta\nimport operator\nfrom collections import defaultdict\nimport traceback\ndef keyword_reject_rule(rule_data, bureau_data, bureau_type, OwnershipDict, account_type_dict, approval=True):\n    try:\n        ops = {\n            '>': operator.gt,\n            '<': operator.lt,\n            '>=': operator.ge,\n            '<=': operator.le,\n            '==': operator.eq,\n            '!=': operator.ne\n        }\n        historical_key_map = {'experian': {'dpd': 'Days_Past_Due', 'keyword': 'Asset_Classification'},\n                              'equifax': {'dpd': 'sch:PaymentStatus', 'keyword': 'sch:AssetClassificationStatus'}}\n        keyword_rules = rule_data.get(\"rules\")\n        ownership = rule_data.get(\"Ownership\") if rule_data.get(\"Ownership\") else rule_data.get(\"ownership\")\n        keyword_ownership_list = [OwnershipDict[i.capitalize()] for i in ownership]\n        keyword_excluded_account_types = rule_data.get(\"account_types\")\n        grouped_by_month = defaultdict(list)\n        rule_sources = [(keyword_rules, 'keyword')]\n        for current_rules_list, source_name in rule_sources:\n            for item in current_rules_list:\n                month_key = item.get(\"months\")\n                if month_key is not None:\n                    remaining_data = {k: v for k, v in item.items() if k != 'months'}\n                    remaining_data['name'] = source_name\n                    grouped_by_month[month_key].append(remaining_data)\n        triggered_rules = []\n        rule_definitions = {}\n        all_lookbacks = set()\n        today = date.today()\n        lookback_start_dates = {}\n        for months_str, rules_list in grouped_by_month.items():\n            try:\n                months_int = int(months_str) if months_str != '*' else '*'\n                all_lookbacks.add(months_int)\n                if months_int not in lookback_start_dates:\n                    lookback_start_dates[months_int] = today.replace(day=1) - relativedelta(months=months_int - 1) if months_int != '*' else 0\n                for rule in rules_list:\n                    rule_id = (months_int, rule.get('name'), rule.get('operator'), rule.get('value'))\n                    if None not in rule_id and rule_id not in rule_definitions:\n                        try:\n                            req_tradelines = int(rule.get('tradelines', 1))\n                            rule_definitions[rule_id] = (rule, req_tradelines if req_tradelines > 0 else 1)\n                        except ValueError:\n                            rule_definitions[rule_id] = (rule, 1)\n            except ValueError:\n                continue\n        sorted_lookbacks = sorted([elem for elem in all_lookbacks if elem != '*'])\n        if '*' in all_lookbacks:\n            sorted_lookbacks.append('*')\n        rule_tradeline_match_counts = defaultdict(int)\n        record_details = {}\n        if bureau_data:\n            for account in bureau_data:\n                rule_to_check = []\n                if (str(account.get(\"AccountHoldertypeCode\")) in keyword_ownership_list or\n                        OwnershipDict.get(account.get(\"sch:OwnershipType\")) in keyword_ownership_list):\n                    if keyword_excluded_account_types:\n                        if (account_type_dict.get(\n                                str(account.get(\"Account_Type\"))) not in keyword_excluded_account_types and account.get(\n                                \"sch:AccountType\") not in keyword_excluded_account_types):\n                            rule_to_check.append('keyword')\n                    else:\n                        rule_to_check.append('keyword')\n                tradeline_name = account.get(\n                    \"Account_Number\") if bureau_type == \"experian\" else f\"{account.get('@seq')}_{account.get('sch:AccountNumber')}\"\n                if bureau_type == \"experian\":\n                    tradeline_records = [account.get(\"CAIS_Account_History\")] if isinstance(account.get(\"CAIS_Account_History\"), dict) else account.get(\"CAIS_Account_History\")\n                else:\n                    tradeline_records = [account.get(\"sch:History48Months\").get(\"sch:Month\")] if isinstance(account.get(\"sch:History48Months\").get(\"sch:Month\"), dict) else account.get(\"sch:History48Months\").get(\"sch:Month\")\n                satisfied_rules_per_tradeline = set()\n                if rule_to_check and check_if_open_account(account, bureau_type, approval) == 1:\n                    if tradeline_records:\n                        for record in tradeline_records:\n                            try:\n                                if bureau_type == \"equifax\":\n                                    time_key = record.get(\"@key\")\n                                    record_year = f\"20{time_key.split('-')[-1]}\"\n                                    record_month = time_key.split('-')[0]\n                                else:\n                                    record_year = record.get(\"Year\")\n                                    record_month = record.get(\"Month\")\n                                if not record_year or not record_month: continue\n                                record_date = date(int(record_year), int(record_month), 1)\n                                if record_date > today: continue\n                            except (ValueError, TypeError, KeyError):\n                                continue\n                            for months_lookback in sorted_lookbacks:\n                                start_date = lookback_start_dates[months_lookback] if months_lookback != '*' else '0000-00-00'\n                                if start_date == '0000-00-00' or record_date >= start_date:\n                                    months_str = str(months_lookback)\n                                    if months_str not in grouped_by_month: continue\n                                    filtered_grouped_by_month = [item for item in grouped_by_month[months_str] if\n                                                                 item.get(\"name\") in rule_to_check]\n                                    for rule in filtered_grouped_by_month:\n                                        rule_id = (months_lookback, rule.get('name'), rule.get('operator'),\n                                                   rule.get('value'))\n                                        if rule_id in satisfied_rules_per_tradeline:\n                                            continue\n                                        rule_name = rule.get('name')\n                                        rule_value_str = rule.get('value')\n                                        rule_operator = '=='\n                                        historical_key = historical_key_map.get(bureau_type).get(rule_name)\n                                        if not all([rule_name, rule_value_str, rule_operator,\n                                                    historical_key is not None]): continue\n                                        try:\n                                            compare_value_rule = rule_value_str\n                                        except ValueError:\n                                            continue\n                                        record_value_raw = record.get(historical_key)\n                                        compare_value_record = None\n                                        if record_value_raw is not None:\n                                            compare_value_record = str(record_value_raw)\n                                        if bureau_type == 'experian':\n                                            condition_result = check_condition(compare_value_record, rule_operator,\n                                                                               compare_value_rule, rule_name, ops)\n                                        else:\n                                            condition_result = check_condition_equifax(compare_value_record, rule_operator,\n                                                                                       compare_value_rule, rule_name, ops)\n                                        if condition_result:\n                                            satisfied_rules_per_tradeline.add(rule_id)\n                                            rule_tradeline_match_counts[rule_id] += 1\n                                            if rule_id not in record_details:\n                                                record_details[rule_id] = []\n                                            record_details[rule_id].append(f\"{tradeline_name} - {record}\")\n        sorted_rule_ids = sorted(rule_tradeline_match_counts.keys())\n        for rule_id in sorted_rule_ids:\n            count = rule_tradeline_match_counts[rule_id]\n            if rule_id in rule_definitions:\n                original_rule, required_tradelines = rule_definitions[rule_id]\n                original_rule['months'] = rule_id[0]\n                print(f\"  Rule {rule_id}: Matched on {count} tradeline(s). Required: {required_tradelines}\")\n                op_func = ops.get('==' if original_rule.get(\"operator\") == '=' else original_rule.get(\"operator\"))\n                if op_func(count, required_tradelines):\n                    print(f\"-> TRIGGERED\")\n                    triggered_rules.append({\n                        \"rule\": original_rule,\n                        \"records\": record_details[rule_id]\n                    })\n        if triggered_rules:\n            return [1], triggered_rules\n        return 0\n    except Exception as ex:\n        error_message = f'Exception occurred: {str(ex)}\\n{traceback.format_exc()}'\n        print(error_message)",
            "check_account_status": "def check_account_status(rule_data, bureau_data, OwnershipDict, bureau_type, field_mappings, acct_dict):\n    try:\n        rules = rule_data.get(\"rules\")\n        ownership_list = [OwnershipDict[i.capitalize()] for i in rule_data.get(\"ownership\")]\n        violations = []\n        data_key = field_mappings.get(bureau_type).get(\"value_field\")\n        if bureau_data:\n            for account in bureau_data:\n                if (str(account.get(\"AccountHoldertypeCode\")) in ownership_list or\n                        OwnershipDict.get(account.get(\"sch:OwnershipType\")) in ownership_list):\n                    tradeline_name = account.get(\n                        \"Account_Number\") if bureau_type == \"experian\" else f\"{account.get('@seq')}_{account.get('sch:AccountNumber')}\"\n\n                    for specific_rule in rules:\n                        tradeline_value_raw = acct_dict.get(\n                            (account.get(data_key))) if bureau_type == 'experian' else account.get(data_key)\n                        if tradeline_value_raw is None:\n                            continue\n\n                        rule_value_str = specific_rule.get(\"value\")\n                        if rule_value_str is None:\n                            rule_value_str = specific_rule.get(\"values\")\n                        rule_months = specific_rule.get(\"months\")\n\n                        violation_found = False\n                        relevant_date = None\n                        rule_value_str = rule_value_str.replace('amp;', '')\n                        if tradeline_value_raw.replace(\" \", \"\").replace(\"&\", \"and\").replace(\"-\",\n                                                                                            \"\").lower() == rule_value_str.replace(\n                                \" \", \"\").replace(\"&\", \"and\").replace(\"-\", \"\").lower():\n                            if rule_months != '*':\n                                for date_field in field_mappings.get(bureau_type).get(\"date_fields\"):\n                                    relevant_date_str = account.get(date_field)\n                                    if relevant_date_str:\n                                        fmt = \"%Y%m%d\" if bureau_type == 'experian' else \"%Y-%m-%d\"\n                                        relevant_date = datetime.strptime(relevant_date_str, fmt)\n                                        if relevant_date >= datetime.today() - relativedelta(months=int(rule_months)):\n                                            violation_found = True\n                                        break\n                            else:\n                                violation_found = True\n                        if violation_found:\n                            violations.append({\n                                \"tradeline_name\": tradeline_name,\n                                \"rule\": specific_rule,\n                                \"relevance_date\": relevant_date if relevant_date else '*'\n                            })\n        if violations:\n            return [1], violations\n        return 0\n    except Exception as ex:\n        error_message = f\"Exception occurred: {str(ex)}\\n{traceback.format_exc()}\"\n        print(error_message)",
            "check_principal_write_off": "def check_principal_write_off(rule_data, bureau_data, bureau_type, OwnershipDict):\n    field_mappings = {'experian': {'date_fields': ['Date_Closed', 'Date_of_Last_Payment', 'Date_Reported'],\n                                      'value_field': 'Written_Off_Amt_Principal'},\n                         'equifax': {'date_fields': ['sch:DateClosed', 'sch:LastPaymentDate', 'sch:DateReported'],\n                                     'value_field': 'sch:WriteOffAmount'}}\n    rules = rule_data.get(\"rules\")\n    failed_rules = []\n    ownership_list = [OwnershipDict[i.capitalize()] for i in rule_data.get(\"ownership\")]\n    data_key = field_mappings.get(bureau_type).get(\"value_field\")\n    distinct_month = find_distinct_month('months', rules)\n    month_write_off = dict.fromkeys(distinct_month, 0)\n    if bureau_data:\n        for account in bureau_data:\n            if (str(account.get(\"AccountHoldertypeCode\")) in ownership_list or OwnershipDict.get(account.get(\"sch:OwnershipType\")) in ownership_list):\n                write_off_amt = int(account.get(data_key)) if account.get(data_key) else 0\n                for rule_month in distinct_month:\n                    if rule_month != '*':\n                        for date_field in field_mappings.get(bureau_type).get(\"date_fields\"):\n                            relevant_date_str = account.get(date_field)\n                            if relevant_date_str:\n                                fmt = \"%Y%m%d\" if bureau_type == 'experian' else \"%Y-%m-%d\"\n                                relevant_date = datetime.strptime(relevant_date_str, fmt)\n                                if relevant_date >= datetime.today() - relativedelta(months=int(rule_month)):\n                                    month_write_off[rule_month] += write_off_amt\n                                break\n    for rule in rules:\n        if int(rule.get(\"value\")) <= month_write_off[rule.get(\"months\")]:\n            failed_rules.append({\n                'rule': rule,\n                'total_principal_write_off': month_write_off[rule.get(\"months\")],\n                'rule_principal_write_off': rule.get(\"value\")\n            })\n    if failed_rules:\n        return [1], failed_rules\n    return 0",
            "find_distinct_month": "def find_distinct_month(key, rules):\n    distinct_values = []\n    for item_dict in rules:\n        if key in item_dict:\n            value = item_dict[key]\n            if value not in distinct_values:\n                distinct_values.append(value)\n    return distinct_values",
            "check_total_past_dues": "def check_total_past_dues(rule_data, bureau_data, bureau_type, OwnershipDict, AccountTypeDict, approval=True):\n    try:\n        rules = rule_data.get(\"rules\")\n        ownership_list = [OwnershipDict[i.capitalize()] for i in rule_data.get(\"ownership\")]\n        rule_failed = []\n        if bureau_type == 'equifax':\n            data_key = \"sch:PastDueAmount\"\n            reported_date_key = \"sch:DateReported\"\n        else:\n            data_key = \"Amount_Past_Due\"\n            reported_date_key = \"Date_Reported\"\n        for rule in rules:\n            rule_account_type = rule[\"account_type\"]\n            rule_value = float(rule[\"value\"])\n            months_back = int(rule[\"months\"])\n            date_cutoff = datetime.today() - relativedelta(months=months_back)\n            total_overdue = 0\n            if bureau_data:\n                for account in bureau_data:\n                    if (str(account.get(\"AccountHoldertypeCode\")) in ownership_list or OwnershipDict.get(account.get(\"sch:OwnershipType\")) in ownership_list):\n                        tradeline_account_type = AccountTypeDict.get(str(account.get(\"Account_Type\")), \"others\") if bureau_type == 'experian' else account.get(\"sch:AccountType\", \"others\")\n                        is_open = check_if_open_account(account, bureau_type, approval)\n                        if is_open and re.sub(r'[^a-zA-Z0-9]', '', tradeline_account_type).lower() == re.sub(r'[^a-zA-Z0-9]', '', rule_account_type).lower():\n                            reported_datetime = datetime.strptime(account.get(reported_date_key),\"%Y%m%d\") if bureau_type == 'experian' else datetime.strptime(account.get(reported_date_key), \"%Y-%m-%d\")\n                            if reported_datetime >= date_cutoff:\n                                if account.get(data_key) is not None:\n                                    total_overdue += 0 if account.get(data_key) == \"\" else int(account.get(data_key))\n                if total_overdue >= rule_value:\n                    rule_failed.append({\n                        \"rule\": rule,\n                        \"total_past_dues\": total_overdue,\n                        \"account_type\": tradeline_account_type\n                    })\n        if rule_failed:\n            return [1], rule_failed\n        return 0\n    except Exception as ex:\n      print(traceback.print_exc())",
            "check_bounce_number": "def check_bounce_number(data, rule):\n    if rule:\n        import operator\n        ops = {'>': operator.gt, '<': operator.lt, '>=': operator.ge, '<=': operator.le, '==': operator.eq, '!=': operator.ne}\n        rule_month = rule[0].get(\"rules\")[0].get(\"months\")\n        bounce_threshold = rule[0].get(\"rules\")[0].get(\"value\")\n        operator = rule[0].get(\"rules\")[0].get(\"operator\")\n        lookback_period = datetime.today().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - relativedelta(months=int(rule_month) - 1)\n        if data:\n            if data.get(\"completeBounceAnalysis\"):\n                data = data.get(\"completeBounceAnalysis\")\n            elif data.get(\"monthlyBounceAnalysis\"):\n                data = data.get(\"monthlyBounceAnalysis\")\n            elif data.get('bounceAnalysis'):\n                data = data.get('bounceAnalysis')\n            else:\n                return 0\n            if data.get(\"accountWiseBounceAnalytics\"):\n                total_bounce = 0\n                bounce_log = []\n                for month_data in data.get(\"accountWiseBounceAnalytics\")[0].get(\"monthlyBounceAnalysis\"):\n                    if (month_data.get(\"totalEmiBounceTxn\") and month_data.get(\"totalEmiBounceAmt\")) or (month_data.get(\"totalEcsBounceChargeTxns\") and month_data.get(\"totalEcsBounceChargeAmount\")):\n                        current_month_year = datetime.strptime(f\"{month_data.get('year')}-{month_data.get('month')}\",\n                                                               \"%Y-%m\")\n                        if lookback_period <= current_month_year:\n                            if month_data.get(\"totalEmiBounceTxn\") > 0 and month_data.get(\"totalEmiBounceAmt\") > 0:\n                                total_bounce += month_data.get(\"totalEmiBounceTxn\")\n                                bounce_log.append({\n                                    'rule': rule[0],\n                                    'month': current_month_year,\n                                    'totalEmiBounceTxn': month_data.get(\"totalEmiBounceTxn\"),\n                                    'totalEmiBounceAmt': month_data.get(\"totalEmiBounceAmt\")\n                                })\n                            if month_data.get(\"totalEcsBounceChargeTxns\") > 0 and month_data.get(\n                                    \"totalEcsBounceChargeAmount\") > 0:\n                                total_bounce += month_data.get(\"totalEcsBounceChargeTxns\")\n                                bounce_log.append({\n                                    'rule': rule[0],\n                                    'month': current_month_year,\n                                    'totalEcsBounceChargeTxns': month_data.get(\"totalEcsBounceChargeTxns\"),\n                                    'totalEcsBounceChargeAmount': month_data.get(\"totalEcsBounceChargeAmount\")\n                                })\n                op_func = ops.get(operator)\n                if bounce_log and op_func(total_bounce, int(bounce_threshold)):\n                    return [1], bounce_log\n    return 0",
            "get_max": "def getMax(lists):\n    numeric_values = [int(item) for sublist in lists.values() for item in sublist if item.isdigit()]\n    if numeric_values:\n        return max(numeric_values)\n    else:\n        return 0",
            "string_to_date": "from datetime import datetime\ndef string_to_date(string, format):\n  return datetime.strptime(string, format).date()",
            "before_x_years_date": "from datetime import date\ndef before_x_years_date(years):\n  today = date.today()\n  start_date = date(today.year - years, today.month, today.day)\n  return start_date",
            "dpd_check_equifax": "from datetime import datetime, date\n\n\ndef dpd_check_equifax(dpd_years_above_0, dpd_count_above_0, equifax_data):\n    if dpd_years_above_0 is None or dpd_count_above_0 is None or not equifax_data:\n        return 0\n    start_date = before_x_years_date(dpd_years_above_0)\n    count_per_tradeline = 0\n\n    if equifax_data:\n        try:\n            for data in equifax_data:\n                count = 0\n                if string_to_date(data['sch:DateOpened'], '%Y-%m-%d') > start_date:\n                    for index, History in data['sch:History48Months'].items():\n                        count = 0\n                        if isinstance(History, list):\n                            count = sum([1 if '+' in MonthWise['sch:PaymentStatus'] else 0 for MonthWise in History])\n                        elif isinstance(History, dict):\n                            if '+' in History['sch:PaymentStatus']:\n                                count = 1\n                            else:\n                                count = 0\n                count_per_tradeline += count\n                if count_per_tradeline > dpd_count_above_0:\n                    return 0\n            if count_per_tradeline <= dpd_count_above_0:\n                return 1\n        except:\n            return 'No Equifax Bureau'\n    else:\n        return 1",
            "dpd_check_experian": "from datetime import datetime, date\n\ndef dpd_check_experian(dpd_years_above_0, dpd_count_above_0, experian_data):\n    if dpd_years_above_0 is None or dpd_count_above_0 is None:\n        return 0\n    start_date = before_x_years_date(dpd_years_above_0)\n    count_per_tradeline = 0\n    if experian_data:\n        try:\n            if isinstance(experian_data, list):\n                for data in experian_data:\n                    count = 0\n                    if string_to_date(data[\"Open_Date\"], \"%Y%m%d\") > start_date:\n                        if isinstance(data['CAIS_Account_History'], list):\n                            count = sum([1 for MonthWise in data['CAIS_Account_History'] if MonthWise['Days_Past_Due'] not in (\"0\", 0, None)])\n                        elif isinstance(data['CAIS_Account_History'], dict):\n                            if data['CAIS_Account_History']['Days_Past_Due'] not in (0, '0', None):\n                                count = 1\n                            else:\n                                count = 0\n                    count_per_tradeline += count\n                    if count_per_tradeline >= dpd_count_above_0:\n                        return 0\n                if count_per_tradeline < dpd_count_above_0:\n                    return 1\n            if isinstance(experian_data, dict):\n                data = experian_data\n                count = 0\n                if string_to_date(data[\"Open_Date\"], \"%Y%m%d\") > start_date:\n                    if isinstance(data['CAIS_Account_History'], list):\n                        count = sum([1 for MonthWise in data['CAIS_Account_History'] if \"0\" != MonthWise['Days_Past_Due']])\n                    elif isinstance(data['CAIS_Account_History'], dict):\n                        if \"0\" != data['CAIS_Account_History']['Days_Past_Due']:\n                            count = 1\n                        else:\n                            count = 0\n                count_per_tradeline += count\n                print(count_per_tradeline)\n                if count_per_tradeline > dpd_count_above_0:\n                    return 0\n            if count_per_tradeline <= dpd_count_above_0:\n                return 1\n        except:\n            return \"No Experian Bureau\"\n    else:\n        return 1",
            "dpd_check_both": "from datetime import datetime, date\n\ndef dpd_check_bureau(dpd_years_above_0, dpd_count_above_0, bureau_data):\n    start_date = before_x_years_date(dpd_years_above_0)\n    count_per_tradeline = []\n    count = 0\n    try: # experian\n      for data in bureau_data:\n        if string_to_date(data[\"Open_Date\"], \"%Y%m%d\") > start_date:\n          if isinstance(data['CAIS_Account_History'], list):\n            count = sum([1 if \"0\" != MonthWise['Days_Past_Due'] else 0 for MonthWise in data['CAIS_Account_History'] ])\n          elif isinstance(data['CAIS_Account_History'], dict):\n            if \"0\" != data['CAIS_Account_History']['Days_Past_Due']:\n              count = 1\n            else:\n              count = 0\n        count_per_tradeline.append(count)\n      max_occurance = max(count_per_tradeline)\n      if max_occurance>=dpd_count_above_0:\n        return 0\n      else:\n        return 1\n    except: # for equifax\n      for data in bureau_data:\n        if string_to_date(data[\"sch:DateOpened\"], \"%Y-%m-%d\") > start_date:\n          for index, History in data['sch:History48Months'].items():\n            if isinstance(History, list):\n              count = sum([1 if \"+\" in MonthWise['sch:PaymentStatus'] else 0 for MonthWise in History ])\n            elif isinstance(History, dict):\n              if \"+\" in History['sch:PaymentStatus']:\n                count = 1\n              else:\n                count = 0\n          count_per_tradeline.append(count)\n\n        max_occurance = max(count_per_tradeline)\n        if max_occurance>=dpd_count_above_0:\n          return 0\n        else:\n          return 1\n    return None",
            "remove_special_symbols": "import re\n\ndef remove_special_symbols(input_string):\n    pattern = r'[^\\w\\s]'\n    result = re.sub(pattern, '', input_string)\n    return result",
            "is_merchant_eligible": "def is_merchant_eligible(loan_amount, total_amount):\n    if int(loan_amount) > int(total_amount):\n        return 0\n    return 1",
            "is_employment_verification": "import json\n\ndef is_employment_verification(data):\n    epf_data = json.loads(data)\n    is_passed = 1\n    reason = ''\n    is_employed = is_recent = False\n\n    if epf_data:\n        summary = epf_data.get('result', {}).get('summary', {})\n        if summary:\n            current_employer = summary.get('uanLookup', {}).get('currentEmployer', None)\n            if current_employer:\n                uan_data = epf_data.get('result', {}).get('uan', [])\n                if uan_data and len(uan_data) > 0:\n                    employers = uan_data[0].get('employer', [])\n                    current_employer_details = list(filter(lambda employer: employer['name'] == current_employer, employers))\n                    if current_employer_details:\n                        is_employed = current_employer_details[0].get('isEmployed', False)\n                        is_recent = current_employer_details[0].get('isRecent', False)\n\n    if not is_employed or not is_recent:\n        if not is_employed:\n            reason = \"epf employment verification failed - epf\"\n        elif not is_recent:\n            reason = \"PF Filing not Recent - epf\"\n        is_passed = 0\n\n    return [is_passed, reason]",
            "credit_card_check": "import json\ndef credit_card_check(percentage, foir_data, loan_amount):\n    is_passed = 1\n    reason = 'No CC History'\n    if foir_data:\n      credit_card_limits = [float(foir['ApprovedAmount']) - float(foir['Balance']) for foir in json.loads(foir_data) if foir['AccountType'] == 'Credit Card' and foir['DateClosed'] == '-']\n      if percentage and loan_amount and credit_card_limits:\n          cr_limit_should = float(loan_amount) * float(percentage) / 100\n          if max(credit_card_limits) < cr_limit_should:\n            is_passed = 0\n            reason = f'Credit Card tradeline due amount does not match the grade criteria: {cr_limit_should}'\n    return [is_passed, reason]",
            "credit_card_check2": "import json\ndef credit_card_check2(percentage, foir_data, loan_amount):\n    is_passed = 0\n    reason = 'No CC History'\n    if foir_data:\n      credit_card_limits = [float(foir['ApprovedAmount']) - float(foir['Balance']) for foir in json.loads(foir_data) if foir['AccountType'] == 'Credit Card' and foir['DateClosed'] == '-']\n      if percentage and loan_amount and credit_card_limits:\n          cr_limit_should = float(loan_amount) * float(percentage) / 100\n          if max(credit_card_limits) >= cr_limit_should:\n            is_passed = 1\n            reason = \"\"\n          else:\n            reason = f'Credit Card tradeline due amount does not match the grade criteria: {cr_limit_should}'\n    return [is_passed, reason]",
            "account_type_check": "def account_type_check(foir_data):\n    if foir_data is None:\n        return 1\n    gold_loan_count = sum(1 for foir in json.loads(foir_data) if foir['AccountType'] == 'Gold Loan' and foir['DateClosed'] == '-')\n\n    is_passed = 1\n    reason = ''\n\n    if gold_loan_count >= 3:\n        reason = 'Gold Loan tradeline count does not match the grade criteria'\n        is_passed = 0\n\n    return [is_passed, reason]",
            "pre_dpd_check": "def pre_dpd_check(dpd_count, dpd_years, dpd_keywords, foir_data):\n    dpdRange = 0\n    dpdSumAllTradeline = 0\n    is_passed = 1\n    reason = \"\"\n    if foir_data:\n        foir_data = unquote_dict_and_list(foir_data)\n        if dpd_count is not None and dpd_years is not None and dpd_keywords is not None:\n            for foir in json.loads(foir_data):\n                foirData = foir['dpd']\n                dpdRange = dpd_count\n                dateRange = dpd_years\n                created_at = datetime.strptime(foir['created_at'], '%Y-%m-%d')\n                if foir['DateOpened'] == '0000-00-00':\n                    continue\n                diff_days = (created_at - datetime.strptime(foir['DateOpened'], '%Y-%m-%d')).days if foir[\n                    'DateOpened'] else 0\n                all_keywords = []\n                years = diff_days / 365\n                if dpd_keywords and dpd_keywords != '':\n                    keywords = dpd_keywords.split(\",\")\n                    for key in keywords:\n                        keyword = key.strip()\n                        keyword = keyword.strip('\"')\n                        all_keywords.append(keyword)\n                if foirData:\n                    foirClean = [key for key in foirData.keys() if key not in ['000', 'NEW', 'CLSD']]\n                    if foirClean:\n                        if years <= dateRange:\n                            for key, item in foirData.items():\n                                if key not in all_keywords:\n                                    dpdSumAllTradeline += item\n                        if dpdSumAllTradeline > dpdRange:\n                            is_passed = 0\n                            reason = 'Pre DPD count (within a certain period) does not not match the grade criteria'\n\n        if dpd_count is None or dpd_years is None or dpd_keywords is None:\n            is_passed = 0\n            reason = \"Pre DPD count rule not configured properly\"\n    if not foir_data:\n        is_passed = 0\n        reason = \"No Foir Data\"\n    return [is_passed, reason]",
            "case_type_check": "import json\n\ndef case_type_check(case_type_values, foir_data):\n  if not case_type_values:\n      return 0\n  case_exist = 1\n  if foir_data:\n    for foir in json.loads(foir_data):\n        if case_type_values:\n            keywords = case_type_values.split(',')\n            if foir['dpd']:\n                case_type = json.loads(foir['dpd'])\n                if case_type:\n                    for case_type_item in case_type.keys():\n                        if case_type_item in keywords:\n                            case_exist = 0\n                            break\n  else : \n    case_exist = None\n  return case_exist",
            "post_dpd_check": "import json\nfrom datetime import datetime\ndef post_dpd_check(dpd_count, dpd_years, dpd_keywords, foir_data):\n    print(\"In func\")\n    print(dpd_count)\n    print(dpd_years)\n    print(dpd_keywords)\n    dpdRange = 0\n    dpdSumAllTradeline = 0\n    is_passed = 1\n    reason = \"\"\n    if foir_data:\n        foir_data = unquote_dict_and_list(foir_data)\n        if dpd_count is not None and dpd_years is not None and dpd_keywords is not None:\n            for foir in json.loads(foir_data):\n                foirData = foir['dpd']\n                dpdRange = dpd_count\n                dateRange = dpd_years\n                created_at = datetime.strptime(foir['created_at'], '%Y-%m-%d')\n                if foir['DateOpened'] == '0000-00-00':\n                    continue\n                diff_days = (created_at - datetime.strptime(foir['DateOpened'], '%Y-%m-%d')).days if foir['DateOpened'] else 0\n                all_keywords = []\n                years = diff_days / 365\n                if dpd_keywords and dpd_keywords != '':\n                    keywords = dpd_keywords.split(\",\")\n                    for key in keywords:\n                        keyword = key.strip()\n                        keyword = keyword.strip('\"')\n                        all_keywords.append(keyword)\n                if foirData:\n                    foirClean = [key for key in foirData.keys() if key not in ['000', 'NEW', 'CLSD']]\n                    if foirClean:\n                        if years >= dateRange:\n                            for key, item in foirData.items():\n                                if key not in all_keywords:\n                                    dpdSumAllTradeline += item\n                        if dpdSumAllTradeline > dpdRange:\n                            is_passed = 0\n                            reason = \"Post DPD count (above a certain period) does not not match the grade criteria\"\n        if dpd_count is None or dpd_years is None or dpd_keywords is None:\n            is_passed = 0\n            reason = \"Post DPD count not configured properly\"\n    else:\n        is_passed = 0\n        reason = \"No Foir Data\"\n    return [is_passed, reason]",
            "is_term_loan": "import json\ndef is_term_loan(foir_data, is_valid_credit_score, term_loans, current_date, term_loan_amount_paid_percentage, loan_amount):\n    if not term_loan_amount_paid_percentage or not loan_amount:\n        return 0\n    max_sanctioned = []\n    old_loan = 0 if is_valid_credit_score else 1\n    unsecured_loan = 0 if is_valid_credit_score else 1\n    if foir_data:\n        foir_data = unquote_dict_and_list(foir_data)\n    for foir in json.loads(foir_data):\n        if not(foir.get('DateOpened') == '-') and (foir.get('DateClosed')=='-'):\n            month_diff = date_diff_in_month(foir['DateOpened'], current_date)\n            term_loan = month_diff if month_diff >= 6 else 0\n\n            if term_loan > 0:\n                old_loan = 0\n\n            if foir['AccountType'] in term_loans:\n                unsecured_loan = 0\n\n            if foir['AccountType'] in term_loans and old_loan == 0 and unsecured_loan == 0 and foir.get('DateClosed')=='-':\n                max_sanctioned.append(float(foir['ApprovedAmount']) - float(foir['Balance']))\n\n    if term_loan_amount_paid_percentage and loan_amount and max_sanctioned:\n        cr_limit_should = float(loan_amount) * term_loan_amount_paid_percentage / 100\n        if max(max_sanctioned) < cr_limit_should:\n            return 0\n\n    return 1",
            "unquote_dict_and_list": "def unquote_dict_and_list(data):\n    if data is not None:\n        replacement_dict = {'\\\\': '', '\"{': '{', '}\"': '}', '\"[': '[', ']\"': ']'}\n        for key, value in replacement_dict.items():\n            data = data.replace(key, value)\n    else:\n        return data\n    return data",
            "similar_text": "import difflib\n\n\ndef similar_text(str1, str2):\n    # Create a SequenceMatcher instance\n    matcher = difflib.SequenceMatcher(None, str1, str2)\n\n    # Get the similarity ratio (between 0 and 1)\n    ratio = matcher.ratio()\n\n    # Get the number of matching characters\n    matching_blocks = matcher.get_matching_blocks()\n    matching_chars = sum(block.size for block in matching_blocks)\n\n    # Calculate the percentage similarity\n    similarity_percentage = ratio * 100  # Convert the ratio to percentage\n\n    # Return the percentage similarity and the number of matching characters\n    return similarity_percentage",
            "bank_validation_check": "def bank_validation_check(validation_perc, transaction_status, applicant_name, account_name):\n    account_name = handle_byte_array(bytearray(account_name))\n    transaction_status = handle_byte_array(bytearray(transaction_status))\n\n    is_passed = 0\n    reason = \"Bank details not present in the application\"\n    if not validation_perc:\n        return 0\n    status_check = 0\n    name_match = 0\n    if transaction_status:\n        if transaction_status.lower() in ('transaction successful', 'successful transaction'):\n            status_check = 1\n        if account_name:\n            try:\n                percentage = 0\n                percentage = similar_text(applicant_name.lower(), account_name.lower()) if account_name else 0\n                name_match = 1 if round(percentage) >= validation_perc else 0\n            except Exception as e:\n                print(\"Exception while name match check\", e)\n        if status_check == 0 and name_match == 1:\n            reason = \"Status check failed\"\n        elif status_check == 1 and name_match == 0:\n            reason = \"Name match failed\"\n        if status_check == 1 and name_match == 1:\n            is_passed = 1\n            reason = \"\"\n    return [is_passed, reason]",
            "handle_byte_array": "def handle_byte_array(byte_data):\n    # Check if the input is a byte object (either bytes or bytearray)\n    if isinstance(byte_data, (bytes, bytearray)):\n        # Decode it to a string\n        decoded_string = byte_data.decode('utf-8')\n        return decoded_string\n    else:\n        # If it's already a string, just return it\n        return byte_data",
            "check_track_record": "from datetime import datetime\ndef check_track_record(reported_date, net_loan_amount, ownership_dict, accounts, rule, bureau_type, account_type_dict):\n    passed_account = []\n    success = total_payment_grid = 0\n    rule = rule.get(\"value\")\n    ownership_type = rule.get(\"ownership\")\n    reported_date = '' if not reported_date else datetime.strptime(reported_date, '%Y%m%d')\n    relevant_ownerships = [ownership_dict.get(i.capitalize()) for i in ownership_type]\n    exception_account_types = rule.get(\"exception\")\n    exception_account_types = [i.replace(\" \", \"\").lower() for i in exception_account_types]\n    sanction_amount_threshold = rule.get(\"amount\")\n    percentage_threshold = (net_loan_amount * rule.get(\"percentage\")/100)\n    close_months = rule.get(\"close_months\")\n    open_months = rule.get(\"open_months\")\n    for account in accounts:\n        if str(account.get(\"AccountHoldertypeCode\")) in relevant_ownerships or str(account.get(\"sch:OwnershipType\")) in ownership_type:\n            if bureau_type == 'experian':\n                close_date_key = \"Date_Closed\"\n                account_type_key, account_amount_key = \"Account_Type\", \"Highest_Credit_or_Original_Loan_Amount\"\n                if account.get(\"CAIS_Account_History\") and isinstance(account.get(\"CAIS_Account_History\"), list):\n                    total_payment_grid = len(account.get(\"CAIS_Account_History\"))\n                elif isinstance(account.get(\"CAIS_Account_History\"), dict):\n                    total_payment_grid = 1\n            else:\n                close_date_key = \"sch:DateClosed\"\n                account_type_key, account_amount_key = \"sch:AccountType\", \"sch:SanctionAmount\"\n                if account.get(\"sch:History48Months\").get(\"sch:Month\") and isinstance(account.get(\"sch:History48Months\").get(\"sch:Month\"), list):\n                    total_payment_grid = len(account.get(\"sch:History48Months\").get(\"sch:Month\"))\n                elif isinstance(account.get(\"sch:History48Months\").get(\"sch:Month\"), dict):\n                    total_payment_grid = 1\n            threshold_amount = percentage_threshold if percentage_threshold < sanction_amount_threshold else sanction_amount_threshold\n            formatted_close_months = '' if not account.get(close_date_key) else datetime.strptime(account.get(close_date_key), '%Y%m%d')\n            account_close_months = calculate_bureau_months(reported_date, formatted_close_months)\n            if account_close_months is not None and account_close_months < close_months and total_payment_grid > open_months:\n                tradeline_account_type = account_type_dict.get(str(account.get(account_type_key))) if bureau_type == 'experian' else account.get(account_type_key)\n                if tradeline_account_type and tradeline_account_type.replace(\" \", \"\").lower() not in exception_account_types:\n                    if bureau_type == 'equifax' and not account.get(account_amount_key):\n                        account_amount_key = \"sch:HighCredit\"\n                    if account.get(account_amount_key) and float(account.get(account_amount_key)) > float(threshold_amount):\n                        success = 1\n                        passed_account = [account.get(\"Account_Number\")]\n                        break\n    return [success], passed_account",
            "check_ebill_name_match": "import json\ndef check_ebill_name_match(ebill_name_verify, threshold):\n    result = 0\n    name_matches = json.loads(ebill_name_verify) if ebill_name_verify else None\n    if name_matches:\n        if int(name_matches.get(\"applicant_match\")) >= int(threshold):\n            result = 1\n        if result == 0 and name_matches.get(\"co_applicant_match\") and int(name_matches.get(\"co_applicant_match\")) >= int(threshold):\n            result = 1\n    return result",
            "get_banking_month_count": "def get_banking_month_count(analytics_data):\n    from datetime import datetime\n    \n    # --- LIST SANITIZER ---\n    if isinstance(analytics_data, list):\n        analytics_data = analytics_data[0] if analytics_data else {}\n        \n    if not analytics_data or not isinstance(analytics_data, dict):\n        return 0\n        \n    account_profiles = analytics_data.get('accountProfiles', [])\n    if not account_profiles or not isinstance(account_profiles, list):\n        return 0\n        \n    min_date = None\n    max_date = None\n    \n    for profile in account_profiles:\n        summary = profile.get('summary', {})\n        start_str = summary.get('transactionStartDate')\n        end_str = summary.get('transactionEndDate')\n        if start_str:\n            try:\n                s_date = datetime.strptime(start_str, '%Y-%m-%d')\n                if min_date is None or s_date < min_date: min_date = s_date\n            except: pass\n        if end_str:\n            try:\n                e_date = datetime.strptime(end_str, '%Y-%m-%d')\n                if max_date is None or e_date > max_date: max_date = e_date\n            except: pass\n            \n    if not min_date or not max_date:\n        return 0\n        \n    days_gap = (datetime.now() - max_date).days\n    if days_gap > 45:\n        return 0 \n        \n    months = (max_date.year - min_date.year) * 12 + (max_date.month - min_date.month) + 1\n    return int(months)",
            "get_name_match_score": "def get_name_match_score(analytics_data, applicant_name):\n    from fuzzywuzzy import fuzz\n    \n    # --- LIST SANITIZER ---\n    if isinstance(analytics_data, list):\n        analytics_data = analytics_data[0] if analytics_data else {}\n\n    if not applicant_name or not analytics_data or not isinstance(analytics_data, dict):\n        return 0\n\n    account_profiles = analytics_data.get('accountProfiles', [])\n    if not account_profiles:\n        return 0\n\n    applicant_name = str(applicant_name).lower().strip()\n    max_score = 0\n\n    for profile in account_profiles:\n        holders = profile.get('Holders', {}).get('Holder', [])\n        if not holders:\n            continue\n        holder_name = holders[0].get('name', '')\n        if not holder_name:\n            continue\n        holder_name = str(holder_name).lower().strip()\n        name1_set = set(applicant_name.split())\n        name2_set = set(holder_name.split())\n        if name1_set.issubset(name2_set):\n            score = 100\n        else:\n            score = fuzz.ratio(applicant_name, holder_name)\n        if score > max_score:\n            max_score = score\n    return max_score",
            "get_recent_bounce_count": "def get_recent_bounce_count(analytics_data, lookback_months):\n    from datetime import datetime\n    from dateutil.relativedelta import relativedelta\n    analytics_data = analytics_data[0] if isinstance(analytics_data, list) and analytics_data else analytics_data\n    if not analytics_data or not isinstance(analytics_data, dict):\n        return 0\n    months_val = int(lookback_months) if str(lookback_months).isdigit() else 3\n    lookback_period = datetime.today().replace(day=1, hour=0, minute=0, second=0, microsecond=0) - relativedelta(months=months_val - 1)\n    bounce_root = analytics_data.get('bounceAnalysis', {})\n    target_data = bounce_root.get('completeBounceAnalysis') or bounce_root.get('monthlyBounceAnalysis') or bounce_root.get('bounceAnalysis') or bounce_root\n    total_bounce = 0\n    account_wise_list = target_data.get('accountWiseBounceAnalytics') if target_data else None\n    account_data = account_wise_list[0] if isinstance(account_wise_list, list) and account_wise_list else {}\n    monthly_data = account_data.get('monthlyBounceAnalysis', [])\n    for month_data in monthly_data:\n        try:\n            current_month_year = datetime.strptime(f\"{month_data.get('year')}-{month_data.get('month')}\", \"%Y-%m\")\n            if lookback_period <= current_month_year:\n                total_bounce += month_data.get('totalEmiBounceTxn', 0) if month_data.get('totalEmiBounceTxn', 0) > 0 and month_data.get('totalEmiBounceAmt', 0) > 0 else 0\n                total_bounce += month_data.get('totalEcsBounceChargeTxns', 0) if month_data.get('totalEcsBounceChargeTxns', 0) > 0 and month_data.get('totalEcsBounceChargeAmount', 0) > 0 else 0\n                total_bounce += month_data.get('totalInwardNonTechChequeBounceTxns', 0) if month_data.get('totalInwardNonTechChequeBounceTxns', 0) > 0 else 0\n                total_bounce += month_data.get('totalIWNonChqBounceChargeTxns', 0) if month_data.get('totalIWNonChqBounceChargeTxns', 0) > 0 else 0\n        except Exception:\n            continue\n    return int(total_bounce)",
            "get_speculative_spend_percentage": "def get_speculative_spend_percentage(analytics_data, monthly_income):\n    from datetime import datetime\n    \n    # --- LIST SANITIZER ---\n    if isinstance(analytics_data, list):\n        analytics_data = analytics_data[0] if analytics_data else {}\n\n    if not analytics_data or not monthly_income:\n        return 0.0\n    try:\n        income = float(monthly_income)\n        if income <= 0: return 0.0\n    except:\n        return 0.0\n\n    account_profiles = analytics_data.get('accountProfiles', [])\n    all_transactions = analytics_data.get('allTransactions', [])\n    min_date = None\n    max_date = None\n\n    if account_profiles and isinstance(account_profiles, list):\n        for profile in account_profiles:\n            summary = profile.get('summary', {})\n            start_str = summary.get('transactionStartDate')\n            end_str = summary.get('transactionEndDate')\n            if start_str:\n                try:\n                    s_date = datetime.strptime(start_str, '%Y-%m-%d')\n                    if min_date is None or s_date < min_date: min_date = s_date\n                except: pass\n            if end_str:\n                try:\n                    e_date = datetime.strptime(end_str, '%Y-%m-%d')\n                    if max_date is None or e_date > max_date: max_date = e_date\n                except: pass\n\n    months_count = 1\n    if min_date and max_date:\n        months_count = (max_date.year - min_date.year) * 12 + (max_date.month - min_date.month) + 1\n        if months_count < 1: months_count = 1\n\n    speculative_categories = [\"Gambling & Betting\", \"Gaming\"]\n    total_spec_amount = 0.0\n    for txn in all_transactions:\n        category = txn.get('category', '')\n        if category in speculative_categories:\n            amt = float(txn.get('amount', 0))\n            if amt < 0: total_spec_amount += abs(amt)\n            else: total_spec_amount += amt\n\n    avg_monthly_spec_spend = total_spec_amount / months_count\n    percentage = (avg_monthly_spec_spend / income) * 100\n    return float(f\"{percentage:.2f}\")",
            "checkAgeAtLastEMI": "from datetime import datetime\nfrom dateutil.relativedelta import relativedelta\ndef age_at_end_of_tenure(dob, tenure):\n    if isinstance(dob, str):\n        if \"-\" in dob:\n            dob = datetime.strptime(dob, \"%Y-%m-%d\")\n        else:\n            dob = datetime.strptime(dob, \"%Y%m%d\")\n\n    end_date = datetime.today() + relativedelta(months=int(tenure))\n    return relativedelta(end_date, dob).years"
          }
        ]
      }
    ]
  },
  "sources": {
    "sourceFile": {
      "cf": "cfwebbeta",
      "loc": "line_of_credit"
    },
    "connections": [
      {
        "db": "cfwebbeta",
        "status": true,
        "table": [
          {
            "consumer_application": [
              "loan_amount",
              "monthly_income",
              "dob",
              "employee_status"
            ],
            "emi_calculation": [
              "remaining_emi"
            ],
            "credit_report": [
              "Score",
              "panValidation"
            ],
            "foir": [
              "CFFoir"
            ],
            "status_logs": [
              "rejected"
            ]
          }
        ]
      },
      {
        "db": "line_of_credit",
        "table": [
          {
            "applications": [
              "loan_amount"
            ]
          }
        ]
      }
    ]
  }
}