# Changelog of EU & UK Tender Scraper: TED, Find a Tender, Contracts Finder (`fecklesspleb/eu-uk-public-tenders`) Actor

- **URL**: https://apify.com/fecklesspleb/eu-uk-public-tenders/changelog.md
- **Full Actor documentation**: https://apify.com/fecklesspleb/eu-uk-public-tenders.md

## Changelog

### 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.json` `exampleRunInput.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.json` `estimatedCostExamples.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.

### Unreleased: removed leftover parked-source wording - 2026-10-31 (actor.json version unchanged: 0.2)

- `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 "<Country> – <CPV subject> –
  <title>" 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.json`
  `exampleRunInput` = 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.

### 0.1.2 (private build by Ranger)

- Default `maxItems` 100, `.actorignore`, input prefills (another worker).

### 0.1 - 2026-10-28

- TED, Find a Tender and Contracts Finder in one schema; UK dedupe by OCID; Retry-After handling; amountGross values.
