Track purchase prices so FPL selling prices can be computed #18

Open
opened 2026-08-29 20:49:34 +02:00 by solvreven · 2 comments
Owner

Found 2026-08-29 from a user report: "Tried to sell Maguire, says I have
4.7 million, but I've bought Lewis Hall for 5.0, which was the exact amount of
money I had as well."

The 4.7 is not a rounding slip or a stale cache. It is arithmetic the app
cannot do correctly, because it has no representation of money at all.

What the app actually computes (main.py, /api/squad):

"bank": rules.BUDGET - total       # BUDGET is the constant 100.0

total is the sum of every pick's current price. Reproduced exactly
against the live bootstrap:

sum of current prices for the 14 stored picks   95.3
100.0 - 95.3                                     4.7   <- what the user saw

Why that is not a bank. It is only equal to your money if two things hold:
your squad cost exactly 100.0 to assemble, and no price has moved since. Both
fail the moment a season starts.

What is missing, concretely. data/squad.json is:

{"picks": [496, 8, 357, 427, 397, 426, 154, 557, 165, 379, 301, 346, 31, 305]}

Ids and nothing else. There is nowhere to record what a player cost when it was
bought, so:

  1. Selling price cannot be computed. FPL returns the purchase price plus
    half of any rise, rounded down to 0.1 — never the current price. The app has
    never stored a purchase price, so every sale it models is wrong by up to
    half the player's appreciation.
  2. The bank is derived where it should be recorded. The reported case is
    exactly this: 5.0 in hand, 5.0 spent on Hall, so 0.0 remains. The app
    recomputed 4.7 from today's prices — a number with no causal relationship to
    the user's money, which happens to look plausible.

Note also that the file holds 14 picks, not 15, and contains neither
Maguire nor Hall. So it has drifted from the real team as well, and nothing
detects that: a 14-man squad simply produces a larger "bank".

A plausible number with no provenance is the failure this repo names first.
The 4.7 is well-formed, correctly typed, in the right units, and on screen
beside real data — indistinguishable from a measurement.

Suggested fix, in two independent parts.

Part 1 — take the bank from FPL instead of deriving it. entry/{id}/ needs
no authentication and carries it, verified today:

last_deadline_bank   = 0        # tenths of a million
last_deadline_value  = 1001
last_deadline_total_transfers = 0

The app currently has no idea which FPL entry is the user's — there is no entry
id in config, no /entry/ call anywhere, and no sync route (your_xi and
friends in squad.json can only arrive via a hand-made POST to /api/squad).
Storing an entry id would also make the squad self-syncing rather than
hand-maintained, which is the drift above.

Part 2 — record purchase price per pick. Extend squad.json from a list of ids
to a list of {id, bought_for} and compute selling price by FPL's rule. This
is the only way to get sales right, and it is independent of Part 1: the bank
can be authoritative while sale proceeds are still wrong, and vice versa.

my-team/{id}/ returns selling_price per pick directly and would settle both
at once, but it requires a logged-in session — checked today, returns HTTP
error unauthenticated. Worth noting as the ideal source if a login is ever
added, and worth NOT waiting for.

Until either lands, the bank figure on screen should say what it is — a
budget-minus-current-prices estimate, not your bank. A wrong number labelled as
an estimate is a different object from a wrong number presented as a fact.

Related: #12 (the app reports cache freshness but never its own code version) —
same family, a displayed value whose provenance is not stated.

