Skip to content

Repository files navigation

OpenDA — AI Disbursement Account Analyzer

Open-source Human-in-the-Loop AI platform for maritime port agency cost validation.
Accountants upload a Proforma DA (JSON) + Final DA (PDF). An LLM extracts every line item, flags deviations, and generates a full audit trail — all without lock-in to a specific AI provider.


Maritime DA Workflow — Where OpenDA Fits

flowchart TD
subgraph PORT ["Port Operations"]
V[Vessel Arrives at Port]
V --> SVC["Port Agent Provides Services\npilotage · towage · berth · provisions · crew"]
SVC --> FDA_DOC["Port Agent Issues FDA\nFinal Disbursement Account"]
end
subgraph OWNER ["Ship Owner / Operator"]
FDA_DOC --> RECV["Receives FDA + PDA Proforma Estimate"]
RECV --> TRAD["Traditional Approach\n── manual line-by-line comparison ──\n⚠ error-prone · days to complete\n⚠ no audit trail · no provider lock-in"]
end
subgraph OPENDA ["OpenDA — AI-Assisted DA Validation"]
direction TB
UP["① Accountant Uploads\nPDA JSON + FDA PDF"]
EXT["② AI Extraction Docling + LLM\nPDF parsed into structured line items + bounding boxes"]
DEV["③ Deviation Engine\nPDA vs FDA comparison · flags HIGH_DEVIATION,\nLOW_CONFIDENCE, MISSING lines"]
ACCT["④ Accountant Review UI\nAI-highlighted deviations overlaid on PDF\nAccept, annotate or reject each item"]
OPS["⑤ Operator Approval UI\nFinal sign-off with remarks"]
AUD["⑥ Immutable Audit Trail\nEvery decision logged with actor + timestamp"]
UP --> EXT --> DEV --> ACCT --> OPS --> AUD
end
RECV -- "replaces" --> UP
TRAD -. "eliminated by OpenDA" .-> UP
AUD --> ERP["ERP / VMS System\nWebhook push on approval"]
ERP --> FIN(["DA Settled & Archived ✓"])
Loading

Architecture

flowchart TD
A[Accountant Browser\nport :3000] -->|upload PDA JSON + FDA PDF| B[FastAPI :8000\nPOST /api/v1/da/upload]
B -->|enqueue| C[Redis :6379]
C -->|async task| D[Celery Worker]
D -->|parse PDF| E[Docling]
E -->|structured text + BBoxes| D
D -->|extract JSON| F[LiteLLM Gateway]
F -->|route to active provider| G{LLM Provider}
G -->|Anthropic Claude| G
G -->|OpenAI GPT-4o| G
G -->|Google Gemini| G
G -->|Ollama local| G
F -->|FDASchema| D
D -->|compare PDA vs FDA| H[Deviation Engine]
H -->|DeviationReport| I[(PostgreSQL :5432)]
I -->|PENDING_ACCOUNTANT_REVIEW| A
A -->|review + annotate| J[PUT /submit-to-operator]
J -->|PENDING_OPERATOR_APPROVAL| K[Operator Browser\nport :3001]
K -->|approve| L[POST /approve]
L -->|webhook| M[ERP / VMS]
L -->|PUSHED_TO_ERP| I
Loading

Quick Start

Prerequisites

ToolVersion
Docker + Docker Compose24+
Python3.12+
UVlatest
Node.js20+
pnpm9+

1. Clone & Configure

git clone https://github.com/your-org/openda.git
cd openda
cp .env.example .env

Edit .env and set your LLM provider:

# Choose ONE provider block:# Anthropic Claude (recommended)
LLM_MODEL=anthropic/claude-sonnet-4-6-20250514
LLM_API_KEY=sk-ant-...
# Google Gemini# LLM_MODEL=gemini/gemini-2.0-flash# LLM_API_KEY=AIza...# OpenAI# LLM_MODEL=openai/gpt-4o# LLM_API_KEY=sk-proj-...# Ollama (no API key needed)# LLM_MODEL=ollama/llama3.3# LLM_API_KEY=ollama

2. Docker (full stack)

docker compose up --build
ServiceURL
Accountant UIhttp://localhost:3000
Operator UIhttp://localhost:3001
APIhttp://localhost:8000
API docshttp://localhost:8000/docs

3. Local Development

Backend (Python)

cd backend
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
uv pip install -e ".[dev]"# start postgres + redis
docker compose up -d postgres redis
# migrate
alembic upgrade head
# run API
uvicorn main:app --reload --port 8000
# run Celery worker (separate terminal)
celery -A app.workers.celery_app worker --loglevel=info

Frontend

# Accountant UI (port 5173, proxies /api → :8000)cd frontend-accountant && pnpm install && pnpm dev
# Operator UI (port 5174)cd frontend-operator && pnpm install && pnpm dev

