Skip to content

Latest commit

Β 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Bandifinder.it

AI-Powered Public Tender Discovery & Bid Management Platform

Bandifinder.it helps Italian businesses discover, analyze, and bid on public tenders across Italy and the EU. It combines a deterministic, explainable scoring engine with multi-agent AI and structured data pipelines β€” from discovery to post-mortem analytics.

Tender detail with the weighted score breakdown

Every score is shown as the sum of its parts. Each segment of the meter is as wide as that component is worth (CPV 25, certifications 20, economic fit 20, geography 15, experience 10, deadline 10), and filled to what was earned β€” so the gaps show exactly where the points went, sized by how much they mattered.

Features

Tender Discovery

  • Natural language search via AI chat agent
  • TED API v3 + ANAC integration, ingested every 6h via Supabase pg_cron
  • Filters (country, period, value range), full-text search, paginated results
  • Semantic vector search (Pinecone) + GraphRAG hybrid retrieval
  • Country filter defaults to Italy; widening to the EU is one click

Tender list with filters and fit scores

AI chat with suggested searches

Explainable Scoring Engine

  • 6 weighted components out of 100: CPV match (25), certifications (20), economic fit (20), geography (15), experience (10), deadline feasibility (10)
  • Deterministic β€” no LLM involved, so every score is reproducible and defensible
  • Per-component explanations in Italian, plus a pass/fail eligibility checklist
  • BID (β‰₯70) / REVIEW (40–69) / SKIP (<40) recommendations
  • Scores cached per-org with a 24h TTL
  • The same engine backs the REST API and the chat agent, so the two can never disagree

Guest Mode

  • Visitors browse tenders and see real fit scores without creating an account
  • A short inline form (sector, regions, certifications, revenue) feeds the scoring engine
  • The profile lives in localStorage and travels as a request header; nothing is persisted server-side
  • A guest carries no user id and no organization, so guest requests can never reach another org's data
  • Saving bids, favourites and exports prompt for signup

Bid Pipeline (Kanban)

  • 6 stages: Nuovi β†’ In Revisione β†’ In Preparazione β†’ Inviati β†’ Vinti / Persi
  • Drag-and-drop (dnd-kit) + hover move buttons
  • Checklist items, threaded comments, team assignments
  • Auto-generated compliance checklist on bid creation

Kanban pipeline

Pipeline stages darken along a single teal ramp, so how far a bid has travelled is legible from colour alone. Only the terminal states break it.

Post-Mortem Analytics

  • Win/loss tracking with competitive intelligence
  • Win rate segmented by value range and buyer
  • Competitor analysis (direct bid outcomes + TED award data)
  • Lessons learned feed

Dashboard with KPIs and urgent deadlines

The four headline figures are one funnel β€” analysed, submitted, won, average value β€” so they read in order. Missed deadlines is a different kind of number and gets its own card, turning vermilion the moment it is non-zero.

Multi-Source Ingestion

  • Modular connector system (TED API v3, ANAC CSV, TED Awards)
  • Deduplication with amendment detection (8-field comparison)
  • Automatic notification generation for saved searches
  • Scheduled by Supabase pg_cron; admin manual trigger available

Billing & Usage Limits

  • Stripe integration with 4 tiers (Free / Starter / Pro / Enterprise)
  • Daily search quotas and rate limits held in Postgres, so they are shared across all serverless instances and survive restarts
  • Quota consumption is a single atomic statement β€” concurrent requests cannot overshoot a limit
  • Checkout, customer portal, webhook-driven plan sync

