Ctrl K
ComunidadGuías…

docs/global-procurement-intelligence.mdx

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
title: "Global Procurement Intelligence"
description: "How WorldMonitor aggregates active public tender opportunities, communicates source health, and exposes the paginated procurement API."
---

WorldMonitor separates **open opportunities** from historical **recent awards**. Open opportunities live in the dedicated **Global Procurement** panel as a forward-looking, seed-backed feed; awards remain sourced from USASpending in the Economic panel.

Global Procurement is a **Pro feature**. The panel shows the standard sign-in or upgrade gate to free users, and the RPC independently enforces an active tier-1 subscription server-side. The browser only requests and retains tender data while Pro access is active; sign-out or downgrade clears both the panel payload and its in-memory response cache. Recent awards remain part of the free Economic panel behavior.

Pro users can search titles and descriptions, filter by buyer, two-letter country code, and source, choose a supported sort, and page through the complete result set. The panel never reads Upstash directly: it calls the paginated RPC, which reads the canonical seeded snapshot.

## Coverage and freshness

The adapters read official public procurement sources:

| Source | Coverage | Notes |
| --- | --- | --- |
| SAM.gov | United States | Requires the deployment's `SAM_GOV_API_KEY`; an unconfigured adapter is reported as unavailable, not as zero results. |
| TED | European Union | Published public procurement notices from the public TED Search API. |
| Contracts Finder | United Kingdom | Published OCDS tender releases. |
| CanadaBuys | Canada | Official federal open-tender CSV, refreshed daily by CanadaBuys. |
| GETS | New Zealand / Oceania | Current open tenders from the official public GETS RSS feed. |
| World Bank Procurement Notices | Multilateral / global | World Bank-financed opportunity notices across borrower countries. |
| Colombia SECOP II (`colombia-secop`) | Colombia | Up to 100 distinct published processes with a future response deadline. Uses the processes dataset, not awarded contracts. |
| Kenya PPIP (`kenya-ppip`) | Kenya | One active-tenders page, up to 100 records. No in-run retries, including HTTP 429. Staff, contact, team and bidder objects are not copied. |
| South Africa eTenders (`south-africa-etenders`) | South Africa | Up to 20 OCDS releases from the previous day through today. |
| Chile Mercado Público (`chile-mercado-publico`) | Chile | A bounded sample of 20 current-month OCDS entries, with official tender-detail reads. |
| Brazil PNCP (`brazil-pncp`) | Brazil | Up to 10 active proposals with a closing date within 30 days, across procurement modalities. |
| Ukraine Prozorro (`ukraine-prozorro`) | Ukraine | Details for up to 20 recently modified tenders. Only `active.tendering` is accepted; qualification and award stages are excluded. |
| Bulgaria EOP (`bulgaria-eop`) | Bulgaria | Previous day's official OCDS archive. Each lot keeps its own closing date and value. |
| Latvia IUB (`latvia-iub`) | Latvia | Previous day's JSON file. Competition lots with a closing date, closing time and permitted official procurement link. |
| Poland BZP (`poland-bzp`) | Poland | Up to 100 current contract notices from the previous day through today; outdated notices are excluded. |
| Pakistan PPRA (`pakistan-ppra`) | Pakistan | One public active-tenders HTML page, up to 100 rows. Each accepted row needs a published status, official detail link, closing date and time. |
| Pakistan EPADS (`pakistan-epads`) | Pakistan | Up to 20 federal procurement notices. Exact local timestamps come from the list; countdown rows use the official detail page’s Unix deadline. |
| Georgia SPA (`georgia-spa`) | Georgia | First public announced-tenders page, up to 20 detail reads. Only announced or bidding-commenced notices with an exact closing time are accepted. |
| Armenia ARMEPS (`armenia-armeps`) | Armenia | Up to 10 details from the tender-submission search. Awarded notices are excluded; missing estimated values stay absent. |
| Albania APP (`albania-app`) | Albania | The current year's official published-procedures CSV export, regenerated daily (the previous year is also read from January through March, while its late-year procedures can still be open). Cancelled, suspended and awarded procedures and rows without a closing date are excluded. APP has no public per-procedure page, so notices link to the official export page. |
| Dominican Republic DGCP (`dominican-republic-dgcp`) | Dominican Republic | Up to two pages of 200 processes from the open-data API, newest first. Only `Proceso publicado` processes with a closing timestamp and an official `comprasdominicana.gob.do` link are accepted. |
| Malaysia MyProcurement (`malaysia-myprocurement`) | Malaysia | One JSON page of up to 100 current federal tender adverts. Each card needs an official detail link, agency, invitation date and closing date; `Tiada Maklumat` prices stay absent. |

