feat(trade): settle a trade — in/out reconciliation with a per-card report (#332) - #346
Merged
Conversation
…eport (#332) Settling is the only step that touches the collection. The shape of `src/trade_commit.py` is dictated by ADR 0002 (D3), which asked for one specific operation: *apply this set of in/out moves atomically at the card level, reporting exactly which ones applied*. That is more than a purely local trade needs, and paying for it now is deliberate — a trade's two halves will eventually be committed on two different machines, and #332 already asks for a trade that falls through on some cards mid-session to be representable. So "atomic" means per card, not per trade: * a line that can only move some of its copies moves them and reports the shortfall, rather than failing the whole trade; * one line failing never rolls back the ones that worked; * but the whole commit is a single transaction, so a crash halfway leaves the collection exactly as it was — a half-applied trade is worse than a failed one; * what's left staged is exactly what didn't happen, and "Settle the rest" picks it up later. Outgoing cards are re-resolved by copy kind (printing + finish + condition + language) rather than by the stack they were staged from, so the trade still settles after that stack was merged, split or deleted (D4) — and trading away your plain copy never quietly consumes your foil. Copies are drawn from the smallest matching stack first. Incoming cards get the same intake a manual addition gets, including filing them into a binder or box on the way in. Once a copy has moved it can't be unstaged: `remove_item` trims the line back to what was applied instead of deleting it, so the pool keeps an honest record of what really happened. `add_or_increment` grew `commit=False` so the trade commit can own the transaction — reusing it rather than writing a second copy of the stack-key logic, which is exactly the drift #338 existed to fix. Caught by driving it against the real dev collection: a commit where every chosen line applied but other cards were left staged reported "Trade settled", which is not true. The result now keys its heading on `completed` rather than `ok`, and offers "Settle the rest" whenever anything is left. Migration 0032. 24 new tests; suite 1235 passing at 100% coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxrePqFWyzQTcWaroSk4sH
SonarQube python:S3358 on new code — a nested conditional expression. It was a real readability smell in the one place that decides whether a line applied, partly applied, or failed, so an if/elif chain rather than a suppression. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LxrePqFWyzQTcWaroSk4sH
untraceablez
force-pushed
the
feat/332-trade-commit
branch
from
August 21, 2026 17:13
27960bf to
aa0016f
Compare
untraceablez
force-pushed
the
feat/332-trade-commit
branch
from
August 21, 2026 17:14
aa0016f to
27960bf
Compare
| import sqlalchemy as sa | ||
| from alembic import op | ||
|
|
||
| revision: str = "0032_trade_commit" |
| from alembic import op | ||
|
|
||
| revision: str = "0032_trade_commit" | ||
| down_revision: str | None = "0031_trade_pool" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #332. Second half of the Trade Function milestone; #345 (the pool selector) has merged, so this now targets
maindirectly.Settling is the only step that touches the collection.
Why this is more than "apply the list"
The shape of
src/trade_commit.pycomes from ADR 0002 D3, which asked for one specific operation: apply this set of in/out moves atomically at the card level, reporting exactly which ones applied. That's more than a purely local trade needs, and paying for it now is deliberate — a trade's two halves will eventually be committed on two different machines, and the issue already asks for a trade that falls through on some cards mid-session to be representable.So "atomic" means per card, not per trade:
Reconciliation rules
remove_itemtrims the line back to what was applied rather than deleting it, so the pool keeps an honest record of what really happened.add_or_incrementgrewcommit=Falseso the trade commit can own the transaction — reusing it rather than writing a second copy of the stack-key logic, which is exactly the drift #338 existed to fix.Caught by running it, not by the tests
Driving a real partial commit against the dev collection produced a green "Trade settled" banner while two cards were still staged. Every chosen line had applied, so
okwas true — but the trade wasn't done. The heading now keys oncompleted, distinguishes "Partly settled — N problem(s)" from "Cards moved — the rest of this trade is still staged", and offers Settle the rest whenever anything is outstanding.Verification
ruff check src testsclean;mkdocs build --strictclean.1 card(s) left your collection+still staged. The follow-up review page then offered only the one outstanding line. Dev collection restored to its exact prior state afterwards.Sonar
The first build failed the quality gate on one new-code smell —
python:S3358, a nested conditional expression in the one place that decides whether a line applied, partly applied or failed. Real readability finding, so it's anif/elifchain now rather than a suppression.Follow-up
With #331 and #332 landed, ADR 0002's decision (3) triggers: write ADR 0003 with the concrete QR payload and endpoints, jointly with scanme (scanme#325 is the consumer placeholder).
🤖 Generated with Claude Code
https://claude.ai/code/session_01LxrePqFWyzQTcWaroSk4sH