CoolFace
Apppublic

AI-Solutions-KK/grabon-ai-merchant-underwriting-agent

sourceHugging Faceapache-2.0updated 6mo agoView on Hugging Face
0likes
App README

๐Ÿค– GrabOn AI Merchant Underwriting Agent

Production-deployed AI underwriting system โ€” risk scoring, Claude AI decisions, and real WhatsApp offer delivery. Live at โ†’ [huggingface.co/spaces/AI-Solutions-KK/grabon-ai-merchant-underwriting-agent](https://huggingface.co/spaces/AI-Solutions-KK/grabon-ai-merchant-underwriting-agent)

๐Ÿ“ฒ Live WhatsApp Test โ€” 3 Steps

Evaluators: You can receive a real WhatsApp underwriting offer message in under 60 seconds.
StepAction
1. Join SandboxScan the QR code on the dashboard or send join shop-observe to +1 415 523 8886 on WhatsApp
2. Add Your NumberOn the dashboard, click โœ๏ธ next to any approved merchant and enter your number (e.g. 9876543210)
3. Run EngineClick โ–ถ Run Once โ€” if the merchant is APPROVED, you'll receive a live WhatsApp offer message

๐Ÿ—๏ธ System Architecture

mermaid
flowchart TD
    subgraph CLIENT["๐Ÿ‘ค Client Layer"]
        A[Admin / Evaluator Browser]
        WA[๐Ÿ“ฑ WhatsApp User]
    end

    subgraph API["๐ŸŒ API Layer โ€” FastAPI"]
        R[routes.py\nREST API]
        D[dashboard.py\nAdmin Dashboard]
        ADM[admin.py\nEngine Controls]
    end

    subgraph ORCH["๐ŸŽฏ Orchestration Layer"]
        ORC[Orchestrator\norchestrator.py]
    end

    subgraph ENGINES["โš™๏ธ Engine Layer"]
        RE[Risk Engine\nDeterministic Scoring\n13 Business Rules]
        DE[Decision Engine\nApprove / Reject /\nConditional]
        OE[Offer Engine\nCredit + Insurance\nOffer Builder]
        UA[Claude AI Agent\nLLM Explanation +\nRisk Narrative]
    end

    subgraph SERVICES["๐Ÿ”ง Service Layer"]
        MS[Merchant Service]
        ES[Engine Service\nBatch Processor]
        MON[Monitor Service\nBackground Thread\nMD5 Change Detection]
        WAS[WhatsApp Service\nTwilio + retry logic]
        CS[Config Service\nEngine State + Cache]
        APP[Application Service\nRisk Score Persistence]
        AGT[Underwriting Agent\nClaude API Wrapper]
    end

    subgraph DB["๐Ÿ—„๏ธ Data Layer โ€” SQLite"]
        M[(merchants)]
        RS[(risk_scores)]
        SC[(system_config\nEngine state +\nFingerprints)]
    end

    subgraph INFRA["โ˜๏ธ Infrastructure"]
        TW[Twilio\nWhatsApp Sandbox]
        ANT[Anthropic\nClaude 3.5 Sonnet]
        HF[Hugging Face Spaces\nDocker Container]
    end

    A -->|HTTP| D
    A -->|HTTP| ADM
    WA -->|receives offer| TW

    D --> ORC
    ADM --> MON
    ADM --> ES

    ORC --> RE
    ORC --> DE
    ORC --> OE
    ORC --> UA

    RE --> ES
    DE --> ES
    OE --> ES
    UA --> AGT

    ES --> MON
    MON --> WAS
    MON --> CS
    MON --> APP

    WAS --> TW
    AGT --> ANT

    MS --> M
    APP --> RS
    CS --> SC
    ES --> RS

    HF --> API

๐Ÿš€ Overview

This system automates merchant underwriting for GrabCredit and GrabInsure products. It takes raw merchant data, runs it through a deterministic risk scoring pipeline, gets an AI-generated explanation from Claude, makes a final credit/insurance offer decision, and delivers the offer directly to the merchant via WhatsApp โ€” all from an admin dashboard with real-time engine controls.

What makes this production-grade (not a demo):

  • โ€”โœ… Real WhatsApp delivery via Twilio sandbox โ€” not mocked
  • โ€”โœ… Real Claude AI calls โ€” not canned responses
  • โ€”โœ… Persistent SQLite DB โ€” state survives restarts
  • โ€”โœ… MD5 fingerprint change detection โ€” re-processes only changed merchants
  • โ€”โœ… Background daemon engine with 60s polling (ALWAYS_ON mode)
  • โ€”โœ… Docker-containerized and deployed on Hugging Face Spaces
  • โ€”โœ… Rate limit detection with human-readable error reporting
  • โ€”โœ… Idempotent runs โ€” won't spam merchants already messaged

โœ… Latest Update โ€” Deterministic Profile-Derived Credit Scoring

The underwriting pipeline now computes credit_score from merchant profile data in Orchestrator Step 0 using a deterministic weighted CreditEngine.

Updated flow

text
Merchant Profile (API / Seed / SQL)
    โ”‚
    โ–ผ
[1] CreditEngine (weighted deterministic model)
    โ†’ credit_score: 300โ€“850
    โ”‚
    โ–ผ
[2] RiskEngine (hard rules + weighted scoring)
    โ†’ risk_score: 0โ€“100
    โ”‚
    โ–ผ
[3] DecisionEngine (single authority)
    โ†’ APPROVED / APPROVED_WITH_CONDITIONS / REJECTED
    โ”‚
    โ–ผ
[4] OfferEngine
    โ”‚
    โ–ผ
[5] Persist + Optional WhatsApp

Formula (deterministic)

Let factor scores be in $[0,100]$:

  • โ€”$P$ = payment-history proxy
  • โ€”$A$ = amounts-owed proxy
  • โ€”$L$ = length/history proxy
  • โ€”$M$ = credit-mix proxy
  • โ€”$N$ = new-credit proxy

$$ W = 0.35P + 0.30A + 0.15L + 0.10M + 0.10N $$

$$ credit\_score = clamp(300 + 5.5W,\ 300,\ 850) $$

Implementation notes:

  • โ€”Input credit_score remains backward-compatible but is recomputed in orchestration.
  • โ€”Seed/monitor/batch paths rebuild MerchantInput with credit_score=None.
  • โ€”Fingerprint logic excludes derived credit_score from trigger comparison.

๐Ÿง  High-Level Flow


๐Ÿ— System Architecture

1๏ธโƒฃ External Layer

  • โ€”WhatsApp User

๐Ÿ”„ Decision Flow

mermaid
sequenceDiagram
    participant Admin
    participant Dashboard
    participant Orchestrator
    participant RiskEngine
    participant DecisionEngine
    participant OfferEngine
    participant ClaudeAI
    participant MonitorService
    participant WhatsAppService
    participant Merchant

    Admin->>Dashboard: Click "Run Once"
    Dashboard->>MonitorService: POST /admin/engine/on
    MonitorService->>Orchestrator: process_merchant(id)
    Orchestrator->>RiskEngine: score(merchant_data)
    RiskEngine-->>Orchestrator: RiskProfile {score, flags, tier}
    Orchestrator->>ClaudeAI: explain(risk_profile)
    ClaudeAI-->>Orchestrator: AI narrative
    Orchestrator->>DecisionEngine: decide(risk_profile + ai_rec)
    DecisionEngine-->>Orchestrator: APPROVED / REJECTED / CONDITIONAL
    Orchestrator->>OfferEngine: build_offer(decision)
    OfferEngine-->>Orchestrator: CreditOffer + InsuranceOffer
    Orchestrator-->>MonitorService: UnderwritingDecision
    MonitorService->>WhatsAppService: send_offer(merchant_number)
    WhatsAppService-->>Merchant: ๐Ÿ“ฑ WhatsApp message delivered
    MonitorService-->>Dashboard: stats {processed, approved, wa_sent, ...}
    Dashboard-->>Admin: Engine summary banner + per-merchant breakdown

โš™๏ธ Engine Modes

The underwriting engine has 3 operating modes controlled from the dashboard:

ModeBehaviour
โ–ถ Run OnceSynchronous single-pass. Clears cache โ†’ processes all merchants โ†’ returns summary. Blocks until complete.
โˆž Always ONStarts a background daemon thread that polls every 60 seconds. Only re-processes merchants whose data has changed (MD5 fingerprint check).
โน OFFStops the background thread immediately.
Clear CacheWipes all MD5 fingerprints + resets whatsapp_status. Next run treats all merchants as fresh.

Change Detection Logic

Each merchant is fingerprinted across 13 fields (revenue, GMV, chargeback rate, mobile number, etc.) using MD5. Stored in system_config as fp_{merchant_id}. A run only triggers Orchestrator + WhatsApp for merchants whose fingerprint differs from last run.


๐ŸŽฏ Decision Authority Model

mermaid
flowchart LR
    RE[Risk Engine\n13 deterministic rules\nOutputs: score 0-100 + flags]
    AI[Claude AI Agent\nOutputs: recommendation + narrative]
    DE{Decision Engine\nSingle Authority}
    OUT_A[โœ… APPROVED\nCredit + Insurance Offer]
    OUT_R[โŒ REJECTED\nReason + Guidance]
    OUT_C[โšก CONDITIONAL\nApproved with conditions]

    RE --> DE
    AI --> DE
    DE --> OUT_A
    DE --> OUT_R
    DE --> OUT_C

Only the Decision Engine produces final outcomes. Risk Engine and Claude AI are inputs only โ€” this eliminates distributed decision ambiguity and ensures full auditability.


๐Ÿ“Š Risk Scoring โ€” 13 Business Rules

FactorWeightNotes
Monthly Revenue20%Tiered thresholds
GMV15%Gross merchandise volume
Chargeback Rate15%Hard reject >5%
Business Age10%Stability signal
Transaction Volume10%Activity level
Return Rate10%Quality signal
Customer Rating8%NPS proxy
Dispute Count7%Risk indicator
+ 5 moreโ€”Category, payment mix, etc.

Final score 0โ€“100 โ†’ maps to: LOW / MEDIUM / HIGH / CRITICAL risk tier.


๐ŸŒŸ Unique Features

FeatureDetail
3-State Engine ControlON / OFF / ALWAYS_ON with background thread daemon โ€” no page refresh needed
MD5 Change DetectionRe-processes only changed merchants โ€” idempotent at scale
Inline Mobile EditClick โœ๏ธ on dashboard โ†’ edit number in-place โ†’ auto-sends WA if merchant is APPROVED
Per-Merchant WA BreakdownExpandable report card shows sent / failed / skipped per merchant with reason
Human-readable Error UXTwilio error codes (63038, 63007, 21211, 20003) mapped to plain English toasts
Rate Limit GuardFirst 63038 sets _rate_limited flag โ†’ skips all subsequent Twilio calls in that cycle (10ร— faster)
Live WhatsApp Test QREvaluators scan QR โ†’ join sandbox โ†’ receive real offer in <60s
REJECTED GuardOrchestrator blocks WhatsApp dispatch for rejected merchants โ€” no erroneous alerts
Engine Summary BannerAfter every run: processed/approved/rejected/wasent/wafailed/wa_skipped + amber notice on rate limit
Docker + HF SpacesOne-command deploy โ€” persistent SQLite, no external DB required

๐Ÿ› Problems Faced & How They Were Solved

#ProblemRoot CauseSolution
1WA messages never receivedTwilio sandbox 50 msg/day cap (error 63038) hit silentlyAdded 63038 to _NO_RETRY_CODES; hard-fail skips retry loop entirely
2Stats showed false `wa_sent`bool("N/A") == True โ€” Twilio failure returns sid="N/A"sid not in ("N/A", "", None) guard added to wa_ok check
3ALWAYS_ON banner always stalelast_engine_summary was only written by Run Once path, not background thread_run_cycle() now writes summary itself โ€” both modes update dashboard
4Background thread died on `--reload`Uvicorn --reload forks a new process, killing daemon threadsRemoved --reload from production; Run Once made fully synchronous
5Red popup with raw Twilio blobRaw exception message passed to toast directlyhumanizeWaError() JS function maps error codes to plain English
6Merchants re-messaged on every runNo deduplication between runsMD5 fingerprint stored per merchant in system_config table
7Engine ran even when REJECTEDOrchestrator didn't check prior decision stateREJECTED guard added โ€” skips WA dispatch if decision is REJECTED
8Port conflict on HF SpacesFastAPI defaulting to 8000, HF requires 7860CMD in Dockerfile updated to --port 7860
9`aiofiles` missing in DockerStaticFiles mount requires aiofiles implicitlyAdded aiofiles>=23.2.1 to requirements.txt
10`python-multipart` missingFastAPI Form parameters require itAdded python-multipart==0.0.22 to requirements.txt
11Rate limit cycle took 60+ seconds2s ร— 2 retry per merchant on 63038 = 2 min for 10 merchants_rate_limited flag skips all subsequent Twilio calls in that cycle
12Error toast disappeared before readError toasts had same 3.5s timeout as successError toasts now 7s; success stays 3.5s

๐Ÿ—‚ Project Structure (Active Files)

grabon-assignment/
โ”œโ”€โ”€ Dockerfile                          # HF Spaces Docker deploy
โ”œโ”€โ”€ requirements.txt                    # Pinned production deps
โ”œโ”€โ”€ alembic.ini                         # DB migrations config
โ”œโ”€โ”€ .env                                # API keys (not committed)
โ”œโ”€โ”€ .env.example                        # Template for env setup
โ”‚
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ main.py                         # FastAPI app + lifespan + routers
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ api/
โ”‚   โ”‚   โ”œโ”€โ”€ routes.py                   # REST underwriting endpoints
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard.py                # Admin dashboard + inline edit
โ”‚   โ”‚   โ””โ”€โ”€ admin.py                    # Engine control endpoints
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ engines/
โ”‚   โ”‚   โ”œโ”€โ”€ risk_engine.py              # 13-rule deterministic scorer
โ”‚   โ”‚   โ”œโ”€โ”€ decision_engine.py          # Final decision authority
โ”‚   โ”‚   โ””โ”€โ”€ offer_engine.py             # Credit + insurance offer builder
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ orchestrator/
โ”‚   โ”‚   โ””โ”€โ”€ orchestrator.py             # Pipeline coordinator
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ services/
โ”‚   โ”‚   โ”œโ”€โ”€ monitor_service.py          # 3-state engine + MD5 change detection
โ”‚   โ”‚   โ”œโ”€โ”€ engine_service.py           # Batch merchant processor
โ”‚   โ”‚   โ”œโ”€โ”€ merchant_service.py         # Merchant CRUD
โ”‚   โ”‚   โ”œโ”€โ”€ application_service.py      # Risk score persistence
โ”‚   โ”‚   โ”œโ”€โ”€ config_service.py           # system_config key-value store
โ”‚   โ”‚   โ”œโ”€โ”€ whatsapp_service.py         # Twilio WA with retry + rate limit guard
โ”‚   โ”‚   โ””โ”€โ”€ underwriting_agent.py       # Claude AI wrapper
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ”œโ”€โ”€ merchant.py                 # Merchant SQLAlchemy model
โ”‚   โ”‚   โ”œโ”€โ”€ risk_score.py               # Risk score + WA status
โ”‚   โ”‚   โ””โ”€โ”€ system_config.py            # Engine state + fingerprints
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ schemas/
โ”‚   โ”‚   โ”œโ”€โ”€ merchant_schema.py          # Merchant Pydantic schema
โ”‚   โ”‚   โ””โ”€โ”€ decision_schema.py          # Decision + offer schemas
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ db/
โ”‚   โ”‚   โ”œโ”€โ”€ base.py                     # SQLAlchemy declarative base
โ”‚   โ”‚   โ”œโ”€โ”€ session.py                  # Engine + SessionLocal
โ”‚   โ”‚   โ””โ”€โ”€ init_db.py                  # Table creation + seeding
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ scripts/
โ”‚   โ”‚   โ””โ”€โ”€ seed_merchants.py           # Seeds 10 test merchants
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ templates/
โ”‚       โ”œโ”€โ”€ merchant_list.html          # Main dashboard (segmented engine UI)
โ”‚       โ””โ”€โ”€ merchant_detail.html        # Individual merchant detail view
โ”‚
โ””โ”€โ”€ tests/
    โ”œโ”€โ”€ test_decision_engine.py
    โ””โ”€โ”€ test_risk_engine.py

๐Ÿ“ฆ Tech Stack

LayerTechnology
FrameworkFastAPI 0.110 + Uvicorn 0.27
ORM / DBSQLAlchemy 2.0 + SQLite
AIAnthropic Claude 3.5 Sonnet
MessagingTwilio WhatsApp Business API
TemplatingJinja2 3.1
ValidationPydantic v2
MigrationsAlembic 1.13
DeploymentDocker + Hugging Face Spaces
Python3.11-slim

๐Ÿ”ง Local Setup

bash
# 1. Clone
git clone https://huggingface.co/spaces/AI-Solutions-KK/grabon-ai-merchant-underwriting-agent
cd grabon-ai-merchant-underwriting-agent

# 2. Virtual environment
python -m venv .venv
.venv\Scripts\activate        # Windows
# source .venv/bin/activate   # Linux/Mac

# 3. Install dependencies
pip install -r requirements.txt

# 4. Configure environment
cp .env.example .env
# Edit .env with your API keys (see below)

# 5. Run (no --reload โ€” required for background threads)
python -m uvicorn app.main:app --port 8000

# 6. Open dashboard
# http://localhost:8000/dashboard

Required .env Keys

env
# Database
DATABASE_URL=sqlite:///./underwriting.db

# Claude AI
ANTHROPIC_API_KEY=sk-ant-...
CLAUDE_MODEL=claude-3-5-sonnet-20241022

# Twilio WhatsApp
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886

# App
SECRET_KEY=your-secret-key-here
APP_ENV=production

๐Ÿณ Docker

bash
# Build
docker build -t grabon-underwriting .

# Run (maps HF port 7860 โ†’ local 8000)
docker run -p 8000:7860 \
  -e ANTHROPIC_API_KEY=sk-ant-... \
  -e TWILIO_ACCOUNT_SID=ACxxx \
  -e TWILIO_AUTH_TOKEN=xxx \
  -e TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886 \
  grabon-underwriting

๐Ÿ”’ Production Considerations

  • โ€”No `--reload` in production โ€” kills background daemon threads
  • โ€”SQLite persistence โ€” underwriting.db persists across container restarts on HF Spaces
  • โ€”Idempotent engine runs โ€” MD5 fingerprints prevent duplicate WA messages
  • โ€”Rate limit short-circuit โ€” 63038 triggers _rate_limited flag, skips remaining Twilio calls
  • โ€”REJECTED guard โ€” approved-only WhatsApp dispatch, no false notifications
  • โ€”Claude retry policy โ€” exponential backoff with timeout on LLM calls
  • โ€”Structured DB state โ€” all engine decisions persisted in risk_scores table
  • โ€”Webhook idempotency โ€” signature validation, deduplication guard

๐Ÿ“ˆ Development Phases

PhaseDeliverable
Phase 1Architecture design, project scaffold, DB models
Phase 2Risk Engine (13 rules), Decision Engine, Offer Engine
Phase 3Claude AI integration, Orchestrator pipeline wiring
Phase 4WhatsApp delivery via Twilio, message formatting
Phase 5Admin Dashboard, Jinja2 templates, merchant table
Phase 6Engine Service, batch processing, 10 seeded merchants
Phase 7Inline mobile edit, AJAX save, immediate WA on edit
Phase 8.1Monitor Service, MD5 change detection
Phase 8.23-state engine (ON/OFF/ALWAYS_ON), background daemon
Phase 8.3Segmented button UI, auto-refresh, summary banner
Phase 8.4Sr. No. column, expandable per-merchant report card
Phase 8.5WA false-positive fix (sid="N/A" guard)
Phase 8.663038 rate-limit handling, _rate_limited flag, human error toasts
Phase 8.7ALWAYSON writes `lastengine_summary`, amber rate-limit notice
Phase 9Docker containerization, Hugging Face Spaces deployment โœ…


๐Ÿ“„ License

Apache 2.0


Built for GrabOn AI Engineering Assignment โ€” Production deployment on Hugging Face Spaces with live WhatsApp integration.