Skip to content

VictorGoic0/SpendSense

Repository files navigation

SpendSense

Personalized Financial Education Platform

SpendSense transforms synthetic bank transaction data into actionable financial education through behavioral pattern detection, persona-based recommendations, and AI-generated content. The platform analyzes user spending patterns, assigns educational personas, and generates personalized financial recommendations with strict consent management and operator oversight workflows.

Table of Contents


Overview

SpendSense is a comprehensive financial education platform that:

  • Detects Financial Patterns: Analyzes transaction data to identify behavioral patterns (subscriptions, credit utilization, savings habits, income variability)
  • Assigns Educational Personas: Categorizes users into one of five personas (High Utilization, Variable Income, Subscription Heavy, Savings Builder, Wealth Builder) based on their financial behavior
  • Generates AI Recommendations: Creates personalized financial education content using OpenAI GPT-4o-mini, tailored to each user's persona and financial situation
  • Manages Consent: Implements strict consent management with audit trails
  • Operator Oversight: Provides approval workflows for operators to review, approve, reject, or override AI-generated recommendations before they reach users

The platform is designed with explainability, responsible AI practices, and user privacy at its core.


Key Features

  • Behavioral Feature Detection: Computes 20+ financial signals from transaction data
  • Persona Assignment: Automatic assignment to educational personas based on behavioral patterns
  • AI-Powered Recommendations: GPT-4o-mini generates personalized financial education content
  • Consent Management: Full consent tracking with grant/revoke capabilities and audit logs
  • Operator Workflows: Approval, rejection, override, and bulk approval capabilities
  • Dual Window Analysis: Supports both 30-day and 180-day analysis windows
  • Operator Dashboard: Comprehensive metrics and user insights
  • Responsive UI: Modern React frontend with Shadcn/ui components

Project Setup

Prerequisites

  • Python: 3.11 or higher
  • Node.js: Latest stable version (check frontend/package.json for compatibility)
  • SQLite: Included with Python (no separate installation needed)
  • OpenAI API Key: Required for recommendation generation

Backend Setup

  1. Navigate to the backend directory:
cd backend
  1. Create and activate a virtual environment:
# macOS/Linux
python3 -m venv venv
source venv/bin/activate

# Windows
python -m venv venv
venv\Scripts\activate
  1. Install dependencies:
pip install -r requirements.txt
  1. Set up environment variables:
# Copy example environment file (if available)
# Create a .env file with:
# OPENAI_API_KEY=your_api_key_here
  1. Initialize the database (runs automatically on startup):
# The database is initialized automatically when the server starts
  1. Start the FastAPI server:

Development (with auto-reload):

uvicorn app.main:app --reload

Production/Concurrent requests (with workers):

uvicorn app.main:app --workers 4 --host 0.0.0.0 --port 8000

The API will be available at http://localhost:8000

  • API Documentation: http://localhost:8000/docs (Swagger UI)
  • Alternative Docs: http://localhost:8000/redoc

Frontend Setup

  1. Navigate to the frontend directory:
cd frontend
  1. Install dependencies:
npm install
  1. Start the development server:
npm run dev

The frontend will be available at http://localhost:5173 (Vite default port)

Environment Configuration

Backend (.env file in backend/):

OPENAI_API_KEY=your_openai_api_key_here

Frontend: No environment variables required for basic setup. API base URL is configured in src/lib/api.js.


Tech Stack

Backend

  • Framework: FastAPI 0.104.1
  • Language: Python 3.11+
  • Database: SQLite (via SQLAlchemy 2.0.23)
  • ORM: SQLAlchemy 2.0.23
  • Validation: Pydantic 2.5.0
  • Server: Uvicorn 0.24.0 (with standard extras)
  • AI Integration: OpenAI SDK 2.7.1
  • Utilities:
    • python-dotenv 1.0.0 (environment variable management)
    • faker 20.1.0 (synthetic data generation)
    • requests 2.31.0 (HTTP client for testing)

