Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 62 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,62 @@
# Candidate Assessment: Spec-Driven Development With Codegen Tools

This assessment evaluates how you use modern code generation tools (for example `5.2-Codex`, `Claude`, `Copilot`, and similar) to design, build, and test a software application using a spec-driven development pattern. You may build a frontend, a backend, or both.

## Goals
- Build a working application with at least one meaningful feature.
- Create a testing framework to validate the application.
- Demonstrate effective use of code generation tools to accelerate delivery.
- Show clear, maintainable engineering practices.

## Deliverables
- Application source code in this repository.
- A test suite and test harness that can be run locally.
- Documentation that explains how to run the app and the tests.

## Scope Options
Pick one:
- Frontend-only application.
- Backend-only application.
- Full-stack application.

Your solution should include at least one real workflow, for example:
- Create and view a resource.
- Search or filter data.
- Persist data in memory or storage.

## Rules
- You must use a code generation tool (for example `5.2-Codex`, `Claude`, or similar). You can use multiple tools.
- You must build the application and a testing framework for it.
- The application and tests must run locally.
- Do not include secrets or credentials in this repository.

## Evaluation Criteria
- Working product: Does the app do what it claims?
- Test coverage: Do tests cover key workflows and edge cases?
- Engineering quality: Clarity, structure, and maintainability.
- Use of codegen: How effectively you used tools to accelerate work.
- Documentation: Clear setup and run instructions.

## What to Submit
- When you are complete, put up a Pull Request against this repository with your changes.
- A short summary of your approach and tools used in your PR submission
- Any additional information or approach that helped you.
# Banking Transaction System

A full-stack banking application built for the spec-driven-development assessment.

## Features
- Create bank accounts with initial balance
- Deposit and withdraw funds
- Transfer between accounts
- View transaction history
- Input validation and error handling

## Tech Stack
- **Backend:** FastAPI (Python)
- **Database:** SQLite
- **Frontend:** HTML/CSS/JavaScript
- **Testing:** pytest, Playwright

## Setup

