# Alemeno Payments [![Python Version](https://img.shields.io/badge/python-3.14-blue?style=flat-square&logo=python)](file:///home/sortedcord/Projects/alemno_payments/pyproject.toml) [![FastAPI](https://img.shields.io/badge/FastAPI-0.139-009688?style=flat-square&logo=fastapi)](file:///home/sortedcord/Projects/alemno_payments/app/app.py) [![Test CI Status](https://github.com/sortedcord/alemno-payments/actions/workflows/tests.yml/badge.svg)](https://github.com/sortedcord/alemno-payments/actions/workflows/tests.yml) [![Lint CI Status](https://github.com/sortedcord/alemno-payments/actions/workflows/lint.yml/badge.svg)](https://github.com/sortedcord/alemno-payments/actions/workflows/lint.yml) [![Docker CI Status](https://github.com/sortedcord/alemno-payments/actions/workflows/docker.yml/badge.svg)](https://github.com/sortedcord/alemno-payments/actions/workflows/docker.yml) [![Security CI Status](https://github.com/sortedcord/alemno-payments/actions/workflows/security.yml/badge.svg)](https://github.com/sortedcord/alemno-payments/actions/workflows/security.yml) 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-lite` 75% 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.0` if 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. [![High Level Overview](./docs/assets/Overview.svg)](https://drive.google.com/file/d/1yk0-pXClrOp0ccTiaO7tbXPHZg4EH1eE/view?usp=sharing) [Technical Video](https://www.youtube.com/watch?v=Xjfyf1ct-0U) _Click on the image to view the [Draw.io](https://drive.google.com/file/d/1yk0-pXClrOp0ccTiaO7tbXPHZg4EH1eE/view?usp=sharing) 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` : ```yaml 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: ```bash 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](file:///home/sortedcord/Projects/alemno_payments/transactions.csv) dataset. ### 1. Upload CSV Transaction Data Submit a CSV file for asynchronous processing. ```bash curl -X POST -F "file=@transactions.csv" http://localhost:3000/api/v1/jobs/upload ``` _Response Example:_ ```json { "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. ```bash curl http://localhost:3000/api/v1/jobs/c0249af1-c52d-4d49-9305-3d1a10f29e03/status ``` _Response Example (while processing):_ ```json { "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. ```bash 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`). ```bash # 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. ```bash 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. ```bash curl -X POST \ -H "Content-Type: application/json" \ -d '{"name": "Subscription"}' \ http://localhost:3000/api/v1/categories ```