A full-stack loan scenario calculator application that allows users to calculate loan payments, save multiple scenarios, and view detailed amortization schedules. Built with FastAPI backend and vanilla HTML/CSS/JavaScript frontend.
LoanFlow provides an intuitive interface for calculating loan payments and managing multiple loan scenarios. Users can:
- Calculate loan payments with custom amounts, interest rates, and terms
- Save multiple loan scenarios with descriptive names
- View saved scenarios with all calculation details
- Analyze amortization schedules showing payment breakdown (principal vs interest)
Key Features:
- ✅ Real-time loan calculations using amortization formula
- ✅ Persistent scenario storage with SQLite database
- ✅ Detailed amortization schedules (up to 12 months preview)
- ✅ RESTful API with comprehensive error handling
- ✅ Responsive web interface
- ✅ Comprehensive test suite (40 tests)
- Framework: FastAPI 0.104.1 (Python async web framework)
- Server: Uvicorn 0.24.0 (ASGI server)
- ORM: SQLAlchemy 2.0.45 (Object-relational mapping)
- Database: SQLite (file-based, no external setup required)
- Validation: Pydantic 2.x (data validation and serialization)
- Environment: Python 3.14
- HTML5: Semantic markup for loan calculator form
- CSS3: Modern gradient design with responsive layout
- JavaScript: Vanilla ES6+ (no build tools required)
- HTTP: Fetch API for backend communication
- Framework: pytest 9.0.2
- HTTP Client: httpx 0.28.1
- Test Coverage: 40 tests (unit + integration)
- Python 3.10+
- Git
- A web browser
git clone https://github.com/samueltuffour/LoanFlow.git
cd LoanFlowcd backend
pip install -r requirements.txtDependencies include:
- fastapi - Web framework
- uvicorn - ASGI server
- sqlalchemy - Database ORM
- pydantic-settings - Configuration management
- pytest - Testing framework
- httpx - HTTP client for testing
Frontend files are static HTML/CSS/JavaScript served directly by Python's http.server.
cd backend
python -m uvicorn app.main:app --reload --host 127.0.0.1 --port 8000Output:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started server process
INFO: Waiting for application startup.
INFO: Application startup complete.
In a new terminal:
cd frontend
python -m http.server 8080 --bind 127.0.0.1Output:
Serving HTTP on 127.0.0.1 port 8080 (http://127.0.0.1:8080/) ...
Open your browser and navigate to:
http://127.0.0.1:8080
cd backend
python -m pytest tests/ -vpython -m pytest tests/test_calculations.py -v # Unit tests
python -m pytest tests/test_api.py -v # API testspython -m pytest tests/ --cov=app --cov-report=htmlcollected 40 items
tests/test_api.py::TestHealthEndpoints::test_root_endpoint PASSED [ 2%]
tests/test_api.py::TestCalculateEndpoint::test_calculate_basic_loan PASSED [ 7%]
tests/test_calculations.py::TestCalculateMonthlyPayment::test_standard_loan_calculation PASSED [ 55%]
...
======================== 40 passed in 0.92s ========================
GET /- Root endpointGET /health- Health check
POST /api/loans/calculate- Calculate loan without savingPOST /api/loans/save- Calculate and save scenarioGET /api/loans/scenarios- List all saved scenariosGET /api/loans/scenarios/{id}- Get scenario detailsGET /api/loans/amortization- Get amortization schedule
LoanFlow uses the standard amortization formula to calculate monthly loan payments:
Where:
- M = Monthly payment
- P = Principal (loan amount)
- r = Monthly interest rate (annual_rate / 100 / 12)
- n = Number of payments (years × 12)
- Monthly Payment: Calculated using the amortization formula above
- Total Amount Paid: Monthly payment × number of months
- Total Interest: Total amount paid - principal
- Amortization Schedule: For each payment period:
- Interest payment = remaining_balance × monthly_rate
- Principal payment = monthly_payment - interest_payment
- Remaining balance = previous_balance - principal_payment
Scenario: $300,000 mortgage at 5% annual interest for 30 years
Monthly Rate (r) = 5 / 100 / 12 = 0.004167
Number of Payments (n) = 30 × 12 = 360
M = 300,000 × [0.004167(1.004167)^360] / [(1.004167)^360 - 1]
M = 300,000 × [0.004167 × 3.8477] / [2.8477]
M = $1,610.06
Total Amount Paid = $1,610.06 × 360 = $579,625
Total Interest = $579,625 - $300,000 = $279,625
The schedule shows how each payment is split between principal and interest:
| Period | Payment | Principal | Interest | Balance |
|---|---|---|---|---|
| 1 | $1,610 | $582 | $1,028 | $299,418 |
| 2 | $1,610 | $585 | $1,025 | $298,833 |
| ... | ... | ... | ... | ... |
| 360 | $1,610 | $1,607 | $3 | $0 |
Observation: Early payments are mostly interest; later payments are mostly principal.
Decision: SQLite (file-based database)
Tradeoffs:
| Aspect | SQLite | PostgreSQL |
|---|---|---|
| Setup | No external setup | Requires server |
| Development Speed | ⭐ Faster | ⭐⭐ Extra setup |
| Scalability | Single-file | Scales to 1000s |
| Concurrency | Limited | Excellent |
Assumption: Application is for single-user/small-team use during development.
Decision: Vanilla HTML/CSS/JavaScript (no build tools)
Rationale:
- Simple calculator interface doesn't require complex state management
- No build tools = instant setup for students/learners
- Fast page loads (no bundle overhead)
- Educational value of understanding HTTP requests
Tradeoff: More manual DOM manipulation vs framework convenience
Decision: Standard compound interest formula with rounding
Assumptions:
- Interest compounds monthly (standard in US mortgages)
- Payments are fixed (not variable rate)
- No extra payments or early payoff calculations
- Rounding to 2 decimal places (standard currency)
Edge Case: Final payment may differ by cents due to rounding accumulation (handled by rounding final balance to zero)
Decision: Simplified error responses
Approach:
422 Unprocessable Entityfor validation errors (missing/invalid fields)404 Not Foundfor nonexistent scenarios500 Internal Server Errorfor database/calculation errors
Assumption: Client handles errors appropriately
Decision: RESTful with stateless operations
Reasoning:
- Each request is independent (stateless)
- No session management required
- Easy to test and debug
- Natural CORS support
LoanFlow/
├── backend/
│ ├── app/
│ │ ├── __init__.py
│ │ ├── main.py # FastAPI app setup
│ │ ├── config.py # Settings/environment
│ │ ├── database/
│ │ │ └── database.py # SQLAlchemy config
│ │ ├── models/
│ │ │ └── loan.py # Loan ORM model
│ │ ├── schemas/
│ │ │ └── loan.py # Pydantic request/response
│ │ ├── routes/
│ │ │ └── loans.py # API endpoints
│ │ └── utils/
│ │ └── calculations.py # Amortization logic
│ ├── tests/
│ │ ├── test_calculations.py # Unit tests
│ │ ├── test_api.py # Integration tests
│ │ └── conftest.py # Pytest config
│ ├── requirements.txt # Python dependencies
│ ├── TESTING.md # Testing guide
│ └── run.py # Alternative entry point
├── frontend/
│ ├── index.html # Main page
│ └── static/
│ ├── css/style.css # Styling
│ └── js/main.js # Client logic
├── README.md # This file
├── TESTING_SUMMARY.md # Test documentation
└── DATABASE_ISSUE_RESOLUTION.md # Debugging guide
- Make changes to backend or frontend files
- Backend auto-reloads (uvicorn --reload flag)
- Frontend auto-refreshes (browser cache busting with Ctrl+Shift+R)
- Run tests to verify changes:
pytest tests/ -v - Commit with descriptive message:
git commit -m "Feature: ..." - Push to GitHub:
git push
Solution: Database tables will auto-create on first startup. Verify backend started without errors.
Solution: Use 127.0.0.1 instead of localhost (Windows networking issue). Both servers run on 127.0.0.1.
Solution: Change port in startup command:
# Backend on different port
python -m uvicorn app.main:app --host 127.0.0.1 --port 8001
# Frontend on different port
python -m http.server 8081 --bind 127.0.0.1- 40 tests total: 20 unit tests + 20 integration tests
- 100% endpoint coverage: All 5 API routes tested
- Calculation validation: All formulas verified
- Error handling: Edge cases covered
See TESTING_SUMMARY.md for detailed test documentation.
- Calculation Speed: < 1ms for most loans
- Amortization Schedule: < 10ms even for 30-year loans
- Database: SQLite sufficient for 1000+ scenarios
- Frontend Load: HTML/CSS/JS < 100KB total
- Support for different compounding periods (annual, quarterly)
- Variable rate loans
- Extra payment calculator
- Loan comparison tool
- Export scenarios to PDF
- User authentication
- PostgreSQL support for production
This project is part of a coding challenge for Summer 2026 internship program.
Samuel Tuffour
For issues or questions, see:
- DATABASE_ISSUE_RESOLUTION.md - Database debugging
- TESTING_SUMMARY.md - Testing details
- backend/TESTING.md - Test examples