| import gradio as gr |
| import os |
| from typing import List, Dict, Tuple |
| import json |
| import datetime |
|
|
| |
| def validate_documents(files: List[str]) -> Tuple[str, str]: |
| """Simulate Intake Agent: Check if all required docs are present.""" |
| if not files: |
| return "β No documents uploaded", "error" |
| |
| required = ["ID", "Pay Stubs", "W-2", "Tax Returns", "Bank Statements", "Offer", "Insurance", "Title"] |
| uploaded = [os.path.basename(f).split(".")[0] for f in files] |
| missing = [doc for doc in required if doc not in uploaded] |
| |
| if missing: |
| return f"β οΈ Missing documents: {', '.join(missing)}", "warning" |
| return "β
All documents validated successfully", "success" |
|
|
| def extract_data(files: List[str], ssn: str) -> Dict: |
| """Simulate Extraction Agent: Extract data from documents.""" |
| if not files or not ssn: |
| return {} |
| |
| return { |
| "application_id": "APP-2024-001234", |
| "ssn": ssn, |
| "applicant_name": "John Doe", |
| "monthly_income": 8500, |
| "monthly_debts": 2100, |
| "credit_score": 720, |
| "property_value": 450000, |
| "loan_amount": 360000, |
| "insurance_coverage": "12_months", |
| "title_status": "Clear", |
| "employment_status": "Full-time", |
| "years_employed": 3.5 |
| } |
|
|
| def analyze_data(data: Dict) -> Dict: |
| """Simulate Credit/Capacity/Collateral/Compliance Agents: Analyze data.""" |
| if not data: |
| return {} |
| |
| dti = (data["monthly_debts"] / data["monthly_income"]) * 100 |
| ltv = (data["loan_amount"] / data["property_value"]) * 100 |
| flags = [] |
| risk_level = "Low" |
| |
| if dti > 45: |
| flags.append("High DTI Ratio") |
| risk_level = "High" |
| elif dti > 36: |
| flags.append("Elevated DTI Ratio") |
| risk_level = "Medium" |
| |
| if data["credit_score"] < 620: |
| flags.append("Low Credit Score") |
| risk_level = "High" |
| elif data["credit_score"] < 680: |
| flags.append("Fair Credit Score") |
| if risk_level != "High": |
| risk_level = "Medium" |
| |
| if ltv > 95: |
| flags.append("High LTV Ratio") |
| risk_level = "High" |
| elif ltv > 80: |
| flags.append("Elevated LTV Ratio") |
| if risk_level == "Low": |
| risk_level = "Medium" |
| |
| if "Clear" not in data["title_status"]: |
| flags.append("Title Issues") |
| risk_level = "High" |
| |
| status = "Ready for Approval" if not flags else "Requires Review" |
| if risk_level == "High": |
| status = "High Risk - Manual Review Required" |
| |
| return { |
| "application_id": data["application_id"], |
| "applicant_name": data["applicant_name"], |
| "dti_ratio": round(dti, 2), |
| "ltv_ratio": round(ltv, 2), |
| "credit_score": data["credit_score"], |
| "monthly_income": f"${data['monthly_income']:,}", |
| "monthly_debts": f"${data['monthly_debts']:,}", |
| "loan_amount": f"${data['loan_amount']:,}", |
| "property_value": f"${data['property_value']:,}", |
| "risk_level": risk_level, |
| "flags": flags, |
| "status": status, |
| "processed_date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| } |
|
|
| def handle_conditions(additional_files: List[str], comments: str) -> str: |
| """Simulate Conditions Agent: Process additional uploads.""" |
| if additional_files and comments.strip(): |
| file_names = [os.path.basename(f) for f in additional_files] |
| return f"β
Additional documentation received: {', '.join(file_names)}\n\nπ Comments: {comments}\n\nβ³ Status: Under Review" |
| elif additional_files: |
| file_names = [os.path.basename(f) for f in additional_files] |
| return f"β
Additional files uploaded: {', '.join(file_names)}\n\nβ οΈ Please provide comments for review" |
| elif comments.strip(): |
| return f"π Comments noted: {comments}\n\nβ οΈ Please upload supporting documents" |
| return "β No additional files or comments provided" |
|
|
| def finalize_approval(decision: str, comments: str, analysis: Dict) -> str: |
| """Simulate Final Approval Agent: Finalize decision.""" |
| if not analysis: |
| return "β Error: No analysis data available. Please complete analysis first." |
| |
| app_id = analysis.get("application_id", "N/A") |
| applicant = analysis.get("applicant_name", "N/A") |
| timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| |
| if decision == "Approve": |
| if not analysis.get("flags", []): |
| return f""" |
| π **APPLICATION APPROVED** |
| |
| π **Application ID:** {app_id} |
| π€ **Applicant:** {applicant} |
| π
**Decision Date:** {timestamp} |
| β
**Status:** Clear to Close |
| |
| π¦ **Next Steps:** |
| - Closing package will be generated automatically |
| - Borrower will be contacted within 24 hours |
| - Expected closing date: {(datetime.datetime.now() + datetime.timedelta(days=14)).strftime("%Y-%m-%d")} |
| |
| π¬ **Comments:** {comments if comments.strip() else 'Standard approval - all criteria met'} |
| """ |
| else: |
| return f""" |
| β
**APPLICATION APPROVED WITH CONDITIONS** |
| |
| π **Application ID:** {app_id} |
| π€ **Applicant:** {applicant} |
| π
**Decision Date:** {timestamp} |
| β οΈ **Conditions:** {', '.join(analysis['flags'])} |
| |
| π **Required Actions:** |
| - Address flagged conditions before closing |
| - Submit additional documentation if required |
| - Schedule manual review if needed |
| |
| π¬ **Comments:** {comments} |
| """ |
| else: |
| return f""" |
| β **APPLICATION REJECTED** |
| |
| π **Application ID:** {app_id} |
| π€ **Applicant:** {applicant} |
| π
**Decision Date:** {timestamp} |
| |
| π« **Rejection Reasons:** |
| {chr(10).join([f"β’ {flag}" for flag in analysis.get('flags', ['Manual rejection'])])} |
| |
| π **Next Steps:** |
| - Applicant will be contacted within 48 hours |
| - Adverse action notice will be sent |
| - Reapplication possible after addressing issues |
| |
| π¬ **Comments:** {comments} |
| """ |
|
|
| |
| custom_css = """ |
| /* Global Styles */ |
| .gradio-container { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; |
| min-height: 100vh; |
| } |
| |
| .main { |
| background: rgba(255, 255, 255, 0.95) !important; |
| border-radius: 20px !important; |
| box-shadow: 0 20px 40px rgba(0,0,0,0.1) !important; |
| margin: 20px !important; |
| padding: 0 !important; |
| } |
| |
| /* Header Styling */ |
| .header-section { |
| background: linear-gradient(90deg, #1e3c72 0%, #2a5298 100%) !important; |
| color: white !important; |
| padding: 30px !important; |
| border-radius: 20px 20px 0 0 !important; |
| margin-bottom: 0 !important; |
| text-align: center !important; |
| } |
| |
| .header-section h1 { |
| margin: 0 !important; |
| font-size: 2.5rem !important; |
| font-weight: 700 !important; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3) !important; |
| } |
| |
| .header-section p { |
| margin: 10px 0 0 0 !important; |
| font-size: 1.1rem !important; |
| opacity: 0.9 !important; |
| } |
| |
| /* Section Styling */ |
| .section-card { |
| background: white !important; |
| border-radius: 15px !important; |
| padding: 25px !important; |
| margin: 20px !important; |
| box-shadow: 0 8px 25px rgba(0,0,0,0.1) !important; |
| border: 1px solid rgba(0,0,0,0.05) !important; |
| transition: all 0.3s ease !important; |
| } |
| |
| .section-card:hover { |
| transform: translateY(-2px) !important; |
| box-shadow: 0 12px 35px rgba(0,0,0,0.15) !important; |
| } |
| |
| .section-title { |
| color: #2c3e50 !important; |
| font-size: 1.5rem !important; |
| font-weight: 600 !important; |
| margin-bottom: 20px !important; |
| padding-bottom: 10px !important; |
| border-bottom: 3px solid #3498db !important; |
| display: flex !important; |
| align-items: center !important; |
| gap: 10px !important; |
| } |
| |
| /* Button Styling */ |
| .btn-primary { |
| background: linear-gradient(45deg, #667eea, #764ba2) !important; |
| border: none !important; |
| border-radius: 10px !important; |
| padding: 12px 30px !important; |
| font-weight: 600 !important; |
| font-size: 1rem !important; |
| transition: all 0.3s ease !important; |
| text-transform: uppercase !important; |
| letter-spacing: 0.5px !important; |
| } |
| |
| .btn-primary:hover { |
| transform: translateY(-2px) !important; |
| box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4) !important; |
| } |
| |
| .btn-success { |
| background: linear-gradient(45deg, #56ab2f, #a8e6cf) !important; |
| border: none !important; |
| border-radius: 10px !important; |
| padding: 12px 30px !important; |
| font-weight: 600 !important; |
| } |
| |
| .btn-danger { |
| background: linear-gradient(45deg, #ff416c, #ff4b2b) !important; |
| border: none !important; |
| border-radius: 10px !important; |
| padding: 12px 30px !important; |
| font-weight: 600 !important; |
| } |
| |
| /* Input Styling */ |
| input, textarea, select { |
| border-radius: 8px !important; |
| border: 2px solid #e1e8ed !important; |
| padding: 12px !important; |
| font-size: 1rem !important; |
| transition: all 0.3s ease !important; |
| } |
| |
| input:focus, textarea:focus, select:focus { |
| border-color: #667eea !important; |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important; |
| outline: none !important; |
| } |
| |
| /* Status Messages */ |
| .status-success { |
| background: linear-gradient(45deg, #56ab2f, #a8e6cf) !important; |
| color: white !important; |
| padding: 15px !important; |
| border-radius: 10px !important; |
| font-weight: 600 !important; |
| } |
| |
| .status-warning { |
| background: linear-gradient(45deg, #f7971e, #ffd200) !important; |
| color: #333 !important; |
| padding: 15px !important; |
| border-radius: 10px !important; |
| font-weight: 600 !important; |
| } |
| |
| .status-error { |
| background: linear-gradient(45deg, #ff416c, #ff4b2b) !important; |
| color: white !important; |
| padding: 15px !important; |
| border-radius: 10px !important; |
| font-weight: 600 !important; |
| } |
| |
| /* Progress Indicators */ |
| .step-indicator { |
| display: flex !important; |
| justify-content: space-between !important; |
| margin: 20px 0 !important; |
| position: relative !important; |
| } |
| |
| .step { |
| background: #e1e8ed !important; |
| border-radius: 50% !important; |
| width: 40px !important; |
| height: 40px !important; |
| display: flex !important; |
| align-items: center !important; |
| justify-content: center !important; |
| font-weight: bold !important; |
| color: #666 !important; |
| z-index: 2 !important; |
| } |
| |
| .step.active { |
| background: linear-gradient(45deg, #667eea, #764ba2) !important; |
| color: white !important; |
| } |
| |
| .step.completed { |
| background: linear-gradient(45deg, #56ab2f, #a8e6cf) !important; |
| color: white !important; |
| } |
| |
| /* JSON Display Enhancement */ |
| .json-display { |
| background: #f8f9fa !important; |
| border: 1px solid #e9ecef !important; |
| border-radius: 10px !important; |
| padding: 20px !important; |
| font-family: 'Monaco', 'Consolas', monospace !important; |
| max-height: 400px !important; |
| overflow-y: auto !important; |
| } |
| |
| /* File Upload Styling */ |
| .file-upload { |
| border: 2px dashed #667eea !important; |
| border-radius: 10px !important; |
| padding: 30px !important; |
| text-align: center !important; |
| background: rgba(102, 126, 234, 0.05) !important; |
| transition: all 0.3s ease !important; |
| } |
| |
| .file-upload:hover { |
| background: rgba(102, 126, 234, 0.1) !important; |
| border-color: #5a67d8 !important; |
| } |
| |
| /* Responsive Design */ |
| @media (max-width: 768px) { |
| .main { |
| margin: 10px !important; |
| } |
| |
| .section-card { |
| margin: 10px !important; |
| padding: 20px !important; |
| } |
| |
| .header-section h1 { |
| font-size: 2rem !important; |
| } |
| } |
| """ |
|
|
| |
| with gr.Blocks(css=custom_css, title="Mortgage Underwriting System", theme=gr.themes.Soft()) as app: |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML(""" |
| <div class="header-section"> |
| <h1>π Mortgage Underwriting Automation</h1> |
| <p>Intelligent Document Processing & Risk Assessment Platform</p> |
| </div> |
| """) |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML(""" |
| <div class="section-card"> |
| <div class="step-indicator"> |
| <div class="step active">1</div> |
| <div class="step">2</div> |
| <div class="step">3</div> |
| <div class="step">4</div> |
| </div> |
| <div style="text-align: center; margin-top: 10px; color: #666;"> |
| <strong>Document Upload</strong> β Data Analysis β Conditions β Final Approval |
| </div> |
| </div> |
| """) |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML('<div class="section-card">') |
| gr.HTML('<div class="section-title">π Document Upload & Validation</div>') |
| |
| with gr.Row(): |
| with gr.Column(scale=2): |
| files_input = gr.File( |
| label="Required Documents", |
| file_count="multiple", |
| file_types=[".pdf", ".png", ".jpg", ".jpeg"], |
| elem_classes=["file-upload"] |
| ) |
| gr.HTML(""" |
| <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin: 10px 0;"> |
| <strong>π Required Documents Checklist:</strong><br> |
| β’ Government-issued ID<br> |
| β’ Recent Pay Stubs (2-3 months)<br> |
| β’ W-2 Forms (2 years)<br> |
| β’ Tax Returns (2 years)<br> |
| β’ Bank Statements (3 months)<br> |
| β’ Purchase Offer/Contract<br> |
| β’ Insurance Documentation<br> |
| β’ Property Title Information |
| </div> |
| """) |
| |
| with gr.Column(scale=1): |
| ssn_input = gr.Textbox( |
| label="Social Security Number", |
| placeholder="XXX-XX-XXXX", |
| type="password" |
| ) |
| validate_btn = gr.Button("π Validate Documents", variant="primary", size="lg") |
| validate_output = gr.Textbox( |
| label="Validation Status", |
| interactive=False, |
| lines=2 |
| ) |
| |
| gr.HTML('</div>') |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML('<div class="section-card">') |
| gr.HTML('<div class="section-title">π Automated Analysis & Risk Assessment</div>') |
| |
| with gr.Row(): |
| with gr.Column(): |
| extract_btn = gr.Button("β‘ Extract Data & Analyze Risk", variant="primary", size="lg") |
| |
| with gr.Row(): |
| with gr.Column(): |
| analysis_output = gr.JSON( |
| label="π Comprehensive Analysis Results", |
| elem_classes=["json-display"] |
| ) |
| |
| with gr.Column(): |
| gr.HTML(""" |
| <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px;"> |
| <h3 style="margin: 0 0 15px 0;">π― Risk Assessment Criteria</h3> |
| <div style="background: rgba(255,255,255,0.1); padding: 15px; border-radius: 8px;"> |
| <strong>π DTI Ratio:</strong> β€ 45% (Optimal: β€ 36%)<br> |
| <strong>π³ Credit Score:</strong> β₯ 620 (Preferred: β₯ 680)<br> |
| <strong>ποΈ LTV Ratio:</strong> β€ 95% (Conventional: β€ 80%)<br> |
| <strong>π Documentation:</strong> Complete & Verified<br> |
| <strong>π’ Employment:</strong> Stable Income History |
| </div> |
| </div> |
| """) |
| |
| gr.HTML('</div>') |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML('<div class="section-card">') |
| gr.HTML('<div class="section-title">π Additional Documentation & Conditions</div>') |
| |
| with gr.Row(): |
| with gr.Column(): |
| additional_files = gr.File( |
| label="Upload Additional Documents", |
| file_count="multiple", |
| file_types=[".pdf", ".png", ".jpg", ".jpeg"], |
| elem_classes=["file-upload"] |
| ) |
| condition_comments = gr.Textbox( |
| label="Underwriter Comments & Instructions", |
| placeholder="Provide detailed comments about additional requirements...", |
| lines=4 |
| ) |
| condition_btn = gr.Button("π€ Submit Additional Documentation", variant="secondary", size="lg") |
| |
| with gr.Column(): |
| condition_output = gr.Textbox( |
| label="π Conditions Status", |
| interactive=False, |
| lines=6 |
| ) |
| |
| gr.HTML(""" |
| <div style="background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 8px; color: #856404;"> |
| <strong>π‘ Common Additional Requirements:</strong><br> |
| β’ Gift Letter (for down payment assistance)<br> |
| β’ Employment Verification Letter<br> |
| β’ Asset Verification Documents<br> |
| β’ Property Appraisal Report<br> |
| β’ Explanation Letters (for credit items)<br> |
| β’ Homeowner's Insurance Proof |
| </div> |
| """) |
| |
| gr.HTML('</div>') |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML('<div class="section-card">') |
| gr.HTML('<div class="section-title">βοΈ Final Underwriting Decision</div>') |
| |
| with gr.Row(): |
| with gr.Column(): |
| approve_radio = gr.Radio( |
| choices=["Approve", "Reject"], |
| label="π Underwriting Decision", |
| info="Select final decision based on comprehensive analysis" |
| ) |
| approval_comments = gr.Textbox( |
| label="π¬ Decision Comments & Rationale", |
| placeholder="Provide detailed reasoning for the decision...", |
| lines=4 |
| ) |
| approve_btn = gr.Button("β
Submit Final Decision", variant="primary", size="lg") |
| |
| with gr.Column(): |
| final_output = gr.Textbox( |
| label="π Final Approval Status", |
| interactive=False, |
| lines=12, |
| elem_classes=["status-display"] |
| ) |
| |
| gr.HTML('</div>') |
| |
| |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML(""" |
| <div style="text-align: center; padding: 20px; color: #666; background: rgba(0,0,0,0.05); border-radius: 0 0 20px 20px;"> |
| <p>π Secure β’ π€ AI-Powered β’ β‘ Real-time Processing</p> |
| <p><small>Β© 2024 Mortgage Underwriting Automation Platform | Version 2.0</small></p> |
| </div> |
| """) |
| |
| |
| validate_btn.click( |
| fn=lambda files: validate_documents(files)[0], |
| inputs=files_input, |
| outputs=validate_output |
| ) |
| |
| extract_btn.click( |
| fn=lambda files, ssn: analyze_data(extract_data(files, ssn)), |
| inputs=[files_input, ssn_input], |
| outputs=analysis_output |
| ) |
| |
| condition_btn.click( |
| fn=handle_conditions, |
| inputs=[additional_files, condition_comments], |
| outputs=condition_output |
| ) |
| |
| approve_btn.click( |
| fn=finalize_approval, |
| inputs=[approve_radio, approval_comments, analysis_output], |
| outputs=final_output |
| ) |
|
|
| if __name__ == "__main__": |
| app.launch( |
| server_name="0.0.0.0", |
| server_port=7860, |
| share=False, |
| show_error=True, |
| favicon_path=None, |
| ssl_verify=False, |
| debug=True |
| ) |