Frontend

  • Framework: React 18.3.1
  • Build Tool: Vite 7.1.7
  • Routing: React Router DOM 7.9.5
  • UI Components:
    • Shadcn/ui (Radix UI primitives)
    • @radix-ui/react-dialog 1.1.15
    • @radix-ui/react-slot 1.2.4
    • @radix-ui/react-switch 1.2.6
  • Styling:
    • Tailwind CSS 3.4.17
    • tailwindcss-animate 1.0.7
    • tailwind-merge 3.3.1
    • class-variance-authority 0.7.1
    • clsx 2.1.1
  • HTTP Client: Axios 1.13.2
  • Charts: Recharts 3.3.0
  • Icons: Lucide React 0.552.0
  • Development Tools:
    • ESLint 9.36.0
    • PostCSS 8.4.47
    • Autoprefixer 10.4.20

AI & Infrastructure

  • AI Model: OpenAI GPT-4o-mini
  • Deployment Target: Railway (backend), Netlify (frontend), S3 (analytics)
  • Database: SQLite (development), ready for PostgreSQL migration

API Routes

All API routes are prefixed with their router prefix. The base URL is http://localhost:8000 in development.

Root

GET /

  • Description: Root endpoint, returns API status
  • Response: {"message": "SpendSense API"}

Ingestion

POST /ingest/

  • Description: Bulk ingest users, accounts, transactions, liabilities, and products
  • Request Body: IngestRequest (users, accounts, transactions, liabilities, products arrays)
  • Response: IngestResponse (status, ingested counts, duration_ms)
  • Tags: ingestion
  • Processing Order: Users → Accounts → Transactions → Liabilities → Products
  • Note: Transactions are processed in batches of 1000 for performance

Features

POST /features/compute/{user_id}

  • Description: Compute all behavioral features for a user
  • Path Parameters:
    • user_id (string): User ID
  • Query Parameters:
    • window_days (int, optional): Time window in days (default: 30, max: 365)
  • Response: Dictionary with user_id, window_days, and features object
  • Tags: features

Profile

GET /profile/{user_id}

  • Description: Get user profile with features and personas
  • Path Parameters:
    • user_id (string): User ID
  • Query Parameters:
    • window (int, optional): Window filter (30 or 180). If not provided, returns both.
  • Response: Dictionary with user info, features (30d and/or 180d), and personas
  • Tags: profile

Users

GET /users/

  • Description: Get list of users with pagination and filtering
  • Query Parameters:
    • limit (int, optional): Number of users to return (default: 25, max: 100)
    • offset (int, optional): Number of users to skip (default: 0)
    • user_type (string, optional): Filter by user type (customer or operator)
    • consent_status (bool, optional): Filter by consent status
  • Response: Dictionary with users list, total count, limit, and offset
  • Tags: users

GET /users/{user_id}

  • Description: Get a single user by ID with personas for both 30d and 180d windows
  • Path Parameters:
    • user_id (string): User ID
  • Response: Dictionary with user data and personas list
  • Tags: users

Operator

GET /operator/dashboard

  • Description: Get operator dashboard metrics and statistics
  • Response: Dictionary with:
    • total_users: Total number of users
    • users_with_consent: Number of users with consent_status=True
    • persona_distribution: Count of users per persona type (30d window)
    • recommendations: Count of recommendations per status
    • metrics: Performance metrics (avg_latency_ms)
  • Tags: operator

GET /operator/users/{user_id}/signals

  • Description: Get detailed signals for a user for operator view
  • Path Parameters:
    • user_id (string): User ID
  • Response: Dictionary with user info, 30d_signals, 180d_signals, and personas
  • Tags: operator

Personas

POST /personas/{user_id}/assign

  • Description: Assign persona to a user based on computed features
  • Path Parameters:
    • user_id (string): User ID
  • Query Parameters:
    • window_days (int, optional): Time window in days (default: 30, max: 365)
  • Response: PersonaResponse with assigned persona details
  • Tags: personas

