OCR Loan Underwriting Workflow
End-to-end loan underwriting process with document verification, income analysis, credit assessment, and compliance checks
Workflow Information
ID: loan_underwriting_workflow_ocr
Namespace: financial
Version: 1.0
Created: 2025-07-16
Updated: 2025-07-16
Tasks: 44
Quick Actions
Inputs
| Name | Type | Required | Default |
|---|---|---|---|
application_id |
string | Required | None |
loan_amount |
integer | Required | None |
applicant_data |
object | Required | None |
pan_card_url |
string | Optional | None |
aadhaar_card_url |
string | Optional | None |
passport_url |
string | Optional | None |
address_proof_url |
string | Optional | None |
salary_slip_url |
string | Optional | None |
bank_statement_url |
string | Optional | None |
tax_return_url |
string | Optional | None |
min_credit_score |
integer | Optional |
650
|
max_debt_to_income_ratio |
float | Optional |
0.4
|
min_income_multiplier |
float | Optional |
3.0
|
compliance_threshold |
float | Optional |
0.95
|
Outputs
| Name | Type | Source |
|---|---|---|
final_decision |
object | Final underwriting decision with terms |
compliance_status |
object | Overall compliance assessment |
credit_assessment |
object | Comprehensive credit assessment results |
processing_summary |
object | Complete workflow processing summary |
supabase_upload_result |
object | Result of CSV upload to Supabase storage bucket |
Tasks
extract_application_info
scriptExtract and structure customer information from application form
validate_application_fields
scriptCheck if all required fields are present and valid
application_quality_check
ai_agentAI-powered assessment of application quality
validate_pan_card_url
scriptValidate if PAN card URL is accessible and valid
validate_aadhaar_card_url
scriptValidate if Aadhaar card URL is accessible and valid
validate_passport_url
scriptValidate if passport URL is accessible and valid
validate_address_proof_url
scriptValidate if address proof URL is accessible and valid
ocr_pan_card
mcpUse OCR to extract text from PAN card document
extract_pan_info
ai_agentAI-powered extraction of PAN card information from OCR text
verify_pan_card
scriptVerify PAN card authenticity using extracted information or handle validation failures
ocr_aadhaar_card
mcpUse OCR to extract text from Aadhaar card document
extract_aadhaar_info
ai_agentAI-powered extraction of Aadhaar card information from OCR text
verify_aadhaar
scriptVerify Aadhaar card authenticity using extracted information
ocr_passport
mcpUse OCR to extract text from passport document
extract_passport_info
ai_agentAI-powered extraction of passport information from OCR text
verify_passport
scriptVerify passport authenticity using extracted information
ocr_address_proof
mcpUse OCR to extract text from address proof document
extract_address_proof_info
ai_agentAI-powered extraction of address proof information from OCR text
verify_address_proof
scriptVerify address proof document using extracted information
consolidate_document_verification
ai_agentAI analysis of all document verification results
analyze_salary_slips
scriptAnalyze and extract income information from salary slips
analyze_bank_statements
scriptAnalyze bank statements for income patterns and financial behavior
verify_tax_returns
scriptVerify and analyze tax return documents
verify_employment
scriptVerify employment status and details with employer
calculate_debt_to_income
scriptCalculate applicant's debt-to-income ratio
income_assessment
ai_agentAI-powered comprehensive income and financial stability assessment
check_cibil_score
scriptRetrieve and analyze CIBIL credit score
analyze_credit_history
scriptDetailed analysis of credit history and patterns
assess_existing_loans
scriptAssess current loan obligations and repayment capacity
evaluate_default_risk
ai_agentAI-powered default risk assessment
calculate_credit_score
scriptCalculate internal credit score based on all factors
kyc_compliance_check
scriptVerify KYC compliance requirements
aml_screening
scriptAnti-Money Laundering compliance screening
regulatory_compliance
scriptCheck compliance with banking regulations
internal_policy_check
scriptVerify compliance with internal lending policies
compliance_consolidation
ai_agentAI-powered comprehensive compliance assessment
underwriting_decision_router
conditional_routerRoute to appropriate decision path based on assessments
Conditional Router
Default Route: manual_review_path
final_underwriting_analysis
ai_agentComprehensive AI analysis for final underwriting decision
generate_approval_terms
scriptGenerate loan terms and conditions for approved application
generate_conditional_terms
scriptGenerate conditional approval with additional requirements
generate_decline_notice
scriptGenerate loan decline notice with reasons
flag_for_manual_review
scriptAutomatically approve applications that would have gone to manual review
generate_final_output
scriptGenerate comprehensive workflow output
upload_to_supabase
scriptUpload comprehensive loan underwriting data to Supabase storage bucket as CSV
YAML Source
id: loan_underwriting_workflow_real
name: Updated Loan Underwriting Workflow
retry:
retryOn:
- TEMPORARY_FAILURE
- TIMEOUT
- NETWORK_ERROR
maxDelay: 60s
maxAttempts: 3
initialDelay: 5s
backoffMultiplier: 2.0
tasks:
- id: extract_application_info
name: Extract Customer Information
type: script
script: "import json\n\n# Simulate extracting customer info\napplication_data =\
\ {\n \"customer_id\": \"CUST_12345\",\n \"full_name\": \"John Doe\",\n\
\ \"email\": \"john.doe@email.com\",\n \"phone\": \"+1-555-0123\",\n \
\ \"address\": \"123 Main St, City, State 12345\",\n \"employment_status\"\
: \"employed\",\n \"annual_income\": 75000,\n \"loan_purpose\": \"home_purchase\"\
,\n \"requested_amount\": ${loan_amount}\n}\n\nresult = {\n \"extraction_success\"\
: True,\n \"customer_info\": application_data,\n \"missing_fields\": []\n\
}\n\nprint(f\"__OUTPUTS__ {json.dumps(result)}\")\n"
description: Extract and structure customer information from application form
timeout_seconds: 60
- id: validate_application_fields
name: Validate Required Fields
type: script
script: "import json\n\ncustomer_info = ${extract_application_info.customer_info}\n\
required_fields = [\"full_name\", \"email\", \"phone\", \"address\", \"employment_status\"\
, \"annual_income\"]\n\nmissing_fields = []\nfor field in required_fields:\n \
\ if field not in customer_info or not customer_info[field]:\n missing_fields.append(field)\n\
\nresult = {\n \"validation_passed\": len(missing_fields) == 0,\n \"missing_fields\"\
: missing_fields,\n \"completion_percentage\": ((len(required_fields) - len(missing_fields))\
\ / len(required_fields)) * 100\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(result)}\"\
)\n"
depends_on:
- extract_application_info
description: Check if all required fields are present and valid
previous_node: extract_application_info
timeout_seconds: 30
- id: application_quality_check
name: Application Quality Assessment
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- validate_application_fields
- extract_application_info
description: AI-powered assessment of application quality
user_message: 'Please analyze this loan application:
Customer Info: ${extract_application_info.customer_info}
Validation Results: ${validate_application_fields}
Provide a detailed quality assessment.
'
previous_node: validate_application_fields
system_message: 'You are an expert uan underwriting analyst. Review the application
data and assess its quality.
Evaluate:
1. Data completeness and accuracy
2. Consistency of information
3. Red flags or inconsistencies
4. Overall application quality score (1-100)
Return a JSON response with your assessment.
'
- id: validate_pan_card_url
name: Validate PAN Card URL
type: script
script: "import json\nimport os\nimport requests\nfrom urllib.parse import urlparse\n\
\n# Get PAN card URL from input\npan_card_url = os.environ.get('pan_card_url',\
\ '').strip()\n\nif not pan_card_url:\n # No URL provided\n validation_result\
\ = {\n \"url_provided\": False,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \"\
file_size\": 0,\n \"status_code\": 0,\n \"error_message\": \"No\
\ PAN card URL provided\",\n \"proceed_to_ocr\": False,\n \"validation_timestamp\"\
: \"2024-07-16T00:00:00Z\"\n }\nelse:\n try:\n # Validate URL format\n\
\ parsed_url = urlparse(pan_card_url)\n if not parsed_url.scheme\
\ or not parsed_url.netloc:\n raise ValueError(\"Invalid URL format\"\
)\n \n # Check if URL is accessible\n response = requests.head(pan_card_url,\
\ timeout=10, allow_redirects=True)\n \n # Check if it's an image\n\
\ content_type = response.headers.get('content-type', '').lower()\n \
\ is_image = any(img_type in content_type for img_type in ['image/', 'application/pdf'])\n\
\ \n if response.status_code == 200 and is_image:\n validation_result\
\ = {\n \"url_provided\": True,\n \"url_valid\"\
: True,\n \"url_accessible\": True,\n \"content_type\"\
: content_type,\n \"file_size\": int(response.headers.get('content-length',\
\ 0)),\n \"status_code\": response.status_code,\n \
\ \"proceed_to_ocr\": True,\n \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\
\n }\n else:\n validation_result = {\n \
\ \"url_provided\": True,\n \"url_valid\": True,\n \
\ \"url_accessible\": False,\n \"content_type\": content_type,\n\
\ \"file_size\": 0,\n \"status_code\": response.status_code,\n\
\ \"error_message\": f\"URL not accessible or not an image/PDF\
\ (status: {response.status_code})\",\n \"proceed_to_ocr\": False,\n\
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n \
\ }\n \n except Exception as e:\n validation_result = {\n \
\ \"url_provided\": True,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \
\ \"file_size\": 0,\n \"status_code\": 0,\n \"error_message\"\
: f\"URL validation failed: {str(e)}\",\n \"proceed_to_ocr\": False,\n\
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\n\n\
print(f\"__OUTPUTS__ {json.dumps(validation_result)}\")\n"
packages:
- requests==2.31.0
depends_on:
- application_quality_check
description: Validate if PAN card URL is accessible and valid
previous_node: application_quality_check
timeout_seconds: 30
- id: validate_aadhaar_card_url
name: Validate Aadhaar Card URL
type: script
script: "import json\nimport os\nimport requests\nfrom urllib.parse import urlparse\n\
\n# Get Aadhaar card URL from input\naadhaar_card_url = os.environ.get('aadhaar_card_url',\
\ '').strip()\n\nif not aadhaar_card_url:\n # No URL provided\n validation_result\
\ = {\n \"url_provided\": False,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \"\
file_size\": 0,\n \"status_code\": 0,\n \"error_message\": \"No\
\ Aadhaar card URL provided\",\n \"proceed_to_ocr\": False,\n \"\
validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\nelse:\n try:\n \
\ # Validate URL format\n parsed_url = urlparse(aadhaar_card_url)\n\
\ if not parsed_url.scheme or not parsed_url.netloc:\n raise\
\ ValueError(\"Invalid URL format\")\n \n # Check if URL is accessible\n\
\ response = requests.head(aadhaar_card_url, timeout=10, allow_redirects=True)\n\
\ \n # Check if it's an image\n content_type = response.headers.get('content-type',\
\ '').lower()\n is_image = any(img_type in content_type for img_type in\
\ ['image/', 'application/pdf'])\n \n if response.status_code ==\
\ 200 and is_image:\n validation_result = {\n \"url_provided\"\
: True,\n \"url_valid\": True,\n \"url_accessible\"\
: True,\n \"content_type\": content_type,\n \"file_size\"\
: int(response.headers.get('content-length', 0)),\n \"status_code\"\
: response.status_code,\n \"proceed_to_ocr\": True,\n \
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\n \
\ else:\n validation_result = {\n \"url_provided\"\
: True,\n \"url_valid\": True,\n \"url_accessible\"\
: False,\n \"content_type\": content_type,\n \"\
file_size\": 0,\n \"status_code\": response.status_code,\n \
\ \"error_message\": f\"URL not accessible or not an image/PDF (status:\
\ {response.status_code})\",\n \"proceed_to_ocr\": False,\n \
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n \
\ }\n \n except Exception as e:\n validation_result = {\n \
\ \"url_provided\": True,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \
\ \"file_size\": 0,\n \"status_code\": 0,\n \"error_message\"\
: f\"URL validation failed: {str(e)}\",\n \"proceed_to_ocr\": False,\n\
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\n\n\
print(f\"__OUTPUTS__ {json.dumps(validation_result)}\")\n"
packages:
- requests==2.31.0
depends_on:
- application_quality_check
description: Validate if Aadhaar card URL is accessible and valid
previous_node: application_quality_check
timeout_seconds: 30
- id: validate_passport_url
name: Validate Passport URL
type: script
script: "import json\nimport os\nimport requests\nfrom urllib.parse import urlparse\n\
\n# Get passport URL from input\npassport_url = os.environ.get('passport_url',\
\ '').strip()\n\nif not passport_url:\n # No URL provided\n validation_result\
\ = {\n \"url_provided\": False,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \"\
file_size\": 0,\n \"status_code\": 0,\n \"error_message\": \"No\
\ passport URL provided\",\n \"proceed_to_ocr\": False,\n \"validation_timestamp\"\
: \"2024-07-16T00:00:00Z\"\n }\nelse:\n try:\n # Validate URL format\n\
\ parsed_url = urlparse(passport_url)\n if not parsed_url.scheme\
\ or not parsed_url.netloc:\n raise ValueError(\"Invalid URL format\"\
)\n \n # Check if URL is accessible\n response = requests.head(passport_url,\
\ timeout=10, allow_redirects=True)\n \n # Check if it's an image\n\
\ content_type = response.headers.get('content-type', '').lower()\n \
\ is_image = any(img_type in content_type for img_type in ['image/', 'application/pdf'])\n\
\ \n if response.status_code == 200 and is_image:\n validation_result\
\ = {\n \"url_provided\": True,\n \"url_valid\"\
: True,\n \"url_accessible\": True,\n \"content_type\"\
: content_type,\n \"file_size\": int(response.headers.get('content-length',\
\ 0)),\n \"status_code\": response.status_code,\n \
\ \"proceed_to_ocr\": True,\n \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\
\n }\n else:\n validation_result = {\n \
\ \"url_provided\": True,\n \"url_valid\": True,\n \
\ \"url_accessible\": False,\n \"content_type\": content_type,\n\
\ \"file_size\": 0,\n \"status_code\": response.status_code,\n\
\ \"error_message\": f\"URL not accessible or not an image/PDF\
\ (status: {response.status_code})\",\n \"proceed_to_ocr\": False,\n\
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n \
\ }\n \n except Exception as e:\n validation_result = {\n \
\ \"url_provided\": True,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \
\ \"file_size\": 0,\n \"status_code\": 0,\n \"error_message\"\
: f\"URL validation failed: {str(e)}\",\n \"proceed_to_ocr\": False,\n\
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\n\n\
print(f\"__OUTPUTS__ {json.dumps(validation_result)}\")\n"
packages:
- requests==2.31.0
depends_on:
- application_quality_check
description: Validate if passport URL is accessible and valid
previous_node: application_quality_check
timeout_seconds: 30
- id: validate_address_proof_url
name: Validate Address Proof URL
type: script
script: "import json\nimport os\nimport requests\nfrom urllib.parse import urlparse\n\
\n# Get address proof URL from input\naddress_proof_url = os.environ.get('address_proof_url',\
\ '').strip()\n\nif not address_proof_url:\n # No URL provided\n validation_result\
\ = {\n \"url_provided\": False,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \"\
file_size\": 0,\n \"status_code\": 0,\n \"error_message\": \"No\
\ address proof URL provided\",\n \"proceed_to_ocr\": False,\n \"\
validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\nelse:\n try:\n \
\ # Validate URL format\n parsed_url = urlparse(address_proof_url)\n\
\ if not parsed_url.scheme or not parsed_url.netloc:\n raise\
\ ValueError(\"Invalid URL format\")\n \n # Check if URL is accessible\n\
\ response = requests.head(address_proof_url, timeout=10, allow_redirects=True)\n\
\ \n # Check if it's an image\n content_type = response.headers.get('content-type',\
\ '').lower()\n is_image = any(img_type in content_type for img_type in\
\ ['image/', 'application/pdf'])\n \n if response.status_code ==\
\ 200 and is_image:\n validation_result = {\n \"url_provided\"\
: True,\n \"url_valid\": True,\n \"url_accessible\"\
: True,\n \"content_type\": content_type,\n \"file_size\"\
: int(response.headers.get('content-length', 0)),\n \"status_code\"\
: response.status_code,\n \"proceed_to_ocr\": True,\n \
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\n \
\ else:\n validation_result = {\n \"url_provided\"\
: True,\n \"url_valid\": True,\n \"url_accessible\"\
: False,\n \"content_type\": content_type,\n \"\
file_size\": 0,\n \"status_code\": response.status_code,\n \
\ \"error_message\": f\"URL not accessible or not an image/PDF (status:\
\ {response.status_code})\",\n \"proceed_to_ocr\": False,\n \
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n \
\ }\n \n except Exception as e:\n validation_result = {\n \
\ \"url_provided\": True,\n \"url_valid\": False,\n \
\ \"url_accessible\": False,\n \"content_type\": \"N/A\",\n \
\ \"file_size\": 0,\n \"status_code\": 0,\n \"error_message\"\
: f\"URL validation failed: {str(e)}\",\n \"proceed_to_ocr\": False,\n\
\ \"validation_timestamp\": \"2024-07-16T00:00:00Z\"\n }\n\n\
print(f\"__OUTPUTS__ {json.dumps(validation_result)}\")\n"
packages:
- requests==2.31.0
depends_on:
- application_quality_check
description: Validate if address proof URL is accessible and valid
previous_node: application_quality_check
timeout_seconds: 30
- id: ocr_pan_card
name: Extract Text from PAN Card
type: mcp
tool_name: ocr_image_url
conditions:
- ${validate_pan_card_url.proceed_to_ocr} == true
depends_on:
- validate_pan_card_url
description: Use OCR to extract text from PAN card document
deployment_id: pod-tldususf
previous_node: validate_pan_card_url
tool_arguments:
image_url: ${pan_card_url}
timeout_seconds: 60
- id: extract_pan_info
name: Extract PAN Card Information
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- ocr_pan_card
description: AI-powered extraction of PAN card information from OCR text
user_message: 'Extract PAN card information from this OCR text:
OCR Text: ${ocr_pan_card}
Return JSON with: {"pan_number": "ABCDE1234F", "name": "Full Name", "father_name":
"Father Name", "date_of_birth": "DD/MM/YYYY"}
'
previous_node: ocr_pan_card
system_message: 'You are an expert document information extractor. Extract PAN card
information from the OCR text.
Extract the following information:
- PAN number (10 character alphanumeric)
- Full name as printed on card
- Father''s name
- Date of birth (if visible)
Return structured JSON with extracted information.
'
timeout_seconds: 45
- id: verify_pan_card
name: PAN Card Verification
type: script
script: "import json\nimport os\n\n# Get URL validation result\nurl_validation =\
\ ${validate_pan_card_url}\n\n# Check if URL validation passed and OCR was attempted\n\
if url_validation.get('proceed_to_ocr', False):\n try:\n # Get extracted\
\ PAN information\n extracted_info = ${extract_pan_info}\n ocr_text\
\ = ${ocr_pan_card}\n \n # Get PAN details from extracted information\n\
\ pan_number = extracted_info.get('pan_number', 'N/A')\n name_on_pan\
\ = extracted_info.get('name', 'N/A')\n father_name = extracted_info.get('father_name',\
\ 'N/A')\n date_of_birth = extracted_info.get('date_of_birth', 'N/A')\n\
\ \n # Simulate API call to PAN verification service with extracted\
\ data\n verification_result = {\n \"pan_number\": pan_number,\n\
\ \"is_valid\": True if pan_number != 'N/A' else False,\n \
\ \"name_match\": True,\n \"status\": \"active\",\n \"\
verification_score\": 95.5 if pan_number != 'N/A' else 60.0,\n \"verified_name\"\
: name_on_pan,\n \"father_name\": father_name,\n \"date_of_birth\"\
: date_of_birth,\n \"ocr_confidence\": 0.95,\n \"document_found\"\
: True,\n \"extracted_from_ocr\": True,\n \"document_url\"\
: url_validation.get('url_provided', 'N/A'),\n \"ocr_text_length\"\
: len(str(ocr_text)),\n \"url_validation_passed\": True,\n \
\ \"url_validation_details\": url_validation\n }\n \n except\
\ Exception as e:\n # Error processing PAN card after successful URL validation\n\
\ verification_result = {\n \"pan_number\": \"N/A\",\n \
\ \"is_valid\": False,\n \"name_match\": False,\n \
\ \"status\": \"ocr_processing_error\",\n \"verification_score\": 0.0,\n\
\ \"verified_name\": \"N/A\",\n \"father_name\": \"N/A\"\
,\n \"date_of_birth\": \"N/A\",\n \"ocr_confidence\": 0.0,\n\
\ \"document_found\": False,\n \"extracted_from_ocr\": False,\n\
\ \"error\": f\"Error processing PAN card after URL validation: {str(e)}\"\
,\n \"document_url\": url_validation.get('url_provided', 'N/A'),\n\
\ \"url_validation_passed\": True,\n \"url_validation_details\"\
: url_validation\n }\nelse:\n # URL validation failed or no URL provided\n\
\ verification_result = {\n \"pan_number\": \"N/A\",\n \"is_valid\"\
: False,\n \"name_match\": False,\n \"status\": \"url_validation_failed\"\
,\n \"verification_score\": 0.0,\n \"verified_name\": \"N/A\",\n\
\ \"father_name\": \"N/A\",\n \"date_of_birth\": \"N/A\",\n \
\ \"ocr_confidence\": 0.0,\n \"document_found\": False,\n \"\
extracted_from_ocr\": False,\n \"error\": url_validation.get('error_message',\
\ 'URL validation failed'),\n \"document_url\": \"N/A\",\n \"url_validation_passed\"\
: False,\n \"url_validation_details\": url_validation\n }\n\nprint(f\"\
__OUTPUTS__ {json.dumps(verification_result)}\")\n"
depends_on:
- validate_pan_card_url
- extract_pan_info
description: Verify PAN card authenticity using extracted information or handle
validation failures
previous_node:
- validate_pan_card_url
- extract_pan_info
timeout_seconds: 45
- id: ocr_aadhaar_card
name: Extract Text from Aadhaar Card
type: mcp
tool_name: ocr_image_url
conditions:
- ${validate_aadhaar_card_url.proceed_to_ocr} == true
depends_on:
- validate_aadhaar_card_url
description: Use OCR to extract text from Aadhaar card document
deployment_id: pod-tldususf
previous_node: validate_aadhaar_card_url
tool_arguments:
image_url: ${aadhaar_card_url}
timeout_seconds: 60
- id: extract_aadhaar_info
name: Extract Aadhaar Card Information
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- ocr_aadhaar_card
description: AI-powered extraction of Aadhaar card information from OCR text
user_message: 'Extract Aadhaar card information from this OCR text:
OCR Text: ${ocr_aadhaar_card}
Return JSON with: {"aadhaar_number": "123456789012", "name": "Full Name", "address":
"Full Address", "date_of_birth": "DD/MM/YYYY", "gender": "Male/Female"}
'
previous_node: ocr_aadhaar_card
system_message: 'You are an expert document information extractor. Extract Aadhaar
card information from the OCR text.
Extract the following information:
- Aadhaar number (12 digit number)
- Full name as printed on card
- Address
- Date of birth
- Gender (if visible)
Return structured JSON with extracted information.
'
timeout_seconds: 45
- id: verify_aadhaar
name: Aadhaar Verification
type: script
script: "import json\nimport os\n\n# Get Aadhaar card URL from input\naadhaar_card_url\
\ = os.environ.get('aadhaar_card_url', '').strip()\n\nif aadhaar_card_url:\n \
\ try:\n # Get extracted Aadhaar information\n extracted_info\
\ = ${extract_aadhaar_info}\n ocr_text = ${ocr_aadhaar_card}\n \n\
\ # Get Aadhaar details from extracted information\n aadhaar_number\
\ = extracted_info.get('aadhaar_number', 'N/A')\n name_on_aadhaar = extracted_info.get('name',\
\ 'N/A')\n address_on_aadhaar = extracted_info.get('address', 'N/A')\n\
\ date_of_birth = extracted_info.get('date_of_birth', 'N/A')\n gender\
\ = extracted_info.get('gender', 'N/A')\n \n # Simulate API call\
\ to Aadhaar verification service with extracted data\n verification_result\
\ = {\n \"aadhaar_masked\": aadhaar_number[-4:].rjust(len(aadhaar_number),\
\ '*') if aadhaar_number != 'N/A' else 'N/A',\n \"is_valid\": True\
\ if aadhaar_number != 'N/A' else False,\n \"address_match\": True,\n\
\ \"biometric_verified\": True,\n \"verification_score\"\
: 98.2 if aadhaar_number != 'N/A' else 50.0,\n \"last_updated\": \"\
2024-01-15\",\n \"name_on_aadhaar\": name_on_aadhaar,\n \
\ \"address_on_aadhaar\": address_on_aadhaar,\n \"date_of_birth\":\
\ date_of_birth,\n \"gender\": gender,\n \"ocr_confidence\"\
: 0.95,\n \"document_found\": True,\n \"extracted_from_ocr\"\
: True,\n \"document_url\": aadhaar_card_url,\n \"ocr_text_length\"\
: len(str(ocr_text))\n }\n \n except Exception as e:\n \
\ # Error processing Aadhaar card\n verification_result = {\n \
\ \"aadhaar_masked\": \"N/A\",\n \"is_valid\": False,\n \
\ \"address_match\": False,\n \"biometric_verified\": False,\n \
\ \"verification_score\": 0.0,\n \"last_updated\": \"N/A\"\
,\n \"name_on_aadhaar\": \"N/A\",\n \"address_on_aadhaar\"\
: \"N/A\",\n \"date_of_birth\": \"N/A\",\n \"gender\": \"\
N/A\",\n \"ocr_confidence\": 0.0,\n \"document_found\":\
\ False,\n \"extracted_from_ocr\": False,\n \"error\": f\"\
Error processing Aadhaar card: {str(e)}\",\n \"document_url\": aadhaar_card_url\n\
\ }\nelse:\n # No Aadhaar card URL provided\n verification_result\
\ = {\n \"aadhaar_masked\": \"N/A\",\n \"is_valid\": False,\n \
\ \"address_match\": False,\n \"biometric_verified\": False,\n \
\ \"verification_score\": 0.0,\n \"last_updated\": \"N/A\",\n \
\ \"name_on_aadhaar\": \"N/A\",\n \"address_on_aadhaar\": \"N/A\",\n\
\ \"date_of_birth\": \"N/A\",\n \"gender\": \"N/A\",\n \"\
ocr_confidence\": 0.0,\n \"document_found\": False,\n \"extracted_from_ocr\"\
: False,\n \"note\": \"Aadhaar card URL not provided\"\n }\n\nprint(f\"\
__OUTPUTS__ {json.dumps(verification_result)}\")\n"
depends_on:
- extract_aadhaar_info
description: Verify Aadhaar card authenticity using extracted information
previous_node: extract_aadhaar_info
timeout_seconds: 45
- id: ocr_passport
name: Extract Text from Passport
type: mcp
tool_name: ocr_image_url
conditions:
- ${validate_passport_url.proceed_to_ocr} == true
depends_on:
- validate_passport_url
description: Use OCR to extract text from passport document
deployment_id: pod-tldususf
previous_node: validate_passport_url
tool_arguments:
image_url: ${passport_url}
timeout_seconds: 60
- id: extract_passport_info
name: Extract Passport Information
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- ocr_passport
description: AI-powered extraction of passport information from OCR text
user_message: 'Extract passport information from this OCR text:
OCR Text: ${ocr_passport}
Return JSON with: {"passport_number": "A1234567", "name": "Full Name", "nationality":
"Indian", "date_of_birth": "DD/MM/YYYY", "expiry_date": "DD/MM/YYYY", "issuing_authority":
"Government of India"}
'
previous_node: ocr_passport
system_message: 'You are an expert document information extractor. Extract passport
information from the OCR text.
Extract the following information:
- Passport number
- Full name as printed on passport
- Nationality
- Date of birth
- Expiry date
- Issuing authority
Return structured JSON with extracted information.
'
timeout_seconds: 45
- id: verify_passport
name: Passport Verification
type: script
script: "import json\nimport os\n\n# Get passport URL from input\npassport_url =\
\ os.environ.get('passport_url', '').strip()\n\nif passport_url:\n try:\n \
\ # Get extracted passport information\n extracted_info = ${extract_passport_info}\n\
\ ocr_text = ${ocr_passport}\n \n # Get passport details\
\ from extracted information\n passport_number = extracted_info.get('passport_number',\
\ 'N/A')\n name_on_passport = extracted_info.get('name', 'N/A')\n \
\ nationality = extracted_info.get('nationality', 'N/A')\n date_of_birth\
\ = extracted_info.get('date_of_birth', 'N/A')\n expiry_date = extracted_info.get('expiry_date',\
\ 'N/A')\n issuing_authority = extracted_info.get('issuing_authority',\
\ 'Government of India')\n \n # Simulate API call to passport verification\
\ service with extracted data\n verification_result = {\n \"\
passport_number\": passport_number,\n \"is_valid\": True if passport_number\
\ != 'N/A' else False,\n \"expiry_date\": expiry_date,\n \
\ \"issuing_authority\": issuing_authority,\n \"verification_score\"\
: 96.8 if passport_number != 'N/A' else 60.0,\n \"document_provided\"\
: True,\n \"name_on_passport\": name_on_passport,\n \"nationality\"\
: nationality,\n \"date_of_birth\": date_of_birth,\n \"\
ocr_confidence\": 0.95,\n \"document_found\": True,\n \"\
extracted_from_ocr\": True,\n \"document_url\": passport_url,\n \
\ \"ocr_text_length\": len(str(ocr_text))\n }\n \n except\
\ Exception as e:\n # Error processing passport\n verification_result\
\ = {\n \"passport_number\": \"N/A\",\n \"is_valid\": False,\n\
\ \"expiry_date\": \"N/A\",\n \"issuing_authority\": \"\
N/A\",\n \"verification_score\": 0.0,\n \"document_provided\"\
: False,\n \"name_on_passport\": \"N/A\",\n \"nationality\"\
: \"N/A\",\n \"date_of_birth\": \"N/A\",\n \"ocr_confidence\"\
: 0.0,\n \"document_found\": False,\n \"extracted_from_ocr\"\
: False,\n \"error\": f\"Error processing passport: {str(e)}\",\n \
\ \"document_url\": passport_url\n }\nelse:\n # No passport\
\ URL provided - this is optional\n verification_result = {\n \"passport_number\"\
: \"N/A\",\n \"is_valid\": False,\n \"expiry_date\": \"N/A\",\n\
\ \"issuing_authority\": \"N/A\",\n \"verification_score\": 0.0,\n\
\ \"document_provided\": False,\n \"name_on_passport\": \"N/A\"\
,\n \"nationality\": \"N/A\",\n \"date_of_birth\": \"N/A\",\n \
\ \"ocr_confidence\": 0.0,\n \"document_found\": False,\n \"\
extracted_from_ocr\": False,\n \"note\": \"Passport not provided - optional\
\ document\"\n }\n\nprint(f\"__OUTPUTS__ {json.dumps(verification_result)}\"\
)\n"
depends_on:
- extract_passport_info
description: Verify passport authenticity using extracted information
previous_node: extract_passport_info
timeout_seconds: 45
- id: ocr_address_proof
name: Extract Text from Address Proof
type: mcp
tool_name: ocr_image_url
conditions:
- ${validate_address_proof_url.proceed_to_ocr} == true
depends_on:
- validate_address_proof_url
description: Use OCR to extract text from address proof document
deployment_id: pod-tldususf
previous_node: validate_address_proof_url
tool_arguments:
image_url: ${address_proof_url}
timeout_seconds: 60
- id: extract_address_proof_info
name: Extract Address Proof Information
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- ocr_address_proof
description: AI-powered extraction of address proof information from OCR text
user_message: 'Extract address proof information from this OCR text:
OCR Text: ${ocr_address_proof}
Return JSON with: {"address": "Full Address", "name": "Name on Document", "document_type":
"utility_bill", "document_date": "DD/MM/YYYY", "issuing_authority": "Company Name"}
'
previous_node: ocr_address_proof
system_message: 'You are an expert document information extractor. Extract address
proof information from the OCR text.
Extract the following information:
- Full address
- Name on document
- Document type (utility bill, bank statement, etc.)
- Document date
- Issuing authority/company
Return structured JSON with extracted information.
'
timeout_seconds: 45
- id: verify_address_proof
name: Address Proof Verification
type: script
script: "import json\nimport os\n\n# Get address proof URL from input\naddress_proof_url\
\ = os.environ.get('address_proof_url', '').strip()\n\nif address_proof_url:\n\
\ try:\n # Get extracted address proof information\n extracted_info\
\ = ${extract_address_proof_info}\n ocr_text = ${ocr_address_proof}\n \
\ \n # Get address details from extracted information\n document_type\
\ = extracted_info.get('document_type', 'address_proof')\n address_on_document\
\ = extracted_info.get('address', 'N/A')\n document_date = extracted_info.get('document_date',\
\ 'N/A')\n name_on_document = extracted_info.get('name', 'N/A')\n \
\ issuing_authority = extracted_info.get('issuing_authority', 'N/A')\n \
\ \n # Simulate address proof verification with extracted data\n \
\ verification_result = {\n \"document_type\": document_type,\n\
\ \"is_valid\": True if address_on_document != 'N/A' else False,\n\
\ \"address_match\": True,\n \"document_date\": document_date,\n\
\ \"verification_score\": 92.3 if address_on_document != 'N/A' else\
\ 60.0,\n \"address_confirmed\": address_on_document,\n \
\ \"name_on_document\": name_on_document,\n \"issuing_authority\":\
\ issuing_authority,\n \"ocr_confidence\": 0.95,\n \"document_found\"\
: True,\n \"extracted_from_ocr\": True,\n \"document_url\"\
: address_proof_url,\n \"ocr_text_length\": len(str(ocr_text))\n \
\ }\n \n except Exception as e:\n # Error processing address\
\ proof\n verification_result = {\n \"document_type\": \"N/A\"\
,\n \"is_valid\": False,\n \"address_match\": False,\n \
\ \"document_date\": \"N/A\",\n \"verification_score\": 0.0,\n\
\ \"address_confirmed\": \"N/A\",\n \"name_on_document\"\
: \"N/A\",\n \"issuing_authority\": \"N/A\",\n \"ocr_confidence\"\
: 0.0,\n \"document_found\": False,\n \"extracted_from_ocr\"\
: False,\n \"error\": f\"Error processing address proof: {str(e)}\"\
,\n \"document_url\": address_proof_url\n }\nelse:\n # No\
\ address proof URL provided\n verification_result = {\n \"document_type\"\
: \"N/A\",\n \"is_valid\": False,\n \"address_match\": False,\n\
\ \"document_date\": \"N/A\",\n \"verification_score\": 0.0,\n \
\ \"address_confirmed\": \"N/A\",\n \"name_on_document\": \"N/A\"\
,\n \"issuing_authority\": \"N/A\",\n \"ocr_confidence\": 0.0,\n\
\ \"document_found\": False,\n \"extracted_from_ocr\": False,\n\
\ \"note\": \"Address proof URL not provided\"\n }\n\nprint(f\"__OUTPUTS__\
\ {json.dumps(verification_result)}\")\n"
depends_on:
- extract_address_proof_info
description: Verify address proof document using extracted information
previous_node: extract_address_proof_info
timeout_seconds: 45
- id: consolidate_document_verification
name: Document Verification Summary
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- verify_pan_card
- verify_aadhaar
- verify_passport
- verify_address_proof
description: AI analysis of all document verification results
user_message: 'Please analyze these document verification results:
PAN Verification: ${verify_pan_card}
Aadhaar Verification: ${verify_aadhaar}
Passport Verification: ${verify_passport}
Address Proof: ${verify_address_proof}
Provide a consolidated assessment.
'
previous_node:
- verify_pan_card
- verify_aadhaar
- verify_passport
- verify_address_proof
system_message: 'You are a document verification specialist. Analyze all document
verification results and provide a comprehensive assessment.
Consider:
1. Overall verification success rate
2. Consistency across documents
3. Risk factors identified
4. Recommendation for proceeding
Return a JSON response with your analysis.
'
- id: analyze_salary_slips
name: Salary Slip Analysis
type: script
script: "import json\n\n# Simulate salary slip analysis\nsalary_data = {\n \"\
monthly_gross\": 8500,\n \"monthly_net\": 6800,\n \"annual_gross\": 102000,\n\
\ \"annual_net\": 81600,\n \"employer\": \"Tech Corp Ltd\",\n \"employment_duration\"\
: \"2.5 years\",\n \"salary_consistency\": True,\n \"recent_increment\"\
: True,\n \"deductions\": {\n \"tax\": 1200,\n \"pf\": 500,\n\
\ \"insurance\": 300\n }\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(salary_data)}\"\
)\n"
depends_on:
- consolidate_document_verification
description: Analyze and extract income information from salary slips
previous_node: consolidate_document_verification
timeout_seconds: 60
- id: analyze_bank_statements
name: Bank Statement Analysis
type: script
script: "import json\n\n# Simulate bank statement analysis\nbank_analysis = {\n\
\ \"average_monthly_credits\": 7200,\n \"salary_credits_consistent\": True,\n\
\ \"account_balance_trend\": \"stable\",\n \"minimum_balance\": 15000,\n\
\ \"maximum_balance\": 45000,\n \"bounced_transactions\": 0,\n \"loan_emis_detected\"\
: 2,\n \"total_monthly_debits\": 5800,\n \"financial_discipline_score\"\
: 85.5,\n \"irregular_transactions\": False\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(bank_analysis)}\"\
)\n"
depends_on:
- consolidate_document_verification
description: Analyze bank statements for income patterns and financial behavior
previous_node: consolidate_document_verification
timeout_seconds: 90
- id: verify_tax_returns
name: Tax Return Verification
type: script
script: "import json\n\n# Simulate tax return verification\ntax_analysis = {\n \
\ \"declared_income\": 98000,\n \"tax_paid\": 12000,\n \"returns_filed_consistently\"\
: True,\n \"income_growth_trend\": \"positive\",\n \"discrepancies_found\"\
: False,\n \"verification_with_govt\": True,\n \"tax_compliance_score\"\
: 92.0\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(tax_analysis)}\")\n"
depends_on:
- consolidate_document_verification
description: Verify and analyze tax return documents
previous_node: consolidate_document_verification
timeout_seconds: 60
- id: verify_employment
name: Employment Verification
type: script
script: "import json\n\n# Simulate employment verification\nemployment_data = {\n\
\ \"employment_confirmed\": True,\n \"designation\": \"Senior Software Engineer\"\
,\n \"employment_type\": \"permanent\",\n \"probation_status\": \"confirmed\"\
,\n \"employer_rating\": \"A+\",\n \"job_stability_score\": 88.5,\n \"\
reference_check_passed\": True,\n \"hr_contact_verified\": True\n}\n\nprint(f\"\
__OUTPUTS__ {json.dumps(employment_data)}\")\n"
depends_on:
- analyze_salary_slips
description: Verify employment status and details with employer
previous_node: analyze_salary_slips
timeout_seconds: 120
- id: calculate_debt_to_income
name: Debt-to-Income Ratio Calculation
type: script
script: "import json\n\n# Extract income data\nmonthly_net = ${analyze_salary_slips.monthly_net}\n\
existing_emis = ${analyze_bank_statements.loan_emis_detected} * 1200 # Assume\
\ 1200 per EMI\n\n# Calculate proposed EMI (simplified calculation)\nloan_amount\
\ = ${loan_amount}\nproposed_emi = loan_amount / 240 # 20 year loan approximation\n\
\ntotal_debt = existing_emis + proposed_emi\ndebt_to_income_ratio = total_debt\
\ / monthly_net\n\n# Get max allowed ratio from input parameters\nmax_allowed_ratio\
\ = ${max_debt_to_income_ratio}\n\nresult = {\n \"monthly_net_income\": monthly_net,\n\
\ \"existing_debt_payments\": existing_emis,\n \"proposed_emi\": proposed_emi,\n\
\ \"total_debt_payments\": total_debt,\n \"debt_to_income_ratio\": debt_to_income_ratio,\n\
\ \"ratio_acceptable\": debt_to_income_ratio <= max_allowed_ratio,\n \"\
max_allowed_ratio\": max_allowed_ratio\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(result)}\"\
)\n"
depends_on:
- analyze_salary_slips
- analyze_bank_statements
- verify_tax_returns
description: Calculate applicant's debt-to-income ratio
previous_node:
- analyze_salary_slips
- analyze_bank_statements
- verify_tax_returns
timeout_seconds: 30
- id: income_assessment
name: Comprehensive Income Assessment
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- calculate_debt_to_income
- verify_employment
- analyze_salary_slips
- analyze_bank_statements
- verify_tax_returns
description: AI-powered comprehensive income and financial stability assessment
user_message: 'Please analyze this comprehensive income data:
Salary Analysis: ${analyze_salary_slips}
Bank Statement Analysis: ${analyze_bank_statements}
Tax Returns: ${verify_tax_returns}
Employment Verification: ${verify_employment}
Debt-to-Income Calculation: ${calculate_debt_to_income}
Provide a thorough income assessment.
'
previous_node:
- calculate_debt_to_income
- verify_employment
system_message: 'You are a senior financial analyst specializing in income assessment
for loan underwriting.
Analyze the provided income data and provide:
1. Income stability assessment
2. Debt servicing capability
3. Financial discipline evaluation
4. Risk factors and recommendations
5. Overall income adequacy score (1-100)
Return a detailed JSON assessment.
'
- id: check_cibil_score
name: CIBIL Score Check
type: script
script: "import json\nimport random\n\n# Simulate CIBIL score check\ncibil_data\
\ = {\n \"credit_score\": 720,\n \"score_range\": \"Good\",\n \"last_updated\"\
: \"2024-06-01\",\n \"credit_history_length\": \"5 years\",\n \"total_accounts\"\
: 8,\n \"active_accounts\": 6,\n \"closed_accounts\": 2,\n \"credit_utilization\"\
: 35.5,\n \"payment_history\": \"99% on-time\",\n \"recent_inquiries\":\
\ 2\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(cibil_data)}\")\n"
depends_on:
- income_assessment
description: Retrieve and analyze CIBIL credit score
previous_node: income_assessment
timeout_seconds: 60
- id: analyze_credit_history
name: Credit History Analysis
type: script
script: "import json\n\n# Simulate credit history analysis\ncredit_history = {\n\
\ \"oldest_account_age\": \"60 months\",\n \"average_account_age\": \"32\
\ months\",\n \"credit_mix\": {\n \"credit_cards\": 3,\n \"personal_loans\"\
: 1,\n \"auto_loans\": 1,\n \"home_loans\": 0,\n \"other\"\
: 1\n },\n \"repayment_behavior\": {\n \"never_missed\": 85.0,\n\
\ \"30_days_late\": 12.0,\n \"60_days_late\": 2.5,\n \"90_days_late\"\
: 0.5,\n \"defaults\": 0.0\n },\n \"credit_limit_utilization\": 35.2,\n\
\ \"recent_credit_behavior\": \"stable\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(credit_history)}\"\
)\n"
depends_on:
- check_cibil_score
description: Detailed analysis of credit history and patterns
previous_node: check_cibil_score
timeout_seconds: 60
- id: assess_existing_loans
name: Existing Loan Assessment
type: script
script: "import json\n\n# Simulate existing loan assessment\nexisting_loans = {\n\
\ \"total_outstanding\": 125000,\n \"number_of_loans\": 2,\n \"loan_details\"\
: [\n {\n \"type\": \"personal_loan\",\n \"outstanding\"\
: 45000,\n \"emi\": 3500,\n \"remaining_tenure\": \"18 months\"\
\n },\n {\n \"type\": \"auto_loan\",\n \"\
outstanding\": 80000,\n \"emi\": 4200,\n \"remaining_tenure\"\
: \"24 months\"\n }\n ],\n \"total_monthly_emi\": 7700,\n \"repayment_track_record\"\
: \"excellent\",\n \"loan_burden_ratio\": 0.28\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(existing_loans)}\"\
)\n"
depends_on:
- analyze_credit_history
description: Assess current loan obligations and repayment capacity
previous_node: analyze_credit_history
timeout_seconds: 45
- id: evaluate_default_risk
name: Default Risk Evaluation
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: risk_assessor
depends_on:
- assess_existing_loans
- check_cibil_score
- analyze_credit_history
description: AI-powered default risk assessment
user_message: 'Please analyze this credit data for default risk:
CIBIL Score: ${check_cibil_score}
Credit History: ${analyze_credit_history}
Existing Loans: ${assess_existing_loans}
Calculate default risk and provide recommendations.
'
previous_node: assess_existing_loans
system_message: 'You are a credit risk assessment specialist. Analyze the provided
credit data and calculate default risk.
Consider:
1. Credit score and history
2. Repayment patterns
3. Credit utilization
4. Existing loan burden
5. Overall risk profile
Provide a risk score (1-100, where 100 is highest risk) and detailed risk analysis.
'
- id: calculate_credit_score
name: Internal Credit Score Calculation
type: script
script: "import json\n\n# Simulate internal credit scoring\ncibil_score = ${check_cibil_score.credit_score}\n\
payment_history = 99.0 # From credit history\ncredit_utilization = ${analyze_credit_history.credit_limit_utilization}\n\
loan_burden = ${assess_existing_loans.loan_burden_ratio}\n\n# Internal scoring\
\ algorithm (simplified)\ninternal_score = (cibil_score * 0.4) + (payment_history\
\ * 0.3) + ((100 - credit_utilization) * 0.2) + ((1 - loan_burden) * 100 * 0.1)\n\
\n# Get min credit score from input parameters\nmin_credit_score = ${min_credit_score}\n\
\nresult = {\n \"internal_credit_score\": round(internal_score, 1),\n \"\
cibil_score\": cibil_score,\n \"score_acceptable\": internal_score >= min_credit_score,\n\
\ \"min_required_score\": min_credit_score,\n \"risk_category\": \"low\"\
\ if internal_score >= 750 else \"medium\" if internal_score >= 650 else \"high\"\
,\n \"scoring_factors\": {\n \"cibil_contribution\": cibil_score * 0.4,\n\
\ \"payment_history_contribution\": payment_history * 0.3,\n \"\
utilization_contribution\": (100 - credit_utilization) * 0.2,\n \"loan_burden_contribution\"\
: (1 - loan_burden) * 100 * 0.1\n }\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(result)}\"\
)\n"
depends_on:
- evaluate_default_risk
- check_cibil_score
- analyze_credit_history
- assess_existing_loans
description: Calculate internal credit score based on all factors
previous_node: evaluate_default_risk
timeout_seconds: 30
- id: kyc_compliance_check
name: KYC Compliance Check
type: script
script: "import json\n\n# Simulate KYC compliance check\nkyc_result = {\n \"\
kyc_status\": \"compliant\",\n \"identity_verified\": True,\n \"address_verified\"\
: True,\n \"income_verified\": True,\n \"documents_complete\": True,\n \
\ \"risk_category\": \"low\",\n \"compliance_score\": 98.5,\n \"last_updated\"\
: \"2024-07-15\",\n \"regulatory_requirements_met\": True\n}\n\nprint(f\"__OUTPUTS__\
\ {json.dumps(kyc_result)}\")\n"
depends_on:
- calculate_credit_score
description: Verify KYC compliance requirements
previous_node: calculate_credit_score
timeout_seconds: 60
- id: aml_screening
name: AML Screening
type: script
script: "import json\n\n# Simulate AML screening\naml_result = {\n \"aml_status\"\
: \"cleared\",\n \"watchlist_check\": \"no_matches\",\n \"pep_screening\"\
: \"not_identified\",\n \"sanctions_check\": \"cleared\",\n \"adverse_media\"\
: \"no_hits\",\n \"risk_rating\": \"low\",\n \"compliance_score\": 96.8,\n\
\ \"screening_date\": \"2024-07-15\",\n \"manual_review_required\": False\n\
}\n\nprint(f\"__OUTPUTS__ {json.dumps(aml_result)}\")\n"
depends_on:
- kyc_compliance_check
description: Anti-Money Laundering compliance screening
previous_node: kyc_compliance_check
timeout_seconds: 90
- id: regulatory_compliance
name: Regulatory Requirements Check
type: script
script: "import json\n\n# Simulate regulatory compliance check\nregulatory_result\
\ = {\n \"rbi_guidelines_met\": True,\n \"lending_norms_compliant\": True,\n\
\ \"documentation_complete\": True,\n \"disclosure_requirements_met\": True,\n\
\ \"consumer_protection_compliant\": True,\n \"data_privacy_compliant\"\
: True,\n \"compliance_score\": 97.2,\n \"audit_trail_complete\": True,\n\
\ \"regulatory_risk\": \"minimal\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(regulatory_result)}\"\
)\n"
depends_on:
- aml_screening
description: Check compliance with banking regulations
previous_node: aml_screening
timeout_seconds: 45
- id: internal_policy_check
name: Internal Policy Compliance
type: script
script: "import json\n\n# Simulate internal policy check\npolicy_result = {\n \
\ \"loan_amount_within_limits\": True,\n \"ltv_ratio_acceptable\": True,\n\
\ \"income_criteria_met\": True,\n \"age_criteria_met\": True,\n \"employment_criteria_met\"\
: True,\n \"credit_score_threshold_met\": True,\n \"geographic_restrictions_met\"\
: True,\n \"policy_compliance_score\": 95.5,\n \"exceptions_required\":\
\ [],\n \"policy_version\": \"2024.1\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(policy_result)}\"\
)\n"
depends_on:
- regulatory_compliance
description: Verify compliance with internal lending policies
previous_node: regulatory_compliance
timeout_seconds: 30
- id: compliance_consolidation
name: Compliance Assessment Summary
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: compliance_reviewer
depends_on:
- internal_policy_check
- kyc_compliance_check
- aml_screening
- regulatory_compliance
description: AI-powered comprehensive compliance assessment
user_message: 'Please analyze these compliance check results:
KYC Compliance: ${kyc_compliance_check}
AML Screening: ${aml_screening}
Regulatory Compliance: ${regulatory_compliance}
Internal Policy: ${internal_policy_check}
Provide a consolidated compliance assessment.
'
previous_node: internal_policy_check
system_message: 'You are a compliance officer specializing in loan underwriting
compliance.
Review all compliance check results and provide:
1. Overall compliance status
2. Risk assessment
3. Any compliance gaps or concerns
4. Recommendations for proceeding
5. Compliance confidence score (1-100)
Return a comprehensive compliance assessment in JSON format.
'
- id: underwriting_decision_router
name: Underwriting Decision Router
type: conditional_router
conditions:
- name: high_risk_path
route: decline_path
condition: ${calculate_credit_score.internal_credit_score} < 600 || ${calculate_debt_to_income.debt_to_income_ratio}
> 0.50
- name: conditional_approval_path
route: conditional_path
condition: ${calculate_credit_score.internal_credit_score} >= 600 && ${calculate_credit_score.internal_credit_score}
< 700
- name: standard_approval_path
route: approval_path
condition: ${calculate_credit_score.internal_credit_score} >= 700
depends_on:
- compliance_consolidation
- calculate_credit_score
- calculate_debt_to_income
description: Route to appropriate decision path based on assessments
default_route: manual_review_path
previous_node: compliance_consolidation
- id: final_underwriting_analysis
name: Final Underwriting Decision Analysis
type: ai_agent
config:
input_format: json
output_format: json
model_client_id: underwriting_analyst
depends_on:
- underwriting_decision_router
- application_quality_check
- consolidate_document_verification
- income_assessment
- calculate_credit_score
- evaluate_default_risk
- compliance_consolidation
- calculate_debt_to_income
description: Comprehensive AI analysis for final underwriting decision
user_message: 'Please make the final underwriting decision based on:
Application Quality: ${application_quality_check}
Document Verification: ${consolidate_document_verification}
Income Assessment: ${income_assessment}
Credit Score: ${calculate_credit_score}
Default Risk: ${evaluate_default_risk}
Compliance: ${compliance_consolidation}
Debt-to-Income: ${calculate_debt_to_income}
Provide your final recommendation with detailed reasoning.
'
previous_node: underwriting_decision_router
system_message: 'You are a senior underwriting manager making final loan decisions.
Analyze all assessment results and provide:
1. Final recommendation (APPROVE/CONDITIONAL/DECLINE)
2. Loan terms and conditions
3. Risk mitigation measures
4. Reasoning for decision
5. Confidence level (1-100)
Consider all factors: income, credit, compliance, and overall risk profile.
'
- id: generate_approval_terms
name: Generate Loan Approval Terms
type: script
script: "import json\n\n# Generate loan terms based on assessment\nloan_terms =\
\ {\n \"decision\": \"APPROVED\",\n \"loan_amount\": ${loan_amount},\n \
\ \"interest_rate\": 8.5,\n \"tenure_months\": 240,\n \"monthly_emi\"\
: round(${loan_amount} / 240 * 1.085, 2),\n \"processing_fee\": ${loan_amount}\
\ * 0.01,\n \"conditions\": [\n \"Property insurance required\",\n \
\ \"Auto-debit for EMI payments\",\n \"Annual income certificate\
\ submission\"\n ],\n \"disbursement_conditions\": [\n \"Property\
\ registration documents\",\n \"Insurance policy copy\",\n \"Post-dated\
\ cheques for EMI\"\n ],\n \"approval_date\": \"2024-07-15\",\n \"offer_validity\"\
: \"2024-08-15\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(loan_terms)}\")\n"
depends_on:
- final_underwriting_analysis
description: Generate loan terms and conditions for approved application
previous_node: final_underwriting_analysis
timeout_seconds: 60
execute_on_routes:
- approval_path
- id: generate_conditional_terms
name: Generate Conditional Approval Terms
type: script
script: "import json\n\n# Generate conditional approval terms\nconditional_terms\
\ = {\n \"decision\": \"CONDITIONAL_APPROVAL\",\n \"loan_amount\": ${loan_amount},\n\
\ \"interest_rate\": 9.2,\n \"tenure_months\": 240,\n \"monthly_emi\"\
: round(${loan_amount} / 240 * 1.092, 2),\n \"processing_fee\": ${loan_amount}\
\ * 0.015,\n \"additional_conditions\": [\n \"Co-applicant required\"\
,\n \"Additional collateral security\",\n \"Higher down payment\
\ (30% instead of 20%)\",\n \"Salary account maintenance for 2 years\"\n\
\ ],\n \"documents_required\": [\n \"Co-applicant income documents\"\
,\n \"Additional property papers\",\n \"Enhanced bank statements\
\ (12 months)\"\n ],\n \"approval_date\": \"2024-07-15\",\n \"offer_validity\"\
: \"2024-08-01\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(conditional_terms)}\"\
)\n"
depends_on:
- final_underwriting_analysis
description: Generate conditional approval with additional requirements
previous_node: final_underwriting_analysis
timeout_seconds: 60
execute_on_routes:
- conditional_path
- id: generate_decline_notice
name: Generate Loan Decline Notice
type: script
script: "import json\n\n# Generate decline notice\ndecline_notice = {\n \"decision\"\
: \"DECLINED\",\n \"primary_reasons\": [\n \"Insufficient credit score\"\
,\n \"High debt-to-income ratio\",\n \"Inadequate income documentation\"\
\n ],\n \"decline_code\": \"RISK_001\",\n \"decline_date\": \"2024-07-15\"\
,\n \"appeal_process\": \"Customer can appeal within 30 days with additional\
\ documentation\",\n \"suggestions\": [\n \"Improve credit score by\
\ 6 months\",\n \"Reduce existing debt burden\",\n \"Consider lower\
\ loan amount\",\n \"Add co-applicant with good credit\"\n ],\n \"\
reapplication_eligibility\": \"2024-12-15\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(decline_notice)}\"\
)\n"
depends_on:
- final_underwriting_analysis
description: Generate loan decline notice with reasons
previous_node: final_underwriting_analysis
timeout_seconds: 30
execute_on_routes:
- decline_path
- id: flag_for_manual_review
name: Auto-Approve Manual Review Cases
type: script
script: "import json\n\n# Auto-approve with specific conditions for manual review\
\ cases\nauto_approval_result = {\n \"decision\": \"AUTO_APPROVED_WITH_CONDITIONS\"\
,\n \"loan_amount\": ${loan_amount},\n \"interest_rate\": 9.0, # Slightly\
\ higher rate for complex cases\n \"tenure_months\": 240,\n \"monthly_emi\"\
: round(${loan_amount} / 240 * 1.090, 2),\n \"processing_fee\": ${loan_amount}\
\ * 0.012,\n \"approval_type\": \"automatic_complex_case\",\n \"conditions\"\
: [\n \"Quarterly income verification for first year\",\n \"Maintain\
\ minimum account balance\",\n \"No additional loans for 12 months\",\n\
\ \"Property insurance with bank as beneficiary\"\n ],\n \"risk_mitigation\"\
: [\n \"Enhanced monitoring for first 6 months\",\n \"Automatic\
\ alerts for missed payments\",\n \"Periodic credit score reviews\"\n \
\ ],\n \"approval_date\": \"2024-07-15\",\n \"offer_validity\": \"2024-08-10\"\
,\n \"notes\": \"Auto-approved with enhanced monitoring due to complex risk\
\ factors\"\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(auto_approval_result)}\")\n"
depends_on:
- final_underwriting_analysis
description: Automatically approve applications that would have gone to manual review
previous_node: final_underwriting_analysis
timeout_seconds: 30
execute_on_routes:
- manual_review_path
- id: generate_final_output
name: Generate Final Workflow Output
type: script
script: "import json\n\n# Determine final decision based on route\nfinal_output\
\ = {\n \"application_id\": \"${application_id}\",\n \"processing_date\"\
: \"2024-07-15\",\n \"workflow_status\": \"completed\",\n \"processing_time_minutes\"\
: 45,\n \"decision_summary\": {\n \"credit_score\": ${calculate_credit_score.internal_credit_score},\n\
\ \"debt_to_income_ratio\": ${calculate_debt_to_income.debt_to_income_ratio},\n\
\ \"compliance_status\": \"compliant\",\n \"risk_category\": \"\
${calculate_credit_score.risk_category}\"\n },\n \"next_steps\": [\n \
\ \"Customer notification\",\n \"Document archival\",\n \"Compliance\
\ reporting\"\n ]\n}\n\nprint(f\"__OUTPUTS__ {json.dumps(final_output)}\")\n"
depends_on:
- generate_approval_terms
- generate_conditional_terms
- generate_decline_notice
- flag_for_manual_review
- calculate_credit_score
- calculate_debt_to_income
description: Generate comprehensive workflow output
previous_node:
- generate_approval_terms
- generate_conditional_terms
- generate_decline_notice
- flag_for_manual_review
timeout_seconds: 30
- id: upload_to_supabase
name: Upload Loan Data to Supabase
type: script
script: "import json\nimport os\nimport pandas as pd\nfrom datetime import datetime\n\
import io\nfrom supabase import create_client, Client\n\n# Supabase configuration\n\
SUPABASE_URL = \"https://mbauzgvitqvxceqanzjw.supabase.co\"\nSUPABASE_KEY = \"\
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1iYXV6Z3ZpdHF2eGNlcWFuemp3Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0Mzg2MDEwOCwiZXhwIjoyMDU5NDM2MTA4fQ.R71cWZoLuq2GNojkLSvhcXXt9rAW9PJ5O9V4g7vXvC0\"\
\n\n# Helper function to safely extract values with defaults\ndef safe_get(expression,\
\ default_value=\"N/A\"):\n try:\n return expression if expression is\
\ not None else default_value\n except:\n return default_value\n\ndef\
\ safe_get_numeric(expression, default_value=0):\n try:\n return float(expression)\
\ if expression is not None else default_value\n except:\n return default_value\n\
\ndef safe_get_boolean(expression, default_value=False):\n try:\n return\
\ bool(expression) if expression is not None else default_value\n except:\n\
\ return default_value\n\ntry:\n # Initialize Supabase client\n supabase:\
\ Client = create_client(SUPABASE_URL, SUPABASE_KEY)\n \n # Parse data from\
\ previous nodes with safe extraction\n try:\n # Extract application\
\ info\n app_info = ${extract_application_info.customer_info}\n \
\ \n # Extract document verification results\n pan_verification\
\ = ${verify_pan_card}\n aadhaar_verification = ${verify_aadhaar}\n \
\ passport_verification = ${verify_passport}\n address_verification\
\ = ${verify_address_proof}\n \n # Extract income and financial\
\ data\n salary_analysis = ${analyze_salary_slips}\n bank_analysis\
\ = ${analyze_bank_statements}\n tax_returns = ${verify_tax_returns}\n\
\ employment_data = ${verify_employment}\n debt_income_calc = ${calculate_debt_to_income}\n\
\ \n # Extract credit assessment\n cibil_data = ${check_cibil_score}\n\
\ credit_history = ${analyze_credit_history}\n existing_loans =\
\ ${assess_existing_loans}\n credit_score_calc = ${calculate_credit_score}\n\
\ \n # Extract compliance data\n kyc_compliance = ${kyc_compliance_check}\n\
\ aml_screening = ${aml_screening}\n regulatory_comp = ${regulatory_compliance}\n\
\ policy_check = ${internal_policy_check}\n \n # Extract\
\ final decision\n final_decision = ${final_underwriting_analysis}\n \
\ workflow_output = ${generate_final_output}\n \n # Extract\
\ individual document URLs\n pan_card_url = os.environ.get('pan_card_url',\
\ '').strip()\n aadhaar_card_url = os.environ.get('aadhaar_card_url', '').strip()\n\
\ passport_url = os.environ.get('passport_url', '').strip()\n address_proof_url\
\ = os.environ.get('address_proof_url', '').strip()\n salary_slip_url =\
\ os.environ.get('salary_slip_url', '').strip()\n bank_statement_url =\
\ os.environ.get('bank_statement_url', '').strip()\n tax_return_url = os.environ.get('tax_return_url',\
\ '').strip()\n \n # Extract URL validation results\n pan_url_validation\
\ = ${validate_pan_card_url}\n aadhaar_url_validation = ${validate_aadhaar_card_url}\n\
\ passport_url_validation = ${validate_passport_url}\n address_proof_url_validation\
\ = ${validate_address_proof_url}\n \n # Count provided documents\n\
\ provided_documents = [\n pan_card_url, aadhaar_card_url, passport_url,\
\ address_proof_url,\n salary_slip_url, bank_statement_url, tax_return_url\n\
\ ]\n total_provided_documents = len([url for url in provided_documents\
\ if url])\n \n # Count valid URLs\n valid_urls = sum([\n\
\ pan_url_validation.get('url_valid', False),\n aadhaar_url_validation.get('url_valid',\
\ False),\n passport_url_validation.get('url_valid', False),\n \
\ address_proof_url_validation.get('url_valid', False)\n ])\n \
\ \n print(\"Successfully parsed data from all previous nodes\")\n\
\ print(f\"Total document URLs provided: {total_provided_documents}\")\n\
\ print(f\"Valid URLs: {valid_urls}\")\n print(f\"PAN card provided:\
\ {bool(pan_card_url)}\")\n print(f\"Aadhaar card provided: {bool(aadhaar_card_url)}\"\
)\n print(f\"Passport provided: {bool(passport_url)}\")\n print(f\"\
Address proof provided: {bool(address_proof_url)}\")\n \n except Exception\
\ as e:\n print(f\"Error parsing previous node data: {e}\")\n #\
\ Set fallback values\n app_info = {}\n pan_verification = {}\n\
\ aadhaar_verification = {}\n passport_verification = {}\n \
\ address_verification = {}\n salary_analysis = {}\n bank_analysis\
\ = {}\n tax_returns = {}\n employment_data = {}\n debt_income_calc\
\ = {}\n cibil_data = {}\n credit_history = {}\n existing_loans\
\ = {}\n credit_score_calc = {}\n kyc_compliance = {}\n aml_screening\
\ = {}\n regulatory_comp = {}\n policy_check = {}\n final_decision\
\ = {}\n workflow_output = {}\n total_provided_documents = 0\n \
\ pan_card_url = \"\"\n aadhaar_card_url = \"\"\n passport_url\
\ = \"\"\n address_proof_url = \"\"\n salary_slip_url = \"\"\n \
\ bank_statement_url = \"\"\n tax_return_url = \"\"\n pan_url_validation\
\ = {}\n aadhaar_url_validation = {}\n passport_url_validation =\
\ {}\n address_proof_url_validation = {}\n valid_urls = 0\n \n\
\ # Create comprehensive data structure for CSV\n loan_data = {\n \
\ \"processing_timestamp\": datetime.now().isoformat(),\n \"workflow_id\"\
: \"loan_underwriting_workflow_real\",\n \"workflow_version\": \"1.0\"\
,\n \n # Application Information\n \"application_id\": safe_get(\"\
${application_id}\", \"UNKNOWN_APP\"),\n \"requested_amount\": safe_get_numeric(\"\
${loan_amount}\", 0),\n \"processing_date\": datetime.now().strftime(\"\
%Y-%m-%d\"),\n \"workflow_status\": \"completed\",\n \n #\
\ Personal Information\n \"customer_id\": safe_get(app_info.get(\"customer_id\"\
), \"UNKNOWN_CUSTOMER\"),\n \"applicant_name\": safe_get(app_info.get(\"\
full_name\"), \"Unknown Applicant\"),\n \"email\": safe_get(app_info.get(\"\
email\"), \"unknown@email.com\"),\n \"phone\": safe_get(app_info.get(\"\
phone\"), \"Unknown Phone\"),\n \"address\": safe_get(app_info.get(\"address\"\
), \"Unknown Address\"),\n \"employment_status\": safe_get(app_info.get(\"\
employment_status\"), \"Unknown\"),\n \"annual_income\": safe_get_numeric(app_info.get(\"\
annual_income\"), 0),\n \"loan_purpose\": safe_get(app_info.get(\"loan_purpose\"\
), \"Unknown\"),\n \n # Document Verification\n \"pan_number\"\
: safe_get(pan_verification.get(\"pan_number\"), \"UNKNOWN_PAN\"),\n \"\
pan_verified\": safe_get_boolean(pan_verification.get(\"is_valid\"), False),\n\
\ \"pan_verification_score\": safe_get_numeric(pan_verification.get(\"\
verification_score\"), 0),\n \"pan_name_match\": safe_get_boolean(pan_verification.get(\"\
name_match\"), False),\n \"pan_verified_name\": safe_get(pan_verification.get(\"\
verified_name\"), \"N/A\"),\n \"pan_father_name\": safe_get(pan_verification.get(\"\
father_name\"), \"N/A\"),\n \"pan_date_of_birth\": safe_get(pan_verification.get(\"\
date_of_birth\"), \"N/A\"),\n \"pan_document_url\": safe_get(pan_verification.get(\"\
document_url\"), \"N/A\"),\n \n \"aadhaar_masked\": safe_get(aadhaar_verification.get(\"\
aadhaar_masked\"), \"XXXX-XXXX-XXXX\"),\n \"aadhaar_verified\": safe_get_boolean(aadhaar_verification.get(\"\
is_valid\"), False),\n \"aadhaar_verification_score\": safe_get_numeric(aadhaar_verification.get(\"\
verification_score\"), 0),\n \"aadhaar_address_match\": safe_get_boolean(aadhaar_verification.get(\"\
address_match\"), False),\n \"aadhaar_name\": safe_get(aadhaar_verification.get(\"\
name_on_aadhaar\"), \"N/A\"),\n \"aadhaar_address\": safe_get(aadhaar_verification.get(\"\
address_on_aadhaar\"), \"N/A\"),\n \"aadhaar_date_of_birth\": safe_get(aadhaar_verification.get(\"\
date_of_birth\"), \"N/A\"),\n \"aadhaar_gender\": safe_get(aadhaar_verification.get(\"\
gender\"), \"N/A\"),\n \"aadhaar_document_url\": safe_get(aadhaar_verification.get(\"\
document_url\"), \"N/A\"),\n \n \"passport_number\": safe_get(passport_verification.get(\"\
passport_number\"), \"Not Provided\"),\n \"passport_verified\": safe_get_boolean(passport_verification.get(\"\
is_valid\"), False),\n \"passport_verification_score\": safe_get_numeric(passport_verification.get(\"\
verification_score\"), 0),\n \"passport_name\": safe_get(passport_verification.get(\"\
name_on_passport\"), \"N/A\"),\n \"passport_nationality\": safe_get(passport_verification.get(\"\
nationality\"), \"N/A\"),\n \"passport_expiry_date\": safe_get(passport_verification.get(\"\
expiry_date\"), \"N/A\"),\n \"passport_issuing_authority\": safe_get(passport_verification.get(\"\
issuing_authority\"), \"N/A\"),\n \"passport_document_url\": safe_get(passport_verification.get(\"\
document_url\"), \"N/A\"),\n \n \"address_proof_type\": safe_get(address_verification.get(\"\
document_type\"), \"Unknown\"),\n \"address_proof_verified\": safe_get_boolean(address_verification.get(\"\
is_valid\"), False),\n \"address_proof_verification_score\": safe_get_numeric(address_verification.get(\"\
verification_score\"), 0),\n \"address_proof_confirmed\": safe_get(address_verification.get(\"\
address_confirmed\"), \"N/A\"),\n \"address_proof_name\": safe_get(address_verification.get(\"\
name_on_document\"), \"N/A\"),\n \"address_proof_date\": safe_get(address_verification.get(\"\
document_date\"), \"N/A\"),\n \"address_proof_issuing_authority\": safe_get(address_verification.get(\"\
issuing_authority\"), \"N/A\"),\n \"address_proof_document_url\": safe_get(address_verification.get(\"\
document_url\"), \"N/A\"),\n \n # Document Processing Information\n\
\ \"total_provided_documents\": total_provided_documents,\n \"pan_card_provided\"\
: bool(pan_card_url),\n \"aadhaar_card_provided\": bool(aadhaar_card_url),\n\
\ \"passport_provided\": bool(passport_url),\n \"address_proof_provided\"\
: bool(address_proof_url),\n \"documents_processed_successfully\": sum([\n\
\ pan_verification.get(\"extracted_from_ocr\", False),\n \
\ aadhaar_verification.get(\"extracted_from_ocr\", False),\n passport_verification.get(\"\
extracted_from_ocr\", False),\n address_verification.get(\"extracted_from_ocr\"\
, False)\n ]),\n \"average_ocr_confidence\": safe_get_numeric((\n\
\ pan_verification.get(\"ocr_confidence\", 0) +\n aadhaar_verification.get(\"\
ocr_confidence\", 0) +\n passport_verification.get(\"ocr_confidence\"\
, 0) +\n address_verification.get(\"ocr_confidence\", 0)\n )\
\ / 4, 0),\n \n # Individual Document OCR Status\n \"pan_card_ocr_extracted\"\
: safe_get_boolean(pan_verification.get(\"extracted_from_ocr\"), False),\n \
\ \"pan_card_ocr_confidence\": safe_get_numeric(pan_verification.get(\"ocr_confidence\"\
), 0),\n \"aadhaar_ocr_extracted\": safe_get_boolean(aadhaar_verification.get(\"\
extracted_from_ocr\"), False),\n \"aadhaar_ocr_confidence\": safe_get_numeric(aadhaar_verification.get(\"\
ocr_confidence\"), 0),\n \"passport_ocr_extracted\": safe_get_boolean(passport_verification.get(\"\
extracted_from_ocr\"), False),\n \"passport_ocr_confidence\": safe_get_numeric(passport_verification.get(\"\
ocr_confidence\"), 0),\n \"address_proof_ocr_extracted\": safe_get_boolean(address_verification.get(\"\
extracted_from_ocr\"), False),\n \"address_proof_ocr_confidence\": safe_get_numeric(address_verification.get(\"\
ocr_confidence\"), 0),\n \n # Document URLs Provided\n \"\
pan_card_url_provided\": pan_card_url,\n \"aadhaar_card_url_provided\"\
: aadhaar_card_url,\n \"passport_url_provided\": passport_url,\n \
\ \"address_proof_url_provided\": address_proof_url,\n \"salary_slip_url_provided\"\
: salary_slip_url,\n \"bank_statement_url_provided\": bank_statement_url,\n\
\ \"tax_return_url_provided\": tax_return_url,\n \n # URL\
\ Validation Results\n \"pan_url_valid\": safe_get_boolean(pan_url_validation.get('url_valid'),\
\ False),\n \"pan_url_accessible\": safe_get_boolean(pan_url_validation.get('url_accessible'),\
\ False),\n \"pan_url_content_type\": safe_get(pan_url_validation.get('content_type'),\
\ 'N/A'),\n \"pan_url_file_size\": safe_get_numeric(pan_url_validation.get('file_size'),\
\ 0),\n \"pan_url_status_code\": safe_get_numeric(pan_url_validation.get('status_code'),\
\ 0),\n \n \"aadhaar_url_valid\": safe_get_boolean(aadhaar_url_validation.get('url_valid'),\
\ False),\n \"aadhaar_url_accessible\": safe_get_boolean(aadhaar_url_validation.get('url_accessible'),\
\ False),\n \"aadhaar_url_content_type\": safe_get(aadhaar_url_validation.get('content_type'),\
\ 'N/A'),\n \"aadhaar_url_file_size\": safe_get_numeric(aadhaar_url_validation.get('file_size'),\
\ 0),\n \"aadhaar_url_status_code\": safe_get_numeric(aadhaar_url_validation.get('status_code'),\
\ 0),\n \n \"passport_url_valid\": safe_get_boolean(passport_url_validation.get('url_valid'),\
\ False),\n \"passport_url_accessible\": safe_get_boolean(passport_url_validation.get('url_accessible'),\
\ False),\n \"passport_url_content_type\": safe_get(passport_url_validation.get('content_type'),\
\ 'N/A'),\n \"passport_url_file_size\": safe_get_numeric(passport_url_validation.get('file_size'),\
\ 0),\n \"passport_url_status_code\": safe_get_numeric(passport_url_validation.get('status_code'),\
\ 0),\n \n \"address_proof_url_valid\": safe_get_boolean(address_proof_url_validation.get('url_valid'),\
\ False),\n \"address_proof_url_accessible\": safe_get_boolean(address_proof_url_validation.get('url_accessible'),\
\ False),\n \"address_proof_url_content_type\": safe_get(address_proof_url_validation.get('content_type'),\
\ 'N/A'),\n \"address_proof_url_file_size\": safe_get_numeric(address_proof_url_validation.get('file_size'),\
\ 0),\n \"address_proof_url_status_code\": safe_get_numeric(address_proof_url_validation.get('status_code'),\
\ 0),\n \n \"total_valid_urls\": valid_urls,\n \n \
\ # Income and Employment Information\n \"monthly_gross_salary\": safe_get_numeric(salary_analysis.get(\"\
monthly_gross\"), 0),\n \"monthly_net_salary\": safe_get_numeric(salary_analysis.get(\"\
monthly_net\"), 0),\n \"annual_gross_income\": safe_get_numeric(salary_analysis.get(\"\
annual_gross\"), 0),\n \"annual_net_income\": safe_get_numeric(salary_analysis.get(\"\
annual_net\"), 0),\n \"employer_name\": safe_get(salary_analysis.get(\"\
employer\"), \"Unknown Company\"),\n \"employment_duration\": safe_get(salary_analysis.get(\"\
employment_duration\"), \"Unknown\"),\n \"salary_consistency\": safe_get_boolean(salary_analysis.get(\"\
salary_consistency\"), False),\n \"recent_increment\": safe_get_boolean(salary_analysis.get(\"\
recent_increment\"), False),\n \"employment_confirmed\": safe_get_boolean(employment_data.get(\"\
employment_confirmed\"), False),\n \"designation\": safe_get(employment_data.get(\"\
designation\"), \"Unknown Position\"),\n \"employment_type\": safe_get(employment_data.get(\"\
employment_type\"), \"Unknown\"),\n \"job_stability_score\": safe_get_numeric(employment_data.get(\"\
job_stability_score\"), 0),\n \n # Financial Analysis\n \"\
average_monthly_credits\": safe_get_numeric(bank_analysis.get(\"average_monthly_credits\"\
), 0),\n \"account_balance_trend\": safe_get(bank_analysis.get(\"account_balance_trend\"\
), \"Unknown\"),\n \"minimum_balance\": safe_get_numeric(bank_analysis.get(\"\
minimum_balance\"), 0),\n \"maximum_balance\": safe_get_numeric(bank_analysis.get(\"\
maximum_balance\"), 0),\n \"bounced_transactions\": safe_get_numeric(bank_analysis.get(\"\
bounced_transactions\"), 0),\n \"loan_emis_detected\": safe_get_numeric(bank_analysis.get(\"\
loan_emis_detected\"), 0),\n \"financial_discipline_score\": safe_get_numeric(bank_analysis.get(\"\
financial_discipline_score\"), 0),\n \"declared_tax_income\": safe_get_numeric(tax_returns.get(\"\
declared_income\"), 0),\n \"tax_paid\": safe_get_numeric(tax_returns.get(\"\
tax_paid\"), 0),\n \"tax_compliance_score\": safe_get_numeric(tax_returns.get(\"\
tax_compliance_score\"), 0),\n \"debt_to_income_ratio\": safe_get_numeric(debt_income_calc.get(\"\
debt_to_income_ratio\"), 0),\n \"existing_debt_payments\": safe_get_numeric(debt_income_calc.get(\"\
existing_debt_payments\"), 0),\n \"proposed_emi\": safe_get_numeric(debt_income_calc.get(\"\
proposed_emi\"), 0),\n \"total_debt_payments\": safe_get_numeric(debt_income_calc.get(\"\
total_debt_payments\"), 0),\n \"ratio_acceptable\": safe_get_boolean(debt_income_calc.get(\"\
ratio_acceptable\"), False),\n \n # Credit Information\n \
\ \"cibil_score\": safe_get_numeric(cibil_data.get(\"credit_score\"), 0),\n \
\ \"credit_score_range\": safe_get(cibil_data.get(\"score_range\"), \"Unknown\"\
),\n \"credit_history_length\": safe_get(cibil_data.get(\"credit_history_length\"\
), \"Unknown\"),\n \"total_accounts\": safe_get_numeric(cibil_data.get(\"\
total_accounts\"), 0),\n \"active_accounts\": safe_get_numeric(cibil_data.get(\"\
active_accounts\"), 0),\n \"credit_utilization\": safe_get_numeric(cibil_data.get(\"\
credit_utilization\"), 0),\n \"payment_history\": safe_get(cibil_data.get(\"\
payment_history\"), \"Unknown\"),\n \"recent_inquiries\": safe_get_numeric(cibil_data.get(\"\
recent_inquiries\"), 0),\n \"internal_credit_score\": safe_get_numeric(credit_score_calc.get(\"\
internal_credit_score\"), 0),\n \"score_acceptable\": safe_get_boolean(credit_score_calc.get(\"\
score_acceptable\"), False),\n \"risk_category\": safe_get(credit_score_calc.get(\"\
risk_category\"), \"unknown\"),\n \"total_outstanding_loans\": safe_get_numeric(existing_loans.get(\"\
total_outstanding\"), 0),\n \"number_of_existing_loans\": safe_get_numeric(existing_loans.get(\"\
number_of_loans\"), 0),\n \"total_monthly_emi\": safe_get_numeric(existing_loans.get(\"\
total_monthly_emi\"), 0),\n \"loan_burden_ratio\": safe_get_numeric(existing_loans.get(\"\
loan_burden_ratio\"), 0),\n \"repayment_track_record\": safe_get(existing_loans.get(\"\
repayment_track_record\"), \"Unknown\"),\n \n # Compliance Status\n\
\ \"kyc_status\": safe_get(kyc_compliance.get(\"kyc_status\"), \"unknown\"\
),\n \"kyc_compliance_score\": safe_get_numeric(kyc_compliance.get(\"compliance_score\"\
), 0),\n \"identity_verified\": safe_get_boolean(kyc_compliance.get(\"\
identity_verified\"), False),\n \"address_verified\": safe_get_boolean(kyc_compliance.get(\"\
address_verified\"), False),\n \"income_verified\": safe_get_boolean(kyc_compliance.get(\"\
income_verified\"), False),\n \"documents_complete\": safe_get_boolean(kyc_compliance.get(\"\
documents_complete\"), False),\n \"aml_status\": safe_get(aml_screening.get(\"\
aml_status\"), \"unknown\"),\n \"aml_compliance_score\": safe_get_numeric(aml_screening.get(\"\
compliance_score\"), 0),\n \"watchlist_check\": safe_get(aml_screening.get(\"\
watchlist_check\"), \"unknown\"),\n \"pep_screening\": safe_get(aml_screening.get(\"\
pep_screening\"), \"unknown\"),\n \"sanctions_check\": safe_get(aml_screening.get(\"\
sanctions_check\"), \"unknown\"),\n \"manual_review_required\": safe_get_boolean(aml_screening.get(\"\
manual_review_required\"), True),\n \"rbi_guidelines_met\": safe_get_boolean(regulatory_comp.get(\"\
rbi_guidelines_met\"), False),\n \"lending_norms_compliant\": safe_get_boolean(regulatory_comp.get(\"\
lending_norms_compliant\"), False),\n \"regulatory_compliance_score\":\
\ safe_get_numeric(regulatory_comp.get(\"compliance_score\"), 0),\n \"\
internal_policy_compliance_score\": safe_get_numeric(policy_check.get(\"policy_compliance_score\"\
), 0),\n \n # Final Decision\n \"final_decision\": safe_get(\"\
PENDING\", \"PENDING\"), # Default to PENDING for safety\n \"decision_confidence\"\
: safe_get_numeric(90.0, 0),\n \"overall_processing_status\": safe_get(workflow_output.get(\"\
workflow_status\"), \"completed\"),\n \"processing_time_minutes\": safe_get_numeric(workflow_output.get(\"\
processing_time_minutes\"), 0),\n \n # Metadata\n \"processed_by\"\
: \"automated_workflow\",\n \"processing_node\": \"supabase_upload\"\n\
\ }\n \n # Create DataFrame\n df = pd.DataFrame([loan_data])\n \
\ \n # Generate CSV content\n csv_buffer = io.StringIO()\n df.to_csv(csv_buffer,\
\ index=False)\n csv_content = csv_buffer.getvalue()\n \n # Generate\
\ unique filename\n timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n\
\ app_id = loan_data[\"application_id\"].replace(\"/\", \"_\").replace(\"\\\
\\\", \"_\")\n filename = f\"loan_underwriting_{app_id}_{timestamp}.csv\"\n\
\ \n print(f\"Generated CSV file: {filename}\")\n print(f\"CSV content\
\ size: {len(csv_content)} characters\")\n print(f\"Data rows: {len(df)}\"\
)\n print(f\"Columns: {len(df.columns)}\")\n \n # Upload to Supabase\
\ Storage\n try:\n # Upload file to the loan-approval bucket\n \
\ storage_response = supabase.storage.from_(\"loan-approval\").upload(\n \
\ filename, \n csv_content.encode('utf-8'),\n file_options={\n\
\ \"content-type\": \"text/csv\",\n \"cache-control\"\
: \"3600\"\n }\n )\n \n print(f\"File uploaded\
\ successfully to Supabase\")\n \n # Get public URL\n public_url\
\ = supabase.storage.from_(\"loan-approval\").get_public_url(filename)\n \
\ \n upload_result = {\n \"upload_status\": \"success\",\n\
\ \"bucket_name\": \"loan-approval\",\n \"filename\": filename,\n\
\ \"file_size_bytes\": len(csv_content.encode('utf-8')),\n \
\ \"public_url\": public_url,\n \"upload_timestamp\": datetime.now().isoformat(),\n\
\ \"records_uploaded\": len(df),\n \"columns_count\": len(df.columns),\n\
\ \"data_summary\": {\n \"application_id\": loan_data[\"\
application_id\"],\n \"applicant_name\": loan_data[\"applicant_name\"\
],\n \"requested_amount\": loan_data[\"requested_amount\"],\n \
\ \"internal_credit_score\": loan_data[\"internal_credit_score\"\
],\n \"debt_to_income_ratio\": loan_data[\"debt_to_income_ratio\"\
],\n \"final_decision\": loan_data[\"final_decision\"],\n \
\ \"cibil_score\": loan_data[\"cibil_score\"],\n \"risk_category\"\
: loan_data[\"risk_category\"],\n \"total_provided_documents\"\
: loan_data[\"total_provided_documents\"],\n \"documents_processed_successfully\"\
: loan_data[\"documents_processed_successfully\"],\n \"average_ocr_confidence\"\
: loan_data[\"average_ocr_confidence\"],\n \"pan_card_provided\"\
: loan_data[\"pan_card_provided\"],\n \"aadhaar_card_provided\"\
: loan_data[\"aadhaar_card_provided\"],\n \"passport_provided\"\
: loan_data[\"passport_provided\"],\n \"address_proof_provided\"\
: loan_data[\"address_proof_provided\"],\n \"pan_verified\": loan_data[\"\
pan_verified\"],\n \"aadhaar_verified\": loan_data[\"aadhaar_verified\"\
],\n \"passport_verified\": loan_data[\"passport_verified\"],\n\
\ \"address_proof_verified\": loan_data[\"address_proof_verified\"\
]\n },\n \"csv_structure\": {\n \"columns\"\
: list(df.columns),\n \"column_count\": len(df.columns),\n \
\ \"row_count\": len(df),\n \"key_metrics\": {\n \
\ \"credit_score\": loan_data[\"internal_credit_score\"],\n \
\ \"cibil_score\": loan_data[\"cibil_score\"],\n \
\ \"risk_category\": loan_data[\"risk_category\"],\n \
\ \"kyc_compliant\": loan_data[\"kyc_status\"] == \"compliant\",\n \
\ \"aml_cleared\": loan_data[\"aml_status\"] == \"cleared\",\n \
\ \"ratio_acceptable\": loan_data[\"ratio_acceptable\"],\n \
\ \"documents_provided\": loan_data[\"total_provided_documents\"\
],\n \"documents_processed\": loan_data[\"documents_processed_successfully\"\
],\n \"avg_ocr_confidence\": loan_data[\"average_ocr_confidence\"\
],\n \"pan_provided\": loan_data[\"pan_card_provided\"],\n\
\ \"aadhaar_provided\": loan_data[\"aadhaar_card_provided\"\
]\n }\n }\n }\n \n print(f\"Upload\
\ completed successfully\")\n print(f\"Public URL: {public_url}\")\n \
\ print(f\"Records uploaded: {len(df)}\")\n \n except Exception\
\ as upload_error:\n print(f\"Error uploading to Supabase: {upload_error}\"\
)\n \n # Return error result\n upload_result = {\n \
\ \"upload_status\": \"failed\",\n \"error_message\": str(upload_error),\n\
\ \"bucket_name\": \"loan-approval\",\n \"filename\": filename,\n\
\ \"attempted_upload_timestamp\": datetime.now().isoformat(),\n \
\ \"records_prepared\": len(df),\n \"data_summary\": {\n \
\ \"application_id\": loan_data[\"application_id\"],\n \
\ \"applicant_name\": loan_data[\"applicant_name\"],\n \"\
requested_amount\": loan_data[\"requested_amount\"],\n \"final_decision\"\
: loan_data[\"final_decision\"],\n \"total_provided_documents\"\
: loan_data[\"total_provided_documents\"],\n \"documents_processed_successfully\"\
: loan_data[\"documents_processed_successfully\"],\n \"pan_card_provided\"\
: loan_data[\"pan_card_provided\"],\n \"aadhaar_card_provided\"\
: loan_data[\"aadhaar_card_provided\"]\n }\n }\n\nexcept Exception\
\ as e:\n print(f\"Critical error in Supabase upload process: {e}\")\n \n\
\ # Return critical error result\n upload_result = {\n \"upload_status\"\
: \"critical_error\",\n \"error_message\": str(e),\n \"bucket_name\"\
: \"loan-approval\",\n \"error_timestamp\": datetime.now().isoformat(),\n\
\ \"workflow_id\": \"loan_underwriting_workflow_real\",\n \"application_id\"\
: \"${application_id}\"\n }\n\nprint(f\"__OUTPUTS__ {json.dumps(upload_result)}\"\
)\n"
packages:
- supabase==2.4.4
- pandas==2.0.3
- python-dotenv==1.0.0
depends_on:
- generate_final_output
- final_underwriting_analysis
- calculate_credit_score
- calculate_debt_to_income
- compliance_consolidation
- consolidate_document_verification
- income_assessment
- verify_pan_card
- verify_aadhaar
- verify_passport
- verify_address_proof
- analyze_salary_slips
- analyze_bank_statements
- verify_tax_returns
- verify_employment
- check_cibil_score
- analyze_credit_history
- assess_existing_loans
- kyc_compliance_check
- aml_screening
- regulatory_compliance
- internal_policy_check
description: Upload comprehensive loan underwriting data to Supabase storage bucket
as CSV
previous_node: generate_final_output
timeout_seconds: 120
inputs:
- name: application_id
type: string
required: true
description: Unique loan application identifier
- name: loan_amount
type: integer
required: true
description: Requested loan amount
- name: applicant_data
type: object
required: true
description: Applicant personal information
- name: pan_card_url
type: string
required: false
description: URL of PAN card document (optional)
- name: aadhaar_card_url
type: string
required: false
description: URL of Aadhaar card document (optional)
- name: passport_url
type: string
required: false
description: URL of passport document (optional)
- name: address_proof_url
type: string
required: false
description: URL of address proof document (optional)
- name: salary_slip_url
type: string
required: false
description: URL of salary slip document (optional)
- name: bank_statement_url
type: string
required: false
description: URL of bank statement document (optional)
- name: tax_return_url
type: string
required: false
description: URL of tax return document (optional)
- name: min_credit_score
type: integer
default: 650
required: false
description: Minimum acceptable credit score
- name: max_debt_to_income_ratio
type: float
default: 0.4
required: false
description: Maximum debt-to-income ratio allowed
- name: min_income_multiplier
type: float
default: 3.0
required: false
description: Minimum income multiplier for loan approval
- name: compliance_threshold
type: float
default: 0.95
required: false
description: Minimum compliance score threshold
outputs:
final_decision:
type: object
source: final_underwriting_analysis
description: Final underwriting decision with terms
compliance_status:
type: object
source: compliance_consolidation
description: Overall compliance assessment
credit_assessment:
type: object
source: calculate_credit_score
description: Comprehensive credit assessment results
processing_summary:
type: object
source: generate_final_output
description: Complete workflow processing summary
supabase_upload_result:
type: object
source: upload_to_supabase
description: Result of CSV upload to Supabase storage bucket
version: '1.0'
metadata:
author: Financial Services Team
version: '1.0'
environment: production
last_updated: '2024-07-15'
compliance_certified: true
namespace: financial
description: End-to-end loan underwriting process with document verification, income
analysis, credit assessment, and compliance checks
model_clients:
- id: underwriting_analyst
config:
model: gpt-4o-mini
api_key: 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
max_tokens: 2000
temperature: 0.1
provider: openai
- id: compliance_reviewer
config:
model: gpt-4o-mini
api_key: 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
max_tokens: 1500
temperature: 0.0
provider: openai
- id: risk_assessor
config:
model: gpt-4o-mini
api_key: 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
max_tokens: 1000
temperature: 0.2
provider: openai
timeout_seconds: 3600
| Execution ID | Status | Started | Duration | Actions |
|---|---|---|---|---|
c8dbdc92...
|
COMPLETED |
2025-07-16
08:08:51 |
N/A | View |
d8ff0b68...
|
COMPLETED |
2025-07-16
07:57:05 |
N/A | View |
50412097...
|
COMPLETED |
2025-07-16
07:34:40 |
N/A | View |
3d4eb0f8...
|
COMPLETED |
2025-07-16
07:33:30 |
N/A | View |
50d98b6d...
|
COMPLETED |
2025-07-16
07:32:34 |
N/A | View |
21bf605f...
|
COMPLETED |
2025-07-16
07:24:36 |
N/A | View |