```bash
# Clone and navigate
git clone https://github.com/Farhod75/spec-driven-development.git
cd spec-driven-development

# Create virtual environment
python -m venv venv
.\venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux

# Install dependencies
pip install -r requirements.txt
playwright install chromium

# Run the Application
uvicorn app.main:app --reload --port 8000
Open http://localhost:8000

# Run Tests
# API tests
pytest tests/test_api.py -v

# UI tests (server must be running)
pytest tests/test_ui.py -v

# All tests
pytest -v

API Endpoints
Method Endpoint Description
POST /accounts Create account
GET /accounts/{id} Get account details
POST /accounts/{id}/deposit Deposit funds
POST /accounts/{id}/withdraw Withdraw funds
POST /transfers Transfer between accounts
GET /accounts/{id}/transactions Get transaction history

# AI Tools Used

Claude (Anthropic) - Code generation and debugging

# Author
Farhod Elbekov
27 changes: 27 additions & 0 deletions SPECS/banking-transactions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Feature Spec: Banking Transactions

## Goal
Build a simple banking system that allows users to create accounts, manage balances, and perform transactions.

## Scope
- In: Account creation, deposits, withdrawals, transfers, transaction history, basic UI
- Out: Authentication, multi-currency, external integrations

## Requirements
- Users can create bank accounts with initial balance
- Users can deposit money into accounts
- Users can withdraw money (with insufficient funds validation)
- Users can transfer between accounts
- Users can view transaction history
- API returns appropriate status codes and error messages

## Acceptance Criteria
- [ ] POST /accounts creates a new account and returns account details
- [ ] GET /accounts/{id} returns account with current balance
- [ ] POST /accounts/{id}/deposit increases balance
- [ ] POST /accounts/{id}/withdraw decreases balance (fails if insufficient funds)
- [ ] POST /transfers moves money between accounts
- [ ] GET /accounts/{id}/transactions returns transaction history
- [ ] UI allows all operations above
- [ ] All API endpoints have passing tests
- [ ] UI has Playwright E2E tests
Binary file added app/__pycache__/database.cpython-313.pyc
Binary file not shown.
Binary file added app/__pycache__/main.cpython-313.pyc
Binary file not shown.
Binary file added app/__pycache__/models.cpython-313.pyc
Binary file not shown.
32 changes: 32 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sqlite3
from contextlib import contextmanager

DATABASE = "banking.db"

def init_db():
with get_db() as conn:
conn.executescript("""
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
balance REAL DEFAULT 0.0
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL,
type TEXT NOT NULL,
amount REAL NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts(id)
);
""")

@contextmanager
def get_db():
conn = sqlite3.connect(DATABASE)
conn.row_factory = sqlite3.Row
try:
yield conn
conn.commit()
finally:
conn.close()
108 changes: 108 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from app.models import AccountCreate, Account, DepositWithdraw, Transfer, Transaction
from app.database import init_db, get_db

app = FastAPI(title="Banking API")

@app.on_event("startup")
def startup():
init_db()

@app.post("/accounts", response_model=Account, status_code=201)
def create_account(data: AccountCreate):
with get_db() as conn:
cursor = conn.execute(
"INSERT INTO accounts (name, balance) VALUES (?, ?)",
(data.name, data.initial_balance)
)
account_id = cursor.lastrowid
return {"id": account_id, "name": data.name, "balance": data.initial_balance}

@app.get("/accounts/{account_id}", response_model=Account)
def get_account(account_id: int):
with get_db() as conn:
row = conn.execute("SELECT * FROM accounts WHERE id = ?", (account_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Account not found")
return dict(row)

@app.post("/accounts/{account_id}/deposit", response_model=Account)
def deposit(account_id: int, data: DepositWithdraw):
if data.amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be positive")
with get_db() as conn:
row = conn.execute("SELECT * FROM accounts WHERE id = ?", (account_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Account not found")
new_balance = row["balance"] + data.amount
conn.execute("UPDATE accounts SET balance = ? WHERE id = ?", (new_balance, account_id))
conn.execute(
"INSERT INTO transactions (account_id, type, amount) VALUES (?, ?, ?)",
(account_id, "deposit", data.amount)
)
return {"id": account_id, "name": row["name"], "balance": new_balance}

@app.post("/accounts/{account_id}/withdraw", response_model=Account)
def withdraw(account_id: int, data: DepositWithdraw):
if data.amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be positive")
with get_db() as conn:
row = conn.execute("SELECT * FROM accounts WHERE id = ?", (account_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Account not found")
if row["balance"] < data.amount:
raise HTTPException(status_code=400, detail="Insufficient funds")
new_balance = row["balance"] - data.amount
conn.execute("UPDATE accounts SET balance = ? WHERE id = ?", (new_balance, account_id))
conn.execute(
"INSERT INTO transactions (account_id, type, amount) VALUES (?, ?, ?)",
(account_id, "withdraw", data.amount)
)
return {"id": account_id, "name": row["name"], "balance": new_balance}

@app.post("/transfers", response_model=dict)
def transfer(data: Transfer):
if data.amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be positive")
with get_db() as conn:
from_acc = conn.execute("SELECT * FROM accounts WHERE id = ?", (data.from_account_id,)).fetchone()
to_acc = conn.execute("SELECT * FROM accounts WHERE id = ?", (data.to_account_id,)).fetchone()
if not from_acc:
raise HTTPException(status_code=404, detail="Source account not found")
if not to_acc:
raise HTTPException(status_code=404, detail="Destination account not found")
if from_acc["balance"] < data.amount:
raise HTTPException(status_code=400, detail="Insufficient funds")

conn.execute("UPDATE accounts SET balance = balance - ? WHERE id = ?", (data.amount, data.from_account_id))
conn.execute("UPDATE accounts SET balance = balance + ? WHERE id = ?", (data.amount, data.to_account_id))
conn.execute(
"INSERT INTO transactions (account_id, type, amount) VALUES (?, ?, ?)",
(data.from_account_id, "transfer_out", data.amount)
)
conn.execute(
"INSERT INTO transactions (account_id, type, amount) VALUES (?, ?, ?)",
(data.to_account_id, "transfer_in", data.amount)
)
return {"message": "Transfer successful"}

@app.get("/accounts/{account_id}/transactions", response_model=list[Transaction])
def get_transactions(account_id: int):
with get_db() as conn:
row = conn.execute("SELECT * FROM accounts WHERE id = ?", (account_id,)).fetchone()
if not row:
raise HTTPException(status_code=404, detail="Account not found")
rows = conn.execute(
"SELECT * FROM transactions WHERE account_id = ? ORDER BY timestamp DESC",
(account_id,)
).fetchall()
return [dict(r) for r in rows]

# Serve frontend
app.mount("/static", StaticFiles(directory="frontend"), name="static")

@app.get("/")
def serve_frontend():
return FileResponse("frontend/index.html")
27 changes: 27 additions & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime

class AccountCreate(BaseModel):
name: str
initial_balance: float = 0.0

class Account(BaseModel):
id: int
name: str
balance: float

class DepositWithdraw(BaseModel):
amount: float

class Transfer(BaseModel):
from_account_id: int
to_account_id: int
amount: float

class Transaction(BaseModel):
id: int
account_id: int
type: str
amount: float
timestamp: str
Binary file added banking.db
Binary file not shown.
Loading