GET /personas/{user_id}

  • Description: Get persona(s) for a user
  • Path Parameters:
    • user_id (string): User ID
  • Query Parameters:
    • window (int, optional): Window filter (30 or 180). If not provided, returns both.
  • Response: List of PersonaResponse objects
  • Tags: personas

Recommendations

POST /recommendations/generate/{user_id}

  • Description: Generate AI-powered financial recommendations for a user
  • Path Parameters:
    • user_id (string): User ID
  • Query Parameters:
    • window_days (int, optional): Time window in days (default: 30, max: 365)
    • force_regenerate (bool, optional): Force regeneration even if recommendations exist (default: false)
  • Response: Dictionary with user_id, persona, recommendations list, and generation_time_ms
  • Tags: recommendations

GET /recommendations/{user_id}

  • Description: Get recommendations for a user
  • Path Parameters:
    • user_id (string): User ID
  • Query Parameters:
    • status (string, optional): Filter by status (pending_approval, approved, overridden, rejected)
    • window_days (int, optional): Filter by window_days (default: return all)
  • Response: Dictionary with recommendations list and total count
  • Tags: recommendations

POST /recommendations/{recommendation_id}/approve

  • Description: Approve a recommendation
  • Path Parameters:
    • recommendation_id (string): Recommendation ID
  • Request Body: RecommendationApprove (operator_id, notes optional)
  • Response: Updated recommendation object
  • Tags: recommendations

POST /recommendations/{recommendation_id}/override

  • Description: Override a recommendation with new content
  • Path Parameters:
    • recommendation_id (string): Recommendation ID
  • Request Body: RecommendationOverride (operator_id, new_title optional, new_content optional, reason required)
  • Response: Updated recommendation with original_content and override_reason
  • Tags: recommendations

POST /recommendations/{recommendation_id}/reject

  • Description: Reject a recommendation
  • Path Parameters:
    • recommendation_id (string): Recommendation ID
  • Request Body: RecommendationReject (operator_id, reason required)
  • Response: Updated recommendation object
  • Tags: recommendations

POST /recommendations/bulk-approve

  • Description: Bulk approve multiple recommendations
  • Request Body: BulkApproveRequest (operator_id, recommendation_ids array)
  • Response: BulkApproveResponse (approved count, failed count, errors list)
  • Tags: recommendations

Project Structure

SpendSense/
├── backend/                 # FastAPI backend application
│   ├── app/
│   │   ├── __init__.py
│   │   ├── main.py         # FastAPI app initialization
│   │   ├── database.py     # Database connection and initialization
│   │   ├── models.py       # SQLAlchemy models
│   │   ├── schemas.py       # Pydantic schemas
│   │   ├── routers/         # API route handlers
│   │   │   ├── ingest.py
│   │   │   ├── features.py
│   │   │   ├── profile.py
│   │   │   ├── users.py
│   │   │   ├── operator.py
│   │   │   ├── personas.py
│   │   │   └── recommendations.py
│   │   ├── services/        # Business logic services
│   │   │   ├── feature_detection.py
│   │   │   ├── persona_assignment.py
│   │   │   ├── recommendation_engine.py
│   │   │   └── guardrails.py
│   │   ├── prompts/         # Persona-specific prompt templates
│   │   └── utils/            # Utility functions
│   ├── requirements.txt      # Python dependencies
│   ├── venv/                 # Virtual environment (gitignored)
│   └── spendsense.db        # SQLite database (gitignored)
│
├── frontend/                # React frontend application
│   ├── src/
│   │   ├── components/      # React components
│   │   │   ├── ui/          # Shadcn/ui components
│   │   │   ├── Layout.jsx
│   │   │   ├── UserTable.jsx
│   │   │   └── ...
│   │   ├── pages/           # Page components
│   │   │   ├── OperatorDashboard.jsx
│   │   │   ├── OperatorUserList.jsx
│   │   │   ├── OperatorUserDetail.jsx
│   │   │   ├── OperatorApprovalQueue.jsx
│   │   │   └── UserDashboard.jsx
│   │   ├── lib/             # Utilities and API client
│   │   │   ├── api.js
│   │   │   ├── apiService.js
│   │   │   └── utils.js
│   │   ├── constants/       # Constants and enums
│   │   └── main.jsx         # React entry point
│   ├── package.json
│   └── vite.config.js
│
├── scripts/                 # Utility scripts
│   ├── generate_synthetic_data.py
│   ├── generate_product_catalog.py
│   ├── compute_all_features.py
│   ├── assign_all_personas.py
│   ├── test_*.py            # Test scripts for various endpoints
│   └── verify_ingest.py
│
├── data/                    # Synthetic data files
│   ├── synthetic_users.json
│   ├── synthetic_accounts.json
│   ├── synthetic_transactions.json
│   ├── synthetic_liabilities.json
│   └── product_catalog.json  # Generated product catalog
│
├── docs/                    # Documentation
│   ├── DECISIONS.md
│   └── LIMITATIONS.md
│
├── memory-bank/             # Project documentation and context
│   ├── projectbrief.md
│   ├── productContext.md
│   ├── systemPatterns.md
│   ├── techContext.md
│   ├── activeContext.md
│   └── progress.md
│
├── PRD.md                   # Product Requirements Document
├── README.md                # This file
└── architecture.mermaid     # System architecture diagram

