stamp_duty 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +27 -0
- data/CONTEXT.md +31 -0
- data/README.md +19 -5
- data/lib/stamp_duty/band_selector.rb +1 -1
- data/lib/stamp_duty/residential_calculator.rb +19 -13
- data/lib/stamp_duty/version.rb +1 -1
- data/lib/stamp_duty.rb +12 -0
- data/stamp_duty.gemspec +14 -10
- metadata +34 -55
- data/.travis.yml +0 -4
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 71cfaf89bad2686407f8d31d767f30656f89c390f82fcaf05f53385df1d975d5
|
|
4
|
+
data.tar.gz: cd2f4a70c7409c3c7462f7280de4b192e6fc645a87b5b8679bb1b9ed70d1fcb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 722ef366bd8f96c5ee09fcb843a67ffe939c59c4232aef779f07b57f76a2e3f7d340561e5b5281e2364934afa350acf43c1f48d6fa4ce2eb41c90a349cfabd10
|
|
7
|
+
data.tar.gz: 589f6094772da6ba947131c0fef3e5d7c569cbd9727bc7f4cf9ae7dd06daed5c3e38c8dbe7e6336e47af0d2e575688c5f21f2cd94b571e2cb2a0b80d4aba500d
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- Requires Ruby 3.3 or later.
|
|
8
|
+
- `stamp_duty` is rounded down to the whole pound, matching HMRC.
|
|
9
|
+
- Invalid prices (non-numeric, `nil`, negative, NaN, infinite) raise `ArgumentError`.
|
|
10
|
+
Numeric strings such as `"275000"` are no longer accepted.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Gem failed to load on Ruby 3.x (`uninitialized constant Forwardable`).
|
|
15
|
+
- Calling `calculate` more than once doubled the result.
|
|
16
|
+
- `stamp_duty` returned 0 unless `calculate` had been called first; results are now computed lazily.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Declared `bigdecimal` as a runtime dependency (bundled gem from Ruby 3.4).
|
|
21
|
+
- Updated development dependencies (rake 13, minitest 5.25, simplecov 0.22); dropped mocha and pry.
|
|
22
|
+
- Require MFA for gem pushes (`rubygems_mfa_required`).
|
|
23
|
+
- CI moved from Travis to GitHub Actions.
|
|
24
|
+
|
|
25
|
+
## 0.1.0
|
|
26
|
+
|
|
27
|
+
- Initial release.
|
data/CONTEXT.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# StampDuty
|
|
2
|
+
|
|
3
|
+
Calculates Stamp Duty Land Tax owed on a residential property purchase in England and Northern Ireland at the standard rates.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Stamp Duty**:
|
|
8
|
+
Stamp Duty Land Tax (SDLT) owed on a purchase, rounded down to the whole pound.
|
|
9
|
+
_Avoid_: LBTT, LTT (Scottish and Welsh equivalents, out of scope)
|
|
10
|
+
|
|
11
|
+
**Purchase Price**:
|
|
12
|
+
The amount paid for the property, on which Stamp Duty is charged. Never negative.
|
|
13
|
+
_Avoid_: value, consideration
|
|
14
|
+
|
|
15
|
+
**Band**:
|
|
16
|
+
A slice of the Purchase Price between a lower and optional upper bound, taxed at a single percentage rate.
|
|
17
|
+
_Avoid_: bracket, tier
|
|
18
|
+
|
|
19
|
+
**Band Amount**:
|
|
20
|
+
The tax due on the part of a Purchase Price that falls within one Band.
|
|
21
|
+
|
|
22
|
+
## Relationships
|
|
23
|
+
|
|
24
|
+
- A **Purchase Price** falls into one or more **Bands**, starting from the lowest
|
|
25
|
+
- Each **Band** the price reaches produces one **Band Amount**
|
|
26
|
+
- **Stamp Duty** is the sum of the **Band Amounts**, rounded down to the pound
|
|
27
|
+
|
|
28
|
+
## Flagged ambiguities
|
|
29
|
+
|
|
30
|
+
- "Stamp duty" colloquially covers Scottish LBTT and Welsh LTT; here it means SDLT for England and Northern Ireland only.
|
|
31
|
+
- Surcharges (additional properties, non-residents) and reliefs (first-time buyers) are not modelled.
|
data/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# StampDuty
|
|
2
2
|
|
|
3
|
-
StampDuty is a
|
|
4
|
-
|
|
3
|
+
StampDuty is a Stamp Duty Land Tax (SDLT) calculator for residential property
|
|
4
|
+
in England and Northern Ireland. It uses the standard residential rates and
|
|
5
|
+
doesn't include surcharges (additional properties, non-residents) or reliefs
|
|
6
|
+
(first-time buyers). Scotland (LBTT) and Wales (LTT) are not covered.
|
|
7
|
+
|
|
8
|
+
Requires Ruby 3.3 or later.
|
|
5
9
|
|
|
6
10
|
## Installation
|
|
7
11
|
|
|
@@ -23,12 +27,22 @@ Or install it yourself as:
|
|
|
23
27
|
|
|
24
28
|
```ruby
|
|
25
29
|
calc = StampDuty.for(275000)
|
|
26
|
-
calc.calculate
|
|
27
30
|
|
|
28
|
-
#
|
|
31
|
+
# returns a BigDecimal, rounded down to the whole pound as HMRC does
|
|
29
32
|
calc.stamp_duty.to_s("F") # => 3750.0
|
|
30
33
|
```
|
|
31
34
|
|
|
35
|
+
The price must be a non-negative, finite number (Integer, BigDecimal, Rational
|
|
36
|
+
or Float). Anything else raises `ArgumentError`:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
StampDuty.for("275000") # => ArgumentError
|
|
40
|
+
StampDuty.for(-1) # => ArgumentError
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`calc.calculate` still exists for backwards compatibility, but results are
|
|
44
|
+
computed on first access so calling it is no longer necessary.
|
|
45
|
+
|
|
32
46
|
You can also can get each the amount in each band
|
|
33
47
|
|
|
34
48
|
```ruby
|
|
@@ -62,7 +76,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
62
76
|
|
|
63
77
|
## Contributing
|
|
64
78
|
|
|
65
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
|
79
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/j-dexx/stamp_duty.
|
|
66
80
|
|
|
67
81
|
|
|
68
82
|
## License
|
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
module StampDuty
|
|
2
2
|
class ResidentialCalculator
|
|
3
3
|
|
|
4
|
-
attr_reader :purchase_price, :
|
|
4
|
+
attr_reader :purchase_price, :bands
|
|
5
5
|
|
|
6
6
|
def initialize(purchase_price, bands)
|
|
7
|
-
@purchase_price = purchase_price
|
|
7
|
+
@purchase_price = StampDuty.normalize_price(purchase_price)
|
|
8
8
|
@bands = bands
|
|
9
|
-
@band_amounts = []
|
|
10
9
|
end
|
|
11
10
|
|
|
11
|
+
# Total tax, rounded down to the whole pound as HMRC does.
|
|
12
12
|
def stamp_duty
|
|
13
|
-
band_amounts.map(&:amount).reduce(0, :+)
|
|
13
|
+
@stamp_duty ||= band_amounts.map(&:amount).reduce(BigDecimal(0), :+).round(0, BigDecimal::ROUND_FLOOR)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Band amounts ordered lowest band first.
|
|
17
|
+
def band_amounts
|
|
18
|
+
@band_amounts ||= build_band_amounts
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Kept for backwards compatibility; results are computed lazily.
|
|
16
22
|
def calculate
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
bands.each do |band|
|
|
20
|
-
add_band_amount(band, price)
|
|
21
|
-
price = band.lower_bound
|
|
22
|
-
end
|
|
23
|
-
end
|
|
23
|
+
band_amounts
|
|
24
|
+
self
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
private
|
|
27
28
|
|
|
28
|
-
def
|
|
29
|
-
|
|
29
|
+
def build_band_amounts
|
|
30
|
+
price = purchase_price
|
|
31
|
+
bands.sort_by { |band| -band.lower_bound }.map do |band|
|
|
32
|
+
band_amount = StampDuty::BandAmount.new(price: price, band: band)
|
|
33
|
+
price = band.lower_bound
|
|
34
|
+
band_amount
|
|
35
|
+
end.reverse
|
|
30
36
|
end
|
|
31
37
|
end
|
|
32
38
|
end
|
data/lib/stamp_duty/version.rb
CHANGED
data/lib/stamp_duty.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "bigdecimal"
|
|
2
2
|
require "bigdecimal/util"
|
|
3
|
+
require "forwardable"
|
|
3
4
|
require "stamp_duty/version"
|
|
4
5
|
require "stamp_duty/band"
|
|
5
6
|
require "stamp_duty/band_selector"
|
|
@@ -8,7 +9,18 @@ require "stamp_duty/residential_calculator"
|
|
|
8
9
|
|
|
9
10
|
module StampDuty
|
|
10
11
|
def self.for(price)
|
|
12
|
+
price = normalize_price(price)
|
|
11
13
|
bands = StampDuty::BandSelector.new(price).bands
|
|
12
14
|
StampDuty::ResidentialCalculator.new(price, bands)
|
|
13
15
|
end
|
|
16
|
+
|
|
17
|
+
def self.normalize_price(price)
|
|
18
|
+
unless price.is_a?(Numeric) && price.real?
|
|
19
|
+
raise ArgumentError, "price must be a number, got #{price.inspect}"
|
|
20
|
+
end
|
|
21
|
+
raise ArgumentError, "price must be finite, got #{price.inspect}" unless price.finite?
|
|
22
|
+
raise ArgumentError, "price must not be negative, got #{price.inspect}" if price.negative?
|
|
23
|
+
|
|
24
|
+
price.is_a?(Rational) ? price.to_d(20) : price.to_d
|
|
25
|
+
end
|
|
14
26
|
end
|
data/stamp_duty.gemspec
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# coding: utf-8
|
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
3
|
require 'stamp_duty/version'
|
|
@@ -9,20 +8,25 @@ Gem::Specification.new do |spec|
|
|
|
9
8
|
spec.authors = ["James Doyley"]
|
|
10
9
|
spec.email = ["jdoyley@gmail.com"]
|
|
11
10
|
|
|
12
|
-
spec.summary = %q{ A UK
|
|
11
|
+
spec.summary = %q{ A UK property stamp duty calculator }
|
|
13
12
|
spec.homepage = "https://github.com/j-dexx/stamp_duty"
|
|
14
13
|
spec.license = "MIT"
|
|
15
14
|
|
|
16
|
-
spec.
|
|
15
|
+
spec.metadata = {
|
|
16
|
+
"source_code_uri" => spec.homepage,
|
|
17
|
+
"changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md",
|
|
18
|
+
"rubygems_mfa_required" => "true"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|bin|\.github)/}) }
|
|
17
22
|
spec.bindir = "exe"
|
|
18
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
24
|
spec.require_paths = ["lib"]
|
|
20
|
-
spec.required_ruby_version = "
|
|
25
|
+
spec.required_ruby_version = ">= 3.3"
|
|
26
|
+
|
|
27
|
+
spec.add_dependency "bigdecimal", ">= 3.1", "< 5"
|
|
21
28
|
|
|
22
|
-
spec.add_development_dependency "
|
|
23
|
-
spec.add_development_dependency "
|
|
24
|
-
spec.add_development_dependency "
|
|
25
|
-
spec.add_development_dependency "simplecov", "~> 0.11.2"
|
|
26
|
-
spec.add_development_dependency "mocha", "~> 1.1.0"
|
|
27
|
-
spec.add_development_dependency "pry"
|
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
30
|
+
spec.add_development_dependency "minitest", "~> 5.25"
|
|
31
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
|
28
32
|
end
|
metadata
CHANGED
|
@@ -1,100 +1,78 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: stamp_duty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Doyley
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: bigdecimal
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '1
|
|
20
|
-
|
|
19
|
+
version: '3.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '5'
|
|
23
|
+
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '1
|
|
29
|
+
version: '3.1'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: rake
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
37
|
- - "~>"
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
39
|
+
version: '13.0'
|
|
34
40
|
type: :development
|
|
35
41
|
prerelease: false
|
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
43
|
requirements:
|
|
38
44
|
- - "~>"
|
|
39
45
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
46
|
+
version: '13.0'
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
42
48
|
name: minitest
|
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
|
44
50
|
requirements:
|
|
45
51
|
- - "~>"
|
|
46
52
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '5.
|
|
53
|
+
version: '5.25'
|
|
48
54
|
type: :development
|
|
49
55
|
prerelease: false
|
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
57
|
requirements:
|
|
52
58
|
- - "~>"
|
|
53
59
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '5.
|
|
60
|
+
version: '5.25'
|
|
55
61
|
- !ruby/object:Gem::Dependency
|
|
56
62
|
name: simplecov
|
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
|
58
64
|
requirements:
|
|
59
65
|
- - "~>"
|
|
60
66
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0.
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0.11.2
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: mocha
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: 1.1.0
|
|
67
|
+
version: '0.22'
|
|
76
68
|
type: :development
|
|
77
69
|
prerelease: false
|
|
78
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
71
|
requirements:
|
|
80
72
|
- - "~>"
|
|
81
73
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
83
|
-
|
|
84
|
-
name: pry
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
97
|
-
description:
|
|
74
|
+
version: '0.22'
|
|
75
|
+
description:
|
|
98
76
|
email:
|
|
99
77
|
- jdoyley@gmail.com
|
|
100
78
|
executables: []
|
|
@@ -102,13 +80,12 @@ extensions: []
|
|
|
102
80
|
extra_rdoc_files: []
|
|
103
81
|
files:
|
|
104
82
|
- ".gitignore"
|
|
105
|
-
-
|
|
83
|
+
- CHANGELOG.md
|
|
84
|
+
- CONTEXT.md
|
|
106
85
|
- Gemfile
|
|
107
86
|
- LICENSE.txt
|
|
108
87
|
- README.md
|
|
109
88
|
- Rakefile
|
|
110
|
-
- bin/console
|
|
111
|
-
- bin/setup
|
|
112
89
|
- lib/stamp_duty.rb
|
|
113
90
|
- lib/stamp_duty/band.rb
|
|
114
91
|
- lib/stamp_duty/band_amount.rb
|
|
@@ -119,25 +96,27 @@ files:
|
|
|
119
96
|
homepage: https://github.com/j-dexx/stamp_duty
|
|
120
97
|
licenses:
|
|
121
98
|
- MIT
|
|
122
|
-
metadata:
|
|
123
|
-
|
|
99
|
+
metadata:
|
|
100
|
+
source_code_uri: https://github.com/j-dexx/stamp_duty
|
|
101
|
+
changelog_uri: https://github.com/j-dexx/stamp_duty/blob/master/CHANGELOG.md
|
|
102
|
+
rubygems_mfa_required: 'true'
|
|
103
|
+
post_install_message:
|
|
124
104
|
rdoc_options: []
|
|
125
105
|
require_paths:
|
|
126
106
|
- lib
|
|
127
107
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
108
|
requirements:
|
|
129
|
-
- - "
|
|
109
|
+
- - ">="
|
|
130
110
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '
|
|
111
|
+
version: '3.3'
|
|
132
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
113
|
requirements:
|
|
134
114
|
- - ">="
|
|
135
115
|
- !ruby/object:Gem::Version
|
|
136
116
|
version: '0'
|
|
137
117
|
requirements: []
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
signing_key:
|
|
118
|
+
rubygems_version: 3.5.3
|
|
119
|
+
signing_key:
|
|
141
120
|
specification_version: 4
|
|
142
|
-
summary: A UK
|
|
121
|
+
summary: A UK property stamp duty calculator
|
|
143
122
|
test_files: []
|
data/.travis.yml
DELETED
data/bin/console
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
require "stamp_duty"
|
|
5
|
-
|
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
-
|
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
-
# require "pry"
|
|
11
|
-
# Pry.start
|
|
12
|
-
|
|
13
|
-
require "irb"
|
|
14
|
-
IRB.start
|