A Django REST Framework backend for financial transaction analysis and risk monitoring. LedgerWatch ingests synthetic business transactions, runs pluggable analysis engines, and generates severity-ranked alerts — all scoped to multi-tenant organizations.
Most of the code and text in this project was generated with the help of AI tools.
- Bulk transaction import with full validation and atomic writes
- Pluggable analysis engines — large transactions, burn rate, vendor spikes, duplicates
- Automatic alert generation with severity mapping (LOW / MEDIUM / HIGH)
- Alert lifecycle — OPEN → ACKNOWLEDGED → RESOLVED with audit trail
- Write-once audit log for every significant action
- Interactive API docs via Swagger UI and ReDoc (drf-spectacular)
- Docker + PostgreSQL — single-command local setup
- Jenkins CI/CD — lint, test, build, deploy pipeline
HTTP Request
│
▼
View (HTTP parsing only — no business logic)
│
▼
Service (orchestration, atomic DB transactions)
│
├─► Analyzer (pluggable; returns plain dict)
│ └── AnalyzerFactory (Factory Method pattern)
│
├─► AlertService (dict → Alert objects with severity)
│
├─► Model (ORM clean() + DB CheckConstraints)
│
└─► AuditLog (write-once, immutable)
| Key | What it detects |
|---|---|
large_transaction |
Amounts > 5× mean (HIGH) or > 2× mean / $10k floor (MEDIUM) |
burn_rate |
Monthly cash runway — < 3 months (HIGH), 3–6 months (MEDIUM) |
vendor_spike |
Month-over-month vendor spend — ≥ 50% increase (MEDIUM), 25–50% or new vendor (LOW) |
duplicate |
Identical vendor + amount within a 48-hour window (LOW) |
git clone https://github.com/abhandary14/ledger-watch.git
cd ledger-watch
cp config/.env.example config/.env # edit credentials if needed
docker compose up --buildThe API will be available at http://localhost:8000.
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp config/.env.example config/.env # point DB_HOST=localhost
python manage.py migrate
python manage.py runserver| Interface | URL |
|---|---|
| Swagger UI | http://localhost:8000/api/docs/ |
| ReDoc | http://localhost:8000/api/redoc/ |
| Raw OpenAPI schema | http://localhost:8000/api/schema/ |
All endpoints are under /api/v1/.
| Method | Path | Description |
|---|---|---|
POST |
/transactions/import |
Bulk-import transactions. Body: {organization_id, transactions: [...]} |
GET |
/transactions/ |
List transactions. Filters: vendor, category, date_from, date_to |
GET |
/transactions/{id}/ |
Retrieve single transaction |
Import request body:
{
"organization_id": "uuid",
"transactions": [
{
"date": "2025-11-01",
"vendor": "AWS",
"amount": "4200.00",
"category": "Infrastructure",
"description": "Monthly compute"
}
]
}| Method | Path | Description |
|---|---|---|
POST |
/analysis/run |
Trigger an analysis run |
GET |
/analysis/results |
List runs. Filters: organization_id, analysis_type, status |
GET |
/analysis/results/{id}/ |
Retrieve single run with full results_summary |
Run request body:
{
"organization_id": "uuid",
"analysis_type": "large_transaction"
}| Method | Path | Description |
|---|---|---|
GET |
/alerts/ |
List alerts. Filters: organization_id, alert_type, severity, status |
POST |
/alerts/{id}/acknowledge |
OPEN → ACKNOWLEDGED. Returns 409 if not OPEN |
POST |
/alerts/{id}/resolve |
→ RESOLVED. Returns 409 if already RESOLVED |
python manage.py seed_transactions # ~270 synthetic transactions
python manage.py seed_transactions --count 100 --clear # wipe first, then seed
python manage.py seed_transactions --org "My Org" # seed into named orgSeeded data includes anomalous large transactions, near-duplicate pairs, vendor spikes, and revenue entries — designed to trigger all four analysis engines.
pytest # all tests
pytest tests/test_health.py # single file
pytest tests/test_health.py::TestHealth::test_health_check # single testThe test suite covers models, all four analyzers, both services, and every API endpoint.
ruff check .
ruff format .Copy config/.env.example to config/.env and adjust as needed:
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
(required) | Django secret key |
DEBUG |
True |
Set False in production |
ALLOWED_HOSTS |
localhost,127.0.0.1 |
Comma-separated |
DB_NAME |
ledgerwatch |
PostgreSQL database name |
DB_USER |
ledger |
Database user |
DB_PASSWORD |
ledger |
Database password |
DB_HOST |
postgres |
Hostname (use localhost outside Docker) |
DB_PORT |
5432 |
PostgreSQL port |
The Jenkinsfile defines a pipeline with the following stages:
- Lint —
ruff check . - Test —
pytest --tb=shortagainst an isolated PostgreSQL container - Build Image —
docker build -t ledgerwatch:<build> - Run Migrations —
python manage.py migrate - Deploy —
docker compose up -d
Credentials (SECRET_KEY, DB_PASSWORD) are injected as Jenkins secret text credentials.
ledger_watch/
├── apps/
│ ├── organizations/ # Organization model (multi-tenant scoping)
│ ├── transactions/ # Transaction model, import API, seed command
│ ├── analytics/ # AnalysisRun model, analysis API
│ ├── alerts/ # Alert model, acknowledge/resolve API
│ └── audit/ # AuditLog (write-once)
├── services/
│ ├── analyzers/ # BaseAnalyzer + 4 concrete analyzers
│ ├── analysis_service.py
│ ├── alert_service.py
│ └── transaction_service.py
├── factories/
│ └── analyzer_factory.py # Factory Method pattern
├── ledgerwatch/ # Django project config (settings, urls)
├── tests/ # pytest test suite
├── config/ # .env.example
├── docker/ # Dockerfile
├── docs/ # PRD.md, TDD.md
├── docker-compose.yml
├── Jenkinsfile
└── requirements.txt
MIT — see LICENSE.