Configurable per-property tax by region tag + formula#16
Merged
Conversation
Property tax was a single hardcoded formula per owner (2.5*plots^2 - 6*plots). Replace it with a configurable, tag-matched ruleset assessed per property. For each freehold region an owner holds, the first matching rule (top-to- bottom) supplies a formula; if none match, default-formula applies. Each property's tax is summed into one daily charge per owner. <plots> binds to the owner's total freehold count, and the exemption threshold is configurable. - TaxFormula: recursive-descent evaluator over <plots> (+ - * /, right-assoc ^, unary minus, parens, decimals). Compile-once / evaluate-many. - TagMatch (all/any, case-insensitive) + TaxRule config records; TaxSettings gains rules, default-formula, exempt-plot-threshold. Default taxes.yml ships an example ruleset. - PropertyTaxPolicy: compiles the ruleset (bad formulas logged + dropped, invalid default falls back to the built-in), first-match selection, clamps non-positive/non-finite to zero. - Backend: TitleHeldRegionTag entity + FreeholdContractMapper. selectTitleHeldRegionTags() (FreeholdContract -> Contract -> RealtyRegion LEFT JOIN RegionTag) to enumerate each owner's regions with their tags. No schema migration needed. - Rewrote PropertyTaxListener to enumerate per property, group by owner, apply the configurable threshold + exempt-uuids, emit one summed charge per owner. Tests: TaxFormulaTest (12) + PropertyTaxPolicyTest (9) pass; backend integration test for the new query (runs under Testcontainers in CI). Also fixes a pre-existing compile break in AbstractDatabaseTest (its RealtyBackendImpl name-resolver arg had drifted to an async signature), which was blocking the realty-backend test source set. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous per-property summation charged N Γ formula(N) (cubic) with a quadratic default and a 3-plot exemption β overcharging by 60β235Γ vs the Taxation Act and taxing owners the Act exempts. Align with the Act: - Tax is a single function of plot count, evaluated ONCE per owner (not summed per property), rounded DOWN to the cent. - Default formula = the Act's: 0.25*1.16^<plots> + 0.3*<plots>^2 + 2.5*<plots> - 25. - Exemption threshold = 7 plots (was 3). - Verified against the Act's published figures: $15.01 @ 8 plots, $149.86 @ 20. Tagging is retained as optional local-government overrides (the Act's "unless otherwise provided by Local Governments" clause): a tagged plot is taxed per-property by its rule and excluded from the federal count. Rules ship empty, so by default the Act applies uniformly. PropertyTaxPolicy.taxForRegion β taxForOwner; tests assert once-per-owner evaluation, the exemption, floor rounding, the published figures, and that the parser handles the 1.16^<plots> term. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
What
Replaces the single hardcoded property-tax formula with a configurable formula that follows the Taxation Act, plus an optional tag-matched override mechanism for local governments.
Property tax is a single function of an owner's plot count, evaluated once per owner (not per property), rounded down to the nearest cent, with owners of 7 or fewer plots exempt β exactly as the Act specifies.
taxes.ymlships the Act's formula with overrides disabled:This reproduces the Act's published figures: ~$15.01/day at 8 plots, ~$149.86 at 20, ~$1,267 at 50.
Semantics
default-formulais the federal tax: the owner's total daily tax as a function of<plots>(their freehold plot count). It is evaluated once on the federally-taxed plot count and floored to the cent; owners at/belowexempt-plot-thresholdplots pay nothing.exempt-uuidsare also honoured.rulesare optional local-government overrides (the Act's "unless otherwise provided by Local Governments where the plot is located" clause). A freehold region whose tags match a rule (first match, top-to-bottom) is taxed per-property by that rule and is excluded from the federal count. Rules ship empty, so by default the Act applies uniformly to every plot.<plots>= the owner's total freehold plot count.match.allβ region must carry every listed tag;match.anyβ at least one; empty/omitted = no constraint. Case-insensitive; tag ids are thetag-ids fromregion-tags.yml.How
TaxFormulaβ recursive-descent evaluator over<plots>:+ - * /, right-associative^(incl. a constant base raised to the variable, e.g.1.16^<plots>), unary minus, parens, decimals. Compile-once / evaluate-many; throws on malformed input.TagMatch+TaxRuleconfig records;TaxSettingscarriesrules,default-formula,exempt-plot-threshold.DEFAULT_FORMULAis the Act's formula.PropertyTaxPolicy.taxForOwner(plotTagSets, exemptThreshold)β partitions an owner's plots into federal (no matching rule) and overridden (matched); the federal formula is evaluated once on the federal count above the threshold, per-rule overrides are summed, the total is floored. Bad rule formulas are logged & dropped (that plot falls back to federal); an invalid default falls back to the built-in Act formula; non-finite/non-positive results clamp to zero.TitleHeldRegionTagentity +FreeholdContractMapper.selectTitleHeldRegionTags()(FreeholdContract β Contract β RealtyRegion LEFT JOIN RegionTag). No schema migration needed.PropertyTaxListenerβ enumerates regions, groups by owner, and emits one charge per owner from a singletaxForOwnercall.Testing
PropertyTaxPolicyTestβ asserts the Act's behaviour: evaluated once per owner (and explicitly not the oldNΓvalue), the β€7-plot exemption, floor rounding, the published figures ($15.01 @ 8, $149.86 @ 20), the local-override path, case-insensitive matching, bad-formula fallback, and invalid-default fallback.TaxFormulaTestβ precedence, right-assoc power, and the Act's1.16^<plots>exponential term + full default formula.:realty-paper:testgreen.Incidental
Fixes a pre-existing compile break in
AbstractDatabaseTestβ itsRealtyBackendImplname-resolver arg had drifted to an async (CompletableFuture) signature, which was blocking the entirerealty-backendtest source set.π€ Generated with Claude Code