**Found 2026-08-29 from a user report:** *"Tried to sell Maguire, says I have 4.7 million, but I've bought Lewis Hall for 5.0, which was the exact amount of money I had as well."* The 4.7 is not a rounding slip or a stale cache. It is arithmetic the app cannot do correctly, because it has no representation of money at all. **What the app actually computes** (`main.py`, /api/squad): ```python "bank": rules.BUDGET - total # BUDGET is the constant 100.0 ``` `total` is the sum of every pick's **current** price. Reproduced exactly against the live bootstrap: ``` sum of current prices for the 14 stored picks 95.3 100.0 - 95.3 4.7 <- what the user saw ``` **Why that is not a bank.** It is only equal to your money if two things hold: your squad cost exactly 100.0 to assemble, and no price has moved since. Both fail the moment a season starts. **What is missing, concretely.** `data/squad.json` is: ```json {"picks": [496, 8, 357, 427, 397, 426, 154, 557, 165, 379, 301, 346, 31, 305]} ``` Ids and nothing else. There is nowhere to record what a player cost when it was bought, so: 1. **Selling price cannot be computed.** FPL returns the purchase price plus half of any rise, rounded down to 0.1 — never the current price. The app has never stored a purchase price, so every sale it models is wrong by up to half the player's appreciation. 2. **The bank is derived where it should be recorded.** The reported case is exactly this: 5.0 in hand, 5.0 spent on Hall, so 0.0 remains. The app recomputed 4.7 from today's prices — a number with no causal relationship to the user's money, which happens to look plausible. Note also that the file holds **14 picks, not 15**, and contains neither Maguire nor Hall. So it has drifted from the real team as well, and nothing detects that: a 14-man squad simply produces a larger "bank". **A plausible number with no provenance is the failure this repo names first.** The 4.7 is well-formed, correctly typed, in the right units, and on screen beside real data — indistinguishable from a measurement. **Suggested fix, in two independent parts.** *Part 1 — take the bank from FPL instead of deriving it.* `entry/{id}/` needs no authentication and carries it, verified today: ``` last_deadline_bank = 0 # tenths of a million last_deadline_value = 1001 last_deadline_total_transfers = 0 ``` The app currently has no idea which FPL entry is the user's — there is no entry id in config, no `/entry/` call anywhere, and no sync route (`your_xi` and friends in squad.json can only arrive via a hand-made POST to `/api/squad`). Storing an entry id would also make the squad self-syncing rather than hand-maintained, which is the drift above. *Part 2 — record purchase price per pick.* Extend squad.json from a list of ids to a list of `{id, bought_for}` and compute selling price by FPL's rule. This is the only way to get sales right, and it is independent of Part 1: the bank can be authoritative while sale proceeds are still wrong, and vice versa. `my-team/{id}/` returns `selling_price` per pick directly and would settle both at once, but it requires a logged-in session — checked today, returns HTTP error unauthenticated. Worth noting as the ideal source if a login is ever added, and worth NOT waiting for. **Until either lands, the bank figure on screen should say what it is** — a budget-minus-current-prices estimate, not your bank. A wrong number labelled as an estimate is a different object from a wrong number presented as a fact. Related: #12 (the app reports cache freshness but never its own code version) — same family, a displayed value whose provenance is not stated.
Collaborator

Authoritative bank/selling prices require user-specific state that is not currently available. Please provide or decide:

  1. The FPL entry ID to sync, and whether it may be stored in repository config or must remain local/private.
  2. Whether unauthenticated entry sync is the supported source for bank plus picks, or whether manual squad editing must continue alongside it.
  3. For purchase prices, should the app add a manual bought_for field now, or wait for authenticated my-team/{id}/ access?
  4. How should the existing ID-only data/squad.json be migrated when purchase prices are unknowable: reject it, preserve picks with “selling price unavailable”, or ask once for explicit values?
  5. Should the immediate UI mitigation rename the current figure to “budget minus current prices (estimate)” while authoritative state is unavailable?

The migration policy is especially important: inventing purchase prices would recreate the provenance defect described by this issue.

Authoritative bank/selling prices require user-specific state that is not currently available. Please provide or decide: 1. The FPL entry ID to sync, and whether it may be stored in repository config or must remain local/private. 2. Whether unauthenticated entry sync is the supported source for bank plus picks, or whether manual squad editing must continue alongside it. 3. For purchase prices, should the app add a manual `bought_for` field now, or wait for authenticated `my-team/{id}/` access? 4. How should the existing ID-only `data/squad.json` be migrated when purchase prices are unknowable: reject it, preserve picks with “selling price unavailable”, or ask once for explicit values? 5. Should the immediate UI mitigation rename the current figure to “budget minus current prices (estimate)” while authoritative state is unavailable? The migration policy is especially important: inventing purchase prices would recreate the provenance defect described by this issue.
Author
Owner