Other

  • Clerk authentication (JWT, organizations, RBAC), verified server-side on every protected route
  • GDPR cookie consent banner (controls Sentry replay + Vercel Analytics)
  • Sentry error tracking (frontend + API)
  • Guided onboarding flow (org creation β†’ profile completion)
  • Full Italian UI

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Next.js 16 Frontend (Vercel)                       β”‚
β”‚  React 19 Β· Clerk Auth Β· ShadCN UI Β· TanStack Query β”‚
β”‚  dnd-kit Β· Sentry Β· Vercel Analytics                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚ REST + SSE
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Hono.js API (Vercel Serverless)                    β”‚
β”‚  LangGraph Supervisor β†’ 5 Specialized Agents        β”‚
β”‚  Scoring Engine Β· Ingestion Pipeline Β· Stripe       β”‚
β”‚  Security Middleware (auth, rate limit, PII)        β”‚
β”‚  Observability (metrics, logging, tracing, Sentry)  β”‚
β””β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚          β”‚          β”‚          β”‚
 Supabase   Pinecone   TED API   Stripe
 Postgres   Vectors    + ANAC    Billing

Authentication model

Route group Access
/tenders/*, /analytics/*, /compare/* Signed-in or guest
/agent/*, /suggestions/* Optional auth (public chat)
/company, /bids, /billing, /preferences, /notifications, /saved-searches, /organizations, /export Verified Clerk session required
/ingestion/* Admin session or Authorization: Bearer $CRON_SECRET
/webhooks/* Signature-verified (Clerk via Svix, Stripe)

Identity comes only from a verified Clerk JWT. Guests get a separate context that carries no user id and no organization id.

AI Multi-Agent System

A router pattern: classify intent, route to one specialist, format the result.

Agent Role
Supervisor Keyword intent classification and routing
Search TED API queries and tender discovery
Analysis Eligibility and scoring β€” delegates to the scoring engine
Ranking Ordering and shortlisting β€” delegates to the scoring engine
Personalization Profile-based recommendations
Contract Review Risk analysis and clause extraction
General Product questions and greetings (no tools)

Agent identity comes from the graph config (configurable.user_id), never from the model. Conversation threads are checkpointed to Postgres when DATABASE_URL is set, falling back to in-memory otherwise.

Getting Started

Prerequisites

  • Node.js 22+ (the deployed API targets Node 24)
  • npm

Installation

npm install            # frontend
cd backend && npm install  # API

Environment Variables

Root .env / .env.local:

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
NEXT_PUBLIC_TENDER_API_BASE=http://localhost:3001   # API base URL
NEXT_PUBLIC_SENTRY_DSN=https://...
SENTRY_AUTH_TOKEN=sntrys_...
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project

backend/.env.local:

OPENROUTER_API_KEY=sk-or-v1-...
OPENAI_API_KEY=sk-...            # embeddings for Pinecone
PINECONE_API_KEY=pcsk_...
SUPABASE_URL=https://....supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...
DATABASE_URL=postgresql://...    # Postgres connection string (use the pooler)
CLERK_SECRET_KEY=sk_...
CLERK_WEBHOOK_SECRET=whsec_...
STRIPE_SECRET_KEY=sk_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_STARTER=price_...
STRIPE_PRICE_PRO=price_...
STRIPE_PRICE_ENTERPRISE=price_...
FRONTEND_URL=http://localhost:3000
SENTRY_DSN=https://...
CRON_SECRET=...                  # shared secret for scheduled ingestion

Optional, local development only:

Variable Purpose
PORT API port (default 3001)
CORS_EXTRA_ORIGINS Comma-separated extra origins, e.g. when port 3000 is taken
ALLOW_INSECURE_HEADER_AUTH Set to true to accept an unverified x-user-id header. Never set this in a deployed environment β€” it is a complete authentication bypass
LOG_LEVEL debug | info | warn | error

DATABASE_URL is a Postgres connection string, not SUPABASE_URL (which is the REST endpoint). Use Supabase's pooler URL β€” direct connections exhaust Postgres connection limits from serverless.

Database

Apply the migrations in supabase/migrations/ in order. Then schedule the housekeeping job:

select cron.schedule('purge-usage', '17 3 * * *', 'select purge_usage_data()');

Development

# Terminal 1 β€” Frontend (http://localhost:3000)
npm run dev

# Terminal 2 β€” API (http://localhost:3001)
cd backend && npm run dev

Testing

cd backend && npm test

262 tests covering the scoring engine, auth enforcement, guest access, usage limits, agent tools, and GraphRAG isolation. The SQL in migration 006 is executed against a real Postgres via PGlite rather than mocked.

Project Structure

Tender-Finder-AI/
β”œβ”€β”€ app/                              # Next.js App Router
β”‚   β”œβ”€β”€ page.tsx                      # AI Chat interface
β”‚   β”œβ”€β”€ dashboard/                    # KPIs, high-fit tenders, deadlines
β”‚   β”œβ”€β”€ tenders/                      # Search, filters, table
β”‚   β”‚   └── [id]/                     # Detail, scoring, eligibility
β”‚   β”œβ”€β”€ pipeline/                     # Kanban board (dnd-kit)
β”‚   β”œβ”€β”€ bids/[id]/                    # Bid detail, checklist, comments
β”‚   β”œβ”€β”€ analytics/                    # Win/loss, competitors, lessons
β”‚   β”œβ”€β”€ onboarding/                   # Org creation + profile wizard
β”‚   β”œβ”€β”€ settings/                     # Profile, notifications, billing, team
β”‚   └── admin/                        # Platform metrics + ingestion control
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ui/                           # ShadCN primitives
β”‚   β”œβ”€β”€ tender/                       # ScoreMeter, FitScoreBadge, DeadlineCountdown
β”‚   β”œβ”€β”€ guest/                        # GuestBanner, GuestProfileDialog
β”‚   β”œβ”€β”€ notifications/                # Bell, drawer, items
β”‚   β”œβ”€β”€ onboarding/                   # Stepper, completeness bar
β”‚   β”œβ”€β”€ Header.tsx                    # Navigation
β”‚   β”œβ”€β”€ OnboardingGate.tsx            # Global onboarding enforcement
β”‚   └── CookieConsent.tsx             # GDPR banner
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ hooks/                        # useApiQuery, useBids, useBilling, etc.
β”‚   β”œβ”€β”€ utils/                        # Formatters (EUR, dates, urgency)
β”‚   β”œβ”€β”€ guestSession.ts               # Guest id + profile (localStorage)
β”‚   └── cookieConsent.ts              # Consent utility
β”œβ”€β”€ backend/src/                      # Hono.js API
β”‚   β”œβ”€β”€ app.ts                        # Route mounting + middleware
β”‚   β”œβ”€β”€ server.ts                     # Local dev server
β”‚   β”œβ”€β”€ loadEnv.ts                    # Env loading, imported before the app
β”‚   β”œβ”€β”€ agents/                       # LangGraph supervisor + agents + tools
β”‚   β”œβ”€β”€ routes/                       # tenders, bids, billing, analytics, …
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ db/                       # Supabase query layers
β”‚   β”‚   β”œβ”€β”€ scoring/                  # 6-component engine + compliance
β”‚   β”‚   β”œβ”€β”€ ingestion/                # TED, ANAC, Award connectors + runner
β”‚   β”‚   β”œβ”€β”€ graphrag/                 # Knowledge graph + hybrid retrieval
β”‚   β”‚   β”œβ”€β”€ guest.ts                  # Guest profile parsing + scoring adapter
β”‚   β”‚   β”œβ”€β”€ scoringContext.ts         # Resolves the profile to score against
β”‚   β”‚   β”œβ”€β”€ checkpointer.ts           # LangGraph Postgres checkpointer
β”‚   β”‚   └── observability/            # Metrics, logging, tracing
β”‚   β”œβ”€β”€ middleware/                   # auth, guest, rate limit, billing, PII
β”‚   β”œβ”€β”€ __tests__/                    # Vitest suites (incl. PGlite SQL tests)
β”‚   └── build.mjs                     # esbuild β†’ Vercel Build Output API
β”œβ”€β”€ supabase/migrations/
β”‚   β”œβ”€β”€ 001_initial_schema.sql        # Core tables
β”‚   β”œβ”€β”€ 002_tender_scores.sql         # Score cache
β”‚   β”œβ”€β”€ 003_bid_pipeline.sql          # Bids, assignments, comments, checklist
β”‚   β”œβ”€β”€ 004_bid_outcomes.sql          # Post-mortem outcomes
β”‚   β”œβ”€β”€ 005_tender_awards.sql         # Award data fields
β”‚   └── 006_usage_limits.sql          # Quota + rate limit counters
└── vercel.json                       # Rewrites + CORS headers

Design System

The interface is built around procurement as a precision instrument.

  • Type β€” Archivo (display), IBM Plex Sans (interface), IBM Plex Mono (CPV codes, protocol numbers, figures)
  • Palette β€” warm paper ground, deep teal primary (Verderame), brass for caution, graphite for inert
  • One rule β€” vermilion means "time is running out" and nothing else. A poor fit is graphite, never red, which is what makes an approaching deadline actually register
  • Score meter β€” segment widths are proportional to each component's maximum, so the bar shows both what you scored and how much each dimension counts

Tech Stack

Category Technology
Frontend Next.js 16, React 19, Tailwind CSS 4, TypeScript 6
UI ShadCN UI, Radix UI
State TanStack Query, dnd-kit
Auth Clerk v7 (JWT, orgs, RBAC)
Backend Hono.js on Vercel Serverless, TypeScript 7
AI LangGraph 1.4, LangChain, OpenRouter
Database Supabase Postgres
Vectors Pinecone
Billing Stripe
Testing Vitest, PGlite
Monitoring Sentry, Vercel Analytics
Deployment Vercel

API Endpoints

Tenders

Method Endpoint Description Guest
GET /tenders List tenders βœ…
GET /tenders/search Search with filters + fit scores βœ…
GET /tenders/best Best matches for the profile βœ…
GET /tenders/:id Tender detail βœ…
GET /tenders/:id/analysis Scoring + eligibility βœ…
POST /tenders/semantic Vector search βœ…
POST /tenders/graphrag Hybrid graph retrieval βœ…
POST /tenders/favorite Toggle favorite ❌

Bids

Method Endpoint Description
GET /bids List bids (with status filter)
POST /bids Create bid from tender
PATCH /bids/:id Update bid (status, priority)
GET/POST /bids/:id/outcome Record bid outcome
GET/POST /bids/:id/comments Threaded comments
GET/PATCH /bids/:id/checklist Compliance checklist

Analytics

Method Endpoint Description
GET /analytics/outcomes Win/loss stats, segments, competitors
GET /analytics/kpis Dashboard KPIs
GET /analytics/market-competitors TED award-based competitors

Billing

Method Endpoint Description
GET /billing Current plan
GET /billing/plans Available plans
POST /billing/checkout Stripe checkout session
POST /billing/portal Stripe customer portal

Other

Method Endpoint Description
POST /agent/stream AI chat (SSE)
GET/POST /company Company profile
POST /ingestion/run Trigger ingestion (admin or cron secret)
POST /webhooks/clerk Clerk webhook
POST /webhooks/stripe Stripe webhook
GET /health Β· /metrics Health and metrics

Deployment

Both projects deploy to Vercel from this repository, and both watch main β€” a single push triggers two production deployments.

Project Root Notes
Frontend repo root Next.js preset
API backend/ framework: null β€” build.mjs emits a Build Output API bundle, so no framework preset should build on top of it

Before deploying the API, confirm:

  1. CRON_SECRET is set, and the pg_cron ingestion jobs send it as Authorization: Bearer …
  2. DATABASE_URL is set to the Postgres pooler URL
  3. Migrations through 006 are applied
  4. CLERK_SECRET_KEY and SUPABASE_URL exist in Preview as well as Production, or preview deployments will reject every authenticated request

License

MIT License β€” see LICENSE.


Built for the Italian and EU public procurement community

About

AI-powered discovery and bid management for Italian and EU public tenders. Explainable 0-100 fit scoring, TED + ANAC ingestion, Kanban bid pipeline.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages