en14960 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e392aeaeca04d9c6c7648b5517675cecdd55470c983aa834e7f09cd083cedc6c
4
+ data.tar.gz: 0a60d6ac76ca2bee3463215f0ec2e94c9947b1d881be4d2abdc83f70d07a1784
5
+ SHA512:
6
+ metadata.gz: '0953db3f8d5c86091ff07ac92f9597299cef1ef4482d67fb877b1d3dfc08bd60e92c4fc7396827ad0946f7e4c4bcee4a28bb663d669f403af1b917c1e01b5725'
7
+ data.tar.gz: 023212ec36c17575cc234413fb42812d041dc1df8a393b0e747570e0a890d352876f178a12a6b48b7e7676222eb9b0d382748605c451d3f54fe30eef6d0c24b9
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rspec_status ADDED
@@ -0,0 +1,31 @@
1
+ example_id | status | run_time |
2
+ ----------------------------------------------------- | ------ | --------------- |
3
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:1:1] | passed | 0.00004 seconds |
4
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:1:2] | passed | 0.00006 seconds |
5
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:1:3] | passed | 0.00005 seconds |
6
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:2:1] | passed | 0.00005 seconds |
7
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:2:2] | passed | 0.00004 seconds |
8
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:3:1] | passed | 0.001 seconds |
9
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:3:2] | passed | 0.00043 seconds |
10
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:4:1] | passed | 0.00028 seconds |
11
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:4:2] | passed | 0.00003 seconds |
12
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:4:3] | passed | 0.00002 seconds |
13
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:5:1] | passed | 0.00005 seconds |
14
+ ./spec/calculators/anchor_calculator_spec.rb[1:1:5:2] | passed | 0.00007 seconds |
15
+ ./spec/calculators/anchor_calculator_spec.rb[1:2:1:1] | passed | 0.00002 seconds |
16
+ ./spec/calculators/anchor_calculator_spec.rb[1:2:1:2] | passed | 0.00003 seconds |
17
+ ./spec/calculators/anchor_calculator_spec.rb[1:2:1:3] | passed | 0.00003 seconds |
18
+ ./spec/calculators/anchor_calculator_spec.rb[1:2:1:4] | passed | 0.00003 seconds |
19
+ ./spec/calculators/anchor_calculator_spec.rb[1:2:1:5] | passed | 0.00005 seconds |
20
+ ./spec/calculators/anchor_calculator_spec.rb[1:3:1] | passed | 0.00003 seconds |
21
+ ./spec/calculators/anchor_calculator_spec.rb[1:4:1] | passed | 0.00044 seconds |
22
+ ./spec/en14960_spec.rb[1:1] | passed | 0.00027 seconds |
23
+ ./spec/en14960_spec.rb[1:2:1:1] | passed | 0.00004 seconds |
24
+ ./spec/en14960_spec.rb[1:2:2:1] | passed | 0.00022 seconds |
25
+ ./spec/en14960_spec.rb[1:2:2:2] | passed | 0.00004 seconds |
26
+ ./spec/en14960_spec.rb[1:2:3:1] | passed | 0.00006 seconds |
27
+ ./spec/en14960_spec.rb[1:2:4:1] | passed | 0.00008 seconds |
28
+ ./spec/en14960_spec.rb[1:2:4:2] | passed | 0.00005 seconds |
29
+ ./spec/en14960_spec.rb[1:2:5:1] | passed | 0.00005 seconds |
30
+ ./spec/en14960_spec.rb[1:2:6:1] | passed | 0.00004 seconds |
31
+ ./spec/en14960_spec.rb[1:2:7:1] | passed | 0.00003 seconds |
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-01-28
9
+
10
+ ### Added
11
+ - Initial release of EN14960 gem
12
+ - Anchor calculator for wind load calculations per EN 14960:2019 Annex A
13
+ - Slide calculator for runout distances and wall height requirements
14
+ - User capacity calculator based on age-appropriate space allocation
15
+ - Material validator for rope, fabric, thread, and netting specifications
16
+ - Comprehensive constants from EN 14960:2019 standard
17
+ - Full test suite with RSpec
18
+ - Detailed documentation and usage examples
19
+
20
+ ### Features
21
+ - Framework-agnostic implementation (no Rails dependency)
22
+ - Clean public API with intuitive method names
23
+ - Detailed calculation breakdowns for transparency
24
+ - Support for all height categories defined in EN 14960:2019
25
+ - Configurable parameters for various scenarios
data/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (C) 2025 Chobble.com
2
+
3
+ This program is free software: you can redistribute it and/or modify
4
+ it under the terms of the GNU Affero General Public License as published
5
+ by the Free Software Foundation, either version 3 of the License, or
6
+ (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU Affero General Public License for more details.
12
+
13
+ You should have received a copy of the GNU Affero General Public License
14
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 [Your Name]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # EN14960
2
+
3
+ A Ruby gem providing calculators and validators for BS EN 14960:2019 - the British/European safety standard for inflatable play equipment.
4
+
5
+ ## Features
6
+
7
+ - **Anchor Calculations**: Calculate required ground anchors based on dimensions and wind loads
8
+ - **Slide Safety**: Calculate runout distances and wall height requirements
9
+ - **User Capacity**: Calculate safe user capacity based on play area and user heights
10
+ - **Material Validation**: Validate material specifications against EN 14960 requirements
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'en14960'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle install
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install en14960
27
+
28
+ ## Usage
29
+
30
+ ### Anchor Calculations
31
+
32
+ Calculate required anchors for inflatable play equipment:
33
+
34
+ ```ruby
35
+ result = EN14960.calculate_anchors(length: 5, width: 4, height: 3)
36
+ puts result.value # => 8
37
+ puts result.breakdown
38
+ # => [
39
+ # ["Front/back area", "4m (W) × 3m (H) = 12m²"],
40
+ # ["Sides area", "5m (L) × 3m (H) = 15m²"],
41
+ # ["Front & back anchor counts", "((12.0 × 114.0 * 1.5) ÷ 1600.0 = 2"],
42
+ # ["Left & right anchor counts", "((15.0 × 114.0 * 1.5) ÷ 1600.0 = 2"],
43
+ # ["Required anchors", "(2 + 2) × 2 = 8"]
44
+ # ]
45
+ ```
46
+
47
+ ### Slide Runout Calculations
48
+
49
+ Calculate minimum runout distance for slides:
50
+
51
+ ```ruby
52
+ # Basic runout calculation
53
+ result = EN14960.calculate_slide_runout(2.5)
54
+ puts result.value # => 1.25 (meters)
55
+
56
+ # With stop wall
57
+ result = EN14960.calculate_slide_runout(2.5, has_stop_wall: true)
58
+ puts result.value # => 1.75 (meters)
59
+ ```
60
+
61
+ ### Wall Height Requirements
62
+
63
+ Calculate containing wall requirements for different platform heights:
64
+
65
+ ```ruby
66
+ result = EN14960.calculate_wall_height(2.0, 1.5)
67
+ puts result.value # => 1.5 (meters)
68
+ puts result.breakdown
69
+ # => [
70
+ # ["Height range", "0.6m - 3.0m"],
71
+ # ["Calculation", "1.5m (user height)"]
72
+ # ]
73
+ ```
74
+
75
+ ### User Capacity Calculations
76
+
77
+ Calculate safe user capacity based on play area:
78
+
79
+ ```ruby
80
+ result = EN14960.calculate_user_capacity(10, 8)
81
+ puts result.value
82
+ # => {
83
+ # users_1000mm: 80, # 1.0m tall users
84
+ # users_1200mm: 60, # 1.2m tall users
85
+ # users_1500mm: 48, # 1.5m tall users
86
+ # users_1800mm: 40 # 1.8m tall users
87
+ # }
88
+
89
+ # With maximum user height restriction
90
+ result = EN14960.calculate_user_capacity(10, 8, 1.5, 0)
91
+ puts result.value[:users_1800mm] # => 0 (not allowed)
92
+
93
+ # With obstacles/negative play space
94
+ result = EN14960.calculate_user_capacity(10, 8, nil, 15)
95
+ # Reduces usable area by 15m²
96
+ ```
97
+
98
+ ### Material Validation
99
+
100
+ Validate materials against EN 14960 requirements:
101
+
102
+ ```ruby
103
+ # Rope diameter validation
104
+ EN14960.valid_rope_diameter?(20) # => true (within 18-45mm range)
105
+ EN14960.valid_rope_diameter?(10) # => false (too thin)
106
+
107
+ # Access material standards
108
+ standards = EN14960.material_standards
109
+ puts standards[:fabric][:min_tensile_strength] # => 1850 (Newtons)
110
+ puts standards[:rope][:min_diameter] # => 18 (mm)
111
+ ```
112
+
113
+ ### Direct Module Access
114
+
115
+ You can also use the calculator modules directly:
116
+
117
+ ```ruby
118
+ # Using AnchorCalculator directly
119
+ result = EN14960::Calculators::AnchorCalculator.calculate(
120
+ length: 10,
121
+ width: 8,
122
+ height: 4
123
+ )
124
+
125
+ # Using MaterialValidator directly
126
+ EN14960::Validators::MaterialValidator.valid_fabric_tensile_strength?(2000)
127
+ # => true
128
+ ```
129
+
130
+ ## Constants
131
+
132
+ The gem provides access to all EN 14960:2019 constants:
133
+
134
+ ```ruby
135
+ # Height categories
136
+ EN14960.height_categories
137
+ # => {
138
+ # 1000 => {label: "1.0m (Young children)", max_users: :calculate_by_area},
139
+ # 1200 => {label: "1.2m (Children)", max_users: :calculate_by_area},
140
+ # 1500 => {label: "1.5m (Adolescents)", max_users: :calculate_by_area},
141
+ # 1800 => {label: "1.8m (Adults)", max_users: :calculate_by_area}
142
+ # }
143
+
144
+ # Access all constants
145
+ EN14960::Constants::SLIDE_HEIGHT_THRESHOLDS
146
+ EN14960::Constants::MATERIAL_STANDARDS
147
+ EN14960::Constants::ANCHOR_CALCULATION_CONSTANTS
148
+ ```
149
+
150
+ ## EN 14960:2019 Compliance
151
+
152
+ This gem implements calculations based on BS EN 14960:2019, including:
153
+
154
+ - **Section 4.2.9**: Containment requirements (wall heights)
155
+ - **Section 4.2.11**: Slide runout requirements
156
+ - **Section 4.3**: Number of users calculations
157
+ - **Annex A**: Wind load and anchor calculations
158
+ - **Material specifications**: Fabric, thread, rope, and netting requirements
159
+
160
+ ## Development
161
+
162
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
163
+
164
+ To install this gem onto your local machine, run `bundle exec rake install`.
165
+
166
+ ## Releasing
167
+
168
+ This gem uses GitHub Actions for automated releases to RubyGems.
169
+
170
+ To release a new version:
171
+
172
+ 1. Update the version number in `lib/en14960/version.rb`
173
+ 2. Commit the change: `git commit -am "Bump version to x.y.z"`
174
+ 3. Create a tag: `git tag -a vx.y.z -m "Release version x.y.z"`
175
+ 4. Push the tag: `git push origin vx.y.z`
176
+
177
+ The GitHub Action will automatically:
178
+ - Build the gem
179
+ - Publish it to RubyGems
180
+ - Create a GitHub release with the gem file attached
181
+
182
+ **Note**: You need to set up RubyGems authentication in your GitHub repository settings. Add a repository secret named `GEM_HOST_API_KEY` with your RubyGems API key.
183
+
184
+ ## Contributing
185
+
186
+ Bug reports and pull requests are welcome on GitHub at https://github.com/chobbledotcom/en14960.
187
+
188
+ ## License
189
+
190
+ The gem is available as open source under the terms of the [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.html).
191
+
192
+ ## References
193
+
194
+ - BS EN 14960:2019 - Inflatable play equipment - Safety requirements and test methods
195
+ - EN 71-3 - Safety of toys - Migration of certain elements
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "standard/rake"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: %i[spec standard]
10
+
11
+ desc "Run tests with coverage"
12
+ task :coverage do
13
+ ENV["COVERAGE"] = "true"
14
+ Rake::Task["spec"].invoke
15
+ end
data/en14960.gemspec ADDED
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/en14960/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "en14960"
7
+ spec.version = EN14960::VERSION
8
+ spec.authors = ["Chobble.com"]
9
+ spec.email = ["hello@chobble.com"]
10
+
11
+ spec.summary = "BS EN 14960:2019 safety standard calculators for inflatable play equipment"
12
+ spec.description = "A Ruby gem providing calculators and validators for BS EN 14960:2019 - the safety standard for inflatable play equipment. Includes calculations for anchoring requirements, slide safety, user capacity, and material specifications."
13
+ spec.homepage = "https://github.com/chobbledotcom/en14960"
14
+ spec.license = "AGPL-3.0-or-later"
15
+ spec.required_ruby_version = ">= 3.0.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = spec.homepage
21
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile]) ||
29
+ f.end_with?(".gem")
30
+ end
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ # No runtime dependencies - this gem is standalone
37
+
38
+ # Development dependencies
39
+ spec.add_development_dependency "bundler", "~> 2.0"
40
+ spec.add_development_dependency "rake", "~> 13.0"
41
+ spec.add_development_dependency "rspec", "~> 3.0"
42
+ spec.add_development_dependency "standard", "~> 1.0"
43
+ spec.add_development_dependency "simplecov", "~> 0.21"
44
+
45
+ # For more information and examples about making a new gem, check out our
46
+ # guide at: https://bundler.io/guides/creating_gem.html
47
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+ require_relative "../models/calculator_response"
5
+
6
+ module EN14960
7
+ module Calculators
8
+ module AnchorCalculator
9
+ extend self
10
+
11
+ def calculate_required_anchors(area_m2)
12
+ # EN 14960-1:2019 Annex A (Lines 1175-1210) - Anchor calculation formula
13
+ # Force = 0.5 × Cw × ρ × V² × A
14
+ # Where: Cw = 1.5, ρ = 1.24 kg/m³, V = 11.1 m/s (Lines 1194-1199)
15
+ # Number of anchors = Force / 1600N (Line 450 - each anchor withstands 1600N)
16
+ return 0 if area_m2.nil? || area_m2 <= 0
17
+
18
+ # Pre-calculated: 0.5 × 1.5 × 1.24 × 11.1² ≈ 114
19
+ area_coeff = Constants::ANCHOR_CALCULATION_CONSTANTS[:area_coefficient]
20
+ base_div = Constants::ANCHOR_CALCULATION_CONSTANTS[:base_divisor]
21
+ safety_mult = Constants::ANCHOR_CALCULATION_CONSTANTS[:safety_factor]
22
+
23
+ ((area_m2.to_f * area_coeff * safety_mult) / base_div).ceil
24
+ end
25
+
26
+ def calculate(length:, width:, height:)
27
+ # EN 14960-1:2019 Lines 1175-1210 (Annex A) - Calculate exposed surface areas
28
+ front_area = (width * height).round(1)
29
+ sides_area = (length * height).round(1)
30
+
31
+ required_front = calculate_required_anchors(front_area)
32
+ required_sides = calculate_required_anchors(sides_area)
33
+
34
+ # EN 14960-1:2019 Line 1204 - Calculate for each side
35
+ total_required = (required_front + required_sides) * 2
36
+
37
+ # EN 14960-1:2019 Lines 441-442 - "Each inflatable shall have at least six anchorage points"
38
+ minimum = Constants::ANCHOR_CALCULATION_CONSTANTS[:minimum_anchors]
39
+ total_required = [total_required, minimum].max
40
+
41
+ area_coeff = Constants::ANCHOR_CALCULATION_CONSTANTS[:area_coefficient]
42
+ base_div = Constants::ANCHOR_CALCULATION_CONSTANTS[:base_divisor]
43
+ safety_mult = Constants::ANCHOR_CALCULATION_CONSTANTS[:safety_factor]
44
+
45
+ formula_front = "((#{front_area} × #{area_coeff} * #{safety_mult}) ÷ #{base_div} = #{required_front}"
46
+ formula_sides = "((#{sides_area} × #{area_coeff} * #{safety_mult}) ÷ #{base_div} = #{required_sides}"
47
+
48
+ calculated_total = (required_front + required_sides) * 2
49
+
50
+ breakdown = [
51
+ ["Front/back area", "#{width}m (W) × #{height}m (H) = #{front_area}m²"],
52
+ ["Sides area", "#{length}m (L) × #{height}m (H) = #{sides_area}m²"],
53
+ ["Front & back anchor counts", formula_front],
54
+ ["Left & right anchor counts", formula_sides],
55
+ ["Required anchors", "(#{required_front} + #{required_sides}) × 2 = #{calculated_total}"]
56
+ ]
57
+
58
+ # Add minimum requirement note if applicable
59
+ if calculated_total < minimum
60
+ breakdown << ["EN 14960 minimum", "Minimum #{minimum} anchors required, using #{minimum}"]
61
+ end
62
+
63
+ CalculatorResponse.new(
64
+ value: total_required,
65
+ value_suffix: "",
66
+ breakdown: breakdown
67
+ )
68
+ end
69
+
70
+ def anchor_formula_text
71
+ area_coeff = Constants::ANCHOR_CALCULATION_CONSTANTS[:area_coefficient]
72
+ base_div = Constants::ANCHOR_CALCULATION_CONSTANTS[:base_divisor]
73
+ safety_fact = Constants::ANCHOR_CALCULATION_CONSTANTS[:safety_factor]
74
+ "((Area × #{area_coeff} × #{safety_fact}) ÷ #{base_div})"
75
+ end
76
+
77
+ def anchor_calculation_description
78
+ "Anchors must be calculated based on the play area to ensure adequate ground restraint for wind loads."
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,254 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+ require_relative "../models/calculator_response"
5
+
6
+ module EN14960
7
+ module Calculators
8
+ module SlideCalculator
9
+ extend self
10
+
11
+ # Simple calculation method that returns just the numeric value
12
+ def calculate_runout_value(platform_height, has_stop_wall: false)
13
+ return 0 if platform_height.nil? || platform_height <= 0
14
+
15
+ height_ratio = Constants::RUNOUT_CALCULATION_CONSTANTS[:platform_height_ratio]
16
+ minimum_runout = Constants::RUNOUT_CALCULATION_CONSTANTS[:minimum_runout_meters]
17
+ stop_wall_add = Constants::RUNOUT_CALCULATION_CONSTANTS[:stop_wall_addition]
18
+
19
+ calculated_runout = platform_height * height_ratio
20
+ base_runout = [calculated_runout, minimum_runout].max
21
+
22
+ has_stop_wall ? base_runout + stop_wall_add : base_runout
23
+ end
24
+
25
+ def calculate_required_runout(platform_height, has_stop_wall: false)
26
+ # EN 14960-1:2019 Section 4.2.11 (Lines 930-939) - Runout requirements
27
+ # Line 934-935: The runout distance must be at least half the height of the slide's
28
+ # highest platform (measured from ground level), with an absolute minimum of 300mm
29
+ # Line 936: If a stop-wall is installed at the runout's end, an additional
30
+ # 50cm must be added to the total runout length
31
+ return CalculatorResponse.new(value: 0, value_suffix: "m", breakdown: []) if platform_height.nil? || platform_height <= 0
32
+
33
+ # Get constants
34
+ height_ratio = Constants::RUNOUT_CALCULATION_CONSTANTS[:platform_height_ratio]
35
+ minimum_runout = Constants::RUNOUT_CALCULATION_CONSTANTS[:minimum_runout_meters]
36
+ stop_wall_add = Constants::RUNOUT_CALCULATION_CONSTANTS[:stop_wall_addition]
37
+
38
+ # Calculate values using the shared method
39
+ calculated_runout = platform_height * height_ratio
40
+ base_runout = calculate_runout_value(platform_height, has_stop_wall: false)
41
+ final_runout = calculate_runout_value(platform_height, has_stop_wall: has_stop_wall)
42
+
43
+ # Build breakdown
44
+ breakdown = [
45
+ ["50% calculation", "#{platform_height}m × 0.5 = #{calculated_runout}m"],
46
+ ["Minimum requirement", "#{minimum_runout}m (300mm)"],
47
+ ["Base runout", "Maximum of #{calculated_runout}m and #{minimum_runout}m = #{base_runout}m"]
48
+ ]
49
+
50
+ # Add stop-wall if applicable
51
+ if has_stop_wall
52
+ breakdown << ["Stop-wall addition", "#{base_runout}m + #{stop_wall_add}m = #{final_runout}m"]
53
+ end
54
+
55
+ CalculatorResponse.new(
56
+ value: final_runout,
57
+ value_suffix: "m",
58
+ breakdown: breakdown
59
+ )
60
+ end
61
+
62
+ def meets_height_requirements?(platform_height, user_height, containing_wall_height, has_permanent_roof)
63
+ # EN 14960-1:2019 Section 4.2.9 (Lines 854-887) - Containment requirements
64
+ # Lines 859-860: Containing walls become mandatory for platforms exceeding 0.6m in height
65
+ # Lines 861-862: Platforms between 0.6m and 3.0m need walls at least as tall as the maximum user height
66
+ # Lines 863-864: Platforms between 3.0m and 6.0m require walls at least 1.25 times the maximum user height OR a permanent roof
67
+ # Lines 865-866: Platforms over 6.0m must have both containing walls and a permanent roof structure
68
+ return false if platform_height.nil? || user_height.nil? || containing_wall_height.nil? || has_permanent_roof.nil?
69
+
70
+ enhanced_multiplier = Constants::WALL_HEIGHT_CONSTANTS[:enhanced_height_multiplier]
71
+ thresholds = Constants::SLIDE_HEIGHT_THRESHOLDS
72
+
73
+ case platform_height
74
+ when 0..thresholds[:no_walls_required]
75
+ true # No containing walls required
76
+ when (thresholds[:no_walls_required]..thresholds[:basic_walls])
77
+ containing_wall_height >= user_height
78
+ when (thresholds[:basic_walls]..thresholds[:enhanced_walls])
79
+ # EITHER walls at 1.25x user height OR permanent roof
80
+ has_permanent_roof || containing_wall_height >= (user_height * enhanced_multiplier)
81
+ when (thresholds[:enhanced_walls]..thresholds[:max_safe_height])
82
+ # BOTH containing walls AND permanent roof required
83
+ has_permanent_roof && containing_wall_height >= (user_height * enhanced_multiplier)
84
+ else
85
+ false # Exceeds safe height limits
86
+ end
87
+ end
88
+
89
+ def meets_runout_requirements?(runout_length, platform_height, has_stop_wall: false)
90
+ # EN 14960-1:2019 Section 4.2.11 (Lines 930-939) - Runout requirements
91
+ # Lines 934-935: The runout area must extend at least half the platform's height
92
+ # or 300mm (whichever is greater) to allow users to decelerate safely
93
+ return false if runout_length.nil? || platform_height.nil?
94
+
95
+ required_runout = calculate_runout_value(platform_height, has_stop_wall: has_stop_wall)
96
+ runout_length >= required_runout
97
+ end
98
+
99
+ def slide_runout_formula_text
100
+ ratio_constant = Constants::RUNOUT_CALCULATION_CONSTANTS[:platform_height_ratio]
101
+ height_ratio = (ratio_constant * 100).to_i
102
+ min_constant = Constants::RUNOUT_CALCULATION_CONSTANTS[:minimum_runout_meters]
103
+ min_runout = (min_constant * 1000).to_i
104
+ "#{height_ratio}% of platform height, minimum #{min_runout}mm"
105
+ end
106
+
107
+ def calculate_wall_height_requirements(platform_height, user_height, has_permanent_roof = nil)
108
+ # EN 14960-1:2019 Section 4.2.9 (Lines 854-887) - Containment requirements
109
+ return CalculatorResponse.new(value: 0, value_suffix: "m", breakdown: []) if platform_height.nil? || user_height.nil? || platform_height <= 0 || user_height <= 0
110
+
111
+ # Get requirement details and breakdown
112
+ requirement_details = get_wall_height_requirement_details(platform_height, user_height, has_permanent_roof)
113
+
114
+ # Extract the required wall height from the details
115
+ required_height = extract_required_wall_height(platform_height, user_height)
116
+
117
+ CalculatorResponse.new(
118
+ value: required_height,
119
+ value_suffix: "m",
120
+ breakdown: requirement_details[:breakdown]
121
+ )
122
+ end
123
+
124
+ def get_wall_height_requirement_details(platform_height, user_height, has_permanent_roof)
125
+ thresholds = Constants::SLIDE_HEIGHT_THRESHOLDS
126
+ enhanced_multiplier = Constants::WALL_HEIGHT_CONSTANTS[:enhanced_height_multiplier]
127
+
128
+ case platform_height
129
+ when 0..thresholds[:no_walls_required]
130
+ {
131
+ text: "No containing walls required",
132
+ breakdown: [
133
+ ["Height range", "Under 0.6m"],
134
+ ["Requirement", "No containing walls required"]
135
+ ]
136
+ }
137
+ when (thresholds[:no_walls_required]..thresholds[:basic_walls])
138
+ {
139
+ text: "Walls must be at least #{user_height}m (equal to user height)",
140
+ breakdown: [
141
+ ["Height range", "0.6m - 3.0m"],
142
+ ["Calculation", "#{user_height}m (user height)"]
143
+ ]
144
+ }
145
+ when (thresholds[:basic_walls]..thresholds[:enhanced_walls])
146
+ required_height = (user_height * enhanced_multiplier).round(2)
147
+ breakdown = [
148
+ ["Height range", "3.0m - 6.0m"],
149
+ ["Calculation", "#{user_height}m × #{enhanced_multiplier} = #{required_height}m"],
150
+ ["Alternative requirement", "Permanent roof (can replace heightened walls)"]
151
+ ]
152
+
153
+ # Add roof status if known
154
+ if !has_permanent_roof.nil?
155
+ breakdown << if has_permanent_roof
156
+ ["Permanent roof", "Fitted ✓"]
157
+ else
158
+ ["Permanent roof", "Not fitted ✗"]
159
+ end
160
+ end
161
+
162
+ {
163
+ text: "Walls must be at least #{required_height}m (1.25× user height)",
164
+ breakdown: breakdown
165
+ }
166
+ when (thresholds[:enhanced_walls]..thresholds[:max_safe_height])
167
+ required_height = (user_height * enhanced_multiplier).round(2)
168
+ breakdown = [
169
+ ["Height range", "Over 6.0m"],
170
+ ["Calculation", "#{user_height}m × #{enhanced_multiplier} = #{required_height}m"],
171
+ ["Additional requirement", "Permanent roof required"]
172
+ ]
173
+
174
+ # Add roof status if known
175
+ if !has_permanent_roof.nil?
176
+ breakdown << if has_permanent_roof
177
+ ["Permanent roof", "Required and fitted ✓"]
178
+ else
179
+ ["Permanent roof", "Required but not fitted ✗"]
180
+ end
181
+ end
182
+
183
+ {
184
+ text: "Walls must be at least #{required_height}m + permanent roof required",
185
+ breakdown: breakdown
186
+ }
187
+ else
188
+ {
189
+ text: "Platform height exceeds safe limits",
190
+ breakdown: [
191
+ ["Status", "Platform height exceeds safe limits"]
192
+ ]
193
+ }
194
+ end
195
+ end
196
+
197
+ def requires_permanent_roof?(platform_height)
198
+ # EN 14960-1:2019 Section 4.2.9 (Lines 865-866)
199
+ # Inflatable structures with platforms higher than 6.0m must be equipped
200
+ # with both containing walls and a permanent roof
201
+ threshold = Constants::SLIDE_HEIGHT_THRESHOLDS[:enhanced_walls]
202
+ !platform_height.nil? && platform_height > threshold
203
+ end
204
+
205
+ def wall_height_requirement
206
+ multiplier = Constants::WALL_HEIGHT_CONSTANTS[:enhanced_height_multiplier]
207
+ "Containing walls required #{multiplier} times user height"
208
+ end
209
+
210
+ def slide_calculations
211
+ # EN 14960:2019 - Comprehensive slide safety requirements
212
+ {
213
+ containing_wall_heights: {
214
+ under_600mm: "No containing walls required",
215
+ between_600_3000mm: "Containing walls required of user height",
216
+ between_3000_6000mm: wall_height_requirement,
217
+ over_6000mm: "Both containing walls AND permanent roof required"
218
+ },
219
+ runout_requirements: {
220
+ minimum_length: "50% of highest platform height",
221
+ absolute_minimum: "300mm in any case",
222
+ maximum_inclination: "Not more than 10°",
223
+ stop_wall_addition: "If fitted, adds 50cm to required run-out length",
224
+ wall_height_requirement: "50% of user height on run-out sides"
225
+ },
226
+ safety_factors: {
227
+ first_metre_gradient: "Special requirements for first metre of slope",
228
+ surface_requirements: "Non-slip surface material required",
229
+ edge_protection: "Rounded edges and smooth transitions"
230
+ }
231
+ }
232
+ end
233
+
234
+ private
235
+
236
+ def extract_required_wall_height(platform_height, user_height)
237
+ thresholds = Constants::SLIDE_HEIGHT_THRESHOLDS
238
+ enhanced_multiplier = Constants::WALL_HEIGHT_CONSTANTS[:enhanced_height_multiplier]
239
+
240
+ case platform_height
241
+ when 0..thresholds[:no_walls_required]
242
+ 0 # No walls required
243
+ when (thresholds[:no_walls_required]..thresholds[:basic_walls])
244
+ user_height # Equal to user height
245
+ when (thresholds[:basic_walls]..thresholds[:enhanced_walls]),
246
+ (thresholds[:enhanced_walls]..thresholds[:max_safe_height])
247
+ (user_height * enhanced_multiplier).round(2) # 1.25× user height
248
+ else
249
+ 0 # Exceeds safe limits
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+ require_relative "../models/calculator_response"
5
+
6
+ module EN14960
7
+ module Calculators
8
+ module UserCapacityCalculator
9
+ extend self
10
+
11
+ def calculate(length, width, max_user_height = nil, negative_adjustment_area = 0)
12
+ return default_result if length.nil? || width.nil?
13
+
14
+ total_area = (length * width).round(2)
15
+ negative_adjustment_area = negative_adjustment_area.to_f.abs
16
+ usable_area = [total_area - negative_adjustment_area, 0].max.round(2)
17
+
18
+ breakdown = build_breakdown(length, width, total_area, negative_adjustment_area, usable_area)
19
+ capacities = calculate_capacities(usable_area, max_user_height, breakdown)
20
+
21
+ CalculatorResponse.new(
22
+ value: capacities,
23
+ value_suffix: "",
24
+ breakdown: breakdown
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def build_breakdown(length, width, total_area, negative_adjustment_area, usable_area)
31
+ breakdown = []
32
+ formatted_length = format_number(length)
33
+ formatted_width = format_number(width)
34
+ formatted_total = format_number(total_area)
35
+ formatted_usable = format_number(usable_area)
36
+
37
+ breakdown << ["Total area", "#{formatted_length}m × #{formatted_width}m = #{formatted_total}m²"]
38
+
39
+ if negative_adjustment_area > 0
40
+ formatted_adjustment = format_number(negative_adjustment_area)
41
+ breakdown << ["Obstacles/adjustments", "- #{formatted_adjustment}m²"]
42
+ end
43
+
44
+ breakdown << ["Usable area", "#{formatted_usable}m²"]
45
+ breakdown << ["Capacity calculations", "Based on usable area"]
46
+
47
+ breakdown
48
+ end
49
+
50
+ def calculate_capacities(usable_area, max_user_height, breakdown)
51
+ capacities = {}
52
+
53
+ Constants::AREA_DIVISOR.each do |height_mm, divisor|
54
+ height_m = height_mm / 1000.0
55
+ key = :"users_#{height_mm}mm"
56
+
57
+ if max_user_height.nil? || height_m <= max_user_height
58
+ capacity = (usable_area / divisor).floor
59
+ capacities[key] = capacity
60
+ formatted_area = format_number(usable_area)
61
+ formatted_divisor = format_number(divisor)
62
+ calculation = "#{formatted_area} ÷ #{formatted_divisor} = #{capacity} "
63
+ calculation += (capacity == 1) ? "user" : "users"
64
+ breakdown << ["#{format_number(height_m)}m users", calculation]
65
+ else
66
+ capacities[key] = 0
67
+ breakdown << ["#{format_number(height_m)}m users", "Not allowed (exceeds height limit)"]
68
+ end
69
+ end
70
+
71
+ capacities
72
+ end
73
+
74
+ def default_result
75
+ CalculatorResponse.new(
76
+ value: default_capacity,
77
+ value_suffix: "",
78
+ breakdown: [["Invalid dimensions", ""]]
79
+ )
80
+ end
81
+
82
+ def default_capacity
83
+ {
84
+ users_1000mm: 0,
85
+ users_1200mm: 0,
86
+ users_1500mm: 0,
87
+ users_1800mm: 0
88
+ }
89
+ end
90
+
91
+ def format_number(number)
92
+ # Remove trailing zeros after decimal point
93
+ formatted = sprintf("%.1f", number)
94
+ formatted.sub(/\.0$/, "")
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EN14960
4
+ module Constants
5
+ # Height category constants based on EN 14960:2019
6
+ HEIGHT_CATEGORIES = {
7
+ 1000 => {label: "1.0m (Young children)", max_users: :calculate_by_area},
8
+ 1200 => {label: "1.2m (Children)", max_users: :calculate_by_area},
9
+ 1500 => {label: "1.5m (Adolescents)", max_users: :calculate_by_area},
10
+ 1800 => {label: "1.8m (Adults)", max_users: :calculate_by_area}
11
+ }.freeze
12
+
13
+ # Grounding test weights by user height (EN 14960:2019)
14
+ GROUNDING_TEST_WEIGHTS = {
15
+ height_1000mm: 25, # kg test weight for 1.0m users
16
+ height_1200mm: 35, # kg test weight for 1.2m users
17
+ height_1500mm: 65, # kg test weight for 1.5m users
18
+ height_1800mm: 85 # kg test weight for 1.8m users
19
+ }.freeze
20
+
21
+ # Reinspection interval
22
+ REINSPECTION_INTERVAL_DAYS = 365 # days
23
+
24
+ # Anchor calculation constants from EN 14960-1:2019
25
+ # Line 450: Each anchor must withstand 1600N force
26
+ # Lines 441-442: Minimum 6 anchorage points required
27
+ # Lines 1194-1199: Cw=1.5, ρ=1.24 kg/m³, V=11.1 m/s
28
+ # Pre-calculated: 0.5 × 1.5 × 1.24 × 11.1² ≈ 114
29
+ ANCHOR_CALCULATION_CONSTANTS = {
30
+ area_coefficient: 114.0, # Pre-calculated wind force coefficient
31
+ base_divisor: 1600.0, # Force per anchor in Newtons (Line 450)
32
+ safety_factor: 1.5, # Safety factor multiplier
33
+ minimum_anchors: 6 # Minimum required anchors (Lines 441-442)
34
+ }.freeze
35
+
36
+ # Slide safety thresholds
37
+ SLIDE_HEIGHT_THRESHOLDS = {
38
+ no_walls_required: 0.6, # Under 600mm
39
+ basic_walls: 3.0, # 600mm - 3000mm
40
+ enhanced_walls: 6.0, # 3000mm - 6000mm
41
+ max_safe_height: 8.0 # Maximum recommended height
42
+ }.freeze
43
+
44
+ # Slide runout calculation constants (EN 14960:2019)
45
+ RUNOUT_CALCULATION_CONSTANTS = {
46
+ platform_height_ratio: 0.5, # 50% of platform height
47
+ minimum_runout_meters: 0.3, # Absolute minimum 300mm (0.3m)
48
+ stop_wall_addition: 0.5 # 50cm addition when stop-wall fitted (Line 936)
49
+ }.freeze
50
+
51
+ # Wall height calculation constants (EN 14960:2019)
52
+ WALL_HEIGHT_CONSTANTS = {
53
+ enhanced_height_multiplier: 1.25 # 1.25× multiplier for enhanced walls
54
+ }.freeze
55
+
56
+ # EN 14960-1:2019 Section 4.3 (Lines 940-961) - Number of users
57
+ # Note: EN 14960 doesn't specify exact calculation formulas, only factors to consider:
58
+ # - Height of user (Line 946)
59
+ # - Size of playing area (Line 954)
60
+ # - Type of activity (Line 956)
61
+ # The calculation below is industry standard practice, not from EN 14960
62
+ AREA_DIVISOR = {
63
+ 1000 => 1.0, # 1 user per m² for 1.0m height
64
+ 1200 => 1.33, # 0.75 users per m² for 1.2m height
65
+ 1500 => 1.66, # 0.60 users per m² for 1.5m height
66
+ 1800 => 2.0 # 0.5 users per m² for 1.8m height
67
+ }.freeze
68
+
69
+ # Material safety standards (EN 14960:2019 & EN 71-3)
70
+ MATERIAL_STANDARDS = {
71
+ fabric: {
72
+ min_tensile_strength: 1850, # Newtons minimum
73
+ min_tear_strength: 350, # Newtons minimum
74
+ fire_standard: "EN 71-3" # Fire retardancy standard
75
+ },
76
+ thread: {
77
+ min_tensile_strength: 88 # Newtons minimum
78
+ },
79
+ rope: {
80
+ min_diameter: 18, # mm minimum
81
+ max_diameter: 45, # mm maximum
82
+ max_swing_percentage: 20 # % maximum swing
83
+ },
84
+ netting: {
85
+ max_vertical_mesh: 30, # mm maximum for >1m height
86
+ max_roof_mesh: 8 # mm maximum
87
+ }
88
+ }.freeze
89
+ end
90
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EN14960
4
+ # Data class for calculator responses
5
+ # Provides a consistent structure for all calculator results
6
+ class CalculatorResponse < Struct.new(:value, :value_suffix, :breakdown, keyword_init: true)
7
+ def initialize(value:, value_suffix: "", breakdown: [])
8
+ super
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ value: value,
14
+ value_suffix: value_suffix,
15
+ breakdown: breakdown
16
+ }
17
+ end
18
+
19
+ alias_method :as_json, :to_h
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+
5
+ module EN14960
6
+ module Validators
7
+ module MaterialValidator
8
+ extend self
9
+
10
+ def valid_rope_diameter?(diameter_mm)
11
+ # EN 14960:2019 - Rope diameter range prevents finger entrapment while
12
+ # ensuring adequate grip and structural strength
13
+ return false if diameter_mm.nil?
14
+
15
+ min_diameter = Constants::MATERIAL_STANDARDS[:rope][:min_diameter]
16
+ max_diameter = Constants::MATERIAL_STANDARDS[:rope][:max_diameter]
17
+ diameter_mm.between?(min_diameter, max_diameter)
18
+ end
19
+
20
+ def fabric_tensile_requirement
21
+ fabric_standards = Constants::MATERIAL_STANDARDS[:fabric]
22
+ "#{fabric_standards[:min_tensile_strength]} Newtons minimum"
23
+ end
24
+
25
+ def fabric_tear_requirement
26
+ fabric_standards = Constants::MATERIAL_STANDARDS[:fabric]
27
+ "#{fabric_standards[:min_tear_strength]} Newtons minimum"
28
+ end
29
+
30
+ # Additional validation methods
31
+ def valid_fabric_tensile_strength?(strength_n)
32
+ return false if strength_n.nil?
33
+ strength_n >= Constants::MATERIAL_STANDARDS[:fabric][:min_tensile_strength]
34
+ end
35
+
36
+ def valid_fabric_tear_strength?(strength_n)
37
+ return false if strength_n.nil?
38
+ strength_n >= Constants::MATERIAL_STANDARDS[:fabric][:min_tear_strength]
39
+ end
40
+
41
+ def valid_thread_tensile_strength?(strength_n)
42
+ return false if strength_n.nil?
43
+ strength_n >= Constants::MATERIAL_STANDARDS[:thread][:min_tensile_strength]
44
+ end
45
+
46
+ def valid_netting_mesh?(mesh_mm, is_roof: false)
47
+ return false if mesh_mm.nil?
48
+
49
+ max_mesh = is_roof ?
50
+ Constants::MATERIAL_STANDARDS[:netting][:max_roof_mesh] :
51
+ Constants::MATERIAL_STANDARDS[:netting][:max_vertical_mesh]
52
+
53
+ mesh_mm <= max_mesh
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EN14960
4
+ VERSION = "0.1.0"
5
+ end
data/lib/en14960.rb ADDED
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "en14960/version"
4
+ require_relative "en14960/models/calculator_response"
5
+ require_relative "en14960/constants"
6
+ require_relative "en14960/calculators/anchor_calculator"
7
+ require_relative "en14960/calculators/slide_calculator"
8
+ require_relative "en14960/calculators/user_capacity_calculator"
9
+ require_relative "en14960/validators/material_validator"
10
+
11
+ # EN14960 provides calculators and validators for BS EN 14960:2019
12
+ # the safety standard for inflatable play equipment
13
+ module EN14960
14
+ class Error < StandardError; end
15
+
16
+ # Public API methods for easy access to calculators
17
+ class << self
18
+ # Calculate required anchors for inflatable play equipment
19
+ # @param length [Float] Length in meters
20
+ # @param width [Float] Width in meters
21
+ # @param height [Float] Height in meters
22
+ # @return [CalculatorResponse] Response with anchor count and breakdown
23
+ def calculate_anchors(length:, width:, height:)
24
+ Calculators::AnchorCalculator.calculate(length: length, width: width, height: height)
25
+ end
26
+
27
+ # Calculate required slide runout distance
28
+ # @param platform_height [Float] Platform height in meters
29
+ # @param has_stop_wall [Boolean] Whether a stop wall is fitted
30
+ # @return [CalculatorResponse] Response with runout distance and breakdown
31
+ def calculate_slide_runout(platform_height, has_stop_wall: false)
32
+ Calculators::SlideCalculator.calculate_required_runout(platform_height, has_stop_wall: has_stop_wall)
33
+ end
34
+
35
+ # Calculate wall height requirements for slides
36
+ # @param platform_height [Float] Platform height in meters
37
+ # @param user_height [Float] Maximum user height in meters
38
+ # @param has_permanent_roof [Boolean] Whether unit has permanent roof
39
+ # @return [CalculatorResponse] Response with wall height requirements
40
+ def calculate_wall_height(platform_height, user_height, has_permanent_roof = nil)
41
+ Calculators::SlideCalculator.calculate_wall_height_requirements(
42
+ platform_height,
43
+ user_height,
44
+ has_permanent_roof
45
+ )
46
+ end
47
+
48
+ # Calculate user capacity based on play area
49
+ # @param length [Float] Length in meters
50
+ # @param width [Float] Width in meters
51
+ # @param max_user_height [Float, nil] Maximum allowed user height
52
+ # @param negative_adjustment_area [Float] Area to subtract for obstacles
53
+ # @return [CalculatorResponse] Response with capacity by user height
54
+ def calculate_user_capacity(length, width, max_user_height = nil, negative_adjustment_area = 0)
55
+ Calculators::UserCapacityCalculator.calculate(
56
+ length,
57
+ width,
58
+ max_user_height,
59
+ negative_adjustment_area
60
+ )
61
+ end
62
+
63
+ # Check if rope diameter meets safety requirements
64
+ # @param diameter_mm [Float] Rope diameter in millimeters
65
+ # @return [Boolean] Whether diameter is within safe range
66
+ def valid_rope_diameter?(diameter_mm)
67
+ Validators::MaterialValidator.valid_rope_diameter?(diameter_mm)
68
+ end
69
+
70
+ # Get height categories defined by EN 14960:2019
71
+ # @return [Hash] Height categories with labels and requirements
72
+ def height_categories
73
+ Constants::HEIGHT_CATEGORIES
74
+ end
75
+
76
+ # Get material standards defined by EN 14960:2019
77
+ # @return [Hash] Material requirements for fabrics, threads, ropes, and netting
78
+ def material_standards
79
+ Constants::MATERIAL_STANDARDS
80
+ end
81
+ end
82
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: en14960
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chobble.com
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: standard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.21'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.21'
83
+ description: A Ruby gem providing calculators and validators for BS EN 14960:2019
84
+ - the safety standard for inflatable play equipment. Includes calculations for anchoring
85
+ requirements, slide safety, user capacity, and material specifications.
86
+ email:
87
+ - hello@chobble.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".rspec"
93
+ - ".rspec_status"
94
+ - CHANGELOG.md
95
+ - LICENSE
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - en14960.gemspec
100
+ - lib/en14960.rb
101
+ - lib/en14960/calculators/anchor_calculator.rb
102
+ - lib/en14960/calculators/slide_calculator.rb
103
+ - lib/en14960/calculators/user_capacity_calculator.rb
104
+ - lib/en14960/constants.rb
105
+ - lib/en14960/models/calculator_response.rb
106
+ - lib/en14960/validators/material_validator.rb
107
+ - lib/en14960/version.rb
108
+ homepage: https://github.com/chobbledotcom/en14960
109
+ licenses:
110
+ - AGPL-3.0-or-later
111
+ metadata:
112
+ allowed_push_host: https://rubygems.org
113
+ homepage_uri: https://github.com/chobbledotcom/en14960
114
+ source_code_uri: https://github.com/chobbledotcom/en14960
115
+ changelog_uri: https://github.com/chobbledotcom/en14960/blob/main/CHANGELOG.md
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: 3.0.0
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubygems_version: 3.4.19
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: BS EN 14960:2019 safety standard calculators for inflatable play equipment
135
+ test_files: []