These are samples of upstream catalogues, not exhaustive national coverage. Each added source has a 75-second request budget, no in-run retries and at most four concurrent detail reads. Most requests have a 15-second timeout; PNCP has 30 seconds, the ARMEPS search has up to 50 seconds and the Albania CSV export (about 7.6 MB by November, served at roughly 160 KB/s; two concurrent year exports measured 60 seconds) has up to 70 seconds within the shared budget. The hourly Railway schedule limits Kenya to one request per run. Bulgaria and Latvia use the previous day's archive to allow publication time. Local closing times are converted using the publisher's time zone. Missing closing dates or times are not inferred. Albania and Malaysia publish closing dates without a time; as with TED's date-only deadlines, those notices are kept at the start of the closing day in the publisher's time zone and leave the open feed at local midnight before their real closing hour, never after it. Dominican Republic timestamps are used as the API publishes them, with a UTC suffix. Raw staff, contact-point, supplier and bidder objects are never stored in the canonical snapshot.

The added sources use the same source, country, buyer, category, value, date and technology-relevance filters in the panel, RPC and MCP tool. No new credentials are required.

Georgia and Armenia use anonymous publisher sessions whose cookies remain in memory for that fetch. Pakistan PPRA and EPADS notices with the same nonempty normalized buyer and title and the same exact deadline are deduplicated only when exactly one notice from each source matches after request filters and before pagination. Source-specific queries still return that source’s notices; canonical records and source health counts retain their original provenance. Ambiguous groups, different deadlines and missing buyers or deadlines are retained. Same-origin session redirects are followed explicitly, up to three hops; other origins are rejected before any request is sent. EPADS does not follow redirects.

Each added source writes a durable `seed-activated:economic:global-tenders:<source>` marker after its first successful canonical publication and source-status writes, including a valid empty result. Before that publication, a missing key reports `EMPTY_ON_DEMAND`. After activation, a missing or stale key is a health fault even after the source metadata expires. Failed attempts never activate a source.

The Railway seeder runs hourly and stores a canonical snapshot at `economic:global-tenders:v1`. Its TTL is three hours so the prior successful snapshot survives a missed run. If one adapter fails, healthy-source results remain available, that source's last-good records are retained, and the response reports `availability: "partial"`. If every adapter fails, the previous records remain available with `availability: "stale"`.

`availability` has five meanings:

- `available`: all configured adapters succeeded and returned one or more notices.
- `empty`: configured adapters succeeded but returned no notices.
- `partial`: at least one adapter is unavailable or failed while another succeeded.
- `stale`: all current source attempts failed, so retained last-good records are being served.
- `unavailable`: no fresh canonical snapshot is available.

Per-source state, record counts, retrieval-attempt time, last-success time, and stale flag are included in the feed response. Every per-source seed key is independently registered in `/api/health`. Unsupported country filters are never treated as a confirmed zero-result source.

World Bank requests cover a sample of up to 100 notices, not the full upstream catalogue. Each request can make three attempts, including response-body reading, with a 20-second attempt timeout and 5- and 10-second retry delays. All attempts and `Retry-After` waits share a 75-second budget. A failed body read can retry; missing or malformed notices and incomplete declared result windows cannot become an empty success.