Partly implemented on fix/gw2-window-and-provenance (9d9bd4d, pushed just now). Answering the questions above against what exists.

  1. Entry ID6261. It lives in .env as FPL_ENTRY_ID, i.e. local and private, not repository config. backend/web/fpl_team.py reads it; with none configured the sync refuses and says what is missing rather than defaulting.
  2. Unauthenticated entry sync is the supported source, and it is enough for bank + picks. entry/{id}/event/{gw}/picks/ carries the 15 picks, captain, vice and entry_history.bank. Confirmed against the live account: bank is now 0.0 from FPL where the derived figure said 4.7, which is what prompted the report. Manual squad editing still works; the sync merges over the keys it owns and leaves notes/overrides alone.
  3. Purchase prices — still open, and this is the half that is NOT fixed. Selling price needs my-team/{id}/, which requires a login; verified unavailable unauthenticated. No bought_for field has been added, deliberately: inventing one would be a plausible number with no provenance.
  4. Migration — the ID-only data/squad.json is preserved and enriched, not rejected. fetch_squad refuses anything that is not exactly 15 picks, because a short squad silently becomes a larger bank, which is the state the hand-written file had drifted into (14 picks containing neither Maguire nor Hall).
  5. UI wording — done differently from the suggestion. Rather than renaming the figure, /api/squad carries bank_source: "FPL" | "ESTIMATE" and synced_gw, so the screen can say which it is instead of hedging permanently. A derived figure is still labelled as an estimate; a synced one is not.

Also fixed alongside it: validate_squad compared today's prices against the constant 100.0, so a legal synced team came back "budget exceeded: 1003 > 1000". It now takes the squad's own value plus bank.

What is left for this issue: purchase/selling prices only. Suggest the title or scope be narrowed to that, since bank itself is no longer derived.

WHAT THE PUBLIC ENDPOINT CANNOT KNOW, stated because the UI has to: it serves the team as of a deadline that has passed. A transfer made for the next round is not published until that round locks. So during a round it returns the team playing; between rounds it returns the team that just played, not the one being planned. synced_gw is on every response so that distinction is visible rather than inferred.

**Partly implemented on `fix/gw2-window-and-provenance` (`9d9bd4d`, pushed just now).** Answering the questions above against what exists. 1. **Entry ID** — `6261`. It lives in `.env` as `FPL_ENTRY_ID`, i.e. local and private, not repository config. `backend/web/fpl_team.py` reads it; with none configured the sync refuses and says what is missing rather than defaulting. 2. **Unauthenticated entry sync is the supported source, and it is enough for bank + picks.** `entry/{id}/event/{gw}/picks/` carries the 15 picks, captain, vice and `entry_history.bank`. Confirmed against the live account: bank is now `0.0` from FPL where the derived figure said `4.7`, which is what prompted the report. Manual squad editing still works; the sync merges over the keys it owns and leaves notes/overrides alone. 3. **Purchase prices — still open, and this is the half that is NOT fixed.** Selling price needs `my-team/{id}/`, which requires a login; verified unavailable unauthenticated. No `bought_for` field has been added, deliberately: inventing one would be a plausible number with no provenance. 4. **Migration** — the ID-only `data/squad.json` is preserved and enriched, not rejected. `fetch_squad` refuses anything that is not exactly 15 picks, because a short squad silently becomes a *larger* bank, which is the state the hand-written file had drifted into (14 picks containing neither Maguire nor Hall). 5. **UI wording** — done differently from the suggestion. Rather than renaming the figure, `/api/squad` carries `bank_source: "FPL" | "ESTIMATE"` and `synced_gw`, so the screen can say which it is instead of hedging permanently. A derived figure is still labelled as an estimate; a synced one is not. Also fixed alongside it: `validate_squad` compared today's prices against the constant 100.0, so a legal synced team came back "budget exceeded: 1003 > 1000". It now takes the squad's own value plus bank. **What is left for this issue: purchase/selling prices only.** Suggest the title or scope be narrowed to that, since bank itself is no longer derived. WHAT THE PUBLIC ENDPOINT CANNOT KNOW, stated because the UI has to: it serves the team as of a deadline that has **passed**. A transfer made for the next round is not published until that round locks. So during a round it returns the team playing; between rounds it returns the team that just played, not the one being planned. `synced_gw` is on every response so that distinction is visible rather than inferred.
kristofferopsahl changed title from Bank is derived as budget minus current prices, not tracked; selling prices are unknowable to Track purchase prices so FPL selling prices can be computed 2026-08-30 21:25:16 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
solvreven/FPL#18
No description provided.