A full-stack industrial waste exchange platform that connects waste producers with companies that can reuse those materials as raw inputs — closing the loop in India's circular economy. Built with React 19, Flask, PyTorch, and a 5-database polyglot persistence architecture.
- Overview
- Architecture
- Tech Stack
- Features
- AI Matching Algorithm
- Database Architecture (5 DBs)
- API Endpoints
- Frontend Pages
- Getting Started
- Project Structure
Indian industries generate millions of tonnes of waste annually — metal scrap, organic residues, plastics, e-waste, construction debris, and more. Most of it ends up in landfills even though other industries could use it as raw material.
CircularConnect solves this by:
- Classifying waste from images using an EfficientNet-B3 CNN (11 waste categories)
- Matching waste producers with compatible buyer companies using a 6-stage hybrid ML + rule-based pipeline
- Connecting them via real-time chat and a structured deal system
- Tracking environmental impact (CO₂ saved, landfill diverted, energy recovered) across every transaction
┌─────────────────────────────────────────────────────────────────┐
│ FRONTEND (React 19 + Vite) │
│ Login → Classify → Match → Chat → Deal → Dashboard → Deals │
└────────────────────────────┬────────────────────────────────────┘
│ /api proxy (port 5173 → 5002)
┌────────────────────────────▼────────────────────────────────────┐
│ BACKEND (Flask + PyTorch) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ EfficientNet │ │ Matching │ │ Chat / Deal / Notify │ │
│ │ B3 Classifier│ │ Engine (6 │ │ System │ │
│ │ (11 classes) │ │ stages) │ │ │ │
│ └──────────────┘ └──────────────┘ └────────────────────────┘ │
└──┬──────────┬──────────┬──────────┬──────────┬──────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────────┐ ┌───────┐ ┌───────────┐
│Postgr│ │Neo4j │ │ MongoDB │ │ Redis │ │ Cassandra │
│ SQL │ │ │ │ │ │ │ │ │
│Users │ │Graph │ │Documents │ │Cache │ │TimeSeries │
│Deals │ │Paths │ │Activity │ │Rate │ │Analytics │
│Chat │ │Rels │ │History │ │Limit │ │Audit Logs │
└──────┘ └──────┘ └──────────┘ └───────┘ └───────────┘
:5433 :7687 :27017 :6379 :9042
Every significant user action (classification, match query, message, deal) fans out to all 5 databases — each storing the data in the format it's best at.
| Technology | Version | Purpose |
|---|---|---|
| React | 19.2 | UI framework |
| Vite | 8.0 (beta) | Build tool & dev server |
| React Router | 7.13 | Client-side routing |
| Leaflet + React-Leaflet | 1.9 / 5.0 | Interactive map (India) |
| Technology | Version | Purpose |
|---|---|---|
| Flask | 2.3.3 | REST API server |
| PyTorch | 2.10 | Deep learning framework |
| timm (EfficientNet-B3) | — | Pre-trained CNN for waste classification |
| scikit-learn | — | Cosine similarity, KNN, MinMaxScaler, OneHotEncoder |
| SciPy | — | Hungarian algorithm (linear_sum_assignment) |
| NumPy | 1.24 | Numerical computing |
| Database | Type | Port | Purpose |
|---|---|---|---|
| PostgreSQL 13 | Relational | 5433 | Users, auth, deals, chat, notifications |
| Neo4j 5 | Graph | 7474 / 7687 | Supply chain network, company relationships |
| MongoDB 5 | Document | 27017 | Classification history, activity feed, waste listings |
| Redis 7 | Key-Value | 6379 | Caching, rate limiting, online presence, counters |
| Cassandra 4 | Column-Family | 9042 | Time-series analytics, environmental metrics, audit logs |
- Email/password registration with PBKDF2-HMAC-SHA256 hashing
- Protected routes (all pages except login/signup require auth)
- Editable company profiles with Indian city geocoding (30 cities)
- Upload a waste image → EfficientNet-B3 CNN classifies it into one of 11 categories:
Metal, Plastic, Paper/Cardboard, Glass, Organic, Textile, Construction, Hazardous, Industrial Ash, Electronic, Mixed - Multi-label sigmoid output with confidence percentages
- Results cached in Redis (by image SHA-256 hash, 1h TTL)
- Rate limited at 30 classifications/minute per IP
- Each classification is recorded in all 5 databases
- 6-stage hybrid pipeline (see algorithm section below)
- Rule-based compatibility acts as a hard gate — incompatible industries are eliminated before ML scoring
- Interactive animated pipeline visualization during matching
- Results page with expandable score breakdowns per company
- Leaflet interactive map of India with all registered companies
- Filter by industry, search by company/city name
- Distance calculation from your company (Haversine)
- Overlapping marker jittering for dense areas
- Map view + list view toggle
- 1-on-1 conversations between matched companies
- Unread message counts and conversation list
- Green online indicator with Redis heartbeat (30s TTL)
- Automatic notification creation on new messages
- Propose deals from chat (waste type, quantity in tonnes, price in ₹)
- Deal lifecycle:
active → completed / cancelled - Environmental impact auto-calculated on deal completion (CO₂ saved, landfill diverted)
- Deal events recorded in all 5 databases
- Live platform analytics from all 5 databases
- Stat cards: Total Users, Classifications, Deals Done, Environmental Impact
- Refreshes from API (not stale localStorage)
- Comprehensive
/dealspage with:- Stat cards (total deals, active, completed, revenue)
- Partner grid with deal counts
- Filterable deal table
- Cassandra timeline of deal events
- MongoDB activity feed
- Redis live platform counters
- Database source badges showing which DB each section pulls from
- Auto-triggered on: new message, deal proposed, deal accepted/completed
- Header bell icon with unread count (polling)
- Dropdown with clickable notifications → navigates to relevant page
- Mark as read (individual or all)
- All pricing in ₹ (INR)
- Quantities in tonnes
- 30 Indian cities with geocoding (Mumbai, Delhi, Bangalore, Chennai, etc.)
- Indian industry types (Tata Steel, Reliance Polymers, Hindalco Aluminum, etc.)
The matching engine uses a 6-stage pipeline to find the best companies for a given waste type:
┌─────────────────────┐
│ ALL COMPANIES (DB) │
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Stage 0: Exclude │ Remove the requesting user
│ Self │
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Stage 1: HARD GATE │ Rule-based compatibility filter
│ 🚪 PASS / REJECT │ Waste → Industry affinity table
│ │ If affinity = 0 → ELIMINATED
└─────────┬───────────┘
▼ (only compatible companies remain)
┌─────────────────────┐
│ Stage 2: Cosine │ One-hot encode industry + waste type
│ Similarity (35%) │ + normalised lat/lng + activity score
│ 🧠 ML │ → cosine similarity
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Stage 3: KNN (20%) │ k-Nearest-Neighbours in feature
│ 🔬 ML │ space → rank score (1.0 → 0.0)
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Stage 4: Distance │ Haversine great-circle distance
│ (45%) 📍 │ Inverted: closer = higher score
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Stage 5: Blend │ final = 0.35×sim + 0.20×knn
│ ⚖️ Weighted Sum │ + 0.45×distance
└─────────┬───────────┘
▼
┌─────────────────────┐
│ Stage 6: LP Tie- │ Hungarian algorithm gives +0.02
│ Breaking ⚙️ │ bonus to globally optimal match
└─────────┬───────────┘
▼
┌─────────────────────┐
│ TOP K RESULTS │ Sorted by final_score desc
└─────────────────────┘
Maps 11 waste types to compatible industries with affinity scores (0–1):
| Waste Type | Top Compatible Industries (affinity) |
|---|---|
| Metal | Steel (0.95), Aluminum (0.95), Metals & Mining (0.90), Automobile (0.80) |
| Plastic | Plastics (0.95), Rubber (0.85), Manufacturing (0.70), Chemicals (0.65) |
| Organic | Agriculture (0.95), Food Processing (0.85), Renewable Energy (0.70) |
| Glass | Glass (0.95), Ceramics (0.85), Construction (0.60) |
| Paper/Cardboard | Paper & Pulp (0.95), Packaging (0.90), Recycling (0.80) |
| Textile | Textiles (0.95), Fashion (0.85), Recycling (0.70) |
| Electronic | Electronics (0.90), IT Hardware (0.85), Metals & Mining (0.60) |
| Construction | Construction (0.90), Cement (0.85), Manufacturing (0.60) |
| Hazardous | Waste Management (0.90), Chemicals (0.80), Pharmaceuticals (0.60) |
| Industrial Ash | Cement (0.90), Construction (0.85), Agriculture (0.40) |
| Mixed | Recycling (0.85), Waste Management (0.80), Manufacturing (0.50) |
If a company's industry is not in the table for the waste type → it's eliminated. No amount of proximity or ML similarity can save an incompatible company.
| Factor | Weight | Rationale |
|---|---|---|
| Distance (Haversine) | 45% | Logistics cost dominates — closer = cheaper transport |
| ML Cosine Similarity | 35% | Feature-space compatibility of profiles |
| KNN Ranking | 20% | Structural neighbourhood in feature space |
The source of truth for structured, transactional data.
| Table | Purpose |
|---|---|
users |
Accounts (email, password_hash, salt, company, industry, location, lat/lng) |
companies |
Extended company registry (capacity, certifications as JSONB) |
waste_types |
Waste type catalog with processing methods |
conversations |
Chat conversations (unique user1/user2 pairs) |
messages |
Chat messages (with read receipts) |
deals |
Deal lifecycle: active → completed / cancelled |
notifications |
User notifications (type, title, body, link, is_read) |
compliance_standards |
Regulatory standards (JSONB requirements) |
Models the supply chain network and company relationships.
Nodes: :Company, :Waste, :Industry, :WasteType, :Product
| Relationship | Meaning |
|---|---|
MATCHED_WITH |
ML matching result (score, waste_type) |
CHATTED_WITH |
Chat connection (waste_context, count) |
DEALT_WITH |
Completed deal (waste_type, total_quantity) |
SUPPLIED_TO |
Waste → Industry supply link |
PRODUCES |
Industry → Product |
GENERATES |
Product → Waste (circular loop) |
Key queries: find_circular_pathways() traces full Waste → Industry → Product → Waste → Industry loops up to 5 hops.
Flexible document storage for unstructured and semi-structured data.
| Collection | Purpose |
|---|---|
classification_history |
Full classification results with all metadata |
activity_feed |
Stream of all user actions (match, message, deal events) |
user_profiles |
Extended user documents |
waste_listings |
Marketplace listings (text search + geospatial indexes) |
transactions |
Transaction history documents |
user_preferences |
User preference tracking |
Also uses GridFS for waste image storage.
Optimised for high-write time-series data with time-bucketed partitions.
| Table | Partition Key | Purpose |
|---|---|---|
transactions |
transaction_id (UUID) | Transaction records |
environmental_impact |
category + date | CO₂ savings, waste diversion by date |
audit_logs |
entity_type | Audit trail (clustered by timestamp DESC) |
analytics_timeseries |
event_type | All events: classification, match, chat, deal (clustered by timestamp DESC) |
Sub-millisecond reads for caching, rate limiting, and real-time features.
| Key Pattern | TTL | Purpose |
|---|---|---|
classify:<sha256> |
1h | Classification result cache |
match:<waste>:<user> |
5min | Match result cache |
ratelimit:classify:<ip> |
1min | Rate limiting (30 req/min) |
user:online:<id> |
30s | Online presence heartbeat |
session:<id> |
1h | User session data |
stats:classifications:total |
— | Classification counter |
stats:deals:completed |
— | Deal completion counter |
stats:deals:total_value |
— | Cumulative deal value (₹) |
| Method | Route | Description |
|---|---|---|
| GET | /api/health |
Health check — model status + all 5 DB states |
| POST | /api/classify |
AI waste classification (image upload) |
| Method | Route | Description |
|---|---|---|
| GET | /api/match-companies |
Hybrid ML + rule-based matching |
| GET | /api/smart-match |
Scored + ranked matches (for Matches page) |
| GET | /api/companies/map |
All companies with coordinates for map |
| Method | Route | Description |
|---|---|---|
| POST | /api/auth/register |
User registration |
| POST | /api/auth/login |
User login |
| POST | /api/auth/logout |
User logout |
| PUT | /api/auth/profile |
Update user profile |
| Method | Route | Description |
|---|---|---|
| GET | /api/chat/conversations |
List conversations (with online status) |
| POST | /api/chat/start |
Start or resume a conversation |
| GET | /api/chat/messages/<id> |
Get messages in a conversation |
| POST | /api/chat/messages/<id> |
Send a message |
| GET | /api/chat/unread-count |
Total unread message count |
| Method | Route | Description |
|---|---|---|
| POST | /api/deals |
Create a deal proposal |
| GET | /api/deals/conversation/<id> |
Deals in a conversation |
| PUT | /api/deals/<id>/accept |
Accept a deal |
| PUT | /api/deals/<id>/complete |
Complete a deal (calculates impact) |
| PUT | /api/deals/<id>/cancel |
Cancel a deal |
| GET | /api/user/deals |
All deals for a user |
| GET | /api/user/deal-analytics |
Rich analytics from all 5 DBs |
| Method | Route | Description |
|---|---|---|
| GET | /api/notifications |
Get notifications for a user |
| GET | /api/notifications/count |
Unread notification count |
| PUT | /api/notifications/<id>/read |
Mark notification as read |
| PUT | /api/notifications/read-all |
Mark all as read |
| Method | Route | Description |
|---|---|---|
| GET | /api/analytics/dashboard |
Platform analytics from all 5 DBs |
| GET | /api/analytics/activity |
Activity history (MongoDB) |
| GET | /api/analytics/classifications |
Classification history (MongoDB) |
| GET | /api/analytics/supply-chain |
Supply chain insights (Neo4j) |
| GET | /api/analytics/environmental |
Environmental impact report |
| GET | /api/network/graph |
Company relationship graph (Neo4j) |
| Route | Page | Description |
|---|---|---|
/login |
Login | Email/password login |
/signup |
Signup | Registration with company details |
/ |
Home | Landing page with platform overview |
/about |
About | Platform mission and team |
/how-it-works |
How It Works | Step-by-step platform guide |
/marketplace |
Marketplace | Interactive India map with all companies |
/classify |
Classify | AI waste image classification + animated matching |
/matches |
Matches | AI-scored company matches with pipeline visualization |
/dashboard |
Dashboard | Live analytics dashboard |
/deals |
Deal Analytics | Deal history, charts, all 5 DB sections |
/chat |
Chat | Real-time messaging + deal proposals |
/profile |
Profile | Editable company profile |
/impact |
Impact | Environmental impact tracking |
/business |
Business | Business model information |
/contact |
Contact | Contact form |
All pages except /login and /signup are protected by authentication.
- Node.js ≥ 18
- Python ≥ 3.10
- Docker & Docker Compose
cd backend
docker compose up -dVerify all 5 services are running:
docker compose pscd backend
pip install -r requirements.txt
pip install -r database_requirements.txt
python3 app.pyThis project includes a production-ready Dockerfile for the backend at backend/Dockerfile. The container runs the Flask app under gunicorn for production.
Build and run locally:
# from repository root
docker build -t circular-backend ./backend
docker run -p 5002:5002 -e PORT=5002 --env-file backend/.env -it circular-backendDeploy to Fly.io (quick guide):
- Install
flyctl: https://fly.io/docs/hands-on/install-flyctl/ - Login and init your app:
flyctl auth login
cd backend
flyctl launch --image circular-backend --name circular-backend --org personal --region iad- Set environment variables (replace placeholders):
flyctl secrets set JWT_SECRET="<strong-secret>" \
POSTGRES_URL="<postgres-conn>" MONGO_URI="<mongo-uri>" \
NEO4J_URI="<neo4j-uri>" NEO4J_USER="<user>" NEO4J_PASSWORD="<pass>" \
REDIS_URL="<redis-url>" S3_BUCKET="<bucket>" MODEL_S3_KEY="<key>"- Deploy:
flyctl deployNotes:
- The container entrypoint is
gunicorn -w 4 -k gthread -b 0.0.0.0:5002 app:app— tune workers/threads per instance based on memory and CPU. - For free-tier hosting, Fly.io provides small VMs that are suitable for CPU inference on moderate workloads. If you require GPU inference or higher throughput, use managed inference (see below).
- The
model.pthis intentionally excluded from the Docker image. Store the model in object storage (Hugging Face Hub or S3). On startup, the app should download the model to/app/model.pthif missing — see the next section for automating this.
We recommend hosting the model.pth on the Hugging Face Hub (free for public/private repos with limits). The backend supports two ways to fetch the model at container start:
MODEL_HF_REPO+MODEL_HF_FILENAME: repository id (e.g.username/repo) and filename stored in the repo. The app useshuggingface_hubto download the file on startup.MODEL_HTTP_URL: a direct HTTP(S) URL to the model file (e.g. GitHub release asset, or object storage URL).
Environment variables to set before deploying:
MODEL_HF_REPO(optional) — Hugging Face repo id to download the model from.MODEL_HF_FILENAME(optional) — filename in the HF repo (default:model.pth).MODEL_HTTP_URL(optional) — direct URL to model file.
Example (using Hugging Face):
# After uploading model.pth to HF repo 'my-user/circular-models'
flyctl secrets set MODEL_HF_REPO="my-user/circular-models" MODEL_HF_FILENAME="model.pth"When the service starts, if /app/model.pth is missing it will attempt to download the model from the configured source. This keeps Docker images lightweight and allows you to update models without rebuilding containers.
The Flask server starts on http://localhost:5002.
npm install
npm run devThe Vite dev server starts on http://localhost:5173 with API proxy to port 5002.
Navigate to http://localhost:5173. You'll be redirected to the login page. Create an account to get started.
# Stop databases
cd backend && docker compose down
# Stop backend
Ctrl+C (or kill the Flask process)
# Stop frontend
Ctrl+C| Service | Username | Password |
|---|---|---|
| Neo4j | neo4j |
password |
| PostgreSQL | postgres |
postgres (port 5433) |
| MongoDB | — | No auth (dev mode) |
| Redis | — | No auth (dev mode) |
| Cassandra | — | No auth (dev mode) |
circular_economy/
├── README.md
├── package.json
├── vite.config.js # Vite config + /api proxy
├── index.html
│
├── src/ # React frontend
│ ├── main.jsx # Entry point
│ ├── App.jsx # Router + protected routes
│ ├── App.css / index.css # Global styles
│ ├── components/
│ │ ├── Header.jsx/css # Nav bar + notification bell
│ │ └── Footer.jsx/css # Footer
│ └── pages/
│ ├── Home.jsx/css # Landing page
│ ├── Login.jsx/css # Login form
│ ├── Signup.jsx/css # Registration form
│ ├── Classify.jsx/css # AI classification + matching animation
│ ├── Matches.jsx/css # AI-scored match results + pipeline viz
│ ├── Marketplace.jsx/css # India map + company list
│ ├── Chat.jsx/css # Messaging + deals
│ ├── Dashboard.jsx/css # Analytics dashboard
│ ├── DealHistory.jsx/css # Deal analytics (all 5 DBs)
│ ├── Impact.jsx/css # Environmental impact
│ ├── About.jsx/css # About page
│ ├── HowItWorks.jsx/css # How it works
│ ├── Business.jsx/css # Business model
│ └── Contact.jsx/css # Contact form
│
├── backend/
│ ├── app.py # Flask API server (all endpoints)
│ ├── matching_engine.py # 6-stage hybrid ML matching pipeline
│ ├── model.pth # Trained EfficientNet-B3 weights
│ ├── requirements.txt # Python ML dependencies
│ ├── database_requirements.txt # DB driver packages
│ ├── docker-compose.yml # All 5 database services
│ └── database/
│ ├── database_manager.py # Unified 5-DB orchestrator
│ ├── postgresql_manager.py # Relational core
│ ├── neo4j_manager.py # Graph database
│ ├── mongodb_manager.py # Document store
│ ├── cassandra_manager.py # Time-series
│ ├── redis_manager.py # Cache & real-time
│ └── config.py # Connection configs
│
└── public/ # Static assets
This project was built as an academic demonstration of polyglot persistence and AI-driven circular economy concepts.
- Keep credentials in
backend/.envaligned withbackend/docker-compose.yml
This project uses JSON Web Tokens (JWT) for authenticating API requests.
- The backend issues an access token (short-lived) and a refresh token on successful login (
/api/auth/login). - Send the access token in the
Authorizationheader:Authorization: Bearer <access_token>for protected endpoints. - The refresh token is set as an
httpOnlycookie by default; exchange it for a new access token at/api/auth/refresh. - Environment variables:
JWT_SECRET— secret key used to sign tokens (REQUIRED in production).JWT_ALGORITHM— signing algorithm (defaultHS256).ACCESS_TOKEN_EXPIRES_SECONDS— access token lifetime (default 900 = 15 minutes).REFRESH_TOKEN_EXPIRES_DAYS— refresh token lifetime (default 14 days).
Security notes:
- Keep
JWT_SECRETout of source control and set it in production environment variables or secrets manager. - Prefer storing refresh token as
httpOnlycookie to avoid XSS leakage. - For revocation support, store refresh token identifiers server-side (Redis/Postgres) and mark them revoked on logout.
Frontend integration:
- Store access tokens in memory or
localStorageand attach them to API requests. - On 401 responses, call
/api/auth/refreshto obtain a new access token and retry the original request.
Detailed Frontend Integration (step-by-step)
- Login flow
- POST
/api/auth/loginwith{ email, password }. - Server responds with
{ access_token, refresh_token, user }and sets anhttpOnlyrefresh cookie. - Store
access_tokeninlocalStorage(or memory) anduserobject for UI.
- Sending authenticated requests
- Add
Authorization: Bearer <access_token>header to API requests. - Include
credentials: 'include'onfetchcalls so the browser sends the refresh cookie when needed. - Use a small helper that wraps
fetch, automatically adds the header, and retries once after attempting a refresh.
- Refresh flow
- When an API returns
401 Unauthorized, callPOST /api/auth/refreshwithcredentials: 'include'. - If the refresh succeeds, store the new access token and retry the original request.
- If refresh fails, redirect to
/loginand clear stored auth data.
- Logout
- Call
POST /api/auth/logoutto revoke the refresh token server-side and clear the cookie. - Clear local storage and redirect to login.
Implementation notes
- The repository includes
src/utils/auth.js, a minimal helper that implementsfetchWithAuth,refreshAccessToken, and token storage helpers. Update existing API calls to usefetchWithAuthwhere possible. - For security, prefer keeping the refresh token only as an
httpOnlycookie (so JavaScript cannot read it). Access tokens may be stored in memory to reduce XSS exposure. - Consider adding CSRF protections if both cookies and state-changing endpoints are used alongside JWT cookies.