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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1147690e21ebe12467c191b40afbd6f29e4c4c90
4
- data.tar.gz: 63fe7bd8fabb23dc64f76c71d030b821b3a7a765
2
+ SHA256:
3
+ metadata.gz: 71cfaf89bad2686407f8d31d767f30656f89c390f82fcaf05f53385df1d975d5
4
+ data.tar.gz: cd2f4a70c7409c3c7462f7280de4b192e6fc645a87b5b8679bb1b9ed70d1fcb8
5
5
  SHA512:
6
- metadata.gz: dfaed9758a2773e05453e368f6d97a244350203973256c2f849acbb6315f802373420690cd025ea68d424f01e3db41ac3e9915fe8ba04b35b7fec8fd3a22611a
7
- data.tar.gz: 26763a41182056a87a64a2fbeeae8187b3341504bad4d74113680f9141d75b91fbaf9c11c8f41c165d5ca318e288d74711a39498ced0ccd30d6464d9ce17ba31
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 stamp duty calculator for the UK property market. Currently only
4
- for residential property, and doesn't include the extra 3% for owning multiple properties
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
- # .to_s("F") as returns a big decimal
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/jdexx/stamp_duty.
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
@@ -25,7 +25,7 @@ module StampDuty
25
25
  end
26
26
 
27
27
  def sorted_band_data
28
- band_data_for_price.sort_by{ |band| band[:lower_band] }.reverse!
28
+ band_data_for_price.sort_by { |band| -band[:lower_bound] }
29
29
  end
30
30
 
31
31
  def band_data_for_price
@@ -1,32 +1,38 @@
1
1
  module StampDuty
2
2
  class ResidentialCalculator
3
3
 
4
- attr_reader :purchase_price, :band_amounts, :bands
4
+ attr_reader :purchase_price, :bands
5
5
 
6
6
  def initialize(purchase_price, bands)
7
- @purchase_price = purchase_price.to_d
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
- price = purchase_price
18
- while price > 0
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 add_band_amount(band, price)
29
- @band_amounts.unshift(StampDuty::BandAmount.new(price: price, band: band))
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
@@ -1,3 +1,3 @@
1
1
  module StampDuty
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
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 proerty stamp duty calculator }
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.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
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 = "~> 2.1"
25
+ spec.required_ruby_version = ">= 3.3"
26
+
27
+ spec.add_dependency "bigdecimal", ">= 3.1", "< 5"
21
28
 
22
- spec.add_development_dependency "bundler", "~> 1.11"
23
- spec.add_development_dependency "rake", "~> 10.0"
24
- spec.add_development_dependency "minitest", "~> 5.0"
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.1.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: 2016-04-27 00:00:00.000000000 Z
11
+ date: 2026-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: bigdecimal
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.11'
20
- type: :development
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.11'
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: '10.0'
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: '10.0'
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.0'
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.0'
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.11.2
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: 1.1.0
83
- - !ruby/object:Gem::Dependency
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
- - ".travis.yml"
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
- post_install_message:
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: '2.1'
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
- rubyforge_project:
139
- rubygems_version: 2.5.1
140
- signing_key:
118
+ rubygems_version: 3.5.3
119
+ signing_key:
141
120
  specification_version: 4
142
- summary: A UK proerty stamp duty calculator
121
+ summary: A UK property stamp duty calculator
143
122
  test_files: []
data/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3.0
4
- before_install: gem install bundler -v 1.11.2
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
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here