World Bank, Contracts Finder and the sixteen added sources retain usable open notices only within 24 hours of the source's last success. Failed attempts and successful aggregate publications do not renew that source clock. A valid empty response clears prior records; a failed request remains a source error, including when the last successful result was empty. If the seeder cannot read the previous canonical snapshot, it preserves the existing publication instead of treating the read failure as missing data. Aggregate completion is not proof that every source succeeded; failed sources remain visible in health and are attempted on the normal hourly cadence.

### Remaining sources from issue #9840

The [source proposal](https://github.com/koala73/worldmonitor/issues/9840) includes archives and portals that need separate adapters. They are not advertised as covered, and country filters for unobserved countries remain `unknown`. Read-only checks on 2026-10-29 and 2026-11-01 found the following limits; these are single-run observations, not long-term availability claims.

| Source | Current limit |
| --- | --- |
| Lithuania CVP IS | The linked API instructions returned HTTP 403. The POST schema and published public-key mechanism still need verification. |
| Moldova MTender | The linked `/tenders/` endpoint returned an empty HTTP 200 body; the attempted `/api/0.2/tenders` route returned 404. A current list/detail contract is required. |
| Romania SEAP | The linked quarterly XLSX/ODS exports describe purchases and awards. An open-opportunity interface with future closing dates is required. |
| Czech contracts register | XML contract dumps describe executed contracts, not invitations to bid. Keep them separate from open opportunities. |
| Turkey EKAP | The search now returns an application shell; a usable current-tender request contract remains unverified. |
| Peru OECE | The linked monthly-file API returned HTTP 403 with a browser User-Agent. |
| Uzbekistan xarid plans | Planned purchases must not be classified as open tenders. A separate planning data contract is needed. |
| Uzbekistan UzEx e-tender | The application publishes a `TradeList` POST route, but bounded test requests returned no usable body. No active-tender payload was verified. |
| Qatar Monaqasat | Public cards were reachable, but linked notice details redirected to sign-in; exact closing times were not verified. |
| Oman Tender Board | Public list HTML was reachable. Detail requests returned a session/authorization error; a permitted stable detail link and exact closing time remain unverified. |
| Bahrain Tender Board | Public HTML was reachable, but its signed list/detail requests and exact closing-time response remain unverified. |
| Kuwait CAPT | The linked endpoint returned HTTP 403. |
| Jordan JONEPS | Public list rows were reachable, but the detail route returned an empty form. Its form-submission contract and exact closing time remain unverified. |

### Not implemented from issue #10052

| Source | Current limit |
| --- | --- |
| Vietnam e-GP muasamcong | The contractor-selection search (`services/smart/search`) is called with a reCAPTCHA v3 token minted in the browser. A keyless seeder would have to bypass that bot check, so the portal is not read. Checked 2026-11-02. |
| Mexico Postor / ComprasMX | The third-party index exposes NDJSON bulk lots. A bounded current-opportunity import with official notice provenance is still required. |
| Nigeria NOCOPO via OCP | Third-party annual/all-time JSONL archives need a bounded import and official notice-link verification. |
| Bangkok BMA via OCP | Third-party annual/all-time JSONL archives need a bounded import and official notice-link verification. |
| OCP Data Registry | A catalogue of datasets, not one independent national opportunity feed. Individual publishers need their own adapters. |

TED and World Bank were already integrated. Supplier debarment lists are not tender opportunities and are not imported here. Weekly map aggregates are outside the panel/API extension.

## Natural run receipts

The private Redis list `economic:global-tenders:run-history:v1` preserves the latest 72 completed fetch attempts. The key expires seven days after its last update. It is separate from the public tender snapshot and does not change source retention or health thresholds.

Each receipt has a unique `runId`, start and completion times, and per-source outcomes. A source entry separates newly fetched records from retained records and preserves its last successful fetch time. SAM runs inside the existing request-pacing interval use the `paced` outcome and count previous records as retained. Each source records up to 32 HTTP attempts, with a total request count and a truncation flag. Request details include the host, timeout budget, HTTP status, and failure phase (`headers` or `body`). Successful body reads use `complete`. Failure codes exclude raw error messages. Receipts contain no query strings, headers, response bodies, or tender records.

Bulgaria receipts include the requested bucket date, attempted dates, the HTTP status that caused fallback, and the selected bucket date. `selectedDate` remains null unless the selected file parses and normalizes successfully. These dates describe the export window, not a new freshness policy.

Publication starts as `unconfirmed`. The seeder changes it to `published` only after the canonical snapshot, source status, source metadata, and aggregate freshness writes succeed. The confirmation includes `publishedAt` and the seed runner's `seedRunId`. A failed validation or publication leaves the receipt unconfirmed. An unconfirmed receipt does not identify which write failed. Use the runner logs for that cause.

Operators can read the newest receipts with `LRANGE economic:global-tenders:run-history:v1 0 9` through the existing authorized Redis tooling. Search runner logs for `Procurement receipt:` to correlate a receipt with its natural execution. Compare `outcome`, `fetchedCount`, `retainedCount`, request failures, and bucket dates before changing a provider adapter.

The history records attempts that finish the source fetch phase. A process exit during fetch or a failed canonical-cache read can prevent a receipt. Redis write failures also prevent receipt persistence. The seeder stops publication when its required receipt write fails and does not repeat provider requests to retry that write. After publication, the existing bundle completion marker remains the separate attestation that the runner completed its final required write.

## Deployment configuration

Only the U.S. SAM.gov adapter needs a new credential:

1. Sign in to [SAM.gov](https://sam.gov/), open **Account Details**, and enter your account password to view the API-key controls.
2. Request a **Public API Key**. SAM.gov displays the generated key on the Account Details page.
3. Add it to the Railway service running `node scripts/seed-bundle-relay-backup.mjs` as `SAM_GOV_API_KEY`. Do not expose it through a `VITE_` variable or commit it to the repository.

The official setup and request-limit details are in the [GSA Get Opportunities Public API guide](https://open.gsa.gov/api/get-opportunities-public-api/). TED, Contracts Finder, CanadaBuys, GETS, and World Bank do not require API keys. If `SAM_GOV_API_KEY` is absent, only the SAM source reports `unavailable`; the other adapters continue normally.

Leaving SAM unconfigured is a supported state, not a degradation: `/api/health` reports that source as [`NOT_CONFIGURED`](/health-endpoints) (green, excluded from `problems`) rather than as an error, so a deployment that never obtains a GSA key still reports a clean health endpoint. Adding the key flips it to `OK` on the next seed run with no health-config change.

### Australia: AusTender adapter is blocked

An official Australian `austender` adapter cannot ship safely today, because no permitted machine-readable AusTender interface publishes the closing date that an open-opportunity record requires. Evidence, current as of 2026-07-13:

- The official AusTender current-ATM RSS feed (`https://www.tenders.gov.au/public_data/rss/rss.xml`, registered on [data.gov.au](https://data.gov.au/data/dataset/latest-approaches-to-markets-listed-on-austender) by the Department of Finance) publishes only a notice title, official link, free-text description, and publish date. It carries no closing date, buyer, or category fields.
- The official [AusTender OCDS API](https://github.com/austender/austender-ocds-api) exposes awarded contract notices from 2013 onward, not open Approaches to Market.
- data.gov.au's machine-readable open-ATM exports ended in June 2014.
- Closing dates are published only on per-notice HTML pages; harvesting them would mean scraping the AusTender website, which is not a documented machine-readable reuse interface and is excluded by this feed's non-goals.

This feed never represents a record without a verifiable future closing date, so Australian coverage remains absent rather than inferred, and New Zealand GETS coverage is not substituted for it. Filtering on `country=AU` keeps the standard `countryCoverage: "unknown"` semantics for unobserved countries. If AusTender begins publishing closing dates through a machine-readable channel, the adapter can ship without pipeline changes.

## API

`GET /api/economic/v1/list-global-tenders` is Pro-gated, deliberately paginated, and is **not** part of the bootstrap payload. It supports `country`, repeated `countries`, `region`, `source`, `status`, `buyer`, `published_from`, `published_to`, `deadline_from`, `deadline_to`, `min_value`, `max_value`, `currency`, `category`, `query`, `min_automation_score`, `page_size`, `cursor`, and `sort`.

Supported sorts are `newest`, `closing_soon`, `estimated_value`, and `relevance`. `page_size` is bounded to 100. The response includes applied filters, a cursor, snapshot time, availability, and source-health summaries.

Every record contains a stable WorldMonitor ID (`source:sourceNoticeId`), its official notice URL, source notice ID, typed money fields when supplied, source timestamps, category data, and a provenance-preserving source name. Missing upstream fields remain absent rather than being inferred.

## MCP tool

`get_procurement_opportunities` exposes this same canonical route to authenticated MCP clients; it never reads `economic:global-tenders:v1` directly. Its intentional context budget is **10 compact opportunities by default, 25 maximum**. It exposes `country`, repeated `countries`, `source`, `query`, `buyer`, `deadline_from`, `deadline_to`, `sort`, `min_automation_score`, `page_size`, and `cursor`.

The compact projection omits description text, eligibility requirements, and submission URLs while retaining each opportunity's official URL, source, title, buyer, dates, money, categories, sectors, `participationMode`, and compact `automationFit`. It also retains cursor pagination, `total`, `appliedFilters`, `countryCoverage`, availability, snapshot time, and source-health summaries. The 25-record MCP maximum is deliberately tighter than the REST route's 100-record maximum.

The MCP tool is Pro-gated exactly like the HTTP route and is not part of bootstrap. An unfiltered tool call preserves all open opportunities; `min_automation_score` remains opt-in. See the [MCP tool reference](/mcp-tools/trade#get_procurement_opportunities) for the complete parameter contract.

## WebMCP browser tool

The fresh full, tech, and finance layouts include the Global Procurement panel. Because panels are cross-addressable, an entitled user can also enable it on another variant. The connected, visible, data-ready, and idle panel can expose the declarative WebMCP tool `search_procurement`; its optional inputs come from the human form: `query` and `buyer` text of at most 160 characters each, `country` of exactly two ASCII letters (`^[A-Za-z]{2}$`), `source`, `sort`, and the boolean `techRelevant`.

Once an agent invocation starts, the exact form and its declaring attributes remain mounted until the browser-owned response promise settles; a concurrent invocation receives the retryable `request_in_progress` outcome. Hiding, locking, or destroying the panel makes the tool unavailable, while ordinary loading without an active agent invocation removes the declaration.

An invocation visibly activates and submits the same form, follows the canonical request path, and returns only a bounded summary of matches, coverage, applied filters, availability, and source status. Reset/cancel aborts pending work. It does not expose hidden tender content or bypass Pro entitlement. See [WebMCP](/webmcp#declarative-procurement-tool) for the exact browser schema and lifecycle.

## Technology relevance

`automationFit` is a transparent keyword-based relevance signal for software, AI, data, cybersecurity, automation, cloud, and related terms. It contains a score, classification version, match reasons, and text evidence. It is **not** a legal or procurement-eligibility determination: `participationMode` remains `unknown` unless an upstream source explicitly supports it.

The optional `min_automation_score` query parameter (an integer from 1–100; larger values are clamped and non-integer values are ignored) returns only opportunities whose `automationFit.score` meets the threshold. It is disabled by default, so existing callers keep receiving every matching open opportunity, and when active it appears in the response's `appliedFilters`. The dedicated panel exposes it as a **Technology relevant only** checkbox. Both the filter and the per-record match reasons express keyword relevance evidence only — they never assert that an AI system, agent, or vendor is eligible to bid.

The dashboard renders official notice links only after URL sanitization and exposes the relevance reason alongside the source-provided opportunity details.