Skip to main content

OpenTradeIntel

Open-source procurement and B2B sourcing intelligence engine.

CI Python License

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.

Why?

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.

Features

  • 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 matches workflow.
  • 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.

TED in 30 seconds

Search active public notices through the official TED API:

uv run opentradeintel ted search \
  --query "dried fruit" \
  --limit 10 \
  --output json

Search by CPV/place and save normalized tenders:

uv run opentradeintel ted search \
  --cpv 15897200 \
  --country DE \
  --limit 20 \
  --output-file tenders.json

Match the returned notices directly against a local catalog:

uv run opentradeintel ted match \
  --query "dried fruit" \
  --catalog examples/catalogs/sample.csv \
  --match-limit 3

ted 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.

Quick start

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 --help

Inspect the synthetic tender:

uv run opentradeintel inspect examples/tenders/sample.json

Match it against the synthetic catalog:

uv run opentradeintel match \
  --tender examples/tenders/sample.json \
  --catalog examples/catalogs/sample.csv

Example 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.

API

Start the API:

uv run uvicorn opentradeintel.api.app:app --reload

Available routes:

  • GET /health
  • GET /version
  • POST /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"]
    }]
  }'

How matching works

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.

Architecture

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.

Data privacy

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 providers and MCP

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

docker compose up --build
curl http://127.0.0.1:8000/health

The runtime image uses Python 3.12, runs as a non-root user, and exposes port 8000.

Developing

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.py

On systems with GNU Make, make check runs the same gates. See development documentation.

Roadmap

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.

Contributing

Issues and pull requests are welcome. Read CONTRIBUTING.md before adding a parser or connector, and follow the Code of Conduct.

Security

Do not open a public issue for a vulnerability. Follow the private reporting instructions in SECURITY.md.

License

Licensed under the Apache License 2.0.

Metadata

Release files for opentradeintel 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opentradeintel 0.2.0
File Size Uploaded
opentradeintel-0.2.0.tar.gz 136.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for opentradeintel 0.2.0
File Interpreter ABI Platform
opentradeintel-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 171.8 kB

Release files / opentradeintel-0.2.0.tar.gz

Download URL opentradeintel-0.2.0.tar.gz
Size 136.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a1267d6be88b6f6c79f89e1d63ebadb43874f4367f98f0235713ef314b3805bc
BLAKE2b-256 checksum
How to use checksums
88b0d8ead624b8a82879e3d781bc9517a0d7544d5885ad621bc26d67860f3d0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release files / opentradeintel-0.2.0-py3-none-any.whl

Download URL opentradeintel-0.2.0-py3-none-any.whl
Size 35.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e5a46dbb699fcd76338b35548bdb2d72d86e5cb9a1ad7c065184d0e7149588dc
BLAKE2b-256 checksum
How to use checksums
2681dda08426edc6a088fe98db2d2e75b81053b6d8059c5d73943fc88003d1d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page