Development Workflow

Backend Development

  1. Activate virtual environment:
cd backend
source venv/bin/activate  # or venv\Scripts\activate on Windows
  1. Run server:

Development (with auto-reload):

uvicorn app.main:app --reload

Production/Concurrent requests (with workers):

uvicorn app.main:app --workers 4 --host 0.0.0.0 --port 8000

Note: Using --workers 4 runs 4 separate processes, allowing concurrent request handling even with blocking operations (like recommendation generation). This prevents one long-running request from blocking others.

  1. Access API documentation:

Frontend Development

  1. Start development server:
cd frontend
npm run dev
  1. Build for production:
npm run build
  1. Preview production build:
npm run preview

Data Ingestion Workflow

First-Time Setup (complete workflow):

  1. Start the backend server (creates database tables automatically):
cd backend
source venv/bin/activate  # or venv\Scripts\activate on Windows
uvicorn app.main:app --reload

The database tables are automatically created on server startup via init_db() in backend/app/main.py. This uses SQLAlchemy's Base.metadata.create_all() to create all tables from the model definitions.

  1. Generate synthetic user data (creates JSON files):
# From project root
python backend/scripts/generate_synthetic_data.py

This generates:

  • backend/data/synthetic_users.json (75 users: 71 customers, 4 operators)
  • backend/data/synthetic_accounts.json (2-4 accounts per user)
  • backend/data/synthetic_transactions.json (150-300 transactions per user)
  • backend/data/synthetic_liabilities.json (credit card liabilities)
  1. Ingest user data via API (loads JSON into database):
# Make sure backend server is running first
python backend/scripts/test_ingest.py

This POSTs the JSON data to /ingest/ endpoint, which bulk inserts users, accounts, transactions, and liabilities into the database.

  1. Compute features for users:
python backend/scripts/compute_all_features.py

Computes behavioral features (subscriptions, savings, credit utilization, income patterns) for all users.

  1. Assign personas:
python backend/scripts/assign_all_personas.py

Assigns personas (high_utilization, variable_income, subscription_heavy, savings_builder, wealth_builder) to all users based on their features.

  1. Generate and ingest product catalog (optional, for product recommendations):
# Step 6a: Generate product catalog JSON (requires OPENAI_API_KEY in .env)
python backend/scripts/generate_product_catalog.py

This uses OpenAI GPT-4o to generate 20-25 realistic financial products matched to the 5 personas. The catalog is saved to backend/data/product_catalog.json.

