Alemeno Payments
An asynchronous transactional data cleaning, validation, and LLM powered categorization pipeline.
Features
- Reads multi format dates and handles missing transaction IDs by auto generating unique fallbacks, and parses messy amounts with currency signs and commas cleanly.
- Offloads validation, spend conversion, and analysis to a Celery background worker backed by Redis.
- Integrates with the Google GenAI SDK (
gemini-2.5-flash-lite75% cheaper than `gemini-3.5-flash-lite with no tangible decrease in performance) using structured output schemas to categorize transaction records into database- defined custom categories. - Uses Redis caching with a 24-hour TTL to store live exchange rates, falling back to a hardcoded rate of
93.0if offline. - Statistical Anomaly Detection:
- Flags transactions exceeding 3 times the account's median spend as statistical outliers.
- Flags domestic transactions in USD currency.
- Flags high value transactions and transactions with fraud indicative keywords.
Click on the image to view the Draw.io file
Self Hosting & Deployment
We publish production-ready images to GitHub Container Registry (GHCR). You can pull them directly or run them via Docker Compose.
Create a docker-compose.yml file to host the entire stack (or just clone the repo and directly run docker compose up -d :
version: "3.8"
services:
db:
image: postgres:16-alpine
container_name: alemno_db
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: alemno_payments
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d alemno_payments"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: alemno_redis
ports:
- "6379:6379"
web:
image: ghcr.io/sortedcord/alemno-payments/web:latest
container_name: alemno_web
ports:
- "3000:3000"
environment:
- POSTGRES_HOST=db
- POSTGRES_PORT=5432
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=alemno_payments
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_DB=0
- GEMINI_API_KEY=your-gemini-api-key
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
worker:
image: ghcr.io/sortedcord/alemno-payments/worker:latest
container_name: alemno_worker
environment:
- POSTGRES_HOST=db
- POSTGRES_PORT=5432
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=alemno_payments
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_DB=0
- GEMINI_API_KEY=your-gemini-api-key
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
postgres_data:
Launch the stack:
docker compose up -d
Testing the API with curl
All API endpoints are prefixed with /api/v1. Below are copy-pasteable curl commands using the sample transactions.csv dataset.
1. Upload CSV Transaction Data
Submit a CSV file for asynchronous processing.
curl -X POST -F "file=@transactions.csv" http://localhost:3000/api/v1/jobs/upload
Response Example:
{
"filename": "transactions.csv",
"id": "c0249af1-c52d-4d49-9305-3d1a10f29e03",
"status": "pending",
"row_count_raw": null,
"row_count_clean": null,
"created_at": "2026-07-03T06:29:13.123456Z",
"completed_at": null,
"error_message": null
}
2. Check Job Status
Check the status of a job using the job ID returned by the upload endpoint.
curl http://localhost:3000/api/v1/jobs/c0249af1-c52d-4d49-9305-3d1a10f29e03/status
Response Example (while processing):
{
"id": "c0249af1-c52d-4d49-9305-3d1a10f29e03",
"status": "processing",
"summary": null
}
3. Get Full Job Results
Retrieve the high-level summary along with all categorized and parsed transactions once the job is completed.
curl http://localhost:3000/api/v1/jobs/c0249af1-c52d-4d49-9305-3d1a10f29e03/results
4. List All Jobs
List all historical processing jobs. You can optionally filter by status (e.g. pending, processing, completed, failed).
# List all jobs
curl http://localhost:3000/api/v1/jobs
# List completed jobs only
curl "http://localhost:3000/api/v1/jobs?status=completed"
5. List Categories
List all available transaction categories sorted alphabetically.
curl http://localhost:3000/api/v1/categories
6. Create Custom Category
Add a new custom category. Newly uploaded transactions will dynamically map to this category during classification if identified by the LLM.
curl -X POST \
-H "Content-Type: application/json" \
-d '{"name": "Subscription"}' \
http://localhost:3000/api/v1/categories