# Changelog of Football Contract Expiry Radar - Transfermarkt (`datagrit/football-contract-expiry-radar`) Actor

- **URL**: https://apify.com/datagrit/football-contract-expiry-radar/changelog.md
- **Full Actor documentation**: https://apify.com/datagrit/football-contract-expiry-radar.md

### 0.3 - Rework after review

- The run status line now counts position, recognised position group and nationality (for example "position present for 85 of 85"), next to the existing counts for contract end date, market value and date of birth. These are the columns the position and nationality filters rely on.
- A source that returns 20 or more players but none with a position, none with a recognised position group, or none with a nationality now fails the run with a clear message instead of delivering rows with empty fields and a "relax the filters" hint.
- When no player is delivered, the reason now follows the real cause: players read but all outside the expiry window point to "widen the expiry window" and name the dates; players removed by filters inside the window point to "relax the filters" with the count.
- Tested by hand against the fixture: position column emptied (exit 91, "none has a position"); flag images removed (exit 91, "none has a nationality"); GB1 with a 6-month window and a goalkeeper filter (reason names the window, 85 players read, 85 outside); GB1 12 months with goalkeeper and 900 million euro value (reason names the filters, 85 removed). Default prefill unchanged: 20 rows, 5.2 s, about 0.007 USD of compute per 1,000 results.

### 0.2 - Rework after review

- An expiry window that ends before it starts (for example "expiresTo" in the past without "expiresFrom") now fails at once with a message naming the two dates and the field to change, instead of reading no lists and blaming the competition code.
- README gained the Related Actors section.
- Tested by hand: "expiresTo" earlier than today fails with "The expiry window is empty: it starts ... but "expiresTo" is ..."; "expiresTo" equal to today still reads the current year; default prefill unchanged.

### 0.1 - Initial release

Reads the Transfermarkt end-of-contract lists per league and calendar year and returns one flat row per player, filtered by expiry window, position group, age, nationality, market value and extension option.

Hand-tested against the live source on 2026-10-01:

- Typical: GB1 + ES1, 12 months, 100 results. Exit 0, 100 rows in 10 s from 4 league-year lists (174 players read). Contract end date read for 174 of 174 rows, market value for 173 of 174, date of birth for 174 of 174.
- Edge: nonexistent competition code ZZ9. The run fails with "None of the requested competitions could be read from Transfermarkt - not recognised: ZZ9" and nothing is billed. Only-new mode on SLO1 with a cap of 10: the first run delivers 10 players, the second run with the same settings skips those 10 and delivers the next 10.
- No results: GB1, 6 months, goalkeepers with a market value of at least 900 million euros. Exit 0, one status row with `found: false`, status message "No player matched your filters among 85 players read - relax the filters", 0 billed.

Measured on the default prefill (GB1, 20 rows): 7.6 s, 0.5 GB, about 0.001 compute units, about 0.011 USD of compute per 1,000 results.

### 0.4 (2026-10-05, follow-ups after review r3)

- New coverage counter in the run status: "contract option present for X of Y players (column found on N of M lists)". If the "Contract option" column is missing on every list that returned players, the run now fails with a clear error instead of returning rows with an empty option and wrong `extensionOptionType`.
- When no list could be read because of network or HTTP errors, the failure message now leads with the read errors, not with "not recognised: none".
- When the run stops at your spending limit before the first result, the status no longer adds a misleading hint about the window or filters.
- Internal: the expiry window is checked in one place only.
