Spaces:
Sleeping
Sleeping
Commit Β·
612435b
1
Parent(s): 6a71f57
fix: prevent HF 502 by removing sklearn import at startup and hardening DB init
Browse files- backend/app/database/database.py +5 -1
- backend/app/loans/router.py +4 -5
- backend/app/loans/service.py +109 -0
- backend/app/main.py +5 -2
- hf/start.sh +5 -3
backend/app/database/database.py
CHANGED
|
@@ -12,7 +12,11 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__fil
|
|
| 12 |
sqlite_db_path = os.path.join(BASE_DIR, "bankbot.db")
|
| 13 |
|
| 14 |
if USE_SQLITE:
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
connect_args = {}
|
| 18 |
if "sqlite" in SQLALCHEMY_DATABASE_URL:
|
|
|
|
| 12 |
sqlite_db_path = os.path.join(BASE_DIR, "bankbot.db")
|
| 13 |
|
| 14 |
if USE_SQLITE:
|
| 15 |
+
env_db = os.getenv("DATABASE_URL", "")
|
| 16 |
+
if env_db.startswith("sqlite:"):
|
| 17 |
+
SQLALCHEMY_DATABASE_URL = env_db
|
| 18 |
+
else:
|
| 19 |
+
SQLALCHEMY_DATABASE_URL = f"sqlite:///{sqlite_db_path}"
|
| 20 |
|
| 21 |
connect_args = {}
|
| 22 |
if "sqlite" in SQLALCHEMY_DATABASE_URL:
|
backend/app/loans/router.py
CHANGED
|
@@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
|
|
| 6 |
|
| 7 |
from app.database.database import get_db
|
| 8 |
from app.database.models import User
|
| 9 |
-
from app.
|
| 10 |
|
| 11 |
router = APIRouter(prefix="/api/loans", tags=["Loans"])
|
| 12 |
|
|
@@ -36,7 +36,7 @@ def loan_eligibility(
|
|
| 36 |
db: Session = Depends(get_db),
|
| 37 |
):
|
| 38 |
_resolve_user(db, user_id)
|
| 39 |
-
|
| 40 |
body.salary,
|
| 41 |
body.credit_score,
|
| 42 |
body.existing_loans,
|
|
@@ -44,8 +44,6 @@ def loan_eligibility(
|
|
| 44 |
body.age,
|
| 45 |
body.loan_amount,
|
| 46 |
)
|
| 47 |
-
comparison = generate_loan_comparison(body.loan_amount)
|
| 48 |
-
return {**result, "comparison": comparison[:12]}
|
| 49 |
|
| 50 |
|
| 51 |
@router.get("/comparison")
|
|
@@ -55,4 +53,5 @@ def loan_comparison(
|
|
| 55 |
db: Session = Depends(get_db),
|
| 56 |
):
|
| 57 |
_resolve_user(db, user_id)
|
| 58 |
-
|
|
|
|
|
|
| 6 |
|
| 7 |
from app.database.database import get_db
|
| 8 |
from app.database.models import User
|
| 9 |
+
from app.loans.service import analyze_loan
|
| 10 |
|
| 11 |
router = APIRouter(prefix="/api/loans", tags=["Loans"])
|
| 12 |
|
|
|
|
| 36 |
db: Session = Depends(get_db),
|
| 37 |
):
|
| 38 |
_resolve_user(db, user_id)
|
| 39 |
+
return analyze_loan(
|
| 40 |
body.salary,
|
| 41 |
body.credit_score,
|
| 42 |
body.existing_loans,
|
|
|
|
| 44 |
body.age,
|
| 45 |
body.loan_amount,
|
| 46 |
)
|
|
|
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
@router.get("/comparison")
|
|
|
|
| 53 |
db: Session = Depends(get_db),
|
| 54 |
):
|
| 55 |
_resolve_user(db, user_id)
|
| 56 |
+
result = analyze_loan(85000, 720, 1, 4, 32, loan_amount)
|
| 57 |
+
return {"loan_amount": loan_amount, "comparison": result["comparison"]}
|
backend/app/loans/service.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Loan eligibility without heavy ML deps (safe for HF Docker)."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def calculate_emi(principal: float, rate_percent: float, tenure_years: int) -> float:
|
| 5 |
+
if rate_percent <= 0:
|
| 6 |
+
months = tenure_years * 12
|
| 7 |
+
return principal / months if months else principal
|
| 8 |
+
monthly_rate = rate_percent / 100 / 12
|
| 9 |
+
months = tenure_years * 12
|
| 10 |
+
if monthly_rate == 0:
|
| 11 |
+
return principal / months
|
| 12 |
+
return principal * monthly_rate * ((1 + monthly_rate) ** months) / (((1 + monthly_rate) ** months) - 1)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def analyze_loan(
|
| 16 |
+
salary: float,
|
| 17 |
+
credit_score: int,
|
| 18 |
+
existing_loans: int,
|
| 19 |
+
employment_years: float,
|
| 20 |
+
age: int,
|
| 21 |
+
loan_amount: float,
|
| 22 |
+
) -> dict:
|
| 23 |
+
issues: list[str] = []
|
| 24 |
+
score = 50.0
|
| 25 |
+
|
| 26 |
+
if credit_score >= 750:
|
| 27 |
+
score += 20
|
| 28 |
+
elif credit_score >= 700:
|
| 29 |
+
score += 12
|
| 30 |
+
elif credit_score >= 650:
|
| 31 |
+
score += 5
|
| 32 |
+
else:
|
| 33 |
+
score -= 10
|
| 34 |
+
issues.append("Credit score below 650 β consider improving before applying")
|
| 35 |
+
|
| 36 |
+
if employment_years >= 3:
|
| 37 |
+
score += 10
|
| 38 |
+
elif employment_years < 1:
|
| 39 |
+
score -= 8
|
| 40 |
+
issues.append("Less than 1 year employment β higher scrutiny expected")
|
| 41 |
+
|
| 42 |
+
if age < 21 or age > 65:
|
| 43 |
+
issues.append("Age outside typical lending range (21β65)")
|
| 44 |
+
|
| 45 |
+
if existing_loans > 3:
|
| 46 |
+
score -= 12
|
| 47 |
+
issues.append("Multiple existing loans may reduce approval odds")
|
| 48 |
+
|
| 49 |
+
emi = calculate_emi(loan_amount, 12, 10)
|
| 50 |
+
monthly_salary = salary / 12
|
| 51 |
+
emi_ratio = (emi / monthly_salary * 100) if monthly_salary > 0 else 100
|
| 52 |
+
|
| 53 |
+
if emi_ratio > 50:
|
| 54 |
+
score -= 15
|
| 55 |
+
issues.append(f"EMI is {emi_ratio:.0f}% of monthly income β reduce loan amount")
|
| 56 |
+
elif emi_ratio < 35:
|
| 57 |
+
score += 8
|
| 58 |
+
|
| 59 |
+
if loan_amount > salary * 5:
|
| 60 |
+
score -= 10
|
| 61 |
+
issues.append("Loan amount exceeds 5Γ annual salary")
|
| 62 |
+
|
| 63 |
+
score = max(0, min(100, score))
|
| 64 |
+
approval_probability = score
|
| 65 |
+
|
| 66 |
+
if approval_probability >= 70:
|
| 67 |
+
status = "APPROVED"
|
| 68 |
+
risk = "Low"
|
| 69 |
+
elif approval_probability >= 45:
|
| 70 |
+
status = "UNDER REVIEW"
|
| 71 |
+
risk = "Medium"
|
| 72 |
+
else:
|
| 73 |
+
status = "LIKELY REJECTED"
|
| 74 |
+
risk = "High"
|
| 75 |
+
|
| 76 |
+
recommendations = []
|
| 77 |
+
if approval_probability >= 70:
|
| 78 |
+
recommendations.append("Strong profile β you are likely to qualify at competitive rates")
|
| 79 |
+
elif approval_probability < 45:
|
| 80 |
+
recommendations.append("Consider a smaller loan or improving credit score before applying")
|
| 81 |
+
if emi_ratio < 35:
|
| 82 |
+
recommendations.append(f"Healthy EMI ratio ({emi_ratio:.0f}% of monthly income)")
|
| 83 |
+
elif emi_ratio > 40:
|
| 84 |
+
recommendations.append("Try a longer tenure or lower amount to reduce monthly EMI")
|
| 85 |
+
|
| 86 |
+
comparison = []
|
| 87 |
+
for rate in (9, 10, 11, 12):
|
| 88 |
+
for tenure in (5, 7, 10):
|
| 89 |
+
e = calculate_emi(loan_amount, rate, tenure)
|
| 90 |
+
total = e * 12 * tenure
|
| 91 |
+
comparison.append({
|
| 92 |
+
"rate": f"{rate}%",
|
| 93 |
+
"tenure": f"{tenure} years",
|
| 94 |
+
"emi": round(e, 2),
|
| 95 |
+
"total_amount": round(total, 2),
|
| 96 |
+
"interest": round(total - loan_amount, 2),
|
| 97 |
+
})
|
| 98 |
+
|
| 99 |
+
return {
|
| 100 |
+
"approval_probability": round(approval_probability, 1),
|
| 101 |
+
"approval_status": status,
|
| 102 |
+
"risk_level": risk,
|
| 103 |
+
"loan_score": round(score, 1),
|
| 104 |
+
"emi": round(emi, 2),
|
| 105 |
+
"monthly_emi": round(emi, 2),
|
| 106 |
+
"issues": issues,
|
| 107 |
+
"recommendations": recommendations,
|
| 108 |
+
"comparison": comparison[:12],
|
| 109 |
+
}
|
backend/app/main.py
CHANGED
|
@@ -111,8 +111,11 @@ async def rate_limit(request: Request, call_next):
|
|
| 111 |
def startup():
|
| 112 |
api_logger.info("BankBot API starting", extra={"version": "2.0.0"})
|
| 113 |
Base.metadata.create_all(bind=engine)
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
| 116 |
api_logger.info("Database tables ready")
|
| 117 |
|
| 118 |
# Log active backends
|
|
|
|
| 111 |
def startup():
|
| 112 |
api_logger.info("BankBot API starting", extra={"version": "2.0.0"})
|
| 113 |
Base.metadata.create_all(bind=engine)
|
| 114 |
+
try:
|
| 115 |
+
from app.database.chat_migrate import migrate_chat_schema
|
| 116 |
+
migrate_chat_schema()
|
| 117 |
+
except Exception as exc:
|
| 118 |
+
api_logger.warning("Chat schema migrate skipped", extra={"error": str(exc)[:200]})
|
| 119 |
api_logger.info("Database tables ready")
|
| 120 |
|
| 121 |
# Log active backends
|
hf/start.sh
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
# BankBot AI β Hugging Face Spaces startup script
|
| 4 |
# Runs: DB init β optional seed β supervisord (nginx + fastapi + nextjs)
|
| 5 |
# ============================================================
|
| 6 |
-
set -
|
| 7 |
|
| 8 |
echo "============================================"
|
| 9 |
echo " BankBot AI β Starting on Hugging Face"
|
|
@@ -57,9 +57,11 @@ cd /app/backend
|
|
| 57 |
python -c "
|
| 58 |
from app.database.database import engine, Base
|
| 59 |
import app.database.models
|
|
|
|
| 60 |
Base.metadata.create_all(bind=engine)
|
|
|
|
| 61 |
print(' Database tables ready')
|
| 62 |
-
"
|
| 63 |
|
| 64 |
# ββ Seed demo data (only if DB is empty) βββββββββββββββββββββββββββββββββββββ
|
| 65 |
echo "[2/3] Checking demo data..."
|
|
@@ -87,7 +89,7 @@ if count == 0:
|
|
| 87 |
print(' Demo account ready: alex@bankbot.dev / BankBot2026!')
|
| 88 |
else:
|
| 89 |
print(f' Database has {count} users β skipping seed')
|
| 90 |
-
"
|
| 91 |
|
| 92 |
# ββ Start all services via supervisord βββββββββββββββββββββββββββββββββββββββ
|
| 93 |
echo "[3/3] Starting services (Nginx + FastAPI + Next.js)..."
|
|
|
|
| 3 |
# BankBot AI β Hugging Face Spaces startup script
|
| 4 |
# Runs: DB init β optional seed β supervisord (nginx + fastapi + nextjs)
|
| 5 |
# ============================================================
|
| 6 |
+
set -euo pipefail
|
| 7 |
|
| 8 |
echo "============================================"
|
| 9 |
echo " BankBot AI β Starting on Hugging Face"
|
|
|
|
| 57 |
python -c "
|
| 58 |
from app.database.database import engine, Base
|
| 59 |
import app.database.models
|
| 60 |
+
from app.database.chat_migrate import migrate_chat_schema
|
| 61 |
Base.metadata.create_all(bind=engine)
|
| 62 |
+
migrate_chat_schema()
|
| 63 |
print(' Database tables ready')
|
| 64 |
+
" || { echo "[WARN] DB init had errors β continuing"; }
|
| 65 |
|
| 66 |
# ββ Seed demo data (only if DB is empty) βββββββββββββββββββββββββββββββββββββ
|
| 67 |
echo "[2/3] Checking demo data..."
|
|
|
|
| 89 |
print(' Demo account ready: alex@bankbot.dev / BankBot2026!')
|
| 90 |
else:
|
| 91 |
print(f' Database has {count} users β skipping seed')
|
| 92 |
+
" || echo "[WARN] Seed check failed β continuing"
|
| 93 |
|
| 94 |
# ββ Start all services via supervisord βββββββββββββββββββββββββββββββββββββββ
|
| 95 |
echo "[3/3] Starting services (Nginx + FastAPI + Next.js)..."
|