An AI-powered contract analysis service built with FastAPI, layered architecture, Celery, Redis and PostgreSQL.
|
|
|
|
Contract Clause Reviewer is a portfolio project for exploring how an AI-powered backend can be structured as a production-oriented service rather than as a single FastAPI application.
The application accepts contract text, sends the document to an LLM for structured analysis, stores the resulting clauses and risk information, and can generate a downloadable PDF report.
The project focuses primarily on the engineering behind the product:
- Layered architecture with explicit separation of responsibilities
- Dependency injection through FastAPI dependencies
- Asynchronous HTTP, database and Redis I/O at the API boundary
- Celery-based background processing for LLM analysis and PDF generation
- PostgreSQL persistence with SQLModel and Alembic
- Local JWT authentication with access/refresh token workflow
- Refresh-token rotation and server-side token persistence
- Role-based access control for administrative endpoints
- Redis-backed global and endpoint-specific rate limiting
- Queue-based application logging with dedicated audit logging
- Dockerized API and worker deployment
- Unit and integration test separation
This is primarily an architecture and backend engineering portfolio project. It is not intended to replace professional legal review.
Users can submit contract text for asynchronous analysis.
The analysis service:
- identifies key contract clauses
- classifies clause types
- assigns clause-level risk levels
- calculates an overall document risk score
- produces a document summary
- highlights key terms
- provides suggested actions and recommendations
LLM output is validated against a Pydantic schema before it is persisted.
The current analyzer also applies a maximum document length and truncates long documents at a paragraph boundary where possible.
LLM analysis and PDF generation are deliberately kept out of the request/response path.
The API queues Celery tasks and immediately returns a task identifier. Workers perform the expensive operations and persist the results.
Celery is configured with:
- dedicated analysis and cleanup queues
- task retry policies
- exponential retry backoff with jitter
- late acknowledgements
- task time limits
- worker concurrency control
- periodic cleanup tasks
Authentication uses a local JWT workflow.
Access tokens are short-lived JWTs containing a subject, token type, expiry and JTI. Refresh tokens are opaque random values and only their SHA-256 hashes are persisted in the database.
The authentication layer supports:
- access-token validation
- refresh-token validation
- refresh-token rotation
- token revocation
- session metadata such as IP address and user-agent
- active-user checks
- admin-only dependencies
See docs/authentication.md for the complete workflow.
Redis is used for rate limiting at multiple levels.
The project includes:
- global per-IP protection
- login-specific limits
- public endpoint limits
- authenticated-user limits
- stricter analysis limits
- admin endpoint limits
- reusable custom rate-limit dependencies
Rate-limit information is exposed through standard response headers such as X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After.
The application uses a queue-based logging pipeline so application code does not synchronously perform file I/O for every log record.
The logging infrastructure uses:
QueueHandlerQueueListener- rotating application logs
- separate error and warning logs
- access logging
- a dedicated audit log
- custom
AuditLoggerhelpers - graceful queue flushing during shutdown
User and administrator actions are represented as structured key/value log messages so operational events are easier to search and inspect.
Completed analyses can be converted into PDF reports.
The report pipeline is:
Analysis in PostgreSQL
|
v
Celery report task
|
v
HTML report generation
|
v
WeasyPrint
|
v
PDF file in reports storage
|
v
Analysis record updated with report metadata
An example report is included at docs/example_report.pdf.
Example generated report showing document summary, overall risk and clause-level findings.
The application follows a layered architecture with infrastructure concerns kept separate from business-oriented services.
flowchart TB
Client[Client / Appsmith / API Consumer]
subgraph API["API Layer"]
Routes[FastAPI Routers]
Deps[Authentication & Rate-Limit Dependencies]
end
subgraph Services["Service Layer"]
Auth[Auth Services]
Analysis[Analysis Services]
Users[User Services]
Reports[Report Services]
Analyzer[LLM Analyzer]
end
subgraph Data["Data Access"]
Repos[Repository Layer]
Models[SQLModel Models]
DB[(PostgreSQL)]
end
subgraph Infra["Infrastructure"]
Redis[(Redis)]
Celery[Celery Workers]
OpenAI[OpenAI-compatible LLM]
Files[Report & Log Storage]
Logging[Queue-based Logging]
end
Client --> Routes
Routes --> Deps
Routes --> Auth
Routes --> Analysis
Routes --> Users
Routes --> Reports
Auth --> Repos
Analysis --> Repos
Users --> Repos
Reports --> Repos
Repos --> Models
Models --> DB
Deps --> Redis
Analysis --> Celery
Celery --> Analyzer
Analyzer --> OpenAI
Celery --> DB
Celery --> Files
Routes --> Logging
Services --> Logging
Celery --> Logging
The main application boundaries are:
API
├── routing
├── request/response concerns
├── authentication dependencies
└── rate-limit dependencies
Services
├── business workflows
├── authentication logic
├── analysis orchestration
├── user operations
└── report orchestration
Repositories
└── database access capabilities
Models / Schemas
├── persistence models
└── API / validation models
Core
├── configuration
├── security primitives
├── Celery configuration
├── enums / exceptions
└── reusable application utilities
Infrastructure
├── Redis
├── OpenAI client integration
├── file storage
└── logging
Tasks
├── document analysis
├── report generation
└── cleanup jobs
The architecture is intentionally layered rather than trying to implement a fully abstract Clean Architecture or hexagonal architecture. The goal is to get most of the practical benefits of separation and testability without excessive indirection.
More detail is available in docs/architecture.md.
The backend is built with:
- FastAPI: API framework and dependency injection
- SQLModel: database models and SQLAlchemy integration
- PostgreSQL: primary relational database
- Alembic: database migrations
- Celery: distributed background task processing
- Redis: Celery broker/result backend and rate-limit storage
- Pydantic: request, response and LLM output validation
- Granian: ASGI application server
- WeasyPrint: HTML-to-PDF report generation
- OpenAI-compatible API: LLM integration
The Python project requires Python 3.14+ and uses uv for dependency management.
The HTTP application uses asynchronous database and Redis clients and asynchronous OpenAI calls.
The important design distinction is that the worker boundary is intentionally synchronous:
HTTP request
|
| async
v
FastAPI
|
| queue task
v
Celery
|
| worker process
v
CPU / blocking / long-running work
|
+--> LLM analysis
+--> database persistence
+--> PDF generation
This means the API remains responsive while long-running analysis and report-generation work is handled by worker processes.
Celery tasks are synchronous functions by design. The document analysis task bridges to the asynchronous LLM service inside the worker process, while database persistence and PDF generation are performed synchronously within the task.
This is more accurate than describing the entire system as "fully async": the web layer is async-first, while background workers intentionally isolate blocking work.
PostgreSQL stores:
- users
- refresh-token records
- analyses
- analyzed clauses
- report metadata
SQLModel models live in app/models/, while database access is organized through repository functions in app/repositories/.
The application has both:
- an async database URL for the API
- a sync database URL for Celery workers
Alembic is used for schema migrations.
sequenceDiagram
participant C as Client
participant API as FastAPI
participant DB as PostgreSQL
C->>API: POST /auth/login/oauth
API->>DB: Verify user
API->>DB: Store hashed refresh token
API-->>C: Access JWT + Refresh Token
C->>API: Protected request + Access JWT
API->>DB: Resolve user / authorization
API-->>C: Protected response
C->>API: POST /auth/refresh + Refresh Token
API->>DB: Validate stored token hash
API->>DB: Revoke old token
API->>DB: Persist new refresh token
API-->>C: New Access JWT + Refresh Token
Access tokens are short-lived JWTs. Refresh tokens are opaque values whose hashes are stored server-side.
This avoids putting refresh-token secrets directly into the database and makes server-side revocation possible.
The project includes several security-oriented controls:
- password hashing with Argon2 through
pwdlib - short-lived JWT access tokens
- opaque refresh tokens
- refresh-token rotation
- refresh-token revocation
- server-side refresh-token persistence
- JTI values on access tokens
- active-user checks
- admin-only dependencies
- Redis-backed rate limiting
- audit logging for sensitive actions
- environment-based secrets and database configuration
This portfolio project should still be treated as a learning system rather than as a security-certified legal product. Production deployment would require additional controls such as secrets management, HTTPS termination, hardened cookie/CORS policy where applicable, security headers, monitoring and a formal threat model.
Docker is used to run the production-shaped application as separate services.
The Compose setup contains:
PostgreSQL
|
+------------------+
| |
v v
FastAPI API Celery worker
| |
+--------+---------+
|
Redis
The Dockerfile provides separate build targets for:
apicelery-worker
The runtime image includes:
- Python 3.14
- a non-root
appuser - cached
uvdependency installation - WeasyPrint system dependencies
- application health checks for the API
Docker Compose also provides persistent volumes for PostgreSQL, Redis, reports and logs.
An Appsmith dashboard is provided in appsmith/ directory.
The dashboard is intended to demonstrate that the backend can serve more than one presentation layer:
+-------------------+
| FastAPI API |
| /api/v1 |
+-------------------+
/ \
/ \
v v
Application UI Appsmith
Dashboard
As a result the dashboard is deliberately treated as a client of the API rather than as part of the backend architecture.
To use and test the dashboard you should:
- importing the Appsmith JSON export
- configuring the REST API datasource
- setting the API base URL
Make sure you have a running instance of app using [#How To Run] section. Then find the API base URL/IP needed for Appsmith datasource using:
$ docker inspect contract_clause_reviewer_api | grep IPAddressYou will get something like:
"IPAddress": "172.22.0.4"
Use this IP as the base URL for datasource.
Make sure the following tools are installed:
- Python 3.14+
- uv
- Docker
- Docker Compose
An OpenAI-compatible API endpoint is also required for actual document analysis.
Clone the repository:
git clone https://github.com/enoorii/contract-clause-reviewer.git
cd contract-clause-reviewerInstall Python dependencies:
uv syncCreate the environment file:
cp .env.example .envSet the required LLM configuration and review the database, Redis, authentication and application settings in .env.
Start the development services:
docker compose up -d --buildThis will run alembic migrations using the existing migration file in the repo.
The API is exposed on:
http://localhost:9000
OpenAPI documentation is available at:
http://localhost:9000/docs
To do this you should run database using docker-compose.test.yaml and then run alembic.
docker compose -f docker-compose.test.yaml --env-file .env.test up -d
ENV_FILE=.env.test uv run alembic revision --autogenerate -m "message"
For development without the full Compose worker:
ENV_FILE=.env.test uv run celery \
-A app.core.celery:celery_app \
worker \
--loglevel=info \
-Q analysisRun the API separately:
ENV_FILE=.env.test uv run granian \
--interface asgi \
app.main:app \
--host 0.0.0.0 \
--port 8000For hot reload during development:
ENV_FILE=.env.test uv run granian \
--reload \
--interface asgi \
app.main:app \
--host 0.0.0.0 \
--port 8000The test suite is separated into unit and integration tests:
tests/
├── conftest.py
├── unit/
│ ├── core/
│ └── services/
└── integration/
├── api/
└── middleware/
The test environment is designed to minimize external dependencies:
py-pgliteis used for PostgreSQL-compatible database testingfakeredisis used for Redis-dependent tests- pytest-asyncio handles async test execution
Run the full suite:
ENV_FILE=.env.test uv run pytestRun a specific test module:
ENV_FILE=.env.test uv run pytest tests/integration/api/test_auth.py -vRun tests matching a name:
ENV_FILE=.env.test uv run pytest -k "login"See docs/testing.md for the testing strategy and fixture design.
contract-clause-reviewer/
├── alembic/
│ └── versions/
├── app/
│ ├── api/
│ │ ├── analysis.py
│ │ ├── auth.py
│ │ ├── deps.py
│ │ ├── reports.py
│ │ └── users.py
│ ├── core/
│ │ ├── filters/
│ │ ├── celery.py
│ │ ├── config.py
│ │ ├── enums.py
│ │ ├── exceptions.py
│ │ ├── security.py
│ │ └── utilities.py
│ ├── db/
│ │ ├── database.py
│ │ └── seed.py
│ ├── infrastructure/
│ │ ├── openai/
│ │ ├── redis/
│ │ ├── logging.py
│ │ └── storage.py
│ ├── middleware/
│ │ ├── rate_limit.py
│ │ └── request_logging.py
│ ├── models/
│ │ └── models.py
│ ├── repositories/
│ │ ├── analysis_repositories.py
│ │ ├── refresh_token_repositories.py
│ │ └── user_repositories.py
│ ├── schemas/
│ │ ├── analysis.py
│ │ ├── base.py
│ │ ├── report.py
│ │ └── users.py
│ ├── services/
│ │ ├── analysis.py
│ │ ├── auth.py
│ │ ├── document_analyzer.py
│ │ ├── report_generator.py
│ │ ├── reports.py
│ │ ├── services.py
│ │ └── users.py
│ ├── tasks/
│ │ ├── cleanup_tasks.py
│ │ ├── document_tasks.py
│ │ └── report_tasks.py
│ └── main.py
├── docs/
│ ├── images/
│ │ ├── report.webp
│ │ └── table-structure.svg
│ ├── example_llm_schema.json
│ └── example_report.pdf
├── tests/
│ ├── integration/
│ │ ├── api/
│ │ └── middleware/
│ ├── unit/
│ │ ├── core/
│ │ └── services/
│ └── conftest.py
├── .env.example
├── .env.test.example
├── Dockerfile
├── docker-compose.yaml
├── docker-compose.test.yaml
├── alembic.ini
├── pyproject.toml
└── uv.lock
More detailed engineering notes are kept under docs/:
architecture.md— layer responsibilities, dependency direction and design decisionsauthentication.md— JWT, refresh-token rotation and RBAC workflowbackground-processing.md— Celery queues, retries and worker designtesting.md— unit/integration strategy and test infrastructureappsmith.md— planned Appsmith dashboard integrationexample_report.pdf— sample generated reportexample_llm_schema.json— example structured LLM output schema
The project was built around a few practical engineering goals:
FastAPI routes should primarily translate HTTP concerns into calls to application services.
Repositories expose data-access capabilities. Services compose those capabilities into application operations.
LLM analysis and PDF generation should not block HTTP requests.
Redis, the LLM provider, file storage and logging live behind infrastructure-oriented modules rather than being scattered across route handlers.
Authentication, authorization, refresh-token persistence and rate limiting are treated as first-class application concerns.
The test suite uses test substitutes for PostgreSQL and Redis so most tests can run without starting the full production stack.
This repository intentionally focuses on backend architecture rather than on building a complete commercial legal product.
Some areas that could be expanded in a production system include:
- document upload and extraction from PDF/DOCX files
- streaming or progress events for long-running analyses
- more advanced LLM evaluation and regression testing
- prompt/version management
- model/provider failover
- distributed tracing and metrics
- centralized secrets management
- object storage instead of local report files
- more granular authorization policies
- stronger production security hardening
- CI/CD workflows and automated release management
- the Appsmith dashboard and its integration documentation
This project is intended as a portfolio and educational project.