LLM Provider Reference

OpenDA uses LiteLLM as a universal gateway. Switch providers by changing two env vars — zero code changes required.

ProviderLLM_MODELLLM_API_KEY
Anthropic Claudeanthropic/claude-sonnet-4-6-20250514Anthropic API key
Google Geminigemini/gemini-2.0-flashGoogle AI Studio key
OpenAIopenai/gpt-4oOpenAI API key
Azure OpenAIazure/gpt-4oAzure API key
Ollama (local)ollama/llama3.3ollama

For Ollama, also set OLLAMA_API_BASE=http://localhost:11434.


API Reference

MethodPathDescription
POST/api/v1/da/uploadUpload PDA JSON + FDA PDF, starts AI processing
GET/api/v1/da/{id}/statusPoll DA lifecycle status
GET/api/v1/da/{id}/deviation-reportFull deviation report with per-item flags
GET/api/v1/da/{id}/audit-logImmutable audit trail
PUT/api/v1/da/{id}/submit-to-operatorAccountant submits reviewed items
POST/api/v1/da/{id}/approveOperator approves → fires ERP webhook
POST/api/v1/da/{id}/rejectReject at any review stage
GET/api/v1/healthHealth check (DB + Redis + LLM config)

Full interactive docs: http://localhost:8000/docs


Deviation Flags

FlagCondition
HIGH_DEVIATIONAbsolute variance > $500 or percentage variance > 10%
LOW_CONFIDENCEAI extraction confidence < 0.85
MISSING_PDA_LINEFDA item has no matching PDA category
MISSING_FROM_FDAPDA item absent from FDA

ERP Webhook

When an operator approves a DA, OpenDA fires a POST to WEBHOOK_URL with:

{
"da_id": "uuid",
"port_call_id": "PC-2024-SGSIN-0001",
"status": "APPROVED",
"total_estimated": 12500.00,
"total_actual": 13050.00,
"operator_remarks": "Anchorage fees validated against port authority receipt",
"llm_provider": "anthropic/claude-sonnet-4-6-20250514",
"approved_at": "2025-01-15T09:32:00Z"
}

Set WEBHOOK_URL=http://your-erp/api/inbound/disbursement in .env.
For local testing: WEBHOOK_URL=http://localhost:8000/api/v1/da/webhook-echo


DA Lifecycle

stateDiagram-v2
direction LR
[*] --> UPLOADING : accountant uploads PDA JSON + FDA PDF
UPLOADING --> AI_PROCESSING : file stored, Celery task enqueued
AI_PROCESSING --> AI_PROCESSING : retry on transient LLM failure
AI_PROCESSING --> PENDING_ACCOUNTANT_REVIEW : deviation report generated
AI_PROCESSING --> REJECTED : unrecoverable extraction error
PENDING_ACCOUNTANT_REVIEW --> PENDING_OPERATOR_APPROVAL : accountant reviews & submits
PENDING_ACCOUNTANT_REVIEW --> REJECTED : accountant rejects DA
PENDING_OPERATOR_APPROVAL --> APPROVED : operator approves
PENDING_OPERATOR_APPROVAL --> REJECTED : operator rejects
APPROVED --> PUSHED_TO_ERP : ERP webhook fired
PUSHED_TO_ERP --> [*]
REJECTED --> [*]
Loading

Testing

cd backend
# Generate synthetic test fixtures (5 PDA JSONs + 5 FDA PDFs)
python ../test_data/generate_fixtures.py
# Run schema smoke tests
python ../test_data/smoke_test_schemas.py
# Run pytest suite
pytest tests/ -v

Project Structure

openda/
├── backend/ # FastAPI + Celery + SQLAlchemy
│ ├── app/
│ │ ├── api/routes/ # da.py, health.py
│ │ ├── models/ # SQLAlchemy ORM models
│ │ ├── schemas/ # Pydantic v2 schemas (PDA, FDA, deviation)
│ │ ├── services/ # llm_provider, extraction_service, deviation_engine, state_machine
│ │ └── workers/ # Celery app + tasks
│ ├── alembic/ # DB migrations
│ └── main.py
├── frontend-accountant/ # React + Vite (split-screen PDF review)
├── frontend-operator/ # React + Vite (approval dashboard)
├── test_data/ # Synthetic PDA/FDA fixtures
├── docker-compose.yml
└── .env.example

Contributing

  1. Fork → feature branch → PR against main
  2. Run ruff check . and mypy before pushing
  3. Add tests for any new business logic in backend/tests/

License

MIT — see LICENSE

About

Open-source Human-in-the-Loop AI platform for maritime port agency cost validation.

Topics

Resources

Stars

6 stars

Watchers

0 watching

Forks

Contributors

Languages