Open-source procurement and B2B sourcing intelligence engine.
OpenTradeIntel queries real EU public procurement notices from the official TED Search API, normalizes them into typed tenders, and ranks them against a local supplier catalog. It also accepts local JSON/CSV tender data. The project is an early-stage, self-hosted toolkit for SMEs, exporters, sourcing and procurement teams, researchers, developers, and trade-intelligence projects.
Procurement requirements and catalogs often arrive in incompatible spreadsheets or data exports. OpenTradeIntel provides a small, inspectable baseline for validating that data, normalizing common terms, and explaining why one catalog product ranks above another—without sending commercial data to an external AI service.
- Typed tender/RFQ and product models using Pydantic.
- JSON and CSV ingestion behind extensible connector/parser interfaces.
- Official public TED v3 Search API connector with no API key or scraper.
- TED keyword, CPV, country, page-number, and iteration-token search support.
- Direct
TED -> normalized tender -> local catalog -> ranked matchesworkflow. - Local text, unit, category, keyword, and market normalization.
- Deterministic 0–100 matching with a complete component breakdown.
- Typer CLI and FastAPI API backed by the same application service.
- Dependency-free MCP adapter and optional provider protocol.
- Synthetic examples, tests, Docker packaging, and OSS project files.
- No API key, LLM, database, queue, or network call required by core features.
Install the latest release from PyPI:
pip install opentradeintelOr add it to a uv-managed project:
uv add opentradeintelVerify the installed CLI:
opentradeintel version
opentradeintel --helpSearch active public notices through the official TED API:
uv run opentradeintel ted search \
--query "dried fruit" \
--limit 10 \
--output jsonSearch by CPV/place and save normalized tenders:
uv run opentradeintel ted search \
--cpv 15897200 \
--country DE \
--limit 20 \
--output-file tenders.jsonMatch the returned notices directly against a local catalog:
uv run opentradeintel ted match \
--query "dried fruit" \
--catalog examples/catalogs/sample.csv \
--match-limit 3ted match returns at most 10 catalog matches per notice by default; use --match-limit to choose a different positive bound.
TED search requires outbound HTTPS access but no credential. Local file matching and the deterministic core remain fully offline.
Requirements: Python 3.12 or newer and uv.
git clone https://github.com/b2baitrade-ui/opentradeintel.git
cd opentradeintel
uv sync --all-groups --locked
uv run opentradeintel --helpInspect the synthetic tender:
uv run opentradeintel inspect examples/tenders/sample.jsonMatch it against the synthetic catalog:
uv run opentradeintel match \
--tender examples/tenders/sample.json \
--catalog examples/catalogs/sample.csvExample excerpt (values are computed, not hard-coded):
1. Dried Mango 500g
Score: 100/100
Reasons:
[+] Product similarity: 40/40 (name 24/24, keywords 16/16)
[+] Category: 15/15 (dried fruit)
[+] Certifications: 20/20 (2/2 covered)
[+] Market compatibility: 15/15
[+] MOQ compatibility: 10/10
Supplier, tender, and benchmark examples are synthetic. examples/ted/ is a clearly attributed, trimmed snapshot of public TED procurement data.
Start the API:
uv run uvicorn opentradeintel.api.app:app --reloadAvailable routes:
GET /healthGET /versionPOST /match- Interactive OpenAPI documentation at
/docs
Minimal request:
curl -X POST http://127.0.0.1:8000/match \
-H "Content-Type: application/json" \
-d '{
"tender": {
"id": "demo-rfq", "title": "Dried mango", "buyer": "Demo Buyer",
"description": "Organic dried fruit", "products": ["mango"],
"quantity": 1000, "unit": "kg", "destination": "Germany",
"currency": "EUR", "required_certifications": ["EU Organic"],
"source": "synthetic-demo"
},
"products": [{
"sku": "DM-1", "name": "Dried mango", "description": "Organic slices",
"category": "Dried fruit", "origin": "Exampleland",
"certifications": ["EU Organic"], "min_order_quantity": 100,
"available_markets": ["EU"], "keywords": ["mango", "organic"]
}]
}'The score is the integer sum of five deterministic components:
| Component | Maximum | Baseline behavior |
|---|---|---|
| Product similarity | 40 | Product-name token coverage (24) plus keyword coverage (16) |
| Category | 15 | Normalized category tokens appear in the tender |
| Certifications | 20 | Proportional coverage of required certifications |
| Market | 15 | Destination and available-market aliases intersect |
| MOQ | 10 | Tender quantity meets product minimum order quantity |
Missing quantity or MOQ receives five neutral points and a warning. Results include reasons, warnings, and the component breakdown. Exact CPV overlap is a score-neutral tie-break signal; SKU is the final stable tie-breaker. See matching documentation and the 20-opportunity benchmark.
local JSON/CSV -> connector -> parser -------\
-> typed Tender -> OpportunityService -> matcher
official TED -> search client -> TED mapper -/ -> CLI / FastAPI / MCP adapter
The boundaries are intentionally small. New source connectors acquire text; parsers interpret formats; interfaces never reimplement matching. See architecture and connector guidance.
OpenTradeIntel does not require procurement or supplier data to be sent to an external AI API. The default workflow is local, deterministic, and suitable for self-hosting. Users remain responsible for access control, retention, applicable procurement rules, and the accuracy of final commercial decisions.
AI is optional. The EnrichmentProvider protocol is a future extension point; no provider is installed or called by core. .env.example contains a blank optional key only to document that future integration. The dependency-free OpenTradeIntelMCPAdapter remains an interface only; registering it with a concrete MCP SDK is deferred.
docker compose up --build
curl http://127.0.0.1:8000/healthThe runtime image uses Python 3.12, runs as a non-root user, and exposes port 8000.
uv sync --all-groups --locked
uv run ruff check .
uv run ruff format --check .
uv run mypy src tests benchmarks examples
uv run pytest
uv run python benchmarks/run.pyOn systems with GNU Make, make check runs the same gates. See development documentation.
Version 0.2 adds the official TED public connector and a measurable matcher baseline. PDF/XLSX parsing, additional reviewed public connectors, evaluated semantic methods, and a concrete MCP server remain future candidates. See the honest, non-binding roadmap.
Issues and pull requests are welcome. Read CONTRIBUTING.md before adding a parser or connector, and follow the Code of Conduct.
Do not open a public issue for a vulnerability. Follow the private reporting instructions in SECURITY.md.
Licensed under the Apache License 2.0.