fix(validate): cap GE floor tax at 5,000,000 GP per item (owner decision A)
Owner decision 2026-08-28: single global GE tax formula for all entries: min(floor(0.02 * sell_price), 5_000_000). Apply the per-item cap inside ge_floor_tax_error(); the margin-tag scope (flipping/merchanting) and the generic-ge non-margin exemption are unchanged. Regression fixtures now cover: - fractional-tax flipping entry must FAIL (kept) - non-margin entry with only ge + processing must PASS (kept) - correctly-floored flipping entry must PASS (kept) - high-value flip above the 5,000,000 GP cap (sell 300,000,000 / buy 294,000,000 / net 1,000,000) must PASS — the old uncapped rule computed 6,000,000 tax, demanded net 0, and rejected this valid entry (new) Closes the governor's attempt-3 blocker on PR !7.
This commit is contained in:
parent
f6675ba543
commit
00bfc358cb
@ -13,15 +13,18 @@ Runs:
|
|||||||
=> WARNING (citation TODO — file a follow-up issue)
|
=> WARNING (citation TODO — file a follow-up issue)
|
||||||
- GE-margin entries (tags include flipping and/or merchanting —
|
- GE-margin entries (tags include flipping and/or merchanting —
|
||||||
the unambiguous margin signals): the 2% GE convenience fee is
|
the unambiguous margin signals): the 2% GE convenience fee is
|
||||||
rounded DOWN to whole coins per item, so net_per_cycle MUST
|
rounded DOWN to whole coins per item and capped at 5,000,000 GP
|
||||||
equal income - floor(0.02*income) - cost. Fractional-tax
|
per item, so net_per_cycle MUST equal
|
||||||
arithmetic (0.98*sell - buy) is an error.
|
income - min(floor(0.02*income), 5_000_000) - cost.
|
||||||
|
Fractional-tax arithmetic (0.98*sell - buy) and uncapped tax
|
||||||
|
(6M on a 300M sale) are errors.
|
||||||
NOTE: the generic `ge` tag alone does NOT gate this rule — a
|
NOTE: the generic `ge` tag alone does NOT gate this rule — a
|
||||||
training/processing/procurement entry may legitimately carry
|
training/processing/procurement entry may legitimately carry
|
||||||
`ge` while its income/cost model has nothing to do with flip tax.
|
`ge` while its income/cost model has nothing to do with flip tax.
|
||||||
- Regression fixtures (--selftest): a fractional-tax flipping
|
- Regression fixtures (--selftest): a fractional-tax flipping
|
||||||
entry must FAIL; a non-margin entry carrying only `ge` plus a
|
entry must FAIL; an above-cap high-value flip with the
|
||||||
non-flip tag must PASS.
|
correctly capped-tax net must PASS; a non-margin entry carrying
|
||||||
|
only `ge` plus a non-flip tag must PASS.
|
||||||
3. Global id uniqueness across the whole codex tree.
|
3. Global id uniqueness across the whole codex tree.
|
||||||
|
|
||||||
Exit codes: 0 = clean (warnings ok), 1 = validation errors.
|
Exit codes: 0 = clean (warnings ok), 1 = validation errors.
|
||||||
@ -69,7 +72,11 @@ def ge_floor_tax_error(rel, data):
|
|||||||
|
|
||||||
Wiki: "The 2% tax rounds down to the nearest whole number, so items
|
Wiki: "The 2% tax rounds down to the nearest whole number, so items
|
||||||
sold for below 50 coins have a tax of less than one coin" — per-item
|
sold for below 50 coins have a tax of less than one coin" — per-item
|
||||||
net MUST use the floored tax, never 0.98*sell - buy.
|
net MUST use the floored tax, never 0.98*sell - buy. The fee is also
|
||||||
|
capped at 5,000,000 GP per item (per-item cap, documented in the
|
||||||
|
flip-candidates best_practice/risk), so tax = min(floor(0.02*sell),
|
||||||
|
5_000_000). Owner decision 2026-08-28: one global formula for all
|
||||||
|
entries (option A), no per-entry tax-model discriminator.
|
||||||
"""
|
"""
|
||||||
tags = {str(t).lower() for t in data.get("tags", [])}
|
tags = {str(t).lower() for t in data.get("tags", [])}
|
||||||
if not tags & MARGIN_TAGS:
|
if not tags & MARGIN_TAGS:
|
||||||
@ -78,13 +85,15 @@ def ge_floor_tax_error(rel, data):
|
|||||||
if not all(k in eco for k in ("income_per_cycle", "cost_per_cycle", "net_per_cycle")):
|
if not all(k in eco for k in ("income_per_cycle", "cost_per_cycle", "net_per_cycle")):
|
||||||
return None
|
return None
|
||||||
sell, buy = eco["income_per_cycle"], eco["cost_per_cycle"]
|
sell, buy = eco["income_per_cycle"], eco["cost_per_cycle"]
|
||||||
tax = math.floor(sell * 0.02)
|
tax = min(math.floor(sell * 0.02), 5_000_000)
|
||||||
expected = sell - tax - buy
|
expected = sell - tax - buy
|
||||||
if eco["net_per_cycle"] != expected:
|
if eco["net_per_cycle"] != expected:
|
||||||
return (
|
return (
|
||||||
f"{rel}: net_per_cycle {eco['net_per_cycle']} != floored-tax model "
|
f"{rel}: net_per_cycle {eco['net_per_cycle']} != capped floored-tax "
|
||||||
f"{expected} (sell {sell} - floor({sell}*0.02)={tax} - buy {buy}); "
|
f"model {expected} (sell {sell} - min(floor({sell}*0.02), 5_000_000)"
|
||||||
f"GE tax rounds down per item — fractional 0.98*sell - buy is invalid")
|
f"={tax} - buy {buy}); "
|
||||||
|
f"GE tax rounds down per item and is capped at 5,000,000 GP per item "
|
||||||
|
f"— fractional 0.98*sell - buy and uncapped tax are invalid")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -101,6 +110,13 @@ def run_selftest(verbose=False):
|
|||||||
{"income_per_cycle": 5000, "cost_per_cycle": 3000, "net_per_cycle": 2000}, False),
|
{"income_per_cycle": 5000, "cost_per_cycle": 3000, "net_per_cycle": 2000}, False),
|
||||||
("correctly-floored flipping entry", ["flipping", "merchanting"],
|
("correctly-floored flipping entry", ["flipping", "merchanting"],
|
||||||
{"income_per_cycle": 244, "cost_per_cycle": 223, "net_per_cycle": 17}, False),
|
{"income_per_cycle": 244, "cost_per_cycle": 223, "net_per_cycle": 17}, False),
|
||||||
|
# sell 300,000,000 -> uncapped floor tax is 6,000,000; the per-item
|
||||||
|
# cap makes it 5,000,000, so the correct net is 1,000,000. The old
|
||||||
|
# uncapped rule demanded net=0 and rejected this valid entry.
|
||||||
|
("high-value flip above the 5,000,000 GP per-item tax cap",
|
||||||
|
["flipping"],
|
||||||
|
{"income_per_cycle": 300_000_000, "cost_per_cycle": 294_000_000,
|
||||||
|
"net_per_cycle": 1_000_000}, False),
|
||||||
]
|
]
|
||||||
errors = []
|
errors = []
|
||||||
for name, tags, eco, expect_error in fixtures:
|
for name, tags, eco, expect_error in fixtures:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user