AI-assisted contract extraction, finance planning, and reporting
Graduation Project — Information Technology Institute (ITI) · May 2026
ConstLedger currently has two implemented backend services and a React frontend scaffold. Backend AI handles S3 uploads, PDF parsing, AI extraction, validation, and contract storage. Backend Core handles authentication, users, finance planning, reporting, and XLSX exports.
The frontend currently contains routing, API clients, authentication state, and placeholder pages. Its service modules still include endpoints from an older API design and need alignment before the UI is fully functional.
| Name | Role |
|---|---|
| Yousef Hany Mahmoud | MERN Stack Developer |
| Mohamed Elshahat Amer | MERN Stack Developer |
| Mohamed Nasr Mansour El Khoreby | MERN Stack Developer |
| Abdalla Mohamed Ragheb Elhagar | MERN Stack Developer |
| Mohamed Wael Mohamed Salem | MERN Stack Developer |
| Nagwa Mahmoud Roshdy | Testing & QA |
| Sarah Tarek Mohamed | Testing & QA |
| Rahma Emad Mohamed Mohamed Farid | UI/UX Design |
constledger/
├── frontend/ React 18 + Vite (JSX) → port 5173
├── backend-core/ Node/Express (JS) → port 3000
├── backend-ai/ Node/Express (TS) → port 5000
├── docker-compose.yml Local dev orchestration
└── README.md
The browser talks to both backend services. Core owns /api/auth,
/api/users, /api/finance, and /api/reports; AI owns /api/uploads and
/api/contracts. Both services use the same MongoDB database and verify the
same HTTP-only JWT cookie. There is no active x-internal-secret service call
between them.
Browser
├── /api/auth, /api/users, /api/finance, /api/reports
│ └── backend-core :3000
└── /api/uploads, /api/contracts
└── backend-ai :5000
backend-core ──┐
├── shared MongoDB :27017
backend-ai ───┘
└── Amazon S3
└── Gemini, OpenRouter, or Groq
| Module | Description | AI? |
|---|---|---|
| Contracts | Direct-to-S3 upload, background PDF extraction, validation, human review, editing, and re-analysis | Gemini, OpenRouter, or Groq |
| Finance | Straight-line, S-curve, and milestone-weighted plans; manual edits; confirmation; payment schedule | Deterministic |
| Reports | Contract, planned-budget, payment-schedule, and project-summary JSON/XLSX reports | Aggregation |
| Frontend | React/Vite routes and service scaffold; page implementations are placeholders | Not complete |
| Role | Permissions |
|---|---|
contract_manager |
Upload contracts, review AI extractions, edit contract data |
pmo |
Everything above + manage users and all finance operations |
finance_team |
Read finance plans and reports |
top_management |
Read finance plans and reports |
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, React Router v6 |
| Core Backend | Node.js, Express, Mongoose |
| AI Backend | Node.js, Express, TypeScript |
| Database | MongoDB (Mongoose ODM) |
| File Storage | Amazon S3 |
| LLM | Gemini, OpenRouter, or Groq |
| Auth | JWT in HTTP-only cookie + RBAC |
| Testing | Vitest |
| Dev Orchestration | Docker Compose |
Prerequisites: Docker Desktop, an Amazon S3 bucket, and at least one Gemini, OpenRouter, or Groq API key.
# 1. Clone
git clone https://github.com/your-org/constledger.git && cd constledger
# 2. Create env files
cp frontend/.env.example frontend/.env.local
cp backend-core/.env.example backend-core/.env
cp backend-ai/.env.example backend-ai/.env
# 3. Fill in secrets
# use the same JWT_SECRET in both backend env files
# backend-core/.env → MONGODB_URI, JWT_SECRET
# backend-ai/.env → MONGODB_URI, JWT_SECRET, AWS_*, S3_BUCKET,
# and at least one AI provider API key
# 4. Start everything
docker-compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| Backend Core | http://localhost:3000 |
| Backend AI | http://localhost:5000 |
| MongoDB | mongodb://localhost:27017/cpms |
Prerequisites: Node.js 20+, MongoDB running locally.
mongod --dbpath ~/data/db
# or use a MongoDB Atlas free clustercd backend-core
cp .env.example .env # set MONGODB_URI=mongodb://localhost:27017/cpms
npm install
npm run devcd backend-ai
cp .env.example .env # set MONGODB_URI=mongodb://localhost:27017/cpms
# configure JWT_SECRET, S3, and an AI provider key
npm install
npm run devcd frontend
cp .env.example .env.local # already set for localhost
npm install
npm run devStart order: MongoDB → backend-core and backend-ai → frontend
curl http://localhost:3000/health # {"status":"ok","service":"cpms-core"}
curl http://localhost:5000/health # {"status":"ok","service":"cpms-ai"}Focused Vitest suites cover the main backend business logic without requiring MongoDB, AWS, OCR, or AI provider credentials.
cd backend-core && npm test
cd ../backend-ai && npm test
cd ../frontend && npm run buildUse npm run test:watch inside either backend while developing. Docker files
do not need test-specific changes because Vitest is a development dependency
and the runtime images continue to install production dependencies only.
| Service | Mounted API prefixes |
|---|---|
| Backend Core | /api/auth, /api/users, /api/finance, /api/reports |
| Backend AI | /api/uploads, /api/contracts |
See backend-core/README.md and backend-ai/README.md for the current endpoints, authorization rules, request shapes, and environment variables.
docker-compose up used to bring up all four containers successfully, but
the frontend couldn't actually reach backend-ai once you started clicking
around (contract list, upload, analysis all failed) — even though
npm run dev for each service on the host (Option B) worked fine. Two
stacked issues caused it, and both are now fixed in vite.config.js and
docker-compose.yml:
-
vite.config.js's dev-server proxy targets were hardcoded tolocalhost. That proxy runs inside whichever process is running Vite. When that process is thefrontendcontainer,localhostresolves to the container itself — not tobackend-coreorbackend-ai, which are only reachable fromfrontendvia their Compose service names (http://backend-core:3000,http://backend-ai:5000) on thecpms-netnetwork. Running all three as plain host processes has no such isolation, so the same hardcoded targets happened to be correct there.Fix: the proxy targets are now read from
CORE_PROXY_TARGET/AI_PROXY_TARGETenv vars, defaulting tolocalhostfor local dev.docker-compose.ymlsets them to the service names instead. -
docker-compose.ymloverrodeVITE_API_URLto an absolute URL (http://localhost:3000/api) instead of the relative/apidefault infrontend/.env.example.frontend/.dockerignoreexcludes.env.localfrom the build context, so inside the container that Compose value was the only value Vite ever saw. Becauseservices/api.jshas a single Axios instance whosebaseURLisVITE_API_URL— andVITE_AI_API_URL(also set in the olddocker-compose.yml) was never read anywhere in the frontend code — every request, including ones for/api/contractsand/api/uploads, got sent tobackend-core, which doesn't mount those routes, so they 404'd.In local/standalone dev,
VITE_API_URLstayed relative (/api), so requests went through the Vite proxy from issue #1 instead — and that proxy did correctly split traffic by path between:3000and:5000, because in that modelocalhostwas unambiguous. That's why contracts/uploads worked outside Docker even though the frontend only ever had one configured API base URL.Fix:
docker-compose.ymlnow setsVITE_API_URL=/api(relative, matching local dev), so every request goes through the now-correctly- targeted Vite proxy from fix #1.VITE_AI_API_URLwas removed since nothing reads it.
If you pulled this repo before this fix landed: docker-compose up --build
again to pick up the new frontend environment variables, no other changes
needed.
- All frontend page components are placeholders.
- The frontend authentication client expects a response token and sends bearer
tokens, while the backends use only an HTTP-only cookie. Axios also needs
withCredentials: true. - Several frontend finance and report URLs belong to an older API and are not mounted by the current backends.
- Upload signing accepts PDF, DOC, and DOCX MIME types, but active analysis
supports PDF only. Word uploads currently end in
analysis_failed. - AI extraction always finishes in
pending_review; a manager must activate the contract before finance-plan generation. docker-compose.ymlstill setsAI_SERVICE_URL/CORE_SERVICE_URLon the backends, and both.env.examplefiles documentAI_SERVICE_SECRET/ anx-internal-secretheader. None of these are read anywhere insrc/for either backend — there is no service-to-service HTTP call today (matches the "Service Communication" note above). Safe to ignore or remove until that call is actually built.- There are no automated frontend tests yet.
See frontend/README.md for the frontend integration work that remains.
cd frontend && npm run build
npx vercel --prod
# Set in Vercel dashboard:
# VITE_API_URL = https://your-core-service.run.app/api
# VITE_AI_API_URL = https://your-ai-service.run.app/api# backend-core
cd backend-core
gcloud builds submit --tag gcr.io/YOUR_PROJECT/constledger-backend-core
gcloud run deploy constledger-backend-core \
--image gcr.io/YOUR_PROJECT/constledger-backend-core \
--platform managed --region us-central1 \
--set-env-vars MONGODB_URI=...,JWT_SECRET=...
# backend-ai
cd backend-ai
gcloud builds submit --tag gcr.io/YOUR_PROJECT/constledger-backend-ai
gcloud run deploy constledger-backend-ai \
--image gcr.io/YOUR_PROJECT/constledger-backend-ai \
--platform managed --region us-central1 \
--allow-unauthenticated \
--set-env-vars MONGODB_URI=...,JWT_SECRET=...,S3_BUCKET=...,AWS_REGION=...The browser currently calls Backend AI directly, so a private Cloud Run service would require an additional authenticated proxy architecture. Inject JWT, database, S3, and AI-provider secrets through Secret Manager in production.