Public procurement notices from the official TED (EU), Find a Tender and Contracts Finder (UK) APIs in one schema: title, buyer, CPV, value, deadline, winners. Dedupe, CPV/country/value filters. Ausschreibungen, appels d'offres, licitaciones, gare d'appalto, przetargi, aanbestedingen. Unofficial.
Unreleased: smaller default and line breaks - 2026-11-02 (local, not built or pushed; actor.json version unchanged: 0.2)
Smaller default:maxItems input-schema default and prefill, the code fallback (DEFAULT_MAX_ITEMS in
my_actor/main.py, used by parse_input) and actor.jsonexampleRunInput.maxItems are now 4 (were 5). {}, the
prefill and the example input save at most 4 notices = at most 4 x $0.002 = $0.008, under the $0.01 Store test limit
with margin. The maximum (50,000) is unchanged. Schema description: "Default 4 (at most $0.008)".
Line breaks in descriptions:description text now has CRLF (\r\n) and lone CR (\r) line breaks turned into
\n before trimming (_trim in my_actor/sources.py). Contracts Finder sent CRLF; TED and Find a Tender text goes
through the same function. Nothing else in the text changes; titles are not touched.
Docs: README "Price" paragraph (4 notices, $0.008) and pricing.jsonestimatedCostExamples.defaultDailyRun.
Tests: tests/test_input.py::test_defaults and tests/test_default_cost.py expect 4 (the README check now also looks
for "at most $0.008"); new tests/test_sources.py::test_description_crlf_normalised.
Unreleased: free status row when nothing new or nothing matches - 2026-11-01 (actor.json version unchanged: 0.2)
A cloud {} run ended with 0 items and an empty dataset (saved state plus a weekend window: TED and Contracts Finder
had 0 notices; Find a Tender had 2 already delivered and 3 cut by the default stage filter). Now such a run writes one
free row (recordType: "status", status: "warning", pushed with no charged event):
"Not charged: no new items since last run. ..." when notices were skipped as already delivered with this
stateKey (same shape as grants/usaspending push_no_new_status; fields skippedSeen, stateKey, filteredBy);
"Not charged: no matching notices in the selected window (...)" otherwise, naming the filters that cut notices,
e.g. "3 by stages (tender, the default)" (fields read, stages, filteredBy, from, to).
Neither fires when a notice was saved, when a source failed (the network-down row is unchanged and still takes
precedence) or when the cost cap stopped the run. RUN_SUMMARY gains filteredBy (count per filter).
my_actor/main.py: cut_reason() (passes() kept as a wrapper), push_no_new_status(),
push_no_match_status(), parse_input() returns stagesDefault. Tests: tests/test_empty_window_status.py (9).
README FAQ line added.
my_actor/sources.py: the two country-name words left over from the removed source (the country and its adjective) were dropped from the organisation-word regex (ORG_RE) used by the supplier-name privacy rule; a supplier name whose only organisation word was one of those is now withheld as a possible individual. NOTES.md: the removed source's research notes, dedupe notes and open items were cut. Tests unchanged, 136 passed.
Unreleased: cost wording, canonical folder, titleTranslated doc - 2026-10-31 (actor.json version unchanged: 0.2; no code or pricing change)
Comments and docs no longer say a $0.00005 platform start event is charged: pricing has only tender-notice $0.002
and no start event. {} cost math is per-item only: 5 x $0.002 = $0.010; a start event would apply only if one were
configured. Files: my_actor/main.py (comment above DEFAULT_MAX_ITEMS), pricing.json (defaultDailyRun note),
tests/test_default_cost.py (docstring; no start event added, limit $0.010), and the wording of the older
"{} cost fix" entry below. Pricing logic, defaults and charging are unchanged.
This folder is the canonical source for the eu-uk-public-tenders Actor. The old ../eu-uk-public-tenders/ folder
is kept for reference only and now carries a STALE.md saying it is superseded and must not be pushed.
README.md: titleTranslated is described accurately. TED's English notice-title is " – –
" where only the country and subject are translated; the title part stays in the original language (checked
live on notice 749146-2026: Polish title). A `titleEnglish` field was therefore not added: stripping the prefix
would not give English text.
Unreleased: free status row when the source is down - 2026-10-31 (actor.json version unchanged: 0.2)
Added: one free recordType: "status" row when nothing could be read
A local run with the network down used to finish SUCCEEDED with 0 items and nothing in the dataset, so the user saw an
empty result with no explanation. Now, when a source cannot be reached or keeps answering with errors (robots.txt
unreadable, DNS/connection failure, repeated 5xx, 403, a non-JSON answer) and 0 data rows were saved, the run pushes one
row {recordType: "status", status: "error" or "warning", message: "Not charged: ..."} with no charged event (only the
free default dataset-item event), logs a warning, mentions it in the status message and sets statusRow: true in
RUN_SUMMARY. The run still exits 0 / SUCCEEDED. Runs that saved data, and runs that simply found nothing new, are unchanged.
Files changed:
my_actor/main.py: new status_row() and push_status_if_empty(), called at the end of run(); the early-stop
handler also catches httpx.HTTPError and ValueError (non-JSON answer) so they stop cleanly instead of crashing.
README.md: new "Source down" FAQ line.
tests/test_netdown_status.py: new (DNS down, HTTP 500, non-JSON; helper is quiet when data was saved or nothing failed; some sources down = warning).
CHANGELOG.md: the older entries no longer name the removed parked source (wording only; no code change).
0.2-noaus, {} cost fix - 2026-10-31 (actor.json version unchanged: 0.2)
Default maxItems 100 -> 5 (code DEFAULT_MAX_ITEMS, input-schema default and prefill, and a new actor.jsonexampleRunInput = the prefill with maxItems 5). Ranger's cloud {} run of build 0.2.1 stopped at the $0.02
max-charge cap (10 notices x $0.002). {} now costs at most 5 x $0.002 = $0.010 (per-item events only; a start event
would apply only if one is configured). README Price line, pricing.json cost example and tests updated (new tests/test_default_cost.py).
0.2-noaus (local fallback, not built or pushed) - 2026-10-29
Variant of 0.2 with a parked fourth source removed entirely (no source, code path, tests or fixtures), because its
terms could not be verified. Sources: ted, find_a_tender, contracts_finder.
Kept from 0.2: sourceCountry (EU / GBR), unspscCodes (always empty here, stable schema), the source-independent OCDS
dedupe key, "Not read" status-message logic, and supplier-name withholding for Find a Tender and Contracts Finder
(legal forms such as Ltd, LLP, plc, Inc, & Co, Partners always kept; "x t/a y" shows only the trade name).
Any source name other than ted, find_a_tender or contracts_finder fails with "Unknown source(s)".
The 0.2 entry below is trimmed to what applies to this variant.
0.2 (local, not built or pushed) - 2026-10-29
New fields on every record: sourceCountry (EU / GBR) and unspscCodes (empty for these sources; stable schema).
Supplier names that may be individuals are withheld ([withheld: possible individual]); contact-point names are never output.
Within-run dedupe now covers all OCDS sources with one source-independent key (ocds:ocid:<OCID>:<stage>, fallback notice ID).
Status message separates "Not read" sources from sources that stopped early.
CanadaBuys (robots.txt disallows all) and NZ GETS (terms forbid robots and paid republication) were evaluated and not added.
Kept from 0.1.2: default maxItems 100, .actorignore, input prefills.