Expected runtime: ~30-60 seconds
Expected API cost: ~$0.10-0.20

# Step 6b: Ingest products via API
# Make sure backend server is running first
python backend/scripts/test_ingest_products.py

This POSTs the product catalog JSON to the /ingest/ endpoint, which bulk inserts products into the database. Products are ingested the same way as all other data (users, accounts, transactions, liabilities) for consistency.


Testing

Backend Testing

Test scripts are available in the backend/scripts/ directory:

  • test_ingest.py - Test data ingestion
  • test_feature_detection.py - Test feature computation
  • test_persona_assignment.py - Test persona assignment
  • test_get_recommendations.py - Test recommendation retrieval
  • test_approve_recommendation.py - Test recommendation approval
  • test_bulk_approve.py - Test bulk approval
  • test_override_reject.py - Test override and reject endpoints

Run any test script:

python backend/scripts/test_<script_name>.py [arguments]

API Testing

Use the Swagger UI at http://localhost:8000/docs for interactive API testing, or use the provided test scripts.


Deployment

Railway Deployment

The platform is deployed on Railway for ease of deployment:

  • Backend: FastAPI application running on Railway
  • Frontend: React application deployed on Netlify
  • S3: Analytics exports bucket (spendsense-analytics-goico)
  • Database: SQLite for MVP (can be upgraded to PostgreSQL)

Prerequisites

  1. Railway account (sign up at railway.app)
  2. Python 3.11+ (matches Railway runtime)
  3. OpenAI API Key for recommendation generation

Deployment Steps

  1. Connect GitHub repository to Railway:

    • Log in to Railway dashboard
    • Create a new project
    • Connect your GitHub repository
    • Railway will auto-detect the FastAPI application
  2. Configure environment variables in Railway dashboard:

    • OPENAI_API_KEY: Your OpenAI API key
    • DATABASE_URL: SQLite database path (default: sqlite:///./spendsense.db)
    • S3_BUCKET_NAME: spendsense-analytics-goico
    • ENVIRONMENT: dev, staging, or prod
  3. Deploy:

    • Railway will automatically build and deploy on git push
    • Or trigger a manual deployment from the dashboard
  4. Get the API URL:

    • Railway provides a public URL automatically
    • Format: https://your-app-name.railway.app

Environment Variables

The following environment variables should be configured in Railway:

  • OPENAI_API_KEY: Your OpenAI API key
  • DATABASE_URL: Database connection string (SQLite or PostgreSQL)
  • S3_BUCKET_NAME: spendsense-analytics-goico
  • ENVIRONMENT: dev, staging, or prod

Database Seeding

On application startup:

  • The database is automatically initialized (tables created)
  • If the database is empty, synthetic data is automatically seeded from JSON files in the data/ directory
  • This ensures data is available immediately after deployment
  • The /ingest/ endpoint remains available for additional data ingestion

Testing the Deployment

  1. Check API health:

    curl https://YOUR_RAILWAY_URL/

    Should return: {"message":"SpendSense API"}

  2. Access Swagger UI: Open https://YOUR_RAILWAY_URL/docs in your browser

  3. Test data seeding: The database should be automatically seeded on first request

Database Migration to PostgreSQL

For production, consider migrating from SQLite to PostgreSQL:

  1. Add PostgreSQL service in Railway dashboard
  2. Update DATABASE_URL environment variable to use the PostgreSQL connection string
  3. Redeploy the application

The SQLAlchemy models are database-agnostic and will work with PostgreSQL without code changes.


Additional Resources

  • API Documentation: Available at /docs when the server is running
  • Product Requirements: See PRD.md
  • Architecture Diagram: See architecture.mermaid
  • Project Context: See memory-bank/ directory for detailed project documentation

Note: This README is maintained as a reference document and is updated periodically as the project evolves. For the most current API documentation, refer to the Swagger UI at /docs when the server is running.

About

SpendSense here we go

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published